jz 0.4.0 → 0.5.1
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/README.md +275 -255
- package/cli.js +8 -51
- package/index.js +166 -31
- package/{src/host.js → interop.js} +291 -88
- package/module/array.js +181 -71
- package/module/collection.js +222 -8
- package/module/console.js +1 -1
- package/module/core.js +277 -118
- package/module/date.js +9 -5
- package/module/function.js +11 -1
- package/module/json.js +361 -55
- package/module/math.js +601 -130
- package/module/number.js +390 -227
- package/module/object.js +252 -34
- package/module/regex.js +123 -16
- package/module/schema.js +15 -1
- package/module/string.js +545 -96
- package/module/timer.js +1 -1
- package/module/typedarray.js +674 -57
- package/package.json +10 -7
- package/src/abi/array.js +89 -0
- package/src/abi/index.js +35 -0
- package/src/abi/number.js +137 -0
- package/src/abi/object.js +57 -0
- package/src/abi/string.js +529 -0
- package/src/analyze.js +1870 -485
- package/src/assemble.js +27 -12
- package/src/autoload.js +13 -1
- package/src/compile.js +446 -179
- package/src/ctx.js +85 -18
- package/src/emit.js +705 -196
- package/src/infer.js +548 -0
- package/src/ir.js +291 -23
- package/src/jzify.js +851 -132
- package/src/narrow.js +433 -251
- package/src/optimize.js +126 -122
- package/src/plan.js +703 -34
- package/src/prepare.js +833 -153
- package/src/resolve.js +85 -0
- package/src/vectorize.js +99 -18
- package/src/ast.js +0 -160
- package/src/auto-config.js +0 -120
- package/src/fuse.js +0 -159
package/package.json
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jz",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.5.1",
|
|
4
|
+
"description": "Functional JS subset compiling to WASM",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./index.js",
|
|
9
|
-
"./wasi": "./wasi.js"
|
|
9
|
+
"./wasi": "./wasi.js",
|
|
10
|
+
"./interop": "./interop.js"
|
|
10
11
|
},
|
|
11
12
|
"files": [
|
|
12
13
|
"index.js",
|
|
13
14
|
"cli.js",
|
|
14
15
|
"wasi.js",
|
|
16
|
+
"interop.js",
|
|
15
17
|
"src",
|
|
16
18
|
"module",
|
|
17
19
|
"README.md"
|
|
@@ -26,8 +28,9 @@
|
|
|
26
28
|
"bench": "node bench/bench.mjs",
|
|
27
29
|
"bench:size": "node scripts/bench-size.mjs",
|
|
28
30
|
"bench:compile": "node scripts/bench-compile.mjs",
|
|
29
|
-
"
|
|
30
|
-
"test:
|
|
31
|
+
"build:examples": "for dir in examples/*; do node \"$dir/build.mjs\"; done",
|
|
32
|
+
"test:bench": "node test/bench.js",
|
|
33
|
+
"test:all": "npm test && npm run test:bench",
|
|
31
34
|
"prepublishOnly": "npm run test:all"
|
|
32
35
|
},
|
|
33
36
|
"repository": {
|
|
@@ -41,8 +44,8 @@
|
|
|
41
44
|
"author": "Dmitry Iv",
|
|
42
45
|
"license": "MIT",
|
|
43
46
|
"dependencies": {
|
|
44
|
-
"subscript": "^10.4.
|
|
45
|
-
"watr": "^4.6.
|
|
47
|
+
"subscript": "^10.4.15",
|
|
48
|
+
"watr": "^4.6.9"
|
|
46
49
|
},
|
|
47
50
|
"keywords": [
|
|
48
51
|
"javascript",
|
package/src/abi/array.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/abi/array — ARRAY backing-store carriers.
|
|
3
|
+
*
|
|
4
|
+
* An ARRAY value is an `__alloc_hdr` block — `[-8:len(i32)][-4:cap(i32)]` then
|
|
5
|
+
* N element cells. The carrier owns where element `i` lives and how wide each
|
|
6
|
+
* cell is. Every array-element access routed through `ctx.abi.array.ops` reads
|
|
7
|
+
* layout from one place: the `[…]` literal store, the elem-schema fast-path
|
|
8
|
+
* read, the `ir.js` element helpers (`slotAddr`/`elemLoad`/`elemStore`, and so
|
|
9
|
+
* `arrayLoop`), the boxed-handle inner-array load.
|
|
10
|
+
*
|
|
11
|
+
* Ops are cycle-safe (no `src/*` imports) and return raw wasm IR arrays;
|
|
12
|
+
* callers wrap in `typed()`. `base` is an i32 IR node holding the array's data
|
|
13
|
+
* offset (post-header — a `['local.get', …]`, a `['local.tee', …]`, or a
|
|
14
|
+
* `['call', '$__ptr_offset', …]` that follows the grow-forwarding chain). `idx`
|
|
15
|
+
* is either a JS integer (constant slot — folds the multiply) or an i32 IR node
|
|
16
|
+
* (runtime index).
|
|
17
|
+
*
|
|
18
|
+
* taggedLinear — today's layout: every element one 8-byte f64 cell. An
|
|
19
|
+
* element holds a raw f64 (Array<NUMBER>) or a NaN-boxed
|
|
20
|
+
* pointer (Array<OBJECT|STRING>); both are 8 bytes wide, so
|
|
21
|
+
* one carrier serves every plain `[]`.
|
|
22
|
+
*
|
|
23
|
+
* structInline — SRoA layout for an `Array<{uniform K-field schema}>` whose
|
|
24
|
+
* element pointers never escape as object identities: the K
|
|
25
|
+
* f64 schema fields are inlined into the data region (stride
|
|
26
|
+
* K), so `rows[i].x` is one direct `f64.load` with no per-row
|
|
27
|
+
* object allocation and no pointer indirection. `len`/`cap`
|
|
28
|
+
* still count *physical* f64 cells, so every stride-8 helper
|
|
29
|
+
* (`__alloc_hdr`, `__arr_grow`, `__len`, `__set_len`) is
|
|
30
|
+
* reused untouched — `.push` of a struct writes K cells and
|
|
31
|
+
* adds K to `len`; `.length` divides the physical len by K.
|
|
32
|
+
* Picked per-schema by `analyzeStructInline` (src/analyze.js),
|
|
33
|
+
* whole-program, default-disqualify.
|
|
34
|
+
*
|
|
35
|
+
* Typed arrays (`Float64Array`/`Int32Array`/…) are a distinct value type
|
|
36
|
+
* (`VAL.TYPED`, ctor-determined stride) handled by `module/typedarray.js`, not
|
|
37
|
+
* a carrier here — they are not `VAL.ARRAY`.
|
|
38
|
+
*
|
|
39
|
+
* @module src/abi/array
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
// Byte address of element `idx` off an i32 base. A JS-integer idx folds the
|
|
43
|
+
// `*8` (and idx=0 returns base untouched — matches `slotAddr`); an IR-node idx
|
|
44
|
+
// emits the runtime `i32.shl … 3`.
|
|
45
|
+
const addr = (base, idx) =>
|
|
46
|
+
typeof idx === 'number'
|
|
47
|
+
? (idx === 0 ? base : ['i32.add', base, ['i32.const', idx * 8]])
|
|
48
|
+
: ['i32.add', base, ['i32.shl', idx, ['i32.const', 3]]]
|
|
49
|
+
|
|
50
|
+
export const taggedLinear = {
|
|
51
|
+
// Element operations the compiler routes array access through.
|
|
52
|
+
ops: {
|
|
53
|
+
// Byte address of element `idx` — for callers that need the address
|
|
54
|
+
// itself (`slotAddr`) rather than a load/store.
|
|
55
|
+
addr,
|
|
56
|
+
|
|
57
|
+
// Element `idx` read as the canonical f64 value.
|
|
58
|
+
load: (base, idx) => ['f64.load', addr(base, idx)],
|
|
59
|
+
|
|
60
|
+
// Element `idx` read as raw i64 bits — moves a slot without reinterpreting
|
|
61
|
+
// through f64.
|
|
62
|
+
loadBits: (base, idx) => ['i64.load', addr(base, idx)],
|
|
63
|
+
|
|
64
|
+
// Element `idx` write of an f64-typed value IR node.
|
|
65
|
+
store: (base, idx, val) => ['f64.store', addr(base, idx), val],
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// structInline(K) — SRoA carrier factory. Logical element `idx` occupies K
|
|
70
|
+
// consecutive 8-byte cells; the carrier hands back the byte address of the
|
|
71
|
+
// element's first cell, and field `f` is then a plain `+f*8` composed by the
|
|
72
|
+
// schema slot machinery (`ctx.abi.object.ops`). Built on demand per schema
|
|
73
|
+
// (`K = ctx.schema.list[sid].length`) — not a `ctx.abi` default.
|
|
74
|
+
export const structInline = (K) => ({
|
|
75
|
+
K,
|
|
76
|
+
ops: {
|
|
77
|
+
// Byte address of logical element `idx`'s first cell off i32 `base`.
|
|
78
|
+
// A JS-integer idx folds to a constant; an IR-node idx emits `idx*K`
|
|
79
|
+
// then the `<<3` (via `addr`).
|
|
80
|
+
elemAddr: (base, idx) =>
|
|
81
|
+
typeof idx === 'number'
|
|
82
|
+
? addr(base, idx * K)
|
|
83
|
+
: addr(base, K === 1 ? idx : ['i32.mul', idx, ['i32.const', K]]),
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// Default carrier — picked when the narrower has no stronger evidence.
|
|
88
|
+
// Reached via `ctx.abi.array`.
|
|
89
|
+
export default taggedLinear
|
package/src/abi/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/abi — internal codegen carriers.
|
|
3
|
+
*
|
|
4
|
+
* The `abi/` directory hosts compiler-internal codegen modules — one file per
|
|
5
|
+
* value type, each exporting every carrier (slot strategy) the compiler may
|
|
6
|
+
* pick for that type. **No user surface.** `opts.host` is the only knob users
|
|
7
|
+
* see; internal representation is analysis-driven and per-site.
|
|
8
|
+
*
|
|
9
|
+
* Today the narrower has not yet been wired to pick carriers per site, so
|
|
10
|
+
* each type module's `default` export is used as the carrier for every site
|
|
11
|
+
* of that type. `ctx.abi.<type>` resolves to that default; codegen reads
|
|
12
|
+
* `ctx.abi.string.ops.byteLen(...)` etc. Per-site dispatch arrives by
|
|
13
|
+
* exposing all carriers (`ctx.abi.string.sso`, `.jsstring`) and letting
|
|
14
|
+
* `narrow.js` tag each binding with a carrier choice.
|
|
15
|
+
*
|
|
16
|
+
* No presets, no preset-name discriminant, no public ABI knob — carrier
|
|
17
|
+
* choice is analysis-driven, not user-pickable. See `.work/todo.md`
|
|
18
|
+
* "Boundary protocol and internal representation" for the policy.
|
|
19
|
+
*
|
|
20
|
+
* @module src/abi
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import nanboxF64 from './number.js'
|
|
24
|
+
import sso, { jsstring } from './string.js'
|
|
25
|
+
import tagged from './object.js'
|
|
26
|
+
import taggedLinear, { structInline } from './array.js'
|
|
27
|
+
|
|
28
|
+
/** The default carrier bundle — what `ctx.abi` resolves to. Identity-stable
|
|
29
|
+
* reference so codegen can compare without string keys. */
|
|
30
|
+
export const DEFAULTS = Object.freeze({ number: nanboxF64, string: sso, object: tagged, array: taggedLinear })
|
|
31
|
+
|
|
32
|
+
// Carrier re-exports — for tests and tools that want to reach a specific
|
|
33
|
+
// carrier directly. Per-site narrowing will use these via `ctx.abi.<type>`
|
|
34
|
+
// once the narrower exposes the full carrier dictionary.
|
|
35
|
+
export { nanboxF64, sso, jsstring, tagged, taggedLinear, structInline }
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/abi/number — number carriers.
|
|
3
|
+
*
|
|
4
|
+
* One file holds every strategy the compiler may pick for a number-typed
|
|
5
|
+
* binding. Today `nanboxF64` is the only carrier; flat-i32 / flat-f64
|
|
6
|
+
* specialization is achieved through direct type narrowing in `narrow.js`
|
|
7
|
+
* (Phase E i32 results, Phase E3 pointer results, locals/params lifted by
|
|
8
|
+
* the fixpoint) rather than via a carrier-bundle dispatch. The single-carrier
|
|
9
|
+
* `ctx.abi` infrastructure is scaffold for the architectural cleanup that
|
|
10
|
+
* would migrate those narrowed cases into named carriers here. See
|
|
11
|
+
* `.work/todo.md` "Boundary protocol and internal representation".
|
|
12
|
+
*
|
|
13
|
+
* Carriers:
|
|
14
|
+
* - `nanboxF64` default. f64 carrier with NaN-boxed pointers. Owns the
|
|
15
|
+
* NaN-box-layout peephole folds — pure-WASM equivalences
|
|
16
|
+
* (`wrap(extend x)→x`, `trunc(convert x)→x`, …) stay in
|
|
17
|
+
* `src/optimize.js`; the folds here assume the low 32 bits
|
|
18
|
+
* of a NaN-boxed f64 are the pointer offset.
|
|
19
|
+
*
|
|
20
|
+
* No `name`/`type` discriminant field — carriers are referenced by object
|
|
21
|
+
* identity from the default-bundle in `src/abi/index.js`.
|
|
22
|
+
*
|
|
23
|
+
* @module src/abi/number
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
// ── nanboxF64 ─────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
export const nanboxF64 = {
|
|
29
|
+
slotTypes: ['f64'],
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Carrier-specific peephole rules — folds that depend on the NaN-box layout.
|
|
33
|
+
* Called by the optimizer's fused-rewrite walk after generic folds; returns
|
|
34
|
+
* a replacement node or `null` to leave the node unchanged.
|
|
35
|
+
*
|
|
36
|
+
* The folds:
|
|
37
|
+
* 1. `i64.reinterpret_f64 (f64.reinterpret_i64 x)` → `x` — rebox-undo.
|
|
38
|
+
* Re-introduced by watr's inliner at boundaries (caller's `boxPtrIR(g)`
|
|
39
|
+
* meets callee's `i32.wrap_i64 (i64.reinterpret_f64 __env)`), so the
|
|
40
|
+
* post-watr reopt pass needs this to keep nanbox call boundaries clean.
|
|
41
|
+
* 2. `f64.reinterpret_i64 (i64.reinterpret_f64 x)` → `x` — unbox-undo.
|
|
42
|
+
* 3. `i32.wrap_i64 (i64.reinterpret_f64 (f64.load A ?off))` → `i32.load A ?off`.
|
|
43
|
+
* Wasm is little-endian; the low 32 bits of a NaN-boxed f64 at A are
|
|
44
|
+
* exactly i32.load(A). Saves two ops on every pointer extraction from
|
|
45
|
+
* an array slot or struct field.
|
|
46
|
+
* 4. `i32.wrap_i64 (i64.reinterpret_f64 (call $__mkptr* … offset))` → offset.
|
|
47
|
+
* A NaN-boxed pointer keeps type/aux in the high bits and the i32
|
|
48
|
+
* offset in the low 32, so the f64 round-trip is pure overhead when the
|
|
49
|
+
* consumer only wants the offset. Covers generic `$__mkptr` and
|
|
50
|
+
* specialized `$__mkptr_T_A_d` trampolines (offset is the last arg).
|
|
51
|
+
* 5. As (4) but reaching through `(block (result f64) … (call $__mkptr …))`
|
|
52
|
+
* — `new TypedArray(n)` lowers to this shape — by retyping the block
|
|
53
|
+
* to i32 and dropping the box on its tail.
|
|
54
|
+
* 6. `i32.wrap_i64 (i64.or HIGH_ONLY (i64.extend_i32_* X))` → X.
|
|
55
|
+
* The NaN-tag-or-extend pattern: high bits hold the NaN prefix + tag,
|
|
56
|
+
* low 32 bits carry the i32 offset unchanged.
|
|
57
|
+
*/
|
|
58
|
+
peephole(node) {
|
|
59
|
+
const op = node[0]
|
|
60
|
+
|
|
61
|
+
if (op === 'i64.reinterpret_f64' && node.length === 2) {
|
|
62
|
+
const a = node[1]
|
|
63
|
+
if (Array.isArray(a) && a[0] === 'f64.reinterpret_i64' && a.length === 2) return a[1]
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (op === 'f64.reinterpret_i64' && node.length === 2) {
|
|
68
|
+
const a = node[1]
|
|
69
|
+
if (Array.isArray(a) && a[0] === 'i64.reinterpret_f64' && a.length === 2) return a[1]
|
|
70
|
+
return null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (op === 'i32.wrap_i64' && node.length === 2) {
|
|
74
|
+
const a = node[1]
|
|
75
|
+
|
|
76
|
+
if (Array.isArray(a) && a[0] === 'i64.reinterpret_f64' && a.length === 2) {
|
|
77
|
+
const inner = a[1]
|
|
78
|
+
// (3) wrap(reinterpret(f64.load ADDR ?offset)) → (i32.load ADDR ?offset)
|
|
79
|
+
if (Array.isArray(inner) && inner[0] === 'f64.load') {
|
|
80
|
+
const out = ['i32.load']
|
|
81
|
+
for (let i = 1; i < inner.length; i++) out.push(inner[i])
|
|
82
|
+
return out
|
|
83
|
+
}
|
|
84
|
+
// (4) wrap(reinterpret(call $__mkptr* … offset)) → offset
|
|
85
|
+
if (isMkptr(inner)) return inner[inner.length - 1]
|
|
86
|
+
// (5) reach through (block (result f64) … (call $__mkptr*))
|
|
87
|
+
if (Array.isArray(inner) && inner[0] === 'block' && isMkptr(inner[inner.length - 1])) {
|
|
88
|
+
let ri = -1
|
|
89
|
+
for (let i = 1; i <= 2 && i < inner.length; i++) {
|
|
90
|
+
if (Array.isArray(inner[i]) && inner[i][0] === 'result') { ri = i; break }
|
|
91
|
+
}
|
|
92
|
+
if (ri >= 0 && inner[ri][1] === 'f64') {
|
|
93
|
+
const tail = inner[inner.length - 1]
|
|
94
|
+
const nb = inner.slice()
|
|
95
|
+
nb[ri] = ['result', 'i32']
|
|
96
|
+
nb[nb.length - 1] = tail[tail.length - 1]
|
|
97
|
+
return nb
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return null
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// (6) wrap(or HIGH_ONLY (extend X)) → X
|
|
104
|
+
if (Array.isArray(a) && a[0] === 'i64.or' && a.length === 3) {
|
|
105
|
+
const l = a[1], r = a[2]
|
|
106
|
+
if (isHighOnly(l) && isExtend(r)) return r[1]
|
|
107
|
+
if (isHighOnly(r) && isExtend(l)) return l[1]
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return null
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ── helpers (carrier-local; cycle-safe — no src/* import) ─────────────────
|
|
116
|
+
|
|
117
|
+
const isMkptr = (n) => Array.isArray(n) && n[0] === 'call' && typeof n[1] === 'string'
|
|
118
|
+
&& (n[1] === '$__mkptr' || n[1].startsWith('$__mkptr_'))
|
|
119
|
+
|
|
120
|
+
const isExtend = (n) => Array.isArray(n) &&
|
|
121
|
+
(n[0] === 'i64.extend_i32_u' || n[0] === 'i64.extend_i32_s') && n.length === 2
|
|
122
|
+
|
|
123
|
+
const isHighOnly = (n) => {
|
|
124
|
+
if (!Array.isArray(n) || n[0] !== 'i64.const') return false
|
|
125
|
+
const v = n[1]
|
|
126
|
+
let bi
|
|
127
|
+
if (typeof v === 'number') bi = BigInt(v)
|
|
128
|
+
else if (typeof v === 'string') {
|
|
129
|
+
try { bi = v.startsWith('-') ? -BigInt(v.slice(1)) : BigInt(v) } catch { return false }
|
|
130
|
+
} else return false
|
|
131
|
+
return (bi & 0xFFFFFFFFn) === 0n
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Default carrier — picked when narrower has no stronger evidence. Reached
|
|
135
|
+
// via `ctx.abi.number` (which the default-bundle in `src/abi/index.js` binds
|
|
136
|
+
// to this export).
|
|
137
|
+
export default nanboxF64
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/abi/object — OBJECT layout carriers.
|
|
3
|
+
*
|
|
4
|
+
* An OBJECT value is `__alloc_hdr` + N field cells; the carrier owns where
|
|
5
|
+
* field `i` lives and how wide each cell is. Every object-field access in the
|
|
6
|
+
* compiler — literal stores, `.prop` / `obj['k']` reads & writes, the bulk
|
|
7
|
+
* copies in `Object.values`/`entries`/`assign`/`create` and object spread,
|
|
8
|
+
* JSON-const objects, boxed-handle slot 0, `toPrimitive` method slots —
|
|
9
|
+
* routes through `ctx.abi.object.ops` so layout lives in one place.
|
|
10
|
+
*
|
|
11
|
+
* Ops are cycle-safe (no `src/*` imports) and return raw wasm IR arrays;
|
|
12
|
+
* callers wrap in `typed()`. `base` is an i32 IR node holding the object's
|
|
13
|
+
* data offset (a `['local.get', …]` or a `ptrOffsetIR(…)` expression).
|
|
14
|
+
*
|
|
15
|
+
* tagged — today's layout: schema-tagged, every field one 8-byte f64 cell.
|
|
16
|
+
*
|
|
17
|
+
* A future `packed` carrier (narrowing per-field width from
|
|
18
|
+
* `ctx.schema.slotIntCertain` / `slotTypes`) slots in here without touching a
|
|
19
|
+
* single call site — that is the seam this file exists to provide.
|
|
20
|
+
*
|
|
21
|
+
* `flat` (SRoA) is the third object strategy but lives outside this seam: a
|
|
22
|
+
* non-escaping `let/const o = {staticLiteral}` binding has no heap presence at
|
|
23
|
+
* all — its fields are dissolved into plain WASM locals (`o#0`, `o#1`, …) and
|
|
24
|
+
* `o.prop` compiles to `local.get`. It carries no memory base, so it cannot be
|
|
25
|
+
* expressed as `ops.load(base, i)`; it is a binding-dissolution transform
|
|
26
|
+
* driven by `scanFlatObjects` (src/analyze.js) and the codegen flat hooks
|
|
27
|
+
* (emitDecl, the `.`/`[]` read & write emitters), not a layout carrier here.
|
|
28
|
+
*
|
|
29
|
+
* @module src/abi/object
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
// Byte address of field `i` off an i32 base. idx=0 returns the base node
|
|
33
|
+
// untouched — matches `slotAddr` so routed sites stay byte-identical.
|
|
34
|
+
const addr = (base, i) => i === 0 ? base : ['i32.add', base, ['i32.const', i * 8]]
|
|
35
|
+
|
|
36
|
+
export const tagged = {
|
|
37
|
+
// Field operations the compiler routes object access through.
|
|
38
|
+
ops: {
|
|
39
|
+
// Heap cells `__alloc_hdr` must reserve for an N-field object. Floored at
|
|
40
|
+
// 1 so a zero-field object still owns a header-addressable cell.
|
|
41
|
+
allocSlots: (n) => Math.max(1, n),
|
|
42
|
+
|
|
43
|
+
// Field `i` read as the canonical f64 value.
|
|
44
|
+
load: (base, i) => ['f64.load', addr(base, i)],
|
|
45
|
+
|
|
46
|
+
// Field `i` read as raw i64 bits — dyn-shadow writes and cross-schema
|
|
47
|
+
// copies move slots without reinterpreting through f64.
|
|
48
|
+
loadBits: (base, i) => ['i64.load', addr(base, i)],
|
|
49
|
+
|
|
50
|
+
// Field `i` write of an f64-typed value IR node.
|
|
51
|
+
store: (base, i, val) => ['f64.store', addr(base, i), val],
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Default carrier — picked when the narrower has no stronger evidence.
|
|
56
|
+
// Reached via `ctx.abi.object`.
|
|
57
|
+
export default tagged
|