jz 0.0.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +373 -0
- package/cli.js +163 -0
- package/index.js +247 -0
- package/module/array.js +1322 -0
- package/module/collection.js +793 -0
- package/module/console.js +192 -0
- package/module/core.js +644 -0
- package/module/function.js +181 -0
- package/module/index.js +15 -0
- package/module/json.js +506 -0
- package/module/math.js +390 -0
- package/module/number.js +601 -0
- package/module/object.js +359 -0
- package/module/regex.js +914 -0
- package/module/schema.js +106 -0
- package/module/string.js +985 -0
- package/module/symbol.js +55 -0
- package/module/timer.js +253 -0
- package/module/typedarray.js +713 -0
- package/package.json +56 -5
- package/src/analyze.js +1927 -0
- package/src/autoload.js +175 -0
- package/src/compile.js +1211 -0
- package/src/ctx.js +244 -0
- package/src/emit.js +2105 -0
- package/src/host.js +536 -0
- package/src/ir.js +649 -0
- package/src/jzify.js +418 -0
- package/src/key.js +73 -0
- package/src/narrow.js +928 -0
- package/src/optimize.js +1352 -0
- package/src/plan.js +105 -0
- package/src/prepare.js +1534 -0
- package/src/source.js +76 -0
- package/wasi.js +80 -0
package/module/core.js
ADDED
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core module — NaN-boxing, bump allocator, property dispatch.
|
|
3
|
+
*
|
|
4
|
+
* Foundation for all heap types. Every module depends on this.
|
|
5
|
+
* NaN-boxing: quiet NaN (0x7FF8) + 51-bit payload [type:4][aux:15][offset:32]
|
|
6
|
+
*
|
|
7
|
+
* Auto-included by array/object/string modules.
|
|
8
|
+
*
|
|
9
|
+
* @module core
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { typed, asF64, asI32, 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 } from '../src/analyze.js'
|
|
15
|
+
import { err, inc, PTR } from '../src/ctx.js'
|
|
16
|
+
import { initSchema } from './schema.js'
|
|
17
|
+
import { strHashLiteral } from './collection.js'
|
|
18
|
+
|
|
19
|
+
const NAN_PREFIX = 0x7FF8
|
|
20
|
+
|
|
21
|
+
const PTR_BY_VAL = {
|
|
22
|
+
[VAL.ARRAY]: PTR.ARRAY,
|
|
23
|
+
[VAL.OBJECT]: PTR.OBJECT,
|
|
24
|
+
[VAL.TYPED]: PTR.TYPED,
|
|
25
|
+
[VAL.SET]: PTR.SET,
|
|
26
|
+
[VAL.MAP]: PTR.MAP,
|
|
27
|
+
[VAL.CLOSURE]: PTR.CLOSURE,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default (ctx) => {
|
|
31
|
+
Object.assign(ctx.core.stdlibDeps, {
|
|
32
|
+
__eq: ['__str_eq', '__ptr_type'],
|
|
33
|
+
__typeof: ['__ptr_type', '__is_nullish'],
|
|
34
|
+
__len: ['__typed_shift'],
|
|
35
|
+
__cap: ['__typed_shift', '__ptr_type', '__ptr_offset', '__ptr_aux'],
|
|
36
|
+
__typed_data: ['__ptr_offset', '__ptr_aux'],
|
|
37
|
+
__ptr_offset: [],
|
|
38
|
+
__is_str_key: ['__ptr_type'],
|
|
39
|
+
__str_len: ['__ptr_type', '__ptr_offset'],
|
|
40
|
+
__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
|
+
},
|
|
46
|
+
__typeof: ['__ptr_type', '__is_nullish'],
|
|
47
|
+
__alloc_hdr: ['__alloc'],
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
ctx.core.stdlib['__is_nullish'] = `(func $__is_nullish (param $v f64) (result i32)
|
|
51
|
+
(i32.or
|
|
52
|
+
(i64.eq (i64.reinterpret_f64 (local.get $v)) (i64.const ${NULL_NAN}))
|
|
53
|
+
(i64.eq (i64.reinterpret_f64 (local.get $v)) (i64.const ${UNDEF_NAN}))))`
|
|
54
|
+
|
|
55
|
+
ctx.core.stdlib['__eq'] = `(func $__eq (param $a f64) (param $b f64) (result i32)
|
|
56
|
+
(local $ra i64) (local $rb i64) (local $ta i32) (local $tb i32)
|
|
57
|
+
;; Fast path: bit equality covers identical pointers AND interned/SSO strings (same content
|
|
58
|
+
;; → same bits). Failing universal-NaN test catches NaN===NaN→false. Saves the NaN-check
|
|
59
|
+
;; pair (4 f64.eq) on the hottest case in watr (op === 'literal-string').
|
|
60
|
+
(local.set $ra (i64.reinterpret_f64 (local.get $a)))
|
|
61
|
+
(local.set $rb (i64.reinterpret_f64 (local.get $b)))
|
|
62
|
+
(if (result i32) (i64.eq (local.get $ra) (local.get $rb))
|
|
63
|
+
(then (i64.ne (local.get $ra) (i64.const 0x7FF8000000000000)))
|
|
64
|
+
(else
|
|
65
|
+
;; Bits differ. Numeric path covers -0/+0 and any normal numeric inequality.
|
|
66
|
+
(if (result i32)
|
|
67
|
+
(i32.and
|
|
68
|
+
(f64.eq (local.get $a) (local.get $a))
|
|
69
|
+
(f64.eq (local.get $b) (local.get $b)))
|
|
70
|
+
(then (f64.eq (local.get $a) (local.get $b)))
|
|
71
|
+
(else
|
|
72
|
+
;; ≥1 is a NaN-box. Heap-allocated STRING with same content can have different
|
|
73
|
+
;; offsets — fall through to byte-wise __str_eq.
|
|
74
|
+
(local.set $ta (i32.wrap_i64 (i64.and (i64.shr_u (local.get $ra) (i64.const 47)) (i64.const 0xF))))
|
|
75
|
+
(local.set $tb (i32.wrap_i64 (i64.and (i64.shr_u (local.get $rb) (i64.const 47)) (i64.const 0xF))))
|
|
76
|
+
(if (result i32)
|
|
77
|
+
(i32.and
|
|
78
|
+
(i32.or
|
|
79
|
+
(i32.eq (local.get $ta) (i32.const ${PTR.STRING}))
|
|
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}))))
|
|
84
|
+
(then (call $__str_eq (local.get $a) (local.get $b)))
|
|
85
|
+
(else (i32.const 0))))))))`
|
|
86
|
+
|
|
87
|
+
ctx.core.stdlib['__is_null'] = `(func $__is_null (param $v f64) (result i32)
|
|
88
|
+
(i64.eq (i64.reinterpret_f64 (local.get $v)) (i64.const ${NULL_NAN})))`
|
|
89
|
+
|
|
90
|
+
// Truthy check: handles regular numbers AND NaN-boxed pointers
|
|
91
|
+
// Falsy: 0, -0, NaN, null, undefined, "" (empty SSO)
|
|
92
|
+
ctx.core.stdlib['__is_truthy'] = `(func $__is_truthy (param $v f64) (result i32)
|
|
93
|
+
(local $bits i64)
|
|
94
|
+
(if (result i32) (f64.eq (local.get $v) (local.get $v))
|
|
95
|
+
(then (f64.ne (local.get $v) (f64.const 0)))
|
|
96
|
+
(else
|
|
97
|
+
(local.set $bits (i64.reinterpret_f64 (local.get $v)))
|
|
98
|
+
(i32.and
|
|
99
|
+
(i32.and
|
|
100
|
+
(i64.ne (local.get $bits) (i64.const 0x7FF8000000000000))
|
|
101
|
+
(i64.ne (local.get $bits) (i64.const ${NULL_NAN})))
|
|
102
|
+
(i32.and
|
|
103
|
+
(i64.ne (local.get $bits) (i64.const ${UNDEF_NAN}))
|
|
104
|
+
(i64.ne (local.get $bits) (i64.const 0x7FFA800000000000)))))))`
|
|
105
|
+
|
|
106
|
+
ctx.core.stdlib['__is_str_key'] = `(func $__is_str_key (param $v f64) (result i32)
|
|
107
|
+
(local $t i32)
|
|
108
|
+
(if (result i32) (f64.eq (local.get $v) (local.get $v))
|
|
109
|
+
(then (i32.const 0))
|
|
110
|
+
(else
|
|
111
|
+
(local.set $t (call $__ptr_type (local.get $v)))
|
|
112
|
+
(i32.or
|
|
113
|
+
(i32.eq (local.get $t) (i32.const ${PTR.STRING}))
|
|
114
|
+
(i32.eq (local.get $t) (i32.const ${PTR.SSO}))))))`
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
// Default dynamic-property helpers are harmless stubs. The collection module
|
|
118
|
+
// overrides them with the real sidecar-property implementation.
|
|
119
|
+
ctx.core.stdlib['__dyn_get'] = `(func $__dyn_get (param $obj f64) (param $key f64) (result f64)
|
|
120
|
+
(f64.const nan:${UNDEF_NAN}))`
|
|
121
|
+
ctx.core.stdlib['__dyn_get_or'] = `(func $__dyn_get_or (param $obj f64) (param $key f64) (param $fallback f64) (result f64)
|
|
122
|
+
(local.get $fallback))`
|
|
123
|
+
ctx.core.stdlib['__dyn_set'] = `(func $__dyn_set (param $obj f64) (param $key f64) (param $val f64) (result f64)
|
|
124
|
+
(local.get $val))`
|
|
125
|
+
ctx.core.stdlib['__dyn_move'] = `(func $__dyn_move (param $oldOff i32) (param $newOff i32))`
|
|
126
|
+
|
|
127
|
+
// Memory section auto-enabled: compile.js checks ctx.module.modules.ptr
|
|
128
|
+
|
|
129
|
+
// === NaN-boxing: encode/decode ===
|
|
130
|
+
|
|
131
|
+
ctx.core.stdlib['__mkptr'] = `(func $__mkptr (param $type i32) (param $aux i32) (param $offset i32) (result f64)
|
|
132
|
+
(f64.reinterpret_i64 (i64.or
|
|
133
|
+
(i64.shl (i64.const ${NAN_PREFIX}) (i64.const 48))
|
|
134
|
+
(i64.or
|
|
135
|
+
(i64.shl (i64.and (i64.extend_i32_u (local.get $type)) (i64.const 0xF)) (i64.const 47))
|
|
136
|
+
(i64.or
|
|
137
|
+
(i64.shl (i64.and (i64.extend_i32_u (local.get $aux)) (i64.const 0x7FFF)) (i64.const 32))
|
|
138
|
+
(i64.and (i64.extend_i32_u (local.get $offset)) (i64.const 0xFFFFFFFF)))))))`
|
|
139
|
+
|
|
140
|
+
ctx.core.stdlib['__ptr_offset'] = `(func $__ptr_offset (param $ptr f64) (result i32)
|
|
141
|
+
(local $bits i64) (local $off i32)
|
|
142
|
+
(local.set $bits (i64.reinterpret_f64 (local.get $ptr)))
|
|
143
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const 0xFFFFFFFF))))
|
|
144
|
+
;; Arrays can be reallocated during growth; follow forwarding pointer (cap=-1 sentinel).
|
|
145
|
+
;; Bounds are checked inside the loop so non-array ptrs skip them entirely, and well-formed
|
|
146
|
+
;; ARRAY ptrs without forwarding still pay only one bounds check before the cap load.
|
|
147
|
+
(if (i32.eq
|
|
148
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 47)) (i64.const 0xF)))
|
|
149
|
+
(i32.const ${PTR.ARRAY}))
|
|
150
|
+
(then
|
|
151
|
+
(block $done
|
|
152
|
+
(loop $follow
|
|
153
|
+
(br_if $done (i32.lt_u (local.get $off) (i32.const 8)))
|
|
154
|
+
(br_if $done (i32.gt_u (local.get $off) (i32.shl (memory.size) (i32.const 16))))
|
|
155
|
+
(br_if $done (i32.ne (i32.load (i32.sub (local.get $off) (i32.const 4))) (i32.const -1)))
|
|
156
|
+
(local.set $off (i32.load (i32.sub (local.get $off) (i32.const 8))))
|
|
157
|
+
(br $follow)))))
|
|
158
|
+
(local.get $off))`
|
|
159
|
+
|
|
160
|
+
ctx.core.stdlib['__ptr_aux'] = `(func $__ptr_aux (param $ptr f64) (result i32)
|
|
161
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (i64.reinterpret_f64 (local.get $ptr)) (i64.const 32)) (i64.const 0x7FFF))))`
|
|
162
|
+
|
|
163
|
+
ctx.core.stdlib['__ptr_type'] = `(func $__ptr_type (param $ptr f64) (result i32)
|
|
164
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (i64.reinterpret_f64 (local.get $ptr)) (i64.const 47)) (i64.const 0xF))))`
|
|
165
|
+
|
|
166
|
+
// === Bump allocator ===
|
|
167
|
+
|
|
168
|
+
if (ctx.memory.shared) {
|
|
169
|
+
// Shared memory: heap offset stored at memory[1020] (i32), just before heap start at 1024
|
|
170
|
+
ctx.core.stdlib['__alloc'] = `(func $__alloc (param $bytes i32) (result i32)
|
|
171
|
+
(local $ptr i32)
|
|
172
|
+
(local.set $ptr (i32.load (i32.const 1020)))
|
|
173
|
+
(i32.store (i32.const 1020) (i32.and (i32.add (i32.add (local.get $ptr) (local.get $bytes)) (i32.const 7)) (i32.const -8)))
|
|
174
|
+
(local.get $ptr))`
|
|
175
|
+
ctx.core.stdlib['__reset'] = `(func $__reset
|
|
176
|
+
(i32.store (i32.const 1020) (i32.const 1024)))`
|
|
177
|
+
} else {
|
|
178
|
+
// Own memory: heap offset in a global, auto-grow when needed
|
|
179
|
+
ctx.scope.globals.set('__heap', '(global $__heap (mut i32) (i32.const 1024))')
|
|
180
|
+
ctx.core.stdlib['__alloc'] = `(func $__alloc (param $bytes i32) (result i32)
|
|
181
|
+
(local $ptr i32) (local $next i32)
|
|
182
|
+
(local.set $ptr (global.get $__heap))
|
|
183
|
+
;; Align next allocation to 8 bytes
|
|
184
|
+
(local.set $next (i32.and (i32.add (i32.add (local.get $ptr) (local.get $bytes)) (i32.const 7)) (i32.const -8)))
|
|
185
|
+
;; Grow memory if needed (each page = 65536 bytes)
|
|
186
|
+
(if (i32.gt_u (local.get $next) (i32.mul (memory.size) (i32.const 65536)))
|
|
187
|
+
(then (if (i32.eq (memory.grow
|
|
188
|
+
(i32.shr_u (i32.add (i32.sub (local.get $next) (i32.mul (memory.size) (i32.const 65536))) (i32.const 65535)) (i32.const 16)))
|
|
189
|
+
(i32.const -1)) (then (unreachable)))))
|
|
190
|
+
(global.set $__heap (local.get $next))
|
|
191
|
+
(local.get $ptr))`
|
|
192
|
+
ctx.core.stdlib['__reset'] = `(func $__reset
|
|
193
|
+
(global.set $__heap (i32.const 1024)))`
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// === Memory-based length/cap helpers (C-style headers) ===
|
|
197
|
+
|
|
198
|
+
// Array/TypedArray/Buffer: [-8:len(i32)][-4:cap(i32)][data...]
|
|
199
|
+
// For ARRAY/HASH/SET/MAP: len is element count.
|
|
200
|
+
// For BUFFER: len is byte count. For owned TYPED: header stores byte count; len
|
|
201
|
+
// is derived as byteLen >> log2(stride) so reinterpret views share their parent
|
|
202
|
+
// BUFFER's header (zero-copy aliasing).
|
|
203
|
+
// For TYPED subviews (aux bit 3 set): offset points to a 16-byte descriptor
|
|
204
|
+
// [0:byteLen(i32)][4:dataOff(i32)][8:parentOff(i32)][12:pad]
|
|
205
|
+
// elemType = aux & 7, isView = aux & 8.
|
|
206
|
+
ctx.core.stdlib['__typed_shift'] = `(func $__typed_shift (param $et i32) (result i32)
|
|
207
|
+
(if (result i32) (i32.eq (local.get $et) (i32.const 7))
|
|
208
|
+
(then (i32.const 3))
|
|
209
|
+
(else (if (result i32) (i32.ge_u (local.get $et) (i32.const 4))
|
|
210
|
+
(then (i32.const 2))
|
|
211
|
+
(else (i32.shr_u (local.get $et) (i32.const 1)))))))`
|
|
212
|
+
|
|
213
|
+
// Real data address for any TYPED ptr: owned → offset, view → [offset+4].
|
|
214
|
+
ctx.core.stdlib['__typed_data'] = `(func $__typed_data (param $ptr f64) (result i32)
|
|
215
|
+
(local $off i32)
|
|
216
|
+
(local.set $off (call $__ptr_offset (local.get $ptr)))
|
|
217
|
+
(if (result i32) (i32.and (call $__ptr_aux (local.get $ptr)) (i32.const 8))
|
|
218
|
+
(then (i32.load (i32.add (local.get $off) (i32.const 4))))
|
|
219
|
+
(else (local.get $off))))`
|
|
220
|
+
|
|
221
|
+
// Hot (~85M calls in watr self-host). Type/offset extraction inlined; forwarding
|
|
222
|
+
// loop only entered for ARRAY. ARRAY fast path dominates (nodes?.length, out.length …).
|
|
223
|
+
ctx.core.stdlib['__len'] = `(func $__len (param $ptr f64) (result i32)
|
|
224
|
+
(local $bits i64) (local $t i32) (local $off i32) (local $aux i32)
|
|
225
|
+
(local.set $bits (i64.reinterpret_f64 (local.get $ptr)))
|
|
226
|
+
(local.set $t (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 47)) (i64.const 0xF))))
|
|
227
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const 0xFFFFFFFF))))
|
|
228
|
+
;; ARRAY fast path: follow forwarding inline, then load len at off-8.
|
|
229
|
+
(if (result i32)
|
|
230
|
+
(i32.and (i32.eq (local.get $t) (i32.const 1)) (i32.ge_u (local.get $off) (i32.const 8)))
|
|
231
|
+
(then
|
|
232
|
+
(block $done
|
|
233
|
+
(loop $follow
|
|
234
|
+
(br_if $done (i32.gt_u (local.get $off) (i32.shl (memory.size) (i32.const 16))))
|
|
235
|
+
(br_if $done (i32.ne (i32.load (i32.sub (local.get $off) (i32.const 4))) (i32.const -1)))
|
|
236
|
+
(local.set $off (i32.load (i32.sub (local.get $off) (i32.const 8))))
|
|
237
|
+
(br $follow)))
|
|
238
|
+
(i32.load (i32.sub (local.get $off) (i32.const 8))))
|
|
239
|
+
(else
|
|
240
|
+
(if (result i32)
|
|
241
|
+
(i32.and
|
|
242
|
+
(i32.ge_u (local.get $off) (i32.const 8))
|
|
243
|
+
(i32.or
|
|
244
|
+
(i32.eq (local.get $t) (i32.const 3))
|
|
245
|
+
(i32.or (i32.eq (local.get $t) (i32.const ${PTR.BUFFER}))
|
|
246
|
+
(i32.or (i32.eq (local.get $t) (i32.const 7))
|
|
247
|
+
(i32.or (i32.eq (local.get $t) (i32.const 8)) (i32.eq (local.get $t) (i32.const 9)))))))
|
|
248
|
+
(then
|
|
249
|
+
(if (result i32) (i32.eq (local.get $t) (i32.const 3))
|
|
250
|
+
(then
|
|
251
|
+
(local.set $aux (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 32)) (i64.const 0x7FFF))))
|
|
252
|
+
(if (result i32) (i32.and (local.get $aux) (i32.const 8))
|
|
253
|
+
(then (i32.shr_u (i32.load (local.get $off))
|
|
254
|
+
(call $__typed_shift (i32.and (local.get $aux) (i32.const 7)))))
|
|
255
|
+
(else (i32.shr_u (i32.load (i32.sub (local.get $off) (i32.const 8)))
|
|
256
|
+
(call $__typed_shift (local.get $aux))))))
|
|
257
|
+
(else (i32.load (i32.sub (local.get $off) (i32.const 8))))))
|
|
258
|
+
(else (i32.const 0))))))`
|
|
259
|
+
|
|
260
|
+
ctx.core.stdlib['__cap'] = `(func $__cap (param $ptr f64) (result i32)
|
|
261
|
+
(local $t i32) (local $off i32) (local $aux i32)
|
|
262
|
+
(local.set $t (call $__ptr_type (local.get $ptr)))
|
|
263
|
+
(local.set $off (call $__ptr_offset (local.get $ptr)))
|
|
264
|
+
(if (result i32)
|
|
265
|
+
(i32.and
|
|
266
|
+
(i32.ge_u (local.get $off) (i32.const 4))
|
|
267
|
+
(i32.or
|
|
268
|
+
(i32.or
|
|
269
|
+
(i32.or (i32.eq (local.get $t) (i32.const 1)) (i32.eq (local.get $t) (i32.const 3)))
|
|
270
|
+
(i32.eq (local.get $t) (i32.const ${PTR.BUFFER})))
|
|
271
|
+
(i32.or (i32.eq (local.get $t) (i32.const 7))
|
|
272
|
+
(i32.or (i32.eq (local.get $t) (i32.const 8)) (i32.eq (local.get $t) (i32.const 9))))))
|
|
273
|
+
(then
|
|
274
|
+
(if (result i32) (i32.eq (local.get $t) (i32.const 3))
|
|
275
|
+
(then
|
|
276
|
+
(local.set $aux (call $__ptr_aux (local.get $ptr)))
|
|
277
|
+
(if (result i32) (i32.and (local.get $aux) (i32.const 8))
|
|
278
|
+
;; views are non-growable: cap = len (byteLen at [off])
|
|
279
|
+
(then (i32.shr_u (i32.load (local.get $off))
|
|
280
|
+
(call $__typed_shift (i32.and (local.get $aux) (i32.const 7)))))
|
|
281
|
+
(else (i32.shr_u (i32.load (i32.sub (local.get $off) (i32.const 4)))
|
|
282
|
+
(call $__typed_shift (local.get $aux))))))
|
|
283
|
+
(else (i32.load (i32.sub (local.get $off) (i32.const 4))))))
|
|
284
|
+
(else (i32.const 0))))`
|
|
285
|
+
|
|
286
|
+
// String (heap): [-4:len(i32)][chars...]
|
|
287
|
+
ctx.core.stdlib['__str_len'] = `(func $__str_len (param $ptr f64) (result i32)
|
|
288
|
+
(local $off i32)
|
|
289
|
+
(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)))
|
|
294
|
+
(then (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
295
|
+
(else (i32.const 0))))`
|
|
296
|
+
|
|
297
|
+
// Set len in memory (for push/pop). Hot (~42M calls in watr self-host).
|
|
298
|
+
// Type/offset extraction inlined; forwarding loop only entered for ARRAY.
|
|
299
|
+
ctx.core.stdlib['__set_len'] = `(func $__set_len (param $ptr f64) (param $len i32)
|
|
300
|
+
(local $bits i64) (local $t i32) (local $off i32)
|
|
301
|
+
(local.set $bits (i64.reinterpret_f64 (local.get $ptr)))
|
|
302
|
+
(local.set $t (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 47)) (i64.const 0xF))))
|
|
303
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const 0xFFFFFFFF))))
|
|
304
|
+
;; Only ARRAY (1), TYPED (3), HASH (7), SET (8), MAP (9) carry an 8-byte header.
|
|
305
|
+
;; Of those, only ARRAY can be forwarded — follow the chain inline.
|
|
306
|
+
(if
|
|
307
|
+
(i32.and
|
|
308
|
+
(i32.ge_u (local.get $off) (i32.const 8))
|
|
309
|
+
(i32.or
|
|
310
|
+
(i32.or (i32.eq (local.get $t) (i32.const 1)) (i32.eq (local.get $t) (i32.const 3)))
|
|
311
|
+
(i32.or (i32.eq (local.get $t) (i32.const 7))
|
|
312
|
+
(i32.or (i32.eq (local.get $t) (i32.const 8)) (i32.eq (local.get $t) (i32.const 9))))))
|
|
313
|
+
(then
|
|
314
|
+
(if (i32.eq (local.get $t) (i32.const 1))
|
|
315
|
+
(then
|
|
316
|
+
(block $done
|
|
317
|
+
(loop $follow
|
|
318
|
+
(br_if $done (i32.lt_u (local.get $off) (i32.const 8)))
|
|
319
|
+
(br_if $done (i32.gt_u (local.get $off) (i32.shl (memory.size) (i32.const 16))))
|
|
320
|
+
(br_if $done (i32.ne (i32.load (i32.sub (local.get $off) (i32.const 4))) (i32.const -1)))
|
|
321
|
+
(local.set $off (i32.load (i32.sub (local.get $off) (i32.const 8))))
|
|
322
|
+
(br $follow)))))
|
|
323
|
+
(i32.store (i32.sub (local.get $off) (i32.const 8)) (local.get $len)))))`
|
|
324
|
+
|
|
325
|
+
// Alloc header(8) + data(cap*stride), store len+cap, return data offset (past header)
|
|
326
|
+
ctx.core.stdlib['__alloc_hdr'] = `(func $__alloc_hdr (param $len i32) (param $cap i32) (param $stride i32) (result i32)
|
|
327
|
+
(local $ptr i32)
|
|
328
|
+
(local.set $ptr (call $__alloc (i32.add (i32.const 8) (i32.mul (local.get $cap) (local.get $stride)))))
|
|
329
|
+
(i32.store (local.get $ptr) (local.get $len))
|
|
330
|
+
(i32.store (i32.add (local.get $ptr) (i32.const 4)) (local.get $cap))
|
|
331
|
+
(i32.add (local.get $ptr) (i32.const 8)))`
|
|
332
|
+
|
|
333
|
+
// Allocator + exports are deferred: only included when memory is actually needed.
|
|
334
|
+
// Any module using allocPtr/inc('__alloc') pulls these in via ctx.core.stdlibDeps.
|
|
335
|
+
// compile.js emits _alloc/_reset exports + memory section only when __alloc is in includes.
|
|
336
|
+
ctx.core._allocRawFuncs = [
|
|
337
|
+
'(func (export "_alloc") (param $bytes i32) (result i32) (call $__alloc (local.get $bytes)))',
|
|
338
|
+
'(func (export "_reset") (call $__reset))',
|
|
339
|
+
]
|
|
340
|
+
|
|
341
|
+
// Not-nullish check: f64 WAT node is neither NULL_NAN nor UNDEF_NAN.
|
|
342
|
+
// Routes through isNullish() so peepholes (ptrKind, NaN-boxed literal, local.get inline)
|
|
343
|
+
// apply — otherwise this would always emit a __is_nullish call even for provable cases.
|
|
344
|
+
const notNullish = v => ['i32.eqz', isNullish(v)]
|
|
345
|
+
|
|
346
|
+
// Optional-chain wrapper: eval guard, if non-nullish emit access, else NULL_NAN.
|
|
347
|
+
const emitNullishGuarded = (guard, access) => typed(['if', ['result', 'f64'],
|
|
348
|
+
notNullish(guard),
|
|
349
|
+
['then', access],
|
|
350
|
+
['else', ['f64.const', `nan:${NULL_NAN}`]]], 'f64')
|
|
351
|
+
|
|
352
|
+
// === Shared dispatch helpers ===
|
|
353
|
+
|
|
354
|
+
/** Emit .length access for a WASM f64 node. Monomorphize by vt, or runtime dispatch.
|
|
355
|
+
* ARRAY/SET/MAP share a single layout: length is i32 at offset-8. We inline that load
|
|
356
|
+
* directly instead of calling __len which re-dispatches on type. ptrOffsetIR handles
|
|
357
|
+
* ARRAY forwarding (non-ARRAY skips the forwarding loop). TYPED has a variable-width
|
|
358
|
+
* layout depending on the aux typed-element shift, so it still routes through __len. */
|
|
359
|
+
function emitLengthAccess(va, vt) {
|
|
360
|
+
if (vt === VAL.ARRAY || vt === VAL.SET || vt === VAL.MAP) {
|
|
361
|
+
const off = ptrOffsetIR(va, vt)
|
|
362
|
+
return typed(['f64.convert_i32_s', ['i32.load', ['i32.sub', off, ['i32.const', 8]]]], 'f64')
|
|
363
|
+
}
|
|
364
|
+
if (vt === VAL.TYPED)
|
|
365
|
+
return typed(['f64.convert_i32_s', ['call', '$__len', va]], 'f64')
|
|
366
|
+
// Known string → byteLen (handles SSO + heap)
|
|
367
|
+
if (vt === VAL.STRING) {
|
|
368
|
+
inc('__str_byteLen')
|
|
369
|
+
return typed(['f64.convert_i32_s', ['call', '$__str_byteLen', va]], 'f64')
|
|
370
|
+
}
|
|
371
|
+
// Unknown → runtime dispatch via stdlib. Set/Map dispatch arms are pulled
|
|
372
|
+
// only when user code actually constructs Set/Map (collection.js sets the
|
|
373
|
+
// feature flags at the construction site); otherwise dispatch falls through
|
|
374
|
+
// to ARRAY/STRING/TYPED. typedarray stays on because typed arrays are
|
|
375
|
+
// commonly passed from JS via jz.memory.* without an in-program constructor.
|
|
376
|
+
inc('__length')
|
|
377
|
+
ctx.features.typedarray = true
|
|
378
|
+
return typed(['call', '$__length', va], 'f64')
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Known-schema fields live in the object payload. Dynamic sidecars are only
|
|
382
|
+
// for ad-hoc props on pointer-backed values, so schema reads should bypass it.
|
|
383
|
+
// Slot val-types reach the emit-time consumer via valTypeOf → ctx.schema.slotVT
|
|
384
|
+
// (read on the AST `.prop` node), not via tagging this IR node.
|
|
385
|
+
function emitSchemaSlotRead(baseExpr, idx) {
|
|
386
|
+
const base = baseExpr?.type === 'f64' ? baseExpr : typed(baseExpr, 'f64')
|
|
387
|
+
return typed(['f64.load', ['i32.add', ptrOffsetIR(base, VAL.OBJECT), ['i32.const', idx * 8]]], 'f64')
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function emitHashGetLocalConst(base, key, prop) {
|
|
391
|
+
inc('__hash_get_local_h')
|
|
392
|
+
const receiver = base?.type ? asF64(base) : typed(base, 'f64')
|
|
393
|
+
return typed(['call', '$__hash_get_local_h', receiver, key, ['i32.const', strHashLiteral(prop)]], 'f64')
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function emitTypeTag(receiver, vt) {
|
|
397
|
+
const p = PTR_BY_VAL[vt]
|
|
398
|
+
if (p != null) return ['i32.const', p]
|
|
399
|
+
inc('__ptr_type')
|
|
400
|
+
return ['call', '$__ptr_type', receiver]
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function emitDynGetExprTyped(base, key, vt) {
|
|
404
|
+
inc('__dyn_get_expr_t')
|
|
405
|
+
const receiver = base?.type ? asF64(base) : typed(base, 'f64')
|
|
406
|
+
return typed(['call', '$__dyn_get_expr_t', receiver, key, emitTypeTag(receiver, vt)], 'f64')
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function emitDynGetAnyTyped(base, key, vt) {
|
|
410
|
+
inc('__dyn_get_any_t')
|
|
411
|
+
const receiver = base?.type ? asF64(base) : typed(base, 'f64')
|
|
412
|
+
return typed(['call', '$__dyn_get_any_t', receiver, key, emitTypeTag(receiver, vt)], 'f64')
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** Emit .prop access for a WASM f64 node using schema or HASH fallback. */
|
|
416
|
+
function emitPropAccess(va, obj, prop) {
|
|
417
|
+
const schemaIdx = typeof obj === 'string' ? ctx.schema.find(obj, prop) : ctx.schema.find(null, prop)
|
|
418
|
+
const key = asF64(emit(['str', prop]))
|
|
419
|
+
if (schemaIdx >= 0) return emitSchemaSlotRead(asF64(va), schemaIdx)
|
|
420
|
+
if (typeof obj === 'string') {
|
|
421
|
+
const vt = lookupValType(obj)
|
|
422
|
+
if (usesDynProps(vt)) {
|
|
423
|
+
return emitDynGetExprTyped(va, key, vt)
|
|
424
|
+
}
|
|
425
|
+
if (vt === VAL.HASH) {
|
|
426
|
+
return emitHashGetLocalConst(va, key, prop)
|
|
427
|
+
}
|
|
428
|
+
if (vt == null) {
|
|
429
|
+
ctx.features.external = true
|
|
430
|
+
return emitDynGetAnyTyped(va, key, vt)
|
|
431
|
+
}
|
|
432
|
+
inc('__hash_get', '__str_hash', '__str_eq')
|
|
433
|
+
return typed(['call', '$__hash_get', asF64(va), key], 'f64')
|
|
434
|
+
}
|
|
435
|
+
// Non-string receiver: route through HASH fast path when valTypeOf can
|
|
436
|
+
// resolve the chain to a known HASH (e.g. `o.meta.bias` where `o.meta` is
|
|
437
|
+
// a HASH per the parsed JSON shape). Falls back to dynamic dispatch
|
|
438
|
+
// otherwise.
|
|
439
|
+
if (valTypeOf(obj) === VAL.HASH) {
|
|
440
|
+
return emitHashGetLocalConst(va, key, prop)
|
|
441
|
+
}
|
|
442
|
+
inc('__dyn_get_expr')
|
|
443
|
+
return typed(['call', '$__dyn_get_expr', asF64(va), key], 'f64')
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Runtime .length dispatch — factory elides branches for types that can't exist in
|
|
447
|
+
// this program (features.* + hash-stdlib presence). ARRAY is always live; STRING and
|
|
448
|
+
// number are always dispatched. SSO branch elided when features.sso is off. The __len
|
|
449
|
+
// disjunction collapses to whichever of ARRAY/TYPED/HASH/SET/MAP are reachable.
|
|
450
|
+
ctx.core.stdlib['__length'] = () => {
|
|
451
|
+
const types = [PTR.ARRAY]
|
|
452
|
+
if (ctx.features.typedarray) types.push(PTR.TYPED)
|
|
453
|
+
if (ctx.core.includes.has('__hash_new') || ctx.core.includes.has('__dyn_set') || ctx.core.includes.has('__hash_set'))
|
|
454
|
+
types.push(PTR.HASH)
|
|
455
|
+
if (ctx.features.set) types.push(PTR.SET)
|
|
456
|
+
if (ctx.features.map) types.push(PTR.MAP)
|
|
457
|
+
const eqT = (n) => `(i32.eq (local.get $t) (i32.const ${n}))`
|
|
458
|
+
let disj = eqT(types[0])
|
|
459
|
+
for (let i = 1; i < types.length; i++) disj = `(i32.or ${disj} ${eqT(types[i])})`
|
|
460
|
+
const lenArm = `(if (result f64) ${disj}
|
|
461
|
+
(then
|
|
462
|
+
(if (result f64) (i32.ge_u (local.get $off) (i32.const 8))
|
|
463
|
+
(then (f64.convert_i32_s (call $__len (local.get $v))))
|
|
464
|
+
(else (f64.const nan:${UNDEF_NAN}))))
|
|
465
|
+
(else (f64.const nan:${UNDEF_NAN})))`
|
|
466
|
+
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}))))
|
|
471
|
+
(else ${lenArm}))`
|
|
472
|
+
const afterNumber = ctx.features.sso
|
|
473
|
+
? `(if (result f64) (i32.eq (local.get $t) (i32.const ${PTR.SSO}))
|
|
474
|
+
(then (f64.convert_i32_s (call $__ptr_aux (local.get $v))))
|
|
475
|
+
(else ${stringArm}))`
|
|
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))
|
|
480
|
+
(then (f64.const nan:${UNDEF_NAN}))
|
|
481
|
+
(else
|
|
482
|
+
(local.set $t (call $__ptr_type (local.get $v)))
|
|
483
|
+
(local.set $off (call $__ptr_offset (local.get $v)))
|
|
484
|
+
${afterNumber})))`
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// === Property dispatch (.length, .prop) ===
|
|
488
|
+
|
|
489
|
+
ctx.core.emit['.'] = (obj, prop) => {
|
|
490
|
+
// Boxed object: delegate .length and .prop to inner value or schema
|
|
491
|
+
if (typeof obj === 'string' && ctx.schema.isBoxed(obj)) {
|
|
492
|
+
if (prop === 'length') {
|
|
493
|
+
const inner = ctx.schema.emitInner(obj)
|
|
494
|
+
return typed(['f64.convert_i32_s', ['call', '$__len', inner]], 'f64')
|
|
495
|
+
}
|
|
496
|
+
const idx = ctx.schema.find(obj, prop)
|
|
497
|
+
if (idx >= 0) return emitSchemaSlotRead(asF64(emit(obj)), idx)
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (prop === 'length') {
|
|
501
|
+
// Fast path: typed-narrowed local (ptrKind=TYPED with known ptrAux) — bypass
|
|
502
|
+
// the f64 NaN-rebox + __len ptr-type/aux re-extraction round-trip.
|
|
503
|
+
// Owned typed (aux & 8 == 0): byteLen at off-8, shifted by element shift.
|
|
504
|
+
// View typed (aux & 8): byteLen stored at off+0 (descriptor head), shifted.
|
|
505
|
+
if (typeof obj === 'string') {
|
|
506
|
+
const r = repOf(obj)
|
|
507
|
+
if (r?.ptrKind === VAL.TYPED && r.ptrAux != null) {
|
|
508
|
+
const aux = r.ptrAux, isView = (aux & 8) !== 0
|
|
509
|
+
const et = aux & 7
|
|
510
|
+
const shift = et === 7 ? 3 : et >= 4 ? 2 : et >> 1
|
|
511
|
+
const off = ['local.get', `$${obj}`]
|
|
512
|
+
const byteLen = isView
|
|
513
|
+
? ['i32.load', off]
|
|
514
|
+
: ['i32.load', ['i32.sub', off, ['i32.const', 8]]]
|
|
515
|
+
const lenI32 = shift === 0
|
|
516
|
+
? typed(byteLen, 'i32')
|
|
517
|
+
: typed(['i32.shr_u', byteLen, ['i32.const', shift]], 'i32')
|
|
518
|
+
return typed(['f64.convert_i32_s', lenI32], 'f64')
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
const vt = typeof obj === 'string' ? repOf(obj)?.val : valTypeOf(obj)
|
|
522
|
+
return emitLengthAccess(asF64(emit(obj)), vt)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Module-registered property emitter (.size, etc.)
|
|
526
|
+
const propKey = `.${prop}`
|
|
527
|
+
if (ctx.core.emit[propKey]) return ctx.core.emit[propKey](obj)
|
|
528
|
+
|
|
529
|
+
return emitPropAccess(emit(obj), obj, prop)
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Optional chaining: obj?.prop → null if obj is null, else obj.prop
|
|
533
|
+
ctx.core.emit['?.'] = (obj, prop) => {
|
|
534
|
+
const t = temp()
|
|
535
|
+
const va = asF64(emit(obj))
|
|
536
|
+
const vt = typeof obj === 'string' ? repOf(obj)?.val : valTypeOf(obj)
|
|
537
|
+
let access
|
|
538
|
+
if (prop === 'length') {
|
|
539
|
+
access = emitLengthAccess(['local.get', `$${t}`], vt)
|
|
540
|
+
} else {
|
|
541
|
+
const propIdx = typeof obj === 'string' ? ctx.schema.find(obj, prop) : -1
|
|
542
|
+
if (propIdx >= 0) access = emitSchemaSlotRead(['local.get', `$${t}`], propIdx)
|
|
543
|
+
else {
|
|
544
|
+
if (typeof obj === 'string') {
|
|
545
|
+
const objType = lookupValType(obj)
|
|
546
|
+
if (usesDynProps(objType)) {
|
|
547
|
+
access = emitDynGetExprTyped(['local.get', `$${t}`], asF64(emit(['str', prop])), objType)
|
|
548
|
+
} else if (objType === VAL.HASH) {
|
|
549
|
+
access = emitHashGetLocalConst(['local.get', `$${t}`], asF64(emit(['str', prop])), prop)
|
|
550
|
+
} else if (objType == null) {
|
|
551
|
+
ctx.features.external = true
|
|
552
|
+
access = emitDynGetAnyTyped(['local.get', `$${t}`], asF64(emit(['str', prop])), objType)
|
|
553
|
+
} else {
|
|
554
|
+
inc('__hash_get', '__str_hash', '__str_eq')
|
|
555
|
+
access = ['call', '$__hash_get', ['local.get', `$${t}`], asF64(emit(['str', prop]))]
|
|
556
|
+
}
|
|
557
|
+
} else {
|
|
558
|
+
if (valTypeOf(obj) === VAL.HASH) {
|
|
559
|
+
access = emitHashGetLocalConst(['local.get', `$${t}`], asF64(emit(['str', prop])), prop)
|
|
560
|
+
} else {
|
|
561
|
+
access = emitDynGetExprTyped(['local.get', `$${t}`], asF64(emit(['str', prop])), valTypeOf(obj))
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return emitNullishGuarded(['local.tee', `$${t}`, va], access)
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Optional index: arr?.[i] → null if arr is null, else arr[i]
|
|
570
|
+
// Cache base in temp, propagate valType so []'s type dispatch works
|
|
571
|
+
ctx.core.emit['?.[]'] = (arr, idx) => {
|
|
572
|
+
const t = temp()
|
|
573
|
+
const va = asF64(emit(arr))
|
|
574
|
+
// Propagate source type to temp so [] dispatch (string, typed, etc.) works
|
|
575
|
+
const srcType = typeof arr === 'string' ? repOf(arr)?.val : null
|
|
576
|
+
if (srcType) updateRep(t, { val: srcType })
|
|
577
|
+
if (typeof arr === 'string' && ctx.types.typedElem?.has(arr)) {
|
|
578
|
+
if (!ctx.types.typedElem) ctx.types.typedElem = new Map()
|
|
579
|
+
ctx.types.typedElem.set(t, ctx.types.typedElem.get(arr))
|
|
580
|
+
}
|
|
581
|
+
return emitNullishGuarded(['local.tee', `$${t}`, va], asF64(ctx.core.emit['[]'](t, idx)))
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Optional call: fn?.(...args) → null if fn is null, else call fn
|
|
585
|
+
ctx.core.emit['?.()'] = (callee, ...args) => {
|
|
586
|
+
const t = temp()
|
|
587
|
+
const va = asF64(emit(callee))
|
|
588
|
+
// If nullish → return NULL_NAN, else call via fn.call
|
|
589
|
+
if (!ctx.closure.call) err('Optional call requires fn module')
|
|
590
|
+
const callResult = ctx.closure.call(typed(['local.get', `$${t}`], 'f64'), args)
|
|
591
|
+
return emitNullishGuarded(['local.tee', `$${t}`, va], asF64(callResult))
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// typeof: returns JS-style string. Reachable results are number/undefined/string/function/symbol/object
|
|
595
|
+
// (booleans are f64 and hit the number branch; no bigints). Strings are preallocated into globals and
|
|
596
|
+
// initialized in __start (see compile.js). Comparison patterns (typeof x === 'string') are optimized
|
|
597
|
+
// in prepare.js (resolveTypeof) and emitted as direct type checks via emitTypeofCmp, bypassing this path.
|
|
598
|
+
ctx.core.emit['typeof'] = (a) => {
|
|
599
|
+
if (!ctx.runtime.typeofStrs) {
|
|
600
|
+
ctx.runtime.typeofStrs = ['number', 'undefined', 'string', 'function', 'symbol', 'object']
|
|
601
|
+
for (const s of ctx.runtime.typeofStrs)
|
|
602
|
+
ctx.scope.globals.set(`__tof_${s}`, `(global $__tof_${s} (mut f64) (f64.const 0))`)
|
|
603
|
+
}
|
|
604
|
+
inc('__typeof')
|
|
605
|
+
// Receiver type unknown; enable branches that wouldn't otherwise be reachable.
|
|
606
|
+
ctx.features.closure = true
|
|
607
|
+
return typed(['call', '$__typeof', asF64(emit(a))], 'f64')
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
ctx.core.stdlib['__typeof'] = () => {
|
|
611
|
+
const stringTest = ctx.features.sso
|
|
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}))`
|
|
614
|
+
const closureArm = ctx.features.closure
|
|
615
|
+
? `(if (i32.eq (local.get $t) (i32.const ${PTR.CLOSURE}))
|
|
616
|
+
(then (return (global.get $__tof_function))))`
|
|
617
|
+
: ''
|
|
618
|
+
return `(func $__typeof (param $v f64) (result f64)
|
|
619
|
+
(local $t i32)
|
|
620
|
+
(if (f64.eq (local.get $v) (local.get $v))
|
|
621
|
+
(then (return (global.get $__tof_number))))
|
|
622
|
+
(if (call $__is_nullish (local.get $v))
|
|
623
|
+
(then (return (global.get $__tof_undefined))))
|
|
624
|
+
(local.set $t (call $__ptr_type (local.get $v)))
|
|
625
|
+
(if ${stringTest}
|
|
626
|
+
(then (return (global.get $__tof_string))))
|
|
627
|
+
${closureArm}
|
|
628
|
+
(if (i32.eqz (local.get $t))
|
|
629
|
+
(then (return (global.get $__tof_symbol))))
|
|
630
|
+
(global.get $__tof_object))`
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// === Schema helpers (centralized in module/schema.js) ===
|
|
634
|
+
initSchema(ctx)
|
|
635
|
+
|
|
636
|
+
// Low-level pointer helpers callable from jz code
|
|
637
|
+
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', asF64(emit(p))]], 'f64')
|
|
639
|
+
ctx.core.emit['__ptr_aux'] = (p) => typed(['f64.convert_i32_s', ['call', '$__ptr_aux', asF64(emit(p))]], 'f64')
|
|
640
|
+
ctx.core.emit['__ptr_offset'] = (p) => typed(['f64.convert_i32_s', ['call', '$__ptr_offset', asF64(emit(p))]], 'f64')
|
|
641
|
+
|
|
642
|
+
// Error(msg) — passthrough (throw handles any value)
|
|
643
|
+
ctx.core.emit['Error'] = (msg) => asF64(emit(msg))
|
|
644
|
+
}
|