Fix server import suspension, dist sync, JIT errors

- cek_run patched to handle import suspensions via _import_hook.
  define-library (import ...) now resolves cleanly on the server.
  IO suspension errors: 190 → 0. JIT failures: ~50 → 0.
- _import_hook wired in sx_server.ml to load .sx files on demand.
- compile-modules.js syncs source .sx files to dist/sx/ before
  compiling — eliminates stale bytecode from out-of-date copies.
- WASM binary rebuilt with all fixes.
- 2658/2658 tests pass (8 new — previously failing import tests).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-04 22:52:41 +00:00
parent b0a4be0f22
commit 5ac1ca9756
11 changed files with 480 additions and 408 deletions

View File

@@ -783,6 +783,18 @@ let () =
if not (Sx_primitives.is_primitive name) then
Hashtbl.replace _shared_vm_globals name v)
(* Import hook — resolves (import ...) suspensions inside eval_expr/cek_run.
Loads the .sx file for the library, registers it, and returns true. *)
let () =
Sx_types._import_hook := Some (fun lib_spec ->
let key = Sx_ref.library_name_key lib_spec in
if Sx_types.sx_truthy (Sx_ref.library_loaded_p key) then true
else match resolve_library_path lib_spec with
| Some path ->
(try load_library_file path; true
with _ -> false)
| None -> false)
let make_server_env () =
let env = make_env () in
Sx_render.setup_render_env env;

View File

@@ -16,12 +16,53 @@ const { execSync, spawnSync } = require('child_process');
const distDir = process.argv[2] || path.join(__dirname, 'dist');
const sxDir = path.join(distDir, 'sx');
const projectRoot = path.resolve(__dirname, '..', '..', '..');
if (!fs.existsSync(sxDir)) {
console.error('sx dir not found:', sxDir);
process.exit(1);
}
// Sync source .sx files to dist/sx/ before compiling.
// Source locations: spec/ for core, lib/ for compiler/vm, web/ and web/lib/ for web stack.
const SOURCE_MAP = {
// spec/
'render.sx': 'spec/render.sx',
'core-signals.sx': 'spec/signals.sx',
// lib/
'bytecode.sx': 'lib/bytecode.sx', 'compiler.sx': 'lib/compiler.sx',
'vm.sx': 'lib/vm.sx', 'freeze.sx': 'lib/freeze.sx',
'highlight.sx': 'lib/highlight.sx',
// web/lib/
'dom.sx': 'web/lib/dom.sx', 'browser.sx': 'web/lib/browser.sx',
// web/
'signals.sx': 'web/signals.sx', 'deps.sx': 'web/deps.sx',
'router.sx': 'web/router.sx', 'page-helpers.sx': 'web/page-helpers.sx',
'adapter-html.sx': 'web/adapter-html.sx', 'adapter-sx.sx': 'web/adapter-sx.sx',
'adapter-dom.sx': 'web/adapter-dom.sx',
'boot-helpers.sx': 'web/lib/boot-helpers.sx',
'hypersx.sx': 'web/hypersx.sx',
'harness.sx': 'spec/harness.sx', 'harness-reactive.sx': 'web/harness-reactive.sx',
'harness-web.sx': 'web/harness-web.sx',
'engine.sx': 'web/engine.sx', 'orchestration.sx': 'web/orchestration.sx',
'boot.sx': 'web/boot.sx',
'tw-layout.sx': 'web/tw-layout.sx', 'tw-type.sx': 'web/tw-type.sx', 'tw.sx': 'web/tw.sx',
};
let synced = 0;
for (const [dist, src] of Object.entries(SOURCE_MAP)) {
const srcPath = path.join(projectRoot, src);
const dstPath = path.join(sxDir, dist);
if (fs.existsSync(srcPath)) {
const srcContent = fs.readFileSync(srcPath);
const dstExists = fs.existsSync(dstPath);
if (!dstExists || !fs.readFileSync(dstPath).equals(srcContent)) {
fs.writeFileSync(dstPath, srcContent);
synced++;
}
}
}
if (synced > 0) console.log('Synced ' + synced + ' source files to dist/sx/');
// Find the native OCaml binary
const binPaths = [
path.join(__dirname, '..', '_build', 'default', 'bin', 'sx_server.exe'),

View File

@@ -488,7 +488,21 @@ and cek_step_loop state =
(* cek-run *)
and cek_run state =
(let final = (cek_step_loop (state)) in (if sx_truthy ((cek_suspended_p (final))) then (raise (Eval_error (value_to_str (String "IO suspension in non-IO context")))) else (cek_value (final))))
(let rec run s =
let final = cek_step_loop s in
if sx_truthy (cek_suspended_p final) then begin
let request = cek_io_request final in
let op = match request with Dict d -> (match Hashtbl.find_opt d "op" with Some (String s) -> s | _ -> "") | _ -> "" in
if op = "import" then
let lib_spec = match request with Dict d -> (match Hashtbl.find_opt d "library" with Some v -> v | _ -> Nil) | _ -> Nil in
let key = library_name_key lib_spec in
let resolved = sx_truthy (library_loaded_p key) ||
(match !_import_hook with Some hook -> hook lib_spec | None -> false) in
if resolved then run (cek_resume final Nil)
else raise (Eval_error "IO suspension in non-IO context")
else raise (Eval_error "IO suspension in non-IO context")
end else cek_value final
in run state)
(* cek-resume *)
and cek_resume suspended_state result' =
@@ -815,7 +829,26 @@ let cek_run_iterative state =
s := cek_step !s
done;
(match cek_suspended_p !s with
| Bool true -> raise (Eval_error "IO suspension in non-IO context")
| Bool true ->
let request = cek_io_request !s in
let op = match request with Dict d -> (match Hashtbl.find_opt d "op" with Some (String s) -> s | _ -> "") | _ -> "" in
if op = "import" then begin
let lib_spec = match request with Dict d -> (match Hashtbl.find_opt d "library" with Some v -> v | _ -> Nil) | _ -> Nil in
let key = library_name_key lib_spec in
let resolved = sx_truthy (library_loaded_p key) ||
(match !_import_hook with Some hook -> hook lib_spec | None -> false) in
if resolved then begin
s := cek_resume !s Nil;
(* Continue the step loop after resolving the import *)
while not (match cek_terminal_p !s with Bool true -> true | _ -> false)
&& not (match cek_suspended_p !s with Bool true -> true | _ -> false) do
s := cek_step !s
done;
(match cek_suspended_p !s with
| Bool true -> raise (Eval_error "IO suspension in non-IO context")
| _ -> cek_value !s)
end else raise (Eval_error "IO suspension in non-IO context")
end else raise (Eval_error "IO suspension in non-IO context")
| _ -> cek_value !s)
with Eval_error msg ->
_last_error_kont_ref := cek_kont !s;