comp.ts back under its cap by removing mechanisms at their root: a global File, one way to read a shared cell, and effects that park themselves - #1281
Draft
nicolas-abril wants to merge 12 commits into
Conversation
nicolas-abril
force-pushed
the
comp-simplify
branch
from
October 2, 2026 19:52
5745591 to
a4c7b0f
Compare
nicolas-abril
force-pushed
the
comp-simplify
branch
4 times, most recently
from
October 3, 2026 18:18
63ace6a to
391d842
Compare
…ds the book and no cache is cleared by hand The caches were module globals cleared per book, so the compiler already held one book at a time; 40 functions still took `book` or `fl` only to reach it. Now FL is the book being compiled and all that is memoized about it: compile_book and js_lib make a new File first, so show_main reads this book's caches rather than the last one's, and FL.memo holds what one def's walk memoizes (memo_gc clears it as a whole). type File lists every field, FL.memo's too. The root segment mints no C id: no name is empty, so its FID_ never collided with anything. Emitted C, JS and .mjs are byte-identical for all 1626 .bend files under tests/, bench/ and demos/. comp.ts: 63980 -> 63502 ttok. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Intr.call meant three things: an array op stays intrinsic in C with no C text, emit_intr and js_expr send it to arr_op, and emit_fold does not rebuild a call with folded arguments. The last was redundant (emit_intr folds each argument as it emits it), so the flag only said 'arr_op lays this out', which C: null now says. Emitted C, JS and .mjs are byte-identical for all 1626 files. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…t_open and emit_put compile_book named a foreign def's parameters, opened its segment and wrote its return by hand, which is what emit_open and emit_put do; the one difference is the continuation, a parameter past the live ones, which emit_open now names k. Emitted C, JS and .mjs are byte-identical for all 1626 files. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…l flag
facts_hot matches an erased Var against the current def's telescope by
name and index; a constructor's own erased binder (AuditHide{-A, value:
A}, 6200daa) unbound as Var(A, i) could pass for the def's parameter, so
a local flag ran through facts_hot and facts_ctr to switch the
match off under such a constructor. ctr_tail now opens each binder as
DUMMY, named ~, which no parameter can match: the collision cannot arise,
for erased and live dependent fields alike, and the flag goes. Emitted C,
JS and .mjs are byte-identical for all 1626 files (seal_var_scope and
seal_clone_heat among them). A constructor's own telescope, which the
teles memo served through tele_unbind, is memoized in FL.ctrs, so a
1000-arm Nat literal match emits in the same time as on main.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…_nat shim goes 942573f made a literal one Lit node and kept the old parser's desugar of a Nat past NAT_LITERAL_MAX, U32.to_nat of its word, so the emitted C and JS stayed the same. Nothing needs it: ctr_adt reads a Nat literal in one step, as it reads U32 and F32 ones, and FOLD_FUEL caps any unrolling. A big literal now counts as constant: 32 C files change, most only from ((u64)300ull) to 300ull; a constant list of big Nats becomes a static image (walkers_test), and calls on big literals fold (literal_rows_cubic, array_map_loop). nat_pattern_deep, which pinned the default arm's one checked sum and reached it through the shim, takes its 300 from U32.to_nat, so down still runs its match: its C again holds nat_chk(e, _305_0 + 300). The 22 changed tests pass interpreted, in JS and in C; the 5 changed benches print their pins in C and JS. JS and .mjs are byte-identical everywhere. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ses a leaked one A JS undefined leaked into the generated C is an undeclared name in a segment, a spin or a table, all of which clang compiles on the host, so the build fails there; the scan only reworded that failure, and had a false positive of its own (effect_undefined_word, whose comment now says so). Emitted C, JS and .mjs are byte-identical for all 1626 files. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… impossible states go - corpus_eval: work_loop returning 0, a seam delivery returning 0 and a pool turn all end when the root is done, so the three exits and their two 'delivery lost' messages are one loop and one check after it. - gpu_make (CUDA) took a NULL path no caller passes; ring_rewind skipped a ring holding more than RING_LEN, which a host push's err_fail and the device's error check after its pass both rule out; run_tail wrapped its one argument in an array that run_loop spread back. Full local suite (gates/test.ts's shards on this Mac, every lane, Metal for the bang tests): every test passes but the two display-dependent gfx tests, as on main. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- term_peek reads a redirect cell with one relaxed 64-bit load (plain on the device), as bendlang#1155 made blk_loc do: the target bits never change, so a torn read still finds them, and a shared node's fields never change either (span_fade only bumps its children). rfc_view's two atomic halves and its acquire go; ctr_take reads the count itself after copying and acquires only when it is the last holder, before it frees the cell and owns the node. blk_loc is term_peek for a shared block. FID_EXIT's continuation fast path stays: without it, code that applies a closure which may fork retires 28% more instructions at 8 threads. Full local suite on every lane: all pass but the two display-dependent gfx tests, as on main. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… IO.OP match keeps its default arm in both lanes, so $FFI goes
C refuses a request reaching a Bend match over IO.OP through the match's
kept default arm (ERR_TAGS); JS threw the request object and io_run's
catch turned it into the fail-stop. mat_ctrs now keeps the default arm
for IO.OP itself, so JS takes the same arm, and io_run's catch only maps
a stack overflow. A foreign op is { $: name, args, kont }, built like
C's constructor; the loop looks its effect up by tag and refuses an
alien request at run time, as C does, so js_lib's load-time registry
check goes with $FFI. request_out_of_band's header says so; its lines are
unchanged. Full local suite on every lane: all pass but the two
display-dependent gfx tests, as on main.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… has one way to run a request The loop could park a request before its effect ran, from a need given at registration (IO_READ, IO_TIME in C; a need function in JS), and only IO.sleep, TCP.accept and UDP.recv_from had one. Each effect already parks itself the other way (io_wait_on in C, io_park_on in JS): accept and recv_from on EAGAIN, so they just try first; sleep now parks on its deadline. So io_eff takes (cid, run), IoEff and io_exec go, and io_step takes a request's fields once for Halt and for an effect. Nothing an effect could do is lost: guide/EFFECTS.md now says an effect that waits parks itself, and how to wait on time alone. Breaking for effects outside Base: a C io_eff with a third argument no longer compiles, and JS ignores one. All 62 C registrations in bend2/effs and tests drop their third argument. effect_need_collision keeps its point (two effect files define the same function name) with the helper as a plain function. All 136 io tests pass on every lane. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…he helper tests stop saying need - file_book takes the root file and returns nothing; compile_book and js_lib bind it once (const fl = FL = file_new(...)) and say fl, where they mixed fl and FL for the same object. - js_lib's comment registered effects with io_eff(CID(k), run, need?), which takes no need now: it says io_eff(CID(k), run). - effect_need_collision(_reverse) and their two .js files become effect_helper_*: with needs gone they pin that two effect sources may define the same function name, and the name says so. Emitted C, JS and .mjs are byte-identical for every file but the renamed tests, which pass on every lane. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… compiled, a Scope is where the emitter writes
File held both what lives for a book (its facts, its output, its caches)
and what lives for a scope (the segment written, the locals' names, the
bindings and spare nodes held, the def), so every { ...fl } copy for a
match arm, closure or fork carried the book's dozen caches, and fl.hot
and FL.hot named the same set. Now file_new builds the File (FL) and
scope_new a Scope, which a nested scope copies; functions read the book
through FL and take a scope (sc) only to write. Six that took the file
only to reach the book (arr_q, brw_of, c_ids, effect_srcs, js_host,
js_marshal) take none. Emitted C, JS and .mjs are byte-identical
for all 1626 files.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
nicolas-abril
force-pushed
the
comp-simplify
branch
from
October 3, 2026 20:27
391d842 to
47d055a
Compare
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.
Context
#1285 brought
comp.tsback under its permanent 64,000 cap, but only just: main is at 63,980 ttok. This PR brings it to 62,798.The cuts are simplifications at the root rather than trimming: each removes a mechanism that existed only because another part behaved a certain way, or a second way of doing one job. Each was traced to the commit that added it, to check the reason no longer holds.
Changes (one commit each)
Fileis global and holds every cache. The caches were module globals cleared per book, so the compiler already held one book at a time, yet 40 functions tookbookorflonly to reach it.FLis now the book being compiled and everything memoized about it;FL.memoholds what one def's walk memoizes.type Filelists every field,FL.memo's too.show_mainnow reads this book's caches, not the previous book's (it ran before the caches were cleared): this restores Initialize compiler caches before building printer layouts #961's fix, which main has lost. Two books compiled in one process now each emit what a fresh compile emits; on main the second either gets the first's stale layout or throws.Intr.callgoes: an intrinsic thatarr_oplays out hasC: null; the flag's third use (blocking a fold rebuild) was redundant.emit_open/emit_put, like any def's.DUMMY, so a constructor's erased binder cannot pass for the def's parameter (6200daa), and thelocalflag threaded throughfacts_hot/facts_ctrgoes.FL.ctrsmemoizes those telescopes.lit_callgoes: a Nat literal past 256 is read like any literal, not asU32.to_nat(word)(942573f's compatibility shim). Literals are capped at 2^32-1 by the parser, so a constant never nears the 2^48 immediate.nat_pattern_deeptakes its 300 fromU32.to_nat, so it still exercises the rebuild it pins.undefinedin the emitted C: a leaked one is an undeclared name, which clang refuses.corpus_evalis one loop with one post-condition, and three guards for states that cannot occur go.term_peek(one relaxed 64-bit load, as A shared Array's redirect is read without a race and without an atomic on the device, once per spin (#975) #1155 madeblk_locdo), andrfc_viewgoes;ctr_takeacquires only as the last holder, before it owns the node. Shared Array redirect lookup races with atomic reference-count updates #975's reproducer under ThreadSanitizer: 0 reports in 10 runs on both trees (and 6 with the read made plain, as a control).$FFIgoes.io_efftakes no need, and the loop runs every request one way.IO.sleepparks on its deadline;TCP.acceptandUDP.recv_fromalready parked themselves on EAGAIN.FLis the File being compiled, and aScope(segment, local names, bindings, spare nodes, def) is where the emitter writes, so a nested arm, closure or fork copies only its scope.Breaking
io_eff(cid, run)takes no need. A third argument is a compile error in C, and JS ignores it. Every waiting effect already had the self-parking form (io_wait_on/io_park_on), so nothing becomes impossible;guide/EFFECTS.mdsays so and shows how to wait on time alone. All 62 C registrations inbend2/effsandtestsare updated.Checks
After the rebase onto main (a3f1782), on an Apple M4:
.bendfiles undertests/,bench/anddemos/, each commit that says its output is byte-identical emits byte-identical C, JS and.mjsagainst its parent. Thelit_callcommit changes the same 32 C files and no JS, as before.gates/test.ts's shard script run on this Mac): 1541/1543. The two failures aregfx_clicksandgfx_window, which need a display.gates/_run.ts): repo 50/50, test 1543/1543, perf 121/124. Rerun back to back with main, alternating, perf scored 124/124 and 123/124 on this branch and 121/124 and 124/124 on main. Every cell over 1.15x, on either tree, was a PAR-GPU time under 0.1 s (mandelbrot, merkle, nbody). ping was skipped because the site repo is not checked out here.Before the rebase, on an Apple M4 against main (fb6f32a):
.mjsbyte-identical for all 1608.bendfiles undertests/,bench/anddemos/; the rest change it as described in their messages.gates/test.ts's shard script run on this Mac; every lane, Metal for the!tests): all pass butgfx_clicksandgfx_window, which need no display and fail on main too.within_busy0.75.FID_EXIT's continuation fast path (87a1e9c) cost 28% more instructions at 8 threads in code that applies a closure which may fork (9.03G -> 11.54G; no bench takes that path), so the fast path stays. The 34 commits that aimed at performance are all preserved or untouched.FID_EXIT's fast path (above), element-wise block copy (10% slower on a block-heavy micro-benchmark), a shared device/host drain loop (Metal compiled the work loop twice), JS Nat matches through==(a host's raw BigInt), droppingfl.consts(b84b004's 8 s compile guard), and two emitter shortcuts that relied on term identity.gpu_makechange are untested) and Linux.🤖 Generated with Claude Code