jz 0.1.0 → 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 +221 -62
- package/cli.js +34 -8
- package/index.js +133 -14
- package/module/array.js +399 -131
- package/module/collection.js +457 -212
- package/module/console.js +170 -87
- package/module/core.js +247 -143
- package/module/date.js +691 -0
- package/module/function.js +84 -23
- package/module/index.js +2 -1
- package/module/json.js +485 -138
- package/module/math.js +81 -40
- package/module/number.js +189 -83
- package/module/object.js +228 -46
- package/module/regex.js +34 -30
- package/module/schema.js +17 -18
- package/module/string.js +483 -227
- package/module/symbol.js +6 -4
- package/module/timer.js +90 -32
- package/module/typedarray.js +176 -44
- package/package.json +5 -10
- package/src/analyze.js +639 -124
- package/src/autoload.js +183 -0
- package/src/compile.js +448 -1083
- package/src/ctx.js +42 -12
- package/src/emit.js +646 -147
- package/src/host.js +273 -68
- package/src/ir.js +81 -31
- package/src/jzify.js +220 -36
- package/src/narrow.js +956 -0
- package/src/optimize.js +628 -42
- package/src/plan.js +1233 -0
- package/src/prepare.js +438 -256
- package/src/vectorize.js +1016 -0
- package/wasi.js +23 -4
package/module/core.js
CHANGED
|
@@ -2,19 +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 {
|
|
13
|
-
import {
|
|
12
|
+
import { typed, asF64, asI32, asI64, NULL_NAN, UNDEF_NAN, temp, usesDynProps, ptrOffsetIR, isNullish } from '../src/ir.js'
|
|
13
|
+
import { emit } from '../src/emit.js'
|
|
14
|
+
import { valTypeOf, lookupValType, VAL, T, repOf, updateRep, shapeOf } from '../src/analyze.js'
|
|
15
|
+
import { err, inc, PTR, LAYOUT } from '../src/ctx.js'
|
|
14
16
|
import { initSchema } from './schema.js'
|
|
15
17
|
import { strHashLiteral } from './collection.js'
|
|
16
18
|
|
|
17
|
-
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')
|
|
18
21
|
|
|
19
22
|
const PTR_BY_VAL = {
|
|
20
23
|
[VAL.ARRAY]: PTR.ARRAY,
|
|
@@ -34,91 +37,81 @@ export default (ctx) => {
|
|
|
34
37
|
__typed_data: ['__ptr_offset', '__ptr_aux'],
|
|
35
38
|
__ptr_offset: [],
|
|
36
39
|
__is_str_key: ['__ptr_type'],
|
|
37
|
-
__str_len: ['__ptr_type', '__ptr_offset'],
|
|
40
|
+
__str_len: ['__ptr_type', '__ptr_offset', '__ptr_aux'],
|
|
38
41
|
__set_len: [],
|
|
39
|
-
__length:
|
|
40
|
-
const d = ['__ptr_type', '__ptr_offset', '__str_len', '__len']
|
|
41
|
-
if (ctx.features.sso) d.push('__ptr_aux')
|
|
42
|
-
return d
|
|
43
|
-
},
|
|
42
|
+
__length: ['__ptr_type', '__ptr_offset', '__str_len', '__len'],
|
|
44
43
|
__typeof: ['__ptr_type', '__is_nullish'],
|
|
45
44
|
__alloc_hdr: ['__alloc'],
|
|
46
45
|
})
|
|
47
46
|
|
|
48
|
-
ctx.core.stdlib['__is_nullish'] = `(func $__is_nullish (param $v
|
|
47
|
+
ctx.core.stdlib['__is_nullish'] = `(func $__is_nullish (param $v i64) (result i32)
|
|
49
48
|
(i32.or
|
|
50
|
-
(i64.eq (
|
|
51
|
-
(i64.eq (
|
|
49
|
+
(i64.eq (local.get $v) (i64.const ${NULL_NAN}))
|
|
50
|
+
(i64.eq (local.get $v) (i64.const ${UNDEF_NAN}))))`
|
|
52
51
|
|
|
53
|
-
ctx.core.stdlib['__eq'] = `(func $__eq (param $a
|
|
54
|
-
(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)
|
|
55
54
|
;; Fast path: bit equality covers identical pointers AND interned/SSO strings (same content
|
|
56
55
|
;; → same bits). Failing universal-NaN test catches NaN===NaN→false. Saves the NaN-check
|
|
57
56
|
;; pair (4 f64.eq) on the hottest case in watr (op === 'literal-string').
|
|
58
|
-
(
|
|
59
|
-
|
|
60
|
-
(if (result i32) (i64.eq (local.get $ra) (local.get $rb))
|
|
61
|
-
(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})))
|
|
62
59
|
(else
|
|
63
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)))
|
|
64
63
|
(if (result i32)
|
|
65
64
|
(i32.and
|
|
66
|
-
(f64.eq (local.get $
|
|
67
|
-
(f64.eq (local.get $
|
|
68
|
-
(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)))
|
|
69
68
|
(else
|
|
70
|
-
;;
|
|
71
|
-
;;
|
|
72
|
-
(local.set $ta (i32.wrap_i64 (i64.and (i64.shr_u (local.get $
|
|
73
|
-
(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}))))
|
|
74
73
|
(if (result i32)
|
|
75
74
|
(i32.and
|
|
76
|
-
(i32.
|
|
77
|
-
|
|
78
|
-
(i32.eq (local.get $ta) (i32.const ${PTR.SSO})))
|
|
79
|
-
(i32.or
|
|
80
|
-
(i32.eq (local.get $tb) (i32.const ${PTR.STRING}))
|
|
81
|
-
(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})))
|
|
82
77
|
(then (call $__str_eq (local.get $a) (local.get $b)))
|
|
83
78
|
(else (i32.const 0))))))))`
|
|
84
79
|
|
|
85
|
-
ctx.core.stdlib['__is_null'] = `(func $__is_null (param $v
|
|
86
|
-
(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})))`
|
|
87
82
|
|
|
88
83
|
// Truthy check: handles regular numbers AND NaN-boxed pointers
|
|
89
84
|
// Falsy: 0, -0, NaN, null, undefined, "" (empty SSO)
|
|
90
|
-
ctx.core.stdlib['__is_truthy'] = `(func $__is_truthy (param $v
|
|
91
|
-
(local $
|
|
92
|
-
(
|
|
93
|
-
|
|
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)))
|
|
94
90
|
(else
|
|
95
|
-
(local.set $bits (i64.reinterpret_f64 (local.get $v)))
|
|
96
91
|
(i32.and
|
|
97
92
|
(i32.and
|
|
98
|
-
(i64.ne (local.get $
|
|
99
|
-
(i64.ne (local.get $
|
|
93
|
+
(i64.ne (local.get $v) (i64.const ${NAN_BITS}))
|
|
94
|
+
(i64.ne (local.get $v) (i64.const ${NULL_NAN})))
|
|
100
95
|
(i32.and
|
|
101
|
-
(i64.ne (local.get $
|
|
102
|
-
(i64.ne (local.get $
|
|
96
|
+
(i64.ne (local.get $v) (i64.const ${UNDEF_NAN}))
|
|
97
|
+
(i64.ne (local.get $v) (i64.const 0x7FFA400000000000)))))))`
|
|
103
98
|
|
|
104
|
-
ctx.core.stdlib['__is_str_key'] = `(func $__is_str_key (param $v
|
|
105
|
-
(local $
|
|
106
|
-
(
|
|
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))
|
|
107
103
|
(then (i32.const 0))
|
|
108
104
|
(else
|
|
109
|
-
(
|
|
110
|
-
(i32.or
|
|
111
|
-
(i32.eq (local.get $t) (i32.const ${PTR.STRING}))
|
|
112
|
-
(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})))))`
|
|
113
106
|
|
|
114
107
|
|
|
115
108
|
// Default dynamic-property helpers are harmless stubs. The collection module
|
|
116
109
|
// overrides them with the real sidecar-property implementation.
|
|
117
|
-
ctx.core.stdlib['__dyn_get'] = `(func $__dyn_get (param $obj
|
|
118
|
-
(
|
|
119
|
-
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)
|
|
120
113
|
(local.get $fallback))`
|
|
121
|
-
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)
|
|
122
115
|
(local.get $val))`
|
|
123
116
|
ctx.core.stdlib['__dyn_move'] = `(func $__dyn_move (param $oldOff i32) (param $newOff i32))`
|
|
124
117
|
|
|
@@ -128,22 +121,22 @@ export default (ctx) => {
|
|
|
128
121
|
|
|
129
122
|
ctx.core.stdlib['__mkptr'] = `(func $__mkptr (param $type i32) (param $aux i32) (param $offset i32) (result f64)
|
|
130
123
|
(f64.reinterpret_i64 (i64.or
|
|
131
|
-
(i64.
|
|
124
|
+
(i64.const ${NAN_BITS})
|
|
132
125
|
(i64.or
|
|
133
|
-
(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}))
|
|
134
127
|
(i64.or
|
|
135
|
-
(i64.shl (i64.and (i64.extend_i32_u (local.get $aux)) (i64.const
|
|
136
|
-
(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})))))))`
|
|
137
130
|
|
|
138
|
-
ctx.core.stdlib['__ptr_offset'] = `(func $__ptr_offset (param $ptr
|
|
131
|
+
ctx.core.stdlib['__ptr_offset'] = `(func $__ptr_offset (param $ptr i64) (result i32)
|
|
139
132
|
(local $bits i64) (local $off i32)
|
|
140
|
-
(local.set $bits (
|
|
141
|
-
(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}))))
|
|
142
135
|
;; Arrays can be reallocated during growth; follow forwarding pointer (cap=-1 sentinel).
|
|
143
136
|
;; Bounds are checked inside the loop so non-array ptrs skip them entirely, and well-formed
|
|
144
137
|
;; ARRAY ptrs without forwarding still pay only one bounds check before the cap load.
|
|
145
138
|
(if (i32.eq
|
|
146
|
-
(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})))
|
|
147
140
|
(i32.const ${PTR.ARRAY}))
|
|
148
141
|
(then
|
|
149
142
|
(block $done
|
|
@@ -155,14 +148,19 @@ export default (ctx) => {
|
|
|
155
148
|
(br $follow)))))
|
|
156
149
|
(local.get $off))`
|
|
157
150
|
|
|
158
|
-
ctx.core.stdlib['__ptr_aux'] = `(func $__ptr_aux (param $ptr
|
|
159
|
-
(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}))))`
|
|
160
153
|
|
|
161
|
-
ctx.core.stdlib['__ptr_type'] = `(func $__ptr_type (param $ptr
|
|
162
|
-
(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}))))`
|
|
163
156
|
|
|
164
157
|
// === Bump allocator ===
|
|
165
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
|
+
|
|
166
164
|
if (ctx.memory.shared) {
|
|
167
165
|
// Shared memory: heap offset stored at memory[1020] (i32), just before heap start at 1024
|
|
168
166
|
ctx.core.stdlib['__alloc'] = `(func $__alloc (param $bytes i32) (result i32)
|
|
@@ -170,7 +168,7 @@ export default (ctx) => {
|
|
|
170
168
|
(local.set $ptr (i32.load (i32.const 1020)))
|
|
171
169
|
(i32.store (i32.const 1020) (i32.and (i32.add (i32.add (local.get $ptr) (local.get $bytes)) (i32.const 7)) (i32.const -8)))
|
|
172
170
|
(local.get $ptr))`
|
|
173
|
-
ctx.core.stdlib['
|
|
171
|
+
ctx.core.stdlib['__clear'] = `(func $__clear
|
|
174
172
|
(i32.store (i32.const 1020) (i32.const 1024)))`
|
|
175
173
|
} else {
|
|
176
174
|
// Own memory: heap offset in a global, auto-grow when needed
|
|
@@ -187,7 +185,7 @@ export default (ctx) => {
|
|
|
187
185
|
(i32.const -1)) (then (unreachable)))))
|
|
188
186
|
(global.set $__heap (local.get $next))
|
|
189
187
|
(local.get $ptr))`
|
|
190
|
-
ctx.core.stdlib['
|
|
188
|
+
ctx.core.stdlib['__clear'] = `(func $__clear
|
|
191
189
|
(global.set $__heap (i32.const 1024)))`
|
|
192
190
|
}
|
|
193
191
|
|
|
@@ -209,7 +207,7 @@ export default (ctx) => {
|
|
|
209
207
|
(else (i32.shr_u (local.get $et) (i32.const 1)))))))`
|
|
210
208
|
|
|
211
209
|
// Real data address for any TYPED ptr: owned → offset, view → [offset+4].
|
|
212
|
-
ctx.core.stdlib['__typed_data'] = `(func $__typed_data (param $ptr
|
|
210
|
+
ctx.core.stdlib['__typed_data'] = `(func $__typed_data (param $ptr i64) (result i32)
|
|
213
211
|
(local $off i32)
|
|
214
212
|
(local.set $off (call $__ptr_offset (local.get $ptr)))
|
|
215
213
|
(if (result i32) (i32.and (call $__ptr_aux (local.get $ptr)) (i32.const 8))
|
|
@@ -218,11 +216,11 @@ export default (ctx) => {
|
|
|
218
216
|
|
|
219
217
|
// Hot (~85M calls in watr self-host). Type/offset extraction inlined; forwarding
|
|
220
218
|
// loop only entered for ARRAY. ARRAY fast path dominates (nodes?.length, out.length …).
|
|
221
|
-
ctx.core.stdlib['__len'] = `(func $__len (param $ptr
|
|
219
|
+
ctx.core.stdlib['__len'] = `(func $__len (param $ptr i64) (result i32)
|
|
222
220
|
(local $bits i64) (local $t i32) (local $off i32) (local $aux i32)
|
|
223
|
-
(local.set $bits (
|
|
224
|
-
(local.set $t (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const
|
|
225
|
-
(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}))))
|
|
226
224
|
;; ARRAY fast path: follow forwarding inline, then load len at off-8.
|
|
227
225
|
(if (result i32)
|
|
228
226
|
(i32.and (i32.eq (local.get $t) (i32.const 1)) (i32.ge_u (local.get $off) (i32.const 8)))
|
|
@@ -246,16 +244,16 @@ export default (ctx) => {
|
|
|
246
244
|
(then
|
|
247
245
|
(if (result i32) (i32.eq (local.get $t) (i32.const 3))
|
|
248
246
|
(then
|
|
249
|
-
(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}))))
|
|
250
248
|
(if (result i32) (i32.and (local.get $aux) (i32.const 8))
|
|
251
249
|
(then (i32.shr_u (i32.load (local.get $off))
|
|
252
250
|
(call $__typed_shift (i32.and (local.get $aux) (i32.const 7)))))
|
|
253
251
|
(else (i32.shr_u (i32.load (i32.sub (local.get $off) (i32.const 8)))
|
|
254
|
-
(call $__typed_shift (local.get $aux))))))
|
|
252
|
+
(call $__typed_shift (i32.and (local.get $aux) (i32.const 7)))))))
|
|
255
253
|
(else (i32.load (i32.sub (local.get $off) (i32.const 8))))))
|
|
256
254
|
(else (i32.const 0))))))`
|
|
257
255
|
|
|
258
|
-
ctx.core.stdlib['__cap'] = `(func $__cap (param $ptr
|
|
256
|
+
ctx.core.stdlib['__cap'] = `(func $__cap (param $ptr i64) (result i32)
|
|
259
257
|
(local $t i32) (local $off i32) (local $aux i32)
|
|
260
258
|
(local.set $t (call $__ptr_type (local.get $ptr)))
|
|
261
259
|
(local.set $off (call $__ptr_offset (local.get $ptr)))
|
|
@@ -277,28 +275,30 @@ export default (ctx) => {
|
|
|
277
275
|
(then (i32.shr_u (i32.load (local.get $off))
|
|
278
276
|
(call $__typed_shift (i32.and (local.get $aux) (i32.const 7)))))
|
|
279
277
|
(else (i32.shr_u (i32.load (i32.sub (local.get $off) (i32.const 4)))
|
|
280
|
-
(call $__typed_shift (local.get $aux))))))
|
|
278
|
+
(call $__typed_shift (i32.and (local.get $aux) (i32.const 7)))))))
|
|
281
279
|
(else (i32.load (i32.sub (local.get $off) (i32.const 4))))))
|
|
282
280
|
(else (i32.const 0))))`
|
|
283
281
|
|
|
284
|
-
// String (
|
|
285
|
-
ctx.core.stdlib['__str_len'] = `(func $__str_len (param $ptr
|
|
286
|
-
(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)))))
|
|
287
290
|
(local.set $off (call $__ptr_offset (local.get $ptr)))
|
|
288
|
-
(if (result i32)
|
|
289
|
-
(i32.and
|
|
290
|
-
(i32.eq (call $__ptr_type (local.get $ptr)) (i32.const ${PTR.STRING}))
|
|
291
|
-
(i32.ge_u (local.get $off) (i32.const 4)))
|
|
291
|
+
(if (result i32) (i32.ge_u (local.get $off) (i32.const 4))
|
|
292
292
|
(then (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
293
293
|
(else (i32.const 0))))`
|
|
294
294
|
|
|
295
295
|
// Set len in memory (for push/pop). Hot (~42M calls in watr self-host).
|
|
296
296
|
// Type/offset extraction inlined; forwarding loop only entered for ARRAY.
|
|
297
|
-
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)
|
|
298
298
|
(local $bits i64) (local $t i32) (local $off i32)
|
|
299
|
-
(local.set $bits (
|
|
300
|
-
(local.set $t (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const
|
|
301
|
-
(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}))))
|
|
302
302
|
;; Only ARRAY (1), TYPED (3), HASH (7), SET (8), MAP (9) carry an 8-byte header.
|
|
303
303
|
;; Of those, only ARRAY can be forwarded — follow the chain inline.
|
|
304
304
|
(if
|
|
@@ -320,20 +320,25 @@ export default (ctx) => {
|
|
|
320
320
|
(br $follow)))))
|
|
321
321
|
(i32.store (i32.sub (local.get $off) (i32.const 8)) (local.get $len)))))`
|
|
322
322
|
|
|
323
|
-
// 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).
|
|
324
328
|
ctx.core.stdlib['__alloc_hdr'] = `(func $__alloc_hdr (param $len i32) (param $cap i32) (param $stride i32) (result i32)
|
|
325
329
|
(local $ptr i32)
|
|
326
|
-
(local.set $ptr (call $__alloc (i32.add (i32.const
|
|
327
|
-
(
|
|
328
|
-
(i32.store (i32.add (local.get $ptr) (i32.const
|
|
329
|
-
(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)))`
|
|
330
335
|
|
|
331
336
|
// Allocator + exports are deferred: only included when memory is actually needed.
|
|
332
337
|
// Any module using allocPtr/inc('__alloc') pulls these in via ctx.core.stdlibDeps.
|
|
333
|
-
// compile.js emits _alloc/
|
|
338
|
+
// compile.js emits _alloc/_clear exports + memory section only when __alloc is in includes.
|
|
334
339
|
ctx.core._allocRawFuncs = [
|
|
335
340
|
'(func (export "_alloc") (param $bytes i32) (result i32) (call $__alloc (local.get $bytes)))',
|
|
336
|
-
'(func (export "
|
|
341
|
+
'(func (export "_clear") (call $__clear))',
|
|
337
342
|
]
|
|
338
343
|
|
|
339
344
|
// Not-nullish check: f64 WAT node is neither NULL_NAN nor UNDEF_NAN.
|
|
@@ -341,11 +346,12 @@ export default (ctx) => {
|
|
|
341
346
|
// apply — otherwise this would always emit a __is_nullish call even for provable cases.
|
|
342
347
|
const notNullish = v => ['i32.eqz', isNullish(v)]
|
|
343
348
|
|
|
344
|
-
// 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.
|
|
345
351
|
const emitNullishGuarded = (guard, access) => typed(['if', ['result', 'f64'],
|
|
346
352
|
notNullish(guard),
|
|
347
353
|
['then', access],
|
|
348
|
-
['else', ['f64.const', `nan:${
|
|
354
|
+
['else', ['f64.const', `nan:${UNDEF_NAN}`]]], 'f64')
|
|
349
355
|
|
|
350
356
|
// === Shared dispatch helpers ===
|
|
351
357
|
|
|
@@ -360,11 +366,11 @@ export default (ctx) => {
|
|
|
360
366
|
return typed(['f64.convert_i32_s', ['i32.load', ['i32.sub', off, ['i32.const', 8]]]], 'f64')
|
|
361
367
|
}
|
|
362
368
|
if (vt === VAL.TYPED)
|
|
363
|
-
return typed(['f64.convert_i32_s', ['call', '$__len', va]], 'f64')
|
|
369
|
+
return typed(['f64.convert_i32_s', ['call', '$__len', ['i64.reinterpret_f64', va]]], 'f64')
|
|
364
370
|
// Known string → byteLen (handles SSO + heap)
|
|
365
371
|
if (vt === VAL.STRING) {
|
|
366
372
|
inc('__str_byteLen')
|
|
367
|
-
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')
|
|
368
374
|
}
|
|
369
375
|
// Unknown → runtime dispatch via stdlib. Set/Map dispatch arms are pulled
|
|
370
376
|
// only when user code actually constructs Set/Map (collection.js sets the
|
|
@@ -373,7 +379,7 @@ export default (ctx) => {
|
|
|
373
379
|
// commonly passed from JS via jz.memory.* without an in-program constructor.
|
|
374
380
|
inc('__length')
|
|
375
381
|
ctx.features.typedarray = true
|
|
376
|
-
return typed(['call', '$__length', va], 'f64')
|
|
382
|
+
return typed(['call', '$__length', ['i64.reinterpret_f64', va]], 'f64')
|
|
377
383
|
}
|
|
378
384
|
|
|
379
385
|
// Known-schema fields live in the object payload. Dynamic sidecars are only
|
|
@@ -387,8 +393,8 @@ export default (ctx) => {
|
|
|
387
393
|
|
|
388
394
|
function emitHashGetLocalConst(base, key, prop) {
|
|
389
395
|
inc('__hash_get_local_h')
|
|
390
|
-
const receiver = base?.type ?
|
|
391
|
-
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')
|
|
392
398
|
}
|
|
393
399
|
|
|
394
400
|
function emitTypeTag(receiver, vt) {
|
|
@@ -400,20 +406,82 @@ export default (ctx) => {
|
|
|
400
406
|
|
|
401
407
|
function emitDynGetExprTyped(base, key, vt) {
|
|
402
408
|
inc('__dyn_get_expr_t')
|
|
403
|
-
const receiver = base?.type ?
|
|
404
|
-
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')
|
|
405
411
|
}
|
|
406
412
|
|
|
407
413
|
function emitDynGetAnyTyped(base, key, vt) {
|
|
408
414
|
inc('__dyn_get_any_t')
|
|
409
|
-
const receiver = base?.type ?
|
|
410
|
-
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
|
|
411
457
|
}
|
|
412
458
|
|
|
413
459
|
/** Emit .prop access for a WASM f64 node using schema or HASH fallback. */
|
|
414
460
|
function emitPropAccess(va, obj, prop) {
|
|
415
|
-
|
|
416
|
-
|
|
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]))
|
|
417
485
|
if (schemaIdx >= 0) return emitSchemaSlotRead(asF64(va), schemaIdx)
|
|
418
486
|
if (typeof obj === 'string') {
|
|
419
487
|
const vt = lookupValType(obj)
|
|
@@ -423,12 +491,21 @@ export default (ctx) => {
|
|
|
423
491
|
if (vt === VAL.HASH) {
|
|
424
492
|
return emitHashGetLocalConst(va, key, prop)
|
|
425
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
|
+
}
|
|
426
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)
|
|
427
504
|
ctx.features.external = true
|
|
428
505
|
return emitDynGetAnyTyped(va, key, vt)
|
|
429
506
|
}
|
|
430
507
|
inc('__hash_get', '__str_hash', '__str_eq')
|
|
431
|
-
return typed(['call', '$__hash_get',
|
|
508
|
+
return typed(['f64.reinterpret_i64', ['call', '$__hash_get', asI64(va), key]], 'f64')
|
|
432
509
|
}
|
|
433
510
|
// Non-string receiver: route through HASH fast path when valTypeOf can
|
|
434
511
|
// resolve the chain to a known HASH (e.g. `o.meta.bias` where `o.meta` is
|
|
@@ -438,13 +515,13 @@ export default (ctx) => {
|
|
|
438
515
|
return emitHashGetLocalConst(va, key, prop)
|
|
439
516
|
}
|
|
440
517
|
inc('__dyn_get_expr')
|
|
441
|
-
return typed(['call', '$__dyn_get_expr',
|
|
518
|
+
return typed(['f64.reinterpret_i64', ['call', '$__dyn_get_expr', asI64(va), key]], 'f64')
|
|
442
519
|
}
|
|
443
520
|
|
|
444
521
|
// Runtime .length dispatch — factory elides branches for types that can't exist in
|
|
445
522
|
// this program (features.* + hash-stdlib presence). ARRAY is always live; STRING and
|
|
446
|
-
// number are always dispatched.
|
|
447
|
-
//
|
|
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.
|
|
448
525
|
ctx.core.stdlib['__length'] = () => {
|
|
449
526
|
const types = [PTR.ARRAY]
|
|
450
527
|
if (ctx.features.typedarray) types.push(PTR.TYPED)
|
|
@@ -462,24 +539,17 @@ export default (ctx) => {
|
|
|
462
539
|
(else (f64.const nan:${UNDEF_NAN}))))
|
|
463
540
|
(else (f64.const nan:${UNDEF_NAN})))`
|
|
464
541
|
const stringArm = `(if (result f64) (i32.eq (local.get $t) (i32.const ${PTR.STRING}))
|
|
465
|
-
(then
|
|
466
|
-
(if (result f64) (i32.ge_u (local.get $off) (i32.const 4))
|
|
467
|
-
(then (f64.convert_i32_s (call $__str_len (local.get $v))))
|
|
468
|
-
(else (f64.const nan:${UNDEF_NAN}))))
|
|
542
|
+
(then (f64.convert_i32_s (call $__str_len (local.get $v))))
|
|
469
543
|
(else ${lenArm}))`
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
: stringArm
|
|
475
|
-
return `(func $__length (param $v f64) (result f64)
|
|
476
|
-
(local $t i32) (local $off i32)
|
|
477
|
-
(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))
|
|
478
548
|
(then (f64.const nan:${UNDEF_NAN}))
|
|
479
549
|
(else
|
|
480
550
|
(local.set $t (call $__ptr_type (local.get $v)))
|
|
481
551
|
(local.set $off (call $__ptr_offset (local.get $v)))
|
|
482
|
-
${
|
|
552
|
+
${stringArm})))`
|
|
483
553
|
}
|
|
484
554
|
|
|
485
555
|
// === Property dispatch (.length, .prop) ===
|
|
@@ -489,7 +559,7 @@ export default (ctx) => {
|
|
|
489
559
|
if (typeof obj === 'string' && ctx.schema.isBoxed(obj)) {
|
|
490
560
|
if (prop === 'length') {
|
|
491
561
|
const inner = ctx.schema.emitInner(obj)
|
|
492
|
-
return typed(['f64.convert_i32_s', ['call', '$__len', inner]], 'f64')
|
|
562
|
+
return typed(['f64.convert_i32_s', ['call', '$__len', ['i64.reinterpret_f64', inner]]], 'f64')
|
|
493
563
|
}
|
|
494
564
|
const idx = ctx.schema.find(obj, prop)
|
|
495
565
|
if (idx >= 0) return emitSchemaSlotRead(asF64(emit(obj)), idx)
|
|
@@ -516,6 +586,12 @@ export default (ctx) => {
|
|
|
516
586
|
return typed(['f64.convert_i32_s', lenI32], 'f64')
|
|
517
587
|
}
|
|
518
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
|
+
}
|
|
519
595
|
const vt = typeof obj === 'string' ? repOf(obj)?.val : valTypeOf(obj)
|
|
520
596
|
return emitLengthAccess(asF64(emit(obj)), vt)
|
|
521
597
|
}
|
|
@@ -542,21 +618,25 @@ export default (ctx) => {
|
|
|
542
618
|
if (typeof obj === 'string') {
|
|
543
619
|
const objType = lookupValType(obj)
|
|
544
620
|
if (usesDynProps(objType)) {
|
|
545
|
-
access = emitDynGetExprTyped(['local.get', `$${t}`],
|
|
621
|
+
access = emitDynGetExprTyped(['local.get', `$${t}`], asI64(emit(['str', prop])), objType)
|
|
546
622
|
} else if (objType === VAL.HASH) {
|
|
547
|
-
access = emitHashGetLocalConst(['local.get', `$${t}`],
|
|
623
|
+
access = emitHashGetLocalConst(['local.get', `$${t}`], asI64(emit(['str', prop])), prop)
|
|
548
624
|
} else if (objType == null) {
|
|
549
|
-
|
|
550
|
-
|
|
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)
|
|
551
631
|
} else {
|
|
552
632
|
inc('__hash_get', '__str_hash', '__str_eq')
|
|
553
|
-
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]))]]
|
|
554
634
|
}
|
|
555
635
|
} else {
|
|
556
636
|
if (valTypeOf(obj) === VAL.HASH) {
|
|
557
|
-
access = emitHashGetLocalConst(['local.get', `$${t}`],
|
|
637
|
+
access = emitHashGetLocalConst(['local.get', `$${t}`], asI64(emit(['str', prop])), prop)
|
|
558
638
|
} else {
|
|
559
|
-
access = emitDynGetExprTyped(['local.get', `$${t}`],
|
|
639
|
+
access = emitDynGetExprTyped(['local.get', `$${t}`], asI64(emit(['str', prop])), valTypeOf(obj))
|
|
560
640
|
}
|
|
561
641
|
}
|
|
562
642
|
}
|
|
@@ -581,6 +661,22 @@ export default (ctx) => {
|
|
|
581
661
|
|
|
582
662
|
// Optional call: fn?.(...args) → null if fn is null, else call fn
|
|
583
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
|
+
}
|
|
584
680
|
const t = temp()
|
|
585
681
|
const va = asF64(emit(callee))
|
|
586
682
|
// If nullish → return NULL_NAN, else call via fn.call
|
|
@@ -594,6 +690,7 @@ export default (ctx) => {
|
|
|
594
690
|
// initialized in __start (see compile.js). Comparison patterns (typeof x === 'string') are optimized
|
|
595
691
|
// in prepare.js (resolveTypeof) and emitted as direct type checks via emitTypeofCmp, bypassing this path.
|
|
596
692
|
ctx.core.emit['typeof'] = (a) => {
|
|
693
|
+
if (valTypeOf(a) === VAL.BIGINT) return emit(['str', 'bigint'])
|
|
597
694
|
if (!ctx.runtime.typeofStrs) {
|
|
598
695
|
ctx.runtime.typeofStrs = ['number', 'undefined', 'string', 'function', 'symbol', 'object']
|
|
599
696
|
for (const s of ctx.runtime.typeofStrs)
|
|
@@ -602,20 +699,27 @@ export default (ctx) => {
|
|
|
602
699
|
inc('__typeof')
|
|
603
700
|
// Receiver type unknown; enable branches that wouldn't otherwise be reachable.
|
|
604
701
|
ctx.features.closure = true
|
|
605
|
-
return typed(['call', '$__typeof',
|
|
702
|
+
return typed(['call', '$__typeof', asI64(emit(a))], 'f64')
|
|
606
703
|
}
|
|
607
704
|
|
|
608
705
|
ctx.core.stdlib['__typeof'] = () => {
|
|
609
|
-
const stringTest =
|
|
610
|
-
? `(i32.or (i32.eq (local.get $t) (i32.const ${PTR.STRING})) (i32.eq (local.get $t) (i32.const ${PTR.SSO})))`
|
|
611
|
-
: `(i32.eq (local.get $t) (i32.const ${PTR.STRING}))`
|
|
706
|
+
const stringTest = `(i32.eq (local.get $t) (i32.const ${PTR.STRING}))`
|
|
612
707
|
const closureArm = ctx.features.closure
|
|
613
708
|
? `(if (i32.eq (local.get $t) (i32.const ${PTR.CLOSURE}))
|
|
614
709
|
(then (return (global.get $__tof_function))))`
|
|
615
710
|
: ''
|
|
616
|
-
return `(func $__typeof (param $v
|
|
617
|
-
(local $t i32)
|
|
618
|
-
(
|
|
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)))
|
|
619
723
|
(then (return (global.get $__tof_number))))
|
|
620
724
|
(if (call $__is_nullish (local.get $v))
|
|
621
725
|
(then (return (global.get $__tof_undefined))))
|
|
@@ -633,9 +737,9 @@ export default (ctx) => {
|
|
|
633
737
|
|
|
634
738
|
// Low-level pointer helpers callable from jz code
|
|
635
739
|
ctx.core.emit['__mkptr'] = (t, a, o) => typed(['call', '$__mkptr', asI32(emit(t)), asI32(emit(a)), asI32(emit(o))], 'f64')
|
|
636
|
-
ctx.core.emit['__ptr_type'] = (p) => typed(['f64.convert_i32_s', ['call', '$__ptr_type',
|
|
637
|
-
ctx.core.emit['__ptr_aux'] = (p) => typed(['f64.convert_i32_s', ['call', '$__ptr_aux',
|
|
638
|
-
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')
|
|
639
743
|
|
|
640
744
|
// Error(msg) — passthrough (throw handles any value)
|
|
641
745
|
ctx.core.emit['Error'] = (msg) => asF64(emit(msg))
|