Fix JIT closure isolation, SX wire format, server diagnostics

Root cause: _env_bind_hook mirrored ALL env_bind calls (including
lambda parameter bindings) to the shared VM globals table. Factory
functions like make-page-fn that return closures capturing different
values for the same param names (default-name, prefix, suffix) would
have the last call's values overwrite all previous closures' captured
state in globals. OP_GLOBAL_GET reads globals first, so all closures
returned the last factory call's values.

Fix: only sync root-env bindings (parent=None) to VM globals. Lambda
parameter bindings stay in their local env, found via vm_closure_env
fallback in OP_GLOBAL_GET.

Also in this commit:
- OP_CLOSURE propagates parent vm_closure_env to child closures
- Remove JIT globals injection (closure vars found via env chain)
- sx_server.ml: SX-Request header → returns text/sx (aser only)
- sx_server.ml: diagnostic endpoint GET /sx/_debug/{env,eval,route}
- sx_server.ml: page helper stubs for deep page rendering
- sx_server.ml: skip client-libs/ dir (browser-only definitions)
- adapter-html.sx: unknown components → HTML comment (not error)
- sx-platform.js: .sxbc fallback loader for bytecode modules
- Delete sx_http.ml (standalone HTTP server, unused)
- Delete stale .sxbc.json files (arity=0 bug, replaced by .sxbc)
- 7 new closure isolation tests in test-closure-isolation.sx
- mcp_tree.ml: emit arity + upvalue-count in .sxbc.json output

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-30 17:28:47 +00:00
parent 408eca1cb0
commit 80931e4972
43 changed files with 1528 additions and 1268 deletions

View File

@@ -228,38 +228,58 @@
}
/**
* Try loading a pre-compiled .sxbc.json bytecode module.
* Try loading a pre-compiled bytecode module.
* Tries .sxbc.json first, then .sxbc (SX s-expression format).
* Returns true on success, null on failure (caller falls back to .sx source).
*/
function loadBytecodeFile(path) {
var bcPath = path.replace(/\.sx$/, '.sxbc.json');
var url = _baseUrl + bcPath + _cacheBust;
// Try .sxbc.json (JSON dict format)
var jsonPath = path.replace(/\.sx$/, '.sxbc.json');
var jsonUrl = _baseUrl + jsonPath + _cacheBust;
try {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, false);
xhr.open("GET", jsonUrl, false);
xhr.send();
if (xhr.status !== 200) return null;
var json = JSON.parse(xhr.responseText);
if (!json.module || json.magic !== 'SXBC') return null;
var module = {
_type: 'dict',
arity: json.module.arity || 0,
bytecode: { _type: 'list', items: json.module.bytecode },
constants: { _type: 'list', items: json.module.constants.map(deserializeConstant) },
};
var result = K.loadModule(module);
if (typeof result === 'string' && result.indexOf('Error') === 0) {
console.warn("[sx-platform] bytecode FAIL " + path + ":", result);
return null;
if (xhr.status === 200) {
var json = JSON.parse(xhr.responseText);
if (json.module && json.magic === 'SXBC') {
var module = {
_type: 'dict',
arity: json.module.arity || 0,
bytecode: { _type: 'list', items: json.module.bytecode },
constants: { _type: 'list', items: json.module.constants.map(deserializeConstant) },
};
var result = K.loadModule(module);
if (typeof result !== 'string' || result.indexOf('Error') !== 0) {
console.log("[sx-platform] ok " + path + " (bytecode-json)");
return true;
}
console.warn("[sx-platform] bytecode-json FAIL " + path + ":", result);
}
}
console.log("[sx-platform] ok " + path + " (bytecode)");
return true;
} catch(e) {
return null;
}
} catch(e) { /* fall through to .sxbc */ }
// Try .sxbc (SX s-expression format, loaded via load-sxbc primitive)
var sxbcPath = path.replace(/\.sx$/, '.sxbc');
var sxbcUrl = _baseUrl + sxbcPath + _cacheBust;
try {
var xhr2 = new XMLHttpRequest();
xhr2.open("GET", sxbcUrl, false);
xhr2.send();
if (xhr2.status === 200) {
// Store text in global, parse via SX to avoid JS string escaping
window.__sxbcText = xhr2.responseText;
var result2 = K.eval('(load-sxbc (first (parse (host-global "__sxbcText"))))');
delete window.__sxbcText;
if (typeof result2 !== 'string' || result2.indexOf('Error') !== 0) {
console.log("[sx-platform] ok " + path + " (bytecode-sx)");
return true;
}
console.warn("[sx-platform] bytecode-sx FAIL " + path + ":", result2);
}
} catch(e) { delete window.__sxbcText; /* fall through to source */ }
return null;
}
/**