@fraczak/k 7.0.2 → 7.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +75 -0
- package/DOCS/CODECS.md +37 -2
- package/DOCS/CODEC_ARCHITECTURE.md +12 -5
- package/DOCS/FILE_FORMATS.md +238 -15
- package/DOCS/POLYMORPHIC_IR.md +104 -0
- package/DOCS/REPL.md +11 -3
- package/DOCS/WEB_REPL_REQUIREMENTS.md +1 -1
- package/JOURNAL.md +288 -0
- package/README.md +12 -3
- package/Value.mjs +28 -3
- package/access-table.mjs +343 -0
- package/backend-api.mjs +23 -1
- package/backends/llvm/README.md +20 -21
- package/backends/llvm/bin/k-llvm-build.mjs +105 -32
- package/backends/llvm/bin/k-llvm-compile.mjs +63 -24
- package/backends/llvm/bin/k-llvm.mjs +292 -0
- package/backends/llvm/runtime/krt.c +8 -0
- package/backends/llvm/runtime/krt.h +1 -0
- package/backends/llvm/src/cli.mjs +95 -17
- package/backends/llvm/src/executable.mjs +128 -11
- package/backends/llvm/src/kvm2llvm.mjs +10 -5
- package/backends/llvm/src/llvm.mjs +121 -9
- package/backends/llvm/tests/integration.sh +11 -11
- package/backends/llvm/{bin → tests}/k-llvm-run.mjs +48 -22
- package/backends/llvm/tests/perf-support.mjs +24 -3
- package/backends/llvm/tests/test-llvm.mjs +22 -1
- package/backends/wasm/bin/k-wasm-compile.mjs +46 -20
- package/backends/wasm/bin/k-wasm-run.mjs +61 -20
- package/backends/wasm/bin/k-wasm.mjs +47 -13
- package/backends/wasm/src/cli.mjs +141 -19
- package/backends/wasm/src/wasm.mjs +130 -23
- package/browser/repl-browser.mjs +16 -5
- package/browser/tree-view.mjs +47 -8
- package/browser/vfs-data.mjs +6 -5
- package/codecs/README.md +1 -0
- package/codecs/djson.mjs +270 -0
- package/codecs/int.mjs +2 -1
- package/codecs/json.mjs +7 -2
- package/codecs/k-parse.mjs +36 -9
- package/codecs/k-print.mjs +26 -6
- package/codecs/runtime/codec-sdk.mjs +24 -6
- package/codecs/runtime/pattern-json.mjs +80 -1
- package/codecs/runtime/prefix-codec.mjs +629 -67
- package/codecs/runtime/show-value.mjs +93 -51
- package/codecs/runtime/unicode-string.mjs +29 -6
- package/codecs/show.mjs +24 -6
- package/codecs/utf16.mjs +2 -1
- package/k.mjs +56 -16
- package/kir.mjs +58 -19
- package/kvm.mjs +49 -12
- package/objects/compile.mjs +37 -23
- package/objects/decompile.mjs +40 -10
- package/objects/extract-aliases.mjs +44 -14
- package/objects/inspect.mjs +19 -14
- package/objects/validate.mjs +18 -11
- package/package.json +11 -3
- package/pkir.mjs +772 -0
- package/repl.mjs +92 -23
- package/scripts/build-repl-html.mjs +22 -3
- package/scripts/perf-ieee.mjs +264 -13
- package/scripts/perf-int.mjs +315 -22
- package/scripts/perf-multiplication.mjs +58 -7
- package/scripts/perf-poly.mjs +306 -24
- package/scripts/run-tests.mjs +3 -0
- package/tests/test-access-table.mjs +213 -0
- package/tests/test-codecs.mjs +168 -0
- package/tests/test-pkir.mjs +530 -0
- package/tests/test-polymorphic-tdd.mjs +196 -0
- package/tests/test-repl-html.mjs +16 -2
- package/tests/test-repl.mjs +63 -1
- package/backends/llvm/bin/k-llvm-jit.mjs +0 -160
package/pkir.mjs
ADDED
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
// pkir.mjs: Polymorphic K Intermediate Representation (PKIR)
|
|
2
|
+
// State-indexed, slot-addressed intermediate representation for
|
|
3
|
+
// polymorphic relations in K. Enables direct execution with AccessTable
|
|
4
|
+
// and fast specialization to monomorphic machine code.
|
|
5
|
+
|
|
6
|
+
import { buildAccessTable } from "./access-table.mjs";
|
|
7
|
+
import { Value, isProduct, isVariant } from "./Value.mjs";
|
|
8
|
+
import { patternToPropertyList } from "./codecs/runtime/pattern-json.mjs";
|
|
9
|
+
import { exportPatternGraph } from "./codecs/runtime/codec.mjs";
|
|
10
|
+
|
|
11
|
+
export const PKIR_FORMAT = "pk-ir";
|
|
12
|
+
export const PKIR_VERSION = 1;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Instruction builder for PKIR compilation.
|
|
16
|
+
*/
|
|
17
|
+
class PKIRBuilder {
|
|
18
|
+
constructor(patternGraph, relName) {
|
|
19
|
+
this.patternGraph = patternGraph;
|
|
20
|
+
this.relName = relName;
|
|
21
|
+
this.regCount = 0;
|
|
22
|
+
this.instructions = [];
|
|
23
|
+
// Access profile per pattern state:
|
|
24
|
+
// state -> { fields: string[], variants: string[] }
|
|
25
|
+
this.accessProfile = new Map();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
nextReg() {
|
|
29
|
+
return `%v${this.regCount++}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
emit(inst) {
|
|
33
|
+
this.instructions.push(inst);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getProfile(state) {
|
|
37
|
+
if (!this.accessProfile.has(state)) {
|
|
38
|
+
this.accessProfile.set(state, { fields: [], variants: [] });
|
|
39
|
+
}
|
|
40
|
+
return this.accessProfile.get(state);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
registerFieldAccess(state, label) {
|
|
44
|
+
const prof = this.getProfile(state);
|
|
45
|
+
let slot = prof.fields.indexOf(label);
|
|
46
|
+
if (slot === -1) {
|
|
47
|
+
slot = prof.fields.length;
|
|
48
|
+
prof.fields.push(label);
|
|
49
|
+
}
|
|
50
|
+
return slot;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
registerVariantMatch(state, tag) {
|
|
54
|
+
const prof = this.getProfile(state);
|
|
55
|
+
let branch = prof.variants.indexOf(tag);
|
|
56
|
+
if (branch === -1) {
|
|
57
|
+
branch = prof.variants.length;
|
|
58
|
+
prof.variants.push(tag);
|
|
59
|
+
}
|
|
60
|
+
return branch;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Lowers a relational expression AST into PKIR instructions.
|
|
66
|
+
*/
|
|
67
|
+
function compileExpToPKIR(exp, inputReg, builder) {
|
|
68
|
+
if (!exp) return inputReg;
|
|
69
|
+
|
|
70
|
+
switch (exp.op) {
|
|
71
|
+
case "identity": {
|
|
72
|
+
const dest = builder.nextReg();
|
|
73
|
+
builder.emit({ op: "id", dest, src: inputReg });
|
|
74
|
+
return dest;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
case "empty": {
|
|
78
|
+
builder.emit({ op: "fail" });
|
|
79
|
+
return builder.nextReg();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
case "dot": {
|
|
83
|
+
const dest = builder.nextReg();
|
|
84
|
+
const state = exp.patterns?.[0] ?? 0;
|
|
85
|
+
const slot = builder.registerFieldAccess(state, exp.dot || exp.label);
|
|
86
|
+
builder.emit({
|
|
87
|
+
op: "project_slot",
|
|
88
|
+
dest,
|
|
89
|
+
src: inputReg,
|
|
90
|
+
state,
|
|
91
|
+
slot,
|
|
92
|
+
label: exp.dot || exp.label
|
|
93
|
+
});
|
|
94
|
+
return dest;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
case "div": {
|
|
98
|
+
const dest = builder.nextReg();
|
|
99
|
+
const state = exp.patterns?.[0] ?? 0;
|
|
100
|
+
const tag = exp.div || exp.tag;
|
|
101
|
+
const branchId = builder.registerVariantMatch(state, tag);
|
|
102
|
+
builder.emit({
|
|
103
|
+
op: "match_variant",
|
|
104
|
+
dest,
|
|
105
|
+
src: inputReg,
|
|
106
|
+
state,
|
|
107
|
+
tag,
|
|
108
|
+
branchId
|
|
109
|
+
});
|
|
110
|
+
return dest;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
case "vid": {
|
|
114
|
+
const dest = builder.nextReg();
|
|
115
|
+
const tag = exp.vid || exp.tag;
|
|
116
|
+
builder.emit({
|
|
117
|
+
op: "make_variant",
|
|
118
|
+
dest,
|
|
119
|
+
src: inputReg,
|
|
120
|
+
tag
|
|
121
|
+
});
|
|
122
|
+
return dest;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
case "comp": {
|
|
126
|
+
let currentReg = inputReg;
|
|
127
|
+
for (const child of exp.comp || exp.items || []) {
|
|
128
|
+
currentReg = compileExpToPKIR(child, currentReg, builder);
|
|
129
|
+
}
|
|
130
|
+
return currentReg;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
case "union": {
|
|
134
|
+
const dest = builder.nextReg();
|
|
135
|
+
const branches = [];
|
|
136
|
+
for (const child of exp.union || exp.items || []) {
|
|
137
|
+
const branchBuilder = new PKIRBuilder(
|
|
138
|
+
builder.patternGraph,
|
|
139
|
+
builder.relName
|
|
140
|
+
);
|
|
141
|
+
branchBuilder.regCount = builder.regCount;
|
|
142
|
+
const outReg = compileExpToPKIR(child, inputReg, branchBuilder);
|
|
143
|
+
builder.regCount = branchBuilder.regCount;
|
|
144
|
+
// Merge child profiles into parent
|
|
145
|
+
for (const [st, p] of branchBuilder.accessProfile.entries()) {
|
|
146
|
+
p.fields.forEach((f) => builder.registerFieldAccess(st, f));
|
|
147
|
+
p.variants.forEach((v) => builder.registerVariantMatch(st, v));
|
|
148
|
+
}
|
|
149
|
+
branches.push({
|
|
150
|
+
instructions: branchBuilder.instructions,
|
|
151
|
+
outReg
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
builder.emit({
|
|
155
|
+
op: "union",
|
|
156
|
+
dest,
|
|
157
|
+
src: inputReg,
|
|
158
|
+
branches
|
|
159
|
+
});
|
|
160
|
+
return dest;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
case "product": {
|
|
164
|
+
const dest = builder.nextReg();
|
|
165
|
+
const fieldEntries = [];
|
|
166
|
+
const fieldsList = exp.product || exp.fields || [];
|
|
167
|
+
|
|
168
|
+
for (const field of fieldsList) {
|
|
169
|
+
const label = field.label;
|
|
170
|
+
const childExp = field.exp || field.expr;
|
|
171
|
+
const branchBuilder = new PKIRBuilder(
|
|
172
|
+
builder.patternGraph,
|
|
173
|
+
builder.relName
|
|
174
|
+
);
|
|
175
|
+
branchBuilder.regCount = builder.regCount;
|
|
176
|
+
const outReg = compileExpToPKIR(childExp, inputReg, branchBuilder);
|
|
177
|
+
builder.regCount = branchBuilder.regCount;
|
|
178
|
+
|
|
179
|
+
for (const [st, p] of branchBuilder.accessProfile.entries()) {
|
|
180
|
+
p.fields.forEach((f) => builder.registerFieldAccess(st, f));
|
|
181
|
+
p.variants.forEach((v) => builder.registerVariantMatch(st, v));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
fieldEntries.push({
|
|
185
|
+
label,
|
|
186
|
+
instructions: branchBuilder.instructions,
|
|
187
|
+
outReg
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
builder.emit({
|
|
192
|
+
op: "product",
|
|
193
|
+
dest,
|
|
194
|
+
src: inputReg,
|
|
195
|
+
fields: fieldEntries
|
|
196
|
+
});
|
|
197
|
+
return dest;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
case "ref": {
|
|
201
|
+
const dest = builder.nextReg();
|
|
202
|
+
builder.emit({
|
|
203
|
+
op: "call_rel",
|
|
204
|
+
dest,
|
|
205
|
+
ref: exp.ref,
|
|
206
|
+
src: inputReg
|
|
207
|
+
});
|
|
208
|
+
return dest;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
case "filter": {
|
|
212
|
+
const dest = builder.nextReg();
|
|
213
|
+
builder.emit({
|
|
214
|
+
op: "filter",
|
|
215
|
+
dest,
|
|
216
|
+
src: inputReg,
|
|
217
|
+
filter: exp.filter
|
|
218
|
+
});
|
|
219
|
+
return dest;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
default:
|
|
223
|
+
throw new Error(`Unsupported expression op in PKIR compiler: ${exp.op}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Compiles a relation definition into a PKIR relation artifact.
|
|
229
|
+
*/
|
|
230
|
+
export function compileRelationToPKIR(relDef, relName = "rel") {
|
|
231
|
+
const patternGraph = relDef.patternGraph || relDef.typePatternGraph;
|
|
232
|
+
const builder = new PKIRBuilder(patternGraph, relName);
|
|
233
|
+
const inputReg = "%v0";
|
|
234
|
+
builder.regCount = 1;
|
|
235
|
+
|
|
236
|
+
const bodyAst = relDef.def || relDef.body;
|
|
237
|
+
const outputReg = compileExpToPKIR(bodyAst, inputReg, builder);
|
|
238
|
+
|
|
239
|
+
const [inPat, outPat] = bodyAst?.patterns || [0, 0];
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
format: PKIR_FORMAT,
|
|
243
|
+
version: PKIR_VERSION,
|
|
244
|
+
name: relName,
|
|
245
|
+
inputReg,
|
|
246
|
+
outputReg,
|
|
247
|
+
inputPattern: inPat,
|
|
248
|
+
outputPattern: outPat,
|
|
249
|
+
patternGraph,
|
|
250
|
+
accessProfile: Object.fromEntries(builder.accessProfile),
|
|
251
|
+
instructions: builder.instructions
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Creates a mock/arena KVal word representation for runtime execution.
|
|
257
|
+
*/
|
|
258
|
+
export function createMaterializedKVal(tag, words, state = 0) {
|
|
259
|
+
return {
|
|
260
|
+
repr: "materialized",
|
|
261
|
+
state,
|
|
262
|
+
tag, // numeric discriminator or null for product
|
|
263
|
+
words // array of child KVal references
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Converts a JS Value (from Value.mjs) into a KVal reference.
|
|
269
|
+
*/
|
|
270
|
+
export function valueToKVal(value, state = 0) {
|
|
271
|
+
if (value == null) return null;
|
|
272
|
+
if (isVariant(value)) {
|
|
273
|
+
return {
|
|
274
|
+
repr: "materialized",
|
|
275
|
+
state,
|
|
276
|
+
tag: value.tag,
|
|
277
|
+
words: [valueToKVal(value.value, state)]
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
if (isProduct(value)) {
|
|
281
|
+
// Sort field entries alphabetically for canonical physical layout
|
|
282
|
+
const entries = Object.entries(value.product || {}).sort(([a], [b]) =>
|
|
283
|
+
a.localeCompare(b)
|
|
284
|
+
);
|
|
285
|
+
return {
|
|
286
|
+
repr: "materialized",
|
|
287
|
+
state,
|
|
288
|
+
tag: null,
|
|
289
|
+
fieldNames: entries.map(([k]) => k),
|
|
290
|
+
words: entries.map(([_, v]) => valueToKVal(v, state))
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
// Primitive or unit
|
|
294
|
+
return {
|
|
295
|
+
repr: "materialized",
|
|
296
|
+
state,
|
|
297
|
+
tag: null,
|
|
298
|
+
words: []
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Converts a KVal reference back into a Value or JS object.
|
|
304
|
+
*/
|
|
305
|
+
export function kvalToValue(kval) {
|
|
306
|
+
if (!kval) return null;
|
|
307
|
+
if (kval.tag != null) {
|
|
308
|
+
const childVal = kval.words[0] ? kvalToValue(kval.words[0]) : {};
|
|
309
|
+
return Value.variant(kval.tag, childVal);
|
|
310
|
+
}
|
|
311
|
+
if (kval.fieldNames && Array.isArray(kval.words)) {
|
|
312
|
+
const obj = {};
|
|
313
|
+
kval.fieldNames.forEach((name, i) => {
|
|
314
|
+
obj[name] = kvalToValue(kval.words[i]);
|
|
315
|
+
});
|
|
316
|
+
return Value.product(obj);
|
|
317
|
+
}
|
|
318
|
+
return Value.product({});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Executes a list of PKIR instructions on a KVal reference using
|
|
323
|
+
* an AccessTable. Implements pure partial function semantics.
|
|
324
|
+
*/
|
|
325
|
+
export function executePKIR(pkirFunc, inputVal, accessTable, context = {}) {
|
|
326
|
+
const regs = new Map();
|
|
327
|
+
regs.set(pkirFunc.inputReg, inputVal);
|
|
328
|
+
|
|
329
|
+
function evalInstructions(instructions) {
|
|
330
|
+
for (const inst of instructions) {
|
|
331
|
+
switch (inst.op) {
|
|
332
|
+
case "id": {
|
|
333
|
+
const val = regs.get(inst.src);
|
|
334
|
+
regs.set(inst.dest, val);
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
case "fail": {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
case "project_slot": {
|
|
343
|
+
const src = regs.get(inst.src);
|
|
344
|
+
if (!src || !src.words) return false;
|
|
345
|
+
|
|
346
|
+
let byteOffset, childState;
|
|
347
|
+
if (accessTable) {
|
|
348
|
+
const meta = accessTable.resolveField(src.state, inst.label);
|
|
349
|
+
byteOffset = meta.byteOffset;
|
|
350
|
+
childState = meta.childState;
|
|
351
|
+
} else {
|
|
352
|
+
// Direct physical index fallback
|
|
353
|
+
const idx = src.fieldNames
|
|
354
|
+
? src.fieldNames.indexOf(inst.label)
|
|
355
|
+
: inst.slot;
|
|
356
|
+
if (idx === -1) return false;
|
|
357
|
+
byteOffset = idx * 8;
|
|
358
|
+
childState = src.state;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const wordIdx = Math.floor(byteOffset / 8);
|
|
362
|
+
const childVal = src.words[wordIdx];
|
|
363
|
+
if (!childVal) return false;
|
|
364
|
+
|
|
365
|
+
// Propagate new state to projected child
|
|
366
|
+
regs.set(inst.dest, { ...childVal, state: childState });
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
case "project_offset": {
|
|
371
|
+
const src = regs.get(inst.src);
|
|
372
|
+
if (!src || !src.words) return false;
|
|
373
|
+
const wordIdx = Math.floor(inst.offset / 8);
|
|
374
|
+
const childVal = src.words[wordIdx];
|
|
375
|
+
if (!childVal) return false;
|
|
376
|
+
regs.set(inst.dest, { ...childVal, state: inst.childState });
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
case "match_variant": {
|
|
381
|
+
const src = regs.get(inst.src);
|
|
382
|
+
if (!src || src.tag == null) return false;
|
|
383
|
+
|
|
384
|
+
let branchId, childState;
|
|
385
|
+
if (accessTable) {
|
|
386
|
+
const meta = accessTable.resolveVariant(src.state, src.tag);
|
|
387
|
+
branchId = meta.branchId;
|
|
388
|
+
childState = meta.childState;
|
|
389
|
+
} else {
|
|
390
|
+
branchId = src.tag === inst.tag ? inst.branchId : -1;
|
|
391
|
+
childState = src.state;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (branchId !== inst.branchId && src.tag !== inst.tag) {
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const childVal = src.words[0] || {
|
|
399
|
+
repr: "materialized",
|
|
400
|
+
state: childState,
|
|
401
|
+
tag: null,
|
|
402
|
+
words: []
|
|
403
|
+
};
|
|
404
|
+
regs.set(inst.dest, { ...childVal, state: childState });
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
case "match_tag_imm": {
|
|
409
|
+
const src = regs.get(inst.src);
|
|
410
|
+
if (!src || src.tag == null) return false;
|
|
411
|
+
if (src.tag !== inst.tag) return false;
|
|
412
|
+
const childVal = src.words[0] || {
|
|
413
|
+
repr: "materialized",
|
|
414
|
+
state: inst.childState,
|
|
415
|
+
tag: null,
|
|
416
|
+
words: []
|
|
417
|
+
};
|
|
418
|
+
regs.set(inst.dest, { ...childVal, state: inst.childState });
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
case "make_variant": {
|
|
423
|
+
const src = regs.get(inst.src);
|
|
424
|
+
const varVal = {
|
|
425
|
+
repr: "materialized",
|
|
426
|
+
state: 0,
|
|
427
|
+
tag: inst.tag,
|
|
428
|
+
words: [src]
|
|
429
|
+
};
|
|
430
|
+
regs.set(inst.dest, varVal);
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
case "product": {
|
|
435
|
+
const src = regs.get(inst.src);
|
|
436
|
+
const words = [];
|
|
437
|
+
const fieldNames = [];
|
|
438
|
+
|
|
439
|
+
for (const field of inst.fields) {
|
|
440
|
+
const savedRegs = new Map(regs);
|
|
441
|
+
const ok = evalInstructions(field.instructions);
|
|
442
|
+
if (!ok) {
|
|
443
|
+
regs.clear();
|
|
444
|
+
for (const [k, v] of savedRegs) regs.set(k, v);
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
const fieldVal = regs.get(field.outReg);
|
|
448
|
+
words.push(fieldVal);
|
|
449
|
+
fieldNames.push(field.label);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
regs.set(inst.dest, {
|
|
453
|
+
repr: "materialized",
|
|
454
|
+
state: 0,
|
|
455
|
+
tag: null,
|
|
456
|
+
fieldNames,
|
|
457
|
+
words
|
|
458
|
+
});
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
case "union": {
|
|
463
|
+
let matched = false;
|
|
464
|
+
const savedRegs = new Map(regs);
|
|
465
|
+
|
|
466
|
+
for (const branch of inst.branches) {
|
|
467
|
+
regs.clear();
|
|
468
|
+
for (const [k, v] of savedRegs) regs.set(k, v);
|
|
469
|
+
|
|
470
|
+
const ok = evalInstructions(branch.instructions);
|
|
471
|
+
if (ok) {
|
|
472
|
+
const res = regs.get(branch.outReg);
|
|
473
|
+
regs.clear();
|
|
474
|
+
for (const [k, v] of savedRegs) regs.set(k, v);
|
|
475
|
+
regs.set(inst.dest, res);
|
|
476
|
+
matched = true;
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (!matched) return false;
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
case "call_rel": {
|
|
486
|
+
const src = regs.get(inst.src);
|
|
487
|
+
const targetRel = context.rels?.[inst.ref];
|
|
488
|
+
if (!targetRel) {
|
|
489
|
+
throw new Error(`Relation not found in context: ${inst.ref}`);
|
|
490
|
+
}
|
|
491
|
+
const res = executePKIR(targetRel, src, accessTable, context);
|
|
492
|
+
if (res == null) return false;
|
|
493
|
+
regs.set(inst.dest, res);
|
|
494
|
+
break;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
case "filter": {
|
|
498
|
+
const src = regs.get(inst.src);
|
|
499
|
+
regs.set(inst.dest, src);
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
default:
|
|
504
|
+
throw new Error(`Unknown PKIR instruction opcode: ${inst.op}`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return true;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const ok = evalInstructions(pkirFunc.instructions);
|
|
511
|
+
return ok ? regs.get(pkirFunc.outputReg) : null;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Specializes a PKIR function by baking an AccessTable into monomorphic
|
|
516
|
+
* machine instructions with static offsets and pruned dead branches.
|
|
517
|
+
*/
|
|
518
|
+
export function specializePKIR(pkirFunc, accessTable) {
|
|
519
|
+
function specializeList(instructions, initialRegStates) {
|
|
520
|
+
const regStates = new Map(initialRegStates);
|
|
521
|
+
const result = [];
|
|
522
|
+
|
|
523
|
+
for (const inst of instructions) {
|
|
524
|
+
switch (inst.op) {
|
|
525
|
+
case "id": {
|
|
526
|
+
const st = regStates.get(inst.src);
|
|
527
|
+
if (st != null) regStates.set(inst.dest, st);
|
|
528
|
+
result.push({ ...inst });
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
case "project_slot": {
|
|
533
|
+
const srcState = regStates.get(inst.src) ?? 0;
|
|
534
|
+
let meta = null;
|
|
535
|
+
try {
|
|
536
|
+
meta = accessTable.resolveField(srcState, inst.label);
|
|
537
|
+
} catch {
|
|
538
|
+
meta = null;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (meta) {
|
|
542
|
+
regStates.set(inst.dest, meta.childState);
|
|
543
|
+
result.push({
|
|
544
|
+
op: "project_offset",
|
|
545
|
+
dest: inst.dest,
|
|
546
|
+
src: inst.src,
|
|
547
|
+
offset: meta.byteOffset,
|
|
548
|
+
skipCount: meta.skipCount,
|
|
549
|
+
childState: meta.childState,
|
|
550
|
+
label: inst.label
|
|
551
|
+
});
|
|
552
|
+
} else {
|
|
553
|
+
result.push({ op: "fail" });
|
|
554
|
+
}
|
|
555
|
+
break;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
case "match_variant": {
|
|
559
|
+
const srcState = regStates.get(inst.src) ?? 0;
|
|
560
|
+
let meta = null;
|
|
561
|
+
try {
|
|
562
|
+
meta = accessTable.resolveVariant(srcState, inst.tag);
|
|
563
|
+
} catch {
|
|
564
|
+
meta = null;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (meta && meta.branchId !== -1) {
|
|
568
|
+
regStates.set(inst.dest, meta.childState);
|
|
569
|
+
result.push({
|
|
570
|
+
op: "match_tag_imm",
|
|
571
|
+
dest: inst.dest,
|
|
572
|
+
src: inst.src,
|
|
573
|
+
tagId: meta.tagId,
|
|
574
|
+
branchId: meta.branchId,
|
|
575
|
+
childState: meta.childState,
|
|
576
|
+
tag: inst.tag
|
|
577
|
+
});
|
|
578
|
+
} else {
|
|
579
|
+
// Disjoint variant: cannot match under schema S; prune
|
|
580
|
+
result.push({ op: "fail" });
|
|
581
|
+
}
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
case "make_variant": {
|
|
586
|
+
regStates.set(inst.dest, 0);
|
|
587
|
+
result.push({ ...inst });
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
case "union": {
|
|
592
|
+
const specializedBranches = [];
|
|
593
|
+
for (const b of inst.branches) {
|
|
594
|
+
const branchInsts = specializeList(b.instructions, regStates);
|
|
595
|
+
if (branchInsts.length > 0 && branchInsts[0].op === "fail") {
|
|
596
|
+
// Dead branch eliminated under schema S
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
specializedBranches.push({
|
|
600
|
+
instructions: branchInsts,
|
|
601
|
+
outReg: b.outReg
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
result.push({
|
|
605
|
+
...inst,
|
|
606
|
+
branches: specializedBranches
|
|
607
|
+
});
|
|
608
|
+
break;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
case "product": {
|
|
612
|
+
const specializedFields = inst.fields.map((f) => ({
|
|
613
|
+
...f,
|
|
614
|
+
instructions: specializeList(f.instructions, regStates)
|
|
615
|
+
}));
|
|
616
|
+
result.push({
|
|
617
|
+
...inst,
|
|
618
|
+
fields: specializedFields
|
|
619
|
+
});
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
default:
|
|
624
|
+
result.push({ ...inst });
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
return result;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const initialStates = new Map();
|
|
632
|
+
initialStates.set(pkirFunc.inputReg, accessTable.rootState);
|
|
633
|
+
|
|
634
|
+
return {
|
|
635
|
+
...pkirFunc,
|
|
636
|
+
isSpecialized: true,
|
|
637
|
+
instructions: specializeList(pkirFunc.instructions, initialStates)
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Lowers a PKIR function into a KVM function representation compatible
|
|
643
|
+
* with WebAssembly and machine code generators.
|
|
644
|
+
*/
|
|
645
|
+
export function lowerPKIRToKVM(pkir) {
|
|
646
|
+
function convert(insts, branchInReg = null) {
|
|
647
|
+
function mapReg(r) {
|
|
648
|
+
if (branchInReg != null && r === branchInReg) return "%in";
|
|
649
|
+
if (r === pkir.inputReg) return "%in";
|
|
650
|
+
return r;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const res = [];
|
|
654
|
+
for (const inst of insts) {
|
|
655
|
+
switch (inst.op) {
|
|
656
|
+
case "id":
|
|
657
|
+
case "filter":
|
|
658
|
+
res.push({
|
|
659
|
+
op: "id",
|
|
660
|
+
dest: mapReg(inst.dest),
|
|
661
|
+
src: mapReg(inst.src)
|
|
662
|
+
});
|
|
663
|
+
break;
|
|
664
|
+
case "fail":
|
|
665
|
+
res.push({ op: "fail" });
|
|
666
|
+
break;
|
|
667
|
+
case "project_slot":
|
|
668
|
+
case "project_offset":
|
|
669
|
+
res.push({
|
|
670
|
+
op: "project_field",
|
|
671
|
+
dest: mapReg(inst.dest),
|
|
672
|
+
src: mapReg(inst.src),
|
|
673
|
+
label: inst.label,
|
|
674
|
+
slot: inst.slot ?? inst.skipCount
|
|
675
|
+
});
|
|
676
|
+
break;
|
|
677
|
+
case "match_variant":
|
|
678
|
+
case "match_tag_imm":
|
|
679
|
+
res.push({
|
|
680
|
+
op: "project_variant",
|
|
681
|
+
dest: mapReg(inst.dest),
|
|
682
|
+
src: mapReg(inst.src),
|
|
683
|
+
tag: inst.tag
|
|
684
|
+
});
|
|
685
|
+
break;
|
|
686
|
+
case "make_variant":
|
|
687
|
+
res.push({
|
|
688
|
+
op: "make_variant",
|
|
689
|
+
dest: mapReg(inst.dest),
|
|
690
|
+
src: mapReg(inst.src),
|
|
691
|
+
tag: inst.tag
|
|
692
|
+
});
|
|
693
|
+
break;
|
|
694
|
+
case "product":
|
|
695
|
+
res.push({
|
|
696
|
+
op: "product",
|
|
697
|
+
dest: mapReg(inst.dest),
|
|
698
|
+
src: mapReg(inst.src),
|
|
699
|
+
branches: inst.fields.map((f) => {
|
|
700
|
+
const body = convert(f.instructions, inst.src);
|
|
701
|
+
const branchOut = f.outReg === inst.src
|
|
702
|
+
? "%in"
|
|
703
|
+
: mapReg(f.outReg);
|
|
704
|
+
body.push({ op: "return", src: branchOut });
|
|
705
|
+
return { label: f.label, body };
|
|
706
|
+
})
|
|
707
|
+
});
|
|
708
|
+
break;
|
|
709
|
+
case "union":
|
|
710
|
+
res.push({
|
|
711
|
+
op: "union",
|
|
712
|
+
dest: mapReg(inst.dest),
|
|
713
|
+
src: mapReg(inst.src),
|
|
714
|
+
branches: inst.branches.map((b) => {
|
|
715
|
+
const body = convert(b.instructions, inst.src);
|
|
716
|
+
const branchOut = b.outReg === inst.src
|
|
717
|
+
? "%in"
|
|
718
|
+
: mapReg(b.outReg);
|
|
719
|
+
body.push({ op: "return", src: branchOut });
|
|
720
|
+
return { body };
|
|
721
|
+
})
|
|
722
|
+
});
|
|
723
|
+
break;
|
|
724
|
+
case "call_rel":
|
|
725
|
+
res.push({
|
|
726
|
+
op: "call",
|
|
727
|
+
dest: mapReg(inst.dest),
|
|
728
|
+
func: inst.ref,
|
|
729
|
+
src: mapReg(inst.src)
|
|
730
|
+
});
|
|
731
|
+
break;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return res;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const body = convert(pkir.instructions);
|
|
738
|
+
body.push({
|
|
739
|
+
op: "return",
|
|
740
|
+
src: pkir.outputReg === pkir.inputReg ? "%in" : pkir.outputReg
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
let inputPattern = [["any", []]];
|
|
744
|
+
let outputPattern = [["any", []]];
|
|
745
|
+
const graph = pkir.patternGraph;
|
|
746
|
+
if (graph) {
|
|
747
|
+
if (pkir.inputPattern != null) {
|
|
748
|
+
try {
|
|
749
|
+
const inId = graph.find(pkir.inputPattern);
|
|
750
|
+
inputPattern = patternToPropertyList(
|
|
751
|
+
exportPatternGraph(graph, inId)
|
|
752
|
+
);
|
|
753
|
+
} catch {}
|
|
754
|
+
}
|
|
755
|
+
if (pkir.outputPattern != null) {
|
|
756
|
+
try {
|
|
757
|
+
const outId = graph.find(pkir.outputPattern);
|
|
758
|
+
outputPattern = patternToPropertyList(
|
|
759
|
+
exportPatternGraph(graph, outId)
|
|
760
|
+
);
|
|
761
|
+
} catch {}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
return {
|
|
766
|
+
name: pkir.name,
|
|
767
|
+
body,
|
|
768
|
+
inputPattern,
|
|
769
|
+
outputPattern,
|
|
770
|
+
typePatternGraph: graph
|
|
771
|
+
};
|
|
772
|
+
}
|