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