jz 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +275 -255
- package/cli.js +8 -51
- package/index.js +166 -31
- package/{src/host.js → interop.js} +291 -88
- package/module/array.js +181 -71
- package/module/collection.js +222 -8
- package/module/console.js +1 -1
- package/module/core.js +277 -118
- package/module/date.js +9 -5
- package/module/function.js +11 -1
- package/module/json.js +361 -55
- package/module/math.js +601 -130
- package/module/number.js +390 -227
- package/module/object.js +252 -34
- package/module/regex.js +123 -16
- package/module/schema.js +15 -1
- package/module/string.js +545 -96
- package/module/timer.js +1 -1
- package/module/typedarray.js +674 -57
- package/package.json +10 -7
- package/src/abi/array.js +89 -0
- package/src/abi/index.js +35 -0
- package/src/abi/number.js +137 -0
- package/src/abi/object.js +57 -0
- package/src/abi/string.js +529 -0
- package/src/analyze.js +1870 -485
- package/src/assemble.js +27 -12
- package/src/autoload.js +13 -1
- package/src/compile.js +446 -179
- package/src/ctx.js +85 -18
- package/src/emit.js +705 -196
- package/src/infer.js +548 -0
- package/src/ir.js +291 -23
- package/src/jzify.js +851 -132
- package/src/narrow.js +433 -251
- package/src/optimize.js +126 -122
- package/src/plan.js +703 -34
- package/src/prepare.js +833 -153
- package/src/resolve.js +85 -0
- package/src/vectorize.js +99 -18
- package/src/ast.js +0 -160
- package/src/auto-config.js +0 -120
- package/src/fuse.js +0 -159
package/module/object.js
CHANGED
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
* @module object
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { typed, asF64, asI64, temp, tempI32, allocPtr, needsDynShadow, mkPtrIR, extractF64Bits, appendStaticSlots, slotAddr, elemStore } from '../src/ir.js'
|
|
10
|
+
import { typed, asF64, asI64, temp, tempI32, allocPtr, needsDynShadow, mkPtrIR, extractF64Bits, appendStaticSlots, slotAddr, elemLoad, elemStore } from '../src/ir.js'
|
|
11
11
|
import { emit } from '../src/emit.js'
|
|
12
12
|
import { valTypeOf, lookupValType, VAL, repOf, updateRep, shapeOf } from '../src/analyze.js'
|
|
13
13
|
import { ctx, err, inc, PTR, LAYOUT } from '../src/ctx.js'
|
|
14
|
+
import { includeModule } from '../src/autoload.js'
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
export default (ctx) => {
|
|
@@ -30,8 +31,11 @@ export default (ctx) => {
|
|
|
30
31
|
// schema-slot writes to offsets >= 8 land out-of-bounds.
|
|
31
32
|
const target = takeLiteralTarget()
|
|
32
33
|
const merged = target ? ctx.schema.resolve(target) : null
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
// Register the empty schema so schemaId always indexes a real schema.list
|
|
35
|
+
// entry — __json_obj and dyn-get load keys via $__schema_tbl[sid] and would
|
|
36
|
+
// crash on an unregistered id 0 (table left uninitialized when list empty).
|
|
37
|
+
const schemaId = merged ? ctx.schema.idOf(target) : ctx.schema.register([])
|
|
38
|
+
const cap = ctx.abi.object.ops.allocSlots(merged ? merged.length : 0)
|
|
35
39
|
return mkPtrIR(PTR.OBJECT, schemaId, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const', cap]])
|
|
36
40
|
}
|
|
37
41
|
|
|
@@ -63,7 +67,7 @@ export default (ctx) => {
|
|
|
63
67
|
// R: Static data segment for objects of pure-literal property values (own-memory only).
|
|
64
68
|
// Even with shadow needed, we can skip alloc + N stores; just feed literal values to __dyn_set.
|
|
65
69
|
const shadow = needsDynShadow(target)
|
|
66
|
-
if (values.length >= 2 && !ctx.memory.shared) {
|
|
70
|
+
if (values.length >= 2 && values.length === schema.length && !ctx.memory.shared) {
|
|
67
71
|
const emitted = values.map(emit)
|
|
68
72
|
// asF64 folds i32.const → f64.const so int-literal values also qualify.
|
|
69
73
|
const slots = emitted.map(v => extractF64Bits(asF64(v)))
|
|
@@ -82,16 +86,16 @@ export default (ctx) => {
|
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
const body = [
|
|
85
|
-
['local.set', `$${t}`, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const',
|
|
89
|
+
['local.set', `$${t}`, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const', ctx.abi.object.ops.allocSlots(schema.length)]]],
|
|
86
90
|
]
|
|
87
91
|
for (let i = 0; i < values.length; i++)
|
|
88
|
-
body.push(['
|
|
92
|
+
body.push(ctx.abi.object.ops.store(['local.get', `$${t}`], i, asF64(emit(values[i]))))
|
|
89
93
|
body.push(['local.set', `$${ptr}`, mkPtrIR(PTR.OBJECT, schemaId, ['local.get', `$${t}`])])
|
|
90
94
|
if (shadow) {
|
|
91
95
|
inc('__dyn_set')
|
|
92
96
|
for (let i = 0; i < schema.length; i++)
|
|
93
97
|
body.push(['drop', ['call', '$__dyn_set', ['i64.reinterpret_f64', ['local.get', `$${ptr}`]], asI64(emit(['str', String(schema[i])])),
|
|
94
|
-
['
|
|
98
|
+
ctx.abi.object.ops.loadBits(['local.get', `$${t}`], i)]])
|
|
95
99
|
}
|
|
96
100
|
body.push(['local.get', `$${ptr}`])
|
|
97
101
|
|
|
@@ -100,14 +104,82 @@ export default (ctx) => {
|
|
|
100
104
|
|
|
101
105
|
// === Object static methods ===
|
|
102
106
|
|
|
107
|
+
ctx.core.emit['Object.freeze'] = (obj) => asF64(emit(obj))
|
|
108
|
+
|
|
109
|
+
// Object.isExtensible / isSealed / isFrozen.
|
|
110
|
+
// jz fixes an object's schema at construction: a `{…}` literal can
|
|
111
|
+
// neither grow nor lose keys, so an OBJECT value is non-extensible and
|
|
112
|
+
// sealed; its slots stay writable, so it is not frozen. Arrays, maps,
|
|
113
|
+
// sets and hashes grow dynamically → extensible. Primitives are
|
|
114
|
+
// non-objects → ES2015 reports them sealed & frozen, not extensible.
|
|
115
|
+
const extKind = (obj) => {
|
|
116
|
+
const t = typeof obj === 'string' ? lookupValType(obj) : valTypeOf(obj)
|
|
117
|
+
if (t === VAL.OBJECT) return { ext: 0, sealed: 1, frozen: 0 }
|
|
118
|
+
if (t === VAL.NUMBER || t === VAL.STRING || t === VAL.BIGINT) return { ext: 0, sealed: 1, frozen: 1 }
|
|
119
|
+
return { ext: 1, sealed: 0, frozen: 0 }
|
|
120
|
+
}
|
|
121
|
+
const objQuery = (pick) => (obj) => {
|
|
122
|
+
const v = pick(extKind(obj))
|
|
123
|
+
if (obj == null) return typed(['f64.const', v], 'f64')
|
|
124
|
+
return typed(['block', ['result', 'f64'], ['drop', asF64(emit(obj))], ['f64.const', v]], 'f64')
|
|
125
|
+
}
|
|
126
|
+
ctx.core.emit['Object.isExtensible'] = objQuery((k) => k.ext)
|
|
127
|
+
ctx.core.emit['Object.isSealed'] = objQuery((k) => k.sealed)
|
|
128
|
+
ctx.core.emit['Object.isFrozen'] = objQuery((k) => k.frozen)
|
|
129
|
+
|
|
130
|
+
// RequireObjectCoercible: Object.keys/values/entries reject null & undefined
|
|
131
|
+
// with a TypeError. A literal lowers to a [null, value] node — so `null` is
|
|
132
|
+
// [null, null] and `undefined` is [null, undefined] (both JSON-print alike);
|
|
133
|
+
// a missing argument arrives as JS undefined. Anything else (incl.
|
|
134
|
+
// booleans/numbers, which JS boxes) is left to the normal path.
|
|
135
|
+
const isNullishLiteral = (node) => node === undefined
|
|
136
|
+
|| (Array.isArray(node) && node.length === 2 && node[0] == null && node[1] == null)
|
|
137
|
+
const requireCoercible = (node) => {
|
|
138
|
+
if (!isNullishLiteral(node)) return null
|
|
139
|
+
ctx.runtime.throws = true
|
|
140
|
+
return typed(['block', ['result', 'f64'], ['throw', '$__jz_err', ['f64.const', 0]]], 'f64')
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Arrays and (coerced) strings expose their indices as own enumerable
|
|
144
|
+
// keys — Object.keys/entries iterate "0".."n-1". `arrayValType` mirrors
|
|
145
|
+
// `stringValType` below: a string arg is a variable name, anything else
|
|
146
|
+
// an AST node.
|
|
147
|
+
const arrayValType = (obj) => (typeof obj === 'string' ? lookupValType(obj) : valTypeOf(obj)) === VAL.ARRAY
|
|
148
|
+
// Index-string key array for an array-like receiver. `lenCall` is the
|
|
149
|
+
// length builtin: __len for jz arrays, __str_len for strings.
|
|
150
|
+
const emitIndexKeys = (obj, lenCall) => {
|
|
151
|
+
inc(lenCall, '__to_str')
|
|
152
|
+
const v = temp('ik'), i = tempI32('iki'), len = tempI32('ikl')
|
|
153
|
+
const vPtr = () => ['i64.reinterpret_f64', ['local.get', `$${v}`]]
|
|
154
|
+
const out = allocPtr({ type: PTR.ARRAY, len: ['local.get', `$${len}`], tag: 'ik' })
|
|
155
|
+
const id = ctx.func.uniq++
|
|
156
|
+
return typed(['block', ['result', 'f64'],
|
|
157
|
+
['local.set', `$${v}`, asF64(emit(obj))],
|
|
158
|
+
['local.set', `$${len}`, ['call', `$${lenCall}`, vPtr()]],
|
|
159
|
+
out.init,
|
|
160
|
+
['local.set', `$${i}`, ['i32.const', 0]],
|
|
161
|
+
['block', `$brk${id}`, ['loop', `$loop${id}`,
|
|
162
|
+
['br_if', `$brk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${len}`]]],
|
|
163
|
+
elemStore(out.local, i, ['f64.reinterpret_i64',
|
|
164
|
+
['call', '$__to_str', ['i64.reinterpret_f64', ['f64.convert_i32_s', ['local.get', `$${i}`]]]]]),
|
|
165
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
166
|
+
['br', `$loop${id}`]]],
|
|
167
|
+
out.ptr], 'f64')
|
|
168
|
+
}
|
|
169
|
+
|
|
103
170
|
ctx.core.emit['Object.keys'] = (obj) => {
|
|
171
|
+
const nullish = requireCoercible(obj)
|
|
172
|
+
if (nullish) return nullish
|
|
104
173
|
if (isHashTyped(obj)) return emitHashKeys(obj)
|
|
174
|
+
if (arrayValType(obj)) return emitIndexKeys(obj, '__len')
|
|
175
|
+
if (stringValType(obj)) return emitIndexKeys(obj, '__str_len')
|
|
105
176
|
const schema = resolveSchema(obj)
|
|
106
177
|
if (schema) return emitStringArray(schema)
|
|
107
178
|
// Receiver type unknown at compile time. Dispatch on ptr-type at
|
|
108
179
|
// runtime: HASH walks the probe table, anything else returns [].
|
|
109
180
|
return emitRuntimeKeys(obj)
|
|
110
181
|
}
|
|
182
|
+
ctx.core.emit['Object.getOwnPropertyNames'] = ctx.core.emit['Object.keys']
|
|
111
183
|
|
|
112
184
|
// Object.prototype.hasOwnProperty(key) — own-property presence check.
|
|
113
185
|
// Compile-time fold for literal keys against object literals or variables
|
|
@@ -132,8 +204,36 @@ export default (ctx) => {
|
|
|
132
204
|
ctx.core.emit[`.${VAL.ARRAY}:hasOwnProperty`] = ctx.core.emit['.hasOwnProperty']
|
|
133
205
|
ctx.core.emit[`.${VAL.STRING}:hasOwnProperty`] = ctx.core.emit['.hasOwnProperty']
|
|
134
206
|
ctx.core.emit[`.${VAL.CLOSURE}:hasOwnProperty`] = ctx.core.emit['.hasOwnProperty']
|
|
207
|
+
// Object.hasOwn(o, k) — ES2022 static equivalent of o.hasOwnProperty(k).
|
|
208
|
+
// Reuses the same own-property emitter; receiver-type variants above apply.
|
|
209
|
+
ctx.core.emit['Object.hasOwn'] = (obj, key) => ctx.core.emit['.hasOwnProperty'](obj, key)
|
|
210
|
+
|
|
211
|
+
// String primitives are coerced to exotic String objects whose own enumerable
|
|
212
|
+
// properties are the indexed characters. Object.values/entries iterate them.
|
|
213
|
+
const stringValType = (obj) => (typeof obj === 'string' ? lookupValType(obj) : valTypeOf(obj)) === VAL.STRING
|
|
135
214
|
|
|
136
215
|
ctx.core.emit['Object.values'] = (obj) => {
|
|
216
|
+
const nullish = requireCoercible(obj)
|
|
217
|
+
if (nullish) return nullish
|
|
218
|
+
if (stringValType(obj)) {
|
|
219
|
+
inc('__str_idx', '__str_len')
|
|
220
|
+
const s = temp('osv'), i = tempI32('osvi'), len = tempI32('osvl')
|
|
221
|
+
const sPtr = () => ['i64.reinterpret_f64', ['local.get', `$${s}`]]
|
|
222
|
+
const out = allocPtr({ type: PTR.ARRAY, len: ['local.get', `$${len}`], tag: 'osv' })
|
|
223
|
+
const id = ctx.func.uniq++
|
|
224
|
+
return typed(['block', ['result', 'f64'],
|
|
225
|
+
['local.set', `$${s}`, asF64(emit(obj))],
|
|
226
|
+
['local.set', `$${len}`, ['call', '$__str_len', sPtr()]],
|
|
227
|
+
out.init,
|
|
228
|
+
['local.set', `$${i}`, ['i32.const', 0]],
|
|
229
|
+
['block', `$brk${id}`, ['loop', `$loop${id}`,
|
|
230
|
+
['br_if', `$brk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${len}`]]],
|
|
231
|
+
elemStore(out.local, i, ['call', '$__str_idx', sPtr(), ['local.get', `$${i}`]]),
|
|
232
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
233
|
+
['br', `$loop${id}`]]],
|
|
234
|
+
out.ptr], 'f64')
|
|
235
|
+
}
|
|
236
|
+
if (arrayValType(obj)) { inc('__arr_from'); return typed(['call', '$__arr_from', asI64(emit(obj))], 'f64') }
|
|
137
237
|
if (isHashTyped(obj)) return emitHashValues(obj)
|
|
138
238
|
const schema = resolveSchema(obj)
|
|
139
239
|
if (!schema) return emitRuntimeValues(obj)
|
|
@@ -144,12 +244,59 @@ export default (ctx) => {
|
|
|
144
244
|
const body = [['local.set', `$${t}`, va], out.init,
|
|
145
245
|
['local.set', `$${base}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]]]
|
|
146
246
|
for (let i = 0; i < n; i++)
|
|
147
|
-
body.push(['f64.store', slotAddr(out.local, i), ['
|
|
247
|
+
body.push(['f64.store', slotAddr(out.local, i), ctx.abi.object.ops.load(['local.get', `$${base}`], i)])
|
|
148
248
|
body.push(out.ptr)
|
|
149
249
|
return typed(['block', ['result', 'f64'], ...body], 'f64')
|
|
150
250
|
}
|
|
151
251
|
|
|
152
252
|
ctx.core.emit['Object.entries'] = (obj) => {
|
|
253
|
+
const nullish = requireCoercible(obj)
|
|
254
|
+
if (nullish) return nullish
|
|
255
|
+
if (stringValType(obj)) {
|
|
256
|
+
inc('__str_idx', '__str_len', '__to_str')
|
|
257
|
+
const s = temp('oes'), i = tempI32('oesi'), len = tempI32('oesl'), pair = tempI32('oep')
|
|
258
|
+
const sPtr = () => ['i64.reinterpret_f64', ['local.get', `$${s}`]]
|
|
259
|
+
const out = allocPtr({ type: PTR.ARRAY, len: ['local.get', `$${len}`], tag: 'oes' })
|
|
260
|
+
const id = ctx.func.uniq++
|
|
261
|
+
return typed(['block', ['result', 'f64'],
|
|
262
|
+
['local.set', `$${s}`, asF64(emit(obj))],
|
|
263
|
+
['local.set', `$${len}`, ['call', '$__str_len', sPtr()]],
|
|
264
|
+
out.init,
|
|
265
|
+
['local.set', `$${i}`, ['i32.const', 0]],
|
|
266
|
+
['block', `$brk${id}`, ['loop', `$loop${id}`,
|
|
267
|
+
['br_if', `$brk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${len}`]]],
|
|
268
|
+
['local.set', `$${pair}`, ['call', '$__alloc_hdr', ['i32.const', 2], ['i32.const', 2]]],
|
|
269
|
+
['f64.store', slotAddr(pair, 0), ['f64.reinterpret_i64',
|
|
270
|
+
['call', '$__to_str', ['i64.reinterpret_f64', ['f64.convert_i32_s', ['local.get', `$${i}`]]]]]],
|
|
271
|
+
['f64.store', slotAddr(pair, 1), ['call', '$__str_idx', sPtr(), ['local.get', `$${i}`]]],
|
|
272
|
+
elemStore(out.local, i, mkPtrIR(PTR.ARRAY, 0, ['local.get', `$${pair}`])),
|
|
273
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
274
|
+
['br', `$loop${id}`]]],
|
|
275
|
+
out.ptr], 'f64')
|
|
276
|
+
}
|
|
277
|
+
if (arrayValType(obj)) {
|
|
278
|
+
inc('__len', '__to_str', '__ptr_offset', '__alloc_hdr')
|
|
279
|
+
const v = temp('oea'), i = tempI32('oeai'), len = tempI32('oeal'), base = tempI32('oeab'), pair = tempI32('oeap')
|
|
280
|
+
const vPtr = () => ['i64.reinterpret_f64', ['local.get', `$${v}`]]
|
|
281
|
+
const out = allocPtr({ type: PTR.ARRAY, len: ['local.get', `$${len}`], tag: 'oea' })
|
|
282
|
+
const id = ctx.func.uniq++
|
|
283
|
+
return typed(['block', ['result', 'f64'],
|
|
284
|
+
['local.set', `$${v}`, asF64(emit(obj))],
|
|
285
|
+
['local.set', `$${len}`, ['call', '$__len', vPtr()]],
|
|
286
|
+
out.init,
|
|
287
|
+
['local.set', `$${base}`, ['call', '$__ptr_offset', vPtr()]],
|
|
288
|
+
['local.set', `$${i}`, ['i32.const', 0]],
|
|
289
|
+
['block', `$brk${id}`, ['loop', `$loop${id}`,
|
|
290
|
+
['br_if', `$brk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${len}`]]],
|
|
291
|
+
['local.set', `$${pair}`, ['call', '$__alloc_hdr', ['i32.const', 2], ['i32.const', 2]]],
|
|
292
|
+
['f64.store', slotAddr(pair, 0), ['f64.reinterpret_i64',
|
|
293
|
+
['call', '$__to_str', ['i64.reinterpret_f64', ['f64.convert_i32_s', ['local.get', `$${i}`]]]]]],
|
|
294
|
+
['f64.store', slotAddr(pair, 1), elemLoad(base, i)],
|
|
295
|
+
elemStore(out.local, i, mkPtrIR(PTR.ARRAY, 0, ['local.get', `$${pair}`])),
|
|
296
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
297
|
+
['br', `$loop${id}`]]],
|
|
298
|
+
out.ptr], 'f64')
|
|
299
|
+
}
|
|
153
300
|
if (isHashTyped(obj)) return emitHashEntries(obj)
|
|
154
301
|
const schema = resolveSchema(obj)
|
|
155
302
|
if (!schema) return emitRuntimeEntries(obj)
|
|
@@ -163,7 +310,7 @@ export default (ctx) => {
|
|
|
163
310
|
body.push(
|
|
164
311
|
['local.set', `$${pair}`, ['call', '$__alloc_hdr', ['i32.const', 2], ['i32.const', 2]]],
|
|
165
312
|
['f64.store', slotAddr(pair, 0), emit(['str', schema[i]])],
|
|
166
|
-
['f64.store', slotAddr(pair, 1), ['
|
|
313
|
+
['f64.store', slotAddr(pair, 1), ctx.abi.object.ops.load(['local.get', `$${base}`], i)],
|
|
167
314
|
['f64.store', slotAddr(out.local, i), mkPtrIR(PTR.ARRAY, 0, ['local.get', `$${pair}`])])
|
|
168
315
|
}
|
|
169
316
|
body.push(out.ptr)
|
|
@@ -171,6 +318,9 @@ export default (ctx) => {
|
|
|
171
318
|
}
|
|
172
319
|
|
|
173
320
|
ctx.core.emit['Object.assign'] = (target, ...sources) => {
|
|
321
|
+
// RequireObjectCoercible(target) — null/undefined is a TypeError.
|
|
322
|
+
const nullish = requireCoercible(target)
|
|
323
|
+
if (nullish) return nullish
|
|
174
324
|
if (typeof target === 'string') {
|
|
175
325
|
const vt = repOf(target)?.val
|
|
176
326
|
if (vt && vt !== VAL.OBJECT) {
|
|
@@ -183,11 +333,14 @@ export default (ctx) => {
|
|
|
183
333
|
const boxedSchema = ['__inner__', ...allProps]
|
|
184
334
|
const schemaId = ctx.schema.register(boxedSchema)
|
|
185
335
|
ctx.schema.vars.set(target, schemaId)
|
|
336
|
+
// Emit-time rep mutation: Object.assign's target gains a freshly-registered
|
|
337
|
+
// boxed-schema binding here; downstream `.prop` reads in the same emit pass
|
|
338
|
+
// depend on schemaId being live on the rep, not just in ctx.schema.vars.
|
|
186
339
|
updateRep(target, { schemaId })
|
|
187
340
|
const t = tempI32('bx'), s = temp('bs')
|
|
188
341
|
const body = [
|
|
189
|
-
['local.set', `$${t}`, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const',
|
|
190
|
-
|
|
342
|
+
['local.set', `$${t}`, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const', ctx.abi.object.ops.allocSlots(boxedSchema.length)]]],
|
|
343
|
+
ctx.abi.object.ops.store(['local.get', `$${t}`], 0, asF64(emit(target))),
|
|
191
344
|
]
|
|
192
345
|
const sBase = tempI32('sb')
|
|
193
346
|
for (const source of sources) {
|
|
@@ -197,7 +350,7 @@ export default (ctx) => {
|
|
|
197
350
|
for (let si = 0; si < sSchema.length; si++) {
|
|
198
351
|
const ti = boxedSchema.indexOf(sSchema[si])
|
|
199
352
|
if (ti < 0) continue
|
|
200
|
-
body.push(['
|
|
353
|
+
body.push(ctx.abi.object.ops.store(['local.get', `$${t}`], ti, ctx.abi.object.ops.load(['local.get', `$${sBase}`], si)))
|
|
201
354
|
}
|
|
202
355
|
}
|
|
203
356
|
body.push(['local.set', `$${target}`,
|
|
@@ -207,20 +360,22 @@ export default (ctx) => {
|
|
|
207
360
|
}
|
|
208
361
|
}
|
|
209
362
|
const tSchema = resolveSchema(target)
|
|
363
|
+
const sourceSchemas = sources.map(resolveSchema)
|
|
210
364
|
if (!tSchema) err('Object.assign: target needs known schema')
|
|
365
|
+
if (sourceSchemas.some(s => !s)) return emitDynamicAssign(target, sources, sourceSchemas)
|
|
211
366
|
const t = temp('at'), s = temp('as')
|
|
212
367
|
const tBase = tempI32('tb'), sBase2 = tempI32('sb')
|
|
213
368
|
const body = [['local.set', `$${t}`, asF64(emit(target))],
|
|
214
369
|
['local.set', `$${tBase}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]]]
|
|
215
|
-
for (
|
|
216
|
-
const
|
|
217
|
-
|
|
370
|
+
for (let i = 0; i < sources.length; i++) {
|
|
371
|
+
const source = sources[i]
|
|
372
|
+
const sSchema = sourceSchemas[i]
|
|
218
373
|
body.push(['local.set', `$${s}`, asF64(emit(source))])
|
|
219
374
|
body.push(['local.set', `$${sBase2}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${s}`]]]])
|
|
220
375
|
for (let si = 0; si < sSchema.length; si++) {
|
|
221
376
|
const ti = tSchema.indexOf(sSchema[si])
|
|
222
377
|
if (ti < 0) continue
|
|
223
|
-
body.push(['
|
|
378
|
+
body.push(ctx.abi.object.ops.store(['local.get', `$${tBase}`], ti, ctx.abi.object.ops.load(['local.get', `$${sBase2}`], si)))
|
|
224
379
|
}
|
|
225
380
|
}
|
|
226
381
|
body.push(['local.get', `$${t}`])
|
|
@@ -231,8 +386,12 @@ export default (ctx) => {
|
|
|
231
386
|
err('Object.defineProperty descriptor semantics are outside jz scope; jzify only folds static bundler export helpers')
|
|
232
387
|
}
|
|
233
388
|
|
|
234
|
-
// Object.fromEntries(arr) → creates HASH from array of [key, value] pairs
|
|
389
|
+
// Object.fromEntries(arr) → creates HASH from array of [key, value] pairs.
|
|
390
|
+
// Spec step 1 is RequireObjectCoercible(iterable): a missing/nullish argument
|
|
391
|
+
// is a TypeError, not an `emit(undefined)` compiler crash.
|
|
235
392
|
ctx.core.emit['Object.fromEntries'] = (arr) => {
|
|
393
|
+
const nullishThrow = requireCoercible(arr)
|
|
394
|
+
if (nullishThrow) return nullishThrow
|
|
236
395
|
inc('__hash_new', '__hash_set')
|
|
237
396
|
inc('__str_hash', '__str_eq')
|
|
238
397
|
const va = asF64(emit(arr))
|
|
@@ -267,6 +426,7 @@ export default (ctx) => {
|
|
|
267
426
|
// Header propsPtr lives at $off-16 (current ARRAY layout). We alias src's hash
|
|
268
427
|
// by copying the slot; __dyn_move covers the shifted-array case where props
|
|
269
428
|
// were migrated to the global __dyn_props.
|
|
429
|
+
includeModule('array')
|
|
270
430
|
inc('__arr_from', '__dyn_move', '__ptr_offset')
|
|
271
431
|
const src = temp('ocs')
|
|
272
432
|
const dst = temp('ocd')
|
|
@@ -289,6 +449,7 @@ export default (ctx) => {
|
|
|
289
449
|
if (!schema) {
|
|
290
450
|
if (protoType == null) {
|
|
291
451
|
const value = temp('ocr')
|
|
452
|
+
includeModule('array')
|
|
292
453
|
inc('__arr_from', '__dyn_move', '__ptr_offset')
|
|
293
454
|
const dst2 = temp('ocd')
|
|
294
455
|
const srcOff2 = tempI32('ocso')
|
|
@@ -318,12 +479,12 @@ export default (ctx) => {
|
|
|
318
479
|
const srcBase = tempI32('cb')
|
|
319
480
|
const body = [
|
|
320
481
|
['local.set', `$${s}`, asF64(emit(proto))],
|
|
321
|
-
['local.set', `$${t}`, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const',
|
|
482
|
+
['local.set', `$${t}`, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const', ctx.abi.object.ops.allocSlots(n)]]],
|
|
322
483
|
['local.set', `$${srcBase}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${s}`]]]],
|
|
323
484
|
]
|
|
324
485
|
// Copy all properties from proto
|
|
325
486
|
for (let i = 0; i < n; i++)
|
|
326
|
-
body.push(['
|
|
487
|
+
body.push(ctx.abi.object.ops.store(['local.get', `$${t}`], i, ctx.abi.object.ops.load(['local.get', `$${srcBase}`], i)))
|
|
327
488
|
body.push(mkPtrIR(PTR.OBJECT, schemaId, ['local.get', `$${t}`]))
|
|
328
489
|
return typed(['block', ['result', 'f64'], ...body], 'f64')
|
|
329
490
|
}
|
|
@@ -331,6 +492,57 @@ export default (ctx) => {
|
|
|
331
492
|
|
|
332
493
|
// --- Helpers ---
|
|
333
494
|
|
|
495
|
+
// Used only after the target schema is known. Unknown HASH targets can grow by
|
|
496
|
+
// returning a new pointer, which would not preserve aliases to the old value.
|
|
497
|
+
function emitDynamicAssign(target, sources, sourceSchemas = sources.map(resolveSchema)) {
|
|
498
|
+
includeModule('collection')
|
|
499
|
+
inc('__hash_set', '__dyn_get_any', '__ptr_offset', '__len')
|
|
500
|
+
const t = temp('adt'), s = temp('ads'), sBase = tempI32('adsb')
|
|
501
|
+
const keys = temp('adk'), keysBase = tempI32('adkb'), len = tempI32('adn')
|
|
502
|
+
const i = tempI32('adi'), key = temp('adkey')
|
|
503
|
+
const id = ctx.func.uniq++
|
|
504
|
+
const body = [['local.set', `$${t}`, asF64(emit(target))]]
|
|
505
|
+
|
|
506
|
+
for (let si = 0; si < sources.length; si++) {
|
|
507
|
+
const source = sources[si]
|
|
508
|
+
const sSchema = sourceSchemas[si]
|
|
509
|
+
body.push(['local.set', `$${s}`, asF64(emit(source))])
|
|
510
|
+
if (sSchema) {
|
|
511
|
+
body.push(['local.set', `$${sBase}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${s}`]]]])
|
|
512
|
+
for (let pi = 0; pi < sSchema.length; pi++)
|
|
513
|
+
body.push(['local.set', `$${t}`, ['f64.reinterpret_i64',
|
|
514
|
+
['call', '$__hash_set', ['i64.reinterpret_f64', ['local.get', `$${t}`]],
|
|
515
|
+
asI64(emit(['str', String(sSchema[pi])])),
|
|
516
|
+
ctx.abi.object.ops.loadBits(['local.get', `$${sBase}`], pi)]]])
|
|
517
|
+
continue
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
body.push(
|
|
521
|
+
['local.set', `$${keys}`, runtimeKeysFromTemp(s, 'adk')],
|
|
522
|
+
['local.set', `$${keysBase}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${keys}`]]]],
|
|
523
|
+
['local.set', `$${len}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${keys}`]]]],
|
|
524
|
+
['local.set', `$${i}`, ['i32.const', 0]],
|
|
525
|
+
['block', `$adbrk${id}_${si}`, ['loop', `$adloop${id}_${si}`,
|
|
526
|
+
['br_if', `$adbrk${id}_${si}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${len}`]]],
|
|
527
|
+
['local.set', `$${key}`, ['f64.load',
|
|
528
|
+
['i32.add', ['local.get', `$${keysBase}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', 3]]]]],
|
|
529
|
+
['local.set', `$${t}`, ['f64.reinterpret_i64',
|
|
530
|
+
['call', '$__hash_set',
|
|
531
|
+
['i64.reinterpret_f64', ['local.get', `$${t}`]],
|
|
532
|
+
['i64.reinterpret_f64', ['local.get', `$${key}`]],
|
|
533
|
+
['call', '$__dyn_get_any',
|
|
534
|
+
['i64.reinterpret_f64', ['local.get', `$${s}`]],
|
|
535
|
+
['i64.reinterpret_f64', ['local.get', `$${key}`]]]]]],
|
|
536
|
+
['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
|
|
537
|
+
['br', `$adloop${id}_${si}`]]])
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (typeof target === 'string' && ctx.func.locals.get(target) === 'f64')
|
|
541
|
+
body.push(['local.set', `$${target}`, ['local.get', `$${t}`]])
|
|
542
|
+
body.push(['local.get', `$${t}`])
|
|
543
|
+
return typed(['block', ['result', 'f64'], ...body], 'f64')
|
|
544
|
+
}
|
|
545
|
+
|
|
334
546
|
function resolveSchema(obj) {
|
|
335
547
|
if (typeof obj === 'string') return ctx.schema.resolve(obj)
|
|
336
548
|
if (Array.isArray(obj) && obj[0] === '{}')
|
|
@@ -338,7 +550,7 @@ function resolveSchema(obj) {
|
|
|
338
550
|
// JSON-shape inferred: JSON.parse(constStr) call or `.prop`/`[i]` chain
|
|
339
551
|
// resolving to a known OBJECT shape carries its key list as `names`.
|
|
340
552
|
const sh = shapeOf(obj)
|
|
341
|
-
if (sh?.
|
|
553
|
+
if (sh?.val === VAL.OBJECT && sh.names) return sh.names
|
|
342
554
|
return null
|
|
343
555
|
}
|
|
344
556
|
|
|
@@ -379,7 +591,7 @@ function emitObjectSpread(props, spreadTarget = takeLiteralTarget()) {
|
|
|
379
591
|
const ptr = temp('objp')
|
|
380
592
|
const src = tempI32('osp')
|
|
381
593
|
|
|
382
|
-
const body = [['local.set', `$${t}`, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const',
|
|
594
|
+
const body = [['local.set', `$${t}`, ['call', '$__alloc_hdr', ['i32.const', 0], ['i32.const', ctx.abi.object.ops.allocSlots(schema.length)]]]]
|
|
383
595
|
|
|
384
596
|
// Process props in order — later props override earlier (JS semantics)
|
|
385
597
|
let srcF
|
|
@@ -388,17 +600,17 @@ function emitObjectSpread(props, spreadTarget = takeLiteralTarget()) {
|
|
|
388
600
|
const sSchema = resolveSchema(p[1])
|
|
389
601
|
if (!sSchema) {
|
|
390
602
|
// Unknown-schema source (e.g. parameter). Override each slot via runtime
|
|
391
|
-
// __dyn_get_or using existing value as fallback.
|
|
392
|
-
|
|
603
|
+
// __dyn_get_or using existing value as fallback.
|
|
604
|
+
includeModule('collection')
|
|
393
605
|
inc('__dyn_get_or')
|
|
394
606
|
srcF ??= temp('ospf')
|
|
395
607
|
body.push(['local.set', `$${srcF}`, asF64(emit(p[1]))])
|
|
396
608
|
for (let i = 0; i < schema.length; i++) {
|
|
397
|
-
const
|
|
398
|
-
body.push(
|
|
609
|
+
const base = ['local.get', `$${t}`]
|
|
610
|
+
body.push(ctx.abi.object.ops.store(base, i,
|
|
399
611
|
['f64.reinterpret_i64', ['call', '$__dyn_get_or', ['i64.reinterpret_f64', ['local.get', `$${srcF}`]],
|
|
400
612
|
asI64(emit(['str', String(schema[i])])),
|
|
401
|
-
|
|
613
|
+
ctx.abi.object.ops.loadBits(base, i)]]))
|
|
402
614
|
}
|
|
403
615
|
continue
|
|
404
616
|
}
|
|
@@ -406,11 +618,11 @@ function emitObjectSpread(props, spreadTarget = takeLiteralTarget()) {
|
|
|
406
618
|
for (let si = 0; si < sSchema.length; si++) {
|
|
407
619
|
const ti = schema.indexOf(sSchema[si])
|
|
408
620
|
if (ti < 0) continue
|
|
409
|
-
body.push(['
|
|
621
|
+
body.push(ctx.abi.object.ops.store(['local.get', `$${t}`], ti, ctx.abi.object.ops.load(['local.get', `$${src}`], si)))
|
|
410
622
|
}
|
|
411
623
|
} else if (Array.isArray(p) && p[0] === ':') {
|
|
412
624
|
const ti = schema.indexOf(p[1])
|
|
413
|
-
if (ti >= 0) body.push(['
|
|
625
|
+
if (ti >= 0) body.push(ctx.abi.object.ops.store(['local.get', `$${t}`], ti, asF64(emit(p[2]))))
|
|
414
626
|
}
|
|
415
627
|
}
|
|
416
628
|
|
|
@@ -419,7 +631,7 @@ function emitObjectSpread(props, spreadTarget = takeLiteralTarget()) {
|
|
|
419
631
|
inc('__dyn_set')
|
|
420
632
|
for (let i = 0; i < schema.length; i++)
|
|
421
633
|
body.push(['drop', ['call', '$__dyn_set', ['i64.reinterpret_f64', ['local.get', `$${ptr}`]], asI64(emit(['str', String(schema[i])])),
|
|
422
|
-
['
|
|
634
|
+
ctx.abi.object.ops.loadBits(['local.get', `$${t}`], i)]])
|
|
423
635
|
}
|
|
424
636
|
body.push(['local.get', `$${ptr}`])
|
|
425
637
|
return typed(['block', ['result', 'f64'], ...body], 'f64')
|
|
@@ -565,16 +777,22 @@ function hashEntriesFromTemp(t) {
|
|
|
565
777
|
// types (ARRAY, nullish, primitives) return an empty array. The empty-array
|
|
566
778
|
// fallback is allocated in all arms for type uniformity at the if boundary.
|
|
567
779
|
function emitRuntimeKeys(obj) {
|
|
780
|
+
const t = temp('rk')
|
|
781
|
+
return typed(['block', ['result', 'f64'],
|
|
782
|
+
['local.set', `$${t}`, asF64(emit(obj))],
|
|
783
|
+
runtimeKeysFromTemp(t, 'rk')], 'f64')
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function runtimeKeysFromTemp(t, tag) {
|
|
568
787
|
inc('__ptr_type')
|
|
569
788
|
// Ensure the schema table global exists even in programs that never use
|
|
570
789
|
// JSON.parse or compile-time schemas — the OBJECT arm reads it at runtime
|
|
571
790
|
// and the watr resolver requires the symbol to be declared.
|
|
572
791
|
if (!ctx.scope.globals.has('__schema_tbl'))
|
|
573
792
|
ctx.scope.globals.set('__schema_tbl', '(global $__schema_tbl (mut i32) (i32.const 0))')
|
|
574
|
-
const
|
|
575
|
-
const empty = allocPtr({ type: PTR.ARRAY, len: 0, tag:
|
|
576
|
-
return
|
|
577
|
-
['local.set', `$${t}`, asF64(emit(obj))],
|
|
793
|
+
const tt = tempI32(`${tag}t`)
|
|
794
|
+
const empty = allocPtr({ type: PTR.ARRAY, len: 0, tag: `${tag}e` })
|
|
795
|
+
return ['block', ['result', 'f64'],
|
|
578
796
|
['local.set', `$${tt}`, ['call', '$__ptr_type', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
579
797
|
['if', ['result', 'f64'],
|
|
580
798
|
['i32.eq', ['local.get', `$${tt}`], ['i32.const', PTR.HASH]],
|
|
@@ -584,7 +802,7 @@ function emitRuntimeKeys(obj) {
|
|
|
584
802
|
['i32.eq', ['local.get', `$${tt}`], ['i32.const', PTR.OBJECT]],
|
|
585
803
|
['i32.ne', ['global.get', '$__schema_tbl'], ['i32.const', 0]]],
|
|
586
804
|
['then', objectKeysFromTemp(t)],
|
|
587
|
-
['else', ['block', ['result', 'f64'], empty.init, empty.ptr]]]]]]
|
|
805
|
+
['else', ['block', ['result', 'f64'], empty.init, empty.ptr]]]]]]
|
|
588
806
|
}
|
|
589
807
|
|
|
590
808
|
function emitRuntimeValues(obj) {
|