WASM browser build: interned env keys, async kernel boot, bundled .sx platform

- Symbol interning in sx_types.ml: env lookups use int keys (intern/unintern)
  to avoid repeated string hashing in scope chain walks
- sx-platform.js: poll for SxKernel availability (WASM init is async)
- shell.sx: load sx_browser.bc.wasm.js when SX_USE_WASM=1
- bundle.sh: fix .sx file paths (web-signals.sx rename)
- browser/dune: target byte+js+wasm modes
- Bundle 23 .sx platform files for browser (dom, signals, router, boot, etc.)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 16:37:42 +00:00
parent 589507392c
commit 10576f86d1
25 changed files with 4808 additions and 68 deletions

View File

@@ -315,7 +315,7 @@ let make_integration_env () =
let body_env = { bindings = Hashtbl.create 16; parent = Some e } in
List.iteri (fun i p ->
let v = if i < List.length macro_args then List.nth macro_args i else Nil in
Hashtbl.replace body_env.bindings p v
Hashtbl.replace body_env.bindings (Sx_types.intern p) v
) m.m_params;
Sx_ref.eval_expr m.m_body (Env body_env)
| _ -> raise (Eval_error "expand-macro: expected (macro args env)"));

View File

@@ -709,6 +709,7 @@ let rec handle_tool name args =
let selector = args |> member "selector" |> to_string_option in
let expr = args |> member "expr" |> to_string_option in
let actions = args |> member "actions" |> to_string_option in
let island = args |> member "island" |> to_string_option in
(* Determine whether to run specs or the inspector *)
let use_inspector = match mode with
| Some m when m <> "run" -> true
@@ -745,6 +746,7 @@ let rec handle_tool name args =
(match selector with Some s -> Some ("selector", `String s) | None -> None);
(match expr with Some e -> Some ("expr", `String e) | None -> None);
(match actions with Some a -> Some ("actions", `String a) | None -> None);
(match island with Some i -> Some ("island", `String i) | None -> None);
]) in
let args_json = Yojson.Safe.to_string (Yojson.Safe.from_string (Yojson.Basic.to_string inspector_args)) in
let cmd = Printf.sprintf "cd %s && node tests/playwright/sx-inspect.js '%s' 2>&1" project_dir (String.escaped args_json) in
@@ -1154,14 +1156,15 @@ let rec handle_tool name args =
let bindings = ref [] in
(* Walk env chain collecting all bindings *)
let rec collect_bindings env acc =
Hashtbl.iter (fun k v ->
if not (Hashtbl.mem acc k) then Hashtbl.replace acc k v
Hashtbl.iter (fun id v ->
if not (Hashtbl.mem acc id) then Hashtbl.replace acc id v
) env.bindings;
match env.parent with Some p -> collect_bindings p acc | None -> ()
in
let all = Hashtbl.create 256 in
collect_bindings e all;
Hashtbl.iter (fun k v ->
Hashtbl.iter (fun id v ->
let k = Sx_types.unintern id in
let kind = match v with
| NativeFn _ -> "native"
| Lambda _ -> "lambda"
@@ -1351,10 +1354,11 @@ let tool_definitions = `List [
("files", `Assoc [("type", `String "array"); ("items", `Assoc [("type", `String "string")]); ("description", `String "Multiple .sx files to load in order")]);
("setup", `Assoc [("type", `String "string"); ("description", `String "SX setup expression to run before main evaluation")])]
["expr"];
tool "sx_playwright" "Run Playwright browser tests or inspect SX pages interactively. Modes: run (spec files), inspect (page report), diff (SSR vs hydrated), eval (JS expression), interact (action sequence), screenshot."
tool "sx_playwright" "Run Playwright browser tests or inspect SX pages interactively. Modes: run (spec files), inspect (page/island report with leak detection and handler audit), diff (full SSR vs hydrated DOM), hydrate (lake-focused SSR vs hydrated comparison — detects clobbering), eval (JS expression), interact (action sequence), screenshot."
[("spec", `Assoc [("type", `String "string"); ("description", `String "Spec file to run (run mode). e.g. stepper.spec.js")]);
("mode", `Assoc [("type", `String "string"); ("description", `String "Mode: run, inspect, diff, eval, interact, screenshot")]);
("mode", `Assoc [("type", `String "string"); ("description", `String "Mode: run, inspect, diff, hydrate, eval, interact, screenshot")]);
("url", `Assoc [("type", `String "string"); ("description", `String "URL path to navigate to (default: /)")]);
("island", `Assoc [("type", `String "string"); ("description", `String "Filter inspect to a specific island by name (e.g. home/stepper)")]);
("selector", `Assoc [("type", `String "string"); ("description", `String "CSS selector to focus on (screenshot mode)")]);
("expr", `Assoc [("type", `String "string"); ("description", `String "JS expression to evaluate (eval mode)")]);
("actions", `Assoc [("type", `String "string"); ("description", `String "Semicolon-separated action sequence (interact mode). Actions: click:sel, fill:sel:val, wait:ms, text:sel, html:sel, attrs:sel, screenshot, screenshot:sel, count:sel, visible:sel")])]

View File

@@ -407,7 +407,7 @@ let setup_evaluator_bridge env =
let body_env = { bindings = Hashtbl.create 16; parent = Some e } in
List.iteri (fun i p ->
let v = if i < List.length macro_args then List.nth macro_args i else Nil in
Hashtbl.replace body_env.bindings p v
Hashtbl.replace body_env.bindings (Sx_types.intern p) v
) m.m_params;
Sx_ref.eval_expr m.m_body (Env body_env)
| _ -> raise (Eval_error "expand-macro: expected (macro args env)"));
@@ -629,6 +629,12 @@ let setup_html_tags env =
(* Compose environment *)
(* ====================================================================== *)
(** Convert int-keyed env.bindings to string-keyed Hashtbl for VM globals *)
let env_to_vm_globals env =
let g = Hashtbl.create (Hashtbl.length env.Sx_types.bindings) in
Hashtbl.iter (fun id v -> Hashtbl.replace g (Sx_types.unintern id) v) env.Sx_types.bindings;
g
let make_server_env () =
let env = make_env () in
Sx_render.setup_render_env env;
@@ -703,7 +709,7 @@ let register_jit_hook env =
else begin
_jit_compiling := true;
let t0 = Unix.gettimeofday () in
let compiled = Sx_vm.jit_compile_lambda l env.bindings in
let compiled = Sx_vm.jit_compile_lambda l (env_to_vm_globals env) in
let dt = Unix.gettimeofday () -. t0 in
_jit_compiling := false;
Printf.eprintf "[jit] %s compile %s in %.3fs\n%!"
@@ -726,7 +732,7 @@ let register_jit_hook env =
evaluator.sx defines *custom-special-forms* and register-special-form!
which shadow the native bindings from setup_evaluator_bridge. *)
let rebind_host_extensions env =
Hashtbl.replace env.bindings "register-special-form!"
Hashtbl.replace env.bindings (Sx_types.intern "register-special-form!")
(NativeFn ("register-special-form!", fun args ->
match args with
| [String name; handler] ->
@@ -814,7 +820,7 @@ let rec dispatch env cmd =
| List [Symbol "vm-reset-fn"; String name] ->
(* Reset a function's JIT-compiled bytecode, forcing CEK interpretation.
Used to work around JIT compilation bugs in specific functions. *)
(match Hashtbl.find_opt env.bindings name with
(match Hashtbl.find_opt env.bindings (Sx_types.intern name) with
| Some (Lambda l) ->
l.l_compiled <- Some Sx_vm.jit_failed_sentinel;
Printf.eprintf "[jit] reset %s (forced CEK)\n%!" name;
@@ -889,10 +895,10 @@ let rec dispatch env cmd =
"current-offset"; "patch-i16";
] in
List.iter (fun name ->
match Hashtbl.find_opt env.bindings name with
match Hashtbl.find_opt env.bindings (Sx_types.intern name) with
| Some (Lambda l) when l.l_compiled = None ->
l.l_compiled <- Some Sx_vm.jit_failed_sentinel;
(match Sx_vm.jit_compile_lambda l env.bindings with
(match Sx_vm.jit_compile_lambda l (env_to_vm_globals env) with
| Some cl -> l.l_compiled <- Some cl; incr count
| None -> ())
| _ -> ()
@@ -937,7 +943,7 @@ let rec dispatch env cmd =
in
let t1 = Unix.gettimeofday () in
io_batch_mode := false;
Hashtbl.remove env.bindings "expand-components?";
Hashtbl.remove env.bindings (Sx_types.intern "expand-components?");
let result_str = match result with
| String s | SxExpr s -> s
| _ -> serialize_value result
@@ -953,12 +959,12 @@ let rec dispatch env cmd =
| Eval_error msg ->
io_batch_mode := false;
io_queue := [];
Hashtbl.remove env.bindings "expand-components?";
Hashtbl.remove env.bindings (Sx_types.intern "expand-components?");
send_error msg
| exn ->
io_batch_mode := false;
io_queue := [];
Hashtbl.remove env.bindings "expand-components?";
Hashtbl.remove env.bindings (Sx_types.intern "expand-components?");
send_error (Printexc.to_string exn))
| List (Symbol "sx-page-full-blob" :: shell_kwargs) ->
@@ -991,7 +997,7 @@ let rec dispatch env cmd =
in
let t1 = Unix.gettimeofday () in
io_batch_mode := false;
Hashtbl.remove env.bindings "expand-components?";
Hashtbl.remove env.bindings (Sx_types.intern "expand-components?");
let body_str = match body_result with
| String s | SxExpr s -> s
| _ -> serialize_value body_result
@@ -1038,12 +1044,12 @@ let rec dispatch env cmd =
| Eval_error msg ->
io_batch_mode := false;
io_queue := [];
Hashtbl.remove env.bindings "expand-components?";
Hashtbl.remove env.bindings (Sx_types.intern "expand-components?");
send_error msg
| exn ->
io_batch_mode := false;
io_queue := [];
Hashtbl.remove env.bindings "expand-components?";
Hashtbl.remove env.bindings (Sx_types.intern "expand-components?");
send_error (Printexc.to_string exn))
| List [Symbol "render"; String src] ->
@@ -1065,8 +1071,7 @@ let rec dispatch env cmd =
code_val is a dict with {bytecode, pool} from compiler.sx *)
(try
let code = Sx_vm.code_from_value code_val in
let globals = Hashtbl.create 256 in
Hashtbl.iter (fun k v -> Hashtbl.replace globals k v) env.bindings;
let globals = env_to_vm_globals env in
let result = Sx_vm.execute_module code globals in
send_ok_value result
with
@@ -1081,8 +1086,10 @@ let rec dispatch env cmd =
(try
let code = Sx_vm.code_from_value code_val in
(* VM uses the LIVE kernel env — defines go directly into it *)
let _result = Sx_vm.execute_module code env.bindings in
(* Count how many defines the module added *)
let globals = env_to_vm_globals env in
let _result = Sx_vm.execute_module code globals in
(* Copy defines back into env *)
Hashtbl.iter (fun k v -> Hashtbl.replace env.bindings (Sx_types.intern k) v) globals;
send_ok ()
with
| Eval_error msg -> send_error msg