Skip to content

C builds a segment of any width: continuations take wide frames, joins and closures pack the words past WIDE (#1069) - #1958

Open
Lorenzobattistela wants to merge 6 commits into
mainfrom
wide-spill
Open

Lorenzobattistela wants to merge 6 commits into
mainfrom
wide-spill

Conversation

@Lorenzobattistela

Copy link
Copy Markdown
Collaborator

Fixes #1069.

A program the interpreter and JS run could fail bend -o with an arity over 247 whenever a segment kept more than 247 words live: a record built field by field with calls, a list literal of calls, values live across a fork, a closure's captures. Since 2.0.39 a real program hits it too (hapsland, reported on #1069): #1806 lets command_selected borrow command instead of moving it, so NativeRun.command_selected$k1063 keeps it live and goes from 243 to 254 words. #1806 is right; the limit is the problem, and that program has 27 segments between 200 and 247 words.

The 247 limit is the register bank's, but only some segments enter through it:

  • A continuation reads its live words from the stack (seg_take, WL_ARGS); only its results ride registers, and the bank is already sized without it. It only needed a wider arity: Fid takes u16 fields when some arity passes 255, u8 otherwise, so other builds keep their table. On Metal all four fields must share one width: a u16 ar beside u8 fields leaves the device lane with "frontier drained" even in small programs.
  • A fork join and a closure enter through registers (FID_ENTER's WL_LOAD, Clo~apply). seg_pack moves their live words past WIDE into one ~pack node of 2^c words, a constructor with its own CID, so a dropped closure's term_drop frees it and its fields. seg_open reads it back into locals at the segment's entry and frees it. Both join paths, parallel and sequential, pack the same tail. Words of any width pack, which covers the multiword captures fix(compiler): box wide continuation and closure captures #1788 boxed.
  • term_drop reads 64 | (n - 240) only for constructors: a wide task or closure now exists.

Programs under the limit emit the same C as before, apart from the Fid typedef's position and that term_drop line (checked on 73 tests in tests/run and tests/reg).

Tests (tests/reg), each failing to build on main and passing on interpreter, JS and C at 1 and 4 threads:

  • wide_frame: a 300-String record built by calls (continuations).
  • wide_fork_join, wide_fork_join_gpu: 300 U32 live across a fork, on the host and under !.
  • wide_closure_pack: closures over 300 Strings, applied and dropped.
  • wide_capture_calls, wide_capture_closure: from fix(compiler): box wide continuation and closure captures #1788 (multiword captures).

What stays:

Related: #1788 (multiword captures, superseded by this), #1494 (an earlier spill through the evaluator stack, closed).

Gates on 9d514f3: test 1789 / 1789, perf 124 / 124, ping 81 / 81, safe 1144 agree and 0 false reject.

🤖 Generated with Claude Code

@YidaWeng

YidaWeng commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Verified against current main (00d29f44): all six new tests pass --check-only, interpreter, C and JS; the GPU test falls back to CPU here (no CUDA toolkit), so the device lane still needs a cluster run.

The regression tests are meaningful: on current main, wide_frame.bend and wide_fork_join.bend fail -o with Error: an arity over 247; this PR fixes both.

Blocker: repo-gate fails. Merged with current main, comp.ts is 66,572 ttok, 572 over the 66,000 cap. The PR's base is 9d514f3b, so the reported gate runs are stale.

Please:

  1. Reduce comp.ts by at least 572 ttok by simplifying the change, not by moving code out.
  2. Rerun the cluster gates on current main.
  3. Check the seg_pack case where the live set is empty or res > WIDE.
  4. Update the error text that still says "arity over WIDE" even though frames now allow 65535.
  5. Report the GPU lane separately.

Happy to help with a token-diet patch if useful.

Lorenzobattistela and others added 3 commits November 3, 2026 14:19
…; a continuation's frame takes any width

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…past WIDE

wide_capture_calls and wide_capture_closure come from #1788.

Co-authored-by: Sebastian Griego <177274079+sebastian-griego@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… loop

A pack rounded to 2^cls_fit(len+1) and filled the rest with zeros. Only a
node past WIDE needs a power-of-two arity (lay_node), so a pack now takes
its own size up to WIDE and pads only past it. seg_pack no longer mutates
its parameter inside findIndex, seg_open unpacks in a plain loop, and the
join splits its held words from its results with two filters.

The 207 reg/run programs without a pack emit the same C; the wide tests
pass on C (1 and 4 threads) and JS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nicolas-abril and others added 3 commits November 3, 2026 16:12
The 212 reg/run programs emit the same C.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… its limit

- term_drop kept a node's live-field count in 8 bits of its cursor; a
  task can now pass 255 fields, so one with 256+ live fields keeps the
  count in its last word, as an array or a wide constructor does.
- WL_ARGS checks WL_ROOM: a continuation's frame is no longer capped at
  247 words, and FID_ENTER and FID_EXIT pushed it unchecked on the device.
- seg_open hands a ~pack node to the segment's spares, so the body's
  first alloc of its class reuses it.
- "an arity over" names the limit passed: 247 for a register segment,
  65535 for a frame, 32768 for a constructor.

Programs without a pack differ only in those runtime lines (212 reg/run
programs); the wide tests pass on C (1 and 4 threads) and JS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only a def's own parameters and one parallel let's results keep the 247
limit now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

C backend: a segment with more than 247 live one-word values fails to compile

3 participants