Files
rose-ash/lib/haskell/runtime.sx
giles bf298684fd vm-ext: gate serving-JIT behind SX_SERVING_JIT + fix continuation-guest regressions
Enabling the epoch serving-mode JIT globally regressed continuation-based guest
interpreters (the epoch mode is the shared command channel every loop's
conformance runner uses). Two-part fix:

1. SAFE DEFAULT GATE. register_jit_hook in the persistent server branch is now
   opt-in via SX_SERVING_JIT=1 (default OFF). Default behaviour is unchanged
   (no JIT in epoch serving) → zero regression for sibling loops. The
   content/Smalltalk page server opts in.

2. GENERAL FIXES + per-guest interpret-only declarations:
   - callable? (sx_server/run_tests/integration_tests/mcp_tree) now accepts
     VmClosure. A JIT-compiled higher-order function returns its inner closure
     as a VmClosure; callable? previously rejected it, so scheme-apply's
     (callable? proc) guard failed with "not a procedure: <vm:anon>".
   - jit-exclude! gains a trailing-"*" namespace-prefix form
     (Sx_types.jit_excluded_prefixes), the robust way to mark a whole guest
     interpreter interpret-only (a name-list misses functions in extra files —
     it left erlang's vm/dispatcher JIT'd and 13 tests short).
   - Per-guest exclusions in each guest's runtime.sx:
       scheme  "scheme-*" "scm-*"   erlang "er-*" "erlang-*"
       prolog  "pl-*"               common-lisp "cl-*" "clos-*"
       js      "js-*"               haskell "hk-*"

Verified under opt-in JIT (== CEK, no hang): smalltalk 847/847, scheme/flow
166/166, erlang 530/530, prolog 590/590, apl 152/152, js 147/148. Residual
(documented, protected by the default gate): common-lisp 6 fails in advanced
suites (parser-recovery/debugger/CLOS/MOP). lua (0/16) and tcl (3/4) fail
identically on CEK — pre-existing, not JIT. run_tests --jit/no-jit unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 22:22:40 +00:00

157 lines
4.5 KiB
Plaintext

;; Haskell runtime: constructor registry.
;;
;; A mutable dict keyed by constructor name (e.g. "Just", "[]") with
;; entries of shape {:arity N :type TYPE-NAME-STRING}.
;; Populated by ingesting `data` / `newtype` decls from parsed ASTs.
;; Pre-registers a small set of constructors tied to Haskell syntactic
;; forms (Bool, list, unit) — every nontrivial program depends on
;; these, and the parser/desugar pipeline emits them as (:var "True")
;; etc. without a corresponding `data` decl.
(define hk-constructors (dict))
(define
hk-register-con!
(fn (cname arity type-name) (dict-set! hk-constructors cname {:arity arity :type type-name})))
(define hk-is-con? (fn (name) (has-key? hk-constructors name)))
(define
hk-con-arity
(fn
(name)
(if
(has-key? hk-constructors name)
(get (get hk-constructors name) "arity")
nil)))
(define
hk-con-type
(fn
(name)
(if
(has-key? hk-constructors name)
(get (get hk-constructors name) "type")
nil)))
(define hk-con-names (fn () (keys hk-constructors)))
;; ── Registration from AST ────────────────────────────────────
;; (:data NAME TVARS ((:con-def CNAME FIELDS) …))
(define
hk-register-data!
(fn
(data-node)
(let
((type-name (nth data-node 1)) (cons-list (nth data-node 3)))
(for-each
(fn (cd) (hk-register-con! (nth cd 1) (len (nth cd 2)) type-name))
cons-list))))
;; (:newtype NAME TVARS CNAME FIELD)
(define
hk-register-newtype!
(fn (nt-node) (hk-register-con! (nth nt-node 3) 1 (nth nt-node 1))))
;; Walk a decls list, registering every `data` / `newtype` decl.
(define
hk-register-decls!
(fn
(decls)
(for-each
(fn
(d)
(cond
((and (list? d) (not (empty? d)) (= (first d) "data"))
(hk-register-data! d))
((and (list? d) (not (empty? d)) (= (first d) "newtype"))
(hk-register-newtype! d))
(:else nil)))
decls)))
(define
hk-register-program!
(fn
(ast)
(cond
((nil? ast) nil)
((not (list? ast)) nil)
((empty? ast) nil)
((= (first ast) "program") (hk-register-decls! (nth ast 1)))
((= (first ast) "module") (hk-register-decls! (nth ast 4)))
(:else nil))))
;; Convenience: source → AST → desugar → register.
(define hk-load-source! (fn (src) (hk-register-program! (hk-core src))))
;; ── Built-in constructors pre-registered ─────────────────────
;; Bool — used implicitly by `if`, comparison operators.
(hk-register-con! "True" 0 "Bool")
(hk-register-con! "False" 0 "Bool")
;; List — used by list literals, range syntax, and cons operator.
(hk-register-con! "[]" 0 "List")
(hk-register-con! ":" 2 "List")
;; Unit — produced by empty parens `()`.
(hk-register-con! "()" 0 "Unit")
;; Standard Prelude types — pre-registered so expression-level
;; programs can use them without a `data` decl.
(hk-register-con! "Nothing" 0 "Maybe")
(hk-register-con! "Just" 1 "Maybe")
(hk-register-con! "Left" 1 "Either")
(hk-register-con! "Right" 1 "Either")
(hk-register-con! "LT" 0 "Ordering")
(hk-register-con! "EQ" 0 "Ordering")
(hk-register-con! "GT" 0 "Ordering")
(hk-register-con! "SomeException" 1 "SomeException")
(define
hk-str?
(fn (v) (or (string? v) (and (dict? v) (has-key? v "hk-str")))))
(define
hk-str-head
(fn
(v)
(if
(string? v)
(char-code (char-at v 0))
(char-code (char-at (get v "hk-str") (get v "hk-off"))))))
(define
hk-str-tail
(fn
(v)
(let
((buf (if (string? v) v (get v "hk-str")))
(off (if (string? v) 1 (+ (get v "hk-off") 1))))
(if (>= off (string-length buf)) (list "[]") {:hk-off off :hk-str buf}))))
(define
hk-str-null?
(fn
(v)
(if
(string? v)
(= (string-length v) 0)
(>= (get v "hk-off") (string-length (get v "hk-str"))))))
(define
hk-str-to-native
(fn
(v)
(if
(string? v)
v
(let
((buf (get v "hk-str")) (off (get v "hk-off")))
(reduce
(fn (acc i) (str acc (char-at buf i)))
""
(range off (string-length buf)))))))
;; ── JIT interpret-only boundary ───────────────────────────────────────────
;; The Haskell evaluator (hk-eval and the lazy-thunk forcer) recurses deeply
;; over the AST/graph; under JIT the recursive eval can miscompile into a
;; non-terminating loop. Exclude the hk- namespace from JIT.
(jit-exclude! "hk-*")