VM: fix nested IO suspension frame corruption, island hydration preload

VM frame merging bug: call_closure_reuse now saves caller continuations
on a reuse_stack instead of merging frames. resume_vm restores them in
innermost-first order. Fixes frame count corruption when nested closures
suspend via OP_PERFORM. Zero test regressions (3924/3924).

Island hydration: hydrate-island now looks up components from (global-env)
instead of render-env, triggering the symbol resolve hook. Added JS-level
preload-island-defs that scans DOM for data-sx-island and loads definitions
from the content-addressed manifest BEFORE hydration — avoids K.load
reentrancy when the resolve hook fires inside env_get.

loadDefinitionByHash: fixed isMultiDefine check — defcomp/defisland bodies
containing nested (define ...) forms no longer suppress name insertion.
Added K.load return value checking for silent error string returns.

sx_browser.ml: resolve hook falls back to global_env.bindings when
_vm_globals miss (sync gap). Snapshot reuse_stack alongside pending_cek.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-16 13:23:35 +00:00
parent 684a46297d
commit c9634ba649
5 changed files with 93 additions and 16 deletions

View File

@@ -846,7 +846,11 @@
// already contain named (define name ...) forms.
var name = _hashToName[hash];
if (name) {
var isMultiDefine = /\(define\s+[a-zA-Z]/.test(rewritten);
// Check if this is a multi-define file (client lib with top-level defines).
// Only top-level (define ...) forms count — nested ones inside defisland/defcomp
// bodies should NOT suppress name insertion.
var startsWithDef = /^\((defcomp|defisland|defmacro)\s/.test(rewritten);
var isMultiDefine = !startsWithDef && /\(define\s+[a-zA-Z]/.test(rewritten);
if (!isMultiDefine) {
rewritten = rewritten.replace(
/^\((defcomp|defisland|defmacro|define)\s/,
@@ -856,7 +860,12 @@
}
try {
K.load(rewritten);
var loadRv = K.load(rewritten);
if (typeof loadRv === "string" && loadRv.indexOf("Error") >= 0) {
console.warn("[sx] K.load error for", (_hashToName[hash] || hash) + ":", loadRv);
delete _loadedHashes[hash];
return false;
}
_loadedHashes[hash] = true;
return true;
} catch(e) {
@@ -865,11 +874,28 @@
}
}
// Eagerly pre-load island definitions from the manifest.
// Called from boot.sx before hydration. Scans the DOM for data-sx-island
// attributes and loads definitions via the content-addressed manifest.
// Unlike __resolve-symbol (called from inside env_get), this runs at the
// top level so K.load can register bindings without reentrancy issues.
K.registerNative("preload-island-defs", function() {
var manifest = loadPageManifest();
if (!manifest || !manifest.defs) return null;
var els = document.querySelectorAll('[data-sx-island]');
for (var i = 0; i < els.length; i++) {
var name = "~" + els[i].getAttribute("data-sx-island");
if (manifest.defs[name] && !_loadedHashes[manifest.defs[name]]) {
loadDefinitionByHash(manifest.defs[name]);
}
}
return null;
});
// Register the resolve hook — called by the VM when GLOBAL_GET fails
K.registerNative("__resolve-symbol", function(args) {
var name = args[0];
if (!name) return null;
// Content-addressed resolution — components, libraries, macros
var manifest = loadPageManifest();
if (manifest && manifest.defs && manifest.defs[name]) {
@@ -918,6 +944,20 @@
K.eval("(process-sx-scripts nil)");
console.log("[sx] sx-hydrate-elements...");
K.eval("(sx-hydrate-elements nil)");
// Pre-load island definitions from manifest before hydration.
// Must happen at JS level (not from inside SX eval) to avoid
// K.load reentrancy issues with the symbol resolve hook.
var manifest = loadPageManifest();
if (manifest && manifest.defs) {
var islandEls = document.querySelectorAll("[data-sx-island]");
for (var ii = 0; ii < islandEls.length; ii++) {
var iname = "~" + islandEls[ii].getAttribute("data-sx-island");
var ihash = manifest.defs[iname];
if (ihash && !_loadedHashes[ihash]) {
loadDefinitionByHash(ihash);
}
}
}
console.log("[sx] sx-hydrate-islands...");
K.eval("(sx-hydrate-islands nil)");
console.log("[sx] process-elements...");