JIT allowlist + integration tests + --test mode + clean up debug logging

JIT allowlist (sx_server.ml):
- Replace try-every-lambda strategy with StringSet allowlist. Only
  functions in the list get JIT compiled (compiler, parser, pure transforms).
  Render functions that need dynamic scope skip JIT entirely — no retry
  overhead, no silent fallbacks.
- Add (jit-allow name) command for dynamic expansion from Python bridge.
- JIT failures log once with "[jit] DISABLED fn — reason" then go silent.

Standalone --test mode (sx_server.ml):
- New --test flag loads full env (spec + adapters + compiler + signals),
  supports --eval and --load flags. Quick kernel testing without Docker.
  Example: dune exec bin/sx_server.exe -- --test --eval '(len HTML_TAGS)'

Integration tests (integration_tests.ml):
- New binary exercising the full rendering pipeline: loads spec + adapters
  into a server-like env, renders HTML via both native and SX adapter paths.
- 26 tests: HTML tags, special forms (when/if/let), letrec with side
  effects, component rendering, eval-expr with HTML tag functions.
- Would have caught the "Undefined symbol: div/lake/init" issues from
  the previous commit immediately without Docker.

VM cleanup (sx_vm.ml):
- Remove temporary debug logging (insn counter, call_closure counter,
  VmClosure depth tracking) added during debugging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-23 23:58:40 +00:00
parent dd057247a5
commit 5270d2e956
8 changed files with 573 additions and 77 deletions

View File

@@ -1440,11 +1440,12 @@
(make-cek-value (sf-lambda args env) env kont)))
;; scope: evaluate name, then push ScopeFrame
;; scope/provide/context/emit!/emitted — ALL use hashtable stacks.
;; One world: the aser and CEK share the same scope mechanism.
;; No continuation frame walking — scope-push!/pop!/peek are the primitives.
;; scope/provide/context/emit!/emitted — CEK frame-based.
;; provide/scope push proper CEK frames onto the continuation so that
;; shift/reset can capture and restore them correctly.
;; context/emit!/emitted walk the kont to find the relevant frame.
;; scope: push scope, evaluate body, pop scope.
;; scope: push ScopeAccFrame, evaluate body expressions via continuation.
;; (scope name body...) or (scope name :value v body...)
(define step-sf-scope
(fn (args env kont)
@@ -1458,48 +1459,50 @@
(do (set! val (trampoline (eval-expr (nth rest-args 1) env)))
(set! body (slice rest-args 2)))
(set! body rest-args))
(scope-push! name val)
(let ((result nil))
(for-each (fn (expr) (set! result (trampoline (eval-expr expr env)))) body)
(scope-pop! name)
(make-cek-value result env kont)))))
(if (empty? body)
(make-cek-value nil env kont)
(make-cek-state
(first body) env
(kont-push (make-scope-acc-frame name val (rest body) env) kont))))))
;; provide: sugar for scope with value.
;; provide: push ProvideFrame, evaluate body expressions via continuation.
(define step-sf-provide
(fn (args env kont)
(let ((name (trampoline (eval-expr (first args) env)))
(val (trampoline (eval-expr (nth args 1) env)))
(body (slice args 2)))
(scope-push! name val)
(let ((result nil))
(for-each (fn (expr) (set! result (trampoline (eval-expr expr env)))) body)
(scope-pop! name)
(make-cek-value result env kont)))))
(if (empty? body)
(make-cek-value nil env kont)
(make-cek-state
(first body) env
(kont-push (make-provide-frame name val (rest body) env) kont))))))
;; context: read from scope stack.
;; context: walk kont for nearest ProvideFrame with matching name.
(define step-sf-context
(fn (args env kont)
(let ((name (trampoline (eval-expr (first args) env)))
(default-val (if (>= (len args) 2)
(trampoline (eval-expr (nth args 1) env))
nil))
(val (scope-peek name)))
(make-cek-value (if (nil? val) default-val val) env kont))))
(frame (kont-find-provide kont name)))
(make-cek-value (if (nil? frame) default-val (get frame "value")) env kont))))
;; emit!: append to scope accumulator.
;; emit!: walk kont for nearest ScopeAccFrame, append to its emitted list.
(define step-sf-emit
(fn (args env kont)
(let ((name (trampoline (eval-expr (first args) env)))
(val (trampoline (eval-expr (nth args 1) env))))
(scope-emit! name val)
(val (trampoline (eval-expr (nth args 1) env)))
(frame (kont-find-scope-acc kont name)))
(when frame
(dict-set! frame "emitted" (append (get frame "emitted") (list val))))
(make-cek-value nil env kont))))
;; emitted: read accumulated scope values.
;; emitted: walk kont for nearest ScopeAccFrame, return its emitted list.
(define step-sf-emitted
(fn (args env kont)
(let ((name (trampoline (eval-expr (first args) env)))
(val (scope-peek name)))
(make-cek-value (if (nil? val) (list) val) env kont))))
(frame (kont-find-scope-acc kont name)))
(make-cek-value (if (nil? frame) (list) (get frame "emitted")) env kont))))
;; reset: push ResetFrame, evaluate body
(define step-sf-reset

View File

@@ -568,3 +568,43 @@
(assert-equal 3 (len r))
(assert-equal (list "a" (list "b") (list "c")) r))))
)
(defsuite "define-as-local"
(deftest "define inside fn creates local, not global"
;; When define is inside a fn body, recursive calls must each
;; get their own copy. If define writes to global, recursive
;; calls overwrite each other.
(let ((result
(let ((counter 0))
(letrec
((make-counter (fn ()
(define my-val counter)
(set! counter (inc counter))
my-val)))
(list (make-counter) (make-counter) (make-counter))))))
(assert-equal (list 0 1 2) result)))
(deftest "define inside fn with self-recursion via define"
;; read-list-loop pattern: define a function that calls itself
(let ((result
(let ((items (list)))
(define go (fn (n)
(when (< n 3)
(append! items n)
(go (inc n)))))
(go 0)
items)))
(assert-equal (list 0 1 2) result)))
(deftest "recursive define inside letrec fn doesn't overwrite"
;; Each call to make-list creates its own 'loop' local
(let ((make-list (fn (items)
(let ((result (list)))
(define loop (fn (i)
(when (< i (len items))
(append! result (nth items i))
(loop (inc i)))))
(loop 0)
result))))
(assert-equal (list "a" "b") (make-list (list "a" "b")))
(assert-equal (list 1 2 3) (make-list (list 1 2 3))))))