jz 0.1.1 → 0.2.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 +257 -90
- package/cli.js +34 -8
- package/index.js +102 -13
- package/module/array.js +396 -133
- package/module/collection.js +455 -212
- package/module/console.js +169 -88
- package/module/core.js +246 -144
- package/module/date.js +691 -0
- package/module/function.js +81 -21
- package/module/index.js +2 -1
- package/module/json.js +483 -138
- package/module/math.js +79 -39
- package/module/number.js +188 -78
- package/module/object.js +227 -47
- package/module/regex.js +33 -30
- package/module/schema.js +14 -17
- package/module/string.js +434 -235
- package/module/symbol.js +4 -3
- package/module/timer.js +89 -31
- package/module/typedarray.js +174 -44
- package/package.json +3 -10
- package/src/analyze.js +626 -132
- package/src/autoload.js +14 -6
- package/src/compile.js +399 -70
- package/src/ctx.js +41 -12
- package/src/emit.js +634 -145
- package/src/host.js +244 -51
- package/src/ir.js +81 -31
- package/src/jzify.js +181 -24
- package/src/narrow.js +32 -4
- package/src/optimize.js +628 -42
- package/src/plan.js +1132 -4
- package/src/prepare.js +321 -75
- package/src/vectorize.js +1016 -0
- package/wasi.js +13 -0
- package/src/key.js +0 -73
- package/src/source.js +0 -76
package/module/core.js
CHANGED
|
@@ -2,21 +2,22 @@
|
|
|
2
2
|
* Core module — NaN-boxing, bump allocator, property dispatch.
|
|
3
3
|
*
|
|
4
4
|
* Foundation for all heap types. Every module depends on this.
|
|
5
|
-
* NaN-boxing:
|
|
5
|
+
* NaN-boxing: see LAYOUT in src/ctx.js for the canonical bit layout.
|
|
6
6
|
*
|
|
7
7
|
* Auto-included by array/object/string modules.
|
|
8
8
|
*
|
|
9
9
|
* @module core
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { typed, asF64, asI32, NULL_NAN, UNDEF_NAN, temp, usesDynProps, ptrOffsetIR, isNullish } from '../src/ir.js'
|
|
12
|
+
import { typed, asF64, asI32, asI64, NULL_NAN, UNDEF_NAN, temp, usesDynProps, ptrOffsetIR, isNullish } from '../src/ir.js'
|
|
13
13
|
import { emit } from '../src/emit.js'
|
|
14
|
-
import { valTypeOf, lookupValType, VAL, T, repOf, updateRep } from '../src/analyze.js'
|
|
15
|
-
import { err, inc, PTR } from '../src/ctx.js'
|
|
14
|
+
import { valTypeOf, lookupValType, VAL, T, repOf, updateRep, shapeOf } from '../src/analyze.js'
|
|
15
|
+
import { err, inc, PTR, LAYOUT } from '../src/ctx.js'
|
|
16
16
|
import { initSchema } from './schema.js'
|
|
17
17
|
import { strHashLiteral } from './collection.js'
|
|
18
18
|
|
|
19
|
-
const
|
|
19
|
+
// Pre-shifted NaN prefix as a full i64 mask, for `(i64.const ${NAN_BITS})` use.
|
|
20
|
+
const NAN_BITS = '0x' + LAYOUT.NAN_PREFIX_BITS.toString(16).toUpperCase().padStart(16, '0')
|
|
20
21
|
|
|
21
22
|
const PTR_BY_VAL = {
|
|
22
23
|
[VAL.ARRAY]: PTR.ARRAY,
|
|
@@ -36,91 +37,81 @@ export default (ctx) => {
|
|
|
36
37
|
__typed_data: ['__ptr_offset', '__ptr_aux'],
|
|
37
38
|
__ptr_offset: [],
|
|
38
39
|
__is_str_key: ['__ptr_type'],
|
|
39
|
-
__str_len: ['__ptr_type', '__ptr_offset'],
|
|
40
|
+
__str_len: ['__ptr_type', '__ptr_offset', '__ptr_aux'],
|
|
40
41
|
__set_len: [],
|
|
41
|
-
__length:
|
|
42
|
-
const d = ['__ptr_type', '__ptr_offset', '__str_len', '__len']
|
|
43
|
-
if (ctx.features.sso) d.push('__ptr_aux')
|
|
44
|
-
return d
|
|
45
|
-
},
|
|
42
|
+
__length: ['__ptr_type', '__ptr_offset', '__str_len', '__len'],
|
|
46
43
|
__typeof: ['__ptr_type', '__is_nullish'],
|
|
47
44
|
__alloc_hdr: ['__alloc'],
|
|
48
45
|
})
|
|
49
46
|
|
|
50
|
-
ctx.core.stdlib['__is_nullish'] = `(func $__is_nullish (param $v
|
|
47
|
+
ctx.core.stdlib['__is_nullish'] = `(func $__is_nullish (param $v i64) (result i32)
|
|
51
48
|
(i32.or
|
|
52
|
-
(i64.eq (
|
|
53
|
-
(i64.eq (
|
|
49
|
+
(i64.eq (local.get $v) (i64.const ${NULL_NAN}))
|
|
50
|
+
(i64.eq (local.get $v) (i64.const ${UNDEF_NAN}))))`
|
|
54
51
|
|
|
55
|
-
ctx.core.stdlib['__eq'] = `(func $__eq (param $a
|
|
56
|
-
(local $
|
|
52
|
+
ctx.core.stdlib['__eq'] = `(func $__eq (param $a i64) (param $b i64) (result i32)
|
|
53
|
+
(local $fa f64) (local $fb f64) (local $ta i32) (local $tb i32)
|
|
57
54
|
;; Fast path: bit equality covers identical pointers AND interned/SSO strings (same content
|
|
58
55
|
;; → same bits). Failing universal-NaN test catches NaN===NaN→false. Saves the NaN-check
|
|
59
56
|
;; pair (4 f64.eq) on the hottest case in watr (op === 'literal-string').
|
|
60
|
-
(
|
|
61
|
-
|
|
62
|
-
(if (result i32) (i64.eq (local.get $ra) (local.get $rb))
|
|
63
|
-
(then (i64.ne (local.get $ra) (i64.const 0x7FF8000000000000)))
|
|
57
|
+
(if (result i32) (i64.eq (local.get $a) (local.get $b))
|
|
58
|
+
(then (i64.ne (local.get $a) (i64.const ${NAN_BITS})))
|
|
64
59
|
(else
|
|
65
60
|
;; Bits differ. Numeric path covers -0/+0 and any normal numeric inequality.
|
|
61
|
+
(local.set $fa (f64.reinterpret_i64 (local.get $a)))
|
|
62
|
+
(local.set $fb (f64.reinterpret_i64 (local.get $b)))
|
|
66
63
|
(if (result i32)
|
|
67
64
|
(i32.and
|
|
68
|
-
(f64.eq (local.get $
|
|
69
|
-
(f64.eq (local.get $
|
|
70
|
-
(then (f64.eq (local.get $
|
|
65
|
+
(f64.eq (local.get $fa) (local.get $fa))
|
|
66
|
+
(f64.eq (local.get $fb) (local.get $fb)))
|
|
67
|
+
(then (f64.eq (local.get $fa) (local.get $fb)))
|
|
71
68
|
(else
|
|
72
|
-
;;
|
|
73
|
-
;;
|
|
74
|
-
(local.set $ta (i32.wrap_i64 (i64.and (i64.shr_u (local.get $
|
|
75
|
-
(local.set $tb (i32.wrap_i64 (i64.and (i64.shr_u (local.get $
|
|
69
|
+
;; At least one operand is a NaN-box. Both STRING (heap or SSO) → __str_eq
|
|
70
|
+
;; handles content compare and SSO fast-fail internally.
|
|
71
|
+
(local.set $ta (i32.wrap_i64 (i64.and (i64.shr_u (local.get $a) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
72
|
+
(local.set $tb (i32.wrap_i64 (i64.and (i64.shr_u (local.get $b) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
76
73
|
(if (result i32)
|
|
77
74
|
(i32.and
|
|
78
|
-
(i32.
|
|
79
|
-
|
|
80
|
-
(i32.eq (local.get $ta) (i32.const ${PTR.SSO})))
|
|
81
|
-
(i32.or
|
|
82
|
-
(i32.eq (local.get $tb) (i32.const ${PTR.STRING}))
|
|
83
|
-
(i32.eq (local.get $tb) (i32.const ${PTR.SSO}))))
|
|
75
|
+
(i32.eq (local.get $ta) (i32.const ${PTR.STRING}))
|
|
76
|
+
(i32.eq (local.get $tb) (i32.const ${PTR.STRING})))
|
|
84
77
|
(then (call $__str_eq (local.get $a) (local.get $b)))
|
|
85
78
|
(else (i32.const 0))))))))`
|
|
86
79
|
|
|
87
|
-
ctx.core.stdlib['__is_null'] = `(func $__is_null (param $v
|
|
88
|
-
(i64.eq (
|
|
80
|
+
ctx.core.stdlib['__is_null'] = `(func $__is_null (param $v i64) (result i32)
|
|
81
|
+
(i64.eq (local.get $v) (i64.const ${NULL_NAN})))`
|
|
89
82
|
|
|
90
83
|
// Truthy check: handles regular numbers AND NaN-boxed pointers
|
|
91
84
|
// Falsy: 0, -0, NaN, null, undefined, "" (empty SSO)
|
|
92
|
-
ctx.core.stdlib['__is_truthy'] = `(func $__is_truthy (param $v
|
|
93
|
-
(local $
|
|
94
|
-
(
|
|
95
|
-
|
|
85
|
+
ctx.core.stdlib['__is_truthy'] = `(func $__is_truthy (param $v i64) (result i32)
|
|
86
|
+
(local $f f64)
|
|
87
|
+
(local.set $f (f64.reinterpret_i64 (local.get $v)))
|
|
88
|
+
(if (result i32) (f64.eq (local.get $f) (local.get $f))
|
|
89
|
+
(then (f64.ne (local.get $f) (f64.const 0)))
|
|
96
90
|
(else
|
|
97
|
-
(local.set $bits (i64.reinterpret_f64 (local.get $v)))
|
|
98
91
|
(i32.and
|
|
99
92
|
(i32.and
|
|
100
|
-
(i64.ne (local.get $
|
|
101
|
-
(i64.ne (local.get $
|
|
93
|
+
(i64.ne (local.get $v) (i64.const ${NAN_BITS}))
|
|
94
|
+
(i64.ne (local.get $v) (i64.const ${NULL_NAN})))
|
|
102
95
|
(i32.and
|
|
103
|
-
(i64.ne (local.get $
|
|
104
|
-
(i64.ne (local.get $
|
|
96
|
+
(i64.ne (local.get $v) (i64.const ${UNDEF_NAN}))
|
|
97
|
+
(i64.ne (local.get $v) (i64.const 0x7FFA400000000000)))))))`
|
|
105
98
|
|
|
106
|
-
ctx.core.stdlib['__is_str_key'] = `(func $__is_str_key (param $v
|
|
107
|
-
(local $
|
|
108
|
-
(
|
|
99
|
+
ctx.core.stdlib['__is_str_key'] = `(func $__is_str_key (param $v i64) (result i32)
|
|
100
|
+
(local $f f64)
|
|
101
|
+
(local.set $f (f64.reinterpret_i64 (local.get $v)))
|
|
102
|
+
(if (result i32) (f64.eq (local.get $f) (local.get $f))
|
|
109
103
|
(then (i32.const 0))
|
|
110
104
|
(else
|
|
111
|
-
(
|
|
112
|
-
(i32.or
|
|
113
|
-
(i32.eq (local.get $t) (i32.const ${PTR.STRING}))
|
|
114
|
-
(i32.eq (local.get $t) (i32.const ${PTR.SSO}))))))`
|
|
105
|
+
(i32.eq (call $__ptr_type (i64.reinterpret_f64 (local.get $f))) (i32.const ${PTR.STRING})))))`
|
|
115
106
|
|
|
116
107
|
|
|
117
108
|
// Default dynamic-property helpers are harmless stubs. The collection module
|
|
118
109
|
// overrides them with the real sidecar-property implementation.
|
|
119
|
-
ctx.core.stdlib['__dyn_get'] = `(func $__dyn_get (param $obj
|
|
120
|
-
(
|
|
121
|
-
ctx.core.stdlib['__dyn_get_or'] = `(func $__dyn_get_or (param $obj
|
|
110
|
+
ctx.core.stdlib['__dyn_get'] = `(func $__dyn_get (param $obj i64) (param $key i64) (result i64)
|
|
111
|
+
(i64.const ${UNDEF_NAN}))`
|
|
112
|
+
ctx.core.stdlib['__dyn_get_or'] = `(func $__dyn_get_or (param $obj i64) (param $key i64) (param $fallback i64) (result i64)
|
|
122
113
|
(local.get $fallback))`
|
|
123
|
-
ctx.core.stdlib['__dyn_set'] = `(func $__dyn_set (param $obj
|
|
114
|
+
ctx.core.stdlib['__dyn_set'] = `(func $__dyn_set (param $obj i64) (param $key i64) (param $val i64) (result i64)
|
|
124
115
|
(local.get $val))`
|
|
125
116
|
ctx.core.stdlib['__dyn_move'] = `(func $__dyn_move (param $oldOff i32) (param $newOff i32))`
|
|
126
117
|
|
|
@@ -130,22 +121,22 @@ export default (ctx) => {
|
|
|
130
121
|
|
|
131
122
|
ctx.core.stdlib['__mkptr'] = `(func $__mkptr (param $type i32) (param $aux i32) (param $offset i32) (result f64)
|
|
132
123
|
(f64.reinterpret_i64 (i64.or
|
|
133
|
-
(i64.
|
|
124
|
+
(i64.const ${NAN_BITS})
|
|
134
125
|
(i64.or
|
|
135
|
-
(i64.shl (i64.and (i64.extend_i32_u (local.get $type)) (i64.const
|
|
126
|
+
(i64.shl (i64.and (i64.extend_i32_u (local.get $type)) (i64.const ${LAYOUT.TAG_MASK})) (i64.const ${LAYOUT.TAG_SHIFT}))
|
|
136
127
|
(i64.or
|
|
137
|
-
(i64.shl (i64.and (i64.extend_i32_u (local.get $aux)) (i64.const
|
|
138
|
-
(i64.and (i64.extend_i32_u (local.get $offset)) (i64.const
|
|
128
|
+
(i64.shl (i64.and (i64.extend_i32_u (local.get $aux)) (i64.const ${LAYOUT.AUX_MASK})) (i64.const ${LAYOUT.AUX_SHIFT}))
|
|
129
|
+
(i64.and (i64.extend_i32_u (local.get $offset)) (i64.const ${LAYOUT.OFFSET_MASK})))))))`
|
|
139
130
|
|
|
140
|
-
ctx.core.stdlib['__ptr_offset'] = `(func $__ptr_offset (param $ptr
|
|
131
|
+
ctx.core.stdlib['__ptr_offset'] = `(func $__ptr_offset (param $ptr i64) (result i32)
|
|
141
132
|
(local $bits i64) (local $off i32)
|
|
142
|
-
(local.set $bits (
|
|
143
|
-
(local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const
|
|
133
|
+
(local.set $bits (local.get $ptr))
|
|
134
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
144
135
|
;; Arrays can be reallocated during growth; follow forwarding pointer (cap=-1 sentinel).
|
|
145
136
|
;; Bounds are checked inside the loop so non-array ptrs skip them entirely, and well-formed
|
|
146
137
|
;; ARRAY ptrs without forwarding still pay only one bounds check before the cap load.
|
|
147
138
|
(if (i32.eq
|
|
148
|
-
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const
|
|
139
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
|
|
149
140
|
(i32.const ${PTR.ARRAY}))
|
|
150
141
|
(then
|
|
151
142
|
(block $done
|
|
@@ -157,14 +148,19 @@ export default (ctx) => {
|
|
|
157
148
|
(br $follow)))))
|
|
158
149
|
(local.get $off))`
|
|
159
150
|
|
|
160
|
-
ctx.core.stdlib['__ptr_aux'] = `(func $__ptr_aux (param $ptr
|
|
161
|
-
(i32.wrap_i64 (i64.and (i64.shr_u (
|
|
151
|
+
ctx.core.stdlib['__ptr_aux'] = `(func $__ptr_aux (param $ptr i64) (result i32)
|
|
152
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.AUX_SHIFT})) (i64.const ${LAYOUT.AUX_MASK}))))`
|
|
162
153
|
|
|
163
|
-
ctx.core.stdlib['__ptr_type'] = `(func $__ptr_type (param $ptr
|
|
164
|
-
(i32.wrap_i64 (i64.and (i64.shr_u (
|
|
154
|
+
ctx.core.stdlib['__ptr_type'] = `(func $__ptr_type (param $ptr i64) (result i32)
|
|
155
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))`
|
|
165
156
|
|
|
166
157
|
// === Bump allocator ===
|
|
167
158
|
|
|
159
|
+
// Heap-base watermark: gates header-backed propsPtr fast paths so static-data
|
|
160
|
+
// OBJECT slots (offsets < heap base) don't misread arbitrary memory at off-16.
|
|
161
|
+
// Updated by optimizeModule() when data segment exceeds 1024 bytes.
|
|
162
|
+
ctx.scope.globals.set('__heap_start', '(global $__heap_start (mut i32) (i32.const 1024))')
|
|
163
|
+
|
|
168
164
|
if (ctx.memory.shared) {
|
|
169
165
|
// Shared memory: heap offset stored at memory[1020] (i32), just before heap start at 1024
|
|
170
166
|
ctx.core.stdlib['__alloc'] = `(func $__alloc (param $bytes i32) (result i32)
|
|
@@ -172,7 +168,7 @@ export default (ctx) => {
|
|
|
172
168
|
(local.set $ptr (i32.load (i32.const 1020)))
|
|
173
169
|
(i32.store (i32.const 1020) (i32.and (i32.add (i32.add (local.get $ptr) (local.get $bytes)) (i32.const 7)) (i32.const -8)))
|
|
174
170
|
(local.get $ptr))`
|
|
175
|
-
ctx.core.stdlib['
|
|
171
|
+
ctx.core.stdlib['__clear'] = `(func $__clear
|
|
176
172
|
(i32.store (i32.const 1020) (i32.const 1024)))`
|
|
177
173
|
} else {
|
|
178
174
|
// Own memory: heap offset in a global, auto-grow when needed
|
|
@@ -189,7 +185,7 @@ export default (ctx) => {
|
|
|
189
185
|
(i32.const -1)) (then (unreachable)))))
|
|
190
186
|
(global.set $__heap (local.get $next))
|
|
191
187
|
(local.get $ptr))`
|
|
192
|
-
ctx.core.stdlib['
|
|
188
|
+
ctx.core.stdlib['__clear'] = `(func $__clear
|
|
193
189
|
(global.set $__heap (i32.const 1024)))`
|
|
194
190
|
}
|
|
195
191
|
|
|
@@ -211,7 +207,7 @@ export default (ctx) => {
|
|
|
211
207
|
(else (i32.shr_u (local.get $et) (i32.const 1)))))))`
|
|
212
208
|
|
|
213
209
|
// Real data address for any TYPED ptr: owned → offset, view → [offset+4].
|
|
214
|
-
ctx.core.stdlib['__typed_data'] = `(func $__typed_data (param $ptr
|
|
210
|
+
ctx.core.stdlib['__typed_data'] = `(func $__typed_data (param $ptr i64) (result i32)
|
|
215
211
|
(local $off i32)
|
|
216
212
|
(local.set $off (call $__ptr_offset (local.get $ptr)))
|
|
217
213
|
(if (result i32) (i32.and (call $__ptr_aux (local.get $ptr)) (i32.const 8))
|
|
@@ -220,11 +216,11 @@ export default (ctx) => {
|
|
|
220
216
|
|
|
221
217
|
// Hot (~85M calls in watr self-host). Type/offset extraction inlined; forwarding
|
|
222
218
|
// loop only entered for ARRAY. ARRAY fast path dominates (nodes?.length, out.length …).
|
|
223
|
-
ctx.core.stdlib['__len'] = `(func $__len (param $ptr
|
|
219
|
+
ctx.core.stdlib['__len'] = `(func $__len (param $ptr i64) (result i32)
|
|
224
220
|
(local $bits i64) (local $t i32) (local $off i32) (local $aux i32)
|
|
225
|
-
(local.set $bits (
|
|
226
|
-
(local.set $t (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const
|
|
227
|
-
(local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const
|
|
221
|
+
(local.set $bits (local.get $ptr))
|
|
222
|
+
(local.set $t (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
223
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
228
224
|
;; ARRAY fast path: follow forwarding inline, then load len at off-8.
|
|
229
225
|
(if (result i32)
|
|
230
226
|
(i32.and (i32.eq (local.get $t) (i32.const 1)) (i32.ge_u (local.get $off) (i32.const 8)))
|
|
@@ -248,16 +244,16 @@ export default (ctx) => {
|
|
|
248
244
|
(then
|
|
249
245
|
(if (result i32) (i32.eq (local.get $t) (i32.const 3))
|
|
250
246
|
(then
|
|
251
|
-
(local.set $aux (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const
|
|
247
|
+
(local.set $aux (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const ${LAYOUT.AUX_SHIFT})) (i64.const ${LAYOUT.AUX_MASK}))))
|
|
252
248
|
(if (result i32) (i32.and (local.get $aux) (i32.const 8))
|
|
253
249
|
(then (i32.shr_u (i32.load (local.get $off))
|
|
254
250
|
(call $__typed_shift (i32.and (local.get $aux) (i32.const 7)))))
|
|
255
251
|
(else (i32.shr_u (i32.load (i32.sub (local.get $off) (i32.const 8)))
|
|
256
|
-
(call $__typed_shift (local.get $aux))))))
|
|
252
|
+
(call $__typed_shift (i32.and (local.get $aux) (i32.const 7)))))))
|
|
257
253
|
(else (i32.load (i32.sub (local.get $off) (i32.const 8))))))
|
|
258
254
|
(else (i32.const 0))))))`
|
|
259
255
|
|
|
260
|
-
ctx.core.stdlib['__cap'] = `(func $__cap (param $ptr
|
|
256
|
+
ctx.core.stdlib['__cap'] = `(func $__cap (param $ptr i64) (result i32)
|
|
261
257
|
(local $t i32) (local $off i32) (local $aux i32)
|
|
262
258
|
(local.set $t (call $__ptr_type (local.get $ptr)))
|
|
263
259
|
(local.set $off (call $__ptr_offset (local.get $ptr)))
|
|
@@ -279,28 +275,30 @@ export default (ctx) => {
|
|
|
279
275
|
(then (i32.shr_u (i32.load (local.get $off))
|
|
280
276
|
(call $__typed_shift (i32.and (local.get $aux) (i32.const 7)))))
|
|
281
277
|
(else (i32.shr_u (i32.load (i32.sub (local.get $off) (i32.const 4)))
|
|
282
|
-
(call $__typed_shift (local.get $aux))))))
|
|
278
|
+
(call $__typed_shift (i32.and (local.get $aux) (i32.const 7)))))))
|
|
283
279
|
(else (i32.load (i32.sub (local.get $off) (i32.const 4))))))
|
|
284
280
|
(else (i32.const 0))))`
|
|
285
281
|
|
|
286
|
-
// String (
|
|
287
|
-
ctx.core.stdlib['__str_len'] = `(func $__str_len (param $ptr
|
|
288
|
-
(local $off i32)
|
|
282
|
+
// String length (UTF-8 byte count). Heap: [-4:len(i32)][chars...]; SSO: aux & 7.
|
|
283
|
+
ctx.core.stdlib['__str_len'] = `(func $__str_len (param $ptr i64) (result i32)
|
|
284
|
+
(local $off i32) (local $aux i32)
|
|
285
|
+
(if (i32.ne (call $__ptr_type (local.get $ptr)) (i32.const ${PTR.STRING}))
|
|
286
|
+
(then (return (i32.const 0))))
|
|
287
|
+
(local.set $aux (call $__ptr_aux (local.get $ptr)))
|
|
288
|
+
(if (i32.and (local.get $aux) (i32.const ${LAYOUT.SSO_BIT}))
|
|
289
|
+
(then (return (i32.and (local.get $aux) (i32.const 7)))))
|
|
289
290
|
(local.set $off (call $__ptr_offset (local.get $ptr)))
|
|
290
|
-
(if (result i32)
|
|
291
|
-
(i32.and
|
|
292
|
-
(i32.eq (call $__ptr_type (local.get $ptr)) (i32.const ${PTR.STRING}))
|
|
293
|
-
(i32.ge_u (local.get $off) (i32.const 4)))
|
|
291
|
+
(if (result i32) (i32.ge_u (local.get $off) (i32.const 4))
|
|
294
292
|
(then (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
295
293
|
(else (i32.const 0))))`
|
|
296
294
|
|
|
297
295
|
// Set len in memory (for push/pop). Hot (~42M calls in watr self-host).
|
|
298
296
|
// Type/offset extraction inlined; forwarding loop only entered for ARRAY.
|
|
299
|
-
ctx.core.stdlib['__set_len'] = `(func $__set_len (param $ptr
|
|
297
|
+
ctx.core.stdlib['__set_len'] = `(func $__set_len (param $ptr i64) (param $len i32)
|
|
300
298
|
(local $bits i64) (local $t i32) (local $off i32)
|
|
301
|
-
(local.set $bits (
|
|
302
|
-
(local.set $t (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const
|
|
303
|
-
(local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const
|
|
299
|
+
(local.set $bits (local.get $ptr))
|
|
300
|
+
(local.set $t (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
301
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
304
302
|
;; Only ARRAY (1), TYPED (3), HASH (7), SET (8), MAP (9) carry an 8-byte header.
|
|
305
303
|
;; Of those, only ARRAY can be forwarded — follow the chain inline.
|
|
306
304
|
(if
|
|
@@ -322,20 +320,25 @@ export default (ctx) => {
|
|
|
322
320
|
(br $follow)))))
|
|
323
321
|
(i32.store (i32.sub (local.get $off) (i32.const 8)) (local.get $len)))))`
|
|
324
322
|
|
|
325
|
-
// Alloc header(
|
|
323
|
+
// Alloc header(16) + data(cap*stride). Layout: [propsPtr@-16(f64=0), len@-8, cap@-4],
|
|
324
|
+
// data starts at returned offset. propsPtr at -16 holds a per-object dynamic-property hash
|
|
325
|
+
// (NaN-boxed PTR.HASH) for ARRAY/HASH/MAP/SET; 0 means "no dyn props yet". This lets
|
|
326
|
+
// __dyn_get_t / __dyn_set sidestep the global __dyn_props lookup on the hot path.
|
|
327
|
+
// Read offsets relative to the returned data ptr stay unchanged (-8 len, -4 cap).
|
|
326
328
|
ctx.core.stdlib['__alloc_hdr'] = `(func $__alloc_hdr (param $len i32) (param $cap i32) (param $stride i32) (result i32)
|
|
327
329
|
(local $ptr i32)
|
|
328
|
-
(local.set $ptr (call $__alloc (i32.add (i32.const
|
|
329
|
-
(
|
|
330
|
-
(i32.store (i32.add (local.get $ptr) (i32.const
|
|
331
|
-
(i32.add (local.get $ptr) (i32.const
|
|
330
|
+
(local.set $ptr (call $__alloc (i32.add (i32.const 16) (i32.mul (local.get $cap) (local.get $stride)))))
|
|
331
|
+
(i64.store (local.get $ptr) (i64.const 0))
|
|
332
|
+
(i32.store (i32.add (local.get $ptr) (i32.const 8)) (local.get $len))
|
|
333
|
+
(i32.store (i32.add (local.get $ptr) (i32.const 12)) (local.get $cap))
|
|
334
|
+
(i32.add (local.get $ptr) (i32.const 16)))`
|
|
332
335
|
|
|
333
336
|
// Allocator + exports are deferred: only included when memory is actually needed.
|
|
334
337
|
// Any module using allocPtr/inc('__alloc') pulls these in via ctx.core.stdlibDeps.
|
|
335
|
-
// compile.js emits _alloc/
|
|
338
|
+
// compile.js emits _alloc/_clear exports + memory section only when __alloc is in includes.
|
|
336
339
|
ctx.core._allocRawFuncs = [
|
|
337
340
|
'(func (export "_alloc") (param $bytes i32) (result i32) (call $__alloc (local.get $bytes)))',
|
|
338
|
-
'(func (export "
|
|
341
|
+
'(func (export "_clear") (call $__clear))',
|
|
339
342
|
]
|
|
340
343
|
|
|
341
344
|
// Not-nullish check: f64 WAT node is neither NULL_NAN nor UNDEF_NAN.
|
|
@@ -343,11 +346,12 @@ export default (ctx) => {
|
|
|
343
346
|
// apply — otherwise this would always emit a __is_nullish call even for provable cases.
|
|
344
347
|
const notNullish = v => ['i32.eqz', isNullish(v)]
|
|
345
348
|
|
|
346
|
-
// Optional-chain wrapper: eval guard, if non-nullish emit access, else
|
|
349
|
+
// Optional-chain wrapper: eval guard, if non-nullish emit access, else `undefined`.
|
|
350
|
+
// Per spec, `null?.a` and `undefined?.a` both short-circuit to undefined, not null.
|
|
347
351
|
const emitNullishGuarded = (guard, access) => typed(['if', ['result', 'f64'],
|
|
348
352
|
notNullish(guard),
|
|
349
353
|
['then', access],
|
|
350
|
-
['else', ['f64.const', `nan:${
|
|
354
|
+
['else', ['f64.const', `nan:${UNDEF_NAN}`]]], 'f64')
|
|
351
355
|
|
|
352
356
|
// === Shared dispatch helpers ===
|
|
353
357
|
|
|
@@ -362,11 +366,11 @@ export default (ctx) => {
|
|
|
362
366
|
return typed(['f64.convert_i32_s', ['i32.load', ['i32.sub', off, ['i32.const', 8]]]], 'f64')
|
|
363
367
|
}
|
|
364
368
|
if (vt === VAL.TYPED)
|
|
365
|
-
return typed(['f64.convert_i32_s', ['call', '$__len', va]], 'f64')
|
|
369
|
+
return typed(['f64.convert_i32_s', ['call', '$__len', ['i64.reinterpret_f64', va]]], 'f64')
|
|
366
370
|
// Known string → byteLen (handles SSO + heap)
|
|
367
371
|
if (vt === VAL.STRING) {
|
|
368
372
|
inc('__str_byteLen')
|
|
369
|
-
return typed(['f64.convert_i32_s', ['call', '$__str_byteLen', va]], 'f64')
|
|
373
|
+
return typed(['f64.convert_i32_s', ['call', '$__str_byteLen', ['i64.reinterpret_f64', va]]], 'f64')
|
|
370
374
|
}
|
|
371
375
|
// Unknown → runtime dispatch via stdlib. Set/Map dispatch arms are pulled
|
|
372
376
|
// only when user code actually constructs Set/Map (collection.js sets the
|
|
@@ -375,7 +379,7 @@ export default (ctx) => {
|
|
|
375
379
|
// commonly passed from JS via jz.memory.* without an in-program constructor.
|
|
376
380
|
inc('__length')
|
|
377
381
|
ctx.features.typedarray = true
|
|
378
|
-
return typed(['call', '$__length', va], 'f64')
|
|
382
|
+
return typed(['call', '$__length', ['i64.reinterpret_f64', va]], 'f64')
|
|
379
383
|
}
|
|
380
384
|
|
|
381
385
|
// Known-schema fields live in the object payload. Dynamic sidecars are only
|
|
@@ -389,8 +393,8 @@ export default (ctx) => {
|
|
|
389
393
|
|
|
390
394
|
function emitHashGetLocalConst(base, key, prop) {
|
|
391
395
|
inc('__hash_get_local_h')
|
|
392
|
-
const receiver = base?.type ?
|
|
393
|
-
return typed(['call', '$__hash_get_local_h', receiver, key, ['i32.const', strHashLiteral(prop)]], 'f64')
|
|
396
|
+
const receiver = asI64(base?.type ? base : typed(base, 'f64'))
|
|
397
|
+
return typed(['f64.reinterpret_i64', ['call', '$__hash_get_local_h', receiver, key, ['i32.const', strHashLiteral(prop)]]], 'f64')
|
|
394
398
|
}
|
|
395
399
|
|
|
396
400
|
function emitTypeTag(receiver, vt) {
|
|
@@ -402,20 +406,82 @@ export default (ctx) => {
|
|
|
402
406
|
|
|
403
407
|
function emitDynGetExprTyped(base, key, vt) {
|
|
404
408
|
inc('__dyn_get_expr_t')
|
|
405
|
-
const receiver = base?.type ?
|
|
406
|
-
return typed(['call', '$__dyn_get_expr_t', receiver, key, emitTypeTag(receiver, vt)], 'f64')
|
|
409
|
+
const receiver = asI64(base?.type ? base : typed(base, 'f64'))
|
|
410
|
+
return typed(['f64.reinterpret_i64', ['call', '$__dyn_get_expr_t', receiver, key, emitTypeTag(receiver, vt)]], 'f64')
|
|
407
411
|
}
|
|
408
412
|
|
|
409
413
|
function emitDynGetAnyTyped(base, key, vt) {
|
|
410
414
|
inc('__dyn_get_any_t')
|
|
411
|
-
const receiver = base?.type ?
|
|
412
|
-
return typed(['call', '$__dyn_get_any_t', receiver, key, emitTypeTag(receiver, vt)], 'f64')
|
|
415
|
+
const receiver = asI64(base?.type ? base : typed(base, 'f64'))
|
|
416
|
+
return typed(['f64.reinterpret_i64', ['call', '$__dyn_get_any_t', receiver, key, emitTypeTag(receiver, vt)]], 'f64')
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Walk an AST expression that may resolve to an OBJECT literal at compile
|
|
420
|
+
// time. Returns the literal `['{}', ...]` node, or null. Handles direct
|
|
421
|
+
// literals and `.prop` chains over them. Spread props are unsupported —
|
|
422
|
+
// they shift slot positions and would need their own resolution.
|
|
423
|
+
function literalAst(obj) {
|
|
424
|
+
if (Array.isArray(obj) && obj[0] === '{}') {
|
|
425
|
+
// Bail on spreads — they change effective slot ordering.
|
|
426
|
+
const props = obj.slice(1)
|
|
427
|
+
const flat = props.length === 1 && Array.isArray(props[0]) && props[0][0] === ','
|
|
428
|
+
? props[0].slice(1) : props
|
|
429
|
+
for (const p of flat) if (Array.isArray(p) && p[0] === '...') return null
|
|
430
|
+
return obj
|
|
431
|
+
}
|
|
432
|
+
if (Array.isArray(obj) && obj[0] === '.' && typeof obj[2] === 'string') {
|
|
433
|
+
const inner = literalAst(obj[1])
|
|
434
|
+
if (!inner) return null
|
|
435
|
+
const innerProps = inner.slice(1)
|
|
436
|
+
const innerFlat = innerProps.length === 1 && Array.isArray(innerProps[0]) && innerProps[0][0] === ','
|
|
437
|
+
? innerProps[0].slice(1) : innerProps
|
|
438
|
+
for (const p of innerFlat) {
|
|
439
|
+
if (Array.isArray(p) && p[0] === ':' && p[1] === obj[2]) return literalAst(p[2])
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return null
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Slot index of `prop` within a literal-resolved expression, or -1.
|
|
446
|
+
function literalSlot(obj, prop) {
|
|
447
|
+
const lit = literalAst(obj)
|
|
448
|
+
if (!lit) return -1
|
|
449
|
+
const props = lit.slice(1)
|
|
450
|
+
const flat = props.length === 1 && Array.isArray(props[0]) && props[0][0] === ','
|
|
451
|
+
? props[0].slice(1) : props
|
|
452
|
+
for (let i = 0; i < flat.length; i++) {
|
|
453
|
+
const p = flat[i]
|
|
454
|
+
if (Array.isArray(p) && p[0] === ':' && p[1] === prop) return i
|
|
455
|
+
}
|
|
456
|
+
return -1
|
|
413
457
|
}
|
|
414
458
|
|
|
415
459
|
/** Emit .prop access for a WASM f64 node using schema or HASH fallback. */
|
|
416
460
|
function emitPropAccess(va, obj, prop) {
|
|
417
|
-
|
|
418
|
-
|
|
461
|
+
// Anonymous-literal fast path: when `obj` resolves at compile time to an
|
|
462
|
+
// object literal `{...}` (either directly, or through a `.prop` chain
|
|
463
|
+
// walked back to one), use the literal's slot index instead of falling
|
|
464
|
+
// through to `__dyn_get_expr`. Fresh OBJECT literals carry no off-16
|
|
465
|
+
// propsPtr so the dispatcher reads NULL_NAN. The varName-bound path
|
|
466
|
+
// (`let o = {a:1}; o.a`) already works via `ctx.schema.idOf(varName)`;
|
|
467
|
+
// this extends the same shape resolution to `({a:1}).a` and chains like
|
|
468
|
+
// `({a:{b:1}}).a.b` where the receiver is anonymous. Spread sources
|
|
469
|
+
// (`{...x}`) shift slot ordering and would need their own resolution.
|
|
470
|
+
const slot = literalSlot(obj, prop)
|
|
471
|
+
if (slot >= 0) return emitSchemaSlotRead(asF64(va), slot)
|
|
472
|
+
let schemaIdx = typeof obj === 'string' ? ctx.schema.find(obj, prop) : ctx.schema.find(null, prop)
|
|
473
|
+
// Chain receiver (e.g. `o.meta.bias`): when the chain resolves to a known
|
|
474
|
+
// OBJECT shape via JSON-shape propagation, the parent shape's `names`
|
|
475
|
+
// gives the slot directly. Avoids the structural ambiguity of
|
|
476
|
+
// ctx.schema.find(null, prop) when multiple registered schemas share a key.
|
|
477
|
+
if (schemaIdx < 0 && typeof obj !== 'string') {
|
|
478
|
+
const sh = shapeOf(obj)
|
|
479
|
+
if ((sh?.vt === VAL.OBJECT || sh?.vt === VAL.HASH) && sh.names) {
|
|
480
|
+
const i = sh.names.indexOf(prop)
|
|
481
|
+
if (i >= 0) schemaIdx = i
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
const key = asI64(emit(['str', prop]))
|
|
419
485
|
if (schemaIdx >= 0) return emitSchemaSlotRead(asF64(va), schemaIdx)
|
|
420
486
|
if (typeof obj === 'string') {
|
|
421
487
|
const vt = lookupValType(obj)
|
|
@@ -425,12 +491,21 @@ export default (ctx) => {
|
|
|
425
491
|
if (vt === VAL.HASH) {
|
|
426
492
|
return emitHashGetLocalConst(va, key, prop)
|
|
427
493
|
}
|
|
494
|
+
// OBJECT off-schema prop: __dyn_get_expr_t reads the per-OBJECT propsPtr
|
|
495
|
+
// at off-16 (set by __dyn_set). __hash_get assumes HASH bucket layout
|
|
496
|
+
// and would mis-read OBJECT memory.
|
|
497
|
+
if (vt === VAL.OBJECT) {
|
|
498
|
+
return emitDynGetExprTyped(va, key, vt)
|
|
499
|
+
}
|
|
428
500
|
if (vt == null) {
|
|
501
|
+
// In WASI mode, values are always JSON-derived (never PTR.EXTERNAL host objects).
|
|
502
|
+
// Skip the external branch and dispatch through the typed HASH/OBJECT path.
|
|
503
|
+
if (ctx.transform.host === 'wasi') return emitDynGetExprTyped(va, key, vt)
|
|
429
504
|
ctx.features.external = true
|
|
430
505
|
return emitDynGetAnyTyped(va, key, vt)
|
|
431
506
|
}
|
|
432
507
|
inc('__hash_get', '__str_hash', '__str_eq')
|
|
433
|
-
return typed(['call', '$__hash_get',
|
|
508
|
+
return typed(['f64.reinterpret_i64', ['call', '$__hash_get', asI64(va), key]], 'f64')
|
|
434
509
|
}
|
|
435
510
|
// Non-string receiver: route through HASH fast path when valTypeOf can
|
|
436
511
|
// resolve the chain to a known HASH (e.g. `o.meta.bias` where `o.meta` is
|
|
@@ -440,13 +515,13 @@ export default (ctx) => {
|
|
|
440
515
|
return emitHashGetLocalConst(va, key, prop)
|
|
441
516
|
}
|
|
442
517
|
inc('__dyn_get_expr')
|
|
443
|
-
return typed(['call', '$__dyn_get_expr',
|
|
518
|
+
return typed(['f64.reinterpret_i64', ['call', '$__dyn_get_expr', asI64(va), key]], 'f64')
|
|
444
519
|
}
|
|
445
520
|
|
|
446
521
|
// Runtime .length dispatch — factory elides branches for types that can't exist in
|
|
447
522
|
// this program (features.* + hash-stdlib presence). ARRAY is always live; STRING and
|
|
448
|
-
// number are always dispatched.
|
|
449
|
-
//
|
|
523
|
+
// number are always dispatched. The __len disjunction collapses to whichever of
|
|
524
|
+
// ARRAY/TYPED/HASH/SET/MAP are reachable. STRING covers both heap and SSO via __str_len.
|
|
450
525
|
ctx.core.stdlib['__length'] = () => {
|
|
451
526
|
const types = [PTR.ARRAY]
|
|
452
527
|
if (ctx.features.typedarray) types.push(PTR.TYPED)
|
|
@@ -464,24 +539,17 @@ export default (ctx) => {
|
|
|
464
539
|
(else (f64.const nan:${UNDEF_NAN}))))
|
|
465
540
|
(else (f64.const nan:${UNDEF_NAN})))`
|
|
466
541
|
const stringArm = `(if (result f64) (i32.eq (local.get $t) (i32.const ${PTR.STRING}))
|
|
467
|
-
(then
|
|
468
|
-
(if (result f64) (i32.ge_u (local.get $off) (i32.const 4))
|
|
469
|
-
(then (f64.convert_i32_s (call $__str_len (local.get $v))))
|
|
470
|
-
(else (f64.const nan:${UNDEF_NAN}))))
|
|
542
|
+
(then (f64.convert_i32_s (call $__str_len (local.get $v))))
|
|
471
543
|
(else ${lenArm}))`
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
: stringArm
|
|
477
|
-
return `(func $__length (param $v f64) (result f64)
|
|
478
|
-
(local $t i32) (local $off i32)
|
|
479
|
-
(if (result f64) (f64.eq (local.get $v) (local.get $v))
|
|
544
|
+
return `(func $__length (param $v i64) (result f64)
|
|
545
|
+
(local $f f64) (local $t i32) (local $off i32)
|
|
546
|
+
(local.set $f (f64.reinterpret_i64 (local.get $v)))
|
|
547
|
+
(if (result f64) (f64.eq (local.get $f) (local.get $f))
|
|
480
548
|
(then (f64.const nan:${UNDEF_NAN}))
|
|
481
549
|
(else
|
|
482
550
|
(local.set $t (call $__ptr_type (local.get $v)))
|
|
483
551
|
(local.set $off (call $__ptr_offset (local.get $v)))
|
|
484
|
-
${
|
|
552
|
+
${stringArm})))`
|
|
485
553
|
}
|
|
486
554
|
|
|
487
555
|
// === Property dispatch (.length, .prop) ===
|
|
@@ -491,7 +559,7 @@ export default (ctx) => {
|
|
|
491
559
|
if (typeof obj === 'string' && ctx.schema.isBoxed(obj)) {
|
|
492
560
|
if (prop === 'length') {
|
|
493
561
|
const inner = ctx.schema.emitInner(obj)
|
|
494
|
-
return typed(['f64.convert_i32_s', ['call', '$__len', inner]], 'f64')
|
|
562
|
+
return typed(['f64.convert_i32_s', ['call', '$__len', ['i64.reinterpret_f64', inner]]], 'f64')
|
|
495
563
|
}
|
|
496
564
|
const idx = ctx.schema.find(obj, prop)
|
|
497
565
|
if (idx >= 0) return emitSchemaSlotRead(asF64(emit(obj)), idx)
|
|
@@ -518,6 +586,12 @@ export default (ctx) => {
|
|
|
518
586
|
return typed(['f64.convert_i32_s', lenI32], 'f64')
|
|
519
587
|
}
|
|
520
588
|
}
|
|
589
|
+
// String literal: fold to its UTF-8 byte length. jz strings are stored as
|
|
590
|
+
// UTF-8 and __str_byteLen returns byte count, so this matches the runtime
|
|
591
|
+
// semantics. Skips the call + NaN-unbox round-trip entirely.
|
|
592
|
+
if (Array.isArray(obj) && (obj[0] === 'str' || obj[0] == null) && typeof obj[1] === 'string') {
|
|
593
|
+
return typed(['f64.const', Buffer.byteLength(obj[1], 'utf8')], 'f64')
|
|
594
|
+
}
|
|
521
595
|
const vt = typeof obj === 'string' ? repOf(obj)?.val : valTypeOf(obj)
|
|
522
596
|
return emitLengthAccess(asF64(emit(obj)), vt)
|
|
523
597
|
}
|
|
@@ -544,21 +618,25 @@ export default (ctx) => {
|
|
|
544
618
|
if (typeof obj === 'string') {
|
|
545
619
|
const objType = lookupValType(obj)
|
|
546
620
|
if (usesDynProps(objType)) {
|
|
547
|
-
access = emitDynGetExprTyped(['local.get', `$${t}`],
|
|
621
|
+
access = emitDynGetExprTyped(['local.get', `$${t}`], asI64(emit(['str', prop])), objType)
|
|
548
622
|
} else if (objType === VAL.HASH) {
|
|
549
|
-
access = emitHashGetLocalConst(['local.get', `$${t}`],
|
|
623
|
+
access = emitHashGetLocalConst(['local.get', `$${t}`], asI64(emit(['str', prop])), prop)
|
|
550
624
|
} else if (objType == null) {
|
|
551
|
-
|
|
552
|
-
|
|
625
|
+
// Unknown receiver — in WASI mode use typed dispatch (no PTR.EXTERNAL values).
|
|
626
|
+
// In JS host mode use __dyn_get_any_t but don't force features.external here
|
|
627
|
+
// since ?.prop short-circuits on nullish (EXTERNAL arm is dead unless already on).
|
|
628
|
+
access = ctx.transform.host === 'wasi'
|
|
629
|
+
? emitDynGetExprTyped(['local.get', `$${t}`], asI64(emit(['str', prop])), objType)
|
|
630
|
+
: emitDynGetAnyTyped(['local.get', `$${t}`], asI64(emit(['str', prop])), objType)
|
|
553
631
|
} else {
|
|
554
632
|
inc('__hash_get', '__str_hash', '__str_eq')
|
|
555
|
-
access = ['call', '$__hash_get', ['local.get', `$${t}`],
|
|
633
|
+
access = ['f64.reinterpret_i64', ['call', '$__hash_get', ['i64.reinterpret_f64', ['local.get', `$${t}`]], asI64(emit(['str', prop]))]]
|
|
556
634
|
}
|
|
557
635
|
} else {
|
|
558
636
|
if (valTypeOf(obj) === VAL.HASH) {
|
|
559
|
-
access = emitHashGetLocalConst(['local.get', `$${t}`],
|
|
637
|
+
access = emitHashGetLocalConst(['local.get', `$${t}`], asI64(emit(['str', prop])), prop)
|
|
560
638
|
} else {
|
|
561
|
-
access = emitDynGetExprTyped(['local.get', `$${t}`],
|
|
639
|
+
access = emitDynGetExprTyped(['local.get', `$${t}`], asI64(emit(['str', prop])), valTypeOf(obj))
|
|
562
640
|
}
|
|
563
641
|
}
|
|
564
642
|
}
|
|
@@ -583,6 +661,22 @@ export default (ctx) => {
|
|
|
583
661
|
|
|
584
662
|
// Optional call: fn?.(...args) → null if fn is null, else call fn
|
|
585
663
|
ctx.core.emit['?.()'] = (callee, ...args) => {
|
|
664
|
+
// Method-reference callee: `recv.m(...)` or `recv?.m(...)` form. Methods are
|
|
665
|
+
// statically registered emitters and aren't real closure values, so route them
|
|
666
|
+
// as a direct method call. The outer optional short-circuits when the receiver
|
|
667
|
+
// is nullish — the method itself is statically known to exist.
|
|
668
|
+
if (Array.isArray(callee) && (callee[0] === '.' || callee[0] === '?.') && typeof callee[2] === 'string') {
|
|
669
|
+
const methodEmit = ctx.core.emit[`.${callee[2]}`]
|
|
670
|
+
if (methodEmit) {
|
|
671
|
+
const recv = callee[1]
|
|
672
|
+
const t = temp()
|
|
673
|
+
const va = asF64(emit(recv))
|
|
674
|
+
const vt = typeof recv === 'string' ? repOf(recv)?.val : valTypeOf(recv)
|
|
675
|
+
if (vt) updateRep(t, { val: vt })
|
|
676
|
+
const callResult = methodEmit(t, ...args)
|
|
677
|
+
return emitNullishGuarded(['local.tee', `$${t}`, va], asF64(callResult))
|
|
678
|
+
}
|
|
679
|
+
}
|
|
586
680
|
const t = temp()
|
|
587
681
|
const va = asF64(emit(callee))
|
|
588
682
|
// If nullish → return NULL_NAN, else call via fn.call
|
|
@@ -596,6 +690,7 @@ export default (ctx) => {
|
|
|
596
690
|
// initialized in __start (see compile.js). Comparison patterns (typeof x === 'string') are optimized
|
|
597
691
|
// in prepare.js (resolveTypeof) and emitted as direct type checks via emitTypeofCmp, bypassing this path.
|
|
598
692
|
ctx.core.emit['typeof'] = (a) => {
|
|
693
|
+
if (valTypeOf(a) === VAL.BIGINT) return emit(['str', 'bigint'])
|
|
599
694
|
if (!ctx.runtime.typeofStrs) {
|
|
600
695
|
ctx.runtime.typeofStrs = ['number', 'undefined', 'string', 'function', 'symbol', 'object']
|
|
601
696
|
for (const s of ctx.runtime.typeofStrs)
|
|
@@ -604,20 +699,27 @@ export default (ctx) => {
|
|
|
604
699
|
inc('__typeof')
|
|
605
700
|
// Receiver type unknown; enable branches that wouldn't otherwise be reachable.
|
|
606
701
|
ctx.features.closure = true
|
|
607
|
-
return typed(['call', '$__typeof',
|
|
702
|
+
return typed(['call', '$__typeof', asI64(emit(a))], 'f64')
|
|
608
703
|
}
|
|
609
704
|
|
|
610
705
|
ctx.core.stdlib['__typeof'] = () => {
|
|
611
|
-
const stringTest =
|
|
612
|
-
? `(i32.or (i32.eq (local.get $t) (i32.const ${PTR.STRING})) (i32.eq (local.get $t) (i32.const ${PTR.SSO})))`
|
|
613
|
-
: `(i32.eq (local.get $t) (i32.const ${PTR.STRING}))`
|
|
706
|
+
const stringTest = `(i32.eq (local.get $t) (i32.const ${PTR.STRING}))`
|
|
614
707
|
const closureArm = ctx.features.closure
|
|
615
708
|
? `(if (i32.eq (local.get $t) (i32.const ${PTR.CLOSURE}))
|
|
616
709
|
(then (return (global.get $__tof_function))))`
|
|
617
710
|
: ''
|
|
618
|
-
return `(func $__typeof (param $v
|
|
619
|
-
(local $t i32)
|
|
620
|
-
(
|
|
711
|
+
return `(func $__typeof (param $v i64) (result f64)
|
|
712
|
+
(local $f f64) (local $t i32)
|
|
713
|
+
(local.set $f (f64.reinterpret_i64 (local.get $v)))
|
|
714
|
+
(if (f64.eq (local.get $f) (local.get $f))
|
|
715
|
+
(then (return (global.get $__tof_number))))
|
|
716
|
+
;; Canonical JS NaN (0x7FF8000000000000) overlaps ATOM tag=0 aux=0 offset=0.
|
|
717
|
+
;; That bit pattern is the math NaN value, not a tagged pointer — treat as "number".
|
|
718
|
+
;; Negative-NaN bit patterns (sign bit set) don't match NAN_PREFIX so are uniquely numeric.
|
|
719
|
+
(if (i32.or
|
|
720
|
+
(i64.eq (local.get $v) (i64.const 0x7FF8000000000000))
|
|
721
|
+
(i64.eq (i64.and (local.get $v) (i64.const 0xFFF0000000000000))
|
|
722
|
+
(i64.const 0xFFF0000000000000)))
|
|
621
723
|
(then (return (global.get $__tof_number))))
|
|
622
724
|
(if (call $__is_nullish (local.get $v))
|
|
623
725
|
(then (return (global.get $__tof_undefined))))
|
|
@@ -635,9 +737,9 @@ export default (ctx) => {
|
|
|
635
737
|
|
|
636
738
|
// Low-level pointer helpers callable from jz code
|
|
637
739
|
ctx.core.emit['__mkptr'] = (t, a, o) => typed(['call', '$__mkptr', asI32(emit(t)), asI32(emit(a)), asI32(emit(o))], 'f64')
|
|
638
|
-
ctx.core.emit['__ptr_type'] = (p) => typed(['f64.convert_i32_s', ['call', '$__ptr_type',
|
|
639
|
-
ctx.core.emit['__ptr_aux'] = (p) => typed(['f64.convert_i32_s', ['call', '$__ptr_aux',
|
|
640
|
-
ctx.core.emit['__ptr_offset'] = (p) => typed(['f64.convert_i32_s', ['call', '$__ptr_offset',
|
|
740
|
+
ctx.core.emit['__ptr_type'] = (p) => typed(['f64.convert_i32_s', ['call', '$__ptr_type', asI64(emit(p))]], 'f64')
|
|
741
|
+
ctx.core.emit['__ptr_aux'] = (p) => typed(['f64.convert_i32_s', ['call', '$__ptr_aux', asI64(emit(p))]], 'f64')
|
|
742
|
+
ctx.core.emit['__ptr_offset'] = (p) => typed(['f64.convert_i32_s', ['call', '$__ptr_offset', asI64(emit(p))]], 'f64')
|
|
641
743
|
|
|
642
744
|
// Error(msg) — passthrough (throw handles any value)
|
|
643
745
|
ctx.core.emit['Error'] = (msg) => asF64(emit(msg))
|