Skip to content

Commit 67d186e

Browse files
author
MesTTo
committed
perf(core): precomputed ground flag (tau-prolog Term.apply) short-circuits applySubst/atomVars/occurs + shared leaf type arrays; oracle 62->39ms, 270/270
1 parent 46e0377 commit 67d186e

4 files changed

Lines changed: 36 additions & 10 deletions

File tree

packages/core/src/atom.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ describe("atom constructors", () => {
2525
const atoms: Atom[] = [sym("a"), variable("x"), expr([]), gint(1)];
2626
for (const x of atoms) {
2727
expect(Object.keys(x).sort()).toEqual(
28-
["exec", "items", "kind", "match", "name", "typ", "value"].sort(),
28+
["exec", "ground", "items", "kind", "match", "name", "typ", "value"].sort(),
2929
);
3030
}
3131
});

packages/core/src/atom.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface SymAtom {
1414
readonly typ: undefined;
1515
readonly exec: undefined;
1616
readonly match: undefined;
17+
readonly ground: true;
1718
}
1819
export interface VarAtom {
1920
readonly kind: "var";
@@ -23,6 +24,7 @@ export interface VarAtom {
2324
readonly typ: undefined;
2425
readonly exec: undefined;
2526
readonly match: undefined;
27+
readonly ground: false;
2628
}
2729
export interface ExprAtom {
2830
readonly kind: "expr";
@@ -32,6 +34,9 @@ export interface ExprAtom {
3234
readonly typ: undefined;
3335
readonly exec: undefined;
3436
readonly match: undefined;
37+
/** True iff no variable occurs anywhere inside (tau-prolog's `Term.ground`): lets `applySubst`,
38+
* `atomVars`, and `occurs` short-circuit instantly on closed terms. Computed once at construction. */
39+
readonly ground: boolean;
3540
}
3641
/** A grounded value (LeaTTa `Ground`). Numbers track int vs float so `3` and `3.0` stay distinct. */
3742
export type Ground =
@@ -53,6 +58,7 @@ export interface GndAtom {
5358
readonly typ: Atom;
5459
readonly exec: GroundedExec | undefined;
5560
readonly match: GroundedMatch | undefined;
61+
readonly ground: true;
5662
}
5763
export type Atom = SymAtom | VarAtom | ExprAtom | GndAtom;
5864

@@ -95,6 +101,7 @@ export function sym(name: string): SymAtom {
95101
typ: undefined,
96102
exec: undefined,
97103
match: undefined,
104+
ground: true,
98105
};
99106
SYM_INTERN.set(name, s);
100107
}
@@ -111,10 +118,17 @@ export function variable(name: string): VarAtom {
111118
typ: undefined,
112119
exec: undefined,
113120
match: undefined,
121+
ground: false,
114122
};
115123
}
116124

117125
export function expr(items: readonly Atom[]): ExprAtom {
126+
let ground = true;
127+
for (const it of items)
128+
if (!it.ground) {
129+
ground = false;
130+
break;
131+
}
118132
return {
119133
kind: "expr",
120134
name: undefined,
@@ -123,6 +137,7 @@ export function expr(items: readonly Atom[]): ExprAtom {
123137
typ: undefined,
124138
exec: undefined,
125139
match: undefined,
140+
ground,
126141
};
127142
}
128143

@@ -147,7 +162,7 @@ export function gnd(
147162
exec?: GroundedExec,
148163
match?: GroundedMatch,
149164
): GndAtom {
150-
return { kind: "gnd", name: undefined, items: undefined, value, typ, exec, match };
165+
return { kind: "gnd", name: undefined, items: undefined, value, typ, exec, match, ground: true };
151166
}
152167

153168
/** Grounded literal constructors. */
@@ -182,6 +197,7 @@ export function atomSize(a: Atom): number {
182197

183198
/** All variable names occurring in an atom (LeaTTa `Atom.vars`), in first-seen order, deduped. */
184199
export function atomVars(a: Atom, out: string[] = []): string[] {
200+
if (a.ground) return out; // closed term: no variables (tau-prolog ground short-circuit)
185201
switch (a.kind) {
186202
case "var":
187203
if (!out.includes(a.name)) out.push(a.name);

packages/core/src/eval.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -401,22 +401,30 @@ function returnsAtom(env: MinEnv, a: Atom): boolean {
401401
// ---------- types ----------
402402
const headOr = (xs: readonly Atom[], d: Atom): Atom => (xs.length > 0 ? xs[0]! : d);
403403
const UNDEF = sym("%Undefined%");
404+
// Shared constant type-result arrays for the leaf cases: getTypes is on the hot path and these
405+
// results are read-only (callers index/headOr them, never mutate), so a fresh array per call is
406+
// pure allocation. (MORK-spirit: stop allocating on the hot path.)
407+
const NUMBER_T: Atom[] = [sym("Number")];
408+
const STRING_T: Atom[] = [sym("String")];
409+
const BOOL_T: Atom[] = [sym("Bool")];
410+
const GROUNDED_T: Atom[] = [sym("Grounded")];
411+
const UNDEF_T: Atom[] = [UNDEF];
404412

405413
function getTypes(env: MinEnv, a: Atom): Atom[] {
406414
if (a.kind === "gnd") {
407415
const g = a.value;
408-
if (g.g === "int" || g.g === "float") return [sym("Number")];
409-
if (g.g === "str") return [sym("String")];
410-
if (g.g === "bool") return [sym("Bool")];
411-
return [sym("Grounded")];
416+
if (g.g === "int" || g.g === "float") return NUMBER_T;
417+
if (g.g === "str") return STRING_T;
418+
if (g.g === "bool") return BOOL_T;
419+
return GROUNDED_T;
412420
}
413-
if (a.kind === "var") return [UNDEF];
421+
if (a.kind === "var") return UNDEF_T;
414422
if (a.kind === "sym") {
415423
const ts = env.types.get(a.name);
416-
return ts && ts.length > 0 ? ts : [UNDEF];
424+
return ts && ts.length > 0 ? ts : UNDEF_T;
417425
}
418426
// expression
419-
if (a.items.length === 0) return [UNDEF];
427+
if (a.items.length === 0) return UNDEF_T;
420428
if (opOf(a) === "StateValue" && a.items.length === 2)
421429
return [expr([sym("StateMonad"), headOr(getTypes(env, a.items[1]!), UNDEF)])];
422430
const direct = env.exprTypes.filter((p) => atomEq(p[0], a));
@@ -441,7 +449,7 @@ function getTypes(env: MinEnv, a: Atom): Atom[] {
441449
out.push(instantiate(tb, ret));
442450
}
443451
}
444-
return out.length > 0 ? out : [UNDEF];
452+
return out.length > 0 ? out : UNDEF_T;
445453
}
446454

447455
function matchReduced(tb: Bindings, expected: Atom, actual: Atom): Bindings | undefined {

packages/core/src/substitution.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export function extendSubst(s: Subst, x: string, a: Atom): Subst {
2525
/** Apply `s` to an atom: replace each variable by its assigned value (one pass; the substituted
2626
* value is not itself re-substituted). */
2727
export function applySubst(s: Subst, a: Atom): Atom {
28+
if (a.ground) return a; // closed term: substitution is identity, no walk (tau-prolog Term.apply)
2829
if (s.length === 0) return a; // empty substitution is identity (no clone)
2930
switch (a.kind) {
3031
case "var":
@@ -46,6 +47,7 @@ export function applySubst(s: Subst, a: Atom): Atom {
4647

4748
/** Occurs-check: does `$x` appear anywhere in the atom? */
4849
export function occurs(x: string, a: Atom): boolean {
50+
if (a.ground) return false; // closed term: no variable occurs in it
4951
switch (a.kind) {
5052
case "var":
5153
return x === a.name;

0 commit comments

Comments
 (0)