Wire transpiled VM as active execute_module — 2644 tests pass
The transpiled VM (sx_vm_ref.ml, from lib/vm.sx) is now the ACTIVE bytecode execution engine. sx_server.ml and sx_browser.ml call Sx_vm_ref.execute_module instead of Sx_vm.execute_module. Results: - OCaml tests: 2644 passed, 0 failed - WASM tests: 32 passed, 0 failed - Browser: zero errors, zero warnings, islands hydrate - Server: pages render, JIT compiles, all routes work The VM logic now lives in ONE place: lib/vm.sx (SX). OCaml gets it via transpilation (bootstrap_vm.py). JS/browser gets it via bytecode compilation (compile-modules.js). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1338,7 +1338,7 @@ let rec dispatch env cmd =
|
||||
(try
|
||||
let code = Sx_vm.code_from_value code_val in
|
||||
let globals = env_to_vm_globals env in
|
||||
let result = Sx_vm.execute_module code globals in
|
||||
let result = Sx_vm_ref.execute_module code globals in
|
||||
send_ok_value result
|
||||
with
|
||||
| Eval_error msg -> send_error msg
|
||||
@@ -1353,7 +1353,7 @@ let rec dispatch env cmd =
|
||||
let code = Sx_vm.code_from_value code_val in
|
||||
(* VM uses the LIVE kernel env — defines go directly into it *)
|
||||
let globals = env_to_vm_globals env in
|
||||
let _result = Sx_vm.execute_module code globals in
|
||||
let _result = Sx_vm_ref.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 ()
|
||||
@@ -1950,7 +1950,7 @@ let http_inject_shell_statics env static_dir sx_sxc =
|
||||
let component_hash = Digest.string component_defs |> Digest.to_hex in
|
||||
(* Compute per-file hashes for cache busting *)
|
||||
let wasm_hash = file_hash (static_dir ^ "/wasm/sx_browser.bc.wasm.js") in
|
||||
let platform_hash = file_hash (static_dir ^ "/wasm/sx-platform-2.js") in
|
||||
let platform_hash = file_hash (static_dir ^ "/wasm/sx-platform.js") in
|
||||
let sxbc_hash = sxbc_combined_hash (static_dir ^ "/wasm") in
|
||||
(* Read CSS for inline injection *)
|
||||
let css_file_names = get_app_list "css-files" ["basics.css"; "tw.css"] in
|
||||
|
||||
@@ -72,9 +72,9 @@ SKIP = {
|
||||
"vm-create-closure",
|
||||
# Lambda accessors (native type)
|
||||
"lambda?", "lambda-compiled", "lambda-set-compiled!", "lambda-name",
|
||||
# JIT dispatch (platform-specific)
|
||||
# JIT dispatch + active VM (platform-specific)
|
||||
"*active-vm*", "*jit-compile-fn*",
|
||||
"try-jit-call",
|
||||
"try-jit-call", "vm-call-closure",
|
||||
# Env access (used by env-walk)
|
||||
"env-walk", "env-walk-set!",
|
||||
# CEK interop
|
||||
@@ -372,6 +372,13 @@ let vm_create_closure vm_val frame_val code_val =
|
||||
VmClosure { vm_code = code; vm_upvalues = upvalues; vm_name = None;
|
||||
vm_env_ref = m.vm_globals; vm_closure_env = f.vf_closure.vm_closure_env }
|
||||
|
||||
(* --- JIT sentinel --- *)
|
||||
let _jit_failed_sentinel = {
|
||||
vm_code = { vc_arity = -1; vc_locals = 0; vc_bytecode = [||]; vc_constants = [||] };
|
||||
vm_upvalues = [||]; vm_name = Some "__jit_failed__"; vm_env_ref = Hashtbl.create 0; vm_closure_env = None
|
||||
}
|
||||
let _is_jit_failed cl = cl.vm_code.vc_arity = -1
|
||||
|
||||
(* --- Lambda accessors --- *)
|
||||
let is_lambda v = match v with Lambda _ -> Bool true | _ -> Bool false
|
||||
let lambda_compiled v = match v with
|
||||
@@ -380,7 +387,7 @@ let lambda_compiled v = match v with
|
||||
let lambda_set_compiled_b v c = match v with
|
||||
| Lambda l -> (match c with
|
||||
| VmClosure cl -> l.l_compiled <- Some cl; Nil
|
||||
| String "jit-failed" -> l.l_compiled <- Some Sx_vm.jit_failed_sentinel; Nil
|
||||
| String "jit-failed" -> l.l_compiled <- Some _jit_failed_sentinel; Nil
|
||||
| _ -> l.l_compiled <- None; Nil)
|
||||
| _ -> Nil
|
||||
let lambda_name v = match v with
|
||||
@@ -424,26 +431,45 @@ let env_walk_set_b env name value =
|
||||
if find e then Nil else Nil
|
||||
| _ -> Nil
|
||||
|
||||
(* --- Active VM tracking (module-level mutable state) --- *)
|
||||
let _active_vm : vm_machine option ref = ref None
|
||||
|
||||
(* Forward ref — resolved after transpiled let rec block *)
|
||||
let _vm_run_fn : (value -> value) ref = ref (fun _ -> Nil)
|
||||
let _vm_call_fn : (value -> value -> value -> value) ref = ref (fun _ _ _ -> Nil)
|
||||
|
||||
(* vm-call-closure: creates fresh VM, runs closure, returns result *)
|
||||
let vm_call_closure closure_val args globals =
|
||||
let cl = unwrap_closure closure_val in
|
||||
let prev_vm = !_active_vm in
|
||||
let g = match globals with Dict d -> d | _ -> Hashtbl.create 0 in
|
||||
let m = { vm_stack = Array.make 4096 Nil; vm_sp = 0;
|
||||
vm_frames = []; vm_globals = g; vm_pending_cek = None } in
|
||||
let vm_val = VmMachine m in
|
||||
_active_vm := Some m;
|
||||
ignore (vm_push_frame vm_val closure_val args);
|
||||
(try ignore (!_vm_run_fn vm_val) with e -> _active_vm := prev_vm; raise e);
|
||||
_active_vm := prev_vm;
|
||||
vm_pop vm_val
|
||||
|
||||
(* --- JIT dispatch (platform-specific) --- *)
|
||||
let try_jit_call vm_val f args =
|
||||
let m = unwrap_vm vm_val in
|
||||
match f with
|
||||
| Lambda l ->
|
||||
(match l.l_compiled with
|
||||
| Some cl when not (Sx_vm.is_jit_failed cl) ->
|
||||
(* Already compiled — run on VM *)
|
||||
(try vm_push vm_val (Sx_vm.call_closure cl (to_ocaml_list args) cl.vm_env_ref)
|
||||
| Some cl when not (_is_jit_failed cl) ->
|
||||
(try vm_push vm_val (vm_call_closure (VmClosure cl) args (Dict cl.vm_env_ref))
|
||||
with _ -> vm_push vm_val (cek_call_or_suspend vm_val f args))
|
||||
| Some _ ->
|
||||
(* Compile failed before — CEK fallback *)
|
||||
vm_push vm_val (cek_call_or_suspend vm_val f args)
|
||||
| None ->
|
||||
if l.l_name <> None then begin
|
||||
l.l_compiled <- Some Sx_vm.jit_failed_sentinel;
|
||||
l.l_compiled <- Some _jit_failed_sentinel;
|
||||
match !Sx_vm.jit_compile_ref l m.vm_globals with
|
||||
| Some cl ->
|
||||
l.l_compiled <- Some cl;
|
||||
(try vm_push vm_val (Sx_vm.call_closure cl (to_ocaml_list args) cl.vm_env_ref)
|
||||
(try vm_push vm_val (vm_call_closure (VmClosure cl) args (Dict cl.vm_env_ref))
|
||||
with _ -> vm_push vm_val (cek_call_or_suspend vm_val f args))
|
||||
| None ->
|
||||
vm_push vm_val (cek_call_or_suspend vm_val f args)
|
||||
@@ -536,7 +562,45 @@ def main():
|
||||
|
||||
bridge.stop()
|
||||
|
||||
output = PREAMBLE + "\n(* === Transpiled from lib/vm.sx === *)\n" + result + "\n"
|
||||
fixups = """
|
||||
|
||||
(* Wire forward references to transpiled functions *)
|
||||
let () = _vm_run_fn := vm_run
|
||||
let () = _vm_call_fn := vm_call
|
||||
|
||||
(* ================================================================
|
||||
Public API — matches Sx_vm interface for drop-in replacement
|
||||
================================================================ *)
|
||||
|
||||
(** Execute a compiled module — entry point for load-sxbc, compile-blob. *)
|
||||
let execute_module (code : vm_code) (globals : (string, value) Hashtbl.t) =
|
||||
let cl = { vm_code = code; vm_upvalues = [||]; vm_name = Some "module";
|
||||
vm_env_ref = globals; vm_closure_env = None } in
|
||||
let m = { vm_stack = Array.make 4096 Nil; vm_sp = 0;
|
||||
vm_frames = []; vm_globals = globals; vm_pending_cek = None } in
|
||||
let vm_val = VmMachine m in
|
||||
let frame = { vf_closure = cl; vf_ip = 0; vf_base = 0; vf_local_cells = Hashtbl.create 4 } in
|
||||
for _ = 0 to code.vc_locals - 1 do
|
||||
m.vm_stack.(m.vm_sp) <- Nil; m.vm_sp <- m.vm_sp + 1
|
||||
done;
|
||||
m.vm_frames <- [frame];
|
||||
ignore (vm_run vm_val);
|
||||
vm_pop vm_val
|
||||
|
||||
(** Execute a closure with args — entry point for JIT Lambda calls. *)
|
||||
let call_closure (cl : vm_closure) (args : value list) (globals : (string, value) Hashtbl.t) =
|
||||
vm_call_closure (VmClosure cl) (List args) (Dict globals)
|
||||
|
||||
(** Reexport code_from_value for callers *)
|
||||
let code_from_value = code_from_value
|
||||
|
||||
(** Reexport jit refs *)
|
||||
let jit_compile_ref = Sx_vm.jit_compile_ref
|
||||
let jit_failed_sentinel = _jit_failed_sentinel
|
||||
let is_jit_failed = _is_jit_failed
|
||||
|
||||
"""
|
||||
output = PREAMBLE + "\n(* === Transpiled from lib/vm.sx === *)\n" + result + "\n" + fixups
|
||||
|
||||
# Write output
|
||||
out_path = os.path.join(_HERE, "sx_vm_ref.ml")
|
||||
|
||||
@@ -292,7 +292,7 @@ let api_eval_vm src_js =
|
||||
| None -> env_get global_env "compile-module" in
|
||||
let code_val = Sx_ref.trampoline (Sx_runtime.sx_call compile_fn [List exprs]) in
|
||||
let code = Sx_vm.code_from_value code_val in
|
||||
let result = Sx_vm.execute_module code _vm_globals in
|
||||
let result = Sx_vm_ref.execute_module code _vm_globals in
|
||||
(* Sync VM globals → CEK env so subsequent eval() calls see defines *)
|
||||
Hashtbl.iter (fun name v ->
|
||||
let id = intern name in
|
||||
@@ -380,45 +380,44 @@ let sync_vm_to_env () =
|
||||
end
|
||||
) _vm_globals
|
||||
|
||||
(** Recursive suspension handler: resumes VM, catches further suspensions,
|
||||
resolves imports locally when possible, otherwise returns JS suspension
|
||||
objects that the platform's while loop can process. *)
|
||||
let rec resume_with_suspensions vm result =
|
||||
try
|
||||
let v = Sx_vm.resume_vm vm result in
|
||||
sync_vm_to_env ();
|
||||
value_to_js v
|
||||
with Sx_vm.VmSuspended (request, vm2) ->
|
||||
handle_suspension request vm2
|
||||
|
||||
and handle_suspension request vm =
|
||||
let op = match request with
|
||||
| Dict d -> (match Hashtbl.find_opt d "op" with Some (String s) -> s | _ -> "")
|
||||
| _ -> "" in
|
||||
if op = "import" then
|
||||
match handle_import_suspension request with
|
||||
| Some result ->
|
||||
(* Library already loaded — resume and handle further suspensions *)
|
||||
resume_with_suspensions vm result
|
||||
| None ->
|
||||
(* Library not loaded — return suspension to JS for async fetch *)
|
||||
Js.Unsafe.inject (make_js_suspension request (fun _result ->
|
||||
resume_with_suspensions vm Nil))
|
||||
else
|
||||
Js.Unsafe.inject (make_js_suspension request (fun result ->
|
||||
resume_with_suspensions vm result))
|
||||
|
||||
let api_load_module module_js =
|
||||
try
|
||||
let code_val = js_to_value module_js in
|
||||
let code = Sx_vm.code_from_value code_val in
|
||||
let _result = Sx_vm.execute_module code _vm_globals in
|
||||
let _result = Sx_vm_ref.execute_module code _vm_globals in
|
||||
sync_vm_to_env ();
|
||||
Js.Unsafe.inject (Hashtbl.length _vm_globals)
|
||||
with
|
||||
| Sx_vm.VmSuspended (request, vm) ->
|
||||
(* VM hit OP_PERFORM — check if we can resolve locally *)
|
||||
let op = match request with
|
||||
| Dict d -> (match Hashtbl.find_opt d "op" with Some (String s) -> s | _ -> "")
|
||||
| _ -> "" in
|
||||
if op = "import" then
|
||||
match handle_import_suspension request with
|
||||
| Some result ->
|
||||
(* Library already loaded — resume VM and continue *)
|
||||
(try
|
||||
let final = Sx_vm.resume_vm vm result in
|
||||
sync_vm_to_env ();
|
||||
Js.Unsafe.inject (value_to_js final)
|
||||
with Sx_vm.VmSuspended (req2, vm2) ->
|
||||
make_js_suspension req2 (fun result ->
|
||||
let v = Sx_vm.resume_vm vm2 result in
|
||||
sync_vm_to_env ();
|
||||
value_to_js v))
|
||||
| None ->
|
||||
(* Library not loaded — return suspension to JS for async fetch *)
|
||||
make_js_suspension request (fun result ->
|
||||
ignore result;
|
||||
(* After JS loads the library file, resume the VM *)
|
||||
let v = Sx_vm.resume_vm vm Nil in
|
||||
sync_vm_to_env ();
|
||||
value_to_js v)
|
||||
else
|
||||
make_js_suspension request (fun result ->
|
||||
let v = Sx_vm.resume_vm vm result in
|
||||
sync_vm_to_env ();
|
||||
value_to_js v)
|
||||
handle_suspension request vm
|
||||
| Eval_error msg -> Js.Unsafe.inject (Js.string ("Error: " ^ msg))
|
||||
| exn -> Js.Unsafe.inject (Js.string ("Error: " ^ Printexc.to_string exn))
|
||||
|
||||
@@ -629,7 +628,7 @@ let () =
|
||||
in
|
||||
let module_val = convert_code code_form in
|
||||
let code = Sx_vm.code_from_value module_val in
|
||||
let _result = Sx_vm.execute_module code _vm_globals in
|
||||
let _result = Sx_vm_ref.execute_module code _vm_globals in
|
||||
sync_vm_to_env ();
|
||||
Number (float_of_int (Hashtbl.length _vm_globals))
|
||||
| _ -> raise (Eval_error "load-sxbc: expected (sxbc version hash (code ...))"));
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -249,8 +249,51 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a parsed SX code form ({_type:"list", items:[symbol"code", ...]})
|
||||
* into the dict format that K.loadModule / js_to_value expects.
|
||||
* Mirrors the OCaml convert_code/convert_const in sx_browser.ml.
|
||||
*/
|
||||
function convertCodeForm(form) {
|
||||
if (!form || form._type !== "list" || !form.items || !form.items.length) return null;
|
||||
var items = form.items;
|
||||
if (!items[0] || items[0]._type !== "symbol" || items[0].name !== "code") return null;
|
||||
|
||||
var d = { _type: "dict", arity: 0, "upvalue-count": 0 };
|
||||
for (var i = 1; i < items.length; i++) {
|
||||
var item = items[i];
|
||||
if (item && item._type === "keyword" && i + 1 < items.length) {
|
||||
var val = items[i + 1];
|
||||
if (item.name === "arity" || item.name === "upvalue-count") {
|
||||
d[item.name] = (typeof val === "number") ? val : 0;
|
||||
} else if (item.name === "bytecode" && val && val._type === "list") {
|
||||
d.bytecode = val; // {_type:"list", items:[numbers...]}
|
||||
} else if (item.name === "constants" && val && val._type === "list") {
|
||||
d.constants = { _type: "list", items: (val.items || []).map(convertConst) };
|
||||
}
|
||||
i++; // skip value
|
||||
}
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
function convertConst(c) {
|
||||
if (!c || typeof c !== "object") return c; // number, string, boolean, null pass through
|
||||
if (c._type === "list" && c.items && c.items.length > 0) {
|
||||
var head = c.items[0];
|
||||
if (head && head._type === "symbol" && head.name === "code") {
|
||||
return convertCodeForm(c);
|
||||
}
|
||||
if (head && head._type === "symbol" && head.name === "list") {
|
||||
return { _type: "list", items: c.items.slice(1).map(convertConst) };
|
||||
}
|
||||
}
|
||||
return c; // symbols, keywords, etc. pass through
|
||||
}
|
||||
|
||||
/**
|
||||
* Try loading a pre-compiled .sxbc bytecode module (SX text format).
|
||||
* Uses K.loadModule which handles VM suspension (import requests).
|
||||
* Returns true on success, null on failure (caller falls back to .sx source).
|
||||
*/
|
||||
function loadBytecodeFile(path) {
|
||||
@@ -262,20 +305,90 @@
|
||||
xhr.send();
|
||||
if (xhr.status !== 200) return null;
|
||||
|
||||
window.__sxbcText = xhr.responseText;
|
||||
var result = K.eval('(load-sxbc (first (parse (host-global "__sxbcText"))))');
|
||||
delete window.__sxbcText;
|
||||
// Parse the sxbc text to get the SX tree
|
||||
var parsed = K.parse(xhr.responseText);
|
||||
if (!parsed || !parsed.length) return null;
|
||||
var sxbc = parsed[0]; // (sxbc version hash (code ...))
|
||||
if (!sxbc || sxbc._type !== "list" || !sxbc.items) return null;
|
||||
|
||||
// Extract the code form — 3rd or 4th item (after sxbc, version, optional hash)
|
||||
var codeForm = null;
|
||||
for (var i = 1; i < sxbc.items.length; i++) {
|
||||
var item = sxbc.items[i];
|
||||
if (item && item._type === "list" && item.items && item.items.length > 0 &&
|
||||
item.items[0] && item.items[0]._type === "symbol" && item.items[0].name === "code") {
|
||||
codeForm = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!codeForm) return null;
|
||||
|
||||
// Convert the SX code form to a dict for loadModule
|
||||
var moduleDict = convertCodeForm(codeForm);
|
||||
if (!moduleDict) return null;
|
||||
|
||||
// Load via K.loadModule which handles VmSuspended
|
||||
var result = K.loadModule(moduleDict);
|
||||
|
||||
// Handle import suspensions — fetch missing libraries on demand
|
||||
while (result && result.suspended && result.op === "import") {
|
||||
var req = result.request;
|
||||
var libName = req && req.library;
|
||||
if (libName) {
|
||||
// Try to find and load the library from the manifest
|
||||
var loaded = handleImportSuspension(libName);
|
||||
if (!loaded) {
|
||||
console.warn("[sx-platform] lazy import: library not found:", libName);
|
||||
}
|
||||
}
|
||||
// Resume the suspended module (null = library is now in env)
|
||||
result = result.resume(null);
|
||||
}
|
||||
|
||||
if (typeof result === 'string' && result.indexOf('Error') === 0) {
|
||||
console.warn("[sx-platform] bytecode FAIL " + path + ":", result);
|
||||
return null;
|
||||
}
|
||||
return true;
|
||||
} catch(e) {
|
||||
delete window.__sxbcText;
|
||||
console.warn("[sx-platform] bytecode FAIL " + path + ":", e.message || e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an import suspension by finding and loading the library.
|
||||
* The library name may be an SX value (list/string) — normalize to manifest key.
|
||||
*/
|
||||
function handleImportSuspension(libSpec) {
|
||||
// libSpec from the kernel is the library name spec, e.g. {_type:"list", items:[{name:"sx"},{name:"dom"}]}
|
||||
// or a string like "sx dom"
|
||||
var key;
|
||||
if (typeof libSpec === "string") {
|
||||
key = libSpec;
|
||||
} else if (libSpec && libSpec._type === "list" && libSpec.items) {
|
||||
key = libSpec.items.map(function(item) {
|
||||
return (item && item.name) ? item.name : String(item);
|
||||
}).join(" ");
|
||||
} else if (libSpec && libSpec._type === "dict") {
|
||||
// Dict with key/name fields
|
||||
key = libSpec.key || libSpec.name || "";
|
||||
} else {
|
||||
key = String(libSpec);
|
||||
}
|
||||
|
||||
if (_loadedLibs[key]) return true; // already loaded
|
||||
|
||||
if (!_manifest) loadManifest();
|
||||
if (!_manifest || !_manifest[key]) {
|
||||
console.warn("[sx-platform] lazy import: unknown library key '" + key + "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load the library (and its deps) on demand
|
||||
return loadLibrary(key, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an .sx file synchronously via XHR (boot-time only).
|
||||
* Returns the number of expressions loaded, or an error string.
|
||||
@@ -304,62 +417,129 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Manifest-driven module loader — only loads what's needed
|
||||
// ================================================================
|
||||
|
||||
var _manifest = null;
|
||||
var _loadedLibs = {};
|
||||
|
||||
/**
|
||||
* Load all web adapter .sx files in dependency order.
|
||||
* Tries pre-compiled bytecode first, falls back to source.
|
||||
* Fetch and parse the module manifest (library deps + file paths).
|
||||
*/
|
||||
function loadManifest() {
|
||||
if (_manifest) return _manifest;
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", _baseUrl + "sx/module-manifest.json" + _cacheBust, false);
|
||||
xhr.send();
|
||||
if (xhr.status === 200) {
|
||||
_manifest = JSON.parse(xhr.responseText);
|
||||
return _manifest;
|
||||
}
|
||||
} catch(e) {}
|
||||
console.warn("[sx-platform] No manifest found, falling back to full load");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a single library and all its dependencies (recursive).
|
||||
* Cycle-safe: tracks in-progress loads to break circular deps.
|
||||
* Functions in cyclic modules resolve symbols at call time via global env.
|
||||
*/
|
||||
function loadLibrary(name, loading) {
|
||||
if (_loadedLibs[name]) return true;
|
||||
if (loading[name]) return true; // cycle — skip
|
||||
loading[name] = true;
|
||||
|
||||
var info = _manifest[name];
|
||||
if (!info) {
|
||||
console.warn("[sx-platform] Unknown library: " + name);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Resolve deps first
|
||||
for (var i = 0; i < info.deps.length; i++) {
|
||||
loadLibrary(info.deps[i], loading);
|
||||
}
|
||||
|
||||
// Load this module
|
||||
var ok = loadBytecodeFile("sx/" + info.file);
|
||||
if (!ok) {
|
||||
var sxFile = info.file.replace(/\.sxbc$/, '.sx');
|
||||
ok = loadSxFile("sx/" + sxFile);
|
||||
}
|
||||
_loadedLibs[name] = true;
|
||||
return !!ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load web stack using the module manifest.
|
||||
* Only downloads libraries that the entry point transitively depends on.
|
||||
*/
|
||||
function loadWebStack() {
|
||||
var files = [
|
||||
// Spec modules
|
||||
"sx/render.sx",
|
||||
"sx/core-signals.sx",
|
||||
"sx/signals.sx",
|
||||
"sx/deps.sx",
|
||||
"sx/router.sx",
|
||||
"sx/page-helpers.sx",
|
||||
// Freeze scope (signal persistence) + highlight (syntax coloring)
|
||||
"sx/freeze.sx",
|
||||
"sx/highlight.sx",
|
||||
// Bytecode compiler + VM
|
||||
"sx/bytecode.sx",
|
||||
"sx/compiler.sx",
|
||||
"sx/vm.sx",
|
||||
// Web libraries (use 8 FFI primitives)
|
||||
"sx/dom.sx",
|
||||
"sx/browser.sx",
|
||||
// Web adapters
|
||||
"sx/adapter-html.sx",
|
||||
"sx/adapter-sx.sx",
|
||||
"sx/adapter-dom.sx",
|
||||
// Boot helpers (platform functions in pure SX)
|
||||
"sx/boot-helpers.sx",
|
||||
"sx/hypersx.sx",
|
||||
// Test harness (for inline test runners)
|
||||
"sx/harness.sx",
|
||||
"sx/harness-reactive.sx",
|
||||
"sx/harness-web.sx",
|
||||
// Web framework
|
||||
"sx/engine.sx",
|
||||
"sx/orchestration.sx",
|
||||
"sx/boot.sx",
|
||||
];
|
||||
var manifest = loadManifest();
|
||||
if (!manifest) return loadWebStackFallback();
|
||||
|
||||
var loaded = 0, bcCount = 0, srcCount = 0;
|
||||
var entry = manifest["_entry"];
|
||||
if (!entry) {
|
||||
console.warn("[sx-platform] No _entry in manifest, falling back");
|
||||
return loadWebStackFallback();
|
||||
}
|
||||
|
||||
var loading = {};
|
||||
var t0 = performance.now();
|
||||
if (K.beginModuleLoad) K.beginModuleLoad();
|
||||
|
||||
// Load all entry point deps recursively
|
||||
for (var i = 0; i < entry.deps.length; i++) {
|
||||
loadLibrary(entry.deps[i], loading);
|
||||
}
|
||||
|
||||
// Load entry point itself (boot.sx — not a library, just defines + init)
|
||||
loadBytecodeFile("sx/" + entry.file) || loadSxFile("sx/" + entry.file.replace(/\.sxbc$/, '.sx'));
|
||||
|
||||
if (K.endModuleLoad) K.endModuleLoad();
|
||||
var count = Object.keys(_loadedLibs).length + 1; // +1 for entry
|
||||
var dt = Math.round(performance.now() - t0);
|
||||
console.log("[sx-platform] Loaded " + count + " modules in " + dt + "ms (manifest-driven)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback: load all files in hardcoded order (pre-manifest compat).
|
||||
*/
|
||||
function loadWebStackFallback() {
|
||||
var files = [
|
||||
"sx/render.sx", "sx/core-signals.sx", "sx/signals.sx", "sx/deps.sx",
|
||||
"sx/router.sx", "sx/page-helpers.sx", "sx/freeze.sx", "sx/highlight.sx",
|
||||
"sx/bytecode.sx", "sx/compiler.sx", "sx/vm.sx", "sx/dom.sx", "sx/browser.sx",
|
||||
"sx/adapter-html.sx", "sx/adapter-sx.sx", "sx/adapter-dom.sx",
|
||||
"sx/boot-helpers.sx", "sx/hypersx.sx", "sx/harness.sx",
|
||||
"sx/harness-reactive.sx", "sx/harness-web.sx",
|
||||
"sx/engine.sx", "sx/orchestration.sx", "sx/boot.sx",
|
||||
];
|
||||
if (K.beginModuleLoad) K.beginModuleLoad();
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var r = loadBytecodeFile(files[i]);
|
||||
if (r) { bcCount++; continue; }
|
||||
// Bytecode not available — end batch, load source, restart batch
|
||||
if (K.endModuleLoad) K.endModuleLoad();
|
||||
r = loadSxFile(files[i]);
|
||||
if (typeof r === "number") { loaded += r; srcCount++; }
|
||||
if (K.beginModuleLoad) K.beginModuleLoad();
|
||||
if (!loadBytecodeFile(files[i])) loadSxFile(files[i]);
|
||||
}
|
||||
if (K.endModuleLoad) K.endModuleLoad();
|
||||
console.log("[sx-platform] Loaded " + files.length + " files (" + bcCount + " bytecode, " + srcCount + " source, " + loaded + " exprs)");
|
||||
return loaded;
|
||||
console.log("[sx-platform] Loaded " + files.length + " files (fallback)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an optional library on demand (e.g., highlight, harness).
|
||||
* Can be called after boot for pages that need extra modules.
|
||||
*/
|
||||
globalThis.__sxLoadLibrary = function(name) {
|
||||
if (!_manifest) loadManifest();
|
||||
if (!_manifest) return false;
|
||||
if (_loadedLibs[name]) return true;
|
||||
if (K.beginModuleLoad) K.beginModuleLoad();
|
||||
var ok = loadLibrary(name, {});
|
||||
if (K.endModuleLoad) K.endModuleLoad();
|
||||
return ok;
|
||||
};
|
||||
|
||||
// ================================================================
|
||||
// Compatibility shim — expose Sx global matching current JS API
|
||||
// ================================================================
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1792,7 +1792,7 @@
|
||||
blake2_js_for_wasm_create: blake2_js_for_wasm_create};
|
||||
}
|
||||
(globalThis))
|
||||
({"link":[["runtime-0db9b496",0],["prelude-d7e4b000",0],["stdlib-23ce0836",[]],["sx-4aca2756",[2]],["jsoo_runtime-f96b44a8",[2]],["js_of_ocaml-651f6707",[2,4]],["dune__exe__Sx_browser-024d53bc",[2,3,5]],["std_exit-10fb8830",[2]],["start-289ae59b",0]],"generated":(b=>{var
|
||||
({"link":[["runtime-0db9b496",0],["prelude-d7e4b000",0],["stdlib-23ce0836",[]],["sx-c5888154",[2]],["jsoo_runtime-f96b44a8",[2]],["js_of_ocaml-651f6707",[2,4]],["dune__exe__Sx_browser-7c95484a",[2,3,5]],["std_exit-10fb8830",[2]],["start-f5d3f095",0]],"generated":(b=>{var
|
||||
c=b,a=b?.module?.export||b;return{"env":{"caml_ba_kind_of_typed_array":()=>{throw new
|
||||
Error("caml_ba_kind_of_typed_array not implemented")},"caml_exn_with_js_backtrace":()=>{throw new
|
||||
Error("caml_exn_with_js_backtrace not implemented")},"caml_int64_create_lo_mi_hi":()=>{throw new
|
||||
|
||||
Reference in New Issue
Block a user