jz 0.5.1 → 0.6.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/README.md +288 -314
- package/bench/README.md +319 -0
- package/bench/bench.svg +112 -0
- package/cli.js +32 -24
- package/index.js +177 -55
- package/interop.js +88 -159
- package/jz.svg +5 -0
- package/jzify/arguments.js +97 -0
- package/jzify/bundler.js +382 -0
- package/jzify/classes.js +328 -0
- package/jzify/hoist-vars.js +177 -0
- package/jzify/index.js +51 -0
- package/jzify/names.js +37 -0
- package/jzify/switch.js +106 -0
- package/jzify/transform.js +349 -0
- package/layout.js +179 -0
- package/module/array.js +322 -153
- package/module/collection.js +603 -145
- package/module/console.js +55 -43
- package/module/core.js +266 -153
- package/module/date.js +15 -3
- package/module/function.js +73 -6
- package/module/index.js +2 -1
- package/module/json.js +226 -61
- package/module/math.js +414 -186
- package/module/number.js +306 -60
- package/module/object.js +448 -184
- package/module/regex.js +255 -25
- package/module/schema.js +24 -6
- package/module/simd.js +85 -0
- package/module/string.js +586 -220
- package/module/symbol.js +1 -1
- package/module/timer.js +9 -14
- package/module/typedarray.js +45 -48
- package/package.json +41 -12
- package/src/abi/index.js +39 -23
- package/src/abi/string.js +38 -41
- package/src/ast.js +460 -0
- package/src/autoload.js +26 -24
- package/src/bridge.js +111 -0
- package/src/compile/analyze-scans.js +661 -0
- package/src/compile/analyze.js +1565 -0
- package/src/compile/emit-assign.js +408 -0
- package/src/compile/emit.js +3201 -0
- package/src/compile/flow-types.js +103 -0
- package/src/{compile.js → compile/index.js} +497 -125
- package/src/{infer.js → compile/infer.js} +27 -98
- package/src/{narrow.js → compile/narrow.js} +302 -96
- package/src/compile/plan/advise.js +316 -0
- package/src/compile/plan/common.js +150 -0
- package/src/compile/plan/index.js +118 -0
- package/src/compile/plan/inline.js +679 -0
- package/src/compile/plan/literals.js +984 -0
- package/src/compile/plan/loops.js +472 -0
- package/src/compile/plan/scope.js +573 -0
- package/src/compile/program-facts.js +404 -0
- package/src/ctx.js +176 -58
- package/src/ir.js +540 -171
- package/src/kind-traits.js +105 -0
- package/src/kind.js +462 -0
- package/src/op-policy.js +57 -0
- package/src/{optimize.js → optimize/index.js} +1106 -446
- package/src/optimize/vectorize.js +1874 -0
- package/src/param-reps.js +65 -0
- package/src/parse.js +44 -0
- package/src/{prepare.js → prepare/index.js} +589 -202
- package/src/reps.js +115 -0
- package/src/resolve.js +12 -3
- package/src/static.js +199 -0
- package/src/type.js +647 -0
- package/src/{assemble.js → wat/assemble.js} +86 -48
- package/src/wat/optimize.js +3760 -0
- package/transform.js +21 -0
- package/wasi.js +47 -5
- package/src/analyze.js +0 -3818
- package/src/emit.js +0 -3040
- package/src/jzify.js +0 -1580
- package/src/plan.js +0 -2132
- package/src/vectorize.js +0 -1088
- /package/src/{codegen.js → wat/codegen.js} +0 -0
package/module/object.js
CHANGED
|
@@ -7,12 +7,38 @@
|
|
|
7
7
|
* @module object
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { typed, asF64, asI64, temp, tempI32, allocPtr, needsDynShadow, mkPtrIR, extractF64Bits, appendStaticSlots, slotAddr, elemLoad, elemStore } from '../src/ir.js'
|
|
11
|
-
import { emit } from '../src/
|
|
12
|
-
import { valTypeOf,
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
10
|
+
import { typed, asF64, asI64, NULL_NAN, UNDEF_NAN, temp, tempI32, tempI64, block64, ptrTypeEq, dispatchByPtrType, allocPtr, needsDynShadow, mkPtrIR, extractF64Bits, appendStaticSlots, slotAddr, elemLoad, elemStore, boolBoxIR } from '../src/ir.js'
|
|
11
|
+
import { emit } from '../src/bridge.js'
|
|
12
|
+
import { valTypeOf, shapeOf } from '../src/kind.js'
|
|
13
|
+
import { VAL, lookupValType, repOf, updateRep } from '../src/reps.js'
|
|
14
|
+
import { ctx, err, inc, PTR, LAYOUT, declGlobal } from '../src/ctx.js'
|
|
15
15
|
|
|
16
|
+
// Object.prototype.toString tag per value category. Matches what JS engines
|
|
17
|
+
// return for primitive/built-in types; canonicalized from
|
|
18
|
+
// `Object.prototype.toString.call(x)` by jzify (see jzify/bundler.js).
|
|
19
|
+
const OBJECT_TO_STRING_TAGS = {
|
|
20
|
+
[VAL.NUMBER]: '[object Number]',
|
|
21
|
+
[VAL.BIGINT]: '[object BigInt]',
|
|
22
|
+
[VAL.BOOL]: '[object Boolean]',
|
|
23
|
+
[VAL.STRING]: '[object String]',
|
|
24
|
+
[VAL.ARRAY]: '[object Array]',
|
|
25
|
+
[VAL.OBJECT]: '[object Object]',
|
|
26
|
+
[VAL.HASH]: '[object Object]',
|
|
27
|
+
[VAL.SET]: '[object Set]',
|
|
28
|
+
[VAL.MAP]: '[object Map]',
|
|
29
|
+
[VAL.CLOSURE]: '[object Function]',
|
|
30
|
+
[VAL.REGEX]: '[object RegExp]',
|
|
31
|
+
[VAL.DATE]: '[object Date]',
|
|
32
|
+
[VAL.BUFFER]: '[object ArrayBuffer]',
|
|
33
|
+
[VAL.TYPED]: '[object Object]',
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const objectToStringTagForVal = (obj) => {
|
|
37
|
+
const val = typeof obj === 'string' ? lookupValType(obj) : valTypeOf(obj)
|
|
38
|
+
return val ? OBJECT_TO_STRING_TAGS[val] : null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const storedValue = (node) => valTypeOf(node) === VAL.BOOL ? boolBoxIR(emit(node)) : asF64(emit(node))
|
|
16
42
|
|
|
17
43
|
export default (ctx) => {
|
|
18
44
|
inc('__mkptr', '__alloc', '__alloc_hdr', '__ptr_offset', '__len', '__ptr_type')
|
|
@@ -54,11 +80,21 @@ export default (ctx) => {
|
|
|
54
80
|
if (Array.isArray(p) && p[0] === ':') { names.push(p[1]); values.push(p[2]) }
|
|
55
81
|
}
|
|
56
82
|
|
|
57
|
-
// Use variable's merged schema if available (from Object.assign inference),
|
|
58
|
-
|
|
83
|
+
// Use variable's merged schema if available (from Object.assign inference),
|
|
84
|
+
// else register the literal's own schema. The merged schema is adopted only
|
|
85
|
+
// when it is a *superset* of the literal's own fields — a legitimate
|
|
86
|
+
// accumulation (`let o = {}; o.x = …`) always contains every literal key.
|
|
87
|
+
// A merged schema missing any literal field is a stale cross-function name
|
|
88
|
+
// collision (ctx.schema.vars is module-global, keyed by bare name): adopting
|
|
89
|
+
// it would size the alloc to the wrong schema and overflow the object's
|
|
90
|
+
// slots, corrupting adjacent heap. The literal is authoritative for its own
|
|
91
|
+
// shape, so re-bind the variable to it for precise same-function reads.
|
|
92
|
+
const litId = ctx.schema.register(names)
|
|
93
|
+
let schemaId = litId
|
|
59
94
|
if (target) {
|
|
60
95
|
const merged = ctx.schema.resolve(target)
|
|
61
|
-
if (merged) schemaId = ctx.schema.idOf(target)
|
|
96
|
+
if (merged && names.every(n => merged.includes(n))) schemaId = ctx.schema.idOf(target)
|
|
97
|
+
else if (names.length) ctx.schema.vars.set(target, litId)
|
|
62
98
|
}
|
|
63
99
|
const schema = ctx.schema.list[schemaId]
|
|
64
100
|
const t = tempI32('obj')
|
|
@@ -67,19 +103,35 @@ export default (ctx) => {
|
|
|
67
103
|
// R: Static data segment for objects of pure-literal property values (own-memory only).
|
|
68
104
|
// Even with shadow needed, we can skip alloc + N stores; just feed literal values to __dyn_set.
|
|
69
105
|
const shadow = needsDynShadow(target)
|
|
70
|
-
|
|
71
|
-
|
|
106
|
+
// When the literal adopts a superset/merged schema (schemaId !== litId), the
|
|
107
|
+
// field order in `schema` can differ from the literal's `names`, so each value
|
|
108
|
+
// must land at its named slot `schema.indexOf(name)` — a positional `slot = i`
|
|
109
|
+
// store would scatter values into the wrong (or another field's) slots.
|
|
110
|
+
const slotOf = schemaId === litId ? (i => i) : (i => schema.indexOf(names[i]))
|
|
111
|
+
// SOUNDNESS GATE: a static literal is ONE shared instance — every evaluation
|
|
112
|
+
// returns the same pointer. That is only faithful when the object is never
|
|
113
|
+
// mutated: `let mk = () => ({n:0,m:0}); mk().n++` must not bleed into the
|
|
114
|
+
// next mk(). writtenProps (program-facts) holds every property name ever
|
|
115
|
+
// written through ANY receiver — including expression receivers like
|
|
116
|
+
// `map.get(k).n++` that no alias analysis could attribute — so a literal
|
|
117
|
+
// whose schema intersects it allocates per-evaluation instead.
|
|
118
|
+
const neverWritten = names.every(n => !ctx.module.writtenProps?.has(n))
|
|
119
|
+
if (neverWritten && values.length >= 2 && values.length === schema.length && !ctx.memory.shared) {
|
|
120
|
+
const emitted = values.map(storedValue)
|
|
72
121
|
// asF64 folds i32.const → f64.const so int-literal values also qualify.
|
|
73
|
-
const slots = emitted.map(v => extractF64Bits(
|
|
122
|
+
const slots = emitted.map(v => extractF64Bits(v))
|
|
74
123
|
if (slots.every(b => b !== null)) {
|
|
75
|
-
|
|
124
|
+
// Reorder into schema-slot order before laying out the static segment.
|
|
125
|
+
const ordered = emitted.map(() => null), orderedBits = emitted.map(() => null)
|
|
126
|
+
for (let i = 0; i < values.length; i++) { ordered[slotOf(i)] = emitted[i]; orderedBits[slotOf(i)] = slots[i] }
|
|
127
|
+
const off = appendStaticSlots(orderedBits)
|
|
76
128
|
const staticPtr = mkPtrIR(PTR.OBJECT, schemaId, off)
|
|
77
129
|
if (!shadow) return staticPtr
|
|
78
130
|
inc('__dyn_set')
|
|
79
131
|
const body = [['local.set', `$${ptr}`, staticPtr]]
|
|
80
132
|
for (let i = 0; i < schema.length; i++)
|
|
81
133
|
body.push(['drop', ['call', '$__dyn_set', ['i64.reinterpret_f64', ['local.get', `$${ptr}`]],
|
|
82
|
-
asI64(emit(['str', String(schema[i])])), asI64(
|
|
134
|
+
asI64(emit(['str', String(schema[i])])), asI64(ordered[i])]])
|
|
83
135
|
body.push(['local.get', `$${ptr}`])
|
|
84
136
|
return typed(['block', ['result', 'f64'], ...body], 'f64')
|
|
85
137
|
}
|
|
@@ -89,7 +141,7 @@ export default (ctx) => {
|
|
|
89
141
|
['local.set', `$${t}`, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const', ctx.abi.object.ops.allocSlots(schema.length)]]],
|
|
90
142
|
]
|
|
91
143
|
for (let i = 0; i < values.length; i++)
|
|
92
|
-
body.push(ctx.abi.object.ops.store(['local.get', `$${t}`], i,
|
|
144
|
+
body.push(ctx.abi.object.ops.store(['local.get', `$${t}`], slotOf(i), storedValue(values[i])))
|
|
93
145
|
body.push(['local.set', `$${ptr}`, mkPtrIR(PTR.OBJECT, schemaId, ['local.get', `$${t}`])])
|
|
94
146
|
if (shadow) {
|
|
95
147
|
inc('__dyn_set')
|
|
@@ -106,6 +158,15 @@ export default (ctx) => {
|
|
|
106
158
|
|
|
107
159
|
ctx.core.emit['Object.freeze'] = (obj) => asF64(emit(obj))
|
|
108
160
|
|
|
161
|
+
// Object.is(a, b) — SameValue, which on NaN-boxed f64 values is exact bit
|
|
162
|
+
// equality. That is precisely why it diverges from `===`: +0 and -0 carry
|
|
163
|
+
// distinct bit patterns (→ false), and a NaN equals itself bit-for-bit
|
|
164
|
+
// (→ true). Objects/booleans/null/undefined compare by their fixed boxed
|
|
165
|
+
// bits, i.e. reference identity, as SameValue requires. (Two distinct heap
|
|
166
|
+
// strings would compare by pointer rather than content; jz only uses numeric
|
|
167
|
+
// Object.is — overwhelmingly `Object.is(x, -0)` — so that path never arises.)
|
|
168
|
+
ctx.core.emit['Object.is'] = (a, b) => typed(['i64.eq', asI64(emit(a)), asI64(emit(b))], 'i32')
|
|
169
|
+
|
|
109
170
|
// Object.isExtensible / isSealed / isFrozen.
|
|
110
171
|
// jz fixes an object's schema at construction: a `{…}` literal can
|
|
111
172
|
// neither grow nor lose keys, so an OBJECT value is non-extensible and
|
|
@@ -147,7 +208,7 @@ export default (ctx) => {
|
|
|
147
208
|
const arrayValType = (obj) => (typeof obj === 'string' ? lookupValType(obj) : valTypeOf(obj)) === VAL.ARRAY
|
|
148
209
|
// Index-string key array for an array-like receiver. `lenCall` is the
|
|
149
210
|
// length builtin: __len for jz arrays, __str_len for strings.
|
|
150
|
-
const
|
|
211
|
+
const idxKeys = (obj, lenCall) => {
|
|
151
212
|
inc(lenCall, '__to_str')
|
|
152
213
|
const v = temp('ik'), i = tempI32('iki'), len = tempI32('ikl')
|
|
153
214
|
const vPtr = () => ['i64.reinterpret_f64', ['local.get', `$${v}`]]
|
|
@@ -171,12 +232,16 @@ export default (ctx) => {
|
|
|
171
232
|
const nullish = requireCoercible(obj)
|
|
172
233
|
if (nullish) return nullish
|
|
173
234
|
if (isHashTyped(obj)) return emitHashKeys(obj)
|
|
174
|
-
if (arrayValType(obj)) return
|
|
175
|
-
if (stringValType(obj)) return
|
|
235
|
+
if (arrayValType(obj)) return idxKeys(obj, '__len')
|
|
236
|
+
if (stringValType(obj)) return idxKeys(obj, '__str_len')
|
|
176
237
|
const schema = resolveSchema(obj)
|
|
177
|
-
|
|
178
|
-
//
|
|
179
|
-
//
|
|
238
|
+
// A static schema lists only the literal-known keys; a var that takes
|
|
239
|
+
// computed writes (`o[k]=v`) also carries props in its per-object dyn HASH
|
|
240
|
+
// that the schema omits. Enumerate those live (schema ∪ dyn props) — the gap
|
|
241
|
+
// that blocked metacircularity (the compiler grows dicts then enumerates).
|
|
242
|
+
if (schema && !mayHaveDynProps(obj)) return emitStringArray(schema)
|
|
243
|
+
// Unknown receiver, or schema with possible dyn props: dispatch on ptr-type
|
|
244
|
+
// at runtime (HASH probe table / OBJECT schema+dyn merge / else []).
|
|
180
245
|
return emitRuntimeKeys(obj)
|
|
181
246
|
}
|
|
182
247
|
ctx.core.emit['Object.getOwnPropertyNames'] = ctx.core.emit['Object.keys']
|
|
@@ -194,7 +259,7 @@ export default (ctx) => {
|
|
|
194
259
|
['drop', asF64(emit(obj))],
|
|
195
260
|
['f64.const', has ? 1 : 0]], 'f64')
|
|
196
261
|
}
|
|
197
|
-
if (typeof obj === 'string' && ctx.schema.
|
|
262
|
+
if (typeof obj === 'string' && ctx.schema.slotOf?.(obj, litKey) >= 0)
|
|
198
263
|
return typed(['f64.const', 1], 'f64')
|
|
199
264
|
}
|
|
200
265
|
return typed(['f64.convert_i32_s', emit(['in', key, obj])], 'f64')
|
|
@@ -208,6 +273,41 @@ export default (ctx) => {
|
|
|
208
273
|
// Reuses the same own-property emitter; receiver-type variants above apply.
|
|
209
274
|
ctx.core.emit['Object.hasOwn'] = (obj, key) => ctx.core.emit['.hasOwnProperty'](obj, key)
|
|
210
275
|
|
|
276
|
+
// __object_toString(value) — canonicalized from `Object.prototype.toString.call(value)`
|
|
277
|
+
// by jzify. Returns the spec-defined "[object Tag]" string. When the value's category
|
|
278
|
+
// is known at compile time the tag folds to a static string load; otherwise the
|
|
279
|
+
// runtime path dispatches on NaN-box bits (NaN→Number, NULL/UNDEF, then PTR type).
|
|
280
|
+
ctx.core.emit['__object_toString'] = (obj) => {
|
|
281
|
+
const emitTag = value => asF64(emit(['str', value]))
|
|
282
|
+
const tag = objectToStringTagForVal(obj)
|
|
283
|
+
if (tag) return block64(['drop', asF64(emit(obj))], emitTag(tag))
|
|
284
|
+
|
|
285
|
+
const value = temp('otag'), type = tempI32('otagt')
|
|
286
|
+
const bits = ['i64.reinterpret_f64', ['local.get', `$${value}`]]
|
|
287
|
+
const byType = dispatchByPtrType(type, [
|
|
288
|
+
[PTR.STRING, emitTag('[object String]')],
|
|
289
|
+
[PTR.ARRAY, emitTag('[object Array]')],
|
|
290
|
+
[PTR.BUFFER, emitTag('[object ArrayBuffer]')],
|
|
291
|
+
[PTR.CLOSURE, emitTag('[object Function]')],
|
|
292
|
+
[PTR.SET, emitTag('[object Set]')],
|
|
293
|
+
[PTR.MAP, emitTag('[object Map]')],
|
|
294
|
+
], emitTag('[object Object]'))
|
|
295
|
+
const pointerTag = block64(['local.set', `$${type}`, ['call', '$__ptr_type', bits]], byType)
|
|
296
|
+
const nonNumericTag = ['if', ['result', 'f64'],
|
|
297
|
+
['i64.eq', bits, ['i64.const', NULL_NAN]],
|
|
298
|
+
['then', emitTag('[object Null]')],
|
|
299
|
+
['else', ['if', ['result', 'f64'],
|
|
300
|
+
['i64.eq', bits, ['i64.const', UNDEF_NAN]],
|
|
301
|
+
['then', emitTag('[object Undefined]')],
|
|
302
|
+
['else', pointerTag]]]]
|
|
303
|
+
return block64(
|
|
304
|
+
['local.set', `$${value}`, asF64(emit(obj))],
|
|
305
|
+
['if', ['result', 'f64'],
|
|
306
|
+
['f64.eq', ['local.get', `$${value}`], ['local.get', `$${value}`]],
|
|
307
|
+
['then', emitTag('[object Number]')],
|
|
308
|
+
['else', nonNumericTag]])
|
|
309
|
+
}
|
|
310
|
+
|
|
211
311
|
// String primitives are coerced to exotic String objects whose own enumerable
|
|
212
312
|
// properties are the indexed characters. Object.values/entries iterate them.
|
|
213
313
|
const stringValType = (obj) => (typeof obj === 'string' ? lookupValType(obj) : valTypeOf(obj)) === VAL.STRING
|
|
@@ -236,7 +336,7 @@ export default (ctx) => {
|
|
|
236
336
|
if (arrayValType(obj)) { inc('__arr_from'); return typed(['call', '$__arr_from', asI64(emit(obj))], 'f64') }
|
|
237
337
|
if (isHashTyped(obj)) return emitHashValues(obj)
|
|
238
338
|
const schema = resolveSchema(obj)
|
|
239
|
-
if (!schema) return emitRuntimeValues(obj)
|
|
339
|
+
if (!schema || mayHaveDynProps(obj)) return emitRuntimeValues(obj)
|
|
240
340
|
const va = asF64(emit(obj))
|
|
241
341
|
const n = schema.length
|
|
242
342
|
const t = temp('ov'), base = tempI32('vb')
|
|
@@ -299,7 +399,7 @@ export default (ctx) => {
|
|
|
299
399
|
}
|
|
300
400
|
if (isHashTyped(obj)) return emitHashEntries(obj)
|
|
301
401
|
const schema = resolveSchema(obj)
|
|
302
|
-
if (!schema) return emitRuntimeEntries(obj)
|
|
402
|
+
if (!schema || mayHaveDynProps(obj)) return emitRuntimeEntries(obj)
|
|
303
403
|
const va = asF64(emit(obj))
|
|
304
404
|
const n = schema.length
|
|
305
405
|
const t = temp('oe'), pair = tempI32('op'), base = tempI32('eb')
|
|
@@ -361,10 +461,18 @@ export default (ctx) => {
|
|
|
361
461
|
}
|
|
362
462
|
const tSchema = resolveSchema(target)
|
|
363
463
|
const sourceSchemas = sources.map(resolveSchema)
|
|
364
|
-
if (!tSchema)
|
|
464
|
+
if (!tSchema) return emitObjectAssignDynamic(target, sources)
|
|
365
465
|
if (sourceSchemas.some(s => !s)) return emitDynamicAssign(target, sources, sourceSchemas)
|
|
366
466
|
const t = temp('at'), s = temp('as')
|
|
367
467
|
const tBase = tempI32('tb'), sBase2 = tempI32('sb')
|
|
468
|
+
// When the target carries a dynamic-props shadow (needsDynShadow), reads of an
|
|
469
|
+
// unknown-schema alias (`let r = Object.assign(t, …); r.a`) dispatch through
|
|
470
|
+
// __dyn_get_any → the hash, not the schema slot. A slot-only write would leave
|
|
471
|
+
// the hash stale, so mirror each store into __dyn_set, exactly as the object
|
|
472
|
+
// literal emit does (above). False unless a collection/dyn-key module is live,
|
|
473
|
+
// so the common fixed-schema assign keeps its slot-only fast path.
|
|
474
|
+
const shadow = needsDynShadow(target)
|
|
475
|
+
if (shadow) inc('__dyn_set')
|
|
368
476
|
const body = [['local.set', `$${t}`, asF64(emit(target))],
|
|
369
477
|
['local.set', `$${tBase}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]]]
|
|
370
478
|
for (let i = 0; i < sources.length; i++) {
|
|
@@ -376,6 +484,9 @@ export default (ctx) => {
|
|
|
376
484
|
const ti = tSchema.indexOf(sSchema[si])
|
|
377
485
|
if (ti < 0) continue
|
|
378
486
|
body.push(ctx.abi.object.ops.store(['local.get', `$${tBase}`], ti, ctx.abi.object.ops.load(['local.get', `$${sBase2}`], si)))
|
|
487
|
+
if (shadow)
|
|
488
|
+
body.push(['drop', ['call', '$__dyn_set', ['i64.reinterpret_f64', ['local.get', `$${t}`]],
|
|
489
|
+
asI64(emit(['str', String(tSchema[ti])])), ctx.abi.object.ops.loadBits(['local.get', `$${tBase}`], ti)]])
|
|
379
490
|
}
|
|
380
491
|
}
|
|
381
492
|
body.push(['local.get', `$${t}`])
|
|
@@ -419,6 +530,14 @@ export default (ctx) => {
|
|
|
419
530
|
|
|
420
531
|
// Object.create(proto) → shallow copy of object (same schema, copied properties)
|
|
421
532
|
ctx.core.emit['Object.create'] = (proto) => {
|
|
533
|
+
// Object.create(null) → a fresh, empty, extensible object (no prototype). Without
|
|
534
|
+
// this it falls to the `protoType == null` runtime path below, which returns the
|
|
535
|
+
// proto value (null) itself; property writes on null then land in the GLOBAL
|
|
536
|
+
// __dyn_props table keyed by name, so two such objects collide on same-named keys
|
|
537
|
+
// (`a=Object.create(null); b=Object.create(null); a.x=1; b.x=2` left a.x===2). Reuse
|
|
538
|
+
// the empty-`{}` path, whose per-object hash keeps dynamic keys independent. (Native
|
|
539
|
+
// never hit this — its compiler runs Object.create on the host JS engine.)
|
|
540
|
+
if (isNullishLiteral(proto)) return ctx.core.emit['{}']()
|
|
422
541
|
const protoType = typeof proto === 'string' ? lookupValType(proto) : valTypeOf(proto)
|
|
423
542
|
if (protoType === VAL.ARRAY) {
|
|
424
543
|
// Clone array data + link named-prop sidecar so for-in/bracket-name lookups
|
|
@@ -426,7 +545,7 @@ export default (ctx) => {
|
|
|
426
545
|
// Header propsPtr lives at $off-16 (current ARRAY layout). We alias src's hash
|
|
427
546
|
// by copying the slot; __dyn_move covers the shifted-array case where props
|
|
428
547
|
// were migrated to the global __dyn_props.
|
|
429
|
-
|
|
548
|
+
ctx.module.include('array')
|
|
430
549
|
inc('__arr_from', '__dyn_move', '__ptr_offset')
|
|
431
550
|
const src = temp('ocs')
|
|
432
551
|
const dst = temp('ocd')
|
|
@@ -449,7 +568,7 @@ export default (ctx) => {
|
|
|
449
568
|
if (!schema) {
|
|
450
569
|
if (protoType == null) {
|
|
451
570
|
const value = temp('ocr')
|
|
452
|
-
|
|
571
|
+
ctx.module.include('array')
|
|
453
572
|
inc('__arr_from', '__dyn_move', '__ptr_offset')
|
|
454
573
|
const dst2 = temp('ocd')
|
|
455
574
|
const srcOff2 = tempI32('ocso')
|
|
@@ -457,7 +576,7 @@ export default (ctx) => {
|
|
|
457
576
|
return typed(['block', ['result', 'f64'],
|
|
458
577
|
['local.set', `$${value}`, asF64(emit(proto))],
|
|
459
578
|
['if', ['result', 'f64'],
|
|
460
|
-
['
|
|
579
|
+
ptrTypeEq(['local.get', `$${value}`], PTR.ARRAY),
|
|
461
580
|
['then', ['block', ['result', 'f64'],
|
|
462
581
|
['local.set', `$${dst2}`, ['call', '$__arr_from', ['i64.reinterpret_f64', ['local.get', `$${value}`]]]],
|
|
463
582
|
['local.set', `$${srcOff2}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${value}`]]]],
|
|
@@ -495,7 +614,7 @@ export default (ctx) => {
|
|
|
495
614
|
// Used only after the target schema is known. Unknown HASH targets can grow by
|
|
496
615
|
// returning a new pointer, which would not preserve aliases to the old value.
|
|
497
616
|
function emitDynamicAssign(target, sources, sourceSchemas = sources.map(resolveSchema)) {
|
|
498
|
-
|
|
617
|
+
ctx.module.include('collection')
|
|
499
618
|
inc('__hash_set', '__dyn_get_any', '__ptr_offset', '__len')
|
|
500
619
|
const t = temp('adt'), s = temp('ads'), sBase = tempI32('adsb')
|
|
501
620
|
const keys = temp('adk'), keysBase = tempI32('adkb'), len = tempI32('adn')
|
|
@@ -543,6 +662,56 @@ function emitDynamicAssign(target, sources, sourceSchemas = sources.map(resolveS
|
|
|
543
662
|
return typed(['block', ['result', 'f64'], ...body], 'f64')
|
|
544
663
|
}
|
|
545
664
|
|
|
665
|
+
// Object.assign into a target whose schema is unknown at compile time (e.g.
|
|
666
|
+
// `ctx.core.stdlibDeps` — an empty `{}` grown dynamically). Copy every source
|
|
667
|
+
// key into the target's dynamic props via __dyn_set, which updates the
|
|
668
|
+
// per-object hash in place: the target pointer stays stable, so no write-back
|
|
669
|
+
// to the (possibly member-access) lvalue is needed. Returns the target.
|
|
670
|
+
function emitObjectAssignDynamic(target, sources) {
|
|
671
|
+
ctx.module.include('collection')
|
|
672
|
+
inc('__dyn_set', '__dyn_get_any', '__ptr_offset', '__len')
|
|
673
|
+
const t = temp('oat'), s = temp('oas'), sBase = tempI32('oasb')
|
|
674
|
+
const keys = temp('oak'), keysBase = tempI32('oakb'), len = tempI32('oan')
|
|
675
|
+
const i = tempI32('oai'), key = temp('oakey')
|
|
676
|
+
const id = ctx.func.uniq++
|
|
677
|
+
const setKey = (keyBits, valBits) =>
|
|
678
|
+
['drop', ['call', '$__dyn_set', ['i64.reinterpret_f64', ['local.get', `$${t}`]], keyBits, valBits]]
|
|
679
|
+
const body = [['local.set', `$${t}`, asF64(emit(target))]]
|
|
680
|
+
|
|
681
|
+
for (let si = 0; si < sources.length; si++) {
|
|
682
|
+
const source = sources[si]
|
|
683
|
+
const sSchema = resolveSchema(source)
|
|
684
|
+
body.push(['local.set', `$${s}`, asF64(emit(source))])
|
|
685
|
+
if (sSchema) {
|
|
686
|
+
body.push(['local.set', `$${sBase}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${s}`]]]])
|
|
687
|
+
for (let pi = 0; pi < sSchema.length; pi++)
|
|
688
|
+
body.push(setKey(asI64(emit(['str', String(sSchema[pi])])), ctx.abi.object.ops.loadBits(['local.get', `$${sBase}`], pi)))
|
|
689
|
+
continue
|
|
690
|
+
}
|
|
691
|
+
body.push(
|
|
692
|
+
['local.set', `$${keys}`, runtimeKeysFromTemp(s, 'oak')],
|
|
693
|
+
['local.set', `$${keysBase}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${keys}`]]]],
|
|
694
|
+
['local.set', `$${len}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${keys}`]]]],
|
|
695
|
+
['local.set', `$${i}`, ['i32.const', 0]],
|
|
696
|
+
['block', `$oabrk${id}_${si}`, ['loop', `$oaloop${id}_${si}`,
|
|
697
|
+
['br_if', `$oabrk${id}_${si}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${len}`]]],
|
|
698
|
+
['local.set', `$${key}`, ['f64.load',
|
|
699
|
+
['i32.add', ['local.get', `$${keysBase}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', 3]]]]],
|
|
700
|
+
setKey(['i64.reinterpret_f64', ['local.get', `$${key}`]],
|
|
701
|
+
['call', '$__dyn_get_any', ['i64.reinterpret_f64', ['local.get', `$${s}`]], ['i64.reinterpret_f64', ['local.get', `$${key}`]]]),
|
|
702
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
703
|
+
['br', `$oaloop${id}_${si}`]]])
|
|
704
|
+
}
|
|
705
|
+
body.push(['local.get', `$${t}`])
|
|
706
|
+
return typed(['block', ['result', 'f64'], ...body], 'f64')
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// A bound var is dynamically keyed when some `obj[k]=v` (non-literal key) wrote
|
|
710
|
+
// to it — program-facts records these in `ctx.types.dynKeyVars`. Such an object
|
|
711
|
+
// can hold props beyond its static schema, so schema-only enumeration would drop
|
|
712
|
+
// them; callers route it through the runtime schema∪dyn-props merge instead.
|
|
713
|
+
const mayHaveDynProps = (obj) => typeof obj === 'string' && !!ctx.types.dynKeyVars?.has(obj)
|
|
714
|
+
|
|
546
715
|
function resolveSchema(obj) {
|
|
547
716
|
if (typeof obj === 'string') return ctx.schema.resolve(obj)
|
|
548
717
|
if (Array.isArray(obj) && obj[0] === '{}')
|
|
@@ -554,6 +723,18 @@ function resolveSchema(obj) {
|
|
|
554
723
|
return null
|
|
555
724
|
}
|
|
556
725
|
|
|
726
|
+
// Schema of a spread SOURCE, for the OBJECT-vs-HASH decision. A function
|
|
727
|
+
// parameter's runtime shape is caller-determined — its `resolveSchema` is an
|
|
728
|
+
// inferred/union guess bound only by emit (analysis sees it as unknown). Trusting
|
|
729
|
+
// it would (a) slot-index-copy from a layout the actual argument need not have and
|
|
730
|
+
// (b) make emit build an OBJECT while analysis HASH-typed the binding, so reads
|
|
731
|
+
// misdispatch. Treat params as unknown → dynamic runtime-key spread (always sound),
|
|
732
|
+
// mirroring spreadSchema in src/kind.js so both phases agree.
|
|
733
|
+
function spreadSourceSchema(obj) {
|
|
734
|
+
if (typeof obj === 'string' && ctx.func.current?.params?.some(p => p.name === obj)) return null
|
|
735
|
+
return resolveSchema(obj)
|
|
736
|
+
}
|
|
737
|
+
|
|
557
738
|
/**
|
|
558
739
|
* Emit object literal with spread: {...a, x: 1, ...b, y: 2}
|
|
559
740
|
* Merges schemas from all sources, allocates result, copies in order.
|
|
@@ -568,22 +749,27 @@ function takeLiteralTarget() {
|
|
|
568
749
|
}
|
|
569
750
|
|
|
570
751
|
function emitObjectSpread(props, spreadTarget = takeLiteralTarget()) {
|
|
571
|
-
//
|
|
752
|
+
// Resolve every spread source's schema. A source with no static schema means
|
|
753
|
+
// its full key set is unknown at compile time, so the merge result must be a
|
|
754
|
+
// HASH (dynamic dict) — a fixed schema would silently drop the source's keys
|
|
755
|
+
// it doesn't list. Only when EVERY source is known do we build the fixed-shape
|
|
756
|
+
// OBJECT below.
|
|
572
757
|
const allNames = []
|
|
573
758
|
const addName = n => { if (!allNames.includes(n)) allNames.push(n) }
|
|
759
|
+
let allKnown = true
|
|
574
760
|
for (const p of props) {
|
|
575
761
|
if (Array.isArray(p) && p[0] === '...') {
|
|
576
|
-
const s =
|
|
762
|
+
const s = spreadSourceSchema(p[1])
|
|
577
763
|
if (s) for (const n of s) addName(n)
|
|
764
|
+
else allKnown = false
|
|
578
765
|
} else if (Array.isArray(p) && p[0] === ':') addName(p[1])
|
|
579
766
|
}
|
|
580
|
-
//
|
|
581
|
-
//
|
|
582
|
-
//
|
|
583
|
-
if (!
|
|
767
|
+
// Single unknown spread `{ ...opts }` → alias the source directly. Safe for
|
|
768
|
+
// read-only use; mutation would affect the source (a true clone takes the
|
|
769
|
+
// dynamic-merge path below).
|
|
770
|
+
if (!allKnown && props.length === 1 && Array.isArray(props[0]) && props[0][0] === '...')
|
|
584
771
|
return typed(asF64(emit(props[0][1])), 'f64')
|
|
585
|
-
|
|
586
|
-
if (!allNames.length) err('Object spread: cannot resolve source schema')
|
|
772
|
+
if (!allKnown) return emitDynamicSpread(props)
|
|
587
773
|
|
|
588
774
|
const schemaId = ctx.schema.register(allNames)
|
|
589
775
|
const schema = ctx.schema.list[schemaId]
|
|
@@ -594,26 +780,9 @@ function emitObjectSpread(props, spreadTarget = takeLiteralTarget()) {
|
|
|
594
780
|
const body = [['local.set', `$${t}`, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const', ctx.abi.object.ops.allocSlots(schema.length)]]]]
|
|
595
781
|
|
|
596
782
|
// Process props in order — later props override earlier (JS semantics)
|
|
597
|
-
let srcF
|
|
598
783
|
for (const p of props) {
|
|
599
784
|
if (Array.isArray(p) && p[0] === '...') {
|
|
600
785
|
const sSchema = resolveSchema(p[1])
|
|
601
|
-
if (!sSchema) {
|
|
602
|
-
// Unknown-schema source (e.g. parameter). Override each slot via runtime
|
|
603
|
-
// __dyn_get_or using existing value as fallback.
|
|
604
|
-
includeModule('collection')
|
|
605
|
-
inc('__dyn_get_or')
|
|
606
|
-
srcF ??= temp('ospf')
|
|
607
|
-
body.push(['local.set', `$${srcF}`, asF64(emit(p[1]))])
|
|
608
|
-
for (let i = 0; i < schema.length; i++) {
|
|
609
|
-
const base = ['local.get', `$${t}`]
|
|
610
|
-
body.push(ctx.abi.object.ops.store(base, i,
|
|
611
|
-
['f64.reinterpret_i64', ['call', '$__dyn_get_or', ['i64.reinterpret_f64', ['local.get', `$${srcF}`]],
|
|
612
|
-
asI64(emit(['str', String(schema[i])])),
|
|
613
|
-
ctx.abi.object.ops.loadBits(base, i)]]))
|
|
614
|
-
}
|
|
615
|
-
continue
|
|
616
|
-
}
|
|
617
786
|
body.push(['local.set', `$${src}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', asF64(emit(p[1]))]]])
|
|
618
787
|
for (let si = 0; si < sSchema.length; si++) {
|
|
619
788
|
const ti = schema.indexOf(sSchema[si])
|
|
@@ -622,7 +791,7 @@ function emitObjectSpread(props, spreadTarget = takeLiteralTarget()) {
|
|
|
622
791
|
}
|
|
623
792
|
} else if (Array.isArray(p) && p[0] === ':') {
|
|
624
793
|
const ti = schema.indexOf(p[1])
|
|
625
|
-
if (ti >= 0) body.push(ctx.abi.object.ops.store(['local.get', `$${t}`], ti,
|
|
794
|
+
if (ti >= 0) body.push(ctx.abi.object.ops.store(['local.get', `$${t}`], ti, storedValue(p[2])))
|
|
626
795
|
}
|
|
627
796
|
}
|
|
628
797
|
|
|
@@ -637,6 +806,55 @@ function emitObjectSpread(props, spreadTarget = takeLiteralTarget()) {
|
|
|
637
806
|
return typed(['block', ['result', 'f64'], ...body], 'f64')
|
|
638
807
|
}
|
|
639
808
|
|
|
809
|
+
// Spread merge when any source schema is unknown: build a fresh HASH and copy
|
|
810
|
+
// every key of each source in order (later overrides earlier — JS semantics),
|
|
811
|
+
// threading explicit `k: v` props at their source position. Mirrors
|
|
812
|
+
// emitDynamicAssign but seeds an empty HASH instead of an existing target.
|
|
813
|
+
function emitDynamicSpread(props) {
|
|
814
|
+
ctx.module.include('collection')
|
|
815
|
+
inc('__hash_new', '__hash_set', '__dyn_get_any', '__ptr_offset', '__len')
|
|
816
|
+
const t = temp('dst'), s = temp('dss'), sBase = tempI32('dssb')
|
|
817
|
+
const keys = temp('dsk'), keysBase = tempI32('dskb'), len = tempI32('dsn')
|
|
818
|
+
const i = tempI32('dsi'), key = temp('dskey')
|
|
819
|
+
const id = ctx.func.uniq++
|
|
820
|
+
// `__hash_set` may rehash and return a new pointer, so thread it back into $t.
|
|
821
|
+
const setKey = (keyBits, valBits) =>
|
|
822
|
+
['local.set', `$${t}`, ['f64.reinterpret_i64',
|
|
823
|
+
['call', '$__hash_set', ['i64.reinterpret_f64', ['local.get', `$${t}`]], keyBits, valBits]]]
|
|
824
|
+
const body = [['local.set', `$${t}`, ['call', '$__hash_new']]]
|
|
825
|
+
|
|
826
|
+
for (let pi = 0; pi < props.length; pi++) {
|
|
827
|
+
const p = props[pi]
|
|
828
|
+
if (Array.isArray(p) && p[0] === ':') {
|
|
829
|
+
body.push(setKey(asI64(emit(['str', String(p[1])])), asI64(emit(p[2]))))
|
|
830
|
+
continue
|
|
831
|
+
}
|
|
832
|
+
const sSchema = spreadSourceSchema(p[1])
|
|
833
|
+
body.push(['local.set', `$${s}`, asF64(emit(p[1]))])
|
|
834
|
+
if (sSchema) {
|
|
835
|
+
body.push(['local.set', `$${sBase}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${s}`]]]])
|
|
836
|
+
for (let si = 0; si < sSchema.length; si++)
|
|
837
|
+
body.push(setKey(asI64(emit(['str', String(sSchema[si])])), ctx.abi.object.ops.loadBits(['local.get', `$${sBase}`], si)))
|
|
838
|
+
continue
|
|
839
|
+
}
|
|
840
|
+
body.push(
|
|
841
|
+
['local.set', `$${keys}`, runtimeKeysFromTemp(s, 'dsk')],
|
|
842
|
+
['local.set', `$${keysBase}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${keys}`]]]],
|
|
843
|
+
['local.set', `$${len}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${keys}`]]]],
|
|
844
|
+
['local.set', `$${i}`, ['i32.const', 0]],
|
|
845
|
+
['block', `$dsbrk${id}_${pi}`, ['loop', `$dsloop${id}_${pi}`,
|
|
846
|
+
['br_if', `$dsbrk${id}_${pi}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${len}`]]],
|
|
847
|
+
['local.set', `$${key}`, ['f64.load',
|
|
848
|
+
['i32.add', ['local.get', `$${keysBase}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', 3]]]]],
|
|
849
|
+
setKey(['i64.reinterpret_f64', ['local.get', `$${key}`]],
|
|
850
|
+
['call', '$__dyn_get_any', ['i64.reinterpret_f64', ['local.get', `$${s}`]], ['i64.reinterpret_f64', ['local.get', `$${key}`]]]),
|
|
851
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
852
|
+
['br', `$dsloop${id}_${pi}`]]])
|
|
853
|
+
}
|
|
854
|
+
body.push(['local.get', `$${t}`])
|
|
855
|
+
return typed(['block', ['result', 'f64'], ...body], 'f64')
|
|
856
|
+
}
|
|
857
|
+
|
|
640
858
|
function emitStringArray(names) {
|
|
641
859
|
const n = names.length
|
|
642
860
|
const out = allocPtr({ type: PTR.ARRAY, len: n, tag: 'sa' })
|
|
@@ -687,9 +905,9 @@ function emitHashEntries(obj) {
|
|
|
687
905
|
// IR shape from the same source — only difference is whether they enter from
|
|
688
906
|
// a static type guard or a runtime ptr-type check.
|
|
689
907
|
function hashKeysFromTemp(t) {
|
|
690
|
-
inc('__ptr_offset', '__cap', '__len')
|
|
908
|
+
inc('__ptr_offset', '__cap', '__len', '__coll_order')
|
|
691
909
|
const off = tempI32('hko'), cap = tempI32('hkc'), n = tempI32('hkn')
|
|
692
|
-
const i = tempI32('hki'),
|
|
910
|
+
const i = tempI32('hki'), ord = tempI32('hkr'), slot = tempI32('hks')
|
|
693
911
|
const out = allocPtr({ type: PTR.ARRAY, len: ['local.get', `$${n}`], tag: 'hka' })
|
|
694
912
|
const id = ctx.func.uniq++
|
|
695
913
|
return ['block', ['result', 'f64'],
|
|
@@ -697,26 +915,23 @@ function hashKeysFromTemp(t) {
|
|
|
697
915
|
out.init,
|
|
698
916
|
['local.set', `$${off}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
699
917
|
['local.set', `$${cap}`, ['call', '$__cap', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
918
|
+
['local.set', `$${ord}`, ['call', '$__coll_order', ['local.get', `$${off}`], ['local.get', `$${cap}`], ['i32.const', 24]]],
|
|
700
919
|
['local.set', `$${i}`, ['i32.const', 0]],
|
|
701
|
-
['local.set', `$${o}`, ['i32.const', 0]],
|
|
702
920
|
['block', `$brk${id}`, ['loop', `$loop${id}`,
|
|
703
|
-
['br_if', `$brk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${
|
|
704
|
-
['local.set', `$${slot}`, ['i32.add', ['local.get', `$${
|
|
705
|
-
['i32.
|
|
706
|
-
|
|
707
|
-
['
|
|
708
|
-
elemStore(out.local, o,
|
|
709
|
-
['f64.load', ['i32.add', ['local.get', `$${slot}`], ['i32.const', 8]]]),
|
|
710
|
-
['local.set', `$${o}`, ['i32.add', ['local.get', `$${o}`], ['i32.const', 1]]]]],
|
|
921
|
+
['br_if', `$brk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${n}`]]],
|
|
922
|
+
['local.set', `$${slot}`, ['i32.load', ['i32.add', ['local.get', `$${ord}`],
|
|
923
|
+
['i32.shl', ['local.get', `$${i}`], ['i32.const', 2]]]]],
|
|
924
|
+
elemStore(out.local, i,
|
|
925
|
+
['f64.load', ['i32.add', ['local.get', `$${slot}`], ['i32.const', 8]]]),
|
|
711
926
|
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
712
927
|
['br', `$loop${id}`]]],
|
|
713
928
|
out.ptr]
|
|
714
929
|
}
|
|
715
930
|
|
|
716
931
|
function hashValuesFromTemp(t) {
|
|
717
|
-
inc('__ptr_offset', '__cap', '__len')
|
|
932
|
+
inc('__ptr_offset', '__cap', '__len', '__coll_order')
|
|
718
933
|
const off = tempI32('hvo'), cap = tempI32('hvc'), n = tempI32('hvn')
|
|
719
|
-
const i = tempI32('hvi'),
|
|
934
|
+
const i = tempI32('hvi'), ord = tempI32('hvr'), slot = tempI32('hvs')
|
|
720
935
|
const out = allocPtr({ type: PTR.ARRAY, len: ['local.get', `$${n}`], tag: 'hva' })
|
|
721
936
|
const id = ctx.func.uniq++
|
|
722
937
|
return ['block', ['result', 'f64'],
|
|
@@ -724,26 +939,23 @@ function hashValuesFromTemp(t) {
|
|
|
724
939
|
out.init,
|
|
725
940
|
['local.set', `$${off}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
726
941
|
['local.set', `$${cap}`, ['call', '$__cap', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
942
|
+
['local.set', `$${ord}`, ['call', '$__coll_order', ['local.get', `$${off}`], ['local.get', `$${cap}`], ['i32.const', 24]]],
|
|
727
943
|
['local.set', `$${i}`, ['i32.const', 0]],
|
|
728
|
-
['local.set', `$${o}`, ['i32.const', 0]],
|
|
729
944
|
['block', `$vbrk${id}`, ['loop', `$vloop${id}`,
|
|
730
|
-
['br_if', `$vbrk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${
|
|
731
|
-
['local.set', `$${slot}`, ['i32.add', ['local.get', `$${
|
|
732
|
-
['i32.
|
|
733
|
-
|
|
734
|
-
['
|
|
735
|
-
elemStore(out.local, o,
|
|
736
|
-
['f64.load', ['i32.add', ['local.get', `$${slot}`], ['i32.const', 16]]]),
|
|
737
|
-
['local.set', `$${o}`, ['i32.add', ['local.get', `$${o}`], ['i32.const', 1]]]]],
|
|
945
|
+
['br_if', `$vbrk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${n}`]]],
|
|
946
|
+
['local.set', `$${slot}`, ['i32.load', ['i32.add', ['local.get', `$${ord}`],
|
|
947
|
+
['i32.shl', ['local.get', `$${i}`], ['i32.const', 2]]]]],
|
|
948
|
+
elemStore(out.local, i,
|
|
949
|
+
['f64.load', ['i32.add', ['local.get', `$${slot}`], ['i32.const', 16]]]),
|
|
738
950
|
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
739
951
|
['br', `$vloop${id}`]]],
|
|
740
952
|
out.ptr]
|
|
741
953
|
}
|
|
742
954
|
|
|
743
955
|
function hashEntriesFromTemp(t) {
|
|
744
|
-
inc('__ptr_offset', '__cap', '__len', '__alloc_hdr')
|
|
956
|
+
inc('__ptr_offset', '__cap', '__len', '__alloc_hdr', '__coll_order')
|
|
745
957
|
const off = tempI32('heo'), cap = tempI32('hec'), n = tempI32('hen')
|
|
746
|
-
const i = tempI32('hei'),
|
|
958
|
+
const i = tempI32('hei'), ord = tempI32('her'), slot = tempI32('hes'), pair = tempI32('hep')
|
|
747
959
|
const out = allocPtr({ type: PTR.ARRAY, len: ['local.get', `$${n}`], tag: 'hea' })
|
|
748
960
|
const id = ctx.func.uniq++
|
|
749
961
|
return ['block', ['result', 'f64'],
|
|
@@ -751,21 +963,18 @@ function hashEntriesFromTemp(t) {
|
|
|
751
963
|
out.init,
|
|
752
964
|
['local.set', `$${off}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
753
965
|
['local.set', `$${cap}`, ['call', '$__cap', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
966
|
+
['local.set', `$${ord}`, ['call', '$__coll_order', ['local.get', `$${off}`], ['local.get', `$${cap}`], ['i32.const', 24]]],
|
|
754
967
|
['local.set', `$${i}`, ['i32.const', 0]],
|
|
755
|
-
['local.set', `$${o}`, ['i32.const', 0]],
|
|
756
968
|
['block', `$ebrk${id}`, ['loop', `$eloop${id}`,
|
|
757
|
-
['br_if', `$ebrk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${
|
|
758
|
-
['local.set', `$${slot}`, ['i32.add', ['local.get', `$${
|
|
759
|
-
['i32.
|
|
760
|
-
['
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
['f64.load', ['i32.add', ['local.get', `$${slot}`], ['i32.const', 16]]]],
|
|
767
|
-
elemStore(out.local, o, mkPtrIR(PTR.ARRAY, 0, ['local.get', `$${pair}`])),
|
|
768
|
-
['local.set', `$${o}`, ['i32.add', ['local.get', `$${o}`], ['i32.const', 1]]]]],
|
|
969
|
+
['br_if', `$ebrk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${n}`]]],
|
|
970
|
+
['local.set', `$${slot}`, ['i32.load', ['i32.add', ['local.get', `$${ord}`],
|
|
971
|
+
['i32.shl', ['local.get', `$${i}`], ['i32.const', 2]]]]],
|
|
972
|
+
['local.set', `$${pair}`, ['call', '$__alloc_hdr', ['i32.const', 2], ['i32.const', 2]]],
|
|
973
|
+
['f64.store', ['local.get', `$${pair}`],
|
|
974
|
+
['f64.load', ['i32.add', ['local.get', `$${slot}`], ['i32.const', 8]]]],
|
|
975
|
+
['f64.store', ['i32.add', ['local.get', `$${pair}`], ['i32.const', 8]],
|
|
976
|
+
['f64.load', ['i32.add', ['local.get', `$${slot}`], ['i32.const', 16]]]],
|
|
977
|
+
elemStore(out.local, i, mkPtrIR(PTR.ARRAY, 0, ['local.get', `$${pair}`])),
|
|
769
978
|
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
770
979
|
['br', `$eloop${id}`]]],
|
|
771
980
|
out.ptr]
|
|
@@ -789,7 +998,7 @@ function runtimeKeysFromTemp(t, tag) {
|
|
|
789
998
|
// JSON.parse or compile-time schemas — the OBJECT arm reads it at runtime
|
|
790
999
|
// and the watr resolver requires the symbol to be declared.
|
|
791
1000
|
if (!ctx.scope.globals.has('__schema_tbl'))
|
|
792
|
-
|
|
1001
|
+
declGlobal('__schema_tbl', 'i32')
|
|
793
1002
|
const tt = tempI32(`${tag}t`)
|
|
794
1003
|
const empty = allocPtr({ type: PTR.ARRAY, len: 0, tag: `${tag}e` })
|
|
795
1004
|
return ['block', ['result', 'f64'],
|
|
@@ -798,9 +1007,7 @@ function runtimeKeysFromTemp(t, tag) {
|
|
|
798
1007
|
['i32.eq', ['local.get', `$${tt}`], ['i32.const', PTR.HASH]],
|
|
799
1008
|
['then', hashKeysFromTemp(t)],
|
|
800
1009
|
['else', ['if', ['result', 'f64'],
|
|
801
|
-
['i32.
|
|
802
|
-
['i32.eq', ['local.get', `$${tt}`], ['i32.const', PTR.OBJECT]],
|
|
803
|
-
['i32.ne', ['global.get', '$__schema_tbl'], ['i32.const', 0]]],
|
|
1010
|
+
['i32.eq', ['local.get', `$${tt}`], ['i32.const', PTR.OBJECT]],
|
|
804
1011
|
['then', objectKeysFromTemp(t)],
|
|
805
1012
|
['else', ['block', ['result', 'f64'], empty.init, empty.ptr]]]]]]
|
|
806
1013
|
}
|
|
@@ -808,7 +1015,7 @@ function runtimeKeysFromTemp(t, tag) {
|
|
|
808
1015
|
function emitRuntimeValues(obj) {
|
|
809
1016
|
inc('__ptr_type')
|
|
810
1017
|
if (!ctx.scope.globals.has('__schema_tbl'))
|
|
811
|
-
|
|
1018
|
+
declGlobal('__schema_tbl', 'i32')
|
|
812
1019
|
const t = temp('rv'), tt = tempI32('rvt')
|
|
813
1020
|
const empty = allocPtr({ type: PTR.ARRAY, len: 0, tag: 'rve' })
|
|
814
1021
|
return typed(['block', ['result', 'f64'],
|
|
@@ -818,9 +1025,7 @@ function emitRuntimeValues(obj) {
|
|
|
818
1025
|
['i32.eq', ['local.get', `$${tt}`], ['i32.const', PTR.HASH]],
|
|
819
1026
|
['then', hashValuesFromTemp(t)],
|
|
820
1027
|
['else', ['if', ['result', 'f64'],
|
|
821
|
-
['i32.
|
|
822
|
-
['i32.eq', ['local.get', `$${tt}`], ['i32.const', PTR.OBJECT]],
|
|
823
|
-
['i32.ne', ['global.get', '$__schema_tbl'], ['i32.const', 0]]],
|
|
1028
|
+
['i32.eq', ['local.get', `$${tt}`], ['i32.const', PTR.OBJECT]],
|
|
824
1029
|
['then', objectValuesFromTemp(t)],
|
|
825
1030
|
['else', ['block', ['result', 'f64'], empty.init, empty.ptr]]]]]], 'f64')
|
|
826
1031
|
}
|
|
@@ -828,7 +1033,7 @@ function emitRuntimeValues(obj) {
|
|
|
828
1033
|
function emitRuntimeEntries(obj) {
|
|
829
1034
|
inc('__ptr_type')
|
|
830
1035
|
if (!ctx.scope.globals.has('__schema_tbl'))
|
|
831
|
-
|
|
1036
|
+
declGlobal('__schema_tbl', 'i32')
|
|
832
1037
|
const t = temp('re'), tt = tempI32('ret')
|
|
833
1038
|
const empty = allocPtr({ type: PTR.ARRAY, len: 0, tag: 'ree' })
|
|
834
1039
|
return typed(['block', ['result', 'f64'],
|
|
@@ -838,102 +1043,161 @@ function emitRuntimeEntries(obj) {
|
|
|
838
1043
|
['i32.eq', ['local.get', `$${tt}`], ['i32.const', PTR.HASH]],
|
|
839
1044
|
['then', hashEntriesFromTemp(t)],
|
|
840
1045
|
['else', ['if', ['result', 'f64'],
|
|
841
|
-
['i32.
|
|
842
|
-
['i32.eq', ['local.get', `$${tt}`], ['i32.const', PTR.OBJECT]],
|
|
843
|
-
['i32.ne', ['global.get', '$__schema_tbl'], ['i32.const', 0]]],
|
|
1046
|
+
['i32.eq', ['local.get', `$${tt}`], ['i32.const', PTR.OBJECT]],
|
|
844
1047
|
['then', objectEntriesFromTemp(t)],
|
|
845
1048
|
['else', ['block', ['result', 'f64'], empty.init, empty.ptr]]]]]], 'f64')
|
|
846
1049
|
}
|
|
847
1050
|
|
|
848
|
-
//
|
|
849
|
-
//
|
|
850
|
-
//
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
1051
|
+
// Shared scaffold for Object.{keys,values,entries} on a runtime OBJECT.
|
|
1052
|
+
//
|
|
1053
|
+
// A plain JS object reports ALL its own keys at enumeration time. jz objects
|
|
1054
|
+
// split that surface in two: a static SCHEMA (jz Array of key STRINGs registered
|
|
1055
|
+
// in __schema_tbl[sid] + field values inline at base+i*8) and a per-instance
|
|
1056
|
+
// HASH of dyn props at base-16 added by computed writes `o[k]=v`. Enumerating
|
|
1057
|
+
// only the schema would silently drop dyn keys — the gap that blocked
|
|
1058
|
+
// metacircularity (kernel dicts grow via `o[k]=v` then enumerate via Object.keys).
|
|
1059
|
+
//
|
|
1060
|
+
// All three variants share the entire scaffold — schema lookup, dyn discovery,
|
|
1061
|
+
// over-alloc output, two iteration loops, shadow-mirror dedup, length patch,
|
|
1062
|
+
// ARRAY ptr boxing. They differ ONLY in per-slot stores:
|
|
1063
|
+
// - keys: write i64 key
|
|
1064
|
+
// - values: write f64 value
|
|
1065
|
+
// - entries: alloc 2-slot pair + write boxed ptr
|
|
1066
|
+
//
|
|
1067
|
+
// Callbacks receive the active locals as named fields so each variant can
|
|
1068
|
+
// reference what it needs without knowing the scaffold's layout.
|
|
1069
|
+
function emitEnumerateObject(t, emitStaticStore, emitDynStore) {
|
|
1070
|
+
inc('__alloc_hdr', '__ptr_offset', '__coll_order')
|
|
1071
|
+
const sid = tempI32('oes'), src = tempI32('oesrc'), sn = tempI32('oen')
|
|
1072
|
+
const base = tempI32('oebase'), props = tempI64('oepr'), poff = tempI32('oepo')
|
|
1073
|
+
const pcap = tempI32('oepc'), dn = tempI32('oedn'), total = tempI32('oetot')
|
|
1074
|
+
const out = tempI32('oeo'), i = tempI32('oei'), o = tempI32('oej')
|
|
1075
|
+
const slot = tempI32('oesl'), ord = tempI32('oeord')
|
|
1076
|
+
const j = tempI32('oej2'), skip = tempI32('oesk'), pair = tempI32('oep')
|
|
854
1077
|
const id = ctx.func.uniq++
|
|
1078
|
+
const env = { out, o, src, base, i, slot, pair }
|
|
855
1079
|
return ['block', ['result', 'f64'],
|
|
1080
|
+
// Static schema row: sid (AUX bits) → __schema_tbl[sid] → src offset; n@src-8.
|
|
1081
|
+
// __schema_tbl is omitted when every program schema is empty (dyn-only dicts);
|
|
1082
|
+
// guard the read so empty-table programs see sn=0 here and still enumerate
|
|
1083
|
+
// dyn-props below.
|
|
856
1084
|
['local.set', `$${sid}`, ['i32.wrap_i64', ['i64.and',
|
|
857
1085
|
['i64.shr_u', ['i64.reinterpret_f64', ['local.get', `$${t}`]], ['i64.const', LAYOUT.AUX_SHIFT]],
|
|
858
1086
|
['i64.const', LAYOUT.AUX_MASK]]]],
|
|
859
|
-
['local.set', `$${
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
['i64.load',
|
|
871
|
-
['i32.add', ['local.get', `$${src}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', 3]]]]],
|
|
872
|
-
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
873
|
-
['br', `$kloop${id}`]]],
|
|
874
|
-
mkPtrIR(PTR.ARRAY, 0, ['local.get', `$${out}`])]
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
function objectValuesFromTemp(t) {
|
|
878
|
-
inc('__alloc_hdr', '__ptr_offset')
|
|
879
|
-
const sid = tempI32('ovs'), src = tempI32('ovsrc'), n = tempI32('ovn')
|
|
880
|
-
const base = tempI32('ovbase'), out = tempI32('ovo'), i = tempI32('ovi')
|
|
881
|
-
const id = ctx.func.uniq++
|
|
882
|
-
return ['block', ['result', 'f64'],
|
|
883
|
-
['local.set', `$${sid}`, ['i32.wrap_i64', ['i64.and',
|
|
884
|
-
['i64.shr_u', ['i64.reinterpret_f64', ['local.get', `$${t}`]], ['i64.const', LAYOUT.AUX_SHIFT]],
|
|
885
|
-
['i64.const', LAYOUT.AUX_MASK]]]],
|
|
886
|
-
['local.set', `$${src}`, ['i32.wrap_i64', ['i64.and',
|
|
887
|
-
['i64.load', ['i32.add', ['global.get', '$__schema_tbl'], ['i32.shl', ['local.get', `$${sid}`], ['i32.const', 3]]]],
|
|
888
|
-
['i64.const', LAYOUT.OFFSET_MASK]]]],
|
|
889
|
-
['local.set', `$${n}`, ['i32.load', ['i32.sub', ['local.get', `$${src}`], ['i32.const', 8]]]],
|
|
1087
|
+
['local.set', `$${sn}`, ['i32.const', 0]],
|
|
1088
|
+
['local.set', `$${src}`, ['i32.const', 0]],
|
|
1089
|
+
['if', ['i32.ne', ['global.get', '$__schema_tbl'], ['i32.const', 0]],
|
|
1090
|
+
['then',
|
|
1091
|
+
['local.set', `$${src}`, ['i32.wrap_i64', ['i64.and',
|
|
1092
|
+
['i64.load', ['i32.add', ['global.get', '$__schema_tbl'], ['i32.shl', ['local.get', `$${sid}`], ['i32.const', 3]]]],
|
|
1093
|
+
['i64.const', LAYOUT.OFFSET_MASK]]]],
|
|
1094
|
+
['local.set', `$${sn}`, ['i32.load', ['i32.sub', ['local.get', `$${src}`], ['i32.const', 8]]]]]],
|
|
1095
|
+
// Dyn-props: heap OBJECTs (base >= __heap_start) carry a HASH propsPtr at
|
|
1096
|
+
// base-16 (0 when none). Static-segment objects have no header, so they
|
|
1097
|
+
// contribute no dyn keys (poff stays 0).
|
|
890
1098
|
['local.set', `$${base}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
891
|
-
['local.set', `$${
|
|
892
|
-
|
|
1099
|
+
['local.set', `$${dn}`, ['i32.const', 0]],
|
|
1100
|
+
['local.set', `$${poff}`, ['i32.const', 0]],
|
|
1101
|
+
['if', ['i32.ge_u', ['local.get', `$${base}`], ['global.get', '$__heap_start']],
|
|
1102
|
+
['then',
|
|
1103
|
+
['local.set', `$${props}`, ['i64.load', ['i32.sub', ['local.get', `$${base}`], ['i32.const', 16]]]],
|
|
1104
|
+
['if', ['i32.eq',
|
|
1105
|
+
['i32.wrap_i64', ['i64.and', ['i64.shr_u', ['local.get', `$${props}`], ['i64.const', LAYOUT.TAG_SHIFT]], ['i64.const', LAYOUT.TAG_MASK]]],
|
|
1106
|
+
['i32.const', PTR.HASH]],
|
|
1107
|
+
['then',
|
|
1108
|
+
// Resolve forward chain — HASH may have forwarded on grow; the raw
|
|
1109
|
+
// propsPtr offset would point at the forward record, not live slots.
|
|
1110
|
+
['local.set', `$${poff}`, ['call', '$__ptr_offset', ['local.get', `$${props}`]]],
|
|
1111
|
+
['local.set', `$${pcap}`, ['i32.load', ['i32.sub', ['local.get', `$${poff}`], ['i32.const', 4]]]],
|
|
1112
|
+
['local.set', `$${dn}`, ['i32.load', ['i32.sub', ['local.get', `$${poff}`], ['i32.const', 8]]]]]]]],
|
|
1113
|
+
// Over-allocate sn+dn; patch length to actual `o` post-dedup so removed
|
|
1114
|
+
// shadow-mirror slots never expose garbage tails.
|
|
1115
|
+
['local.set', `$${total}`, ['i32.add', ['local.get', `$${sn}`], ['local.get', `$${dn}`]]],
|
|
1116
|
+
['local.set', `$${out}`, ['call', '$__alloc_hdr', ['local.get', `$${total}`], ['local.get', `$${total}`]]],
|
|
1117
|
+
['local.set', `$${o}`, ['i32.const', 0]],
|
|
1118
|
+
// Static schema slots — no skip, schema keys are unique by construction.
|
|
893
1119
|
['local.set', `$${i}`, ['i32.const', 0]],
|
|
894
|
-
['block', `$
|
|
895
|
-
['br_if', `$
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
['f64.load',
|
|
899
|
-
['i32.add', ['local.get', `$${base}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', 3]]]]],
|
|
1120
|
+
['block', `$sbrk${id}`, ['loop', `$sloop${id}`,
|
|
1121
|
+
['br_if', `$sbrk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${sn}`]]],
|
|
1122
|
+
...emitStaticStore(env),
|
|
1123
|
+
['local.set', `$${o}`, ['i32.add', ['local.get', `$${o}`], ['i32.const', 1]]],
|
|
900
1124
|
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
901
|
-
['br', `$
|
|
1125
|
+
['br', `$sloop${id}`]]],
|
|
1126
|
+
// Dyn-prop slots in insertion order (__coll_order sorts the dn live 24-byte
|
|
1127
|
+
// slots by packed seq; hash@+0, key@+8, value@+16). Skip entries whose key
|
|
1128
|
+
// is already in the schema — when an object literal has shadow=true (per
|
|
1129
|
+
// needsDynShadow), each schema key is mirrored into propsPtr at construction
|
|
1130
|
+
// so dyn-key reads hit the hash fast path; the mirror is not an enumeration
|
|
1131
|
+
// entity, so we must not emit it twice.
|
|
1132
|
+
['if', ['i32.ne', ['local.get', `$${poff}`], ['i32.const', 0]],
|
|
1133
|
+
['then',
|
|
1134
|
+
['local.set', `$${ord}`, ['call', '$__coll_order', ['local.get', `$${poff}`], ['local.get', `$${pcap}`], ['i32.const', 24]]],
|
|
1135
|
+
['local.set', `$${i}`, ['i32.const', 0]],
|
|
1136
|
+
['block', `$dbrk${id}`, ['loop', `$dloop${id}`,
|
|
1137
|
+
['br_if', `$dbrk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${dn}`]]],
|
|
1138
|
+
['local.set', `$${slot}`, ['i32.load', ['i32.add', ['local.get', `$${ord}`],
|
|
1139
|
+
['i32.shl', ['local.get', `$${i}`], ['i32.const', 2]]]]],
|
|
1140
|
+
['local.set', `$${skip}`, ['i32.const', 0]],
|
|
1141
|
+
['local.set', `$${j}`, ['i32.const', 0]],
|
|
1142
|
+
['block', `$skbrk${id}`, ['loop', `$skloop${id}`,
|
|
1143
|
+
['br_if', `$skbrk${id}`, ['i32.ge_s', ['local.get', `$${j}`], ['local.get', `$${sn}`]]],
|
|
1144
|
+
['if', ['i64.eq',
|
|
1145
|
+
['i64.load', ['i32.add', ['local.get', `$${slot}`], ['i32.const', 8]]],
|
|
1146
|
+
['i64.load', ['i32.add', ['local.get', `$${src}`], ['i32.shl', ['local.get', `$${j}`], ['i32.const', 3]]]]],
|
|
1147
|
+
['then', ['local.set', `$${skip}`, ['i32.const', 1]], ['br', `$skbrk${id}`]]],
|
|
1148
|
+
['local.set', `$${j}`, ['i32.add', ['local.get', `$${j}`], ['i32.const', 1]]],
|
|
1149
|
+
['br', `$skloop${id}`]]],
|
|
1150
|
+
['if', ['i32.eqz', ['local.get', `$${skip}`]],
|
|
1151
|
+
['then',
|
|
1152
|
+
...emitDynStore(env),
|
|
1153
|
+
['local.set', `$${o}`, ['i32.add', ['local.get', `$${o}`], ['i32.const', 1]]]]],
|
|
1154
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
1155
|
+
['br', `$dloop${id}`]]]]],
|
|
1156
|
+
['i32.store', ['i32.sub', ['local.get', `$${out}`], ['i32.const', 8]], ['local.get', `$${o}`]],
|
|
902
1157
|
mkPtrIR(PTR.ARRAY, 0, ['local.get', `$${out}`])]
|
|
903
1158
|
}
|
|
904
1159
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
['
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
['
|
|
920
|
-
['local.get', `$${
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
['local.
|
|
925
|
-
['
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
['local.
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
}
|
|
1160
|
+
// Object.keys for an OBJECT — copy schema key (i64@src+i*8) then dyn key (i64@slot+8).
|
|
1161
|
+
const objectKeysFromTemp = (t) => emitEnumerateObject(t,
|
|
1162
|
+
({ out, o, src, i }) => [
|
|
1163
|
+
['i64.store',
|
|
1164
|
+
['i32.add', ['local.get', `$${out}`], ['i32.shl', ['local.get', `$${o}`], ['i32.const', 3]]],
|
|
1165
|
+
['i64.load', ['i32.add', ['local.get', `$${src}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', 3]]]]]],
|
|
1166
|
+
({ out, o, slot }) => [
|
|
1167
|
+
['i64.store',
|
|
1168
|
+
['i32.add', ['local.get', `$${out}`], ['i32.shl', ['local.get', `$${o}`], ['i32.const', 3]]],
|
|
1169
|
+
['i64.load', ['i32.add', ['local.get', `$${slot}`], ['i32.const', 8]]]]])
|
|
1170
|
+
|
|
1171
|
+
// Object.values for an OBJECT — copy schema value (f64@base+i*8) then dyn value (f64@slot+16).
|
|
1172
|
+
const objectValuesFromTemp = (t) => emitEnumerateObject(t,
|
|
1173
|
+
({ out, o, base, i }) => [
|
|
1174
|
+
['f64.store',
|
|
1175
|
+
['i32.add', ['local.get', `$${out}`], ['i32.shl', ['local.get', `$${o}`], ['i32.const', 3]]],
|
|
1176
|
+
['f64.load', ['i32.add', ['local.get', `$${base}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', 3]]]]]],
|
|
1177
|
+
({ out, o, slot }) => [
|
|
1178
|
+
['f64.store',
|
|
1179
|
+
['i32.add', ['local.get', `$${out}`], ['i32.shl', ['local.get', `$${o}`], ['i32.const', 3]]],
|
|
1180
|
+
['f64.load', ['i32.add', ['local.get', `$${slot}`], ['i32.const', 16]]]]])
|
|
1181
|
+
|
|
1182
|
+
// Object.entries for an OBJECT — alloc 2-slot ARRAY pair {key, value} for each
|
|
1183
|
+
// schema slot (key from src+i*8, value from base+i*8) then each dyn slot
|
|
1184
|
+
// (key@slot+8, value@slot+16) and box the pair into out[o*8].
|
|
1185
|
+
const objectEntriesFromTemp = (t) => emitEnumerateObject(t,
|
|
1186
|
+
({ out, o, src, base, i, pair }) => [
|
|
1187
|
+
['local.set', `$${pair}`, ['call', '$__alloc_hdr', ['i32.const', 2], ['i32.const', 2]]],
|
|
1188
|
+
['i64.store', ['local.get', `$${pair}`],
|
|
1189
|
+
['i64.load', ['i32.add', ['local.get', `$${src}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', 3]]]]],
|
|
1190
|
+
['f64.store', ['i32.add', ['local.get', `$${pair}`], ['i32.const', 8]],
|
|
1191
|
+
['f64.load', ['i32.add', ['local.get', `$${base}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', 3]]]]],
|
|
1192
|
+
['f64.store',
|
|
1193
|
+
['i32.add', ['local.get', `$${out}`], ['i32.shl', ['local.get', `$${o}`], ['i32.const', 3]]],
|
|
1194
|
+
mkPtrIR(PTR.ARRAY, 0, ['local.get', `$${pair}`])]],
|
|
1195
|
+
({ out, o, slot, pair }) => [
|
|
1196
|
+
['local.set', `$${pair}`, ['call', '$__alloc_hdr', ['i32.const', 2], ['i32.const', 2]]],
|
|
1197
|
+
['i64.store', ['local.get', `$${pair}`],
|
|
1198
|
+
['i64.load', ['i32.add', ['local.get', `$${slot}`], ['i32.const', 8]]]],
|
|
1199
|
+
['f64.store', ['i32.add', ['local.get', `$${pair}`], ['i32.const', 8]],
|
|
1200
|
+
['f64.load', ['i32.add', ['local.get', `$${slot}`], ['i32.const', 16]]]],
|
|
1201
|
+
['f64.store',
|
|
1202
|
+
['i32.add', ['local.get', `$${out}`], ['i32.shl', ['local.get', `$${o}`], ['i32.const', 3]]],
|
|
1203
|
+
mkPtrIR(PTR.ARRAY, 0, ['local.get', `$${pair}`])]])
|