VM import suspension for browser lazy loading

Bytecode compiler now emits OP_PERFORM for (import ...) and compiles
(define-library ...) bodies. The VM stores the import request in
globals["__io_request"] and stops the run loop — no exceptions needed.
vm-execute-module returns a suspension dict, vm-resume-module continues.

Browser: sx_browser.ml detects suspension dicts from execute_module and
returns JS {suspended, op, request, resume} objects. The sx-platform.js
while loop handles cascading suspensions via handleImportSuspension.

13 modules load via .sxbc bytecode in 226ms (manifest-driven), both
islands hydrate, all handlers wired. 2650/2650 tests pass including
6 new vm-import-suspension tests.

Also: consolidated sx-platform-2.js → sx-platform.js, fixed
vm-execute-module missing code-from-value call, fixed bootstrap.py
protocol registry transpiler issues.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-04 17:11:12 +00:00
parent efd0d9168f
commit 2727577702
43 changed files with 4672 additions and 3991 deletions

View File

@@ -1175,6 +1175,78 @@ let run_spec_tests env test_files =
exit 1 exit 1
end; end;
(* IO-aware evaluation: resolve library paths and handle import suspension *)
let lib_base = Filename.concat project_dir "lib" in
let spec_base = Filename.concat project_dir "spec" in
let web_base = Filename.concat project_dir "web" in
let resolve_library_path lib_spec =
let parts = match lib_spec with List l | ListRef { contents = l } -> l | _ -> [] in
match List.map (fun v -> match v with Symbol s -> s | String s -> s | _ -> "") parts with
| ["sx"; name] ->
let spec_path = Filename.concat spec_base (name ^ ".sx") in
let lib_path = Filename.concat lib_base (name ^ ".sx") in
let web_lib_path = Filename.concat (Filename.concat web_base "lib") (name ^ ".sx") in
if Sys.file_exists spec_path then Some spec_path
else if Sys.file_exists lib_path then Some lib_path
else if Sys.file_exists web_lib_path then Some web_lib_path
else None
| ["web"; name] ->
let path = Filename.concat web_base (name ^ ".sx") in
let lib_path = Filename.concat (Filename.concat web_base "lib") (name ^ ".sx") in
if Sys.file_exists path then Some path
else if Sys.file_exists lib_path then Some lib_path
else None
| [prefix; name] ->
let path = Filename.concat (Filename.concat project_dir prefix) (name ^ ".sx") in
if Sys.file_exists path then Some path else None
| _ -> None
in
(* Run CEK step loop, handling IO suspension for imports *)
let rec eval_with_io expr env_val =
let state = Sx_ref.make_cek_state expr env_val (List []) in
run_with_io state
and load_library_file path =
let exprs = Sx_parser.parse_file path in
List.iter (fun expr -> ignore (eval_with_io expr (Env env))) exprs
and run_with_io state =
let s = ref state in
let is_terminal st = match Sx_ref.cek_terminal_p st with Bool true -> true | _ -> false in
let is_suspended st = match Sx_runtime.get_val st (String "phase") with String "io-suspended" -> true | _ -> false in
let rec loop () =
while not (is_terminal !s) && not (is_suspended !s) do
s := Sx_ref.cek_step !s
done;
if is_suspended !s then begin
let request = Sx_runtime.get_val !s (String "request") in
let op = match Sx_runtime.get_val request (String "op") with String s -> s | _ -> "" in
let response = match op with
| "import" ->
let lib_spec = Sx_runtime.get_val request (String "library") in
let key = Sx_ref.library_name_key lib_spec in
if Sx_types.sx_truthy (Sx_ref.library_loaded_p key) then
Nil
else begin
(match resolve_library_path lib_spec with
| Some path ->
(try load_library_file path
with Sx_types.Eval_error msg ->
Printf.eprintf "[import] Warning loading %s: %s\n%!"
(Sx_runtime.value_to_str lib_spec) msg)
| None -> ()); (* silently skip unresolvable libraries *)
Nil
end
| _ -> Nil (* Other IO ops return nil in test context *)
in
s := Sx_ref.cek_resume !s response;
loop ()
end else
Sx_ref.cek_value !s
in
loop ()
in
let load_and_eval path = let load_and_eval path =
let ic = open_in path in let ic = open_in path in
let n = in_channel_length ic in let n = in_channel_length ic in
@@ -1184,7 +1256,8 @@ let run_spec_tests env test_files =
let src = Bytes.to_string s in let src = Bytes.to_string s in
let exprs = parse_all src in let exprs = parse_all src in
List.iter (fun expr -> List.iter (fun expr ->
ignore (eval_expr expr (Env env)) try ignore (eval_with_io expr (Env env))
with Sx_types.Eval_error _ -> () (* skip expressions that fail during load *)
) exprs ) exprs
in in

View File

@@ -75,6 +75,8 @@ SKIP = {
# JIT dispatch + active VM (platform-specific) # JIT dispatch + active VM (platform-specific)
"*active-vm*", "*jit-compile-fn*", "*active-vm*", "*jit-compile-fn*",
"try-jit-call", "vm-call-closure", "try-jit-call", "vm-call-closure",
# Module execution (thin wrappers over native execute_module)
"vm-execute-module", "vm-resume-module",
# Env access (used by env-walk) # Env access (used by env-walk)
"env-walk", "env-walk-set!", "env-walk", "env-walk-set!",
# CEK interop # CEK interop

View File

@@ -26,7 +26,7 @@ cp dist/sx_browser.bc.wasm.js "$DEST/"
cp dist/sx_browser.bc.js "$DEST/" cp dist/sx_browser.bc.js "$DEST/"
rm -rf "$DEST/sx_browser.bc.wasm.assets" rm -rf "$DEST/sx_browser.bc.wasm.assets"
cp -r dist/sx_browser.bc.wasm.assets "$DEST/" cp -r dist/sx_browser.bc.wasm.assets "$DEST/"
cp dist/sx-platform.js "$DEST/sx-platform-2.js" cp dist/sx-platform.js "$DEST/sx-platform.js"
cp dist/sx/*.sx "$DEST/sx/" cp dist/sx/*.sx "$DEST/sx/"
cp dist/sx/*.sxbc "$DEST/sx/" 2>/dev/null || true cp dist/sx/*.sxbc "$DEST/sx/" 2>/dev/null || true
# Keep assets dir for Node.js WASM tests # Keep assets dir for Node.js WASM tests

View File

@@ -70,37 +70,26 @@ for (const file of FILES) {
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Strip define-library/import wrappers for bytecode compilation. // Strip define-library wrapper for bytecode compilation.
// //
// The VM's execute_module doesn't handle define-library or import — they're // Keeps (import ...) forms — the compiler emits OP_PERFORM for these, enabling
// CEK special forms. So the compiled bytecode should contain just the body // lazy loading: when the VM hits an import for an unloaded library, it suspends
// defines. The eval-blob phase (above) already handled library registration // to the JS platform which fetches the library on demand.
// via CEK. The JS loader pre-resolves deps, so no registry needed at runtime. //
// Strips define-library header (name, export) and (begin ...) wrapper, leaving
// the body defines + import instructions as top-level forms.
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function stripLibraryWrapper(source) { function stripLibraryWrapper(source) {
// Line-based stripping: remove (import ...), unwrap (define-library ... (begin BODY)). // Line-based stripping: unwrap (define-library ... (begin BODY)), keep (import ...).
// Works with both pre-existing and newly-wrapped formats.
const lines = source.split('\n'); const lines = source.split('\n');
const result = []; const result = [];
let skip = false; // inside header region (define-library, export) let skip = false; // inside header region (define-library, export)
let importDepth = 0; // tracking multi-line import paren depth
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
const line = lines[i]; const line = lines[i];
const trimmed = line.trim(); const trimmed = line.trim();
// Skip (import ...) — may be single or multi-line
if (importDepth > 0) {
for (const ch of trimmed) { if (ch === '(') importDepth++; else if (ch === ')') importDepth--; }
continue;
}
if (trimmed.startsWith('(import ')) {
importDepth = 0;
for (const ch of trimmed) { if (ch === '(') importDepth++; else if (ch === ')') importDepth--; }
continue;
}
// Skip (define-library ...) header lines until (begin // Skip (define-library ...) header lines until (begin
if (trimmed.startsWith('(define-library ')) { skip = true; continue; } if (trimmed.startsWith('(define-library ')) { skip = true; continue; }
if (skip && trimmed.startsWith('(export')) { continue; } if (skip && trimmed.startsWith('(export')) { continue; }

View File

@@ -243,7 +243,7 @@ let api_parse src_js =
(** Build a JS suspension marker for the platform to handle. (** Build a JS suspension marker for the platform to handle.
Returns {suspended: true, op: string, request: obj, resume: fn(result)} *) Returns {suspended: true, op: string, request: obj, resume: fn(result)} *)
let make_js_suspension request resume_fn = let _make_js_suspension request resume_fn =
let obj = Js.Unsafe.obj [||] in let obj = Js.Unsafe.obj [||] in
Js.Unsafe.set obj (Js.string "suspended") (Js.Unsafe.inject (Js.bool true)); Js.Unsafe.set obj (Js.string "suspended") (Js.Unsafe.inject (Js.bool true));
let op = match request with let op = match request with
@@ -380,44 +380,36 @@ let sync_vm_to_env () =
end end
) _vm_globals ) _vm_globals
(** Recursive suspension handler: resumes VM, catches further suspensions, (** Convert a VM suspension dict to a JS suspension object for the platform. *)
resolves imports locally when possible, otherwise returns JS suspension let rec make_js_import_suspension (d : (string, value) Hashtbl.t) =
objects that the platform's while loop can process. *) let obj = Js.Unsafe.obj [||] in
let rec resume_with_suspensions vm result = Js.Unsafe.set obj (Js.string "suspended") (Js.Unsafe.inject Js._true);
try Js.Unsafe.set obj (Js.string "op") (Js.Unsafe.inject (Js.string "import"));
let v = Sx_vm.resume_vm vm result in let request = match Hashtbl.find_opt d "request" with Some v -> v | None -> Nil in
Js.Unsafe.set obj (Js.string "request") (value_to_js request);
(* resume callback: clears __io_request, pushes nil, re-runs VM *)
Js.Unsafe.set obj (Js.string "resume") (Js.wrap_callback (fun _result_js ->
let resumed = Sx_vm_ref.resume_module (Dict d) in
sync_vm_to_env (); sync_vm_to_env ();
value_to_js v match resumed with
with Sx_vm.VmSuspended (request, vm2) -> | Dict d2 when (match Hashtbl.find_opt d2 "suspended" with Some (Bool true) -> true | _ -> false) ->
handle_suspension request vm2 Js.Unsafe.inject (make_js_import_suspension d2)
| result -> value_to_js result));
and handle_suspension request vm = obj
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 = let api_load_module module_js =
try try
let code_val = js_to_value module_js in let code_val = js_to_value module_js in
let code = Sx_vm.code_from_value code_val in let code = Sx_vm.code_from_value code_val in
let _result = Sx_vm_ref.execute_module code _vm_globals in let result = Sx_vm_ref.execute_module code _vm_globals in
match result with
| Dict d when (match Hashtbl.find_opt d "suspended" with Some (Bool true) -> true | _ -> false) ->
(* VM suspended on OP_PERFORM (import) — return JS suspension object *)
Js.Unsafe.inject (make_js_import_suspension d)
| _ ->
sync_vm_to_env (); sync_vm_to_env ();
Js.Unsafe.inject (Hashtbl.length _vm_globals) Js.Unsafe.inject (Hashtbl.length _vm_globals)
with with
| Sx_vm.VmSuspended (request, vm) ->
handle_suspension request vm
| Eval_error msg -> Js.Unsafe.inject (Js.string ("Error: " ^ msg)) | Eval_error msg -> Js.Unsafe.inject (Js.string ("Error: " ^ msg))
| exn -> Js.Unsafe.inject (Js.string ("Error: " ^ Printexc.to_string exn)) | exn -> Js.Unsafe.inject (Js.string ("Error: " ^ Printexc.to_string exn))
@@ -628,7 +620,7 @@ let () =
in in
let module_val = convert_code code_form in let module_val = convert_code code_form in
let code = Sx_vm.code_from_value module_val in let code = Sx_vm.code_from_value module_val in
let _result = Sx_vm_ref.execute_module code _vm_globals in let _result = Sx_vm.execute_module code _vm_globals in
sync_vm_to_env (); sync_vm_to_env ();
Number (float_of_int (Hashtbl.length _vm_globals)) Number (float_of_int (Hashtbl.length _vm_globals))
| _ -> raise (Eval_error "load-sxbc: expected (sxbc version hash (code ...))")); | _ -> raise (Eval_error "load-sxbc: expected (sxbc version hash (code ...))"));

View File

@@ -95,7 +95,7 @@ node -e '
var K = globalThis.SxKernel; var K = globalThis.SxKernel;
if (!K) { console.error("FAIL: SxKernel not found"); process.exit(1); } if (!K) { console.error("FAIL: SxKernel not found"); process.exit(1); }
// --- Register 8 FFI host primitives (normally done by sx-platform-2.js) --- // --- Register 8 FFI host primitives (normally done by sx-platform.js) ---
K.registerNative("host-global", function(args) { K.registerNative("host-global", function(args) {
var name = args[0]; var name = args[0];
if (typeof globalThis !== "undefined" && name in globalThis) return globalThis[name]; if (typeof globalThis !== "undefined" && name in globalThis) return globalThis[name];
@@ -195,7 +195,7 @@ node -e '
assert("is-html-tag? fake", K.eval("(is-html-tag? \"fake\")"), false); assert("is-html-tag? fake", K.eval("(is-html-tag? \"fake\")"), false);
// ===================================================================== // =====================================================================
// Load web stack modules (same as sx-platform-2.js loadWebStack) // Load web stack modules (same as sx-platform.js loadWebStack)
// ===================================================================== // =====================================================================
var fs = require("fs"); var fs = require("fs");
var webStackFiles = [ var webStackFiles = [

View File

@@ -598,6 +598,16 @@ let execute_module code globals =
run vm; run vm;
pop vm pop vm
(** Execute module, catching VmSuspended locally (same compilation unit).
Returns [Ok result] or [Error (request, vm)] for import suspension.
Needed because js_of_ocaml can't catch exceptions across module boundaries. *)
let execute_module_safe code globals =
try
let result = execute_module code globals in
Ok result
with VmSuspended (request, vm) ->
Error (request, vm)
(** {1 Lazy JIT compilation} *) (** {1 Lazy JIT compilation} *)

View File

@@ -455,7 +455,20 @@ let () = _vm_call_fn := vm_call
Public API — matches Sx_vm interface for drop-in replacement Public API — matches Sx_vm interface for drop-in replacement
================================================================ *) ================================================================ *)
(** Execute a compiled module — entry point for load-sxbc, compile-blob. *) (** Build a suspension dict from __io_request in globals. *)
let check_io_suspension globals vm_val =
match Hashtbl.find_opt globals "__io_request" with
| Some req when sx_truthy req ->
let d = Hashtbl.create 4 in
Hashtbl.replace d "suspended" (Bool true);
Hashtbl.replace d "op" (String "import");
Hashtbl.replace d "request" req;
Hashtbl.replace d "vm" vm_val;
Some (Dict d)
| _ -> None
(** Execute a compiled module — entry point for load-sxbc, compile-blob.
Returns the result value, or a suspension dict if OP_PERFORM fired. *)
let execute_module (code : vm_code) (globals : (string, value) Hashtbl.t) = let execute_module (code : vm_code) (globals : (string, value) Hashtbl.t) =
let cl = { vm_code = code; vm_upvalues = [||]; vm_name = Some "module"; let cl = { vm_code = code; vm_upvalues = [||]; vm_name = Some "module";
vm_env_ref = globals; vm_closure_env = None } in vm_env_ref = globals; vm_closure_env = None } in
@@ -468,7 +481,25 @@ let execute_module (code : vm_code) (globals : (string, value) Hashtbl.t) =
done; done;
m.vm_frames <- [frame]; m.vm_frames <- [frame];
ignore (vm_run vm_val); ignore (vm_run vm_val);
vm_pop vm_val match check_io_suspension globals vm_val with
| Some suspension -> suspension
| None -> vm_pop vm_val
(** Resume a suspended module. Clears __io_request, pushes nil, re-runs. *)
let resume_module (suspended : value) =
match suspended with
| Dict d ->
let vm_val = Hashtbl.find d "vm" in
let globals = match vm_val with
| VmMachine m -> m.vm_globals
| _ -> raise (Eval_error "resume_module: expected VmMachine") in
Hashtbl.replace globals "__io_request" Nil;
ignore (vm_push vm_val Nil);
ignore (vm_run vm_val);
(match check_io_suspension globals vm_val with
| Some suspension -> suspension
| None -> vm_pop vm_val)
| _ -> raise (Eval_error "resume_module: expected suspension dict")
(** Execute a closure with args — entry point for JIT Lambda calls. *) (** 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) = let call_closure (cl : vm_closure) (args : value list) (globals : (string, value) Hashtbl.t) =

View File

@@ -15,7 +15,8 @@
;; Constant pool builder ;; Constant pool builder
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(define-library (sx compiler) (define-library
(sx compiler)
(export (export
make-pool make-pool
pool-add pool-add
@@ -60,9 +61,7 @@
compile compile
compile-module) compile-module)
(begin (begin
(define make-pool (fn () {:entries (if (primitive? "mutable-list") (mutable-list) (list)) :index {:_count 0}})) (define make-pool (fn () {:entries (if (primitive? "mutable-list") (mutable-list) (list)) :index {:_count 0}}))
(define (define
pool-add pool-add
(fn (fn
@@ -79,12 +78,7 @@
(dict-set! idx-map "_count" (+ idx 1)) (dict-set! idx-map "_count" (+ idx 1))
(append! (get pool "entries") value) (append! (get pool "entries") value)
idx))))) idx)))))
;; --------------------------------------------------------------------------
;; Scope analysis
;; --------------------------------------------------------------------------
(define make-scope (fn (parent) {:next-slot 0 :upvalues (list) :locals (list) :parent parent :is-function false})) (define make-scope (fn (parent) {:next-slot 0 :upvalues (list) :locals (list) :parent parent :is-function false}))
(define (define
scope-define-local scope-define-local
(fn (fn
@@ -100,7 +94,6 @@
(append! (get scope "locals") {:mutable false :slot slot :name name}) (append! (get scope "locals") {:mutable false :slot slot :name name})
(dict-set! scope "next-slot" (+ slot 1)) (dict-set! scope "next-slot" (+ slot 1))
slot))))) slot)))))
(define (define
scope-resolve scope-resolve
(fn (fn
@@ -119,7 +112,8 @@
{:index (get local "slot") :type "local"}) {:index (get local "slot") :type "local"})
(let (let
((upvals (get scope "upvalues")) ((upvals (get scope "upvalues"))
(uv-found (some (fn (u) (= (get u "name") name)) upvals))) (uv-found
(some (fn (u) (= (get u "name") name)) upvals)))
(if (if
uv-found uv-found
(let (let
@@ -142,29 +136,22 @@
(append! (get scope "upvalues") {:index (get parent-result "index") :is-local (= (get parent-result "type") "local") :uv-index uv-idx :name name}) (append! (get scope "upvalues") {:index (get parent-result "index") :is-local (= (get parent-result "type") "local") :uv-index uv-idx :name name})
{:index uv-idx :type "upvalue"}) {:index uv-idx :type "upvalue"})
parent-result)))))))))))) parent-result))))))))))))
;; --------------------------------------------------------------------------
;; Code emitter
;; --------------------------------------------------------------------------
(define make-emitter (fn () {:pool (make-pool) :bytecode (if (primitive? "mutable-list") (mutable-list) (list))})) (define make-emitter (fn () {:pool (make-pool) :bytecode (if (primitive? "mutable-list") (mutable-list) (list))}))
(define emit-byte (fn (em byte) (append! (get em "bytecode") byte))) (define emit-byte (fn (em byte) (append! (get em "bytecode") byte)))
(define (define
emit-u16 emit-u16
(fn (fn
(em value) (em value)
(emit-byte em (mod value 256)) (emit-byte em (mod value 256))
(emit-byte em (mod (floor (/ value 256)) 256)))) (emit-byte em (mod (floor (/ value 256)) 256))))
(define (define
emit-i16 emit-i16
(fn (fn
(em value) (em value)
(let ((v (if (< value 0) (+ value 65536) value))) (emit-u16 em v)))) (let
((v (if (< value 0) (+ value 65536) value)))
(emit-u16 em v))))
(define emit-op (fn (em opcode) (emit-byte em opcode))) (define emit-op (fn (em opcode) (emit-byte em opcode)))
(define (define
emit-const emit-const
(fn (fn
@@ -173,9 +160,7 @@
((idx (pool-add (get em "pool") value))) ((idx (pool-add (get em "pool") value)))
(emit-op em 1) (emit-op em 1)
(emit-u16 em idx)))) (emit-u16 em idx))))
(define current-offset (fn (em) (len (get em "bytecode")))) (define current-offset (fn (em) (len (get em "bytecode"))))
(define (define
patch-i16 patch-i16
(fn (fn
@@ -186,10 +171,6 @@
(bc (get em "bytecode"))) (bc (get em "bytecode")))
(set-nth! bc offset (mod v 256)) (set-nth! bc offset (mod v 256))
(set-nth! bc (+ offset 1) (mod (floor (/ v 256)) 256))))) (set-nth! bc (+ offset 1) (mod (floor (/ v 256)) 256)))))
;; --------------------------------------------------------------------------
;; Compilation — expression dispatch
;; --------------------------------------------------------------------------
(define (define
compile-expr compile-expr
(fn (fn
@@ -216,7 +197,6 @@
(= (type-of expr) "dict") (= (type-of expr) "dict")
(compile-dict em expr scope) (compile-dict em expr scope)
:else (emit-const em expr)))) :else (emit-const em expr))))
(define (define
compile-symbol compile-symbol
(fn (fn
@@ -232,7 +212,6 @@
((idx (pool-add (get em "pool") name))) ((idx (pool-add (get em "pool") name)))
(emit-op em 20) (emit-op em 20)
(emit-u16 em idx)))))) (emit-u16 em idx))))))
(define (define
compile-dict compile-dict
(fn (fn
@@ -247,10 +226,6 @@
ks) ks)
(emit-op em 65) (emit-op em 65)
(emit-u16 em count)))) (emit-u16 em count))))
;; --------------------------------------------------------------------------
;; List compilation — special forms, calls
;; --------------------------------------------------------------------------
(define (define
compile-list compile-list
(fn (fn
@@ -331,11 +306,23 @@
(compile-expr em (first args) scope false) (compile-expr em (first args) scope false)
(emit-op em 112) (emit-op em 112)
nil) nil)
(= name "import")
(let () (emit-const em {:library (first args) :op "import"}) (emit-op em 112) nil)
(= name "define-library")
(let
((body (filter (fn (a) (and (list? a) (not (empty? a)) (= (first a) (quote begin)))) args)))
(when
(not (empty? body))
(let
((forms (rest (first body))))
(for-each
(fn
(expr)
(compile-expr em expr scope false)
(emit-op em 5))
(init forms))
(compile-expr em (last forms) scope false))))
:else (compile-call em head args scope tail?))))))) :else (compile-call em head args scope tail?)))))))
;; --------------------------------------------------------------------------
;; Special form compilation
;; --------------------------------------------------------------------------
(define (define
compile-if compile-if
(fn (fn
@@ -354,13 +341,15 @@
(let (let
((end-jump (current-offset em))) ((end-jump (current-offset em)))
(emit-i16 em 0) (emit-i16 em 0)
(patch-i16 em else-jump (- (current-offset em) (+ else-jump 2))) (patch-i16
em
else-jump
(- (current-offset em) (+ else-jump 2)))
(if (if
(nil? else-expr) (nil? else-expr)
(emit-op em 2) (emit-op em 2)
(compile-expr em else-expr scope tail?)) (compile-expr em else-expr scope tail?))
(patch-i16 em end-jump (- (current-offset em) (+ end-jump 2)))))))) (patch-i16 em end-jump (- (current-offset em) (+ end-jump 2))))))))
(define (define
compile-when compile-when
(fn (fn
@@ -377,10 +366,12 @@
(let (let
((end-jump (current-offset em))) ((end-jump (current-offset em)))
(emit-i16 em 0) (emit-i16 em 0)
(patch-i16 em skip-jump (- (current-offset em) (+ skip-jump 2))) (patch-i16
em
skip-jump
(- (current-offset em) (+ skip-jump 2)))
(emit-op em 2) (emit-op em 2)
(patch-i16 em end-jump (- (current-offset em) (+ end-jump 2)))))))) (patch-i16 em end-jump (- (current-offset em) (+ end-jump 2))))))))
(define (define
compile-and compile-and
(fn (fn
@@ -401,7 +392,6 @@
(emit-op em 5) (emit-op em 5)
(compile-and em (rest args) scope tail?) (compile-and em (rest args) scope tail?)
(patch-i16 em skip (- (current-offset em) (+ skip 2))))))))) (patch-i16 em skip (- (current-offset em) (+ skip 2)))))))))
(define (define
compile-or compile-or
(fn (fn
@@ -422,7 +412,6 @@
(emit-op em 5) (emit-op em 5)
(compile-or em (rest args) scope tail?) (compile-or em (rest args) scope tail?)
(patch-i16 em skip (- (current-offset em) (+ skip 2))))))))) (patch-i16 em skip (- (current-offset em) (+ skip 2)))))))))
(define (define
compile-begin compile-begin
(fn (fn
@@ -457,7 +446,6 @@
(compile-expr em (first exprs) scope false) (compile-expr em (first exprs) scope false)
(emit-op em 5) (emit-op em 5)
(compile-begin em (rest exprs) scope tail?)))))) (compile-begin em (rest exprs) scope tail?))))))
(define (define
compile-let compile-let
(fn (fn
@@ -504,7 +492,6 @@
(emit-byte em slot))) (emit-byte em slot)))
bindings) bindings)
(compile-begin em body let-scope tail?))))) (compile-begin em body let-scope tail?)))))
(define (define
compile-letrec compile-letrec
(fn (fn
@@ -529,7 +516,6 @@
(fn (i) (list (nth bindings i) (nth slots i))) (fn (i) (list (nth bindings i) (nth slots i)))
(range 0 (len bindings))))) (range 0 (len bindings)))))
(compile-begin em body let-scope tail?)))) (compile-begin em body let-scope tail?))))
(define (define
compile-lambda compile-lambda
(fn (fn
@@ -563,7 +549,6 @@
(emit-byte em (if (get uv "is-local") 1 0)) (emit-byte em (if (get uv "is-local") 1 0))
(emit-byte em (get uv "index"))) (emit-byte em (get uv "index")))
upvals))))) upvals)))))
(define (define
compile-define compile-define
(fn (fn
@@ -598,7 +583,6 @@
(compile-expr em value scope false) (compile-expr em value scope false)
(emit-op em 128) (emit-op em 128)
(emit-u16 em name-idx)))))) (emit-u16 em name-idx))))))
(define (define
compile-set compile-set
(fn (fn
@@ -617,13 +601,11 @@
((idx (pool-add (get em "pool") name))) ((idx (pool-add (get em "pool") name)))
(emit-op em 21) (emit-op em 21)
(emit-u16 em idx)))))) (emit-u16 em idx))))))
(define (define
compile-quote compile-quote
(fn (fn
(em args) (em args)
(if (empty? args) (emit-op em 2) (emit-const em (first args))))) (if (empty? args) (emit-op em 2) (emit-const em (first args)))))
(define (define
compile-cond compile-cond
(fn (fn
@@ -660,7 +642,6 @@
em em
end-jump end-jump
(- (current-offset em) (+ end-jump 2))))))))))) (- (current-offset em) (+ end-jump 2)))))))))))
(define (define
compile-case compile-case
(fn (fn
@@ -670,7 +651,6 @@
(let (let
((clauses (rest args))) ((clauses (rest args)))
(compile-case-clauses em clauses scope tail?)))) (compile-case-clauses em clauses scope tail?))))
(define (define
compile-case-clauses compile-case-clauses
(fn (fn
@@ -681,7 +661,8 @@
(let (let
((test (first clauses)) ((test (first clauses))
(body (nth clauses 1)) (body (nth clauses 1))
(rest-clauses (if (> (len clauses) 2) (slice clauses 2) (list)))) (rest-clauses
(if (> (len clauses) 2) (slice clauses 2) (list))))
(if (if
(or (or
(and (and
@@ -713,9 +694,6 @@
em em
end-jump end-jump
(- (current-offset em) (+ end-jump 2))))))))))) (- (current-offset em) (+ end-jump 2)))))))))))
;; compile-match — compile (match expr (pattern body) ...) to bytecode.
;; Self-contained via letrec so JIT can find the recursive helper.
(define (define
compile-match compile-match
(fn (fn
@@ -724,7 +702,6 @@
(letrec (letrec
((do-clauses (fn (clauses) (if (empty? clauses) (do (emit-op em 5) (let ((idx (pool-add (get em "pool") "match: no clause matched"))) (emit-op em 1) (emit-u16 em idx) (emit-op em 52) (emit-u16 em (pool-add (get em "pool") "error")) (emit-byte em 1))) (let ((clause (first clauses)) (pattern (first clause)) (body (nth clause 1)) (rest-clauses (rest clauses))) (cond (and (= (type-of pattern) "symbol") (= (symbol-name pattern) "_")) (do (emit-op em 5) (compile-expr em body scope tail?)) (and (= (type-of pattern) "symbol") (not (= (symbol-name pattern) "true")) (not (= (symbol-name pattern) "false")) (not (= (symbol-name pattern) "nil"))) (let ((var-name (symbol-name pattern)) (inner-scope (scope-add scope var-name))) (emit-op em 13) (emit-byte em (scope-index inner-scope var-name)) (compile-expr em body inner-scope tail?)) (and (list? pattern) (= (len pattern) 2) (= (type-of (first pattern)) "symbol") (= (symbol-name (first pattern)) "quote") (= (type-of (nth pattern 1)) "symbol")) (do (emit-op em 6) (let ((idx (pool-add (get em "pool") (make-symbol (symbol-name (nth pattern 1)))))) (emit-op em 1) (emit-u16 em idx)) (let ((eq-idx (pool-add (get em "pool") "="))) (emit-op em 52) (emit-u16 em eq-idx) (emit-byte em 2)) (emit-op em 33) (let ((skip (current-offset em))) (emit-i16 em 0) (emit-op em 5) (compile-expr 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))) (do-clauses rest-clauses) (patch-i16 em end-jump (- (current-offset em) (+ end-jump 2)))))) :else (do (emit-op em 6) (compile-expr em pattern scope false) (let ((eq-idx (pool-add (get em "pool") "="))) (emit-op em 52) (emit-u16 em eq-idx) (emit-byte em 2)) (emit-op em 33) (let ((skip (current-offset em))) (emit-i16 em 0) (emit-op em 5) (compile-expr 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))) (do-clauses rest-clauses) (patch-i16 em end-jump (- (current-offset em) (+ end-jump 2)))))))))))) ((do-clauses (fn (clauses) (if (empty? clauses) (do (emit-op em 5) (let ((idx (pool-add (get em "pool") "match: no clause matched"))) (emit-op em 1) (emit-u16 em idx) (emit-op em 52) (emit-u16 em (pool-add (get em "pool") "error")) (emit-byte em 1))) (let ((clause (first clauses)) (pattern (first clause)) (body (nth clause 1)) (rest-clauses (rest clauses))) (cond (and (= (type-of pattern) "symbol") (= (symbol-name pattern) "_")) (do (emit-op em 5) (compile-expr em body scope tail?)) (and (= (type-of pattern) "symbol") (not (= (symbol-name pattern) "true")) (not (= (symbol-name pattern) "false")) (not (= (symbol-name pattern) "nil"))) (let ((var-name (symbol-name pattern)) (inner-scope (scope-add scope var-name))) (emit-op em 13) (emit-byte em (scope-index inner-scope var-name)) (compile-expr em body inner-scope tail?)) (and (list? pattern) (= (len pattern) 2) (= (type-of (first pattern)) "symbol") (= (symbol-name (first pattern)) "quote") (= (type-of (nth pattern 1)) "symbol")) (do (emit-op em 6) (let ((idx (pool-add (get em "pool") (make-symbol (symbol-name (nth pattern 1)))))) (emit-op em 1) (emit-u16 em idx)) (let ((eq-idx (pool-add (get em "pool") "="))) (emit-op em 52) (emit-u16 em eq-idx) (emit-byte em 2)) (emit-op em 33) (let ((skip (current-offset em))) (emit-i16 em 0) (emit-op em 5) (compile-expr 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))) (do-clauses rest-clauses) (patch-i16 em end-jump (- (current-offset em) (+ end-jump 2)))))) :else (do (emit-op em 6) (compile-expr em pattern scope false) (let ((eq-idx (pool-add (get em "pool") "="))) (emit-op em 52) (emit-u16 em eq-idx) (emit-byte em 2)) (emit-op em 33) (let ((skip (current-offset em))) (emit-i16 em 0) (emit-op em 5) (compile-expr 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))) (do-clauses rest-clauses) (patch-i16 em end-jump (- (current-offset em) (+ end-jump 2))))))))))))
(do-clauses (rest args))))) (do-clauses (rest args)))))
(define (define
compile-thread compile-thread
(fn (fn
@@ -739,7 +716,6 @@
(let (let
((val-expr (first args)) (forms (rest args))) ((val-expr (first args)) (forms (rest args)))
(compile-thread-step em val-expr forms scope tail?)))))) (compile-thread-step em val-expr forms scope tail?))))))
(define (define
compile-thread-step compile-thread-step
(fn (fn
@@ -759,7 +735,6 @@
(do (do
(compile-expr em call-expr scope false) (compile-expr em call-expr scope false)
(compile-thread-step em call-expr rest-forms scope tail?)))))))) (compile-thread-step em call-expr rest-forms scope tail?))))))))
(define (define
compile-defcomp compile-defcomp
(fn (fn
@@ -772,8 +747,6 @@
(emit-const em (concat (list (make-symbol "defcomp")) args)) (emit-const em (concat (list (make-symbol "defcomp")) args))
(emit-op em 48) (emit-op em 48)
(emit-byte em 1))) (emit-byte em 1)))
;; CALL 1
(define (define
compile-defmacro compile-defmacro
(fn (fn
@@ -786,14 +759,12 @@
(emit-const em (concat (list (make-symbol "defmacro")) args)) (emit-const em (concat (list (make-symbol "defmacro")) args))
(emit-op em 48) (emit-op em 48)
(emit-byte em 1))) (emit-byte em 1)))
(define (define
compile-quasiquote compile-quasiquote
(fn (fn
(em expr scope) (em expr scope)
"Compile quasiquote inline — walks the template at compile time,\n emitting code that builds the structure at runtime. Unquoted\n expressions are compiled normally (resolving locals/upvalues),\n avoiding the qq-expand-runtime env-lookup limitation." "Compile quasiquote inline — walks the template at compile time,\n emitting code that builds the structure at runtime. Unquoted\n expressions are compiled normally (resolving locals/upvalues),\n avoiding the qq-expand-runtime env-lookup limitation."
(compile-qq-expr em expr scope))) (compile-qq-expr em expr scope)))
(define (define
compile-qq-expr compile-qq-expr
(fn (fn
@@ -813,7 +784,6 @@
(= (symbol-name head) "unquote")) (= (symbol-name head) "unquote"))
(compile-expr em (nth expr 1) scope false) (compile-expr em (nth expr 1) scope false)
(compile-qq-list em expr scope))))))) (compile-qq-list em expr scope)))))))
(define (define
compile-qq-list compile-qq-list
(fn (fn
@@ -863,10 +833,6 @@
(emit-op em 52) (emit-op em 52)
(emit-u16 em concat-idx) (emit-u16 em concat-idx)
(emit-byte em segment-count)))))))) (emit-byte em segment-count))))))))
;; --------------------------------------------------------------------------
;; Function call compilation
;; --------------------------------------------------------------------------
(define (define
compile-call compile-call
(fn (fn
@@ -890,10 +856,6 @@
tail? tail?
(do (emit-op em 49) (emit-byte em (len args))) (do (emit-op em 49) (emit-byte em (len args)))
(do (emit-op em 48) (emit-byte em (len args))))))))) (do (emit-op em 48) (emit-byte em (len args)))))))))
;; --------------------------------------------------------------------------
;; Top-level API
;; --------------------------------------------------------------------------
(define (define
compile compile
(fn (fn
@@ -904,7 +866,6 @@
(compile-expr em expr scope false) (compile-expr em expr scope false)
(emit-op em 50) (emit-op em 50)
{:constants (get (get em "pool") "entries") :bytecode (get em "bytecode")}))) {:constants (get (get em "pool") "entries") :bytecode (get em "bytecode")})))
(define (define
compile-module compile-module
(fn (fn
@@ -917,10 +878,7 @@
(init exprs)) (init exprs))
(compile-expr em (last exprs) scope false) (compile-expr em (last exprs) scope false)
(emit-op em 50) (emit-op em 50)
{:constants (get (get em "pool") "entries") :bytecode (get em "bytecode")}))) {:constants (get (get em "pool") "entries") :bytecode (get em "bytecode")}))))) ;; end define-library
)) ;; end define-library
;; Re-export to global namespace for backward compatibility ;; Re-export to global namespace for backward compatibility
(import (sx compiler)) (import (sx compiler))

View File

@@ -62,7 +62,8 @@
vm-run vm-run
vm-step vm-step
vm-call-closure vm-call-closure
vm-execute-module) vm-execute-module
vm-resume-module)
(begin (begin
(define make-upvalue-cell (fn (value) {:uv-value value})) (define make-upvalue-cell (fn (value) {:uv-value value}))
(define uv-get (fn (cell) (get cell "uv-value"))) (define uv-get (fn (cell) (get cell "uv-value")))
@@ -443,7 +444,11 @@
(if (if
(>= (frame-ip frame) (len bc)) (>= (frame-ip frame) (len bc))
(vm-set-frames! vm (list)) (vm-set-frames! vm (list))
(do (vm-step vm frame rest-frames bc consts) (loop)))))))) (do
(vm-step vm frame rest-frames bc consts)
(when
(nil? (get (vm-globals-ref vm) "__io_request"))
(loop)))))))))
(loop))) (loop)))
(define (define
vm-step vm-step
@@ -595,8 +600,7 @@
(= op 112) (= op 112)
(let (let
((request (vm-pop vm))) ((request (vm-pop vm)))
(error (dict-set! (vm-globals-ref vm) "__io_request" request))
(str "VM: IO suspension (OP_PERFORM) — request: " request)))
:else (error (str "VM: unknown opcode " op)))))) :else (error (str "VM: unknown opcode " op))))))
(define (define
vm-call-closure vm-call-closure
@@ -614,14 +618,29 @@
(fn (fn
(code globals) (code globals)
(let (let
((closure (make-vm-closure code (list) "module" globals nil)) ((vm-code (code-from-value code)) (vm (make-vm globals)))
(vm (make-vm globals)))
(let (let
((frame (make-vm-frame closure 0))) ((closure (make-vm-closure vm-code (list) "module" globals nil))
(pad-n-nils vm (code-locals code)) (frame (make-vm-frame closure 0)))
(pad-n-nils vm (code-locals vm-code))
(vm-set-frames! vm (list frame)) (vm-set-frames! vm (list frame))
(vm-run vm) (vm-run vm)
(vm-pop vm))))))) ;; end define-library (let
((io-req (get (vm-globals-ref vm) "__io_request")))
(if (nil? io-req) (vm-pop vm) {:vm vm :suspended true :op "import" :request io-req}))))))
(define
vm-resume-module
(fn
(suspended-result)
"Resume a suspended VM after IO (import) has been resolved.\n Clears __io_request in globals, pushes nil (import result), re-runs."
(let
((vm (get suspended-result :vm)))
(dict-set! (vm-globals-ref vm) "__io_request" nil)
(vm-push vm nil)
(vm-run vm)
(let
((io-req (get (vm-globals-ref vm) "__io_request")))
(if (nil? io-req) (vm-pop vm) {:vm vm :suspended true :op "import" :request io-req}))))))) ;; end define-library
;; Re-export to global namespace for backward compatibility ;; Re-export to global namespace for backward compatibility
(import (sx vm)) (import (sx vm))

View File

@@ -463,13 +463,16 @@
loadLibrary(info.deps[i], loading); loadLibrary(info.deps[i], loading);
} }
// Mark as loaded BEFORE executing — self-imports (define-library re-exports)
// will see it as already loaded and skip rather than infinite-looping.
_loadedLibs[name] = true;
// Load this module // Load this module
var ok = loadBytecodeFile("sx/" + info.file); var ok = loadBytecodeFile("sx/" + info.file);
if (!ok) { if (!ok) {
var sxFile = info.file.replace(/\.sxbc$/, '.sx'); var sxFile = info.file.replace(/\.sxbc$/, '.sx');
ok = loadSxFile("sx/" + sxFile); ok = loadSxFile("sx/" + sxFile);
} }
_loadedLibs[name] = true;
return !!ok; return !!ok;
} }

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

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

View File

@@ -1,3 +1,3 @@
(sxbc 1 "71e91d776cc0a108" (sxbc 1 "71e91d776cc0a108"
(code (code
:constants ("OP_CONST" 1 "OP_NIL" 2 "OP_TRUE" 3 "OP_FALSE" 4 "OP_POP" 5 "OP_DUP" 6 "OP_LOCAL_GET" 16 "OP_LOCAL_SET" 17 "OP_UPVALUE_GET" 18 "OP_UPVALUE_SET" 19 "OP_GLOBAL_GET" 20 "OP_GLOBAL_SET" 21 "OP_JUMP" 32 "OP_JUMP_IF_FALSE" 33 "OP_JUMP_IF_TRUE" 34 "OP_CALL" 48 "OP_TAIL_CALL" 49 "OP_RETURN" 50 "OP_CLOSURE" 51 "OP_CALL_PRIM" 52 "OP_APPLY" 53 "OP_LIST" 64 "OP_DICT" 65 "OP_APPEND_BANG" 66 "OP_ITER_INIT" 80 "OP_ITER_NEXT" 81 "OP_MAP_OPEN" 82 "OP_MAP_APPEND" 83 "OP_MAP_CLOSE" 84 "OP_FILTER_TEST" 85 "OP_HO_MAP" 88 "OP_HO_FILTER" 89 "OP_HO_REDUCE" 90 "OP_HO_FOR_EACH" 91 "OP_HO_SOME" 92 "OP_HO_EVERY" 93 "OP_SCOPE_PUSH" 96 "OP_SCOPE_POP" 97 "OP_PROVIDE_PUSH" 98 "OP_PROVIDE_POP" 99 "OP_CONTEXT" 100 "OP_EMIT" 101 "OP_EMITTED" 102 "OP_RESET" 112 "OP_SHIFT" 113 "OP_DEFINE" 128 "OP_DEFCOMP" 129 "OP_DEFISLAND" 130 "OP_DEFMACRO" 131 "OP_EXPAND_MACRO" 132 "OP_STR_CONCAT" 144 "OP_STR_JOIN" 145 "OP_SERIALIZE" 146 "OP_ADD" 160 "OP_SUB" 161 "OP_MUL" 162 "OP_DIV" 163 "OP_EQ" 164 "OP_LT" 165 "OP_GT" 166 "OP_NOT" 167 "OP_LEN" 168 "OP_FIRST" 169 "OP_REST" 170 "OP_NTH" 171 "OP_CONS" 172 "OP_NEG" 173 "OP_INC" 174 "OP_DEC" 175 "OP_ASER_TAG" 224 "OP_ASER_FRAG" 225 "BYTECODE_MAGIC" "SXBC" "BYTECODE_VERSION" "CONST_NUMBER" "CONST_STRING" "CONST_BOOL" "CONST_NIL" "CONST_SYMBOL" "CONST_KEYWORD" "CONST_LIST" 7 "CONST_DICT" 8 "CONST_CODE" 9 "opcode-name" {:upvalue-count 0 :arity 1 :constants ("=" 1 "CONST" 2 "NIL" 3 "TRUE" 4 "FALSE" 5 "POP" 6 "DUP" 16 "LOCAL_GET" 17 "LOCAL_SET" 20 "GLOBAL_GET" 21 "GLOBAL_SET" 32 "JUMP" 33 "JUMP_IF_FALSE" 48 "CALL" 49 "TAIL_CALL" 50 "RETURN" 52 "CALL_PRIM" 128 "DEFINE" 144 "STR_CONCAT" "str" "OP_") :bytecode (16 0 1 1 0 52 0 0 2 33 6 0 1 2 0 32 59 1 16 0 1 3 0 52 0 0 2 33 6 0 1 4 0 32 41 1 16 0 1 5 0 52 0 0 2 33 6 0 1 6 0 32 23 1 16 0 1 7 0 52 0 0 2 33 6 0 1 8 0 32 5 1 16 0 1 9 0 52 0 0 2 33 6 0 1 10 0 32 243 0 16 0 1 11 0 52 0 0 2 33 6 0 1 12 0 32 225 0 16 0 1 13 0 52 0 0 2 33 6 0 1 14 0 32 207 0 16 0 1 15 0 52 0 0 2 33 6 0 1 16 0 32 189 0 16 0 1 17 0 52 0 0 2 33 6 0 1 18 0 32 171 0 16 0 1 19 0 52 0 0 2 33 6 0 1 20 0 32 153 0 16 0 1 21 0 52 0 0 2 33 6 0 1 22 0 32 135 0 16 0 1 23 0 52 0 0 2 33 6 0 1 24 0 32 117 0 16 0 1 25 0 52 0 0 2 33 6 0 1 26 0 32 99 0 16 0 1 27 0 52 0 0 2 33 6 0 1 28 0 32 81 0 16 0 1 29 0 52 0 0 2 33 6 0 1 30 0 32 63 0 16 0 1 31 0 52 0 0 2 33 6 0 1 32 0 32 45 0 16 0 1 33 0 52 0 0 2 33 6 0 1 34 0 32 27 0 16 0 1 35 0 52 0 0 2 33 6 0 1 36 0 32 9 0 1 38 0 16 0 52 37 0 2 50)}) :bytecode (1 1 0 128 0 0 5 1 3 0 128 2 0 5 1 5 0 128 4 0 5 1 7 0 128 6 0 5 1 9 0 128 8 0 5 1 11 0 128 10 0 5 1 13 0 128 12 0 5 1 15 0 128 14 0 5 1 17 0 128 16 0 5 1 19 0 128 18 0 5 1 21 0 128 20 0 5 1 23 0 128 22 0 5 1 25 0 128 24 0 5 1 27 0 128 26 0 5 1 29 0 128 28 0 5 1 31 0 128 30 0 5 1 33 0 128 32 0 5 1 35 0 128 34 0 5 1 37 0 128 36 0 5 1 39 0 128 38 0 5 1 41 0 128 40 0 5 1 43 0 128 42 0 5 1 45 0 128 44 0 5 1 47 0 128 46 0 5 1 49 0 128 48 0 5 1 51 0 128 50 0 5 1 53 0 128 52 0 5 1 55 0 128 54 0 5 1 57 0 128 56 0 5 1 59 0 128 58 0 5 1 61 0 128 60 0 5 1 63 0 128 62 0 5 1 65 0 128 64 0 5 1 67 0 128 66 0 5 1 69 0 128 68 0 5 1 71 0 128 70 0 5 1 73 0 128 72 0 5 1 75 0 128 74 0 5 1 77 0 128 76 0 5 1 79 0 128 78 0 5 1 81 0 128 80 0 5 1 83 0 128 82 0 5 1 85 0 128 84 0 5 1 87 0 128 86 0 5 1 89 0 128 88 0 5 1 91 0 128 90 0 5 1 93 0 128 92 0 5 1 95 0 128 94 0 5 1 97 0 128 96 0 5 1 99 0 128 98 0 5 1 101 0 128 100 0 5 1 103 0 128 102 0 5 1 105 0 128 104 0 5 1 107 0 128 106 0 5 1 109 0 128 108 0 5 1 111 0 128 110 0 5 1 113 0 128 112 0 5 1 115 0 128 114 0 5 1 117 0 128 116 0 5 1 119 0 128 118 0 5 1 121 0 128 120 0 5 1 123 0 128 122 0 5 1 125 0 128 124 0 5 1 127 0 128 126 0 5 1 129 0 128 128 0 5 1 131 0 128 130 0 5 1 133 0 128 132 0 5 1 135 0 128 134 0 5 1 137 0 128 136 0 5 1 139 0 128 138 0 5 1 141 0 128 140 0 5 1 143 0 128 142 0 5 1 1 0 128 144 0 5 1 1 0 128 145 0 5 1 3 0 128 146 0 5 1 5 0 128 147 0 5 1 7 0 128 148 0 5 1 9 0 128 149 0 5 1 11 0 128 150 0 5 1 152 0 128 151 0 5 1 154 0 128 153 0 5 1 156 0 128 155 0 5 51 158 0 128 157 0 50))) :constants ("OP_CONST" 1 "OP_NIL" 2 "OP_TRUE" 3 "OP_FALSE" 4 "OP_POP" 5 "OP_DUP" 6 "OP_LOCAL_GET" 16 "OP_LOCAL_SET" 17 "OP_UPVALUE_GET" 18 "OP_UPVALUE_SET" 19 "OP_GLOBAL_GET" 20 "OP_GLOBAL_SET" 21 "OP_JUMP" 32 "OP_JUMP_IF_FALSE" 33 "OP_JUMP_IF_TRUE" 34 "OP_CALL" 48 "OP_TAIL_CALL" 49 "OP_RETURN" 50 "OP_CLOSURE" 51 "OP_CALL_PRIM" 52 "OP_APPLY" 53 "OP_LIST" 64 "OP_DICT" 65 "OP_APPEND_BANG" 66 "OP_ITER_INIT" 80 "OP_ITER_NEXT" 81 "OP_MAP_OPEN" 82 "OP_MAP_APPEND" 83 "OP_MAP_CLOSE" 84 "OP_FILTER_TEST" 85 "OP_HO_MAP" 88 "OP_HO_FILTER" 89 "OP_HO_REDUCE" 90 "OP_HO_FOR_EACH" 91 "OP_HO_SOME" 92 "OP_HO_EVERY" 93 "OP_SCOPE_PUSH" 96 "OP_SCOPE_POP" 97 "OP_PROVIDE_PUSH" 98 "OP_PROVIDE_POP" 99 "OP_CONTEXT" 100 "OP_EMIT" 101 "OP_EMITTED" 102 "OP_RESET" 112 "OP_SHIFT" 113 "OP_DEFINE" 128 "OP_DEFCOMP" 129 "OP_DEFISLAND" 130 "OP_DEFMACRO" 131 "OP_EXPAND_MACRO" 132 "OP_STR_CONCAT" 144 "OP_STR_JOIN" 145 "OP_SERIALIZE" 146 "OP_ADD" 160 "OP_SUB" 161 "OP_MUL" 162 "OP_DIV" 163 "OP_EQ" 164 "OP_LT" 165 "OP_GT" 166 "OP_NOT" 167 "OP_LEN" 168 "OP_FIRST" 169 "OP_REST" 170 "OP_NTH" 171 "OP_CONS" 172 "OP_NEG" 173 "OP_INC" 174 "OP_DEC" 175 "OP_ASER_TAG" 224 "OP_ASER_FRAG" 225 "BYTECODE_MAGIC" "SXBC" "BYTECODE_VERSION" "CONST_NUMBER" "CONST_STRING" "CONST_BOOL" "CONST_NIL" "CONST_SYMBOL" "CONST_KEYWORD" "CONST_LIST" 7 "CONST_DICT" 8 "CONST_CODE" 9 "opcode-name" {:upvalue-count 0 :arity 1 :constants ("=" 1 "CONST" 2 "NIL" 3 "TRUE" 4 "FALSE" 5 "POP" 6 "DUP" 16 "LOCAL_GET" 17 "LOCAL_SET" 20 "GLOBAL_GET" 21 "GLOBAL_SET" 32 "JUMP" 33 "JUMP_IF_FALSE" 48 "CALL" 49 "TAIL_CALL" 50 "RETURN" 52 "CALL_PRIM" 128 "DEFINE" 144 "STR_CONCAT" "str" "OP_") :bytecode (16 0 1 1 0 52 0 0 2 33 6 0 1 2 0 32 59 1 16 0 1 3 0 52 0 0 2 33 6 0 1 4 0 32 41 1 16 0 1 5 0 52 0 0 2 33 6 0 1 6 0 32 23 1 16 0 1 7 0 52 0 0 2 33 6 0 1 8 0 32 5 1 16 0 1 9 0 52 0 0 2 33 6 0 1 10 0 32 243 0 16 0 1 11 0 52 0 0 2 33 6 0 1 12 0 32 225 0 16 0 1 13 0 52 0 0 2 33 6 0 1 14 0 32 207 0 16 0 1 15 0 52 0 0 2 33 6 0 1 16 0 32 189 0 16 0 1 17 0 52 0 0 2 33 6 0 1 18 0 32 171 0 16 0 1 19 0 52 0 0 2 33 6 0 1 20 0 32 153 0 16 0 1 21 0 52 0 0 2 33 6 0 1 22 0 32 135 0 16 0 1 23 0 52 0 0 2 33 6 0 1 24 0 32 117 0 16 0 1 25 0 52 0 0 2 33 6 0 1 26 0 32 99 0 16 0 1 27 0 52 0 0 2 33 6 0 1 28 0 32 81 0 16 0 1 29 0 52 0 0 2 33 6 0 1 30 0 32 63 0 16 0 1 31 0 52 0 0 2 33 6 0 1 32 0 32 45 0 16 0 1 33 0 52 0 0 2 33 6 0 1 34 0 32 27 0 16 0 1 35 0 52 0 0 2 33 6 0 1 36 0 32 9 0 1 38 0 16 0 52 37 0 2 50)} {:library (sx bytecode) :op "import"}) :bytecode (1 1 0 128 0 0 5 1 3 0 128 2 0 5 1 5 0 128 4 0 5 1 7 0 128 6 0 5 1 9 0 128 8 0 5 1 11 0 128 10 0 5 1 13 0 128 12 0 5 1 15 0 128 14 0 5 1 17 0 128 16 0 5 1 19 0 128 18 0 5 1 21 0 128 20 0 5 1 23 0 128 22 0 5 1 25 0 128 24 0 5 1 27 0 128 26 0 5 1 29 0 128 28 0 5 1 31 0 128 30 0 5 1 33 0 128 32 0 5 1 35 0 128 34 0 5 1 37 0 128 36 0 5 1 39 0 128 38 0 5 1 41 0 128 40 0 5 1 43 0 128 42 0 5 1 45 0 128 44 0 5 1 47 0 128 46 0 5 1 49 0 128 48 0 5 1 51 0 128 50 0 5 1 53 0 128 52 0 5 1 55 0 128 54 0 5 1 57 0 128 56 0 5 1 59 0 128 58 0 5 1 61 0 128 60 0 5 1 63 0 128 62 0 5 1 65 0 128 64 0 5 1 67 0 128 66 0 5 1 69 0 128 68 0 5 1 71 0 128 70 0 5 1 73 0 128 72 0 5 1 75 0 128 74 0 5 1 77 0 128 76 0 5 1 79 0 128 78 0 5 1 81 0 128 80 0 5 1 83 0 128 82 0 5 1 85 0 128 84 0 5 1 87 0 128 86 0 5 1 89 0 128 88 0 5 1 91 0 128 90 0 5 1 93 0 128 92 0 5 1 95 0 128 94 0 5 1 97 0 128 96 0 5 1 99 0 128 98 0 5 1 101 0 128 100 0 5 1 103 0 128 102 0 5 1 105 0 128 104 0 5 1 107 0 128 106 0 5 1 109 0 128 108 0 5 1 111 0 128 110 0 5 1 113 0 128 112 0 5 1 115 0 128 114 0 5 1 117 0 128 116 0 5 1 119 0 128 118 0 5 1 121 0 128 120 0 5 1 123 0 128 122 0 5 1 125 0 128 124 0 5 1 127 0 128 126 0 5 1 129 0 128 128 0 5 1 131 0 128 130 0 5 1 133 0 128 132 0 5 1 135 0 128 134 0 5 1 137 0 128 136 0 5 1 139 0 128 138 0 5 1 141 0 128 140 0 5 1 143 0 128 142 0 5 1 1 0 128 144 0 5 1 1 0 128 145 0 5 1 3 0 128 146 0 5 1 5 0 128 147 0 5 1 7 0 128 148 0 5 1 9 0 128 149 0 5 1 11 0 128 150 0 5 1 152 0 128 151 0 5 1 154 0 128 153 0 5 1 156 0 128 155 0 5 51 158 0 128 157 0 5 1 159 0 112 50)))

View File

@@ -15,7 +15,8 @@
;; Constant pool builder ;; Constant pool builder
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(define-library (sx compiler) (define-library
(sx compiler)
(export (export
make-pool make-pool
pool-add pool-add
@@ -60,9 +61,7 @@
compile compile
compile-module) compile-module)
(begin (begin
(define make-pool (fn () {:entries (if (primitive? "mutable-list") (mutable-list) (list)) :index {:_count 0}})) (define make-pool (fn () {:entries (if (primitive? "mutable-list") (mutable-list) (list)) :index {:_count 0}}))
(define (define
pool-add pool-add
(fn (fn
@@ -79,12 +78,7 @@
(dict-set! idx-map "_count" (+ idx 1)) (dict-set! idx-map "_count" (+ idx 1))
(append! (get pool "entries") value) (append! (get pool "entries") value)
idx))))) idx)))))
;; --------------------------------------------------------------------------
;; Scope analysis
;; --------------------------------------------------------------------------
(define make-scope (fn (parent) {:next-slot 0 :upvalues (list) :locals (list) :parent parent :is-function false})) (define make-scope (fn (parent) {:next-slot 0 :upvalues (list) :locals (list) :parent parent :is-function false}))
(define (define
scope-define-local scope-define-local
(fn (fn
@@ -100,7 +94,6 @@
(append! (get scope "locals") {:mutable false :slot slot :name name}) (append! (get scope "locals") {:mutable false :slot slot :name name})
(dict-set! scope "next-slot" (+ slot 1)) (dict-set! scope "next-slot" (+ slot 1))
slot))))) slot)))))
(define (define
scope-resolve scope-resolve
(fn (fn
@@ -119,7 +112,8 @@
{:index (get local "slot") :type "local"}) {:index (get local "slot") :type "local"})
(let (let
((upvals (get scope "upvalues")) ((upvals (get scope "upvalues"))
(uv-found (some (fn (u) (= (get u "name") name)) upvals))) (uv-found
(some (fn (u) (= (get u "name") name)) upvals)))
(if (if
uv-found uv-found
(let (let
@@ -142,29 +136,22 @@
(append! (get scope "upvalues") {:index (get parent-result "index") :is-local (= (get parent-result "type") "local") :uv-index uv-idx :name name}) (append! (get scope "upvalues") {:index (get parent-result "index") :is-local (= (get parent-result "type") "local") :uv-index uv-idx :name name})
{:index uv-idx :type "upvalue"}) {:index uv-idx :type "upvalue"})
parent-result)))))))))))) parent-result))))))))))))
;; --------------------------------------------------------------------------
;; Code emitter
;; --------------------------------------------------------------------------
(define make-emitter (fn () {:pool (make-pool) :bytecode (if (primitive? "mutable-list") (mutable-list) (list))})) (define make-emitter (fn () {:pool (make-pool) :bytecode (if (primitive? "mutable-list") (mutable-list) (list))}))
(define emit-byte (fn (em byte) (append! (get em "bytecode") byte))) (define emit-byte (fn (em byte) (append! (get em "bytecode") byte)))
(define (define
emit-u16 emit-u16
(fn (fn
(em value) (em value)
(emit-byte em (mod value 256)) (emit-byte em (mod value 256))
(emit-byte em (mod (floor (/ value 256)) 256)))) (emit-byte em (mod (floor (/ value 256)) 256))))
(define (define
emit-i16 emit-i16
(fn (fn
(em value) (em value)
(let ((v (if (< value 0) (+ value 65536) value))) (emit-u16 em v)))) (let
((v (if (< value 0) (+ value 65536) value)))
(emit-u16 em v))))
(define emit-op (fn (em opcode) (emit-byte em opcode))) (define emit-op (fn (em opcode) (emit-byte em opcode)))
(define (define
emit-const emit-const
(fn (fn
@@ -173,9 +160,7 @@
((idx (pool-add (get em "pool") value))) ((idx (pool-add (get em "pool") value)))
(emit-op em 1) (emit-op em 1)
(emit-u16 em idx)))) (emit-u16 em idx))))
(define current-offset (fn (em) (len (get em "bytecode")))) (define current-offset (fn (em) (len (get em "bytecode"))))
(define (define
patch-i16 patch-i16
(fn (fn
@@ -186,10 +171,6 @@
(bc (get em "bytecode"))) (bc (get em "bytecode")))
(set-nth! bc offset (mod v 256)) (set-nth! bc offset (mod v 256))
(set-nth! bc (+ offset 1) (mod (floor (/ v 256)) 256))))) (set-nth! bc (+ offset 1) (mod (floor (/ v 256)) 256)))))
;; --------------------------------------------------------------------------
;; Compilation — expression dispatch
;; --------------------------------------------------------------------------
(define (define
compile-expr compile-expr
(fn (fn
@@ -216,7 +197,6 @@
(= (type-of expr) "dict") (= (type-of expr) "dict")
(compile-dict em expr scope) (compile-dict em expr scope)
:else (emit-const em expr)))) :else (emit-const em expr))))
(define (define
compile-symbol compile-symbol
(fn (fn
@@ -232,7 +212,6 @@
((idx (pool-add (get em "pool") name))) ((idx (pool-add (get em "pool") name)))
(emit-op em 20) (emit-op em 20)
(emit-u16 em idx)))))) (emit-u16 em idx))))))
(define (define
compile-dict compile-dict
(fn (fn
@@ -247,10 +226,6 @@
ks) ks)
(emit-op em 65) (emit-op em 65)
(emit-u16 em count)))) (emit-u16 em count))))
;; --------------------------------------------------------------------------
;; List compilation — special forms, calls
;; --------------------------------------------------------------------------
(define (define
compile-list compile-list
(fn (fn
@@ -331,11 +306,23 @@
(compile-expr em (first args) scope false) (compile-expr em (first args) scope false)
(emit-op em 112) (emit-op em 112)
nil) nil)
(= name "import")
(let () (emit-const em {:library (first args) :op "import"}) (emit-op em 112) nil)
(= name "define-library")
(let
((body (filter (fn (a) (and (list? a) (not (empty? a)) (= (first a) (quote begin)))) args)))
(when
(not (empty? body))
(let
((forms (rest (first body))))
(for-each
(fn
(expr)
(compile-expr em expr scope false)
(emit-op em 5))
(init forms))
(compile-expr em (last forms) scope false))))
:else (compile-call em head args scope tail?))))))) :else (compile-call em head args scope tail?)))))))
;; --------------------------------------------------------------------------
;; Special form compilation
;; --------------------------------------------------------------------------
(define (define
compile-if compile-if
(fn (fn
@@ -354,13 +341,15 @@
(let (let
((end-jump (current-offset em))) ((end-jump (current-offset em)))
(emit-i16 em 0) (emit-i16 em 0)
(patch-i16 em else-jump (- (current-offset em) (+ else-jump 2))) (patch-i16
em
else-jump
(- (current-offset em) (+ else-jump 2)))
(if (if
(nil? else-expr) (nil? else-expr)
(emit-op em 2) (emit-op em 2)
(compile-expr em else-expr scope tail?)) (compile-expr em else-expr scope tail?))
(patch-i16 em end-jump (- (current-offset em) (+ end-jump 2)))))))) (patch-i16 em end-jump (- (current-offset em) (+ end-jump 2))))))))
(define (define
compile-when compile-when
(fn (fn
@@ -377,10 +366,12 @@
(let (let
((end-jump (current-offset em))) ((end-jump (current-offset em)))
(emit-i16 em 0) (emit-i16 em 0)
(patch-i16 em skip-jump (- (current-offset em) (+ skip-jump 2))) (patch-i16
em
skip-jump
(- (current-offset em) (+ skip-jump 2)))
(emit-op em 2) (emit-op em 2)
(patch-i16 em end-jump (- (current-offset em) (+ end-jump 2)))))))) (patch-i16 em end-jump (- (current-offset em) (+ end-jump 2))))))))
(define (define
compile-and compile-and
(fn (fn
@@ -401,7 +392,6 @@
(emit-op em 5) (emit-op em 5)
(compile-and em (rest args) scope tail?) (compile-and em (rest args) scope tail?)
(patch-i16 em skip (- (current-offset em) (+ skip 2))))))))) (patch-i16 em skip (- (current-offset em) (+ skip 2)))))))))
(define (define
compile-or compile-or
(fn (fn
@@ -422,7 +412,6 @@
(emit-op em 5) (emit-op em 5)
(compile-or em (rest args) scope tail?) (compile-or em (rest args) scope tail?)
(patch-i16 em skip (- (current-offset em) (+ skip 2))))))))) (patch-i16 em skip (- (current-offset em) (+ skip 2)))))))))
(define (define
compile-begin compile-begin
(fn (fn
@@ -457,7 +446,6 @@
(compile-expr em (first exprs) scope false) (compile-expr em (first exprs) scope false)
(emit-op em 5) (emit-op em 5)
(compile-begin em (rest exprs) scope tail?)))))) (compile-begin em (rest exprs) scope tail?))))))
(define (define
compile-let compile-let
(fn (fn
@@ -504,7 +492,6 @@
(emit-byte em slot))) (emit-byte em slot)))
bindings) bindings)
(compile-begin em body let-scope tail?))))) (compile-begin em body let-scope tail?)))))
(define (define
compile-letrec compile-letrec
(fn (fn
@@ -529,7 +516,6 @@
(fn (i) (list (nth bindings i) (nth slots i))) (fn (i) (list (nth bindings i) (nth slots i)))
(range 0 (len bindings))))) (range 0 (len bindings)))))
(compile-begin em body let-scope tail?)))) (compile-begin em body let-scope tail?))))
(define (define
compile-lambda compile-lambda
(fn (fn
@@ -563,7 +549,6 @@
(emit-byte em (if (get uv "is-local") 1 0)) (emit-byte em (if (get uv "is-local") 1 0))
(emit-byte em (get uv "index"))) (emit-byte em (get uv "index")))
upvals))))) upvals)))))
(define (define
compile-define compile-define
(fn (fn
@@ -598,7 +583,6 @@
(compile-expr em value scope false) (compile-expr em value scope false)
(emit-op em 128) (emit-op em 128)
(emit-u16 em name-idx)))))) (emit-u16 em name-idx))))))
(define (define
compile-set compile-set
(fn (fn
@@ -617,13 +601,11 @@
((idx (pool-add (get em "pool") name))) ((idx (pool-add (get em "pool") name)))
(emit-op em 21) (emit-op em 21)
(emit-u16 em idx)))))) (emit-u16 em idx))))))
(define (define
compile-quote compile-quote
(fn (fn
(em args) (em args)
(if (empty? args) (emit-op em 2) (emit-const em (first args))))) (if (empty? args) (emit-op em 2) (emit-const em (first args)))))
(define (define
compile-cond compile-cond
(fn (fn
@@ -660,7 +642,6 @@
em em
end-jump end-jump
(- (current-offset em) (+ end-jump 2))))))))))) (- (current-offset em) (+ end-jump 2)))))))))))
(define (define
compile-case compile-case
(fn (fn
@@ -670,7 +651,6 @@
(let (let
((clauses (rest args))) ((clauses (rest args)))
(compile-case-clauses em clauses scope tail?)))) (compile-case-clauses em clauses scope tail?))))
(define (define
compile-case-clauses compile-case-clauses
(fn (fn
@@ -681,7 +661,8 @@
(let (let
((test (first clauses)) ((test (first clauses))
(body (nth clauses 1)) (body (nth clauses 1))
(rest-clauses (if (> (len clauses) 2) (slice clauses 2) (list)))) (rest-clauses
(if (> (len clauses) 2) (slice clauses 2) (list))))
(if (if
(or (or
(and (and
@@ -713,9 +694,6 @@
em em
end-jump end-jump
(- (current-offset em) (+ end-jump 2))))))))))) (- (current-offset em) (+ end-jump 2)))))))))))
;; compile-match — compile (match expr (pattern body) ...) to bytecode.
;; Self-contained via letrec so JIT can find the recursive helper.
(define (define
compile-match compile-match
(fn (fn
@@ -724,7 +702,6 @@
(letrec (letrec
((do-clauses (fn (clauses) (if (empty? clauses) (do (emit-op em 5) (let ((idx (pool-add (get em "pool") "match: no clause matched"))) (emit-op em 1) (emit-u16 em idx) (emit-op em 52) (emit-u16 em (pool-add (get em "pool") "error")) (emit-byte em 1))) (let ((clause (first clauses)) (pattern (first clause)) (body (nth clause 1)) (rest-clauses (rest clauses))) (cond (and (= (type-of pattern) "symbol") (= (symbol-name pattern) "_")) (do (emit-op em 5) (compile-expr em body scope tail?)) (and (= (type-of pattern) "symbol") (not (= (symbol-name pattern) "true")) (not (= (symbol-name pattern) "false")) (not (= (symbol-name pattern) "nil"))) (let ((var-name (symbol-name pattern)) (inner-scope (scope-add scope var-name))) (emit-op em 13) (emit-byte em (scope-index inner-scope var-name)) (compile-expr em body inner-scope tail?)) (and (list? pattern) (= (len pattern) 2) (= (type-of (first pattern)) "symbol") (= (symbol-name (first pattern)) "quote") (= (type-of (nth pattern 1)) "symbol")) (do (emit-op em 6) (let ((idx (pool-add (get em "pool") (make-symbol (symbol-name (nth pattern 1)))))) (emit-op em 1) (emit-u16 em idx)) (let ((eq-idx (pool-add (get em "pool") "="))) (emit-op em 52) (emit-u16 em eq-idx) (emit-byte em 2)) (emit-op em 33) (let ((skip (current-offset em))) (emit-i16 em 0) (emit-op em 5) (compile-expr 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))) (do-clauses rest-clauses) (patch-i16 em end-jump (- (current-offset em) (+ end-jump 2)))))) :else (do (emit-op em 6) (compile-expr em pattern scope false) (let ((eq-idx (pool-add (get em "pool") "="))) (emit-op em 52) (emit-u16 em eq-idx) (emit-byte em 2)) (emit-op em 33) (let ((skip (current-offset em))) (emit-i16 em 0) (emit-op em 5) (compile-expr 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))) (do-clauses rest-clauses) (patch-i16 em end-jump (- (current-offset em) (+ end-jump 2)))))))))))) ((do-clauses (fn (clauses) (if (empty? clauses) (do (emit-op em 5) (let ((idx (pool-add (get em "pool") "match: no clause matched"))) (emit-op em 1) (emit-u16 em idx) (emit-op em 52) (emit-u16 em (pool-add (get em "pool") "error")) (emit-byte em 1))) (let ((clause (first clauses)) (pattern (first clause)) (body (nth clause 1)) (rest-clauses (rest clauses))) (cond (and (= (type-of pattern) "symbol") (= (symbol-name pattern) "_")) (do (emit-op em 5) (compile-expr em body scope tail?)) (and (= (type-of pattern) "symbol") (not (= (symbol-name pattern) "true")) (not (= (symbol-name pattern) "false")) (not (= (symbol-name pattern) "nil"))) (let ((var-name (symbol-name pattern)) (inner-scope (scope-add scope var-name))) (emit-op em 13) (emit-byte em (scope-index inner-scope var-name)) (compile-expr em body inner-scope tail?)) (and (list? pattern) (= (len pattern) 2) (= (type-of (first pattern)) "symbol") (= (symbol-name (first pattern)) "quote") (= (type-of (nth pattern 1)) "symbol")) (do (emit-op em 6) (let ((idx (pool-add (get em "pool") (make-symbol (symbol-name (nth pattern 1)))))) (emit-op em 1) (emit-u16 em idx)) (let ((eq-idx (pool-add (get em "pool") "="))) (emit-op em 52) (emit-u16 em eq-idx) (emit-byte em 2)) (emit-op em 33) (let ((skip (current-offset em))) (emit-i16 em 0) (emit-op em 5) (compile-expr 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))) (do-clauses rest-clauses) (patch-i16 em end-jump (- (current-offset em) (+ end-jump 2)))))) :else (do (emit-op em 6) (compile-expr em pattern scope false) (let ((eq-idx (pool-add (get em "pool") "="))) (emit-op em 52) (emit-u16 em eq-idx) (emit-byte em 2)) (emit-op em 33) (let ((skip (current-offset em))) (emit-i16 em 0) (emit-op em 5) (compile-expr 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))) (do-clauses rest-clauses) (patch-i16 em end-jump (- (current-offset em) (+ end-jump 2))))))))))))
(do-clauses (rest args))))) (do-clauses (rest args)))))
(define (define
compile-thread compile-thread
(fn (fn
@@ -739,7 +716,6 @@
(let (let
((val-expr (first args)) (forms (rest args))) ((val-expr (first args)) (forms (rest args)))
(compile-thread-step em val-expr forms scope tail?)))))) (compile-thread-step em val-expr forms scope tail?))))))
(define (define
compile-thread-step compile-thread-step
(fn (fn
@@ -759,7 +735,6 @@
(do (do
(compile-expr em call-expr scope false) (compile-expr em call-expr scope false)
(compile-thread-step em call-expr rest-forms scope tail?)))))))) (compile-thread-step em call-expr rest-forms scope tail?))))))))
(define (define
compile-defcomp compile-defcomp
(fn (fn
@@ -772,8 +747,6 @@
(emit-const em (concat (list (make-symbol "defcomp")) args)) (emit-const em (concat (list (make-symbol "defcomp")) args))
(emit-op em 48) (emit-op em 48)
(emit-byte em 1))) (emit-byte em 1)))
;; CALL 1
(define (define
compile-defmacro compile-defmacro
(fn (fn
@@ -786,14 +759,12 @@
(emit-const em (concat (list (make-symbol "defmacro")) args)) (emit-const em (concat (list (make-symbol "defmacro")) args))
(emit-op em 48) (emit-op em 48)
(emit-byte em 1))) (emit-byte em 1)))
(define (define
compile-quasiquote compile-quasiquote
(fn (fn
(em expr scope) (em expr scope)
"Compile quasiquote inline — walks the template at compile time,\n emitting code that builds the structure at runtime. Unquoted\n expressions are compiled normally (resolving locals/upvalues),\n avoiding the qq-expand-runtime env-lookup limitation." "Compile quasiquote inline — walks the template at compile time,\n emitting code that builds the structure at runtime. Unquoted\n expressions are compiled normally (resolving locals/upvalues),\n avoiding the qq-expand-runtime env-lookup limitation."
(compile-qq-expr em expr scope))) (compile-qq-expr em expr scope)))
(define (define
compile-qq-expr compile-qq-expr
(fn (fn
@@ -813,7 +784,6 @@
(= (symbol-name head) "unquote")) (= (symbol-name head) "unquote"))
(compile-expr em (nth expr 1) scope false) (compile-expr em (nth expr 1) scope false)
(compile-qq-list em expr scope))))))) (compile-qq-list em expr scope)))))))
(define (define
compile-qq-list compile-qq-list
(fn (fn
@@ -863,10 +833,6 @@
(emit-op em 52) (emit-op em 52)
(emit-u16 em concat-idx) (emit-u16 em concat-idx)
(emit-byte em segment-count)))))))) (emit-byte em segment-count))))))))
;; --------------------------------------------------------------------------
;; Function call compilation
;; --------------------------------------------------------------------------
(define (define
compile-call compile-call
(fn (fn
@@ -890,10 +856,6 @@
tail? tail?
(do (emit-op em 49) (emit-byte em (len args))) (do (emit-op em 49) (emit-byte em (len args)))
(do (emit-op em 48) (emit-byte em (len args))))))))) (do (emit-op em 48) (emit-byte em (len args)))))))))
;; --------------------------------------------------------------------------
;; Top-level API
;; --------------------------------------------------------------------------
(define (define
compile compile
(fn (fn
@@ -904,7 +866,6 @@
(compile-expr em expr scope false) (compile-expr em expr scope false)
(emit-op em 50) (emit-op em 50)
{:constants (get (get em "pool") "entries") :bytecode (get em "bytecode")}))) {:constants (get (get em "pool") "entries") :bytecode (get em "bytecode")})))
(define (define
compile-module compile-module
(fn (fn
@@ -917,10 +878,7 @@
(init exprs)) (init exprs))
(compile-expr em (last exprs) scope false) (compile-expr em (last exprs) scope false)
(emit-op em 50) (emit-op em 50)
{:constants (get (get em "pool") "entries") :bytecode (get em "bytecode")}))) {:constants (get (get em "pool") "entries") :bytecode (get em "bytecode")}))))) ;; end define-library
)) ;; end define-library
;; Re-export to global namespace for backward compatibility ;; Re-export to global namespace for backward compatibility
(import (sx compiler)) (import (sx compiler))

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,3 @@
(sxbc 1 "320fb4826d09fed3" (sxbc 1 "320fb4826d09fed3"
(code (code
:constants ("freeze-registry" "dict" "freeze-signal" {:upvalue-count 0 :arity 2 :constants ("context" "sx-freeze-scope" "get" "freeze-registry" "list" "append!" "dict" "name" "signal" "dict-set!") :bytecode (1 1 0 2 52 0 0 2 17 2 16 2 33 55 0 20 3 0 16 2 52 2 0 2 6 34 5 0 5 52 4 0 0 17 3 16 3 1 7 0 16 0 1 8 0 16 1 52 6 0 4 52 5 0 2 5 20 3 0 16 2 16 3 52 9 0 3 32 1 0 2 50)} "freeze-scope" {:upvalue-count 0 :arity 2 :constants ("scope-push!" "sx-freeze-scope" "dict-set!" "freeze-registry" "list" "cek-call" "scope-pop!") :bytecode (1 1 0 16 0 52 0 0 2 5 20 3 0 16 0 52 4 0 0 52 2 0 3 5 16 1 2 52 5 0 2 5 1 1 0 52 6 0 1 5 2 50)} "cek-freeze-scope" {:upvalue-count 0 :arity 1 :constants ("get" "freeze-registry" "list" "dict" "for-each" {:upvalue-count 1 :arity 1 :constants ("dict-set!" "get" "name" "signal-value" "signal") :bytecode (18 0 16 0 1 2 0 52 1 0 2 20 3 0 16 0 1 4 0 52 1 0 2 48 1 52 0 0 3 50)} "name" "signals") :bytecode (20 1 0 16 0 52 0 0 2 6 34 5 0 5 52 2 0 0 17 1 52 3 0 0 17 2 51 5 0 1 2 16 1 52 4 0 2 5 1 6 0 16 0 1 7 0 16 2 52 3 0 4 50)} "cek-freeze-all" {:upvalue-count 0 :arity 0 :constants ("map" {:upvalue-count 0 :arity 1 :constants ("cek-freeze-scope") :bytecode (20 0 0 16 0 49 1 50)} "keys" "freeze-registry") :bytecode (51 1 0 20 3 0 52 2 0 1 52 0 0 2 50)} "cek-thaw-scope" {:upvalue-count 0 :arity 2 :constants ("get" "freeze-registry" "list" "signals" "for-each" {:upvalue-count 1 :arity 1 :constants ("get" "name" "signal" "not" "nil?" "reset!") :bytecode (16 0 1 1 0 52 0 0 2 17 1 16 0 1 2 0 52 0 0 2 17 2 18 0 16 1 52 0 0 2 17 3 16 3 52 4 0 1 52 3 0 1 33 12 0 20 5 0 16 2 16 3 49 2 32 1 0 2 50)}) :bytecode (20 1 0 16 0 52 0 0 2 6 34 5 0 5 52 2 0 0 17 2 16 1 1 3 0 52 0 0 2 17 3 16 3 33 14 0 51 5 0 1 3 16 2 52 4 0 2 32 1 0 2 50)} "cek-thaw-all" {:upvalue-count 0 :arity 1 :constants ("for-each" {:upvalue-count 0 :arity 1 :constants ("cek-thaw-scope" "get" "name") :bytecode (20 0 0 16 0 1 2 0 52 1 0 2 16 0 49 2 50)}) :bytecode (51 1 0 16 0 52 0 0 2 50)} "freeze-to-sx" {:upvalue-count 0 :arity 1 :constants ("sx-serialize" "cek-freeze-scope") :bytecode (20 1 0 16 0 48 1 52 0 0 1 50)} "thaw-from-sx" {:upvalue-count 0 :arity 1 :constants ("sx-parse" "not" "empty?" "first" "cek-thaw-scope" "get" "name") :bytecode (20 0 0 16 0 48 1 17 1 16 1 52 2 0 1 52 1 0 1 33 27 0 16 1 52 3 0 1 17 2 20 4 0 16 2 1 6 0 52 5 0 2 16 2 49 2 32 1 0 2 50)}) :bytecode (52 1 0 0 128 0 0 5 51 3 0 128 2 0 5 51 5 0 128 4 0 5 51 7 0 128 6 0 5 51 9 0 128 8 0 5 51 11 0 128 10 0 5 51 13 0 128 12 0 5 51 15 0 128 14 0 5 51 17 0 128 16 0 50))) :constants ("freeze-registry" "dict" "freeze-signal" {:upvalue-count 0 :arity 2 :constants ("context" "sx-freeze-scope" "get" "freeze-registry" "list" "append!" "dict" "name" "signal" "dict-set!") :bytecode (1 1 0 2 52 0 0 2 17 2 16 2 33 55 0 20 3 0 16 2 52 2 0 2 6 34 5 0 5 52 4 0 0 17 3 16 3 1 7 0 16 0 1 8 0 16 1 52 6 0 4 52 5 0 2 5 20 3 0 16 2 16 3 52 9 0 3 32 1 0 2 50)} "freeze-scope" {:upvalue-count 0 :arity 2 :constants ("scope-push!" "sx-freeze-scope" "dict-set!" "freeze-registry" "list" "cek-call" "scope-pop!") :bytecode (1 1 0 16 0 52 0 0 2 5 20 3 0 16 0 52 4 0 0 52 2 0 3 5 16 1 2 52 5 0 2 5 1 1 0 52 6 0 1 5 2 50)} "cek-freeze-scope" {:upvalue-count 0 :arity 1 :constants ("get" "freeze-registry" "list" "dict" "for-each" {:upvalue-count 1 :arity 1 :constants ("dict-set!" "get" "name" "signal-value" "signal") :bytecode (18 0 16 0 1 2 0 52 1 0 2 20 3 0 16 0 1 4 0 52 1 0 2 48 1 52 0 0 3 50)} "name" "signals") :bytecode (20 1 0 16 0 52 0 0 2 6 34 5 0 5 52 2 0 0 17 1 52 3 0 0 17 2 51 5 0 1 2 16 1 52 4 0 2 5 1 6 0 16 0 1 7 0 16 2 52 3 0 4 50)} "cek-freeze-all" {:upvalue-count 0 :arity 0 :constants ("map" {:upvalue-count 0 :arity 1 :constants ("cek-freeze-scope") :bytecode (20 0 0 16 0 49 1 50)} "keys" "freeze-registry") :bytecode (51 1 0 20 3 0 52 2 0 1 52 0 0 2 50)} "cek-thaw-scope" {:upvalue-count 0 :arity 2 :constants ("get" "freeze-registry" "list" "signals" "for-each" {:upvalue-count 1 :arity 1 :constants ("get" "name" "signal" "not" "nil?" "reset!") :bytecode (16 0 1 1 0 52 0 0 2 17 1 16 0 1 2 0 52 0 0 2 17 2 18 0 16 1 52 0 0 2 17 3 16 3 52 4 0 1 52 3 0 1 33 12 0 20 5 0 16 2 16 3 49 2 32 1 0 2 50)}) :bytecode (20 1 0 16 0 52 0 0 2 6 34 5 0 5 52 2 0 0 17 2 16 1 1 3 0 52 0 0 2 17 3 16 3 33 14 0 51 5 0 1 3 16 2 52 4 0 2 32 1 0 2 50)} "cek-thaw-all" {:upvalue-count 0 :arity 1 :constants ("for-each" {:upvalue-count 0 :arity 1 :constants ("cek-thaw-scope" "get" "name") :bytecode (20 0 0 16 0 1 2 0 52 1 0 2 16 0 49 2 50)}) :bytecode (51 1 0 16 0 52 0 0 2 50)} "freeze-to-sx" {:upvalue-count 0 :arity 1 :constants ("sx-serialize" "cek-freeze-scope") :bytecode (20 1 0 16 0 48 1 52 0 0 1 50)} "thaw-from-sx" {:upvalue-count 0 :arity 1 :constants ("sx-parse" "not" "empty?" "first" "cek-thaw-scope" "get" "name") :bytecode (20 0 0 16 0 48 1 17 1 16 1 52 2 0 1 52 1 0 1 33 27 0 16 1 52 3 0 1 17 2 20 4 0 16 2 1 6 0 52 5 0 2 16 2 49 2 32 1 0 2 50)} {:library (sx freeze) :op "import"}) :bytecode (52 1 0 0 128 0 0 5 51 3 0 128 2 0 5 51 5 0 128 4 0 5 51 7 0 128 6 0 5 51 9 0 128 8 0 5 51 11 0 128 10 0 5 51 13 0 128 12 0 5 51 15 0 128 14 0 5 51 17 0 128 16 0 5 1 18 0 112 50)))

View File

@@ -1,3 +1,3 @@
(sxbc 1 "0bc2cc2f659d5a90" (sxbc 1 "0bc2cc2f659d5a90"
(code (code
:constants ("assert-signal-value" {:upvalue-count 0 :arity 2 :constants ("deref" "assert=" "str" "Expected signal value " ", got ") :bytecode (20 0 0 16 0 48 1 17 2 20 1 0 16 2 16 1 1 3 0 16 1 1 4 0 16 2 52 2 0 4 49 3 50)} "assert-signal-has-subscribers" {:upvalue-count 0 :arity 1 :constants ("assert" ">" "len" "signal-subscribers" 0 "Expected signal to have subscribers") :bytecode (20 0 0 20 3 0 16 0 48 1 52 2 0 1 1 4 0 52 1 0 2 1 5 0 49 2 50)} "assert-signal-no-subscribers" {:upvalue-count 0 :arity 1 :constants ("assert" "=" "len" "signal-subscribers" 0 "Expected signal to have no subscribers") :bytecode (20 0 0 20 3 0 16 0 48 1 52 2 0 1 1 4 0 52 1 0 2 1 5 0 49 2 50)} "assert-signal-subscriber-count" {:upvalue-count 0 :arity 2 :constants ("len" "signal-subscribers" "assert=" "str" "Expected " " subscribers, got ") :bytecode (20 1 0 16 0 48 1 52 0 0 1 17 2 20 2 0 16 2 16 1 1 4 0 16 1 1 5 0 16 2 52 3 0 4 49 3 50)} "simulate-signal-set!" {:upvalue-count 0 :arity 2 :constants ("reset!") :bytecode (20 0 0 16 0 16 1 49 2 50)} "simulate-signal-swap!" {:upvalue-count 0 :arity 2 :constants ("swap!") :bytecode (20 0 0 16 0 16 1 49 2 50)} "assert-computed-dep-count" {:upvalue-count 0 :arity 2 :constants ("len" "signal-deps" "assert=" "str" "Expected " " deps, got ") :bytecode (20 1 0 16 0 48 1 52 0 0 1 17 2 20 2 0 16 2 16 1 1 4 0 16 1 1 5 0 16 2 52 3 0 4 49 3 50)} "assert-computed-depends-on" {:upvalue-count 0 :arity 2 :constants ("assert" "contains?" "signal-deps" "Expected computed to depend on the given signal") :bytecode (20 0 0 20 2 0 16 0 48 1 16 1 52 1 0 2 1 3 0 49 2 50)} "count-effect-runs" {:upvalue-count 0 :arity 1 :constants ("signal" 0 "effect" {:upvalue-count 1 :arity 0 :constants ("deref") :bytecode (20 0 0 18 0 49 1 50)} {:upvalue-count 2 :arity 0 :constants ("+" 1 "cek-call") :bytecode (18 0 1 1 0 52 0 0 2 19 0 5 18 1 2 52 2 0 2 50)}) :bytecode (20 0 0 1 1 0 48 1 17 1 20 2 0 51 3 0 1 1 48 1 5 1 1 0 17 2 20 2 0 51 4 0 1 2 1 0 48 1 17 3 16 2 50)} "make-test-signal" {:upvalue-count 0 :arity 1 :constants ("signal" "list" "effect" {:upvalue-count 2 :arity 0 :constants ("append!" "deref") :bytecode (18 0 20 1 0 18 1 48 1 52 0 0 2 50)} "history") :bytecode (20 0 0 16 0 48 1 17 1 52 1 0 0 17 2 20 2 0 51 3 0 1 2 1 1 48 1 5 1 0 0 16 1 1 4 0 16 2 65 2 0 50)} "assert-batch-coalesces" {:upvalue-count 0 :arity 2 :constants (0 "signal" "effect" {:upvalue-count 2 :arity 0 :constants ("deref" "+" 1) :bytecode (20 0 0 18 0 48 1 5 18 1 1 2 0 52 1 0 2 19 1 50)} "batch" "assert=" "str" "Expected " " notifications, got ") :bytecode (1 0 0 17 2 20 1 0 1 0 0 48 1 17 3 20 2 0 51 3 0 1 3 1 2 48 1 5 1 0 0 17 2 5 20 4 0 16 0 48 1 5 20 5 0 16 2 16 1 1 7 0 16 1 1 8 0 16 2 52 6 0 4 49 3 50)}) :bytecode (51 1 0 128 0 0 5 51 3 0 128 2 0 5 51 5 0 128 4 0 5 51 7 0 128 6 0 5 51 9 0 128 8 0 5 51 11 0 128 10 0 5 51 13 0 128 12 0 5 51 15 0 128 14 0 5 51 17 0 128 16 0 5 51 19 0 128 18 0 5 51 21 0 128 20 0 50))) :constants ("assert-signal-value" {:upvalue-count 0 :arity 2 :constants ("deref" "assert=" "str" "Expected signal value " ", got ") :bytecode (20 0 0 16 0 48 1 17 2 20 1 0 16 2 16 1 1 3 0 16 1 1 4 0 16 2 52 2 0 4 49 3 50)} "assert-signal-has-subscribers" {:upvalue-count 0 :arity 1 :constants ("assert" ">" "len" "signal-subscribers" 0 "Expected signal to have subscribers") :bytecode (20 0 0 20 3 0 16 0 48 1 52 2 0 1 1 4 0 52 1 0 2 1 5 0 49 2 50)} "assert-signal-no-subscribers" {:upvalue-count 0 :arity 1 :constants ("assert" "=" "len" "signal-subscribers" 0 "Expected signal to have no subscribers") :bytecode (20 0 0 20 3 0 16 0 48 1 52 2 0 1 1 4 0 52 1 0 2 1 5 0 49 2 50)} "assert-signal-subscriber-count" {:upvalue-count 0 :arity 2 :constants ("len" "signal-subscribers" "assert=" "str" "Expected " " subscribers, got ") :bytecode (20 1 0 16 0 48 1 52 0 0 1 17 2 20 2 0 16 2 16 1 1 4 0 16 1 1 5 0 16 2 52 3 0 4 49 3 50)} "simulate-signal-set!" {:upvalue-count 0 :arity 2 :constants ("reset!") :bytecode (20 0 0 16 0 16 1 49 2 50)} "simulate-signal-swap!" {:upvalue-count 0 :arity 2 :constants ("swap!") :bytecode (20 0 0 16 0 16 1 49 2 50)} "assert-computed-dep-count" {:upvalue-count 0 :arity 2 :constants ("len" "signal-deps" "assert=" "str" "Expected " " deps, got ") :bytecode (20 1 0 16 0 48 1 52 0 0 1 17 2 20 2 0 16 2 16 1 1 4 0 16 1 1 5 0 16 2 52 3 0 4 49 3 50)} "assert-computed-depends-on" {:upvalue-count 0 :arity 2 :constants ("assert" "contains?" "signal-deps" "Expected computed to depend on the given signal") :bytecode (20 0 0 20 2 0 16 0 48 1 16 1 52 1 0 2 1 3 0 49 2 50)} "count-effect-runs" {:upvalue-count 0 :arity 1 :constants ("signal" 0 "effect" {:upvalue-count 1 :arity 0 :constants ("deref") :bytecode (20 0 0 18 0 49 1 50)} {:upvalue-count 2 :arity 0 :constants ("+" 1 "cek-call") :bytecode (18 0 1 1 0 52 0 0 2 19 0 5 18 1 2 52 2 0 2 50)}) :bytecode (20 0 0 1 1 0 48 1 17 1 20 2 0 51 3 0 1 1 48 1 5 1 1 0 17 2 20 2 0 51 4 0 1 2 1 0 48 1 17 3 16 2 50)} "make-test-signal" {:upvalue-count 0 :arity 1 :constants ("signal" "list" "effect" {:upvalue-count 2 :arity 0 :constants ("append!" "deref") :bytecode (18 0 20 1 0 18 1 48 1 52 0 0 2 50)} "history") :bytecode (20 0 0 16 0 48 1 17 1 52 1 0 0 17 2 20 2 0 51 3 0 1 2 1 1 48 1 5 1 0 0 16 1 1 4 0 16 2 65 2 0 50)} "assert-batch-coalesces" {:upvalue-count 0 :arity 2 :constants (0 "signal" "effect" {:upvalue-count 2 :arity 0 :constants ("deref" "+" 1) :bytecode (20 0 0 18 0 48 1 5 18 1 1 2 0 52 1 0 2 19 1 50)} "batch" "assert=" "str" "Expected " " notifications, got ") :bytecode (1 0 0 17 2 20 1 0 1 0 0 48 1 17 3 20 2 0 51 3 0 1 3 1 2 48 1 5 1 0 0 17 2 5 20 4 0 16 0 48 1 5 20 5 0 16 2 16 1 1 7 0 16 1 1 8 0 16 2 52 6 0 4 49 3 50)} {:library (sx harness-reactive) :op "import"}) :bytecode (51 1 0 128 0 0 5 51 3 0 128 2 0 5 51 5 0 128 4 0 5 51 7 0 128 6 0 5 51 9 0 128 8 0 5 51 11 0 128 10 0 5 51 13 0 128 12 0 5 51 15 0 128 14 0 5 51 17 0 128 16 0 5 51 19 0 128 18 0 5 51 21 0 128 20 0 5 1 22 0 112 50)))

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

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

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,3 @@
(sxbc 1 "232c1519553b1d5f" (sxbc 1 "232c1519553b1d5f"
(code (code
:constants ("with-marsh-scope" {:upvalue-count 0 :arity 2 :constants ("list" "with-island-scope" {:upvalue-count 1 :arity 1 :constants ("append!") :bytecode (18 0 16 0 52 0 0 2 50)} "dom-set-data" "sx-marsh-disposers") :bytecode (52 0 0 0 17 2 20 1 0 51 2 0 1 2 16 1 48 2 5 20 3 0 16 0 1 4 0 16 2 49 3 50)} "dispose-marsh-scope" {:upvalue-count 0 :arity 1 :constants ("dom-get-data" "sx-marsh-disposers" "for-each" {:upvalue-count 0 :arity 1 :constants ("cek-call") :bytecode (16 0 2 52 0 0 2 50)} "dom-set-data") :bytecode (20 0 0 16 0 1 1 0 48 2 17 1 16 1 33 24 0 51 3 0 16 1 52 2 0 2 5 20 4 0 16 0 1 1 0 2 49 3 32 1 0 2 50)} "emit-event" {:upvalue-count 0 :arity 3 :constants ("dom-dispatch") :bytecode (20 0 0 16 0 16 1 16 2 49 3 50)} "on-event" {:upvalue-count 0 :arity 3 :constants ("dom-on") :bytecode (20 0 0 16 0 16 1 16 2 49 3 50)} "bridge-event" {:upvalue-count 0 :arity 4 :constants ("effect" {:upvalue-count 4 :arity 0 :constants ("dom-on" {:upvalue-count 2 :arity 1 :constants ("event-detail" "cek-call" "list" "reset!") :bytecode (20 0 0 16 0 48 1 17 1 18 0 33 15 0 18 0 16 1 52 2 0 1 52 1 0 2 32 2 0 16 1 17 2 20 3 0 18 1 16 2 49 2 50)}) :bytecode (20 0 0 18 0 18 1 51 1 0 0 2 0 3 48 3 17 0 16 0 50)}) :bytecode (20 0 0 51 1 0 1 0 1 1 1 3 1 2 49 1 50)} "resource" {:upvalue-count 0 :arity 1 :constants ("signal" "dict" "loading" "data" "error" "promise-then" "cek-call" {:upvalue-count 1 :arity 1 :constants ("reset!" "dict" "loading" "data" "error") :bytecode (20 0 0 18 0 1 2 0 4 1 3 0 16 0 1 4 0 2 52 1 0 6 49 2 50)} {:upvalue-count 1 :arity 1 :constants ("reset!" "dict" "loading" "data" "error") :bytecode (20 0 0 18 0 1 2 0 4 1 3 0 2 1 4 0 16 0 52 1 0 6 49 2 50)}) :bytecode (20 0 0 1 2 0 3 1 3 0 2 1 4 0 2 52 1 0 6 48 1 17 1 20 5 0 16 0 2 52 6 0 2 51 7 0 1 1 51 8 0 1 1 48 3 5 16 1 50)}) :bytecode (51 1 0 128 0 0 5 51 3 0 128 2 0 5 51 5 0 128 4 0 5 51 7 0 128 6 0 5 51 9 0 128 8 0 5 51 11 0 128 10 0 50))) :constants ({:library (sx dom) :op "import"} {:library (sx browser) :op "import"} "with-marsh-scope" {:upvalue-count 0 :arity 2 :constants ("list" "with-island-scope" {:upvalue-count 1 :arity 1 :constants ("append!") :bytecode (18 0 16 0 52 0 0 2 50)} "dom-set-data" "sx-marsh-disposers") :bytecode (52 0 0 0 17 2 20 1 0 51 2 0 1 2 16 1 48 2 5 20 3 0 16 0 1 4 0 16 2 49 3 50)} "dispose-marsh-scope" {:upvalue-count 0 :arity 1 :constants ("dom-get-data" "sx-marsh-disposers" "for-each" {:upvalue-count 0 :arity 1 :constants ("cek-call") :bytecode (16 0 2 52 0 0 2 50)} "dom-set-data") :bytecode (20 0 0 16 0 1 1 0 48 2 17 1 16 1 33 24 0 51 3 0 16 1 52 2 0 2 5 20 4 0 16 0 1 1 0 2 49 3 32 1 0 2 50)} "emit-event" {:upvalue-count 0 :arity 3 :constants ("dom-dispatch") :bytecode (20 0 0 16 0 16 1 16 2 49 3 50)} "on-event" {:upvalue-count 0 :arity 3 :constants ("dom-on") :bytecode (20 0 0 16 0 16 1 16 2 49 3 50)} "bridge-event" {:upvalue-count 0 :arity 4 :constants ("effect" {:upvalue-count 4 :arity 0 :constants ("dom-on" {:upvalue-count 2 :arity 1 :constants ("event-detail" "cek-call" "list" "reset!") :bytecode (20 0 0 16 0 48 1 17 1 18 0 33 15 0 18 0 16 1 52 2 0 1 52 1 0 2 32 2 0 16 1 17 2 20 3 0 18 1 16 2 49 2 50)}) :bytecode (20 0 0 18 0 18 1 51 1 0 0 2 0 3 48 3 17 0 16 0 50)}) :bytecode (20 0 0 51 1 0 1 0 1 1 1 3 1 2 49 1 50)} "resource" {:upvalue-count 0 :arity 1 :constants ("signal" "dict" "loading" "data" "error" "promise-then" "cek-call" {:upvalue-count 1 :arity 1 :constants ("reset!" "dict" "loading" "data" "error") :bytecode (20 0 0 18 0 1 2 0 4 1 3 0 16 0 1 4 0 2 52 1 0 6 49 2 50)} {:upvalue-count 1 :arity 1 :constants ("reset!" "dict" "loading" "data" "error") :bytecode (20 0 0 18 0 1 2 0 4 1 3 0 2 1 4 0 16 0 52 1 0 6 49 2 50)}) :bytecode (20 0 0 1 2 0 3 1 3 0 2 1 4 0 2 52 1 0 6 48 1 17 1 20 5 0 16 0 2 52 6 0 2 51 7 0 1 1 51 8 0 1 1 48 3 5 16 1 50)} {:library (sx signals-web) :op "import"}) :bytecode (1 0 0 112 5 1 1 0 112 5 51 3 0 128 2 0 5 51 5 0 128 4 0 5 51 7 0 128 6 0 5 51 9 0 128 8 0 5 51 11 0 128 10 0 5 51 13 0 128 12 0 5 1 14 0 112 50)))

View File

@@ -62,7 +62,8 @@
vm-run vm-run
vm-step vm-step
vm-call-closure vm-call-closure
vm-execute-module) vm-execute-module
vm-resume-module)
(begin (begin
(define make-upvalue-cell (fn (value) {:uv-value value})) (define make-upvalue-cell (fn (value) {:uv-value value}))
(define uv-get (fn (cell) (get cell "uv-value"))) (define uv-get (fn (cell) (get cell "uv-value")))
@@ -443,7 +444,11 @@
(if (if
(>= (frame-ip frame) (len bc)) (>= (frame-ip frame) (len bc))
(vm-set-frames! vm (list)) (vm-set-frames! vm (list))
(do (vm-step vm frame rest-frames bc consts) (loop)))))))) (do
(vm-step vm frame rest-frames bc consts)
(when
(nil? (get (vm-globals-ref vm) "__io_request"))
(loop)))))))))
(loop))) (loop)))
(define (define
vm-step vm-step
@@ -595,8 +600,7 @@
(= op 112) (= op 112)
(let (let
((request (vm-pop vm))) ((request (vm-pop vm)))
(error (dict-set! (vm-globals-ref vm) "__io_request" request))
(str "VM: IO suspension (OP_PERFORM) — request: " request)))
:else (error (str "VM: unknown opcode " op)))))) :else (error (str "VM: unknown opcode " op))))))
(define (define
vm-call-closure vm-call-closure
@@ -614,14 +618,29 @@
(fn (fn
(code globals) (code globals)
(let (let
((closure (make-vm-closure code (list) "module" globals nil)) ((vm-code (code-from-value code)) (vm (make-vm globals)))
(vm (make-vm globals)))
(let (let
((frame (make-vm-frame closure 0))) ((closure (make-vm-closure vm-code (list) "module" globals nil))
(pad-n-nils vm (code-locals code)) (frame (make-vm-frame closure 0)))
(pad-n-nils vm (code-locals vm-code))
(vm-set-frames! vm (list frame)) (vm-set-frames! vm (list frame))
(vm-run vm) (vm-run vm)
(vm-pop vm))))))) ;; end define-library (let
((io-req (get (vm-globals-ref vm) "__io_request")))
(if (nil? io-req) (vm-pop vm) {:vm vm :suspended true :op "import" :request io-req}))))))
(define
vm-resume-module
(fn
(suspended-result)
"Resume a suspended VM after IO (import) has been resolved.\n Clears __io_request in globals, pushes nil (import result), re-runs."
(let
((vm (get suspended-result :vm)))
(dict-set! (vm-globals-ref vm) "__io_request" nil)
(vm-push vm nil)
(vm-run vm)
(let
((io-req (get (vm-globals-ref vm) "__io_request")))
(if (nil? io-req) (vm-pop vm) {:vm vm :suspended true :op "import" :request io-req}))))))) ;; end define-library
;; Re-export to global namespace for backward compatibility ;; Re-export to global namespace for backward compatibility
(import (sx vm)) (import (sx vm))

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1792,7 +1792,7 @@
blake2_js_for_wasm_create: blake2_js_for_wasm_create}; blake2_js_for_wasm_create: blake2_js_for_wasm_create};
} }
(globalThis)) (globalThis))
({"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 ({"link":[["runtime-0db9b496",0],["prelude-d7e4b000",0],["stdlib-23ce0836",[]],["sx-7a3621b8",[2]],["jsoo_runtime-f96b44a8",[2]],["js_of_ocaml-651f6707",[2,4]],["dune__exe__Sx_browser-e2e6110d",[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 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_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 Error("caml_exn_with_js_backtrace not implemented")},"caml_int64_create_lo_mi_hi":()=>{throw new

View File

@@ -884,7 +884,7 @@ def _get_shell_static() -> dict[str, Any]:
sx_css=sx_css, sx_css=sx_css,
sx_css_classes=sx_css_classes, sx_css_classes=sx_css_classes,
wasm_hash=_wasm_hash("sx_browser.bc.wasm.js"), wasm_hash=_wasm_hash("sx_browser.bc.wasm.js"),
platform_hash=_wasm_hash("sx-platform-2.js"), platform_hash=_wasm_hash("sx-platform.js"),
sxbc_hash=_sxbc_hash(), sxbc_hash=_sxbc_hash(),
asset_url=_ca.config.get("ASSET_URL", "/static"), asset_url=_ca.config.get("ASSET_URL", "/static"),
inline_css=_shell_cfg.get("inline_css"), inline_css=_shell_cfg.get("inline_css"),
@@ -1035,7 +1035,7 @@ def sx_page_streaming_parts(ctx: dict, page_html: str, *,
pages_sx = _build_pages_sx(current_app.name) pages_sx = _build_pages_sx(current_app.name)
wasm_hash = _wasm_hash("sx_browser.bc.wasm.js") wasm_hash = _wasm_hash("sx_browser.bc.wasm.js")
platform_hash = _wasm_hash("sx-platform-2.js") platform_hash = _wasm_hash("sx-platform.js")
sxbc_hash = _sxbc_hash() sxbc_hash = _sxbc_hash()
# Shell: head + body with server-rendered HTML (not SX mount script) # Shell: head + body with server-rendered HTML (not SX mount script)
@@ -1078,7 +1078,7 @@ def sx_page_streaming_parts(ctx: dict, page_html: str, *,
tail = ( tail = (
_SX_STREAMING_BOOTSTRAP + '\n' _SX_STREAMING_BOOTSTRAP + '\n'
+ f'<script src="{asset_url}/wasm/sx_browser.bc.wasm.js?v={wasm_hash}"></script>\n' + f'<script src="{asset_url}/wasm/sx_browser.bc.wasm.js?v={wasm_hash}"></script>\n'
f'<script src="{asset_url}/wasm/sx-platform-2.js?v={platform_hash}" data-sxbc-hash="{sxbc_hash}"></script>\n' f'<script src="{asset_url}/wasm/sx-platform.js?v={platform_hash}" data-sxbc-hash="{sxbc_hash}"></script>\n'
) )
return shell, tail return shell, tail

View File

@@ -81,5 +81,5 @@
"/wasm/sx_browser.bc.wasm.js?v=" "/wasm/sx_browser.bc.wasm.js?v="
(or wasm-hash "0"))) (or wasm-hash "0")))
(script (script
:src (str asset-url "/wasm/sx-platform-2.js?v=" (or platform-hash "0")) :src (str asset-url "/wasm/sx-platform.js?v=" (or platform-hash "0"))
:data-sxbc-hash (or sxbc-hash "0"))))))) :data-sxbc-hash (or sxbc-hash "0")))))))

View File

@@ -207,3 +207,57 @@
((final (cek-resume state 41))) ((final (cek-resume state 41)))
(assert (cek-terminal? final)) (assert (cek-terminal? final))
(assert= (cek-value final) 42))))) (assert= (cek-value final) 42)))))
(defsuite
"vm-import-suspension"
(deftest
"vm-execute-module runs trivial bytecode"
(let
((globals (dict)) (code (compile-module (quote (42)))))
(assert= (vm-execute-module code globals) 42)))
(deftest
"vm-execute-module converts code-from-value internally"
(let
((globals (dict))
(code (compile-module (quote ((define x 99) x)))))
(assert= (vm-execute-module code globals) 99)))
(deftest
"compile-module handles import form"
(let
((code (compile-module (quote ((import (test lib)))))))
(assert (dict? code))
(assert (not (nil? (get code :bytecode))))))
(deftest
"vm-execute-module returns suspension dict on import"
(let
((globals (dict))
(code (compile-module (quote ((import (test lib)))))))
(let
((result (vm-execute-module code globals)))
(assert (dict? result) "result should be a dict")
(assert= (get result :suspended) true)
(assert= (get result :op) "import"))))
(deftest
"vm-resume-module continues after suspension"
(let
((globals (dict))
(code
(compile-module (quote ((import (test lib)) (define x 42) x)))))
(let
((r1 (vm-execute-module code globals)))
(assert= (get r1 :suspended) true)
(let ((r2 (vm-resume-module r1))) (assert= r2 42)))))
(deftest
"vm multiple sequential imports suspend and resume"
(let
((globals (dict))
(code
(compile-module
(quote ((import (test a)) (import (test b)) (define x 99) x)))))
(let
((r1 (vm-execute-module code globals)))
(assert= (get r1 :suspended) true)
(let
((r2 (vm-resume-module r1)))
(assert= (get r2 :suspended) true)
(let ((r3 (vm-resume-module r2))) (assert= r3 99)))))))

View File

@@ -89,7 +89,7 @@ async function createSxEnv(opts = {}) {
const K = globalThis.SxKernel; const K = globalThis.SxKernel;
// ---- Load platform (registers FFI, loads .sxbc web stack) ---- // ---- Load platform (registers FFI, loads .sxbc web stack) ----
const platformPath = path.join(WASM_DIR, 'sx-platform-2.js'); const platformPath = path.join(WASM_DIR, 'sx-platform.js');
delete require.cache[require.resolve(platformPath)]; delete require.cache[require.resolve(platformPath)];
require(platformPath); require(platformPath);

View File

@@ -1238,8 +1238,8 @@ async function modeEvalAt(browser, url, phase, expr) {
}); });
// Inject a hook that pauses the boot at the desired phase. // Inject a hook that pauses the boot at the desired phase.
// We do this by intercepting sx-platform-2.js and injecting eval calls. // We do this by intercepting sx-platform.js and injecting eval calls.
await page.route('**/*sx-platform-2.js*', async (route) => { await page.route('**/*sx-platform.js*', async (route) => {
const resp = await route.fetch(); const resp = await route.fetch();
let body = await resp.text(); let body = await resp.text();