Shen 41.1 port: compiler optimizations and full test suite pass#1
Merged
Conversation
The 41.1 port loaded but couldn't run anything past `(shen.initialise)`: the harness failed immediately, *macros* was nil, and the Prolog suite blew through Lua's parser limits as soon as a CPS chain showed up. Four compiler changes were needed: 1. Strict literal-data hoisting. The old `try_lit_const` over-matched any cons-shaped form, so `(set *macros* (lambda ...))` and the stlib's `(shen.record-kl name (cons ...))` calls were silently replaced with their AST and never executed. Now only `(cons L R)` trees count as literal data. 2. Flat cons-tree builder (`MKTREE`). The stlib's record-kl source trees reach ~7000 cons cells / 216 levels deep; the compiler now emits a flat blueprint array consumed by a runtime helper instead of deeply-nested `F["cons"]` expressions. 3. Hoisted freeze closures (`BIND` + per-defun `KB` table). The Shen Prolog compiler chains 60+ `(freeze ...)` continuations inside argument positions; naive codegen exceeded LuaJIT's ~65 nested `function` body limit. Each freeze body is now hoisted to a flat `KB[N] = function(cap...) ... end` slot inside the enclosing defun and each use site is a plain `BIND(KB[N], cap...)` call -- no nested function literal. 4. Deep let-floating + right-spine call-chain flattening. Rewrites `(F a (G b (let X V B)))` to `(let X V (F a (G b B)))` so chained CPS lets collapse into flat `local` declarations, and emits long `shen.gc(A, shen.gc(A, ...))` tail chains as a sequence of locals rather than one deeply-nested call expression. Also: make atoms / vectors / streams self-evaluate inside `eval-kl` (the macro expander hands these out during `shen.walk`), and add the port-specific `shen.char-stoutput?` / `shen.char-stinput?` predicates that `writer.shen` expects every implementation to provide. End-to-end: full `kerneltests.shen` now runs (129/134 reports pass). The Einstein riddle solves in 0.43 s vs the documented 1.82 s on the old 22.4 port; fib(32) in 0.14 s. See BENCHMARKS.md. https://claude.ai/code/session_01WkcfnTxzoMzQQtYerRgv1N
Subagent worktrees land under .claude/worktrees/<agent-id>/ during parallel investigations and shouldn't be part of the repo. https://claude.ai/code/session_01WkcfnTxzoMzQQtYerRgv1N
Two compiler bugs explained all five remaining report failures:
1. try_let_float must skip binders. The rewriter treated any cons with an
unbound symbol head as an ordinary call, so it would rewrite
`(lambda Z (let X V B))` into `(let X V (lambda Z B))` -- hoisting the
let *out of* the binder. V could reference Z; after floating, Z was no
longer in env when V's body was compiled and the compiler emitted
`S("Z")` (a self-evaluating symbol) instead of the captured Lua local.
This is the bug that turned `mapit` / `nreverse` into functions that
silently returned false (Prolog call, Prolog naive reverse). Fix:
bail out of try_let_float when the head is in SPECIAL_HEADS.
2. ccall baked stale C.ARITY into PARTIAL / over-application. When a
function was later redefined with a different arity -- e.g. binary.shen
redefining `complement` after tableauprolog.shen used it as a 6-arg
Prolog predicate, or shen.update-lambdatable evaluating a curry wrapper
for `depth`'s new arity *before* the new defun installs -- the existing
call sites captured the stale arity and produced wrong-arity residual
closures (rendered as `funex...` for complement, or "attempt to apply
a non-function" for depth). Fix: on arity mismatch, route through
`APP(S(name), args...)` so dispatch consults the live FA[F[name]].
Exact-arity calls still take the direct F[name](...) fast path.
All four parallel subagents converged on these two root causes (each pair
of failures shared one bug). Full suite: 129/134 -> 134/134. Cold-start
and Einstein/fib benchmark numbers unchanged at noise level.
https://claude.ai/code/session_01WkcfnTxzoMzQQtYerRgv1N
pyrex41
pushed a commit
that referenced
this pull request
Jul 2, 2026
…ail closed Fixes from code review: - #1 write with a missing `content` field now 400s ("content: is required") instead of a granted write silently storing "" over the document. present? distinguishes an absent key from an empty value. - #2 resolve identity (User) and tenant (Tenant) ONCE, in Shen, before the proof, and pass the same values into the query, the [granted ...] witness, and the discharge report. Previously prolog? discarded the bindings and grant/ reason re-derived them via fresh host calls — under the cosocket resolver those yield, so the witness could name a different user than the chain proved. Now the only yielding call (host-token-user) happens once; the remaining leaf facts are local, non-yielding reads. - #4 cosocket resolver fails CLOSED by default (no identity on a store error, so the chain denies); degrading to a local fallback is now an explicit opt-in. Documented / nits: - #3 the lmdb append allocates its seq with a read-modify-write outside the txn, which races across workers; commented in store.lua and called out in the README's multi-worker note (was implied to be the multi-worker fix). - audit is now admin-gated (POST /api/admin/audit) — it lists users and reasons. - bodyless POST to a real route now 400s instead of 404 (handlers validate body). - decision-status is now used by respond/write-through (was dead code). - README: file backend is process-crash durable, not power-loss (no fsync); cosocket fails closed + 5s cache TTL means revocation lag. - app.lua: comment that `host` is an intentional global for the lua.call bridge. selftest.lua adds request-shape guards (missing content, no-wipe, bodyless, audit-needs-admin); file/lmdb/cosocket runs still yield an identical audit trail. Port suite green (453/453). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CC5aXvUTaaNgfGHQJjGsvX
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
This PR completes the Shen 41.1 port with critical compiler optimizations that unblock the full kernel boot and test suite. The 41.1 stlib and Prolog-compiled code stress LuaJIT's parser limits far more than the previous 22.4 kernel, requiring several new code-generation strategies to avoid syntax-level overflow and expression-complexity limits.
Test results: 134/134 reports pass (100% pass rate) on the official Shen 41.1 test suite.
Key Changes
Compiler Optimizations (compiler.lua)
Strict literal-data hoisting — Only
(cons L R)trees are now treated as compile-time literals; side-effecting calls like(set ...),(shen.record-kl ...), and(lambda ...)are always evaluated. The previous heuristic over-hoisted, eliding critical initialization side effects.Flat cons-tree builder (MKTREE) — The stlib's
(shen.record-kl ...)calls contain trees up to ~7,000 cons cells / 216 levels deep. Naive nesting blows Lua's expression-complexity limit (~200 levels). The compiler now emits a flat blueprint array consumed by a runtime helper, collapsing deep trees into a single shallow call.Hoisted freeze closures (BIND) — Shen's Prolog compiler chains 60+
(freeze ...)continuations inside argument positions. Each freeze body is now hoisted to a flatKB[N] = function(...) ... endslot inside the enclosing defun; use sites emitBIND(KB[N], cap...)with no nested function literal, sidestepping LuaJIT's 65-syntax-level parser cap.Deep let-floating —
(F1 a1 (F2 a2 (let X V B)))is rewritten to(let X V (F1 a1 (F2 a2 B)))when intermediate args are pure. Combined with hoisted freezes, this flattens deeply nested CPS chains into a single block oflocaldeclarations.Right-spine call-chain flattening — Long chains like
(shen.gc A (shen.gc A (shen.gc A ...)))in tail position are emitted as a sequence oflocalassignments rather than deeply-nested call expressions (another parser-cap fix).Dynamic arity dispatch — Arity mismatches at compile time now route through
APPto use the current runtime arity rather than a stale compile-time value, fixing issues where functions are redefined with different arities between compilation and execution.Runtime Support (runtime.lua / prims.lua)
eval-klare returned as-is rather than compiled as KL forms.shen.char-stoutput?andshen.char-stinput?(both returnfalsefor byte streams).pos,tlstr,string->n,n->stringnow validate inputs and raise errors on out-of-range / invalid arguments.Test Infrastructure
y-or-n?as a non-interactive function and correctly extract pass/fail counts from the test harness's package-scoped globals.https://claude.ai/code/session_01WkcfnTxzoMzQQtYerRgv1N