Skip to content

Reduce compiler report allocation and reuse C generation work - #1649

Open
flip-engineer wants to merge 9 commits into
bendlang:mainfrom
flip-engineer:codex/cli-report-cache-20261017
Open

flip-engineer wants to merge 9 commits into
bendlang:mainfrom
flip-engineer:codex/cli-report-cache-20261017

Conversation

@flip-engineer

@flip-engineer flip-engineer commented Oct 17, 2026 •

Copy link
Copy Markdown

This change reduces allocation while collecting unsafe and foreign-effect references. The higher-term traversal uses the compiler's existing fresh-variable operation and preserves the dependent-reference report.

The normal C and native build paths reuse generated C when the compiler, source closure, foreign inputs, and relevant environment match the stored identity. Foreign-only refresh validates the receipt, manifest structure, and artifact digest before applying one exact source substitution. Invalid or incomplete cache entries trigger ordinary generation. Diagnostic requests execute the compiler to preserve diagnostics.

A changed foreign fragment containing compiler-owned CID/FID transforms triggers generation. Cache authority checks require a private, user-owned directory and reject symlinks throughout its ancestor chain. The manifest and artifact digests validate each reused result.

Structural opening, use, and spine memo entries survive definition boundaries within a compiled book. Definition-specific let entries are evicted at those boundaries, and the set of those entries is cleared when a new book starts. Probe indices remain valid across the book, so this change removes the per-definition probe-table reset. Peak memory for large books still needs measurement.

Remote validation with pinned Bun 1.3.14:

  • The dependent-reference report regression passes.
  • The bounded cache fixture passes 45 checks, including identity changes, diagnostic bypass, foreign refresh, corrupt artifacts and metadata, UTF-8 byte counts, CID edits, permissions, and symlinked ancestors.
  • 85 programs generate byte-identical C with fresh cache directories: 12,263,641 bytes per arm. Five JavaScript backend samples are also identical.
  • Sequential compiles of three books in one process match fresh-process results, including interleaved books.
  • The changed compiler identity produces a new cache entry; both identities show a compile followed by a hit with identical C artifacts.
  • Paired heavy-fixture runs measured approximately 6-13 percent lower generation time. Memory changes varied by fixture and round; boss_3d increased 4.4 and 9.2 percent. The shared host had load 20-26 during those runs.

These measurements cover bounded fixtures. Full repository validation, Main-scale memory behavior, and a full Main cache-hit measurement remain pending.

@flip-engineer

Copy link
Copy Markdown
Author

The foreign refresh path needs one additional correctness check before merge. effect_srcs applies c_ids to foreign C and resolves CID(...) and FID(...) through the compiled namespace. try_refresh substitutes the new raw file bytes. When the previous fragment had no transformed identifiers and the new fragment introduces one, the old raw fragment can match while the replacement skips identifier resolution.

Please add a regression that introduces CID or FID in a foreign-only edit and compares refreshed C with clean generation. A conservative fix can decline refresh when identifier transformation is required. This review finding has been routed to the existing implementation worker; the full Main generator can continue unchanged.

cgen-toolchain-integration added 4 commits October 16, 2026 23:39
try_refresh: decline raw substitution when the changed fragment carries
CID()/FID() call syntax (effect_srcs/c_ids rewrites it on generation).
emit_c_cached: refuse cache dirs that are not owner-private real
directories with clean ancestors (zero cache I/O, rebuild instead).
Fixture: correct MAIN_TS to bend2/main.ts; add I-step (CID decline +
parity) and J-step (open-mode/symlink refusal + parity). 39/39 green.
J asserts empty event logs (not merely no hit), byte-unchanged hostile
dirs, outputs anchored to a separately built trusted-cache binary, plus
an ancestor-symlink refusal case. Authority walks every ancestor
(real dir, never symlink; ownership/privacy at leaf); rename/mount
races remain documented residual, digests gate served bytes. Zero
artifact I/O claim corrected: the check itself only creates-if-missing
and lstats. 45/45 green.
Map the reviewed memo-scope change (baton/cgen-fixedpoint-impl-20261017
fa582b5) onto upstream bend2/comp.ts. OPENS/USES/SPINES are pure over the
frozen book, so memo_gc no longer drops them at every definition; a
pre-opened let's key is fresh and definition-lived, so it is tracked in
OPEN_LET and evicted at each definition boundary instead.

Probe lifetime is part of the mapping. This compiler truncates PROBES to
1 per definition (compile_book), which makes probe indices definition-
local while retained openings hold probe objects and USES values are keyed
by probe index. Porting the retention alone crashes immediately
("undefined is not an object (evaluating 'b.n')" from bind_of when
probe_of resolves a retained Var's index against the truncated table), so
the per-definition truncation is removed and probes live for the compile,
as file_new already resets them. Retention is then sound: an opening's
probes keep their identities and indices for the whole compile.

Bounded evidence (shared host, load 22-26, a full Main generation resident;
paired runs, medians of three, +/-10%):

- 85 upstream programs byte-identical (demos, tests/io, tools/bend-lint),
  12,263,641 C bytes in both modes.
- app_slash_boss_3d byte-identical: 486,784 -> 508,240 KiB peak (+4.4%),
  5.83 -> 5.27 s.
- fp_deep_big 722,392 -> 763,284 KiB (+5.7%), fp_list_big 245,172 ->
  250,132 KiB (+2.0%), fp_clo neutral; all C byte-identical.
- JS backend byte-identical on five inputs.
- Cache: the compiler identity in cache.ts COMPILER_FILES changes, so the
  key changes (6ed054f6... at comp.ts 069aa862... vs 5b55985b... at comp.ts
  1bf8f3dd...); an old entry is not reused, the new one stores and then
  hits (compile -> hit in log.jsonl).

Limits: bounded fixtures only, no Main-scale claim in either direction;
probe memory is now proportionally larger at Main scale, which this
evidence does not measure.
a430d53 evicts pre-opened let keys at definition boundaries, but the Set is
module-global and file_new replaces FL and its memo maps per compile. A
sequential compile in one process could therefore hold the previous book's
last definition until the next memo_gc ran, and a compile that failed
before any memo_gc would hold it indefinitely.

Declare OPEN_LET with the other compile-scoped caches and clear it in
file_new beside PROBES.length = 1, the point where the old memo object is
replaced. memo_gc keeps evicting per definition.

Qualified in one process: three books compiled twice each and then again
after an interleaved second book all produced identical C, matching the
fresh-process CLI hashes (fp_list_big 5592efe9..., fp_clo 2039d187...,
io_hello_world d4e19ab9...).

Bounded re-check after the change, paired base vs mapped, fresh
CGEN_CACHE_DIR per compile: 85/85 upstream programs byte-identical,
12,263,641 C bytes both modes; cache identity moves from key 6ed054f6...
(comp.ts 069aa862...) to key c7159990... (comp.ts 5fa43bdc...), storing
fresh and hitting on the next run, same artifact 5592efe9...

Peak RSS stays inside the shared-host noise band and is not a claim:
boss_3d medians were +4.4% in the first round and +9.2% in the second,
fp_deep +5.7% then -7.2%, fp_list +2.0% then -1.9%. Elapsed improved in
both rounds (boss_3d 5.94 -> 5.18 s this round). No Main-scale result in
either direction, and this is not the fork's 35% RSS improvement.
@flip-engineer flip-engineer changed the title Reduce compiler report allocations and cache proven C output Reduce compiler report allocation and reuse C generation work Oct 17, 2026
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