Skip to content

refactor(foundation): port src/foundation from C to Zig - #164

Merged
ata-sesli merged 1 commit into
mainfrom
refactor/foundation-zig
Nov 11, 2026
Merged

ata-sesli merged 1 commit into
mainfrom
refactor/foundation-zig

Conversation

@ata-sesli

Copy link
Copy Markdown
Member

What does this PR do?

Ports src/foundation from C to Zig, following the pattern of #163. 21 C files become Zig modules in libcbm_zig: arena, compat, compat_fs, compat_regex, compat_thread, diagnostics, dump_verify, file_lock, git_env, limits, log, mem, platform, profile, sha256, slab_alloc, str_intern, str_util, subprocess, system_info and yaml.

The C headers do not change. Each Zig module exports the same symbols, and cbm_zig.zig checks every exported cbm_* function against its header declaration at compile time. OS-specific exports live in nested namespaces (compat.windows, compat_fs.impl, git_env.windows/posix, system_info.linux) that are checked the same way.

The recent close-on-exec and posix_spawn code (compat_fs, subprocess, file_lock; on main as of a592846a) is ported as written. The posix_child_* tests cover it.

Net change: 5,971 lines of C removed; 4,821 lines of Zig in 21 modules plus 53 in interop.zig; 161 lines of C shim and internal header added.

What stays C, and why

  • src/foundation/c_shim.c (new, 127 lines) with log_internal.h (34 lines): the two variadic functions, cbm_arena_sprintf and cbm_log. Zig cannot define a C-variadic function portably, so cbm_log walks its va_list in C and hands each key/value pair to the Zig formatter. The shim also carries the _Static_assert that regex_t fits in cbm_regex_t, because glibc's regex_t is opaque to translate-c.
  • src/foundation/hash_table.c: a thin wrapper that instantiates the vendored Verstable macro template. There is nothing to port without rewriting the hash table itself.
  • Header-only code is unchanged: dyn_array.h macros, the static inline helpers in compat.h and platform.h, win_utf8.h, CBM_SNPRINTF_APPEND and the CBM_PROF_* macros.
  • vmem.c / vmem.h are in no build list on main (dead code) and are untouched.

Behavior differences

  • cbm_mkdir_p (POSIX) with an empty path returns false after the failed mkdir. The C loop read one byte past the copied empty string.
  • cbm_sha256_backend_update with length 0 returns before touching the data pointer on Apple platforms. The digest is the same; the early return keeps the ReleaseSafe test build from trapping on a NULL pointer.
  • yaml: the leading-space count is bounded by the end of the line instead of relying on the terminating NUL. Same result for the NUL-terminated input every caller passes.
  • cbm_str_tolower lowers ASCII only. The C code called tolower(), which is the same in the "C" locale, and nothing in the repo calls setlocale.

Build and audit wiring

  • Makefile.cbm and build.zig drop the C sources (FOUNDATION_SRCS is now hash_table.c + c_shim.c) and list the Zig files and foundation headers in CBM_ZIG_SRCS. cbm_zig_c.h adds the headers the ported code calls.
  • The test-foundation make target is removed. It linked FOUNDATION_SRCS alone, which no longer contains the modules its tests exercise.
  • security-allowlist.txt names compat_fs.zig and subprocess.zig in place of the C files (same six entries; the subprocess fork entry now names runPosix).
  • security-audit.sh's fopen("w") known-safe list names diagnostics.zig and drops log.c, which had no fopen call. No audit count changes.

Verification

Run on macOS arm64 at this branch's commit, through scripts/zova-run-tests.sh with the pinned Zova:

  • Counts equal main (a592846a) for arena 32, hash_table 28, dyn_array 26, str_intern 19, log 16, str_util 65, platform 14, subprocess 24, dump_verify 12, system_info 8, slab_alloc 14, mem 42, security 34, yaml 73, cli 132, zova 39, zova_operations 59, pipeline 276 (13 not run), mcp 176.
  • libcbm_zig.a builds for native, x86_64-linux-gnu, aarch64-linux-gnu, x86_64-windows-gnu and aarch64-macos, each with and without Zova.
  • zig fmt --check, scripts/check-build-sources.sh and scripts/security-audit.sh pass.

Not run:

  • make -f Makefile.cbm test on this branch alone. It was run on the stacked branch (see the follow-up PR): that target builds without Zova and fails 870 tests on main and on the port identically.
  • lint-ci and smoke-test.sh.
  • Any runtime test on Windows or Linux. Those paths are compile-checked only.

Checklist

  • Every commit is signed off (git commit -s)
  • Tests pass locally (make -f Makefile.cbm test): not run on this branch; the suites listed above pass
  • Lint passes (make -f Makefile.cbm lint-ci): not run
  • New behavior is covered by a test: no new behavior; the existing suites cover the ported code

Replace 21 C files in src/foundation with Zig modules in libcbm_zig:
arena, compat, compat_fs, compat_regex, compat_thread, diagnostics,
dump_verify, file_lock, git_env, limits, log, mem, platform, profile,
sha256, slab_alloc, str_intern, str_util, subprocess, system_info and
yaml. The C headers stay the ABI: each Zig module exports the same
symbols, and cbm_zig.zig checks every pub cbm_* export against its
translated header declaration. OS-specific exports live in nested
namespaces (compat.windows, compat_fs.impl, git_env.windows/posix,
system_info.linux) that are checked the same way.

The close-on-exec and posix_spawn code from #77 (compat_fs, subprocess,
file_lock) is ported as written; the posix_child_* tests cover it.

What stays C, and why:

- src/foundation/c_shim.c (new, 127 lines): the two variadic functions,
  cbm_arena_sprintf and cbm_log. Zig cannot define a C-variadic function
  portably, so cbm_log walks its va_list in C and hands each key/value
  pair to the Zig formatter through log_internal.h. It also carries the
  _Static_assert that regex_t fits in cbm_regex_t, because glibc's
  regex_t is opaque to translate-c.
- src/foundation/hash_table.c: a thin wrapper that instantiates the
  vendored Verstable macro template; there is nothing to port without
  rewriting the hash table itself.
- Header-only code is unchanged: dyn_array.h macros, the static inline
  helpers in compat.h and platform.h, win_utf8.h, CBM_SNPRINTF_APPEND
  and the CBM_PROF_* macros.
- vmem.c / vmem.h are in no build list (dead on main) and are untouched.

Behavior differences from the C code:

- cbm_mkdir_p (POSIX) with an empty path returns false after the failed
  mkdir; the C loop read one byte past the copied empty string.
- cbm_sha256_backend_update with len 0 returns before touching data on
  Apple platforms. The C code passed the (possibly NULL) pointer to a
  loop that made no CommonCrypto call for len 0, so the digest is the
  same; the early return keeps the ReleaseSafe test build from trapping
  on the NULL pointer.
- yaml: the leading-space count is bounded by the end of the line
  instead of relying on the terminating NUL. Same result for the
  NUL-terminated input every caller passes.
- cbm_str_tolower lowers ASCII only; the C code called tolower(), which
  is the same thing in the "C" locale (nothing in the repo calls
  setlocale).

Build and audit wiring: Makefile.cbm and build.zig drop the C sources
(FOUNDATION_SRCS is now hash_table.c + c_shim.c) and list the Zig files
and the foundation headers in CBM_ZIG_SRCS; cbm_zig_c.h gains the
platform and foundation headers the ported code calls. The
test-foundation make target is removed: it linked FOUNDATION_SRCS alone,
which no longer contains the modules its tests exercise.
security-allowlist.txt names compat_fs.zig and subprocess.zig in place
of the C files (same six entries; the subprocess fork entry now names
runPosix). security-audit.sh's fopen("w") known-safe list names
diagnostics.zig and drops log.c, which had no fopen call. No audit
count changes.

Net change: 21 Zig modules, +4,821 lines (plus 53 in interop.zig), for
5,971 lines of C removed; 161 lines of C shim and internal header added.

Signed-off-by: ata-sesli <atasesli@protonmail.com>
@ata-sesli
ata-sesli merged commit f02b64d into main Nov 11, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant