Skip to content

v3: make the FastC selfhost build 5x faster and fix its generation chain - #28178

Merged
medvednikov merged 1 commit into
masterfrom
v3-fastc-selfhost-perf
Aug 26, 2026
Merged

v3: make the FastC selfhost build 5x faster and fix its generation chain#28178
medvednikov merged 1 commit into
masterfrom
v3-fastc-selfhost-perf

Conversation

@medvednikov

Copy link
Copy Markdown
Member

Summary

v3 -selfhost -b fastc v3.v took 1.68 s and 5.2 GB RSS on one thread — slower and far heavier than the full parallel pipeline (~0.92 s / ~1 GB), despite FastC having no AST, checker, or transformer. Sampling showed nearly all time under two quadratic scans, both fixed by precomputing an index once per program:

  • Parser.semantic_type_key cloned every declared-type key and re-derived each key's C spelling via string.replace on every query (~4 calls per return statement alone). Replaced with a C-spelling → type-key map built once; first key per spelling wins, matching the old first-match scan over the insertion-ordered key list.
  • Parser.temporary_namespace rescanned every function key through fastc_c_function_name_for_key (two sanitize passes per key) per temporary. Only __v_fastc_-prefixed C names can ever collide with a temporary-namespace candidate, so the collision scan now walks a precomputed short list of those names.

Results (M-series macOS, -selfhost -b fastc -nocache)

before after regular C backend
fastc parse+gen 1590 ms 241 ms (parallel pipeline)
total 1682 ms 303 ms ~924 ms
peak RSS 5160 MB 363 MB ~1004 MB

Generated C is byte-identical to the previous compiler on the same input.

Generation-chain fixes

The FastC self-host chain (v4f building v5f) was broken in two pre-existing ways, masked one behind the other:

  1. The array-membership template nested '!${found_name}' inside another interpolation's ${if ...} block. The FastC selfhost parser renders such nested blocks literally, so second-generation compilers emitted a raw !${found_name} into user C (error: '$' undeclared). The predicate is now hoisted into a variable, exactly like the neighboring string/map membership cases already do.
  2. fastcdriver never mirrored the driver's TinyCC compatibility defines (add_v3_tcc_compat_defines), so descendant generations compiled builtin with tcc_backtrace calls on macOS arm64, where TCC's backtrace runtime cannot be linked (unresolved reference to '_tcc_backtrace'). The same no_backtrace define the first generation uses is now applied.

Verification

  • v3 → v4f → v5f → v6f completes with V_MACOS_V3_NO_FALLBACK=1; generation-2 and generation-3 C outputs are byte-identical (fixed point).
  • Pre-change and post-change compilers emit byte-identical C for the same input.
  • Non-selfhost -b fastc smoke test (loop + interpolation) builds and runs correctly.
  • v fmt -verify passes on both files.

🤖 Generated with Claude Code

Sampling `v3 -selfhost -b fastc v3.v` (1.68 s, 5.2 GB RSS, one thread)
showed nearly all time under Parser.semantic_type_key: every query cloned
every declared-type key and re-derived each key's C spelling through
string.replace, making type-key resolution O(declared types) with three
allocations per candidate. temporary_namespace had the same shape - each
temporary rescanned every function key through fastc_c_function_name_for_key
(two sanitize passes per key) although only `__v_fastc_`-prefixed C names
can ever collide with a temporary namespace candidate.

Precompute both once per program: a C-spelling -> declared-type-key index
(first key per spelling wins, matching the old first-match scan) and the
short list of `__v_fastc_`-prefixed function/global C names. Generated C
is byte-identical to the previous compiler on the same input.

  fastc parse+gen   1590 ms -> 241 ms
  fastc total       1682 ms -> 303 ms   (regular C backend: ~924 ms)
  peak RSS          5160 MB -> 363 MB   (regular C backend: ~1004 MB)

Also repair the FastC generation chain, broken in two places:
- The array-membership template nested `'!${found_name}'` inside another
  interpolation's `${if ...}` block; the FastC selfhost parser renders such
  nested blocks literally, so second-generation compilers emitted a raw
  `!${found_name}` into user C (`error: '$' undeclared`). Hoist the
  predicate like the neighboring string/map membership cases already do.
- fastcdriver never mirrored the driver's TinyCC compatibility defines, so
  descendant generations compiled builtin with tcc_backtrace on macOS
  arm64, where TCC's backtrace runtime cannot link. Add the same
  no_backtrace define the first generation uses.

v3 -> v4f -> v5f -> v6f now completes with V_MACOS_V3_NO_FALLBACK=1, and
the generation-2 and generation-3 C outputs are byte-identical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@medvednikov
medvednikov merged commit 4299fdb into master Aug 26, 2026
5 of 15 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