Skip to content

Shen 41.1 port: compiler optimizations and full test suite pass#1

Merged
pyrex41 merged 3 commits into
mainfrom
claude/vibrant-bohr-NKHTy
Jun 8, 2026
Merged

Shen 41.1 port: compiler optimizations and full test suite pass#1
pyrex41 merged 3 commits into
mainfrom
claude/vibrant-bohr-NKHTy

Conversation

@pyrex41

@pyrex41 pyrex41 commented Jun 8, 2026

Copy link
Copy Markdown
Owner

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)

  1. 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.

  2. 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.

  3. Hoisted freeze closures (BIND) — Shen's Prolog compiler chains 60+ (freeze ...) continuations inside argument positions. Each freeze body is now hoisted to a flat KB[N] = function(...) ... end slot inside the enclosing defun; use sites emit BIND(KB[N], cap...) with no nested function literal, sidestepping LuaJIT's 65-syntax-level parser cap.

  4. 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 of local declarations.

  5. 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 of local assignments rather than deeply-nested call expressions (another parser-cap fix).

  6. Dynamic arity dispatch — Arity mismatches at compile time now route through APP to 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)

  • BIND — Wraps per-defun continuation functions with captured values into 0-arity thunks, enabling the hoisted-freeze optimization.
  • MKTREE — Consumes flat blueprints to build deep cons trees without expression nesting.
  • Self-evaluation in eval — Vectors, streams, and opaque tables passed to eval-kl are returned as-is rather than compiled as KL forms.
  • Port-specific primitives — Added shen.char-stoutput? and shen.char-stinput? (both return false for byte streams).
  • String primitive bounds checkspos, tlstr, string->n, n->string now validate inputs and raise errors on out-of-range / invalid arguments.

Test Infrastructure

  • run-41.1-tests.lua — Updated to properly set y-or-n? as a non-interactive function and correctly extract pass/fail counts from the test harness's package-scoped globals.
  • bench.lua — New self-contained benchmark measuring cold startup, fib(25..32) through the live Shen pipeline, and Einstein's riddle via the Prolog engine.
  • 41.1-STATUS.md — Comprehensive documentation of the port state, compiler changes, and test results.

https://claude.ai/code/session_01WkcfnTxzoMzQQtYerRgv1N

claude added 3 commits June 8, 2026 13:01
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 pyrex41 merged commit 18f9e14 into main Jun 8, 2026
@pyrex41 pyrex41 deleted the claude/vibrant-bohr-NKHTy branch June 8, 2026 13:45
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
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.

2 participants