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:
@@ -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
|
||||
|
||||
@@ -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))))))
|
||||
|
||||
Reference in New Issue
Block a user