Removes the 5993-line bootstrapped Python evaluator (sx_ref.py) and all
code that depended on it exclusively. Both bootstrappers (JS + OCaml)
now use a new synchronous OCaml bridge (ocaml_sync.py) to run the
transpiler. JS build produces identical output; OCaml bootstrap produces
byte-identical sx_ref.ml.
Key changes:
- New shared/sx/ocaml_sync.py: sync subprocess bridge to sx_server.exe
- hosts/javascript/bootstrap.py: serialize defines → temp file → OCaml eval
- hosts/ocaml/bootstrap.py: same pattern for OCaml transpiler
- shared/sx/{html,async_eval,resolver,jinja_bridge,handlers,pages,deps,helpers}:
stub or remove sx_ref imports; runtime uses OCaml bridge (SX_USE_OCAML=1)
- sx/sxc/pages: parse defpage/defhandler from AST instead of Python eval
- hosts/ocaml/lib/sx_primitives.ml: append handles non-list 2nd arg per spec
- Deleted: sx_ref.py, async_eval_ref.py, 6 Python test runners, misc ref/ files
Test results: JS 1078/1078, OCaml 1114/1114.
sx_docs SSR has pre-existing rendering issues to investigate separately.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root causes of missing CSSX classes in SSR:
1. _sx_trampoline_fn in sx_primitives.ml was never wired — call_any in
HO forms (map/filter/for-each) returned unresolved Thunks, so callbacks
like render-lambda-html's param binding never executed. Fixed in
bootstrap.py FIXUPS: wire Sx_primitives._sx_trampoline_fn after eval_expr.
2. adapter-html.sx used (emit! ...) and (emitted ...) which are CEK special
forms (walk kont for ScopeAccFrame), but scope-push!/scope-pop! use the
hashtable. CEK frames and hashtable are two different scope systems.
Fixed: adapter uses scope-emit!/scope-emitted (hashtable primitives).
3. env-* operations (env-has?, env-get, env-bind!, env-set!, env-extend,
env-merge) only accepted Env type. adapter-html.sx passes Dict as env.
Fixed: all env ops go through unwrap_env which handles Dict/Nil.
Also: fix merge conflict in sx/sx/geography/index.sx, remove duplicate
scope primitives from sx_primitives.ml (sx_server.ml registers them).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace AOT adapter compilation with lazy JIT — each named lambda is
compiled to VM bytecode on first call, cached in l_compiled field for
subsequent calls. Compilation failures fall back to CEK gracefully.
VM types (vm_code, vm_upvalue_cell, vm_closure) moved to sx_types.ml
mutual recursion block. Lambda and Component records gain mutable
l_compiled/c_compiled cache fields. jit_compile_lambda in sx_vm.ml
wraps body as (fn (params) body), invokes spec/compiler.sx via CEK,
extracts inner closure from OP_CLOSURE constant.
JIT hooks in both paths:
- vm_call: Lambda calls from compiled VM code
- continue_with_call: Lambda calls from CEK step loop (injected by
bootstrap.py post-processing)
Pre-mark sentinel prevents re-entrancy (compile function itself was
hanging when JIT'd mid-compilation). VM execution errors caught and
fall back to CEK with sentinel marking.
Also: add kbd/samp/var to HTML_TAGS, rebuild sx-browser.js, add page
URL to sx-page-full-py timing log.
Performance: first page 28s (JIT compiles 17 functions), subsequent
pages 0.31s home / 0.71s wittgenstein (was 2.3s). All 1945 tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Compiler fixes:
- Upvalue re-lookup returns own position (uv-index), not parent slot
- Spec: cek-call uses (make-env) not (dict) — OCaml Dict≠Env
- Bootstrap post-processes transpiler Dict→Env for cek_call
VM runtime fixes:
- compile_adapter evaluates constant defines (SPECIAL_FORM_NAMES etc.)
via execute_module instead of wrapping as NativeFn closures
- Native primitives: map-indexed, some, every?
- Nil-safe HO forms: map/filter/for-each/some/every? accept nil as empty
- expand-components? set in kernel env (not just VM globals)
- unwrap_env diagnostic: reports actual type received
sx-page-full command:
- Single OCaml call: aser-slot body + render-to-html shell
- Eliminates two pipe round-trips (was: aser-slot→Python→shell render)
- Shell statics (component_defs, CSS, pages_sx) cached in Python,
injected into kernel once, referenced by symbol in per-request command
- Large blobs use placeholder tokens — Python splices post-render,
pipe transfers ~51KB instead of 2MB
Performance (warm):
- Server total: 0.55s (was ~2s)
- aser-slot VM: 0.3s, shell render: 0.01s, pipe: 0.06s
- kwargs computation: 0.000s (cached)
SX_STANDALONE mode for sx_docs dev (skips fragment fetches).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Transpiler (transpiler.sx): detects CEK state dict literals (5 fields:
control/env/kont/phase/value) and emits CekState OCaml record instead
of Dict(Hashtbl). Eliminates 200K Hashtbl allocations per page.
Bootstrapper: skip stdlib.sx (functions already registered as OCaml
primitives). Only transpile evaluator.sx.
Runtime: get_val handles CekState with direct field access. type_of
returns "dict" for CekState (backward compat).
Profiling results (root cause of slowness):
Pure eval: OCaml 1.6x FASTER than Python (expected)
Aser: OCaml 28x SLOWER than Python (unexpected!)
Root cause: Python has a native optimized aser. OCaml runs the SX
adapter-sx.sx through the CEK machine — each aserCall is ~50 CEK
steps with closures, scope operations, string building.
Fix needed: native OCaml aser (like Python's), not SX adapter
through CEK machine.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The irreducible primitive set drops from 79 to 33. Everything that can
be expressed in SX is now a library function in stdlib.sx, loaded after
evaluator.sx and before render.sx.
Moved to stdlib.sx (pure SX, no host dependency):
- Logic: not
- Comparison: != <= >= eq? eqv? equal?
- Predicates: nil? boolean? number? string? list? dict? continuation?
empty? odd? even? zero? contains?
- Arithmetic: inc dec abs ceil round min max clamp
- Collections: first last rest nth cons append reverse flatten range
chunk-every zip-pairs vals has-key? merge assoc dissoc into
- Strings: upcase downcase string-length substring string-contains?
starts-with? ends-with? split join replace
- Text: pluralize escape assert parse-datetime
Remaining irreducible primitives (33):
+ - * / mod floor pow sqrt = < > type-of symbol-name keyword-name
str slice index-of upper lower trim char-from-code list dict concat
get len keys dict-set! append! random-int json-encode format-date
parse-int format-decimal strip-tags sx-parse error apply
All hosts: JS 957+1080, Python 744, OCaml 952 — zero regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SX-to-OCaml transpiler (transpiler.sx) generates sx_ref.ml (~90KB, ~135
mutually recursive functions) from the spec evaluator. Foundation tests
all pass: parser, primitives, env operations, type system.
Key design decisions:
- Env variant added to value type for CEK state dict storage
- Continuation carries optional data dict for captured frames
- Dynamic var tracking distinguishes OCaml fn calls from SX value dispatch
- Single let rec...and block for forward references between all defines
- Unused ref pre-declarations eliminated via let-bound name detection
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>