diff --git a/hosts/ocaml/bin/run_tests.ml b/hosts/ocaml/bin/run_tests.ml index 6fb99974..dcbbdcd3 100644 --- a/hosts/ocaml/bin/run_tests.ml +++ b/hosts/ocaml/bin/run_tests.ml @@ -1455,17 +1455,68 @@ let run_spec_tests env test_files = let () = let args = Array.to_list Sys.argv |> List.tl in let foundation_only = List.mem "--foundation" args in + let jit_enabled = List.mem "--jit" args in let test_files = List.filter (fun a -> not (String.length a > 0 && a.[0] = '-')) args in (* Always run foundation tests *) run_foundation_tests (); if not foundation_only then begin - Printf.printf "\n=== SX Spec Tests (CEK Evaluator) ===\n%!"; + Printf.printf "\n=== SX Spec Tests (CEK Evaluator + JIT) ===\n%!"; let env = make_test_env () in + (* Load compiler and enable JIT (opt-in via --jit flag) *) + if jit_enabled then begin + let globals = Hashtbl.create 512 in + let rec env_to_globals e = + Hashtbl.iter (fun id v -> + let name = Sx_types.unintern id in + if not (Hashtbl.mem globals name) then + Hashtbl.replace globals name v) e.Sx_types.bindings; + match e.Sx_types.parent with Some p -> env_to_globals p | None -> () + in + env_to_globals env; + (try + let compiler_path = if Sys.file_exists "lib/compiler.sx" then "lib/compiler.sx" + else "../../lib/compiler.sx" in + let ic = open_in compiler_path in + let src = really_input_string ic (in_channel_length ic) in + close_in ic; let _ = src in + let exprs = Sx_parser.parse_all src in + List.iter (fun e -> ignore (Sx_ref.eval_expr e (Env env))) exprs; + env_to_globals env; + Sx_runtime._jit_try_call_fn := Some (fun f args -> + match f with + | Lambda l -> + (match l.l_compiled with + | Some cl when not (Sx_vm.is_jit_failed cl) -> + (try Some (Sx_vm.call_closure cl args cl.vm_env_ref) + with _ -> None) + | Some _ -> None + | None -> + if l.l_name = None then None + else begin + l.l_compiled <- Some Sx_vm.jit_failed_sentinel; + match Sx_vm.jit_compile_lambda l globals with + | Some cl -> l.l_compiled <- Some cl; + (try Some (Sx_vm.call_closure cl args cl.vm_env_ref) with _ -> None) + | None -> None + end) + | _ -> None); + Printf.printf "[jit] Compiler loaded, JIT enabled\n%!" + with e -> + Printf.printf "[jit] Compiler not loaded: %s\n%!" (Printexc.to_string e)); + end; + Sx_runtime.jit_reset_counters (); run_spec_tests env test_files end; + (* JIT statistics *) + let jh = !(Sx_runtime._jit_hit) and jm = !(Sx_runtime._jit_miss) and js = !(Sx_runtime._jit_skip) in + let total = jh + jm + js in + if total > 0 then + Printf.printf "\n[jit] calls=%d hit=%d (%.1f%%) miss=%d skip=%d\n" + total jh (100.0 *. float_of_int jh /. float_of_int (max 1 total)) jm js; + (* Summary *) Printf.printf "\n%s\n" (String.make 60 '='); Printf.printf "Results: %d passed, %d failed\n" !pass_count !fail_count; diff --git a/hosts/ocaml/bin/sx_server.ml b/hosts/ocaml/bin/sx_server.ml index a9fdc7a5..805918f9 100644 --- a/hosts/ocaml/bin/sx_server.ml +++ b/hosts/ocaml/bin/sx_server.ml @@ -1399,6 +1399,27 @@ let rec dispatch env cmd = (* ---- Debugging / introspection commands ---- *) + | List [Symbol "jit-enable"] -> + register_jit_hook env; + send_ok_value (String "jit enabled") + + | List [Symbol "vm-counters"] -> + let d = Hashtbl.create 8 in + Hashtbl.replace d "vm_insns" (Number (float_of_int !(Sx_vm._vm_insn_count))); + Hashtbl.replace d "vm_calls" (Number (float_of_int !(Sx_vm._vm_call_count))); + Hashtbl.replace d "vm_cek_fallbacks" (Number (float_of_int !(Sx_vm._vm_cek_count))); + Hashtbl.replace d "comp_jit" (Number (float_of_int !(Sx_vm._vm_comp_jit_count))); + Hashtbl.replace d "comp_cek" (Number (float_of_int !(Sx_vm._vm_comp_cek_count))); + Hashtbl.replace d "jit_hit" (Number (float_of_int !(Sx_runtime._jit_hit))); + Hashtbl.replace d "jit_miss" (Number (float_of_int !(Sx_runtime._jit_miss))); + Hashtbl.replace d "jit_skip" (Number (float_of_int !(Sx_runtime._jit_skip))); + send_ok_value (Dict d) + + | List [Symbol "vm-counters-reset"] -> + Sx_vm.vm_reset_counters (); + Sx_runtime.jit_reset_counters (); + send_ok_value (String "reset") + | List [Symbol "vm-trace"; String src] -> (* Compile and trace-execute an SX expression, returning step-by-step trace entries with opcode names, stack snapshots, and frame depth. *) diff --git a/hosts/ocaml/lib/sx_primitives.ml b/hosts/ocaml/lib/sx_primitives.ml index 91b0523e..5f0d4a20 100644 --- a/hosts/ocaml/lib/sx_primitives.ml +++ b/hosts/ocaml/lib/sx_primitives.ml @@ -1393,6 +1393,20 @@ let () = end else Nil | _ -> Nil); + (* deref — unwrap a signal value with reactive dependency tracking. + If value is a Signal, returns s_value and registers in tracking context. + Otherwise returns value as-is. *) + register "deref" (fun args -> + match args with + | [Signal sig'] -> + if !_tracking_active then begin + if not (List.memq (Signal sig') !_tracking_deps) then + _tracking_deps := Signal sig' :: !_tracking_deps + end; + sig'.s_value + | [v] -> v + | _ -> Nil); + (* bind — create a tracked computation. Takes a body-fn (lambda). Starts tracking, evaluates body, collects deps, subscribes. On dep change: unsubscribes, re-evaluates, re-subscribes. diff --git a/hosts/ocaml/lib/sx_runtime.ml b/hosts/ocaml/lib/sx_runtime.ml index 5c061a82..6692fd70 100644 --- a/hosts/ocaml/lib/sx_runtime.ml +++ b/hosts/ocaml/lib/sx_runtime.ml @@ -470,13 +470,17 @@ let mutable_list () = ListRef (ref []) (* JIT try-call — ref set by sx_server.ml after compiler loads. Returns Nil (no JIT) or the result value. Spec calls this. *) let _jit_try_call_fn : (value -> value list -> value option) option ref = ref None +let _jit_hit = ref 0 +let _jit_miss = ref 0 +let _jit_skip = ref 0 +let jit_reset_counters () = _jit_hit := 0; _jit_miss := 0; _jit_skip := 0 let jit_try_call f args = match !_jit_try_call_fn with - | None -> Nil + | None -> incr _jit_skip; Nil | Some hook -> match f with | Lambda l when l.l_name <> None -> let arg_list = match args with List a | ListRef { contents = a } -> a | _ -> [] in - (match hook f arg_list with Some result -> result | None -> Nil) - | _ -> Nil + (match hook f arg_list with Some result -> incr _jit_hit; result | None -> incr _jit_miss; Nil) + | _ -> incr _jit_skip; Nil diff --git a/hosts/ocaml/lib/sx_vm.ml b/hosts/ocaml/lib/sx_vm.ml index 31bd4ed8..7888fde5 100644 --- a/hosts/ocaml/lib/sx_vm.ml +++ b/hosts/ocaml/lib/sx_vm.ml @@ -204,6 +204,7 @@ let jit_compile_comp ~name ~params ~has_children ~body ~closure globals = Returns the result value, or raises VmSuspended if CEK suspends. Saves the suspended CEK state in vm.pending_cek for later resume. *) let cek_call_or_suspend vm f args = + incr _vm_cek_count; let a = match args with Nil -> [] | List l -> l | _ -> [args] in let state = Sx_ref.continue_with_call f (List a) (Env (Sx_types.make_env ())) (List a) (List []) in let final = Sx_ref.cek_step_loop state in @@ -358,6 +359,7 @@ and run vm = let saved_ip = frame.ip in let op = bc.(frame.ip) in frame.ip <- frame.ip + 1; + incr _vm_insn_count; (try match op with (* ---- Constants ---- *) | 1 (* OP_CONST *) -> diff --git a/lib/compiler.sx b/lib/compiler.sx index 5baedd69..5f2e328a 100644 --- a/lib/compiler.sx +++ b/lib/compiler.sx @@ -359,6 +359,12 @@ (emit-op em 52) (emit-u16 em (pool-add (get em "pool") "scope-emitted")) (emit-byte em 1)) + (= name "deref") + (do + (compile-expr em (first args) scope false) + (emit-op em 52) + (emit-u16 em (pool-add (get em "pool") "deref")) + (emit-byte em 1)) (= name "perform") (let () @@ -964,6 +970,170 @@ tail? (do (emit-op em 49) (emit-byte em (len args))) (do (emit-op em 48) (emit-byte em (len args))))))))) + (define + compile-provide + (fn + (em args scope tail?) + (let + ((first-arg (first args)) + (name + (cond + (= (type-of first-arg) "keyword") + (keyword-name first-arg) + (= (type-of first-arg) "string") + first-arg + :else (symbol-name first-arg))) + (val-expr (nth args 1)) + (body (slice args 2)) + (name-idx (pool-add (get em "pool") name))) + (emit-op em 1) + (emit-u16 em name-idx) + (compile-expr em val-expr scope false) + (emit-op em 52) + (emit-u16 em (pool-add (get em "pool") "scope-push!")) + (emit-byte em 2) + (emit-op em 5) + (if + (empty? body) + (emit-op em 2) + (compile-begin em body scope false)) + (emit-op em 1) + (emit-u16 em name-idx) + (emit-op em 52) + (emit-u16 em (pool-add (get em "pool") "scope-pop!")) + (emit-byte em 1) + (emit-op em 5)))) + (define + compile-scope + (fn + (em args scope tail?) + (let + ((first-arg (first args)) + (name + (if + (= (type-of first-arg) "keyword") + (keyword-name first-arg) + (symbol-name first-arg))) + (rest-args (rest args)) + (name-idx (pool-add (get em "pool") name))) + (if + (and + (>= (len rest-args) 2) + (= (type-of (first rest-args)) "keyword") + (= (keyword-name (first rest-args)) "value")) + (let + ((val-expr (nth rest-args 1)) (body (slice rest-args 2))) + (emit-op em 1) + (emit-u16 em name-idx) + (compile-expr em val-expr scope false) + (emit-op em 52) + (emit-u16 em (pool-add (get em "pool") "scope-push!")) + (emit-byte em 2) + (emit-op em 5) + (if + (empty? body) + (emit-op em 2) + (compile-begin em body scope false)) + (emit-op em 1) + (emit-u16 em name-idx) + (emit-op em 52) + (emit-u16 em (pool-add (get em "pool") "scope-pop!")) + (emit-byte em 1) + (emit-op em 5)) + (let + ((body rest-args)) + (emit-op em 1) + (emit-u16 em name-idx) + (emit-op em 2) + (emit-op em 52) + (emit-u16 em (pool-add (get em "pool") "scope-push!")) + (emit-byte em 2) + (emit-op em 5) + (if + (empty? body) + (emit-op em 2) + (compile-begin em body scope false)) + (emit-op em 1) + (emit-u16 em name-idx) + (emit-op em 52) + (emit-u16 em (pool-add (get em "pool") "scope-pop!")) + (emit-byte em 1) + (emit-op em 5)))))) + (define + compile-guard + (fn + (em args scope tail?) + (let + ((guard-clause (first args)) + (body (rest args)) + (guard-scope (make-scope scope))) + (let + ((var-name (symbol-name (first guard-clause))) + (clauses (rest guard-clause)) + (var-slot (scope-define-local guard-scope var-name))) + (emit-op em 35) + (let + ((handler-offset (current-offset em))) + (emit-i16 em 0) + (compile-begin em body guard-scope false) + (emit-op em 36) + (emit-op em 32) + (let + ((done-jump (current-offset em))) + (emit-i16 em 0) + (patch-i16 + em + handler-offset + (- (current-offset em) (+ handler-offset 2))) + (emit-op em 17) + (emit-byte em var-slot) + (emit-op em 5) + (compile-guard-clauses em clauses guard-scope var-slot tail?) + (patch-i16 + em + done-jump + (- (current-offset em) (+ done-jump 2))))))))) + (define + compile-guard-clauses + (fn + (em clauses scope var-slot tail?) + (if + (empty? clauses) + (do (emit-op em 16) (emit-byte em var-slot) (emit-op em 37)) + (let + ((clause (first clauses)) + (rest-clauses (rest clauses)) + (test (first clause)) + (body (rest clause))) + (if + (or + (and + (= (type-of test) "keyword") + (= (keyword-name test) "else")) + (= test true)) + (compile-begin em body scope tail?) + (do + (compile-expr em test scope false) + (emit-op em 33) + (let + ((skip (current-offset em))) + (emit-i16 em 0) + (compile-begin 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))) + (compile-guard-clauses + em + rest-clauses + scope + var-slot + tail?) + (patch-i16 + em + end-jump + (- (current-offset em) (+ end-jump 2))))))))))) (define compile (fn @@ -989,129 +1159,4 @@ {:constants (get (get em "pool") "entries") :bytecode (get em "bytecode")}))))) ;; end define-library ;; Re-export to global namespace for backward compatibility -(define - compile-provide - (fn - (em args scope tail?) - (let - ((first-arg (first args)) - (name - (cond - (= (type-of first-arg) "keyword") - (keyword-name first-arg) - (= (type-of first-arg) "string") - first-arg - :else (symbol-name first-arg))) - (val-expr (nth args 1)) - (body (slice args 2)) - (name-idx (pool-add (get em "pool") name))) - (emit-op em 1) - (emit-u16 em name-idx) - (compile-expr em val-expr scope false) - (emit-op em 52) - (emit-u16 em (pool-add (get em "pool") "scope-push!")) - (emit-byte em 2) - (emit-op em 5) - (if (empty? body) (emit-op em 2) (compile-begin em body scope false)) - (emit-op em 1) - (emit-u16 em name-idx) - (emit-op em 52) - (emit-u16 em (pool-add (get em "pool") "scope-pop!")) - (emit-byte em 1) - (emit-op em 5)))) - -(define - compile-scope - (fn - (em args scope tail?) - (let - ((first-arg (first args)) - (name - (if - (= (type-of first-arg) "keyword") - (keyword-name first-arg) - (symbol-name first-arg))) - (rest-args (rest args)) - (name-idx (pool-add (get em "pool") name))) - (if - (and - (>= (len rest-args) 2) - (= (type-of (first rest-args)) "keyword") - (= (keyword-name (first rest-args)) "value")) - (let - ((val-expr (nth rest-args 1)) (body (slice rest-args 2))) - (emit-op em 1) - (emit-u16 em name-idx) - (compile-expr em val-expr scope false) - (emit-op em 52) - (emit-u16 em (pool-add (get em "pool") "scope-push!")) - (emit-byte em 2) - (emit-op em 5) - (if - (empty? body) - (emit-op em 2) - (compile-begin em body scope false)) - (emit-op em 1) - (emit-u16 em name-idx) - (emit-op em 52) - (emit-u16 em (pool-add (get em "pool") "scope-pop!")) - (emit-byte em 1) - (emit-op em 5)) - (let - ((body rest-args)) - (emit-op em 1) - (emit-u16 em name-idx) - (emit-op em 2) - (emit-op em 52) - (emit-u16 em (pool-add (get em "pool") "scope-push!")) - (emit-byte em 2) - (emit-op em 5) - (if - (empty? body) - (emit-op em 2) - (compile-begin em body scope false)) - (emit-op em 1) - (emit-u16 em name-idx) - (emit-op em 52) - (emit-u16 em (pool-add (get em "pool") "scope-pop!")) - (emit-byte em 1) - (emit-op em 5)))))) - -(define - compile-guard-clauses - (fn - (em clauses scope var-slot tail?) - (if - (empty? clauses) - (do (emit-op em 16) (emit-byte em var-slot) (emit-op em 37)) - (let - ((clause (first clauses)) - (rest-clauses (rest clauses)) - (test (first clause)) - (body (rest clause))) - (if - (or - (and - (= (type-of test) "keyword") - (= (keyword-name test) "else")) - (= test true)) - (compile-begin em body scope tail?) - (do - (compile-expr em test scope false) - (emit-op em 33) - (let - ((skip (current-offset em))) - (emit-i16 em 0) - (compile-begin 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))) - (compile-guard-clauses em rest-clauses scope var-slot tail?) - (patch-i16 - em - end-jump - (- (current-offset em) (+ end-jump 2))))))))))) - (import (sx compiler))