JIT allowlist + integration tests + --test mode + clean up debug logging

JIT allowlist (sx_server.ml):
- Replace try-every-lambda strategy with StringSet allowlist. Only
  functions in the list get JIT compiled (compiler, parser, pure transforms).
  Render functions that need dynamic scope skip JIT entirely — no retry
  overhead, no silent fallbacks.
- Add (jit-allow name) command for dynamic expansion from Python bridge.
- JIT failures log once with "[jit] DISABLED fn — reason" then go silent.

Standalone --test mode (sx_server.ml):
- New --test flag loads full env (spec + adapters + compiler + signals),
  supports --eval and --load flags. Quick kernel testing without Docker.
  Example: dune exec bin/sx_server.exe -- --test --eval '(len HTML_TAGS)'

Integration tests (integration_tests.ml):
- New binary exercising the full rendering pipeline: loads spec + adapters
  into a server-like env, renders HTML via both native and SX adapter paths.
- 26 tests: HTML tags, special forms (when/if/let), letrec with side
  effects, component rendering, eval-expr with HTML tag functions.
- Would have caught the "Undefined symbol: div/lake/init" issues from
  the previous commit immediately without Docker.

VM cleanup (sx_vm.ml):
- Remove temporary debug logging (insn counter, call_closure counter,
  VmClosure depth tracking) added during debugging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-23 23:58:40 +00:00
parent dd057247a5
commit 5270d2e956
8 changed files with 573 additions and 77 deletions

View File

@@ -14,7 +14,7 @@
// =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-03-23T23:36:04Z";
var SX_VERSION = "2026-03-23T23:55:49Z";
function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -512,7 +512,12 @@
PRIMITIVES["emitted"] = sxEmitted;
// Aliases for aser adapter (avoids CEK special form conflict on server)
var scopeEmit = sxEmit;
var scopePeek = sxEmitted;
function scopePeek(name) {
if (_scopeStacks[name] && _scopeStacks[name].length) {
return _scopeStacks[name][_scopeStacks[name].length - 1].value;
}
return NIL;
}
PRIMITIVES["scope-emit!"] = scopeEmit;
PRIMITIVES["scope-peek"] = scopePeek;
@@ -1589,13 +1594,7 @@ PRIMITIVES["step-sf-lambda"] = stepSfLambda;
var val = NIL;
var body = NIL;
(isSxTruthy((isSxTruthy((len(restArgs) >= 2)) && isSxTruthy((typeOf(first(restArgs)) == "keyword")) && (keywordName(first(restArgs)) == "value"))) ? ((val = trampoline(evalExpr(nth(restArgs, 1), env))), (body = slice(restArgs, 2))) : (body = restArgs));
scopePush(name, val);
return (function() {
var result = NIL;
{ var _c = body; for (var _i = 0; _i < _c.length; _i++) { var expr = _c[_i]; result = trampoline(evalExpr(expr, env)); } }
scopePop(name);
return makeCekValue(result, env, kont);
})();
return (isSxTruthy(isEmpty(body)) ? makeCekValue(NIL, env, kont) : makeCekState(first(body), env, kontPush(makeScopeAccFrame(name, val, rest(body), env), kont)));
})(); };
PRIMITIVES["step-sf-scope"] = stepSfScope;
@@ -1604,13 +1603,7 @@ PRIMITIVES["step-sf-scope"] = stepSfScope;
var name = trampoline(evalExpr(first(args), env));
var val = trampoline(evalExpr(nth(args, 1), env));
var body = slice(args, 2);
scopePush(name, val);
return (function() {
var result = NIL;
{ var _c = body; for (var _i = 0; _i < _c.length; _i++) { var expr = _c[_i]; result = trampoline(evalExpr(expr, env)); } }
scopePop(name);
return makeCekValue(result, env, kont);
})();
return (isSxTruthy(isEmpty(body)) ? makeCekValue(NIL, env, kont) : makeCekState(first(body), env, kontPush(makeProvideFrame(name, val, rest(body), env), kont)));
})(); };
PRIMITIVES["step-sf-provide"] = stepSfProvide;
@@ -1618,8 +1611,8 @@ PRIMITIVES["step-sf-provide"] = stepSfProvide;
var stepSfContext = function(args, env, kont) { return (function() {
var name = trampoline(evalExpr(first(args), env));
var defaultVal = (isSxTruthy((len(args) >= 2)) ? trampoline(evalExpr(nth(args, 1), env)) : NIL);
var val = scopePeek(name);
return makeCekValue((isSxTruthy(isNil(val)) ? defaultVal : val), env, kont);
var frame = kontFindProvide(kont, name);
return makeCekValue((isSxTruthy(isNil(frame)) ? defaultVal : get(frame, "value")), env, kont);
})(); };
PRIMITIVES["step-sf-context"] = stepSfContext;
@@ -1627,7 +1620,10 @@ PRIMITIVES["step-sf-context"] = stepSfContext;
var stepSfEmit = function(args, env, kont) { return (function() {
var name = trampoline(evalExpr(first(args), env));
var val = trampoline(evalExpr(nth(args, 1), env));
scopeEmit(name, val);
var frame = kontFindScopeAcc(kont, name);
if (isSxTruthy(frame)) {
frame["emitted"] = append(get(frame, "emitted"), [val]);
}
return makeCekValue(NIL, env, kont);
})(); };
PRIMITIVES["step-sf-emit"] = stepSfEmit;
@@ -1635,8 +1631,8 @@ PRIMITIVES["step-sf-emit"] = stepSfEmit;
// step-sf-emitted
var stepSfEmitted = function(args, env, kont) { return (function() {
var name = trampoline(evalExpr(first(args), env));
var val = scopePeek(name);
return makeCekValue((isSxTruthy(isNil(val)) ? [] : val), env, kont);
var frame = kontFindScopeAcc(kont, name);
return makeCekValue((isSxTruthy(isNil(frame)) ? [] : get(frame, "emitted")), env, kont);
})(); };
PRIMITIVES["step-sf-emitted"] = stepSfEmitted;