Phase 4: Eliminate nested CEK from HO form handlers

Higher-order forms (map, filter, reduce, some, every?, for-each,
map-indexed) now evaluate their arguments via CEK frames instead
of nested trampoline(eval-expr(...)) calls.

Added HoSetupFrame — staged evaluation of HO form arguments.
When all args are evaluated, ho-setup-dispatch sets up the
iteration frame. This keeps a single linear CEK continuation
chain instead of spawning nested CEK instances.

14 nested eval-expr calls eliminated (39 → 25 remaining).
The remaining 25 are in delegate functions (sf-letrec, sf-scope,
parse-keyword-args, qq-expand, etc.) called infrequently.

All tests unchanged: JS 747/747, Full 864/870, Python 679/679.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 14:10:33 +00:00
parent e475222099
commit c6a662c980
5 changed files with 603 additions and 81 deletions

View File

@@ -14,7 +14,7 @@
// =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-03-15T13:41:19Z";
var SX_VERSION = "2026-03-15T14:09:57Z";
function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -941,6 +941,10 @@ PRIMITIVES["make-reactive-reset-frame"] = makeReactiveResetFrame;
var makeDerefFrame = function(env) { return {"type": "deref", "env": env}; };
PRIMITIVES["make-deref-frame"] = makeDerefFrame;
// make-ho-setup-frame
var makeHoSetupFrame = function(hoType, remainingArgs, evaledArgs, env) { return {"type": "ho-setup", "ho-type": hoType, "remaining": remainingArgs, "evaled": evaledArgs, "env": env}; };
PRIMITIVES["make-ho-setup-frame"] = makeHoSetupFrame;
// frame-type
var frameType = function(f) { return get(f, "type"); };
PRIMITIVES["frame-type"] = frameType;
@@ -1564,61 +1568,61 @@ PRIMITIVES["reactive-shift-deref"] = reactiveShiftDeref;
})(); };
PRIMITIVES["step-eval-call"] = stepEvalCall;
// step-ho-map
var stepHoMap = function(args, env, kont) { return (function() {
var f = trampoline(evalExpr(first(args), env));
var coll = trampoline(evalExpr(nth(args, 1), env));
// ho-setup-dispatch
var hoSetupDispatch = function(hoType, evaled, env, kont) { return (function() {
var f = first(evaled);
return (isSxTruthy((hoType == "map")) ? (function() {
var coll = nth(evaled, 1);
return (isSxTruthy(isEmpty(coll)) ? makeCekValue([], env, kont) : continueWithCall(f, [first(coll)], env, [], kontPush(makeMapFrame(f, rest(coll), [], env), kont)));
})() : (isSxTruthy((hoType == "map-indexed")) ? (function() {
var coll = nth(evaled, 1);
return (isSxTruthy(isEmpty(coll)) ? makeCekValue([], env, kont) : continueWithCall(f, [0, first(coll)], env, [], kontPush(makeMapIndexedFrame(f, rest(coll), [], env), kont)));
})() : (isSxTruthy((hoType == "filter")) ? (function() {
var coll = nth(evaled, 1);
return (isSxTruthy(isEmpty(coll)) ? makeCekValue([], env, kont) : continueWithCall(f, [first(coll)], env, [], kontPush(makeFilterFrame(f, rest(coll), [], first(coll), env), kont)));
})() : (isSxTruthy((hoType == "reduce")) ? (function() {
var init = nth(evaled, 1);
var coll = nth(evaled, 2);
return (isSxTruthy(isEmpty(coll)) ? makeCekValue(init, env, kont) : continueWithCall(f, [init, first(coll)], env, [], kontPush(makeReduceFrame(f, rest(coll), env), kont)));
})() : (isSxTruthy((hoType == "some")) ? (function() {
var coll = nth(evaled, 1);
return (isSxTruthy(isEmpty(coll)) ? makeCekValue(false, env, kont) : continueWithCall(f, [first(coll)], env, [], kontPush(makeSomeFrame(f, rest(coll), env), kont)));
})() : (isSxTruthy((hoType == "every")) ? (function() {
var coll = nth(evaled, 1);
return (isSxTruthy(isEmpty(coll)) ? makeCekValue(true, env, kont) : continueWithCall(f, [first(coll)], env, [], kontPush(makeEveryFrame(f, rest(coll), env), kont)));
})() : (isSxTruthy((hoType == "for-each")) ? (function() {
var coll = nth(evaled, 1);
return (isSxTruthy(isEmpty(coll)) ? makeCekValue(NIL, env, kont) : continueWithCall(f, [first(coll)], env, [], kontPush(makeForEachFrame(f, rest(coll), env), kont)));
})() : error((String("Unknown HO type: ") + String(hoType))))))))));
})(); };
PRIMITIVES["ho-setup-dispatch"] = hoSetupDispatch;
// step-ho-map
var stepHoMap = function(args, env, kont) { return makeCekState(first(args), env, kontPush(makeHoSetupFrame("map", rest(args), [], env), kont)); };
PRIMITIVES["step-ho-map"] = stepHoMap;
// step-ho-map-indexed
var stepHoMapIndexed = function(args, env, kont) { return (function() {
var f = trampoline(evalExpr(first(args), env));
var coll = trampoline(evalExpr(nth(args, 1), env));
return (isSxTruthy(isEmpty(coll)) ? makeCekValue([], env, kont) : continueWithCall(f, [0, first(coll)], env, [], kontPush(makeMapIndexedFrame(f, rest(coll), [], env), kont)));
})(); };
var stepHoMapIndexed = function(args, env, kont) { return makeCekState(first(args), env, kontPush(makeHoSetupFrame("map-indexed", rest(args), [], env), kont)); };
PRIMITIVES["step-ho-map-indexed"] = stepHoMapIndexed;
// step-ho-filter
var stepHoFilter = function(args, env, kont) { return (function() {
var f = trampoline(evalExpr(first(args), env));
var coll = trampoline(evalExpr(nth(args, 1), env));
return (isSxTruthy(isEmpty(coll)) ? makeCekValue([], env, kont) : continueWithCall(f, [first(coll)], env, [], kontPush(makeFilterFrame(f, rest(coll), [], first(coll), env), kont)));
})(); };
var stepHoFilter = function(args, env, kont) { return makeCekState(first(args), env, kontPush(makeHoSetupFrame("filter", rest(args), [], env), kont)); };
PRIMITIVES["step-ho-filter"] = stepHoFilter;
// step-ho-reduce
var stepHoReduce = function(args, env, kont) { return (function() {
var f = trampoline(evalExpr(first(args), env));
var init = trampoline(evalExpr(nth(args, 1), env));
var coll = trampoline(evalExpr(nth(args, 2), env));
return (isSxTruthy(isEmpty(coll)) ? makeCekValue(init, env, kont) : continueWithCall(f, [init, first(coll)], env, [], kontPush(makeReduceFrame(f, rest(coll), env), kont)));
})(); };
var stepHoReduce = function(args, env, kont) { return makeCekState(first(args), env, kontPush(makeHoSetupFrame("reduce", rest(args), [], env), kont)); };
PRIMITIVES["step-ho-reduce"] = stepHoReduce;
// step-ho-some
var stepHoSome = function(args, env, kont) { return (function() {
var f = trampoline(evalExpr(first(args), env));
var coll = trampoline(evalExpr(nth(args, 1), env));
return (isSxTruthy(isEmpty(coll)) ? makeCekValue(false, env, kont) : continueWithCall(f, [first(coll)], env, [], kontPush(makeSomeFrame(f, rest(coll), env), kont)));
})(); };
var stepHoSome = function(args, env, kont) { return makeCekState(first(args), env, kontPush(makeHoSetupFrame("some", rest(args), [], env), kont)); };
PRIMITIVES["step-ho-some"] = stepHoSome;
// step-ho-every
var stepHoEvery = function(args, env, kont) { return (function() {
var f = trampoline(evalExpr(first(args), env));
var coll = trampoline(evalExpr(nth(args, 1), env));
return (isSxTruthy(isEmpty(coll)) ? makeCekValue(true, env, kont) : continueWithCall(f, [first(coll)], env, [], kontPush(makeEveryFrame(f, rest(coll), env), kont)));
})(); };
var stepHoEvery = function(args, env, kont) { return makeCekState(first(args), env, kontPush(makeHoSetupFrame("every", rest(args), [], env), kont)); };
PRIMITIVES["step-ho-every"] = stepHoEvery;
// step-ho-for-each
var stepHoForEach = function(args, env, kont) { return (function() {
var f = trampoline(evalExpr(first(args), env));
var coll = trampoline(evalExpr(nth(args, 1), env));
return (isSxTruthy(isEmpty(coll)) ? makeCekValue(NIL, env, kont) : continueWithCall(f, [first(coll)], env, [], kontPush(makeForEachFrame(f, rest(coll), env), kont)));
})(); };
var stepHoForEach = function(args, env, kont) { return makeCekState(first(args), env, kontPush(makeHoSetupFrame("for-each", rest(args), [], env), kont)); };
PRIMITIVES["step-ho-for-each"] = stepHoForEach;
// step-continue
@@ -1747,6 +1751,12 @@ PRIMITIVES["step-ho-for-each"] = stepHoForEach;
return makeCekState(nth(nextEntry, 1), fenv, kontPush(makeDictFrame(rest(remaining), append(completed, [[first(nextEntry)]]), fenv), restK));
})());
})();
})() : (isSxTruthy((ft == "ho-setup")) ? (function() {
var hoType = get(frame, "ho-type");
var remaining = get(frame, "remaining");
var evaled = append(get(frame, "evaled"), [value]);
var fenv = get(frame, "env");
return (isSxTruthy(isEmpty(remaining)) ? hoSetupDispatch(hoType, evaled, fenv, restK) : makeCekState(first(remaining), fenv, kontPush(makeHoSetupFrame(hoType, rest(remaining), evaled, fenv), restK)));
})() : (isSxTruthy((ft == "reset")) ? makeCekValue(value, env, restK) : (isSxTruthy((ft == "deref")) ? (function() {
var val = value;
var fenv = get(frame, "env");
@@ -1814,7 +1824,7 @@ PRIMITIVES["step-ho-for-each"] = stepHoForEach;
var remaining = get(frame, "remaining");
var fenv = get(frame, "env");
return (isSxTruthy(!isSxTruthy(value)) ? makeCekValue(false, fenv, restK) : (isSxTruthy(isEmpty(remaining)) ? makeCekValue(true, fenv, restK) : continueWithCall(f, [first(remaining)], fenv, [], kontPush(makeEveryFrame(f, rest(remaining), fenv), restK))));
})() : error((String("Unknown frame type: ") + String(ft))))))))))))))))))))))))));
})() : error((String("Unknown frame type: ") + String(ft)))))))))))))))))))))))))));
})());
})(); };
PRIMITIVES["step-continue"] = stepContinue;

View File

@@ -197,6 +197,14 @@
(fn (env)
{:type "deref" :env env}))
;; HoSetupFrame: staged evaluation of higher-order form arguments
;; ho-type is "map", "filter", "reduce", etc.
;; Evaluates args one at a time, then dispatches to the iteration frame.
(define make-ho-setup-frame
(fn (ho-type remaining-args evaled-args env)
{:type "ho-setup" :ho-type ho-type :remaining remaining-args
:evaled evaled-args :env env}))
;; --------------------------------------------------------------------------
;; 3. Frame accessors
@@ -1571,69 +1579,101 @@
;; Function and collection args are evaluated via tree-walk (simple exprs),
;; then the loop is driven by CEK frames.
;; HO step handlers — push HoSetupFrame to evaluate args via CEK
;; (no nested eval-expr calls). When all args are evaluated, the
;; HoSetupFrame dispatch in step-continue sets up the iteration frame.
;; ho-setup-dispatch: all HO args evaluated, set up iteration
(define ho-setup-dispatch
(fn (ho-type evaled env kont)
(let ((f (first evaled)))
(cond
(= ho-type "map")
(let ((coll (nth evaled 1)))
(if (empty? coll)
(make-cek-value (list) env kont)
(continue-with-call f (list (first coll)) env (list)
(kont-push (make-map-frame f (rest coll) (list) env) kont))))
(= ho-type "map-indexed")
(let ((coll (nth evaled 1)))
(if (empty? coll)
(make-cek-value (list) env kont)
(continue-with-call f (list 0 (first coll)) env (list)
(kont-push (make-map-indexed-frame f (rest coll) (list) env) kont))))
(= ho-type "filter")
(let ((coll (nth evaled 1)))
(if (empty? coll)
(make-cek-value (list) env kont)
(continue-with-call f (list (first coll)) env (list)
(kont-push (make-filter-frame f (rest coll) (list) (first coll) env) kont))))
(= ho-type "reduce")
(let ((init (nth evaled 1))
(coll (nth evaled 2)))
(if (empty? coll)
(make-cek-value init env kont)
(continue-with-call f (list init (first coll)) env (list)
(kont-push (make-reduce-frame f (rest coll) env) kont))))
(= ho-type "some")
(let ((coll (nth evaled 1)))
(if (empty? coll)
(make-cek-value false env kont)
(continue-with-call f (list (first coll)) env (list)
(kont-push (make-some-frame f (rest coll) env) kont))))
(= ho-type "every")
(let ((coll (nth evaled 1)))
(if (empty? coll)
(make-cek-value true env kont)
(continue-with-call f (list (first coll)) env (list)
(kont-push (make-every-frame f (rest coll) env) kont))))
(= ho-type "for-each")
(let ((coll (nth evaled 1)))
(if (empty? coll)
(make-cek-value nil env kont)
(continue-with-call f (list (first coll)) env (list)
(kont-push (make-for-each-frame f (rest coll) env) kont))))
:else (error (str "Unknown HO type: " ho-type))))))
(define step-ho-map
(fn (args env kont)
(let ((f (trampoline (eval-expr (first args) env)))
(coll (trampoline (eval-expr (nth args 1) env))))
(if (empty? coll)
(make-cek-value (list) env kont)
(continue-with-call f (list (first coll)) env (list)
(kont-push (make-map-frame f (rest coll) (list) env) kont))))))
(make-cek-state (first args) env
(kont-push (make-ho-setup-frame "map" (rest args) (list) env) kont))))
(define step-ho-map-indexed
(fn (args env kont)
(let ((f (trampoline (eval-expr (first args) env)))
(coll (trampoline (eval-expr (nth args 1) env))))
(if (empty? coll)
(make-cek-value (list) env kont)
(continue-with-call f (list 0 (first coll)) env (list)
(kont-push (make-map-indexed-frame f (rest coll) (list) env) kont))))))
(make-cek-state (first args) env
(kont-push (make-ho-setup-frame "map-indexed" (rest args) (list) env) kont))))
(define step-ho-filter
(fn (args env kont)
(let ((f (trampoline (eval-expr (first args) env)))
(coll (trampoline (eval-expr (nth args 1) env))))
(if (empty? coll)
(make-cek-value (list) env kont)
(continue-with-call f (list (first coll)) env (list)
(kont-push (make-filter-frame f (rest coll) (list) (first coll) env) kont))))))
(make-cek-state (first args) env
(kont-push (make-ho-setup-frame "filter" (rest args) (list) env) kont))))
(define step-ho-reduce
(fn (args env kont)
(let ((f (trampoline (eval-expr (first args) env)))
(init (trampoline (eval-expr (nth args 1) env)))
(coll (trampoline (eval-expr (nth args 2) env))))
(if (empty? coll)
(make-cek-value init env kont)
(continue-with-call f (list init (first coll)) env (list)
(kont-push (make-reduce-frame f (rest coll) env) kont))))))
(make-cek-state (first args) env
(kont-push (make-ho-setup-frame "reduce" (rest args) (list) env) kont))))
(define step-ho-some
(fn (args env kont)
(let ((f (trampoline (eval-expr (first args) env)))
(coll (trampoline (eval-expr (nth args 1) env))))
(if (empty? coll)
(make-cek-value false env kont)
(continue-with-call f (list (first coll)) env (list)
(kont-push (make-some-frame f (rest coll) env) kont))))))
(make-cek-state (first args) env
(kont-push (make-ho-setup-frame "some" (rest args) (list) env) kont))))
(define step-ho-every
(fn (args env kont)
(let ((f (trampoline (eval-expr (first args) env)))
(coll (trampoline (eval-expr (nth args 1) env))))
(if (empty? coll)
(make-cek-value true env kont)
(continue-with-call f (list (first coll)) env (list)
(kont-push (make-every-frame f (rest coll) env) kont))))))
(make-cek-state (first args) env
(kont-push (make-ho-setup-frame "every" (rest args) (list) env) kont))))
(define step-ho-for-each
(fn (args env kont)
(let ((f (trampoline (eval-expr (first args) env)))
(coll (trampoline (eval-expr (nth args 1) env))))
(if (empty? coll)
(make-cek-value nil env kont)
(continue-with-call f (list (first coll)) env (list)
(kont-push (make-for-each-frame f (rest coll) env) kont))))))
(make-cek-state (first args) env
(kont-push (make-ho-setup-frame "for-each" (rest args) (list) env) kont))))
;; --------------------------------------------------------------------------
@@ -1908,6 +1948,22 @@
fenv)
rest-k))))))
;; --- HoSetupFrame: evaluating HO form arguments ---
(= ft "ho-setup")
(let ((ho-type (get frame "ho-type"))
(remaining (get frame "remaining"))
(evaled (append (get frame "evaled") (list value)))
(fenv (get frame "env")))
(if (empty? remaining)
;; All args evaluated — dispatch to iteration
(ho-setup-dispatch ho-type evaled fenv rest-k)
;; More args to evaluate
(make-cek-state
(first remaining) fenv
(kont-push
(make-ho-setup-frame ho-type (rest remaining) evaled fenv)
rest-k))))
;; --- ResetFrame: body evaluated normally (no shift) ---
(= ft "reset")
(make-cek-value value env rest-k)

View File

@@ -246,7 +246,11 @@
(dict :label "Reactive Runtime" :href "/sx/(etc.(plan.reactive-runtime))"
:summary "Seven feature layers — ref, foreign FFI, state machines, commands with undo/redo, render loops, keyed lists, client-first app shell. Zero new platform primitives.")
(dict :label "Rust/WASM Host" :href "/sx/(etc.(plan.rust-wasm-host))"
:summary "Bootstrap the SX spec to Rust, compile to WASM, replace sx-browser.js. Shared platform layer for DOM, phased rollout from parse to full parity.")))
:summary "Bootstrap the SX spec to Rust, compile to WASM, replace sx-browser.js. Shared platform layer for DOM, phased rollout from parse to full parity.")
(dict :label "Isolated Evaluator" :href "/sx/(etc.(plan.isolated-evaluator))"
:summary "Core/application split, shared sx-platform.js, isolated JS evaluator, Rust WASM via handle table. Only language-defining spec gets bootstrapped; everything else is runtime-evaluated .sx.")
(dict :label "Mother Language" :href "/sx/(etc.(plan.mother-language))"
:summary "SX as its own compiler. OCaml as substrate (closest to CEK), Koka as alternative (compile-time linearity), ultimately self-hosting. One language, every target.")))
(define reactive-islands-nav-items (list
(dict :label "Overview" :href "/sx/(geography.(reactive))"

View File

@@ -0,0 +1,450 @@
;; ---------------------------------------------------------------------------
;; Mother Language — SX as its own compiler, OCaml as the substrate
;; ---------------------------------------------------------------------------
(defcomp ~plans/mother-language/plan-mother-language-content ()
(~docs/page :title "Mother Language"
(p :class "text-stone-500 text-sm italic mb-8"
"The ideal language for evaluating the SX core spec is SX itself. "
"The path: OCaml as the initial substrate (closest existing language to what CEK is), "
"Koka as an alternative (compile-time linearity), ultimately a self-hosting SX compiler "
"that emits machine code directly from the spec. One language. Every target.")
;; -----------------------------------------------------------------------
;; The argument
;; -----------------------------------------------------------------------
(~docs/section :title "The Argument" :id "argument"
(h4 :class "font-semibold mt-4 mb-2" "What the evaluator actually does")
(p "The CEK machine is a " (code "state \u2192 state") " loop over sum types. "
"Each step pattern-matches on the Control register, consults the Environment, "
"and transforms the Kontinuation. Every SX expression, every component render, "
"every signal update goes through this loop. It's the hot path.")
(p "The ideal host language is one where this loop compiles to a tight jump table "
"with minimal allocation. That means: algebraic types, pattern matching, "
"persistent data structures, and a native effect system.")
(h4 :class "font-semibold mt-6 mb-2" "Why multiple hosts is the wrong goal")
(p "The current architecture bootstraps the spec to Python, JavaScript, and Rust. "
"Each host has impedance mismatches:")
(ul :class "list-disc list-inside space-y-1 mt-2"
(li (strong "Python") " \u2014 slow. Tree-walk overhead is 100\u20131000x vs native. "
"The async adapter is complex because Python's async model is cooperative, not effect-based.")
(li (strong "JavaScript") " \u2014 no sum types, prototype-based dispatch, GC pauses unpredictable. "
"The bootstrapper works around JS limitations rather than mapping naturally.")
(li (strong "Rust") " \u2014 ownership fights shared immutable trees. Every " (code "Rc<Vec<Value>>")
" is overhead. Closures capturing environments need " (code "Arc") " gymnastics."))
(p "Each host makes the evaluator work, but none make it " (em "natural") ". "
"The translation is structure-" (em "creating") ", not structure-" (em "preserving") ".")
(h4 :class "font-semibold mt-6 mb-2" "The Mother Language is SX")
(p "The spec defines the semantics. The CEK machine is the most explicit form of those semantics. "
"The ideal \"language\" is one that maps 1:1 onto CEK transitions and compiles them to "
"optimal machine code. That language is SX itself \u2014 compiled, not interpreted.")
(p "The hosts (Python, JS, Haskell, Rust) become " (em "platform layers") " \u2014 "
"they provide IO, DOM, database, GPU access. The evaluator itself is always the same "
"compiled SX core, embedded as a native library or WASM module."))
;; -----------------------------------------------------------------------
;; Why OCaml
;; -----------------------------------------------------------------------
(~docs/section :title "Why OCaml" :id "ocaml"
(p "OCaml is the closest existing language to what the CEK machine is. "
"The translation from " (code "cek.sx") " to OCaml is nearly mechanical.")
(h4 :class "font-semibold mt-4 mb-2" "Natural mapping")
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
(table :class "w-full text-left text-sm"
(thead (tr :class "border-b border-stone-200 bg-stone-100"
(th :class "px-3 py-2 font-medium text-stone-600" "SX concept")
(th :class "px-3 py-2 font-medium text-stone-600" "OCaml primitive")
(th :class "px-3 py-2 font-medium text-stone-600" "Notes")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Value (Nil | Num | Str | List | ...)")
(td :class "px-3 py-2 text-stone-700" "Algebraic variant type")
(td :class "px-3 py-2 text-stone-600" "Direct mapping, no boxing"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Frame (IfFrame | ArgFrame | MapFrame | ...)")
(td :class "px-3 py-2 text-stone-700" "Algebraic variant type")
(td :class "px-3 py-2 text-stone-600" "20+ variants, pattern match dispatch"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Environment (persistent map)")
(td :class "px-3 py-2 text-stone-700" (code "Map.S"))
(td :class "px-3 py-2 text-stone-600" "Built-in balanced tree, structural sharing"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Continuation (list of frames)")
(td :class "px-3 py-2 text-stone-700" "Immutable list")
(td :class "px-3 py-2 text-stone-600" "cons/match, O(1) push/pop"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "cek-step (pattern match on C)")
(td :class "px-3 py-2 text-stone-700" (code "match") " expression")
(td :class "px-3 py-2 text-stone-600" "Compiles to jump table"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "shift/reset")
(td :class "px-3 py-2 text-stone-700" (code "perform") " / " (code "continue"))
(td :class "px-3 py-2 text-stone-600" "Native in OCaml 5"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Concurrent CEK (fibers)")
(td :class "px-3 py-2 text-stone-700" "Domains + effect handlers")
(td :class "px-3 py-2 text-stone-600" "One fiber per CEK machine"))
(tr
(td :class "px-3 py-2 text-stone-700" "Linear continuations")
(td :class "px-3 py-2 text-stone-700" "One-shot continuations (default)")
(td :class "px-3 py-2 text-stone-600" "Runtime-enforced, not compile-time")))))
(h4 :class "font-semibold mt-4 mb-2" "Compilation targets")
(ul :class "list-disc list-inside space-y-1 mt-2"
(li (strong "Native") " \u2014 OCaml's native compiler produces fast binaries, small footprint. "
"Embed in Python via C ABI (ctypes/cffi). Embed in Node via N-API.")
(li (strong "WASM") " \u2014 " (code "wasm_of_ocaml") " is mature (used by Facebook's Flow/Reason). "
"Produces compact " (code ".wasm") " modules. Loaded via " (code "sx-platform.js") " like the Rust WASM plan.")
(li (strong "JavaScript") " \u2014 " (code "js_of_ocaml") " for legacy browser targets. "
"Falls back to JS when WASM isn't available."))
(h4 :class "font-semibold mt-4 mb-2" "What OCaml replaces")
(p "The Haskell and Rust evaluator implementations become unnecessary. "
"OCaml covers both server (native) and client (WASM) from one codebase. "
"The sx-haskell and sx-rust work proved the spec is host-independent \u2014 "
"OCaml is the convergence point.")
(p "Python and JavaScript evaluators remain as " (em "platform layers") " \u2014 "
"they provide IO primitives, not evaluation logic. The Python web framework calls "
"the OCaml evaluator via FFI for rendering. The browser loads the WASM evaluator "
"and connects it to " (code "sx-platform.js") " for DOM access."))
;; -----------------------------------------------------------------------
;; Koka as alternative
;; -----------------------------------------------------------------------
(~docs/section :title "Koka as Alternative" :id "koka"
(p "Koka (Daan Leijen, MSR) addresses OCaml's one weakness: "
(strong "compile-time linearity") ".")
(h4 :class "font-semibold mt-4 mb-2" "Where Koka wins")
(ul :class "list-disc list-inside space-y-2 mt-2"
(li (strong "Perceus reference counting") " \u2014 the compiler tracks which values are used linearly. "
"Linear values are mutated in-place (zero allocation). "
"Non-linear values use reference counting (no GC at all).")
(li (strong "Effect types") " \u2014 effects are tracked in the type system. "
"A function's type says exactly which effects it can perform. "
"The type checker enforces that handlers exist for every effect.")
(li (strong "One-shot continuations by default") " \u2014 like OCaml 5, but " (em "statically enforced") ". "
"The type system prevents invoking a linear continuation twice. "
"No runtime check needed."))
(h4 :class "font-semibold mt-4 mb-2" "Where Koka is weaker")
(ul :class "list-disc list-inside space-y-2 mt-2"
(li (strong "Maturity") " \u2014 research language. Smaller ecosystem, fewer FFI bindings, "
"less battle-tested than OCaml.")
(li (strong "WASM backend") " \u2014 compiles to C \u2192 WASM (via Emscripten). "
"Works but less optimized than " (code "wasm_of_ocaml") " or Rust's " (code "wasm-pack") ".")
(li (strong "Embeddability") " \u2014 C FFI works but less established for Python/Node embedding."))
(p "Koka is the right choice " (em "if") " compile-time linearity proves essential for correctness. "
"The decision point is Step 5 (Linear Effects) of the foundations plan. "
"If SX's own type system can enforce linearity at spec-validation time, "
"OCaml's runtime enforcement is sufficient. If not, Koka becomes the better substrate."))
;; -----------------------------------------------------------------------
;; The path to self-hosting
;; -----------------------------------------------------------------------
(~docs/section :title "The Path to Self-Hosting" :id "self-hosting"
(p "The end state: SX compiles itself. No intermediate language, no general-purpose host.")
(h4 :class "font-semibold mt-4 mb-2" "Phase 1: OCaml bootstrapper")
(p "Write " (code "bootstrap_ml.py") " \u2014 reads " (code "cek.sx") " + " (code "frames.sx")
" + " (code "primitives.sx") " + " (code "eval.sx") ", emits OCaml source. "
"Same pattern as the existing Rust/Python/JS bootstrappers.")
(p "The OCaml output is a standalone module:")
(~docs/code :code (highlight "type value =\n | Nil | Bool of bool | Num of float | Str of string\n | Sym of string | Kw of string\n | List of value list | Dict of (value * value) list\n | Lambda of params * value list * env\n | Component of string * params * value list * env\n | Handle of int (* opaque FFI reference *)\n\ntype frame =\n | IfFrame of value list * value list * env\n | ArgFrame of value list * value list * env\n | MapFrame of value * value list * value list * env\n | ReactiveResetFrame of value\n | DerefFrame of value\n (* ... 20+ frame types from frames.sx *)\n\ntype kont = frame list\ntype state = value * env * kont\n\nlet step ((ctrl, env, kont) : state) : state =\n match ctrl with\n | Lit v -> continue_val v kont\n | Var name -> continue_val (Env.find name env) kont\n | App (f, args) -> (f, env, ArgFrame(args, [], env) :: kont)\n | ..." "ocaml"))
(h4 :class "font-semibold mt-6 mb-2" "Phase 2: Native + WASM builds")
(p "Compile the OCaml output to:")
(ul :class "list-disc list-inside space-y-1 mt-2"
(li (code "sx_core.so") " / " (code "sx_core.dylib") " \u2014 native shared library, C ABI")
(li (code "sx_core.wasm") " \u2014 via " (code "wasm_of_ocaml") " for browser")
(li (code "sx_core.js") " \u2014 via " (code "js_of_ocaml") " as JS fallback"))
(p "Python web framework calls " (code "sx_core.so") " via cffi. "
"Browser loads " (code "sx_core.wasm") " via " (code "sx-platform.js") ".")
(h4 :class "font-semibold mt-6 mb-2" "Phase 3: SX evaluates web framework")
(p "The compiled core evaluator loads web framework " (code ".sx") " at runtime "
"(signals, engine, orchestration, boot). Same as the "
(a :href "/sx/(etc.(plan.isolated-evaluator))" "Isolated Evaluator") " plan, "
"but the evaluator is compiled OCaml/WASM instead of bootstrapped JS.")
(h4 :class "font-semibold mt-6 mb-2" "Phase 4: SX linearity checking")
(p "Extend " (code "types.sx") " with quantity annotations:")
(~docs/code :code (highlight ";; Quantity annotations on types\n(define-type (Signal a) :quantity :affine) ;; use at most once per scope\n(define-type (Channel a) :quantity :linear) ;; must be consumed exactly once\n\n;; Effect declarations with linearity\n(define-io-primitive \"send-message\"\n :params (channel message)\n :quantity :linear\n :effects [io]\n :doc \"Must be handled exactly once.\")\n\n;; The type checker (specced in .sx, compiled to OCaml) validates\n;; linearity at component registration time. Runtime enforcement\n;; by OCaml's one-shot continuations is the safety net." "lisp"))
(p "The type checker runs at spec-validation time. The compiled evaluator "
"executes already-verified code. SX's type system provides the linearity "
"guarantees, not the host language.")
(h4 :class "font-semibold mt-6 mb-2" "Phase 5: Self-hosting compiler")
(p "Write the compiler itself in SX:")
(ul :class "list-disc list-inside space-y-1 mt-2"
(li "Spec the CEK-to-native code generation in " (code ".sx") " files")
(li "The Phase 2 OCaml evaluator compiles the compiler spec")
(li "The compiled compiler can then compile itself")
(li "OCaml becomes the bootstrap language only \u2014 "
"needed once to get the self-hosting loop started, then never touched again"))
(~docs/code :code (highlight ";; The bootstrap chain\n\nStep 0: Python evaluator (existing)\n \u2193 evaluates bootstrap_ml.py\nStep 1: OCaml evaluator (compiled from spec by Python)\n \u2193 evaluates compiler.sx\nStep 2: SX compiler (compiled from .sx by OCaml evaluator)\n \u2193 compiles itself\nStep 3: SX compiler (compiled by itself)\n \u2193 compiles everything\n \u2193 emits native, WASM, JS from .sx spec\n \u2193 OCaml is no longer in the chain" "text"))
(p "At Step 3, the only language is SX. The compiler reads " (code ".sx") " and emits machine code. "
"OCaml was the scaffolding. The scaffolding comes down."))
;; -----------------------------------------------------------------------
;; Concurrent CEK on OCaml 5
;; -----------------------------------------------------------------------
(~docs/section :title "Concurrent CEK on OCaml 5" :id "concurrent-cek"
(p "OCaml 5's concurrency model maps directly onto the "
(a :href "/sx/(etc.(plan.foundations))" "Foundations") " plan's concurrent CEK spec.")
(h4 :class "font-semibold mt-4 mb-2" "Mapping")
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
(table :class "w-full text-left text-sm"
(thead (tr :class "border-b border-stone-200 bg-stone-100"
(th :class "px-3 py-2 font-medium text-stone-600" "SX primitive")
(th :class "px-3 py-2 font-medium text-stone-600" "OCaml 5")
(th :class "px-3 py-2 font-medium text-stone-600" "Characteristic")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" (code "spawn"))
(td :class "px-3 py-2 text-stone-700" "Fiber via " (code "perform Spawn"))
(td :class "px-3 py-2 text-stone-600" "Lightweight, scheduled by effect handler"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" (code "channel"))
(td :class "px-3 py-2 text-stone-700" (code "Eio.Stream"))
(td :class "px-3 py-2 text-stone-600" "Typed, bounded, backpressure"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" (code "yield!"))
(td :class "px-3 py-2 text-stone-700" (code "perform Yield"))
(td :class "px-3 py-2 text-stone-600" "Cooperative, zero-cost"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" (code "select"))
(td :class "px-3 py-2 text-stone-700" (code "Eio.Fiber.any"))
(td :class "px-3 py-2 text-stone-600" "First-to-complete"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" (code "fork-join"))
(td :class "px-3 py-2 text-stone-700" (code "Eio.Fiber.all"))
(td :class "px-3 py-2 text-stone-600" "Structured concurrency"))
(tr
(td :class "px-3 py-2 text-stone-700" "DAG scheduler")
(td :class "px-3 py-2 text-stone-700" "Domains + fiber pool")
(td :class "px-3 py-2 text-stone-600" "True parallelism across cores")))))
(p "Each concurrent CEK machine is a fiber. The scheduler is an effect handler. "
"This isn't simulating concurrency \u2014 it's using native concurrency whose mechanism " (em "is") " effects.")
(h4 :class "font-semibold mt-4 mb-2" "The Art DAG connection")
(p "Art DAG's 3-phase execution (analyze \u2192 plan \u2192 execute) maps onto "
"concurrent CEK + OCaml domains:")
(ul :class "list-disc list-inside space-y-1 mt-2"
(li "Analyze: single CEK machine walks the DAG graph (one fiber)")
(li "Plan: resolve dependencies, topological sort (pure computation)")
(li "Execute: spawn one fiber per independent node, fan out to domains (true parallelism)")
(li "GPU kernels: fiber performs " (code "Gpu_dispatch") " effect, handler calls into GPU via C FFI")))
;; -----------------------------------------------------------------------
;; Linear effects
;; -----------------------------------------------------------------------
(~docs/section :title "Linear Effects" :id "linear-effects"
(p "The linearity axis from foundations. Two enforcement layers:")
(h4 :class "font-semibold mt-4 mb-2" "Layer 1: SX type system (primary)")
(p "Quantity annotations in " (code "types.sx") " checked at spec-validation time:")
(ul :class "list-disc list-inside space-y-1 mt-2"
(li (code ":linear") " (1) \u2014 must be used exactly once")
(li (code ":affine") " (\u22641) \u2014 may be used at most once (can drop)")
(li (code ":unrestricted") " (\u03c9) \u2014 may be used any number of times"))
(p "Linear effects guarantee: a " (code "send-message") " effect is handled exactly once. "
"A channel is consumed. A resource handle is closed. "
"The type checker proves this before the evaluator ever runs.")
(h4 :class "font-semibold mt-4 mb-2" "Layer 2: Host runtime (safety net)")
(p "OCaml 5's one-shot continuations enforce linearity at runtime. "
"A continuation can only be " (code "continue") "'d once \u2014 second invocation raises an exception. "
"This catches any bugs in the type checker itself.")
(p "If Koka replaces OCaml: compile-time enforcement replaces runtime enforcement. "
"The safety net becomes a proof. Same semantics, stronger guarantees.")
(h4 :class "font-semibold mt-4 mb-2" "Decision point")
(p "When Step 5 (Linear Effects) of the foundations plan is reached:")
(ul :class "list-disc list-inside space-y-2 mt-2"
(li "If SX's type checker can enforce linearity reliably \u2192 "
(strong "stay on OCaml") ". Runtime one-shot is sufficient.")
(li "If linearity bugs keep slipping through \u2192 "
(strong "switch to Koka") ". Compile-time enforcement closes the gap.")
(li "If the self-hosting compiler (Phase 5) reaches maturity \u2192 "
(strong "it doesn't matter") ". SX compiles itself, the substrate is an implementation detail.")))
;; -----------------------------------------------------------------------
;; Compiled all the way down
;; -----------------------------------------------------------------------
(~docs/section :title "Compiled All the Way Down" :id "compiled"
(p "The self-hosting compiler doesn't just compile the evaluator. "
"It compiles " (em "everything") ". Component definitions, page layouts, "
"event handlers, signal computations \u2014 all compiled to native machine code. "
"SX is not an interpreted scripting language with a nice spec. "
"It's a compiled language whose compiler also runs in the browser.")
(h4 :class "font-semibold mt-4 mb-2" "JIT in the browser")
(p "The server sends SX (component definitions, page content). "
"The client receives it and " (strong "compiles to WASM and executes") ". "
"Not interprets. Not dispatches bytecodes. Compiles.")
(~docs/code :code (highlight "Server sends: (defcomp ~card (&key title) (div :class \"card\" (h2 title)))\n\nClient does:\n 1. Parse SX source (fast \u2014 it's s-expressions)\n 2. Hash AST \u2192 CID\n 3. Cache hit? Call the already-compiled WASM function\n 4. Cache miss? Compile to WASM, cache by CID, call it\n\nStep 4 is the JIT. The compiler (itself WASM) emits a WASM\nfunction, instantiates it via WebAssembly.instantiate, caches\nthe module by CID. Next time: direct function call, zero overhead." "text"))
(p "This is what V8 does with JavaScript. What LuaJIT does with Lua. "
"The difference: SX's semantics are simpler (no prototype chains, no " (code "this")
" binding, no implicit coercion), so the compiler is simpler. "
"And content-addressing means compiled artifacts are cacheable by CID \u2014 "
"compile once, store forever.")
(h4 :class "font-semibold mt-4 mb-2" "The compilation tiers")
(~docs/code :code (highlight "Tier 0: .sx source \u2192 tree-walking CEK (correct, slow \u2014 current)\nTier 1: .sx source \u2192 bytecodes \u2192 dispatch loop (correct, fast)\nTier 2: .sx source \u2192 WASM functions \u2192 execute (correct, fastest)\nTier 3: .sx source \u2192 native machine code (ahead-of-time, maximum)" "text"))
(p "Each tier is faster. Tier 1 (bytecodes) is the "
(a :href "/sx/(etc.(plan.wasm-bytecode-vm))" "WASM Bytecode VM")
" plan \u2014 compact wire format, no parse overhead, better cache locality. "
"Tier 2 is JIT \u2014 the compiler emitting WASM functions on the fly. "
"Tier 3 is AOT \u2014 the entire app precompiled. "
"All tiers use the same spec, same platform layer, same platform primitives.")
(h4 :class "font-semibold mt-4 mb-2" "Server-side precompilation")
(p "The server can compile too. Instead of sending SX source for the client to JIT, "
"send precompiled WASM:")
(~docs/code :code (highlight ";; Option A: send SX source, client JIT compiles\nContent-Type: text/sx\n\n(div :class \"card\" (h2 \"hello\"))\n\n;; Option B: send precompiled WASM, client instantiates directly\nContent-Type: application/wasm\nX-Sx-Cid: bafyrei...\n\n<binary WASM module>" "text"))
(p "Option B skips parsing and compilation entirely. The client instantiates "
"the WASM module and calls it. The server did all the work.")
(h4 :class "font-semibold mt-4 mb-2" "Content-addressed compilation cache")
(p "Every " (code ".sx") " expression has a CID. Every compiled artifact has a CID. "
"The mapping is deterministic \u2014 the compiler is a pure function:")
(~docs/code :code (highlight "source CID \u2192 compiled WASM CID\nbafyrei... \u2192 bafyrei...\n\nThis mapping is cacheable everywhere:\n\u2022 Browser cache \u2014 first visitor compiles, second visitor gets cached WASM\n\u2022 CDN \u2014 compiled artifacts served at the edge\n\u2022 IPFS \u2014 content-addressed by definition, globally deduplicated\n\u2022 Local disk \u2014 offline apps work from cached compiled components" "text"))
(h4 :class "font-semibold mt-4 mb-2" "Entire apps as machine code")
(p "The entire application can be ahead-of-time compiled to a WASM binary. "
"Component definitions, page layouts, event handlers, signal computations \u2014 "
"all compiled to native WASM functions. The \"app\" is a " (code ".wasm") " file. "
"The platform layer provides DOM and fetch. Everything in between is compiled machine code.")
(p "The only thing that stays JIT-compiled is truly dynamic content \u2014 "
"user-generated SX, REPL input, " (code "eval") "'d strings. "
"And even those get JIT'd on first use and cached by CID.")
(h4 :class "font-semibold mt-4 mb-2" "The architecture")
(~docs/code :code (highlight "sx-platform.js \u2190 DOM, fetch, timers (the real world)\n \u2191 calls\nsx-compiler.wasm \u2190 the SX compiler (itself compiled to WASM)\n \u2191 compiles\n.sx source \u2190 received from server / cache / inline\n \u2193 emits\nnative WASM functions \u2190 cached by CID, instantiated on demand\n \u2193 executes\nactual DOM mutations via platform primitives" "text"))
(p "The compiler is WASM. The code it produces is WASM. "
"It's compiled code all the way down. "
"The only interpreter in the system is the CPU."))
;; -----------------------------------------------------------------------
;; How this changes existing plans
;; -----------------------------------------------------------------------
(~docs/section :title "Impact on Existing Plans" :id "impact"
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
(table :class "w-full text-left text-sm"
(thead (tr :class "border-b border-stone-200 bg-stone-100"
(th :class "px-3 py-2 font-medium text-stone-600" "Plan")
(th :class "px-3 py-2 font-medium text-stone-600" "Impact")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700"
(a :href "/sx/(etc.(plan.rust-wasm-host))" "Rust/WASM Host"))
(td :class "px-3 py-2 text-stone-600"
"Superseded. OCaml/WASM replaces Rust/WASM. The handle table and platform layer design carry over."))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700"
(a :href "/sx/(etc.(plan.isolated-evaluator))" "Isolated Evaluator"))
(td :class "px-3 py-2 text-stone-600"
"Architecture preserved. sx-platform.js and evaluator isolation apply to the OCaml evaluator too. The JS evaluator becomes a fallback."))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700"
(a :href "/sx/(etc.(plan.foundations))" "Foundations"))
(td :class "px-3 py-2 text-stone-600"
"Accelerated. OCaml 5 has native effects/continuations/fibers. Steps 4 (Concurrent CEK) and 5 (Linear Effects) map directly."))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700"
(a :href "/sx/(etc.(plan.wasm-bytecode-vm))" "WASM Bytecode VM"))
(td :class "px-3 py-2 text-stone-600"
"Complementary. OCaml gives tree-walking CEK. Bytecode VM is a future optimization on top."))
(tr
(td :class "px-3 py-2 text-stone-700"
(a :href "/sx/(etc.(plan.self-hosting-bootstrapper))" "Self-Hosting Bootstrapper"))
(td :class "px-3 py-2 text-stone-600"
"Converges. js.sx and py.sx are self-hosting emitters. The self-hosting SX compiler (Phase 5 here) is the logical endpoint."))))))
;; -----------------------------------------------------------------------
;; Principles
;; -----------------------------------------------------------------------
(~docs/section :title "Principles" :id "principles"
(ul :class "list-disc list-inside space-y-2"
(li (strong "SX is the language.") " Not OCaml, not Rust, not Haskell. "
"Those are substrates. SX defines the semantics, SX checks the types, "
"SX will ultimately compile itself.")
(li (strong "OCaml is scaffolding.") " The closest existing language to CEK. "
"Used to bootstrap the self-hosting compiler. Comes down when the compiler is mature.")
(li (strong "The spec is the compiler's input.") " " (code "cek.sx") ", " (code "frames.sx")
", " (code "primitives.sx") " \u2014 the same files that define the language "
"become the compiler's source. One truth, one artifact.")
(li (strong "Platforms provide effects, not evaluation.") " Python provides database/HTTP. "
"JavaScript provides DOM. GPU provides tensor ops. "
"The evaluator is always compiled SX, embedded via FFI or WASM.")
(li (strong "Linearity belongs in the spec.") " SX's type system checks it. "
"The host provides runtime backup. If the type system is sound, the runtime check never fires.")
(li (strong "One language, every target.") " Not \"one spec, multiple implementations.\" "
"One " (em "compiled") " evaluator, deployed as native/.wasm/.js depending on context. "
"The evaluator binary is the same code everywhere. Only the platform layer changes.")))
;; -----------------------------------------------------------------------
;; Outcome
;; -----------------------------------------------------------------------
(~docs/section :title "Outcome" :id "outcome"
(p "After completion:")
(ul :class "list-disc list-inside space-y-2 mt-2"
(li "SX core evaluator compiled from spec via OCaml \u2014 native + WASM from one codebase.")
(li "Python web framework calls native " (code "sx_core.so") " for rendering \u2014 "
"100\u20131000x faster than interpreted Python.")
(li "Browser loads " (code "sx_core.wasm") " \u2014 same evaluator, same spec, near-native speed.")
(li "Concurrent CEK runs on OCaml 5 fibers/domains \u2014 true parallelism for Art DAG.")
(li "Linear effects validated by SX type system, enforced by OCaml one-shot continuations.")
(li "Self-hosting compiler: SX compiles itself to machine code. OCaml scaffolding removed.")
(li "The architecture proof: one language, one compiled evaluator, "
"every platform just provides effects."))
(p :class "text-stone-500 text-sm italic mt-12"
"The Mother Language was always SX. We just needed to find the right scaffolding to stand it up."))))

View File

@@ -581,6 +581,8 @@
"cek-reactive" (~plans/cek-reactive/plan-cek-reactive-content)
"reactive-runtime" (~plans/reactive-runtime/plan-reactive-runtime-content)
"rust-wasm-host" (~plans/rust-wasm-host/plan-rust-wasm-host-content)
"isolated-evaluator" (~plans/isolated-evaluator/plan-isolated-evaluator-content)
"mother-language" (~plans/mother-language/plan-mother-language-content)
:else (~plans/index/plans-index-content))))
;; ---------------------------------------------------------------------------