v3: make the FastC selfhost build 5x faster and fix its generation chain - #28178
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
v3 -selfhost -b fastc v3.vtook 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_keycloned every declared-type key and re-derived each key's C spelling viastring.replaceon 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_namespacerescanned every function key throughfastc_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)Generated C is byte-identical to the previous compiler on the same input.
Generation-chain fixes
The FastC self-host chain (
v4fbuildingv5f) was broken in two pre-existing ways, masked one behind the other:'!${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.fastcdrivernever mirrored the driver's TinyCC compatibility defines (add_v3_tcc_compat_defines), so descendant generations compiled builtin withtcc_backtracecalls on macOS arm64, where TCC's backtrace runtime cannot be linked (unresolved reference to '_tcc_backtrace'). The sameno_backtracedefine the first generation uses is now applied.Verification
v3 → v4f → v5f → v6fcompletes withV_MACOS_V3_NO_FALLBACK=1; generation-2 and generation-3 C outputs are byte-identical (fixed point).-b fastcsmoke test (loop + interpolation) builds and runs correctly.v fmt -verifypasses on both files.🤖 Generated with Claude Code