@fraczak/k 7.0.2 → 7.1.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/DOCS/FILE_FORMATS.md +238 -15
  3. package/DOCS/POLYMORPHIC_IR.md +104 -0
  4. package/JOURNAL.md +288 -0
  5. package/README.md +10 -2
  6. package/access-table.mjs +343 -0
  7. package/backend-api.mjs +18 -0
  8. package/backends/llvm/README.md +20 -21
  9. package/backends/llvm/bin/k-llvm-build.mjs +105 -32
  10. package/backends/llvm/bin/k-llvm-compile.mjs +63 -24
  11. package/backends/llvm/bin/k-llvm.mjs +292 -0
  12. package/backends/llvm/runtime/krt.c +8 -0
  13. package/backends/llvm/runtime/krt.h +1 -0
  14. package/backends/llvm/src/cli.mjs +95 -17
  15. package/backends/llvm/src/executable.mjs +128 -11
  16. package/backends/llvm/src/kvm2llvm.mjs +10 -5
  17. package/backends/llvm/src/llvm.mjs +121 -9
  18. package/backends/llvm/tests/integration.sh +11 -11
  19. package/backends/llvm/{bin → tests}/k-llvm-run.mjs +48 -22
  20. package/backends/llvm/tests/perf-support.mjs +24 -3
  21. package/backends/llvm/tests/test-llvm.mjs +22 -1
  22. package/backends/wasm/bin/k-wasm-compile.mjs +46 -20
  23. package/backends/wasm/bin/k-wasm-run.mjs +61 -20
  24. package/backends/wasm/bin/k-wasm.mjs +47 -13
  25. package/backends/wasm/src/cli.mjs +141 -19
  26. package/backends/wasm/src/wasm.mjs +130 -23
  27. package/browser/repl-browser.mjs +16 -5
  28. package/codecs/k-parse.mjs +29 -8
  29. package/codecs/k-print.mjs +22 -6
  30. package/codecs/runtime/codec-sdk.mjs +8 -4
  31. package/codecs/show.mjs +20 -6
  32. package/k.mjs +56 -16
  33. package/kir.mjs +58 -19
  34. package/kvm.mjs +49 -12
  35. package/objects/compile.mjs +37 -23
  36. package/objects/decompile.mjs +40 -10
  37. package/objects/extract-aliases.mjs +44 -14
  38. package/objects/inspect.mjs +19 -14
  39. package/objects/validate.mjs +18 -11
  40. package/package.json +10 -3
  41. package/pkir.mjs +772 -0
  42. package/repl.mjs +36 -18
  43. package/scripts/build-repl-html.mjs +14 -2
  44. package/scripts/perf-ieee.mjs +264 -13
  45. package/scripts/perf-int.mjs +315 -22
  46. package/scripts/perf-multiplication.mjs +58 -7
  47. package/scripts/perf-poly.mjs +306 -24
  48. package/scripts/run-tests.mjs +3 -0
  49. package/tests/test-access-table.mjs +213 -0
  50. package/tests/test-pkir.mjs +530 -0
  51. package/tests/test-polymorphic-tdd.mjs +196 -0
  52. package/tests/test-repl-html.mjs +15 -2
  53. package/tests/test-repl.mjs +30 -1
  54. package/backends/llvm/bin/k-llvm-jit.mjs +0 -160
package/JOURNAL.md ADDED
@@ -0,0 +1,288 @@
1
+ # Transducer & Polymorphic Execution Journal
2
+
3
+ Branch: `transducer`
4
+ Repository: `@fraczak/k`
5
+
6
+ ## Objective
7
+
8
+ Design, specify, and implement an efficient compilation and execution
9
+ model for polymorphic functions in K.
10
+
11
+ The architecture bridges two fundamental physical data representations:
12
+ 1. **Sequential Prefix-Free Streams** (Wire / Bitstream) operated by
13
+ **Streaming Pushdown Transducers** for linear/erasing access.
14
+ 2. **Random-Access Memory Tables** (Arena Nodes) operated by
15
+ **Layout Witnesses** (Offset Dictionaries) and specialized
16
+ monomorphic kernels (`?S F`).
17
+ 3. **The Hybrid Dual-Form Pointer** (`KVal` / `KChild`), allowing values
18
+ to remain compact wire slices by default, with on-demand **Spine
19
+ Promotion** when non-linear (repeated / out-of-order) access occurs.
20
+
21
+ ---
22
+
23
+ ## Architecture Log & Decisions
24
+
25
+ ### 2026-10-12: Architectural Foundations & TDD Initiation
26
+
27
+ - **Core Duality Formalized**:
28
+ - *Witness Approach*: Operates on indexed random-access memory tables.
29
+ Static offset vector $\Omega(S, F)$ resolves field access without
30
+ dynamic string lookups.
31
+ - *Transducer Approach*: Operates on sequential prefix-free code
32
+ streams. State transitions over choice bits navigate variants and
33
+ skip unneeded product fields without materialization or re-encoding.
34
+ - **Linearity vs Non-Linearity**:
35
+ - Linear/affine access (erasing access: each field read $\le 1$ time in
36
+ stream order) runs entirely in streaming Transducer mode in
37
+ $O(\text{depth})$ stack memory with zero node allocations.
38
+ - Non-linear access (duplication `{()x, ()x}`, out-of-order reads) is
39
+ handled via:
40
+ - Zero-copy slice duplication for independent linear readers.
41
+ - Demand-driven **Spine Promotion**: materializing only the immediate
42
+ outer product spine into an arena node with child pointers
43
+ remaining as serialized wire slices.
44
+ - **TDD Test Suite Planned & Implemented**:
45
+ - Initial suite created in `tests/test-polymorphic-tdd.mjs` (all 6
46
+ tests passing).
47
+ - Targets 6 orthogonal polymorphic patterns:
48
+ 1. Filtered Search (`search`): Opaque payload preservation &
49
+ non-linear condition testing.
50
+ 2. Search with Field Reordering: Varied syntax and memory ordering.
51
+ 3. Boundary Cases: Empty lists, zero matches, all match.
52
+ 4. Multi-Variant Dispatch (`extract_id`): Variable byte offsets
53
+ across variants.
54
+ 5. Dual-Stream Zip (`zip`): Concurrent independent polymorphic
55
+ streams.
56
+ 6. Polymorphic Reverse (`poly_rev`): Accumulator and spine
57
+ reconstruction.
58
+
59
+ ### 2026-10-12: State-Indexed Access Table (`access-table.mjs`)
60
+
61
+ - **Algorithm Implemented (`buildAccessTable`)**:
62
+ - Implemented in `access-table.mjs`.
63
+ - Maps any input value schema $S$ to a function's structural access
64
+ profile $P$.
65
+ - Generates a state-indexed table of `NodeDescriptor` entries:
66
+ - Products: maps logical field slots to physical slots, byte offsets,
67
+ skip counts, and child states.
68
+ - Unions: maps physical discriminators to branch indices and child
69
+ states.
70
+ - Zero runtime string lookups; single-array-load machine instruction
71
+ dispatch.
72
+ - **Unit Verification (`tests/test-access-table.mjs`)**:
73
+ - All tests passing: verifies offset calculation for `search` and
74
+ `extract_id`.
75
+ - Simulates in-memory payload navigation with zero string comparisons.
76
+
77
+ ### 2026-10-12: Formal Specification of PKIR (`DOCS/POLYMORPHIC_IR.md`)
78
+
79
+ - Specified the Polymorphic K Intermediate Representation (PKIR):
80
+ - Abstract state-indexed, slot-addressed instruction set.
81
+ - Dual execution strategy: direct interpretation with `AccessTable`
82
+ and fast monomorphic specialization.
83
+ - Pure partial function semantics with failure propagation.
84
+
85
+ ### 2026-10-12: PKIR Implementation & Specialization (`pkir.mjs`)
86
+
87
+ - **Compiler Lowering (`compileRelationToPKIR`)**:
88
+ - Traverses relational AST and produces state-indexed PKIR instructions.
89
+ - Automatically derives `accessProfile` per pattern node.
90
+ - **Fat-Pointer Runtime (`KVal` / `valueToKVal` / `kvalToValue`)**:
91
+ - Materialized arena node simulation with dual-form support.
92
+ - Lossless round-trip conversion preserving arbitrary nested payloads.
93
+ - **Direct Execution (`executePKIR`)**:
94
+ - Evaluates PKIR instructions using `AccessTable` descriptors.
95
+ - Supports recursive relation invocation (`call_rel`) and failure
96
+ backtracking across union branches.
97
+ - **Monomorphic Specialization (`specializePKIR`)**:
98
+ - Bakes concrete `AccessTable` into specialized instructions
99
+ (`project_offset`, `match_tag_imm`).
100
+ - Dead branch elimination: prunes non-matching union branches.
101
+ - Direct execution of specialized artifacts without `AccessTable`.
102
+ - **Test Verification (`tests/test-pkir.mjs`)**:
103
+ - Comprehensive 8-test suite verifying lowering, recursive search,
104
+ multi-variant dispatch, specialization, and error handling.
105
+ - Full project test suite (`npm test`) passes with 100% green status.
106
+
107
+ ### 2026-10-12: Performance Evaluation (Previous Work vs PKIR)
108
+
109
+ - **Multi-Variant Dispatch Benchmark (`extract_id`, 200k iterations)**:
110
+ - AST Interpreter (`run.mjs`): 225.98 ms (885,035 ops/s)
111
+ [1.00x baseline].
112
+ - kVM Interpreter (`kvm.mjs`): 78.36 ms (2,552,456 ops/s)
113
+ [2.88x speedup].
114
+ - PKIR Generic (`AccessTable`): 74.69 ms (2,677,615 ops/s)
115
+ [3.03x speedup].
116
+ - PKIR Specialized (`specializePKIR`): 62.23 ms (3,214,079 ops/s)
117
+ [3.63x speedup].
118
+ - **Elimination of the Envelope Tax**:
119
+ - In previous work (`scripts/perf-poly.mjs`), polymorphic envelope
120
+ wrapping suffered a 26x performance penalty (31.74 ms envelope-aware
121
+ vs 1.21 ms envelope-free).
122
+ - The Layout Witness approach eliminates envelopes completely: layout
123
+ witnesses index heterogeneous physical records directly without
124
+ boxing or re-encoding.
125
+ - **Sub-30µs Specialization Latency**:
126
+ - `buildAccessTable`: 2.28 µs per table.
127
+ - `specializePKIR`: 24.87 µs per relation specialization.
128
+ - Total Specialization Overhead: ~27.15 µs (compared to 100-1500 ms for
129
+ traditional Wasm/LLVM AOT compilation), enabling instant runtime
130
+ JIT tiering.
131
+ - **Zero Payload Re-encoding in Recursive Traversals (`search_`)**:
132
+ - Evaluated on 100-element lists with arbitrary heterogeneous payloads.
133
+ - Throughput: 404,703 items/sec (0.247 ms per 100-item pass).
134
+ - Opaque payload slices (`data: payload_i`) are forwarded by fat-pointer
135
+ references without traversing, unpacking, or re-serializing them.
136
+
137
+ ### 2026-10-12: WebAssembly Integration in REPL (`repl.mjs` & `repl.html`)
138
+
139
+ - **PKIR-to-KVM Lowering (`lowerPKIRToKVM`)**:
140
+ - Implemented and exported `lowerPKIRToKVM(pkir)` in `pkir.mjs` and
141
+ `backend-api.mjs`.
142
+ - Translates state-indexed PKIR instructions (`id`, `filter`,
143
+ `project_slot`, `match_variant`, `make_variant`, `product`, `union`,
144
+ `call_rel`) into machine-level KVM instructions for `lowerToWasm`.
145
+ - Maps top-level input register and branch-local input registers to
146
+ `%in`, enabling seamless integration with Wasm register allocation.
147
+ - **WebAssembly Compilation Pipeline**:
148
+ - Extended `compileModule`, `compileWasmArtifactFromDefs`, and
149
+ `compileWasmArtifactFromObject` in `backends/wasm/src/wasm.mjs` with
150
+ the `usePKIR` option.
151
+ - Implemented and exported `compileWasmArtifactFromPKIR(pkirInput)`.
152
+ - **REPL CLI Support (`repl.mjs`)**:
153
+ - Added `pkir` engine mode: `--pkir`, `--engine=pkir`, `:pkir`,
154
+ `:engine pkir`, and autocompletion in `:engine [wasm|pkir|js]`.
155
+ - In `executeExpressionWithWasm`: routes compilation through PKIR when
156
+ the engine is set to `pkir`.
157
+ - **Browser Web REPL (`repl.html`)**:
158
+ - Updated `browser/repl-browser.mjs` with a 3-way engine badge toggle
159
+ (`wasm-in-process` -> `pkir-wasm` -> `js-in-process`).
160
+ - Added `.engine-badge.pkir-mode` badge styling in
161
+ `scripts/build-repl-html.mjs` and rebuilt `repl.html`.
162
+ - **Verification**:
163
+ - Verified evaluation of arithmetic, boolean logic, and lists via
164
+ PKIR-generated WebAssembly.
165
+ - Test suites (`tests/test-pkir.mjs`, `tests/test-repl.mjs`,
166
+ `tests/test-repl-html.mjs`, and `npm test`) pass with 100% green.
167
+
168
+ ### 2026-10-12: Integration into Performance Harnesses (`perf:*`)
169
+
170
+ - **Benchmark Integration**:
171
+ - Integrated `pkir` execution lane into:
172
+ - `scripts/perf-int.mjs`: Big Integer ops and 10-step squaring scale.
173
+ - `scripts/perf-ieee.mjs`: IEEE 754 float ops across 144 evaluations.
174
+ - `scripts/perf-multiplication.mjs`: Multi-backend squaring benchmark.
175
+ - `scripts/perf-poly.mjs`: Polymorphic list ops and macro tracing.
176
+ - Generates and compiles WebAssembly artifacts via
177
+ `compileWasmArtifactFromObject(rel.object, { usePKIR: true })`.
178
+ - **Target Backend Selection**:
179
+ - Added support for `BACKENDS=pkir`, `PKIR_ONLY=1`, and `--pkir-only`.
180
+ - When unrestricted, all backends (`llvm`, `wasm`, `pkir`, `kvm`, `js`)
181
+ execute concurrently for direct side-by-side comparison.
182
+ - **Convenience NPM Scripts (`package.json`)**:
183
+ - Added `perf:int:pkir`: `BACKENDS=pkir node scripts/perf-int.mjs`
184
+ - Added `perf:ieee:pkir`: `BACKENDS=pkir node scripts/perf-ieee.mjs`
185
+ - Added `perf:mult:pkir`:
186
+ `node scripts/perf-multiplication.mjs --backends pkir`
187
+ - Added `perf:poly:pkir`: `BACKENDS=pkir node scripts/perf-poly.mjs`
188
+ - **Verification**:
189
+ - All benchmarks verify 100% output conformance against native K
190
+ oracle values.
191
+ - Squaring benchmarks reach full 10/10 steps with matching outputs.
192
+
193
+ ### 2026-10-13: LLVM Backend Integration for PKIR
194
+
195
+ - **Compiler Lowering & CLI Integration**:
196
+ - Implemented `compileLLVMArtifactFromPKIR` and `{ usePKIR: true }` in
197
+ `backends/llvm/src/llvm.mjs` and `backends/llvm/src/executable.mjs`.
198
+ - Added CLI support (`--pkir` and `.pkir` input files) for
199
+ `k-llvm-compile`, `k-llvm-build`, and `k-wasm-compile`.
200
+ - Exported `compilePKIRToExecutable` and `compilePKIRAndRun` from
201
+ `backends/llvm/src/executable.mjs`.
202
+ - **Runtime & Slot Lowering Fixes**:
203
+ - Synthesized union helper `inputPattern`: resolved input pattern in
204
+ `union_expr` and `union_arm` from
205
+ `regPatterns.get(cleanReg(inst.src))`
206
+ so field access in arm functions targets correct physical slots.
207
+ - Open product field indexing in PKIR: restricted static physical slot
208
+ addressing in `getFieldEdgeIndex` to closed products when `usePKIR`
209
+ is active, falling back to dynamic `k_product_get_n` for polymorphic
210
+ open products to prevent physical index misalignment.
211
+ - Wire serialization for polymorphic outputs: implemented and exported
212
+ `k_encode_wire` via `derive_pattern(value)` in `krt.h` and `krt.c`,
213
+ with fallback in `executable.mjs` when static output pattern has
214
+ `KP_ANY`.
215
+ - Pattern graph ID 0 check: updated `compileObjectToLLVM` pattern
216
+ lookups to check `!== undefined` so node ID 0 is not treated as falsy.
217
+ - **Performance Harnesses & NPM Automation**:
218
+ - Added `llvm-pkir` backend lane across:
219
+ - `scripts/perf-multiplication.mjs` (`--backends llvm-pkir`)
220
+ - `scripts/perf-int.mjs` (`BACKENDS=llvm-pkir`)
221
+ - `scripts/perf-poly.mjs` (`BACKENDS=llvm-pkir`)
222
+ - `scripts/perf-ieee.mjs` (`BACKENDS=llvm-pkir`)
223
+ - Added npm scripts to `package.json`:
224
+ - `perf:mult:llvm-pkir`
225
+ - `perf:int:llvm-pkir`
226
+ - `perf:poly:llvm-pkir`
227
+ - `perf:ieee:llvm-pkir`
228
+ - **Verification**:
229
+ - Added PKIR LLVM lowering, compilation, and execution tests
230
+ (Tests 10, 11, 12 in `tests/test-pkir.mjs`).
231
+ - Verified 100% test pass on `npm test` and `npm run test:backends`.
232
+ - Verified 100% output conformance across all `perf:*` benchmarks.
233
+
234
+ ### 2026-10-13: Concurrent LLVM & LLVM-PKIR Benchmark Comparison
235
+
236
+ - **Motivation**:
237
+ - The previous harness integration allowed selecting either `llvm` or
238
+ `llvm-pkir` via `BACKENDS=llvm-pkir`, but did not run both
239
+ concurrently in default runs (`npm run perf:int`, etc.).
240
+ - Users expecting direct performance comparisons between the original
241
+ monomorphic LLVM backend and the new polymorphic PKIR LLVM backend
242
+ now see both lanes evaluated side-by-side by default.
243
+ - **Changes Across All Perf Harnesses**:
244
+ - `scripts/perf-int.mjs`: Separated `runLLVM` and `runLLVMPKIR` into
245
+ distinct lanes enabled by default. Both `LLVM` and `LLVM-PKIR` are
246
+ compiled, warmed, timed, and reported in the main integer benchmark
247
+ table and squaring scale benchmark.
248
+ - `scripts/perf-ieee.mjs`: Both `LLVM` and `LLVM-PKIR` executables are
249
+ compiled across specified optimization levels and reported side-by-
250
+ side in the results table and conformance validation.
251
+ - `scripts/perf-poly.mjs`: Both `LLVM` and `LLVM-PKIR` are benchmarked
252
+ side-by-side in default runs and supported in 7-phase execution
253
+ tracing (`--trace`).
254
+ - `scripts/perf-multiplication.mjs`: Added `llvm-pkir` to default
255
+ backend list (`llvm,llvm-pkir,wasm,pkir,kvm,js`) and respected
256
+ `BACKENDS` environment variable.
257
+
258
+ ---
259
+
260
+ ## Work Items & Progress
261
+
262
+ - [x] Branch creation (`transducer`)
263
+ - [x] Execution model & duality analysis documented
264
+ - [x] Initialize `JOURNAL.md`
265
+ - [x] Create `tests/test-polymorphic-tdd.mjs` test harness (passing)
266
+ - [x] Establish evaluator & kVM baseline passes
267
+ - [x] Design & implement `buildAccessTable` algorithm (`access-table.mjs`)
268
+ - [x] Create `tests/test-access-table.mjs` test suite (passing)
269
+ - [x] Formal PKIR specification (`DOCS/POLYMORPHIC_IR.md`)
270
+ - [x] Prototype `KVal` fat-pointer representation (`pkir.mjs`)
271
+ - [x] Implement PKIR compiler lowering (`compileRelationToPKIR`)
272
+ - [x] Implement PKIR execution engine (`executePKIR`)
273
+ - [x] Implement Monomorphic Specializer with dead branch elimination
274
+ - [x] Create `tests/test-pkir.mjs` test suite (passing)
275
+ - [x] Integrate with test runner (`scripts/run-tests.mjs`) & verify
276
+ - [x] Benchmark performance against previous work (AST & kVM baselines)
277
+ - [x] Integrate PKIR into Wasm lowering (`lowerPKIRToKVM`)
278
+ - [x] Enable PKIR in `repl.mjs` (`--pkir`, `:engine pkir`)
279
+ - [x] Enable PKIR in `repl.html` (3-way engine badge toggle)
280
+ - [x] Add PKIR to perf:* harnesses (perf:int, perf:ieee, perf:mult, poly)
281
+ - [x] Integrate PKIR into LLVM lowering (`compileLLVMArtifactFromPKIR`)
282
+ - [x] Add CLI support for PKIR in LLVM tools (`k-llvm-compile`, etc.)
283
+ - [x] Standalone executable compilation (`compilePKIRToExecutable`)
284
+ - [x] Integrate `llvm-pkir` into all `perf:*` benchmarks and
285
+ `package.json`
286
+ - [x] Fix polymorphic open product slot resolution and dynamic wire
287
+ encoding
288
+ - [x] Run both LLVM and LLVM-PKIR side-by-side by default in perf:* tests
package/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  **k is a small language for content-addressable schemas and data transformations.**
4
4
 
5
+ > [!TIP]
6
+ > **Live Web REPL (`repl.html`):** Try k online with zero install:
7
+ > [**https://fraczak.github.io/k/**](https://fraczak.github.io/k/)
8
+
5
9
  It describes data as algebraic schemas, programs as first-order partial
6
10
  relations, and runtime values as a self-describing binary `schema + payload`
7
11
  stream that can be parsed, transformed, printed, compiled, and inspected.
@@ -69,7 +73,9 @@ adds one successor. The REPL also prints the inferred value envelope.
69
73
 
70
74
  ### In the Browser (Zero Install)
71
75
 
72
- Try k directly in your browser without installing anything:
76
+ Try k directly in your browser without installing anything using the
77
+ live Web REPL (`repl.html`):
78
+
73
79
  **[https://fraczak.github.io/k/](https://fraczak.github.io/k/)**
74
80
 
75
81
  The Web REPL is self-contained, powered by an in-process WebAssembly
@@ -235,7 +241,7 @@ For a detailed guide to all four formats, commands, and workflows, see
235
241
  | `k-kir` | Export KIR-P from `.ko` or `.klib` |
236
242
  | `k-vm` | Execute or inspect lowered kVM bytecode artifacts (`.kvm`) |
237
243
  | `k-wasm` / `k-wasm-compile` / `k-wasm-run` | Compile and run via WebAssembly backend |
238
- | `k-llvm-build` / `k-llvm-compile` / `k-llvm-run` | Compile and run via LLVM backend |
244
+ | `k-llvm` / `k-llvm-build` / `k-llvm-compile` | Compile and run via LLVM backend |
239
245
 
240
246
  Installed binary names are `k-` plus the source basename without
241
247
  `.mjs`, except for `k.mjs` itself. Source names that already include
@@ -372,6 +378,8 @@ The suite covers:
372
378
 
373
379
  ## Further Reading
374
380
 
381
+ - [Web REPL (`repl.html`)](https://fraczak.github.io/k/) -
382
+ live in-browser interpreter (hosted on GitHub Pages)
375
383
  - [DOCS/FILE_FORMATS.md](DOCS/FILE_FORMATS.md) - guide to
376
384
  .k, .ko, .klib, and .kvm file formats and workflows
377
385
  - [DOCS/DICTIONARY.md](DOCS/DICTIONARY.md) - concept names and terminology
@@ -0,0 +1,343 @@
1
+ // access-table.mjs: State-Indexed Access Table for Polymorphic Execution
2
+ // Translates an input schema S into a compact, indexable navigation table
3
+ // allowing function instructions to access payload fields and variants
4
+ // without runtime string comparisons or dynamic dictionary lookups.
5
+
6
+ import { patternToPropertyList } from "./codecs/runtime/pattern-json.mjs";
7
+
8
+ /**
9
+ * Node descriptor for a single state in schema S.
10
+ */
11
+ export class NodeDescriptor {
12
+ constructor(state, kind) {
13
+ this.state = state;
14
+ this.kind = kind; // 'product', 'union', or 'any'
15
+ }
16
+ }
17
+
18
+ /**
19
+ * AccessTable encapsulates the complete navigation structure for a
20
+ * schema S with respect to a function's structural access profile P.
21
+ */
22
+ export class AccessTable {
23
+ constructor(descriptors, rootState = 0, wordSize = 8) {
24
+ this.descriptors = descriptors;
25
+ this.rootState = rootState;
26
+ this.wordSize = wordSize;
27
+ this.stateCount = descriptors.length;
28
+ }
29
+
30
+ getDescriptor(stateId) {
31
+ const desc = this.descriptors[stateId];
32
+ if (!desc) {
33
+ throw new Error(`AccessTable has no descriptor for state ${stateId}`);
34
+ }
35
+ return desc;
36
+ }
37
+
38
+ /**
39
+ * Resolves physical byte offset and child state for a logical
40
+ * field access.
41
+ */
42
+ resolveField(stateId, fieldNameOrSlot) {
43
+ const desc = this.getDescriptor(stateId);
44
+ if (desc.kind !== "product") {
45
+ throw new Error(`State ${stateId} is not a product (is ${desc.kind})`);
46
+ }
47
+
48
+ if (typeof fieldNameOrSlot === "string") {
49
+ const fieldMeta = desc.fields[fieldNameOrSlot];
50
+ if (!fieldMeta) {
51
+ throw new Error(
52
+ `Field '${fieldNameOrSlot}' not found in state ${stateId}`
53
+ );
54
+ }
55
+ return fieldMeta;
56
+ }
57
+
58
+ const slot = fieldNameOrSlot;
59
+ return {
60
+ byteOffset: desc.slotToByteOffset[slot],
61
+ childState: desc.slotToChildState[slot],
62
+ skipCount: desc.slotToSkipCount[slot]
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Resolves branch index and child state for a variant discriminator.
68
+ */
69
+ resolveVariant(stateId, tagOrId) {
70
+ const desc = this.getDescriptor(stateId);
71
+ if (desc.kind !== "union") {
72
+ throw new Error(`State ${stateId} is not a union (is ${desc.kind})`);
73
+ }
74
+
75
+ if (typeof tagOrId === "string") {
76
+ const tagMeta = desc.tags[tagOrId];
77
+ if (!tagMeta) {
78
+ throw new Error(`Variant '${tagOrId}' not found in state ${stateId}`);
79
+ }
80
+ return tagMeta;
81
+ }
82
+
83
+ const tagId = tagOrId;
84
+ return {
85
+ branchId: desc.branchMap[tagId],
86
+ childState: desc.childStateMap[tagId]
87
+ };
88
+ }
89
+
90
+ toJSON() {
91
+ return {
92
+ rootState: this.rootState,
93
+ wordSize: this.wordSize,
94
+ stateCount: this.stateCount,
95
+ descriptors: this.descriptors
96
+ };
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Normalizes a pattern, pattern graph, or property list into
102
+ * a canonical property list format.
103
+ */
104
+ function toPropertyList(patternOrGraph) {
105
+ if (Array.isArray(patternOrGraph)) {
106
+ return patternOrGraph;
107
+ }
108
+ if (patternOrGraph && Array.isArray(patternOrGraph.nodes)) {
109
+ return patternToPropertyList(patternOrGraph);
110
+ }
111
+ if (patternOrGraph?.patterns?.nodes) {
112
+ // TypePatternGraph from KIR / KVM
113
+ return patternOrGraph.patterns.nodes.map((node, nodeIdx) => {
114
+ let kind = "any";
115
+ if (node.pattern === "{}") kind = "closed-product";
116
+ else if (node.pattern === "{...}") kind = "open-product";
117
+ else if (node.pattern === "<>") kind = "closed-union";
118
+ else if (node.pattern === "<...>") kind = "open-union";
119
+ const edgeMap = patternOrGraph.edges?.[nodeIdx] || {};
120
+ const edges = (node.fields || []).map((f) => {
121
+ const dest = edgeMap[f];
122
+ const target = Array.isArray(dest)
123
+ ? dest[0]
124
+ : typeof dest === "object" && dest !== null
125
+ ? Object.values(dest)[0]
126
+ : (dest ?? 0);
127
+ return [f, target];
128
+ });
129
+ return [kind, edges];
130
+ });
131
+ }
132
+ throw new Error("Invalid pattern representation for access table generation");
133
+ }
134
+
135
+ /**
136
+ * Builds an Access Table bridging an Input Schema S to Function
137
+ * Pattern P.
138
+ *
139
+ * @param {Array|Object} schema - Concrete input value schema S
140
+ * @param {Array|Object} pattern - Function's input pattern graph P
141
+ * @param {Object} [options]
142
+ * @param {number} [options.wordSize=8] - Word size in bytes (default: 8)
143
+ * @returns {AccessTable}
144
+ */
145
+ export function buildAccessTable(schema, pattern, options = {}) {
146
+ const wordSize = options.wordSize ?? 8;
147
+ const sList = toPropertyList(schema);
148
+ const pList = toPropertyList(pattern);
149
+
150
+ const stateCount = sList.length;
151
+ const descriptors = new Array(stateCount);
152
+
153
+ const schemaRoot = options.schemaRoot ?? 0;
154
+ const patternRoot = options.patternRoot ?? 0;
155
+
156
+ // Simulation worklist: pairs of [stateInS, stateInP]
157
+ const visited = new Set();
158
+ const worklist = [[schemaRoot, patternRoot]];
159
+
160
+ while (worklist.length > 0) {
161
+ const [sId, pId] = worklist.shift();
162
+ const key = `${sId}:${pId}`;
163
+ if (visited.has(key)) continue;
164
+ visited.add(key);
165
+
166
+ const sNode = sList[sId];
167
+ const pNode = pList[pId];
168
+ if (!sNode || !pNode) continue;
169
+
170
+ const [sKind, sEdges] = sNode;
171
+ const [pKind, pEdges] = pNode;
172
+
173
+ const sIsUnion = sKind.includes("union");
174
+ const pIsUnion = pKind.includes("union");
175
+
176
+ if (sIsUnion !== pIsUnion && sKind !== "any" && pKind !== "any") {
177
+ throw new Error(
178
+ `Structural mismatch at S[${sId}] (${sKind}) vs P[${pId}] (${pKind})`
179
+ );
180
+ }
181
+
182
+ if (sIsUnion) {
183
+ // Sort variant tags in S alphabetically to define physical tag IDs
184
+ const sortedSEdges = [...sEdges].sort((a, b) =>
185
+ a[0].localeCompare(b[0])
186
+ );
187
+ const pEdgeMap = new Map(
188
+ pEdges.map(([label, target], idx) => [
189
+ label,
190
+ { target, branchIdx: idx }
191
+ ])
192
+ );
193
+
194
+ const tags = {};
195
+ const branchMap = [];
196
+ const childStateMap = [];
197
+
198
+ sortedSEdges.forEach(([tag, sTarget], tagId) => {
199
+ const pMatch = pEdgeMap.get(tag);
200
+ const branchId = pMatch ? pMatch.branchIdx : -1;
201
+ const pTarget = pMatch ? pMatch.target : null;
202
+
203
+ tags[tag] = {
204
+ tagId,
205
+ branchId,
206
+ childState: sTarget,
207
+ childPatternState: pTarget
208
+ };
209
+ branchMap[tagId] = branchId;
210
+ childStateMap[tagId] = sTarget;
211
+
212
+ if (pTarget != null && pTarget < pList.length) {
213
+ worklist.push([sTarget, pTarget]);
214
+ }
215
+ });
216
+
217
+ descriptors[sId] = {
218
+ state: sId,
219
+ kind: "union",
220
+ isOpen: sKind.startsWith("open"),
221
+ variantCount: sortedSEdges.length,
222
+ tags,
223
+ branchMap,
224
+ childStateMap
225
+ };
226
+ } else if (sKind.includes("product")) {
227
+ // Physical layout of fields in S: sorted by label
228
+ const sortedSEdges = [...sEdges].sort((a, b) =>
229
+ a[0].localeCompare(b[0])
230
+ );
231
+ const sFieldMap = new Map(
232
+ sortedSEdges.map(([label, target], idx) => [
233
+ label,
234
+ { target, physicalSlot: idx }
235
+ ])
236
+ );
237
+
238
+ const fields = {};
239
+ const slotToByteOffset = [];
240
+ const slotToChildState = [];
241
+ const slotToSkipCount = [];
242
+
243
+ pEdges.forEach(([field, pTarget], logicalSlot) => {
244
+ const sMatch = sFieldMap.get(field);
245
+ if (!sMatch) {
246
+ if (!pKind.startsWith("open")) {
247
+ throw new Error(`Required field '${field}' missing in S[${sId}]`);
248
+ }
249
+ return;
250
+ }
251
+
252
+ const physicalSlot = sMatch.physicalSlot;
253
+ const sTarget = sMatch.target;
254
+ const byteOffset = physicalSlot * wordSize;
255
+ const skipCount = physicalSlot;
256
+
257
+ fields[field] = {
258
+ logicalSlot,
259
+ physicalSlot,
260
+ byteOffset,
261
+ skipCount,
262
+ childState: sTarget,
263
+ childPatternState: pTarget
264
+ };
265
+
266
+ slotToByteOffset[logicalSlot] = byteOffset;
267
+ slotToChildState[logicalSlot] = sTarget;
268
+ slotToSkipCount[logicalSlot] = skipCount;
269
+
270
+ if (pTarget < pList.length) {
271
+ worklist.push([sTarget, pTarget]);
272
+ }
273
+ });
274
+
275
+ descriptors[sId] = {
276
+ state: sId,
277
+ kind: "product",
278
+ isOpen: sKind.startsWith("open"),
279
+ fieldCount: sortedSEdges.length,
280
+ fields,
281
+ slotToByteOffset,
282
+ slotToChildState,
283
+ slotToSkipCount
284
+ };
285
+ } else {
286
+ descriptors[sId] = {
287
+ state: sId,
288
+ kind: "any"
289
+ };
290
+ }
291
+ }
292
+
293
+ // Populate any unreached states in S as self-contained descriptors
294
+ for (let sId = 0; sId < stateCount; sId++) {
295
+ if (!descriptors[sId]) {
296
+ const [sKind, sEdges] = sList[sId];
297
+ if (sKind.includes("union")) {
298
+ const sorted = [...sEdges].sort((a, b) => a[0].localeCompare(b[0]));
299
+ descriptors[sId] = {
300
+ state: sId,
301
+ kind: "union",
302
+ isOpen: sKind.startsWith("open"),
303
+ variantCount: sorted.length,
304
+ tags: Object.fromEntries(
305
+ sorted.map(([t, target], i) => [
306
+ t,
307
+ { tagId: i, branchId: i, childState: target }
308
+ ])
309
+ ),
310
+ branchMap: sorted.map((_, i) => i),
311
+ childStateMap: sorted.map(([_, target]) => target)
312
+ };
313
+ } else if (sKind.includes("product")) {
314
+ const sorted = [...sEdges].sort((a, b) => a[0].localeCompare(b[0]));
315
+ descriptors[sId] = {
316
+ state: sId,
317
+ kind: "product",
318
+ isOpen: sKind.startsWith("open"),
319
+ fieldCount: sorted.length,
320
+ fields: Object.fromEntries(
321
+ sorted.map(([f, target], i) => [
322
+ f,
323
+ {
324
+ logicalSlot: i,
325
+ physicalSlot: i,
326
+ byteOffset: i * wordSize,
327
+ skipCount: i,
328
+ childState: target
329
+ }
330
+ ])
331
+ ),
332
+ slotToByteOffset: sorted.map((_, i) => i * wordSize),
333
+ slotToChildState: sorted.map(([_, target]) => target),
334
+ slotToSkipCount: sorted.map((_, i) => i)
335
+ };
336
+ } else {
337
+ descriptors[sId] = { state: sId, kind: "any" };
338
+ }
339
+ }
340
+ }
341
+
342
+ return new AccessTable(descriptors, schemaRoot, wordSize);
343
+ }
package/backend-api.mjs CHANGED
@@ -27,3 +27,21 @@ export {
27
27
  isIdentityIntrinsic,
28
28
  unsupportedIntrinsic
29
29
  } from "./intrinsics.mjs";
30
+ export { buildAccessTable, AccessTable } from "./access-table.mjs";
31
+ export {
32
+ PKIR_FORMAT,
33
+ PKIR_VERSION,
34
+ compileRelationToPKIR,
35
+ executePKIR,
36
+ specializePKIR,
37
+ valueToKVal,
38
+ kvalToValue,
39
+ lowerPKIRToKVM
40
+ } from "./pkir.mjs";
41
+ export {
42
+ compileWasmArtifactFromPKIR
43
+ } from "./backends/wasm/src/wasm.mjs";
44
+ export {
45
+ compileLLVMArtifactFromPKIR
46
+ } from "./backends/llvm/src/llvm.mjs";
47
+