Merge hs-f into architecture: JIT Phase 2/3 + native unwrap sweep + dict-eq fix

JIT Phase 2 (LRU eviction) + Phase 3 (manual reset), lib/jit.sx convenience layer,
21 host-* natives ABI-compatible with WASM kernel handles, dict-eq fix (structural
eq for plain dicts + Integer/Number in equal?), io-wait-event interceptor fix,
HS test runner unwrap shim for post-JIT-P1 value handles.

Conflicts resolved:
- tests/hs-run-filtered.js: combined arch's fake-timer block (for socket RPC tests)
  with hs-f's auto-unwrap shim
- shared/static/wasm/sx_browser.bc.js: took hs-f's regenerated bundle
This commit is contained in:
2026-05-14 20:10:42 +00:00
10 changed files with 5246 additions and 5596 deletions

View File

@@ -759,11 +759,22 @@ let () =
(List lb | ListRef { contents = lb }) ->
List.length la = List.length lb &&
List.for_all2 safe_eq la lb
(* Dict: check __host_handle for DOM node identity *)
(* Dict: __host_handle identity for DOM-wrapped dicts; otherwise
structural equality over keys + values. *)
| Dict a, Dict b ->
(match Hashtbl.find_opt a "__host_handle", Hashtbl.find_opt b "__host_handle" with
| Some (Number ha), Some (Number hb) -> ha = hb
| _ -> false)
| Some _, _ | _, Some _ -> false
| None, None ->
Hashtbl.length a = Hashtbl.length b &&
(let eq = ref true in
Hashtbl.iter (fun k v ->
if !eq then
match Hashtbl.find_opt b k with
| Some v' -> if not (safe_eq v v') then eq := false
| None -> eq := false
) a;
!eq))
(* Records: same type + structurally equal fields *)
| Record a, Record b ->
a.r_type.rt_uid = b.r_type.rt_uid &&
@@ -4117,17 +4128,34 @@ let () =
register "jit-stats" (fun _args ->
let d = Hashtbl.create 8 in
Hashtbl.replace d "threshold" (Number (float_of_int !Sx_types.jit_threshold));
Hashtbl.replace d "budget" (Number (float_of_int !Sx_types.jit_budget));
Hashtbl.replace d "cache-size" (Number (float_of_int (Sx_types.jit_cache_size ())));
Hashtbl.replace d "compiled" (Number (float_of_int !Sx_types.jit_compiled_count));
Hashtbl.replace d "compile-failed" (Number (float_of_int !Sx_types.jit_skipped_count));
Hashtbl.replace d "below-threshold" (Number (float_of_int !Sx_types.jit_threshold_skipped_count));
Hashtbl.replace d "evicted" (Number (float_of_int !Sx_types.jit_evicted_count));
Dict d);
register "jit-set-threshold!" (fun args ->
match args with
| [Number n] -> Sx_types.jit_threshold := int_of_float n; Nil
| [Integer n] -> Sx_types.jit_threshold := n; Nil
| _ -> raise (Eval_error "jit-set-threshold!: (n) where n is integer"));
register "jit-set-budget!" (fun args ->
match args with
| [Number n] -> Sx_types.jit_budget := int_of_float n; Nil
| [Integer n] -> Sx_types.jit_budget := n; Nil
| _ -> raise (Eval_error "jit-set-budget!: (n) where n is integer"));
register "jit-reset-cache!" (fun _args ->
(* Phase 3 manual cache reset — clear all compiled VmClosures.
Hot paths will re-JIT on next call (after re-hitting threshold). *)
Queue.iter (fun (_, v) ->
match v with Lambda l -> l.l_compiled <- None | _ -> ()
) Sx_types.jit_cache_queue;
Queue.clear Sx_types.jit_cache_queue;
Nil);
register "jit-reset-counters!" (fun _args ->
Sx_types.jit_compiled_count := 0;
Sx_types.jit_skipped_count := 0;
Sx_types.jit_threshold_skipped_count := 0;
Sx_types.jit_evicted_count := 0;
Nil)