Fix JIT closure isolation, SX wire format, server diagnostics

Root cause: _env_bind_hook mirrored ALL env_bind calls (including
lambda parameter bindings) to the shared VM globals table. Factory
functions like make-page-fn that return closures capturing different
values for the same param names (default-name, prefix, suffix) would
have the last call's values overwrite all previous closures' captured
state in globals. OP_GLOBAL_GET reads globals first, so all closures
returned the last factory call's values.

Fix: only sync root-env bindings (parent=None) to VM globals. Lambda
parameter bindings stay in their local env, found via vm_closure_env
fallback in OP_GLOBAL_GET.

Also in this commit:
- OP_CLOSURE propagates parent vm_closure_env to child closures
- Remove JIT globals injection (closure vars found via env chain)
- sx_server.ml: SX-Request header → returns text/sx (aser only)
- sx_server.ml: diagnostic endpoint GET /sx/_debug/{env,eval,route}
- sx_server.ml: page helper stubs for deep page rendering
- sx_server.ml: skip client-libs/ dir (browser-only definitions)
- adapter-html.sx: unknown components → HTML comment (not error)
- sx-platform.js: .sxbc fallback loader for bytecode modules
- Delete sx_http.ml (standalone HTTP server, unused)
- Delete stale .sxbc.json files (arity=0 bug, replaced by .sxbc)
- 7 new closure isolation tests in test-closure-isolation.sx
- mcp_tree.ml: emit arity + upvalue-count in .sxbc.json output

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-30 17:28:47 +00:00
parent 408eca1cb0
commit 80931e4972
43 changed files with 1528 additions and 1268 deletions

View File

@@ -270,6 +270,7 @@
(= name "defisland") (compile-defcomp em args scope)
(= name "quasiquote") (compile-quasiquote em (first args) scope)
(= name "letrec") (compile-letrec em args scope tail?)
(= name "match") (compile-match em args scope tail?)
;; Default — function call
:else
(compile-call em head args scope tail?)))))))
@@ -634,6 +635,75 @@
(patch-i16 em end-jump (- (current-offset em) (+ end-jump 2)))))))))))
;; compile-match — compile (match expr (pattern body) ...) to bytecode.
;; Self-contained via letrec so JIT can find the recursive helper.
(define compile-match
(fn (em args scope tail?)
(compile-expr em (first args) scope false)
(letrec
((do-clauses (fn (clauses)
(if (empty? clauses)
(do (emit-op em 5)
(let ((idx (pool-add (get em "pool") "match: no clause matched")))
(emit-op em 1) (emit-u16 em idx)
(emit-op em 52) (emit-u16 em (pool-add (get em "pool") "error"))
(emit-byte em 1)))
(let ((clause (first clauses))
(pattern (first clause))
(body (nth clause 1))
(rest-clauses (rest clauses)))
(cond
;; Wildcard _
(and (= (type-of pattern) "symbol") (= (symbol-name pattern) "_"))
(do (emit-op em 5) (compile-expr em body scope tail?))
;; Symbol binding
(and (= (type-of pattern) "symbol")
(not (= (symbol-name pattern) "true"))
(not (= (symbol-name pattern) "false"))
(not (= (symbol-name pattern) "nil")))
(let ((var-name (symbol-name pattern))
(inner-scope (scope-add scope var-name)))
(emit-op em 13) (emit-byte em (scope-index inner-scope var-name))
(compile-expr em body inner-scope tail?))
;; Quoted symbol 'foo
(and (list? pattern) (= (len pattern) 2)
(= (type-of (first pattern)) "symbol")
(= (symbol-name (first pattern)) "quote")
(= (type-of (nth pattern 1)) "symbol"))
(do (emit-op em 6)
(let ((idx (pool-add (get em "pool") (make-symbol (symbol-name (nth pattern 1))))))
(emit-op em 1) (emit-u16 em idx))
(let ((eq-idx (pool-add (get em "pool") "=")))
(emit-op em 52) (emit-u16 em eq-idx) (emit-byte em 2))
(emit-op em 33)
(let ((skip (current-offset em)))
(emit-i16 em 0)
(emit-op em 5) (compile-expr em body scope tail?)
(emit-op em 32)
(let ((end-jump (current-offset em)))
(emit-i16 em 0)
(patch-i16 em skip (- (current-offset em) (+ skip 2)))
(do-clauses rest-clauses)
(patch-i16 em end-jump (- (current-offset em) (+ end-jump 2))))))
;; Literal (string, number, boolean, nil)
:else
(do (emit-op em 6)
(compile-expr em pattern scope false)
(let ((eq-idx (pool-add (get em "pool") "=")))
(emit-op em 52) (emit-u16 em eq-idx) (emit-byte em 2))
(emit-op em 33)
(let ((skip (current-offset em)))
(emit-i16 em 0)
(emit-op em 5) (compile-expr em body scope tail?)
(emit-op em 32)
(let ((end-jump (current-offset em)))
(emit-i16 em 0)
(patch-i16 em skip (- (current-offset em) (+ skip 2)))
(do-clauses rest-clauses)
(patch-i16 em end-jump (- (current-offset em) (+ end-jump 2))))))))))))
(do-clauses (rest args)))))
(define compile-thread
(fn (em args scope tail?)
"Compile (-> val (f1 a) (f2 b)) by desugaring to nested calls."