Skip to content

Commit 5a41ae9

Browse files
author
MesTTo
committed
perf(core): interpreter stack as O(1) cons-list (no per-step array slice/spread); 270/270
1 parent b4a781b commit 5a41ae9

1 file changed

Lines changed: 41 additions & 33 deletions

File tree

packages/core/src/eval.ts

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,14 @@ export interface Frame {
1717
readonly vars: readonly string[];
1818
readonly fin: boolean;
1919
}
20-
export type Stack = readonly Frame[];
20+
// The evaluation stack as an immutable cons-list (O(1) push/rest, no per-step array slice/spread
21+
// — the array form showed up as ArrayPrototypeSlice in the profile). `null` is the empty stack.
22+
export interface StackCons {
23+
readonly head: Frame;
24+
readonly tail: Stack;
25+
}
26+
export type Stack = StackCons | null;
27+
const cons = (head: Frame, tail: Stack): StackCons => ({ head, tail });
2128
export interface Item {
2229
readonly stack: Stack;
2330
readonly bnd: Bindings;
@@ -80,7 +87,7 @@ function isEmbeddedOp(a: Atom): boolean {
8087
return op !== undefined && EMBEDDED.has(op);
8188
}
8289

83-
const varsCopy = (prev: Stack): readonly string[] => (prev.length > 0 ? prev[0]!.vars : []);
90+
const varsCopy = (prev: Stack): readonly string[] => (prev !== null ? prev.head.vars : []);
8491

8592
function isVariableHeaded(a: Atom): boolean {
8693
if (a.kind === "var") return true;
@@ -101,24 +108,24 @@ function atomToStack(a: Atom, prev: Stack): Stack {
101108
const op = opOf(a);
102109
const it = a.items;
103110
if (op === "chain" && it.length === 4 && it[2]!.kind === "var") {
104-
return atomToStack(it[1]!, [frame(a, "chain", varsCopy(prev)), ...prev]);
111+
return atomToStack(it[1]!, cons(frame(a, "chain", varsCopy(prev)), prev));
105112
}
106113
if (op === "function" && it.length === 2 && it[1]!.kind === "expr") {
107-
return atomToStack(it[1]!, [frame(a, "function", varsCopy(prev)), ...prev]);
114+
return atomToStack(it[1]!, cons(frame(a, "function", varsCopy(prev)), prev));
108115
}
109116
if (op === "unify" && it.length === 5) {
110-
return [frame(a, "none"), ...prev];
117+
return cons(frame(a, "none"), prev);
111118
}
112-
if (op === "chain") return [frame(errAtom(a, "chain: malformed"), "none", [], true), ...prev];
119+
if (op === "chain") return cons(frame(errAtom(a, "chain: malformed"), "none", [], true), prev);
113120
if (op === "function")
114-
return [frame(errAtom(a, "function: malformed"), "none", [], true), ...prev];
115-
if (op === "unify") return [frame(errAtom(a, "unify: malformed"), "none", [], true), ...prev];
121+
return cons(frame(errAtom(a, "function: malformed"), "none", [], true), prev);
122+
if (op === "unify") return cons(frame(errAtom(a, "unify: malformed"), "none", [], true), prev);
116123
}
117-
return [frame(a, "none", varsCopy(prev)), ...prev];
124+
return cons(frame(a, "none", varsCopy(prev)), prev);
118125
}
119126

120127
function finItem(st: Stack, a: Atom, b: Bindings): Item {
121-
return { stack: [frame(a, "none", [], true), ...st], bnd: b };
128+
return { stack: cons(frame(a, "none", [], true), st), bnd: b };
122129
}
123130

124131
function evalResult(prev: Stack, r: Atom, b: Bindings): Item {
@@ -338,16 +345,17 @@ function unifyOp(prev: Stack, a: Atom, p: Atom, t: Atom, e: Atom, b: Bindings):
338345
}
339346

340347
// ---------- final-item helpers ----------
341-
const isFinal = (it: Item): boolean => it.stack.length === 1 && it.stack[0]!.fin;
348+
const isFinal = (it: Item): boolean =>
349+
it.stack !== null && it.stack.tail === null && it.stack.head.fin;
342350
function finalPair(it: Item): [Atom, Bindings] {
343-
const f = it.stack[0];
344-
return f === undefined ? [emptyA, []] : [instantiate(it.bnd, f.atom), it.bnd];
351+
const f = it.stack;
352+
return f === null ? [emptyA, []] : [instantiate(it.bnd, f.head.atom), it.bnd];
345353
}
346354
function exhaustedPair(it: Item): [Atom, Bindings] {
347-
const f = it.stack[0];
348-
return f === undefined
355+
const f = it.stack;
356+
return f === null
349357
? [emptyA, it.bnd]
350-
: [expr([sym("Error"), instantiate(it.bnd, f.atom), sym("StackOverflow")]), it.bnd];
358+
: [expr([sym("Error"), instantiate(it.bnd, f.head.atom), sym("StackOverflow")]), it.bnd];
351359
}
352360

353361
function resolveAtomFix(b: Bindings, n: number, a: Atom): Atom {
@@ -372,8 +380,8 @@ function restrictBnd(vars: readonly string[], b: Bindings): Bindings {
372380
}
373381
function scopeVars(b: Bindings, prev: Stack): string[] {
374382
const out: string[] = [];
375-
for (const f of prev)
376-
for (const v of atomVars(instantiate(b, f.atom))) if (!out.includes(v)) out.push(v);
383+
for (let p = prev; p !== null; p = p.tail)
384+
for (const v of atomVars(instantiate(b, p.head.atom))) if (!out.includes(v)) out.push(v);
377385
return out;
378386
}
379387
function superposeItem(prev: Stack, b: Bindings, pair: Atom): Item {
@@ -604,29 +612,29 @@ function getDocOf(env: MinEnv, w: World, atom: Atom): Atom {
604612

605613
// ---------- the step function ----------
606614
function interpretStack1(env: MinEnv, fuel: number, st: St, it: Item): [Item[], St] {
607-
if (it.stack.length === 0) return [[], st];
608-
const top = it.stack[0]!;
609-
const prev = it.stack.slice(1);
615+
if (it.stack === null) return [[], st];
616+
const top = it.stack.head;
617+
const prev = it.stack.tail;
610618
if (top.fin) {
611-
if (prev.length === 0) return [[it], st];
612-
const pf = prev[0]!;
613-
const pprev = prev.slice(1);
619+
if (prev === null) return [[it], st];
620+
const pf = prev.head;
621+
const pprev = prev.tail;
614622
const res = instantiate(it.bnd, top.atom);
615623
if (pf.ret === "chain") {
616624
if (opOf(pf.atom) === "chain" && pf.atom.kind === "expr" && pf.atom.items.length === 4) {
617625
const v = pf.atom.items[2]!;
618626
const templ = pf.atom.items[3]!;
619627
const nf = frame(expr([sym("chain"), res, v, templ]), pf.ret, pf.vars, false);
620-
return [[{ stack: [nf, ...pprev], bnd: it.bnd }], st];
628+
return [[{ stack: cons(nf, pprev), bnd: it.bnd }], st];
621629
}
622630
return [[finItem(pprev, errAtom(pf.atom, "chain: corrupt frame"), it.bnd)], st];
623631
}
624632
if (pf.ret === "function") {
625633
if (opOf(res) === "return" && res.kind === "expr" && res.items.length === 2)
626634
return [[finItem(pprev, res.items[1]!, it.bnd)], st];
627635
if (isEmbeddedOp(res))
628-
return [[{ stack: atomToStack(res, [pf, ...pprev]), bnd: it.bnd }], st];
629-
const target = pprev.length > 0 ? pprev[0]!.atom : res;
636+
return [[{ stack: atomToStack(res, cons(pf, pprev)), bnd: it.bnd }], st];
637+
const target = pprev !== null ? pprev.head.atom : res;
630638
return [[finItem(pprev, errAtom(target, "NoReturn"), it.bnd)], st];
631639
}
632640
return [[], st]; // Ret.none on a finished non-top frame
@@ -707,7 +715,7 @@ function interpretStack1(env: MinEnv, fuel: number, st: St, it: Item): [Item[],
707715
case "collapse-bind": {
708716
if (it2.length !== 2) break;
709717
const [atoms, st2] = interpretLoop(env, fuel, st, [
710-
{ stack: atomToStack(it2[1]!, []), bnd: it.bnd },
718+
{ stack: atomToStack(it2[1]!, null), bnd: it.bnd },
711719
]);
712720
return [[finItem(prev, expr(atoms.map((p) => expr([p[0], unitA]))), it.bnd)], st2];
713721
}
@@ -816,7 +824,7 @@ function interpretStack1(env: MinEnv, fuel: number, st: St, it: Item): [Item[],
816824
break;
817825
}
818826
if (isEmbeddedOp(a)) return [[finItem(prev, errAtom(a, "unsupported minimal op"), it.bnd)], st];
819-
return [[{ stack: [frame(top.atom, top.ret, top.vars, true), ...prev], bnd: it.bnd }], st];
827+
return [[{ stack: cons(frame(top.atom, top.ret, top.vars, true), prev), bnd: it.bnd }], st];
820828
}
821829

822830
// space-mutation helpers used by add/remove/import
@@ -1025,7 +1033,7 @@ function mettaEval(
10251033
}
10261034
const wApp = expr([sym(op), ...partAtoms]);
10271035
const [pairs, st3] = interpretLoop(env, fuel, cur2, [
1028-
{ stack: atomToStack(expr([sym("eval"), wApp]), []), bnd },
1036+
{ stack: atomToStack(expr([sym("eval"), wApp]), null), bnd },
10291037
]);
10301038
cur2 = st3;
10311039
for (const p of pairs) {
@@ -1051,15 +1059,15 @@ function mettaEval(
10511059
if (w.kind === "expr" && w.items.length > 0) {
10521060
// expression-headed application
10531061
const [ruleRes, st1] = interpretLoop(env, fuel, st, [
1054-
{ stack: atomToStack(expr([sym("eval"), w]), []), bnd },
1062+
{ stack: atomToStack(expr([sym("eval"), w]), null), bnd },
10551063
]);
10561064
const reduced = ruleRes.filter((p) => !atomEq(p[0], w) && !atomEq(p[0], notReducibleA));
10571065
if (reduced.length === 0) {
10581066
const [tupleRes, st2] = interpretLoop(env, fuel, st1, [
10591067
{
10601068
stack: atomToStack(
10611069
expr([sym("eval"), expr([sym("interpret-tuple"), w, sym("&self")])]),
1062-
[],
1070+
null,
10631071
),
10641072
bnd,
10651073
},
@@ -1088,7 +1096,7 @@ function mettaEval(
10881096

10891097
// bare symbol / variable / grounded
10901098
const [pairs, st1] = interpretLoop(env, fuel, st, [
1091-
{ stack: atomToStack(expr([sym("eval"), w]), []), bnd },
1099+
{ stack: atomToStack(expr([sym("eval"), w]), null), bnd },
10921100
]);
10931101
const out: Array<[Atom, Bindings]> = [];
10941102
let cur = st1;

0 commit comments

Comments
 (0)