Merge loops/sx-vm-extensions into architecture: serving-mode JIT (opt-in)
Brings the bytecode JIT to the persistent epoch serving mode, gated opt-in via SX_SERVING_JIT=1 (default OFF → zero change for existing loops). Includes the correctness fixes that make the JIT match the CEK interpreter, and the interpret-only exclusions that keep continuation-based guest interpreters safe. Kernel / shared: - SX_SERVING_JIT gate in sx_server.ml (loads lib/compiler.sx + register_jit_hook only when opted in). - compiler.sx-as-`compile` correctness: else-symbol in compile-cond/case/guard; OP_DIV rational; OP_EQ/_fast_eq rational+ListRef; callable? accepts VmClosure. - Three composable interpret-only signals in jit_compile_lambda: (1) jit-exclude! name / "ns-*" prefix; (2) PUSH_HANDLER recursive bytecode scan (guard/handler-bind/Dream catch); (3) jit-exclude-callers-of! + code_refs_escaping_caller (call/cc-establishing form callers). Per-guest interpret-only declarations in each guest runtime: smalltalk (dispatch core + pharo-test-class), scheme (scheme-*/scm-*), erlang (er-*/erlang-*), prolog (pl-*), common-lisp (cl-*/clos-* + condition-form callers), js (js-*/jp-*), haskell (hk-*). Verified under SX_SERVING_JIT=1 (== CEK, no hang): host 181, smalltalk 847, scheme/flow 166, erlang 530, prolog 590/mod 390, haskell 285, common-lisp 487, js 148, apl 152, datalog/forth/ocaml. run_tests --jit 4813/1131 (was 4809/1135, improved), no-jit 4834/1110 (unchanged). Default-OFF gate => no loop regresses.
This commit is contained in:
@@ -4190,6 +4190,16 @@ let () =
|
||||
match args with
|
||||
| [String n] | [Symbol n] -> Bool (Sx_types.jit_name_excluded n)
|
||||
| _ -> Bool false);
|
||||
register "jit-exclude-callers-of!" (fun args ->
|
||||
(* Register call/cc-establishing forms (e.g. cl-restart-case). Any function
|
||||
whose bytecode references one of these is itself interpret-only — JIT
|
||||
would force the form into a nested cek-run where its continuation can't
|
||||
escape. A guest declares its condition-system / escaping forms here. *)
|
||||
List.iter (fun a ->
|
||||
match a with
|
||||
| String n | Symbol n -> Hashtbl.replace Sx_types.jit_excluded_caller_names n ()
|
||||
| _ -> ()) args;
|
||||
Nil);
|
||||
register "jit-reset-counters!" (fun _args ->
|
||||
Sx_types.jit_compiled_count := 0;
|
||||
Sx_types.jit_skipped_count := 0;
|
||||
|
||||
@@ -502,6 +502,20 @@ let jit_name_excluded name =
|
||||
String.length name >= String.length p
|
||||
&& String.sub name 0 (String.length p) = p) !jit_excluded_prefixes
|
||||
|
||||
(** Names of functions that ESTABLISH an escaping continuation via call/cc
|
||||
(e.g. Common-Lisp's [cl-restart-case] / [cl-handler-case] — the condition
|
||||
system). Any SX function that *calls* one of these is itself unsafe to JIT:
|
||||
JIT-compiling the caller forces the call/cc-wrapping form to run in a nested
|
||||
cek-run, where invoking the captured continuation runs-to-completion-and-
|
||||
returns instead of escaping — so a restart/non-local exit silently fails
|
||||
and the body falls through (observed as result accumulation / no-abort).
|
||||
|
||||
These callers are NOT a fixed namespace (they are arbitrary user/test code),
|
||||
so they cannot be prefix-excluded. Instead a guest declares its escaping
|
||||
forms here (via [jit-exclude-callers-of!]) and [jit_compile_lambda] skips
|
||||
any function whose constant pool references one of them. *)
|
||||
let jit_excluded_caller_names : (string, unit) Hashtbl.t = Hashtbl.create 16
|
||||
|
||||
(** {2 JIT cache LRU eviction — Phase 2}
|
||||
|
||||
Once a lambda crosses the threshold, its [l_compiled] slot is filled.
|
||||
|
||||
@@ -1156,6 +1156,22 @@ let rec code_uses_handler code =
|
||||
(try code_uses_handler (code_from_value c) with _ -> false)
|
||||
| _ -> false) code.vc_constants
|
||||
|
||||
(** True if [code] — or any nested closure code — references (in its constant
|
||||
pool, as a GLOBAL_GET/CALL name) a function registered in
|
||||
[Sx_types.jit_excluded_caller_names] (a call/cc-establishing form like
|
||||
Common-Lisp's cl-restart-case/cl-handler-case). Such a caller must run on
|
||||
the CEK so the continuation captured inside the called form can escape.
|
||||
The constant-pool string IS the referenced symbol name, so membership is a
|
||||
direct lookup; recurse into nested closure codes. Skipped entirely (no
|
||||
Hashtbl walk) when no escaping forms are registered. *)
|
||||
let rec code_refs_escaping_caller code =
|
||||
Array.exists (fun c ->
|
||||
match c with
|
||||
| String s -> Hashtbl.mem Sx_types.jit_excluded_caller_names s
|
||||
| Dict d when Hashtbl.mem d "bytecode" || Hashtbl.mem d "vc-bytecode" ->
|
||||
(try code_refs_escaping_caller (code_from_value c) with _ -> false)
|
||||
| _ -> false) code.vc_constants
|
||||
|
||||
let jit_compile_lambda (l : lambda) globals =
|
||||
let fn_name = match l.l_name with Some n -> n | None -> "<anon>" in
|
||||
if !_jit_compiling then (
|
||||
@@ -1241,6 +1257,13 @@ let jit_compile_lambda (l : lambda) globals =
|
||||
Printf.eprintf "[jit] SKIP %s: installs an exception handler (guard) — interpret-only\n%!"
|
||||
fn_name;
|
||||
None
|
||||
end else if Hashtbl.length Sx_types.jit_excluded_caller_names > 0
|
||||
&& code_refs_escaping_caller code then begin
|
||||
(* Calls a call/cc-establishing form (e.g. cl-restart-case): must
|
||||
run on the CEK so the captured continuation can escape. *)
|
||||
Printf.eprintf "[jit] SKIP %s: calls a call/cc-establishing form — interpret-only\n%!"
|
||||
fn_name;
|
||||
None
|
||||
end else
|
||||
Some { vm_code = code; vm_upvalues = [||];
|
||||
vm_name = l.l_name; vm_env_ref = effective_globals; vm_closure_env = Some l.l_closure }
|
||||
|
||||
Reference in New Issue
Block a user