jz 0.2.1 → 0.3.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 +6 -25
- package/cli.js +19 -2
- package/index.js +14 -1
- package/module/array.js +66 -9
- package/module/collection.js +68 -35
- package/module/core.js +65 -19
- package/module/date.js +5 -0
- package/module/function.js +2 -1
- package/module/json.js +66 -8
- package/module/number.js +127 -5
- package/module/object.js +88 -1
- package/module/string.js +3 -2
- package/module/typedarray.js +7 -1
- package/package.json +3 -3
- package/src/analyze.js +16 -4
- package/src/assemble.js +544 -0
- package/src/ast.js +160 -0
- package/src/auto-config.js +120 -0
- package/src/autoload.js +4 -1
- package/src/compile.js +22 -620
- package/src/ctx.js +33 -5
- package/src/emit.js +73 -165
- package/src/host.js +12 -0
- package/src/ir.js +13 -0
- package/src/jzify.js +306 -7
- package/src/narrow.js +2 -1
- package/src/optimize.js +298 -24
- package/src/plan.js +220 -34
- package/src/prepare.js +22 -2
- package/src/vectorize.js +3 -12
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ Options are passed as `jz(source, opts)` or `compile(source, opts)`. Common ones
|
|
|
68
68
|
| `imports: { mod: host }` | Wire host namespaces/functions used by `import { fn } from "mod"`; functions may be plain JS functions or `{ fn, returns }` specs. |
|
|
69
69
|
| `memory` | Pass `memory: N` to create owned memory with `N` initial pages, or pass `memory: jz.memory()` / `WebAssembly.Memory` to share memory across modules. |
|
|
70
70
|
| `host: 'js' \| 'wasi'` | Select runtime-service lowering. Default `js` uses small `env.*` imports auto-wired by `jz()`; `wasi` emits WASI Preview 1 imports for wasmtime/wasmer/deno. |
|
|
71
|
-
| `optimize` | `false`/`0` disables optimization, `1` keeps cheap size passes, `true`/`2` is the default, `3` enables aggressive experimental passes,
|
|
71
|
+
| `optimize` | `false`/`0` disables optimization, `1` keeps cheap size passes, `true`/`2` is the default, `3` enables aggressive experimental passes. String aliases `'size'` (unroll/vectorize off, tight scalar caps — smallest wasm), `'balanced'` (= default), `'speed'` (full unroll + SIMD). Object form overrides individual passes/knobs (and accepts `level:` as a number or alias base). |
|
|
72
72
|
| `strict: true` | Reject dynamic fallbacks such as unknown receiver method calls, `obj[k]`, and `for-in` instead of emitting JS-host dynamic dispatch. |
|
|
73
73
|
| `alloc: false` | Omit raw allocator exports like `_alloc`/`_clear` when compiling standalone WASM that never marshals heap values across the host boundary. |
|
|
74
74
|
| `wat: true` | `compile()` returns WAT text instead of a WASM binary. |
|
|
@@ -95,29 +95,6 @@ jz --help
|
|
|
95
95
|
|
|
96
96
|
JZ is a strict functional JS subset. Built-in `jzify` transform extends support to legacy patterns.
|
|
97
97
|
|
|
98
|
-
<!--
|
|
99
|
-
```mermaid
|
|
100
|
-
%%{init: {'flowchart': {'titleTopMargin': 0, 'padding': 0, 'margin': 0}}}%%
|
|
101
|
-
flowchart TB
|
|
102
|
-
classDef plain fill:none,stroke:none,font-size:14px,font-weight:bold,padding:0px,margin:0px
|
|
103
|
-
|
|
104
|
-
subgraph JS[JS — not supported]
|
|
105
|
-
subgraph JZify[JZ + jzify]
|
|
106
|
-
subgraph JZ[JZ strict]
|
|
107
|
-
j1["let/const, arrows, default/rest params, flow, break/continue, try/catch/finally, a[]/a()/a.b, operators, strings, booleans, numbers, std, memory, host"]:::plain
|
|
108
|
-
end
|
|
109
|
-
z1["var, function, arguments, switch, new Foo(), ==, !=, instanceof"]:::plain
|
|
110
|
-
end
|
|
111
|
-
n1["async/await, Promise, generators, this, class, eval, Function, with, Proxy, Reflect, WeakMap, WeakSet, dynamic import, DOM, fetch, Intl, Node APIs"]:::plain
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
style JZ fill:#ffe0b2,stroke-width:0
|
|
115
|
-
style JZify fill:#fff9c4,stroke-width:0
|
|
116
|
-
style JS fill:#ffffff,stroke:#ccc,stroke-width:1px
|
|
117
|
-
style n1 min-width:720px
|
|
118
|
-
```
|
|
119
|
-
-->
|
|
120
|
-
|
|
121
98
|
```
|
|
122
99
|
┌────────────────────────────────────────────────────────────────────────┐
|
|
123
100
|
│ JZify │
|
|
@@ -151,6 +128,10 @@ Not supported
|
|
|
151
128
|
|
|
152
129
|
Numbers pass directly as f64, arrays of ≤ 8 elements return as plain JS arrays (multi-value). Strings, arrays, objects, and typed arrays are heap values — `inst.memory` provides read/write across the boundary:
|
|
153
130
|
|
|
131
|
+
> [!WARNING] jz objects are fixed-layout schemas (like C structs), not dynamic key bags.
|
|
132
|
+
> `memory.Object({ x: 3, y: 4 })` expects the same key order as the jz source `{ x, y }`.
|
|
133
|
+
> `{ y: 4, x: 3 }` with reversed keys will produce wrong values.
|
|
134
|
+
|
|
154
135
|
```js
|
|
155
136
|
const { exports, memory } = jz`
|
|
156
137
|
export let greet = (s) => s.length
|
|
@@ -494,7 +475,7 @@ cc program.c -o program
|
|
|
494
475
|
| [bitwise](bench/bitwise/bitwise.js) | 0.98ms<br>1.3kB | 3.76ms<br>1005 B | fails | 8.79ms<br>1.5kB | 4.86ms<br>355 B | 1.30ms | 5.20ms | 4.15ms | 1.30ms | 14.72ms |
|
|
495
476
|
| [poly](bench/poly/poly.js) | 0.27ms<br>1.4kB | 1.62ms<br>1014 B | fails | 0.73ms<br>1.3kB | 0.81ms<br>359 B | 0.57ms | 0.79ms | 0.89ms | 0.63ms | 0.60ms |
|
|
496
477
|
| [callback](bench/callback/callback.js) | 0.03ms<br>1.6kB | 0.69ms<br>828 B | fails | 1.04ms<br>1.9kB | 0.24ms<br>267 B | 0.08ms | 0.23ms | 0.01ms | 0.12ms | 1.78ms |
|
|
497
|
-
| [json](bench/json/json.js) | 0.
|
|
478
|
+
| [json](bench/json/json.js) | 0.25ms<br>10.9kB | 0.36ms<br>1.2kB | fails | — | — | 0.25ms | 1.16ms | 0.64ms | 0.65ms | 1.20ms |
|
|
498
479
|
| [watr](bench/watr/watr.js) | 1.04ms<br>169.8kB | 1.05ms<br>2.6kB | fails | — | — | — | — | — | — | — |
|
|
499
480
|
|
|
500
481
|
_Numbers from `node bench/bench.mjs` on Apple Silicon. Porffor cells were refreshed with `porf` 0.61.13; `fails` means the latest Porffor compiler/runtime did not complete that benchmark._
|
package/cli.js
CHANGED
|
@@ -11,10 +11,14 @@ import { execFileSync } from 'child_process'
|
|
|
11
11
|
import { parse } from 'subscript/feature/jessie'
|
|
12
12
|
import jz, { compile } from './index.js'
|
|
13
13
|
import jzifyFn, { codegen } from './src/jzify.js'
|
|
14
|
+
import { createRequire } from 'module'
|
|
15
|
+
|
|
16
|
+
const jzRequire = createRequire(import.meta.url)
|
|
17
|
+
const PKG = jzRequire('./package.json')
|
|
14
18
|
|
|
15
19
|
function showHelp() {
|
|
16
20
|
console.log(`
|
|
17
|
-
jz - min JS → WASM compiler
|
|
21
|
+
jz v${PKG.version} - min JS → WASM compiler
|
|
18
22
|
|
|
19
23
|
Usage:
|
|
20
24
|
jz <file.js> Compile JS to WASM (auto-jzify)
|
|
@@ -39,12 +43,19 @@ Options:
|
|
|
39
43
|
--eval, -e Evaluate expression or file
|
|
40
44
|
--wat Output WAT text instead of binary
|
|
41
45
|
--resolve Resolve bare specifiers via Node.js module resolution
|
|
46
|
+
--imports <file> JSON file with host import specs (e.g. {"env":{"fn":{"params":2}}})
|
|
47
|
+
--version, -v Show version number
|
|
42
48
|
`)
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
async function main() {
|
|
46
52
|
const args = process.argv.slice(2)
|
|
47
53
|
|
|
54
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
55
|
+
console.log(PKG.version)
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
48
59
|
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
49
60
|
showHelp()
|
|
50
61
|
return
|
|
@@ -99,13 +110,14 @@ async function handleJzify(args) {
|
|
|
99
110
|
}
|
|
100
111
|
|
|
101
112
|
async function handleCompile(args) {
|
|
102
|
-
let inputFile = null, outputFile = null, wat = false, strict = false, resolveNode = false
|
|
113
|
+
let inputFile = null, outputFile = null, wat = false, strict = false, resolveNode = false, importsFile = null
|
|
103
114
|
|
|
104
115
|
for (let i = 0; i < args.length; i++) {
|
|
105
116
|
if (args[i] === '--output' || args[i] === '-o') outputFile = args[++i]
|
|
106
117
|
else if (args[i] === '--wat') wat = true
|
|
107
118
|
else if (args[i] === '--strict') strict = true
|
|
108
119
|
else if (args[i] === '--resolve') resolveNode = true
|
|
120
|
+
else if (args[i] === '--imports') importsFile = args[++i]
|
|
109
121
|
else if (!inputFile) inputFile = args[i]
|
|
110
122
|
}
|
|
111
123
|
|
|
@@ -170,6 +182,11 @@ async function handleCompile(args) {
|
|
|
170
182
|
...(Object.keys(modules).length && { modules }),
|
|
171
183
|
}
|
|
172
184
|
|
|
185
|
+
if (importsFile) {
|
|
186
|
+
const importsPath = resolve(importsFile)
|
|
187
|
+
opts.imports = JSON.parse(readFileSync(importsPath, 'utf8'))
|
|
188
|
+
}
|
|
189
|
+
|
|
173
190
|
const result = compile(code, opts)
|
|
174
191
|
|
|
175
192
|
if (outputFile === '-') {
|
package/index.js
CHANGED
|
@@ -45,6 +45,7 @@ import prepare, { GLOBALS } from './src/prepare.js'
|
|
|
45
45
|
import compile from './src/compile.js'
|
|
46
46
|
import { emitter } from './src/emit.js'
|
|
47
47
|
import { optimizeFunc, resolveOptimize } from './src/optimize.js'
|
|
48
|
+
import { detectOptimizeConfig } from './src/auto-config.js'
|
|
48
49
|
import jzify from './src/jzify.js'
|
|
49
50
|
import {
|
|
50
51
|
memory as enhanceMemory, instantiate as instantiateRuntime,
|
|
@@ -224,6 +225,16 @@ jz.compile = (code, opts = {}) => {
|
|
|
224
225
|
let parsed = time('parse', () => parse(code))
|
|
225
226
|
if (opts.jzify) parsed = time('jzify', () => jzify(parsed))
|
|
226
227
|
const ast = time('prepare', () => prepare(parsed))
|
|
228
|
+
|
|
229
|
+
// Auto-detect optimization tuning from source characteristics when the user
|
|
230
|
+
// hasn't provided any optimize option.
|
|
231
|
+
if (opts.optimize == null) {
|
|
232
|
+
const autoCfg = detectOptimizeConfig(ast, code)
|
|
233
|
+
if (Object.keys(autoCfg).length) {
|
|
234
|
+
ctx.transform.optimize = resolveOptimize(autoCfg)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
227
238
|
const module = time('compile', () => compile(ast, profiler))
|
|
228
239
|
|
|
229
240
|
// host: 'wasi' — error if the wasm would import any env.__ext_* helper. Those exist
|
|
@@ -249,8 +260,10 @@ jz.compile = (code, opts = {}) => {
|
|
|
249
260
|
// Only valuable to re-run when watr ran (watr is what re-introduces the boundaries).
|
|
250
261
|
if (cfg.watr) {
|
|
251
262
|
const postCfg = { ...cfg, __phase: 'post' }
|
|
263
|
+
// Build global name→type map from ctx.scope.globalTypes for promoteGlobals
|
|
264
|
+
const globalTypesMap = ctx.scope.globalTypes ? new Map([...ctx.scope.globalTypes].map(([k, v]) => [`$${k}`, v])) : null
|
|
252
265
|
time('watrReopt', () => {
|
|
253
|
-
for (const node of optimized) if (Array.isArray(node) && node[0] === 'func') optimizeFunc(node, postCfg)
|
|
266
|
+
for (const node of optimized) if (Array.isArray(node) && node[0] === 'func') optimizeFunc(node, postCfg, globalTypesMap)
|
|
254
267
|
})
|
|
255
268
|
}
|
|
256
269
|
if (opts.wat) return time('watrPrint', () => watrPrint(optimized))
|
package/module/array.js
CHANGED
|
@@ -225,7 +225,8 @@ export default (ctx) => {
|
|
|
225
225
|
'__ptr_offset',
|
|
226
226
|
...(needsArrayDynMove() ? ['__dyn_move', '__hash_new', '__ihash_set_local'] : []),
|
|
227
227
|
],
|
|
228
|
-
__arr_set_idx_ptr: ['__arr_grow', '__ptr_offset'
|
|
228
|
+
__arr_set_idx_ptr: ['__arr_grow', '__ptr_offset'],
|
|
229
|
+
__arr_push1: ['__arr_grow_known', '__ptr_offset'],
|
|
229
230
|
__typed_idx: () => ctx.features.typedarray || ctx.features.external
|
|
230
231
|
? ['__len']
|
|
231
232
|
: ['__len', '__ptr_offset'],
|
|
@@ -247,6 +248,16 @@ export default (ctx) => {
|
|
|
247
248
|
['i32.eq', ['call', '$__ptr_type', ['i64.reinterpret_f64', ['local.get', `$${t}`]]], ['i32.const', PTR.ARRAY]]], 'i32')
|
|
248
249
|
}
|
|
249
250
|
|
|
251
|
+
ctx.core.emit['new.Array'] = (len) => {
|
|
252
|
+
const n = tempI32('alen')
|
|
253
|
+
const nIR = ['local.get', `$${n}`]
|
|
254
|
+
const out = allocPtr({ type: PTR.ARRAY, len: nIR, cap: nIR, tag: 'newarr' })
|
|
255
|
+
return typed(['block', ['result', 'f64'],
|
|
256
|
+
['local.set', `$${n}`, len == null ? ['i32.const', 0] : asI32(emit(len))],
|
|
257
|
+
out.init,
|
|
258
|
+
out.ptr], 'f64')
|
|
259
|
+
}
|
|
260
|
+
|
|
250
261
|
// ARRAY-only indexed read. Inline forwarding-follow + bounds check + load — avoids
|
|
251
262
|
// the redundant double pass through __len then __ptr_offset that both follow forwarding.
|
|
252
263
|
ctx.core.stdlib['__arr_idx'] = `(func $__arr_idx (param $ptr i64) (param $i i32) (result f64)
|
|
@@ -313,8 +324,21 @@ export default (ctx) => {
|
|
|
313
324
|
return `(func $__typed_idx (param $ptr i64) (param $i i32) (result f64)
|
|
314
325
|
(local $t i32) (local $off i32) (local $et i32) (local $len i32) (local $aux i32)
|
|
315
326
|
(local.set $t (i32.wrap_i64 (i64.and (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
316
|
-
(local.set $aux (i32.wrap_i64 (i64.and (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.AUX_SHIFT})) (i64.const ${LAYOUT.AUX_MASK}))))
|
|
317
327
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
328
|
+
;; ARRAY fast path: follow forwarding inline, bounds-check against header len, f64.load — no $__len call.
|
|
329
|
+
(if (i32.and (i32.eq (local.get $t) (i32.const ${PTR.ARRAY})) (i32.ge_u (local.get $off) (i32.const 8)))
|
|
330
|
+
(then
|
|
331
|
+
(block $done
|
|
332
|
+
(loop $follow
|
|
333
|
+
(br_if $done (i32.gt_u (local.get $off) (i32.shl (memory.size) (i32.const 16))))
|
|
334
|
+
(br_if $done (i32.ne (i32.load (i32.sub (local.get $off) (i32.const 4))) (i32.const -1)))
|
|
335
|
+
(local.set $off (i32.load (i32.sub (local.get $off) (i32.const 8))))
|
|
336
|
+
(br $follow)))
|
|
337
|
+
(return (if (result f64)
|
|
338
|
+
(i32.and (i32.ge_s (local.get $i) (i32.const 0)) (i32.lt_u (local.get $i) (i32.load (i32.sub (local.get $off) (i32.const 8)))))
|
|
339
|
+
(then (f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3)))))
|
|
340
|
+
(else (f64.const nan:${UNDEF_NAN}))))))
|
|
341
|
+
(local.set $aux (i32.wrap_i64 (i64.and (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.AUX_SHIFT})) (i64.const ${LAYOUT.AUX_MASK}))))
|
|
318
342
|
(if
|
|
319
343
|
(i32.and
|
|
320
344
|
(i32.eq (local.get $t) (i32.const ${PTR.TYPED}))
|
|
@@ -461,13 +485,29 @@ export default (ctx) => {
|
|
|
461
485
|
(i32.load (i32.sub (local.get $base) (i32.const 8))))
|
|
462
486
|
(then
|
|
463
487
|
(local.set $p (call $__arr_grow (local.get $ptr) (i32.add (local.get $i) (i32.const 1))))
|
|
464
|
-
(call $
|
|
465
|
-
(local.
|
|
488
|
+
(local.set $base (call $__ptr_offset (i64.reinterpret_f64 (local.get $p))))
|
|
489
|
+
(i32.store (i32.sub (local.get $base) (i32.const 8)) (i32.add (local.get $i) (i32.const 1)))))
|
|
466
490
|
(f64.store
|
|
467
491
|
(i32.add (local.get $base) (i32.shl (local.get $i) (i32.const 3)))
|
|
468
492
|
(local.get $val))
|
|
469
493
|
(local.get $p))`
|
|
470
494
|
|
|
495
|
+
// Out-of-line .push(val) for known-ARRAY receivers — keeps each call site to a
|
|
496
|
+
// single call + var update instead of ~30 inlined instructions. Returns the
|
|
497
|
+
// (possibly relocated) array pointer; caller derives the new length if needed.
|
|
498
|
+
ctx.core.stdlib['__arr_push1'] = `(func $__arr_push1 (param $ptr i64) (param $val f64) (result f64)
|
|
499
|
+
(local $p f64) (local $base i32) (local $len i32)
|
|
500
|
+
(local.set $p (f64.reinterpret_i64 (local.get $ptr)))
|
|
501
|
+
(local.set $base (call $__ptr_offset (local.get $ptr)))
|
|
502
|
+
(local.set $len (i32.load (i32.sub (local.get $base) (i32.const 8))))
|
|
503
|
+
(if (i32.lt_s (i32.load (i32.sub (local.get $base) (i32.const 4))) (i32.add (local.get $len) (i32.const 1)))
|
|
504
|
+
(then
|
|
505
|
+
(local.set $p (call $__arr_grow_known (local.get $ptr) (i32.add (local.get $len) (i32.const 1))))
|
|
506
|
+
(local.set $base (call $__ptr_offset (i64.reinterpret_f64 (local.get $p))))))
|
|
507
|
+
(f64.store (i32.add (local.get $base) (i32.shl (local.get $len) (i32.const 3))) (local.get $val))
|
|
508
|
+
(i32.store (i32.sub (local.get $base) (i32.const 8)) (i32.add (local.get $len) (i32.const 1)))
|
|
509
|
+
(local.get $p))`
|
|
510
|
+
|
|
471
511
|
// === Array literal ===
|
|
472
512
|
|
|
473
513
|
ctx.core.emit['['] = (...elems) => {
|
|
@@ -725,6 +765,21 @@ export default (ctx) => {
|
|
|
725
765
|
|
|
726
766
|
// .push(val) → append, increment len, return array (possibly reallocated pointer)
|
|
727
767
|
ctx.core.emit['.push'] = (arr, ...vals) => {
|
|
768
|
+
// Out-of-line fast path: single value, named known-ARRAY receiver. One call +
|
|
769
|
+
// var update instead of ~30 inlined instructions — the dominant size cost of
|
|
770
|
+
// push-heavy code (e.g. watr's WASM emitter).
|
|
771
|
+
if (vals.length === 1 && typeof arr === 'string' && lookupValType(arr) === VAL.ARRAY) {
|
|
772
|
+
inc('__arr_push1')
|
|
773
|
+
const box = ctx.func.boxed?.get(arr)
|
|
774
|
+
const isGlobal = !box && ctx.scope.globals.has(arr) && !ctx.func.locals?.has(arr)
|
|
775
|
+
const readVar = box ? ['f64.load', ['local.get', `$${box}`]] : isGlobal ? ['global.get', `$${arr}`] : ['local.get', `$${arr}`]
|
|
776
|
+
const writeVar = v => box ? ['f64.store', ['local.get', `$${box}`], v] : isGlobal ? ['global.set', `$${arr}`, v] : ['local.set', `$${arr}`, v]
|
|
777
|
+
const vv = asF64(emit(vals[0]))
|
|
778
|
+
return typed(['block', ['result', 'f64'],
|
|
779
|
+
writeVar(['call', '$__arr_push1', ['i64.reinterpret_f64', readVar], vv]),
|
|
780
|
+
['f64.convert_i32_s', ['i32.load', ['i32.sub',
|
|
781
|
+
['call', '$__ptr_offset', ['i64.reinterpret_f64', readVar]], ['i32.const', 8]]]]], 'f64')
|
|
782
|
+
}
|
|
728
783
|
const va = asF64(emit(arr))
|
|
729
784
|
const t = temp('pp'), len = tempI32('pl')
|
|
730
785
|
|
|
@@ -779,8 +834,10 @@ export default (ctx) => {
|
|
|
779
834
|
)
|
|
780
835
|
}
|
|
781
836
|
|
|
782
|
-
// Update length header
|
|
783
|
-
|
|
837
|
+
// Update length header (write directly via the offset we already hold —
|
|
838
|
+
// skips __set_len's tag/forward dispatch), update source variable (pointer
|
|
839
|
+
// may have changed from grow), return new length
|
|
840
|
+
body.push(['i32.store', ['i32.sub', ['local.get', `$${pushBase}`], ['i32.const', 8]], ['local.get', `$${len}`]])
|
|
784
841
|
// Update the source variable if it's a named variable (so arr still points to valid memory)
|
|
785
842
|
if (typeof arr === 'string') {
|
|
786
843
|
if (ctx.func.boxed?.has(arr)) {
|
|
@@ -898,8 +955,8 @@ export default (ctx) => {
|
|
|
898
955
|
['i32.shl',
|
|
899
956
|
['i32.sub', ['i32.sub', ['local.get', `$${len}`], ['local.get', `$${s}`]], ['local.get', `$${cnt}`]],
|
|
900
957
|
['i32.const', 3]]],
|
|
901
|
-
// update length
|
|
902
|
-
['
|
|
958
|
+
// update length (write directly via the offset we already hold)
|
|
959
|
+
['i32.store', ['i32.sub', ['local.get', `$${off}`], ['i32.const', 8]], ['i32.sub', ['local.get', `$${len}`], ['local.get', `$${cnt}`]]],
|
|
903
960
|
out.ptr,
|
|
904
961
|
]
|
|
905
962
|
return typed(['block', ['result', 'f64'], ...body], 'f64')
|
|
@@ -919,7 +976,7 @@ export default (ctx) => {
|
|
|
919
976
|
(local.get $off)
|
|
920
977
|
(i32.shl (local.get $len) (i32.const 3)))
|
|
921
978
|
(f64.store (local.get $off) (local.get $val))
|
|
922
|
-
(
|
|
979
|
+
(i32.store (i32.sub (local.get $off) (i32.const 8)) (i32.add (local.get $len) (i32.const 1)))
|
|
923
980
|
(f64.convert_i32_s (i32.add (local.get $len) (i32.const 1))))`
|
|
924
981
|
|
|
925
982
|
// .some(fn) → return 1 if any element passes, else 0 (early exit)
|
package/module/collection.js
CHANGED
|
@@ -44,6 +44,16 @@ function numConstLiteral(expr) {
|
|
|
44
44
|
const sameValueZeroEq = '(call $__same_value_zero (i64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))'
|
|
45
45
|
const strEq = '(call $__str_eq (i64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))'
|
|
46
46
|
|
|
47
|
+
// Open-addressing probe walked additively by entrySize: avoids an i32.mul + mask per
|
|
48
|
+
// step (vs recomputing slot = off + idx*entrySize). Needs $off/$cap/$h set and $end/$slot
|
|
49
|
+
// locals declared. `idxExpr` is the first-slot index (defaults to h mod cap; cap is pow2).
|
|
50
|
+
const probeStart = (entrySize, idxExpr = '(i32.and (local.get $h) (i32.sub (local.get $cap) (i32.const 1)))') =>
|
|
51
|
+
`(local.set $end (i32.add (local.get $off) (i32.mul (local.get $cap) (i32.const ${entrySize}))))
|
|
52
|
+
(local.set $slot (i32.add (local.get $off) (i32.mul ${idxExpr} (i32.const ${entrySize}))))`
|
|
53
|
+
const probeNext = (entrySize) =>
|
|
54
|
+
`(local.set $slot (i32.add (local.get $slot) (i32.const ${entrySize})))
|
|
55
|
+
(if (i32.ge_u (local.get $slot) (local.get $end)) (then (local.set $slot (local.get $off))))`
|
|
56
|
+
|
|
47
57
|
/** Generate upsert (add/set) probe function. hasVal: store value at slot+16.
|
|
48
58
|
* hasExt: emit EXTERNAL fallthrough (call $__ext_set on non-matching type).
|
|
49
59
|
* Gated off → type mismatch just returns coll unchanged. */
|
|
@@ -62,14 +72,13 @@ function genUpsert(name, entrySize, hashFn, eqExpr, expectedType, hasVal, hasExt
|
|
|
62
72
|
? `(if (i32.ne ${tExpr} (i32.const ${expectedType})) (then (if (i32.eq ${tExpr} (i32.const ${PTR.EXTERNAL})) ${extBranch}) (return (local.get $coll))))`
|
|
63
73
|
: `(if (i32.ne ${tExpr} (i32.const ${expectedType})) (then (return (local.get $coll))))`
|
|
64
74
|
return `(func $${name} (param $coll i64) (param $key i64) ${valParam}(result i64)
|
|
65
|
-
(local $off i32) (local $cap i32) (local $h i32) (local $
|
|
75
|
+
(local $off i32) (local $cap i32) (local $h i32) (local $end i32) (local $slot i32)
|
|
66
76
|
${typeGuard}
|
|
67
77
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $coll) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
68
78
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
69
79
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
70
|
-
|
|
80
|
+
${probeStart(entrySize)}
|
|
71
81
|
(block $done (loop $probe
|
|
72
|
-
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
73
82
|
(if (i64.eqz (i64.load (local.get $slot)))
|
|
74
83
|
(then
|
|
75
84
|
(i64.store (local.get $slot) (i64.extend_i32_u (local.get $h)))
|
|
@@ -78,7 +87,7 @@ function genUpsert(name, entrySize, hashFn, eqExpr, expectedType, hasVal, hasExt
|
|
|
78
87
|
(i32.add (i32.load (i32.sub (local.get $off) (i32.const 8))) (i32.const 1)))
|
|
79
88
|
(br $done)))
|
|
80
89
|
(if ${eqExpr} ${onMatch})
|
|
81
|
-
|
|
90
|
+
${probeNext(entrySize)}
|
|
82
91
|
(br $probe)))
|
|
83
92
|
(local.get $coll))`
|
|
84
93
|
}
|
|
@@ -108,17 +117,16 @@ function genLookup(name, entrySize, hashFn, eqExpr, expectedType, wantValue, has
|
|
|
108
117
|
: `(if (i32.ne ${tExpr} (i32.const ${expectedType})) (then ${onEmpty}))`
|
|
109
118
|
|
|
110
119
|
return `(func $${name} (param $coll i64) (param $key i64) (result ${rt})
|
|
111
|
-
(local $off i32) (local $cap i32) (local $h i32) (local $
|
|
120
|
+
(local $off i32) (local $cap i32) (local $h i32) (local $end i32) (local $slot i32) (local $tries i32)
|
|
112
121
|
${typeGuard}
|
|
113
122
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $coll) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
114
123
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
115
124
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
116
|
-
|
|
125
|
+
${probeStart(entrySize)}
|
|
117
126
|
(block $done (loop $probe
|
|
118
|
-
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
119
127
|
(if (i64.eqz (i64.load (local.get $slot))) (then ${onEmpty}))
|
|
120
128
|
(if ${eqExpr} (then ${onFound}))
|
|
121
|
-
|
|
129
|
+
${probeNext(entrySize)}
|
|
122
130
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
123
131
|
(br_if $done (i32.ge_s (local.get $tries) (local.get $cap)))
|
|
124
132
|
(br $probe)))
|
|
@@ -128,21 +136,20 @@ function genLookup(name, entrySize, hashFn, eqExpr, expectedType, wantValue, has
|
|
|
128
136
|
/** Generate delete probe function. Zero out entry on match. */
|
|
129
137
|
function genDelete(name, entrySize, hashFn, eqExpr, expectedType) {
|
|
130
138
|
return `(func $${name} (param $coll i64) (param $key i64) (result i32)
|
|
131
|
-
(local $off i32) (local $cap i32) (local $h i32) (local $
|
|
139
|
+
(local $off i32) (local $cap i32) (local $h i32) (local $end i32) (local $slot i32) (local $tries i32)
|
|
132
140
|
(if (i32.ne (call $__ptr_type (local.get $coll)) (i32.const ${expectedType})) (then (return (i32.const 0))))
|
|
133
141
|
(local.set $off (call $__ptr_offset (local.get $coll)))
|
|
134
142
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
135
143
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
136
|
-
|
|
144
|
+
${probeStart(entrySize)}
|
|
137
145
|
(block $done (loop $probe
|
|
138
|
-
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
139
146
|
(if (i64.eqz (i64.load (local.get $slot))) (then (return (i32.const 0))))
|
|
140
147
|
(if ${eqExpr}
|
|
141
148
|
(then
|
|
142
149
|
(i64.store (local.get $slot) (i64.const 0))
|
|
143
150
|
(i64.store (i32.add (local.get $slot) (i32.const 8)) (i64.const 0))
|
|
144
151
|
(return (i32.const 1))))
|
|
145
|
-
|
|
152
|
+
${probeNext(entrySize)}
|
|
146
153
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
147
154
|
(br_if $done (i32.ge_s (local.get $tries) (local.get $cap)))
|
|
148
155
|
(br $probe)))
|
|
@@ -168,7 +175,7 @@ function genUpsertGrow(name, entrySize, hashFn, eqExpr, typeConst, strict = fals
|
|
|
168
175
|
${nonHashFallback}
|
|
169
176
|
(return (local.get $obj))))`
|
|
170
177
|
return `(func $${name} (param $obj i64) (param $key i64) (param $val i64) (result i64)
|
|
171
|
-
(local $off i32) (local $cap i32) (local $h i32) (local $
|
|
178
|
+
(local $off i32) (local $cap i32) (local $h i32) (local $end i32) (local $slot i32)
|
|
172
179
|
(local $size i32) (local $newptr i32) (local $newcap i32) (local $i i32)
|
|
173
180
|
(local $oldslot i32) (local $newidx i32) (local $newslot i32)
|
|
174
181
|
${typeGuard}
|
|
@@ -205,9 +212,8 @@ function genUpsertGrow(name, entrySize, hashFn, eqExpr, typeConst, strict = fals
|
|
|
205
212
|
(local.set $obj (i64.reinterpret_f64 (call $__mkptr (i32.const ${typeConst}) (i32.const 0) (local.get $newptr))))))
|
|
206
213
|
;; Insert/update
|
|
207
214
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
208
|
-
|
|
215
|
+
${probeStart(entrySize)}
|
|
209
216
|
(block $done (loop $probe
|
|
210
|
-
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
211
217
|
(if (i64.eqz (i64.load (local.get $slot)))
|
|
212
218
|
(then
|
|
213
219
|
(i64.store (local.get $slot) (i64.extend_i32_u (local.get $h)))
|
|
@@ -220,14 +226,14 @@ function genUpsertGrow(name, entrySize, hashFn, eqExpr, typeConst, strict = fals
|
|
|
220
226
|
(then
|
|
221
227
|
(i64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))
|
|
222
228
|
(br $done)))
|
|
223
|
-
|
|
229
|
+
${probeNext(entrySize)}
|
|
224
230
|
(br $probe)))
|
|
225
231
|
(local.get $obj))`
|
|
226
232
|
}
|
|
227
233
|
|
|
228
234
|
function genLookupStrict(name, entrySize, hashFn, eqExpr, expectedType, missing = UNDEF_NAN) {
|
|
229
235
|
return `(func $${name} (param $coll i64) (param $key i64) (result i64)
|
|
230
|
-
(local $off i32) (local $cap i32) (local $h i32) (local $
|
|
236
|
+
(local $off i32) (local $cap i32) (local $h i32) (local $end i32) (local $slot i32) (local $tries i32)
|
|
231
237
|
(if (i32.ne
|
|
232
238
|
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $coll) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
|
|
233
239
|
(i32.const ${expectedType}))
|
|
@@ -235,14 +241,13 @@ function genLookupStrict(name, entrySize, hashFn, eqExpr, expectedType, missing
|
|
|
235
241
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $coll) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
236
242
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
237
243
|
(local.set $h (call ${hashFn} (local.get $key)))
|
|
238
|
-
|
|
244
|
+
${probeStart(entrySize)}
|
|
239
245
|
(block $done (loop $probe
|
|
240
|
-
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
241
246
|
(if (i64.eqz (i64.load (local.get $slot)))
|
|
242
247
|
(then (return (i64.const ${missing}))))
|
|
243
248
|
(if ${eqExpr}
|
|
244
249
|
(then (return (i64.load (i32.add (local.get $slot) (i32.const 16))))))
|
|
245
|
-
|
|
250
|
+
${probeNext(entrySize)}
|
|
246
251
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
247
252
|
(br_if $done (i32.ge_s (local.get $tries) (local.get $cap)))
|
|
248
253
|
(br $probe)))
|
|
@@ -260,18 +265,17 @@ function genLookupStrictPrehashed(name, entrySize, eqExpr, expectedType, missing
|
|
|
260
265
|
: `(if (i32.ne ${tExpr} (i32.const ${expectedType}))
|
|
261
266
|
(then (return (i64.const ${missing}))))`
|
|
262
267
|
return `(func $${name} (param $coll i64) (param $key i64) (param $h i32) (result i64)
|
|
263
|
-
(local $off i32) (local $cap i32) (local $
|
|
268
|
+
(local $off i32) (local $cap i32) (local $end i32) (local $slot i32) (local $tries i32)
|
|
264
269
|
${typeGuard}
|
|
265
270
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $coll) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
266
271
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
267
|
-
|
|
272
|
+
${probeStart(entrySize)}
|
|
268
273
|
(block $done (loop $probe
|
|
269
|
-
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
270
274
|
(if (i64.eqz (i64.load (local.get $slot)))
|
|
271
275
|
(then (return (i64.const ${missing}))))
|
|
272
276
|
(if ${eqExpr}
|
|
273
277
|
(then (return (i64.load (i32.add (local.get $slot) (i32.const 16))))))
|
|
274
|
-
|
|
278
|
+
${probeNext(entrySize)}
|
|
275
279
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
276
280
|
(br_if $done (i32.ge_s (local.get $tries) (local.get $cap)))
|
|
277
281
|
(br $probe)))
|
|
@@ -280,16 +284,15 @@ function genLookupStrictPrehashed(name, entrySize, eqExpr, expectedType, missing
|
|
|
280
284
|
|
|
281
285
|
function genUpsertStrictPrehashed(name, entrySize, eqExpr, expectedType) {
|
|
282
286
|
return `(func $${name} (param $obj i64) (param $key i64) (param $h i32) (param $val i64) (result i64)
|
|
283
|
-
(local $off i32) (local $cap i32) (local $
|
|
287
|
+
(local $off i32) (local $cap i32) (local $end i32) (local $slot i32)
|
|
284
288
|
(if (i32.ne
|
|
285
289
|
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $obj) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
|
|
286
290
|
(i32.const ${expectedType}))
|
|
287
291
|
(then (return (local.get $obj))))
|
|
288
292
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $obj) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
289
293
|
(local.set $cap (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
290
|
-
|
|
294
|
+
${probeStart(entrySize)}
|
|
291
295
|
(block $done (loop $probe
|
|
292
|
-
(local.set $slot (i32.add (local.get $off) (i32.mul (local.get $idx) (i32.const ${entrySize}))))
|
|
293
296
|
(if (i64.eqz (i64.load (local.get $slot)))
|
|
294
297
|
(then
|
|
295
298
|
(i64.store (local.get $slot) (i64.extend_i32_u (local.get $h)))
|
|
@@ -302,7 +305,7 @@ function genUpsertStrictPrehashed(name, entrySize, eqExpr, expectedType) {
|
|
|
302
305
|
(then
|
|
303
306
|
(i64.store (i32.add (local.get $slot) (i32.const 16)) (local.get $val))
|
|
304
307
|
(br $done)))
|
|
305
|
-
|
|
308
|
+
${probeNext(entrySize)}
|
|
306
309
|
(br $probe)))
|
|
307
310
|
(local.get $obj))`
|
|
308
311
|
}
|
|
@@ -340,9 +343,11 @@ export default (ctx) => {
|
|
|
340
343
|
__hash_set_local: ['__str_hash', '__str_eq'],
|
|
341
344
|
__ihash_get_local: ['__map_hash'],
|
|
342
345
|
__ihash_set_local: ['__map_hash', '__alloc_hdr', '__mkptr'],
|
|
343
|
-
__dyn_get_t: ['
|
|
346
|
+
__dyn_get_t: ['__dyn_get_t_h', '__str_hash'],
|
|
347
|
+
__dyn_get_t_h: ['__ihash_get_local', '__str_eq', '__is_nullish'],
|
|
344
348
|
__dyn_get: ['__dyn_get_t', '__ptr_type'],
|
|
345
349
|
__dyn_get_expr_t: ['__dyn_get_t', '__hash_get_local'],
|
|
350
|
+
__dyn_get_expr_t_h: ['__dyn_get_t_h', '__hash_get_local_h'],
|
|
346
351
|
__dyn_get_expr: ['__dyn_get_expr_t', '__ptr_type'],
|
|
347
352
|
__dyn_get_any: ['__dyn_get_any_t', '__ptr_type'],
|
|
348
353
|
__dyn_get_any_t: () => ctx.features.external
|
|
@@ -666,11 +671,22 @@ export default (ctx) => {
|
|
|
666
671
|
ctx.core.stdlib['__dyn_get'] = `(func $__dyn_get (param $obj i64) (param $key i64) (result i64)
|
|
667
672
|
(call $__dyn_get_t (local.get $obj) (local.get $key) (call $__ptr_type (local.get $obj))))`
|
|
668
673
|
|
|
669
|
-
|
|
674
|
+
// Thin wrapper: hash the key once, delegate to the prehashed body. Constant-key
|
|
675
|
+
// call sites bypass this and call $__dyn_get_t_h directly with strHashLiteral().
|
|
676
|
+
ctx.core.stdlib['__dyn_get_t'] = `(func $__dyn_get_t (param $obj i64) (param $key i64) (param $type i32) (result i64)
|
|
677
|
+
(call $__dyn_get_t_h (local.get $obj) (local.get $key) (local.get $type) (call $__str_hash (local.get $key))))`
|
|
678
|
+
|
|
679
|
+
ctx.core.stdlib['__dyn_get_t_h'] = () => `(func $__dyn_get_t_h (param $obj i64) (param $key i64) (param $type i32) (param $h i32) (result i64)
|
|
670
680
|
(local $props i64) (local $off i32)
|
|
671
|
-
(local $poff i32) (local $pcap i32) (local $
|
|
681
|
+
(local $poff i32) (local $pcap i32) (local $pend i32) (local $idx i32) (local $slot i32) (local $tries i32)
|
|
672
682
|
${buildObjectSchemaLocals()}
|
|
673
683
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $obj) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
684
|
+
;; CLOSURE with no env (offset 0): many function refs share offset 0, so key the
|
|
685
|
+
;; global __dyn_props hash on the function table index (negative — can't collide
|
|
686
|
+
;; with real heap/data offsets). Closures *with* env keep their unique env ptr.
|
|
687
|
+
(if (i32.and (i32.eq (local.get $type) (i32.const ${PTR.CLOSURE})) (i32.eqz (local.get $off)))
|
|
688
|
+
(then (local.set $off (i32.sub (i32.const -1)
|
|
689
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $obj) (i64.const ${LAYOUT.AUX_SHIFT})) (i64.const ${LAYOUT.AUX_MASK})))))))
|
|
674
690
|
(if (i32.eq (local.get $type) (i32.const ${PTR.ARRAY}))
|
|
675
691
|
(then
|
|
676
692
|
(block $done
|
|
@@ -735,14 +751,14 @@ export default (ctx) => {
|
|
|
735
751
|
(global.set $__dyn_get_cache_props (f64.reinterpret_i64 (local.get $props))))))))
|
|
736
752
|
(local.set $poff (i32.wrap_i64 (i64.and (local.get $props) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
737
753
|
(local.set $pcap (i32.load (i32.sub (local.get $poff) (i32.const 4))))
|
|
738
|
-
(local.set $
|
|
739
|
-
(local.set $
|
|
754
|
+
(local.set $pend (i32.add (local.get $poff) (i32.mul (local.get $pcap) (i32.const ${MAP_ENTRY}))))
|
|
755
|
+
(local.set $slot (i32.add (local.get $poff) (i32.mul (i32.and (local.get $h) (i32.sub (local.get $pcap) (i32.const 1))) (i32.const ${MAP_ENTRY}))))
|
|
740
756
|
(block $hdone (loop $hprobe
|
|
741
|
-
(local.set $slot (i32.add (local.get $poff) (i32.mul (local.get $idx) (i32.const ${MAP_ENTRY}))))
|
|
742
757
|
(br_if $dynDone (i64.eqz (i64.load (local.get $slot))))
|
|
743
758
|
(if (call $__str_eq (i64.load (i32.add (local.get $slot) (i32.const 8))) (local.get $key))
|
|
744
759
|
(then (return (i64.load (i32.add (local.get $slot) (i32.const 16))))))
|
|
745
|
-
(local.set $
|
|
760
|
+
(local.set $slot (i32.add (local.get $slot) (i32.const ${MAP_ENTRY})))
|
|
761
|
+
(if (i32.ge_u (local.get $slot) (local.get $pend)) (then (local.set $slot (local.get $poff))))
|
|
746
762
|
(local.set $tries (i32.add (local.get $tries) (i32.const 1)))
|
|
747
763
|
(br_if $hdone (i32.ge_s (local.get $tries) (local.get $pcap)))
|
|
748
764
|
(br $hprobe))))${buildObjectSchemaArm()}
|
|
@@ -770,6 +786,19 @@ export default (ctx) => {
|
|
|
770
786
|
(then (call $__hash_get_local (local.get $obj) (local.get $key)))
|
|
771
787
|
(else (i64.const ${NULL_NAN}))))))`
|
|
772
788
|
|
|
789
|
+
// Prehashed variant of __dyn_get_expr_t for constant string keys: the FNV hash
|
|
790
|
+
// is folded at compile time (strHashLiteral), so no __str_hash call at runtime.
|
|
791
|
+
ctx.core.stdlib['__dyn_get_expr_t_h'] = `(func $__dyn_get_expr_t_h (param $obj i64) (param $key i64) (param $t i32) (param $h i32) (result i64)
|
|
792
|
+
(local $val i64)
|
|
793
|
+
(local.set $val (call $__dyn_get_t_h (local.get $obj) (local.get $key) (local.get $t) (local.get $h)))
|
|
794
|
+
(if (result i64)
|
|
795
|
+
(i64.ne (local.get $val) (i64.const ${UNDEF_NAN}))
|
|
796
|
+
(then (local.get $val))
|
|
797
|
+
(else
|
|
798
|
+
(if (result i64) (i32.eq (local.get $t) (i32.const ${PTR.HASH}))
|
|
799
|
+
(then (call $__hash_get_local_h (local.get $obj) (local.get $key) (local.get $h)))
|
|
800
|
+
(else (i64.const ${NULL_NAN}))))))`
|
|
801
|
+
|
|
773
802
|
// Like __dyn_get_expr but also resolves EXTERNAL host objects via __ext_prop.
|
|
774
803
|
// Used at call sites where receiver type is statically unknown.
|
|
775
804
|
// When features.external is off, collapses to __dyn_get_expr shape (no EXTERNAL probe).
|
|
@@ -809,6 +838,10 @@ export default (ctx) => {
|
|
|
809
838
|
(local $off i32) (local $type i32) ${buildObjectSchemaSetLocals()}
|
|
810
839
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $obj) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
811
840
|
(local.set $type (i32.wrap_i64 (i64.and (i64.shr_u (local.get $obj) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
841
|
+
;; CLOSURE with no env (offset 0): key __dyn_props on the function table index — see __dyn_get_t.
|
|
842
|
+
(if (i32.and (i32.eq (local.get $type) (i32.const ${PTR.CLOSURE})) (i32.eqz (local.get $off)))
|
|
843
|
+
(then (local.set $off (i32.sub (i32.const -1)
|
|
844
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $obj) (i64.const ${LAYOUT.AUX_SHIFT})) (i64.const ${LAYOUT.AUX_MASK})))))))
|
|
812
845
|
(if (i32.eq (local.get $type) (i32.const ${PTR.ARRAY}))
|
|
813
846
|
(then
|
|
814
847
|
(block $done
|