Repository navigation
fix: 200-locals shapes and long list literals (#84, #88; builds on #78) - #91
Merged
Merged
Conversation
A datatype rule with six or more premises over another user type compiled a shen.consume helper whose continuation captured 17 or more locals (two per premise variable, plus the assumption and engine temps). mkhandle refused past 16 captures, so the helper was never registered in NativePred and the caller died with "shen.consumeN is undefined". Five premises captured 15 and fit NEWCONT16. Wider handles now use the existing NEWCONTV path. Co-authored-by: pyrex41 <pyrex41@users.noreply.github.com>
An inline freeze-let thawed inside a value-position control form is expanded inside the hoisted KC function with the freeze's own env, but the KC took only the control form's free variables as parameters. A freeze variable the form did not mention itself resolved to a global: (let G (freeze Z) (let Y (if (= X 0) (thaw G) 5) Y)) returned nil for X = 0. kc_params now adds the free variables of every inline record the form thaws (transitively). Kernel codegen is unchanged. The KC hoisting is factored into hoist_kc for reuse. Co-authored-by: pyrex41 <pyrex41@users.noreply.github.com>
ctail emitted one Lua local per let, so a single function with a long
let chain failed to load ("function at line 1 has more than 200 local
variables"). The kernel's pattern-matching KL for a sequent rule spends
about six lets per premise, so a datatype rule with ~30 premises over a
user type failed to load at all.
Each Lua function now tracks the locals it has declared (params, lets,
chain-hoist steps, snapshot captures, fN refs). Past 120, the rest of the
let chain continues in a fresh KC function via a proper tail call
(return KC[i](live vars)). The split is refused while a goto-kind
failure continuation is active (its label must stay in this function)
or when the continuation itself would need 120+ parameters. Kernel
codegen is unchanged; no kernel function reaches the budget.
Co-authored-by: pyrex41 <pyrex41@users.noreply.github.com>
Two limits in the native Prolog translator left NativePred[shen.consumeN]
unset for wide sequent rules, so typechecks walking the rule died with
"shen.consumeN is undefined":
* every let chained a new env table via __index, and from 16 premises
the consume helper nested past LuaJIT's 100-table chain limit ("loop
in gettable"). extend_env collapses the chain every 32 levels into
one table with the same root lookup.
* the generated Lua then exceeded 200 locals in one function. When
loadstring reports that, the predicate is translated again with let
splitting: past 120 locals the rest of a let chain is lifted into a
static (the existing let-bound freeze closure conversion) and called.
Predicates that already loaded keep byte-identical code, so the
split costs nothing on existing hot paths.
Lifted statics and continuations now keep the vec/lock bindings in their
env, so a misuse refuses as it would in the enclosing scope instead of
compiling the name as a symbol. mkhandle and lift_cont share
newcont_ctor (NEWCONT0..16, NEWCONTV past that).
datatype_consume_spec covers 5..37 premises: every consume helper and
the rule translate natively, the last element is unpacked, and misses
still miss. engine_spec pins NEWCONTV's capture layout and undo.
Co-authored-by: pyrex41 <pyrex41@users.noreply.github.com>
A defun whose ordinary Lua does not load (200 locals, 60 upvalues, 250 registers) is recompiled once with captures passed as tables: hoisted bodies and freezes past 24 captures take one table, lambdas closing over more than 40 names become LAMW closures, let chains split at 80 locals whatever the live set, goto failure continuations stay freeze/thaw, flattened call chains reuse one local, and very wide calls pass UNPACK. Curried lambda chains deeper than 32 compile to CURRY in every mode. Code that loads today is unchanged. Co-authored-by: pyrex41 <pyrex41@users.noreply.github.com>
When the split translation still passes a Lua limit, translate once more with wide statics (free vars as one table), CAPREF reads in wide continuations, NEWCONTT handles, CONSL cons chains and lifted functions in one LT table. Predicates that translate today are unchanged. Co-authored-by: pyrex41 <pyrex41@users.noreply.github.com>
Co-authored-by: pyrex41 <pyrex41@users.noreply.github.com>
Merge origin/main into the wide-premise branch. compiler.lua and prims.lua combined cleanly: the string->n fusion and the Unicode primitives sit next to the wide-mode retry. The test runner lists both the new UTF-8 spec and the wide-compile spec. Co-authored-by: pyrex41 <pyrex41@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PR #78's let splitting and wide-mode recompilation already load both shapes from #84 once merged with 43.1 main: a datatype rule whose conclusion pattern nests 16+ deep (one local per sub-term in <name>#type) and a define returning a 200+-element list literal (one flattened call-chain local per element). Add them to wide_compile_spec (16/30/60-deep patterns, typechecked; 200/250/1000-element lists) so they stay fixed. Fixes #84 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
shen.<s-exprs> (reader.kl) recursed once per token without a tail call, so a list literal past ~1550 elements overflowed LuaJIT's fixed ~65500-slot stack, and read-file's trap-error turned the overflow into a bare "reader error near here:". <strcontents>, <alphanums> and <whitespaces> recursed once per character the same way, so (read-from-string "[1 ... 3000]") failed on its 14 KB string literal. prims.lua now installs iterative versions, next to the native comment scanners that fixed the same failure for comments: - shen.<s-exprs> keeps the KL's ordered choice and calls the kernel's single-token rules through F; brackets are an explicit stack. A missing closer falls back to the remaining alternatives at the bracket, as in the KL, and each sequence is folded right to left so cons-form and add-sexpr run (and raise) in the KL's order. - shen.cons-form builds the cons spine front to back. - <strcontents>/<alphanums> join their pieces with one table.concat; <whitespaces> loops. read-file rethrows a Lua stack overflow instead of reporting it as a reader error. test/reader_native_spec.lua compiles the kernel's KL rules under spec. aliases and requires identical values or identical errors on 145 inputs (every .shen file under tests/ and examples/ plus edge cases), and reads 1600-3000-element lists, a 14 KB string and 3000-deep nesting. Lists past ~3400 elements still overflow, now in the tree walkers after the reader (shen.find-types, shen.process-applications), which see the literal as a cons expression nested once per element. Fixes #88 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 18, 2026
…ls-reader # Conflicts: # CHANGELOG.md
…ls-reader # Conflicts: # CHANGELOG.md
pyrex41
added a commit
that referenced
this pull request
Oct 19, 2026
Resolves compiler.lua against #91: hoist_kc takes the counting mode (inherit for value-position control forms, own for a split let chain), try_split_let returns ret(hoist_kc(..., true)), and the CURRY, LAMW and wide freeze paths compile with CNT = nil and cexpr_tail; counted_body uses new_fnl(true, 1). CHANGELOG keeps both sides. Co-Authored-By: Claude Opus 5.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.
Fixes #84
Fixes #88
This branch contains #78. It is #78's branch (
cursor/fix-consume-arity-662d, 2f3ee3c), merged with current main (43.1, e3dec8b) in 0bdbfc4 without conflicts, plus two commits. #78's let-splitting and wide-mode code is what fixes #84, so this builds on it instead of competing with it. You can merge #78 first and then this PR, or merge this PR alone, which supersedes #78.#84: 200-locals limit (5e7b5d6, tests only)
Once #78 is merged with 43.1 main, it already loads both shapes from the issue:
nest-16/20/30.shen(datatype, nested conclusion)function at line 1 has more than 200 local variablesloadedSHEN_PROLOG_ENGINE=legacyloadedlist-200/250.shen(definereturning a list literal)ok 200/ok 250cc-classify(classify.shen)shen:cc-classify#typeLarger cases also work: 60- and 100-deep patterns, and 1000/1500-element literals. 5e7b5d6 adds the issue's shapes to
test/wide_compile_spec.lua(+12 checks): 16/30/60-deep patterns, typechecked, and 200/250/1000-elementdefines.#88: reader on long list literals (d95688e)
shen.<s-exprs>recursed once per token without a tail call. Past about 1550 elements it overflowed LuaJIT's fixed stack, andread-file'strap-errorreported the overflow asreader error near here:.<strcontents>/<alphanums>/<whitespaces>recursed once per character in the same way, so(read-from-string "[1 … 3000]")failed on its 14 KB string literal.The fix follows the iterative comment scanners that already sit next to it in
prims.lua:shen.<s-exprs>keeps the KL's ordered choice and still calls the kernel's own single-token rules throughF. Open brackets are kept on an explicit stack.cons-form/add-sexprrun, and raise, in the KL's order.shen.cons-formbuilds the spine front to back.<strcontents>/<alphanums>become onetable.concat, and<whitespaces>becomes a loop.read-filerethrows a Lua stack overflow instead of reporting it as a reader error.reader error near here:ok 1600,ok 2000read-from-string, 14 KB string)reader error near here:ok 1ok 1reader error near here:[string "reader"]:26: stack overflowok 4000Remaining limit. rd-4000 now fails as an honest
stack overflowafter the reader. To the rest of the pipeline a list literal is aconsexpression nested once per element, and the kernel's recursive tree walkers overflow at about 3400 elements. A frame histogram at 4000 showsshen.find-types(fromshen.process-sexprs). With that made iterative, the next overflow is 3445 ×shen.map-h+ 3443 ×shen.special-case(shen.process-applications), and then macroexpansion and compilation would follow. The nativefind-typesmoved the limit only from 3200 to 3400, so I left it out. Lifting this properly means making those walkers iterative, which should be a separate change.Evidence that the native reader equals the kernel's. The new
test/reader_native_spec.luacompiles the kernel's own KL rules (<s-exprs>,<s-exprs1/2>,cons-form,<whitespaces>,<atom>,<str>,<sym>,<strcontents>,<alphanums>) underspec.aliases. It requires the same value, or the same error message, from the natives on 145 inputs:.shenfile undertests/andexamples/(86 files);[/(,[a | b c],$,:=,{}, nested and line comments,c#NN;, empty input, stray closers, and UTF-8.It also reads 1600–3000-element literals, a 14 KB string, 3000-deep nesting and a 20000-character symbol. As a mutation check, removing the
:=alternative and the[a | b c]error makes 10 checks fail.Tests
make testandmake certifywere run withGIT_DIR/GIT_WORK_TREE/GIT_INDEX_FILEunset, a privateTMPDIRand a privateXDG_CACHE_HOME.make testmake certify(43.1 suite)TOTAL: 1210 pass, 0 fail across 23 specspassed ... 154/failed ... 0TOTAL: 1511 pass, 0 fail across 25 specspassed ... 154/failed ... 0TOTAL: 1678 pass, 0 fail across 26 specspassed ... 154/failed ... 0Cross-port check (capcov workloads)
Payloads were extracted with capcov's
bench.pypayload filter and compared with shen-go 1f587de (kernel 42.2).a2-libs-typed.cc-classify#type200-locals error.b-typed.cc-contractsjudgements come outok:false(shen-go: 4), because a deep typecheck overflows insidetrap-error. That is Native Prolog engine overflows the Lua stack on modest typecheck depth (~400), where the legacy engine reaches about 1000 and shen-go has no limit #85, fixed in fix(prolog): run deep proofs on fresh coroutine stacks (#85) #90.b-typed,c-untyped,d-g5,a2-libs-typed,e-draftandf-modelcheckare all identical to shen-go 1f587de on a local merge of this PR with fix: resolve file names against *home-directory*; per-user kernel cache (#86, #87) #89 and fix(prolog): run deep proofs on fresh coroutine stacks (#85) #90 (not pushed). There are no kernel 43.1 vs 42.2 differences to classify.🤖 Generated with Claude Code