jz 0.5.0 → 0.6.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 +288 -314
- package/bench/README.md +319 -0
- package/bench/bench.svg +112 -0
- package/cli.js +32 -24
- package/index.js +177 -55
- package/interop.js +88 -159
- package/jz.svg +5 -0
- package/jzify/arguments.js +97 -0
- package/jzify/bundler.js +382 -0
- package/jzify/classes.js +328 -0
- package/jzify/hoist-vars.js +177 -0
- package/jzify/index.js +51 -0
- package/jzify/names.js +37 -0
- package/jzify/switch.js +106 -0
- package/jzify/transform.js +349 -0
- package/layout.js +179 -0
- package/module/array.js +322 -153
- package/module/collection.js +603 -145
- package/module/console.js +55 -43
- package/module/core.js +281 -142
- package/module/date.js +15 -3
- package/module/function.js +73 -6
- package/module/index.js +2 -1
- package/module/json.js +226 -61
- package/module/math.js +461 -185
- package/module/number.js +306 -60
- package/module/object.js +448 -184
- package/module/regex.js +255 -25
- package/module/schema.js +24 -6
- package/module/simd.js +85 -0
- package/module/string.js +591 -220
- package/module/symbol.js +1 -1
- package/module/timer.js +9 -14
- package/module/typedarray.js +45 -48
- package/package.json +41 -12
- package/src/abi/index.js +39 -23
- package/src/abi/string.js +38 -41
- package/src/ast.js +460 -0
- package/src/autoload.js +26 -24
- package/src/bridge.js +111 -0
- package/src/compile/analyze-scans.js +661 -0
- package/src/compile/analyze.js +1565 -0
- package/src/compile/emit-assign.js +408 -0
- package/src/compile/emit.js +3201 -0
- package/src/compile/flow-types.js +103 -0
- package/src/{compile.js → compile/index.js} +497 -125
- package/src/{infer.js → compile/infer.js} +27 -98
- package/src/{narrow.js → compile/narrow.js} +302 -96
- package/src/compile/plan/advise.js +316 -0
- package/src/compile/plan/common.js +150 -0
- package/src/compile/plan/index.js +118 -0
- package/src/compile/plan/inline.js +679 -0
- package/src/compile/plan/literals.js +984 -0
- package/src/compile/plan/loops.js +472 -0
- package/src/compile/plan/scope.js +573 -0
- package/src/compile/program-facts.js +404 -0
- package/src/ctx.js +176 -58
- package/src/ir.js +540 -171
- package/src/kind-traits.js +105 -0
- package/src/kind.js +462 -0
- package/src/op-policy.js +57 -0
- package/src/{optimize.js → optimize/index.js} +1106 -446
- package/src/optimize/vectorize.js +1874 -0
- package/src/param-reps.js +65 -0
- package/src/parse.js +44 -0
- package/src/{prepare.js → prepare/index.js} +600 -205
- package/src/reps.js +115 -0
- package/src/resolve.js +12 -3
- package/src/static.js +199 -0
- package/src/type.js +647 -0
- package/src/{assemble.js → wat/assemble.js} +86 -48
- package/src/wat/optimize.js +3760 -0
- package/transform.js +21 -0
- package/wasi.js +47 -5
- package/src/analyze.js +0 -3818
- package/src/emit.js +0 -2997
- package/src/jzify.js +0 -1553
- package/src/plan.js +0 -2132
- package/src/vectorize.js +0 -1088
- /package/src/{codegen.js → wat/codegen.js} +0 -0
package/module/string.js
CHANGED
|
@@ -12,16 +12,15 @@
|
|
|
12
12
|
* @module string
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { typed, asF64, asI32, asI64, NULL_NAN, UNDEF_NAN, mkPtrIR, temp, tempI32, toNumF64, toStrI64 } from '../src/ir.js'
|
|
16
|
-
import { emit,
|
|
17
|
-
import { valTypeOf
|
|
18
|
-
import {
|
|
15
|
+
import { typed, asF64, asI32, asI64, NULL_NAN, UNDEF_NAN, FALSE_NAN, TRUE_NAN, mkPtrIR, temp, tempI32, toNumF64, toStrI64 } from '../src/ir.js'
|
|
16
|
+
import { emit, bool, method, deps, wat, bind } from '../src/bridge.js'
|
|
17
|
+
import { valTypeOf } from '../src/kind.js'
|
|
18
|
+
import { VAL } from '../src/reps.js'
|
|
19
|
+
import { ctx, inc, PTR, LAYOUT, err, declGlobal } from '../src/ctx.js'
|
|
20
|
+
import { ssoBitI64Hex, sliceBitI64Hex, ptrNanHex, STR_INTERN_BIT } from '../layout.js'
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const SSO_BIT_I64 = '0x' + (BigInt(LAYOUT.SSO_BIT) << BigInt(LAYOUT.AUX_SHIFT)).toString(16).toUpperCase().padStart(16, '0')
|
|
23
|
-
// Slice/view discriminator bit, pre-shifted to bit 45 — same branch-without-aux trick.
|
|
24
|
-
const SLICE_BIT_I64 = '0x' + (BigInt(LAYOUT.SLICE_BIT) << BigInt(LAYOUT.AUX_SHIFT)).toString(16).toUpperCase().padStart(16, '0')
|
|
22
|
+
const SSO_BIT_I64 = ssoBitI64Hex()
|
|
23
|
+
const SLICE_BIT_I64 = sliceBitI64Hex()
|
|
25
24
|
|
|
26
25
|
// WAT (no-locals expression): byte length of a heap STRING given its raw $-local
|
|
27
26
|
// names for the offset and the i64 ptr. A view (SLICE_BIT) carries its length in
|
|
@@ -38,15 +37,86 @@ const heapLenExpr = (ptrLocal, offLocal) => `(if (result i32)
|
|
|
38
37
|
(else (i32.const 0)))))`
|
|
39
38
|
|
|
40
39
|
|
|
40
|
+
|
|
41
|
+
// SSO-pack a ≤4-byte slice straight from the source bytes (both SSO and
|
|
42
|
+
// memory-backed parents) — no allocation, and equal short tokens become
|
|
43
|
+
// bit-equal (SSO content IS the bits). Bails to the caller's heap path on a
|
|
44
|
+
// non-ASCII byte (SSO is ASCII-only). Locals: $sb scratch byte, $sp packed.
|
|
45
|
+
const sliceSsoPackWat = () => `
|
|
46
|
+
(if (i32.le_u (local.get $nlen) (i32.const 4))
|
|
47
|
+
(then (block $heap8
|
|
48
|
+
(local.set $srcOff (i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
49
|
+
(local.set $isSso (i32.wrap_i64 (i64.shr_u
|
|
50
|
+
(i64.and (local.get $ptr) (i64.const ${SSO_BIT_I64}))
|
|
51
|
+
(i64.const ${LAYOUT.AUX_SHIFT}))))
|
|
52
|
+
(local.set $i (i32.const 0)) (local.set $sp (i32.const 0))
|
|
53
|
+
(loop $pk8
|
|
54
|
+
(if (i32.lt_u (local.get $i) (local.get $nlen))
|
|
55
|
+
(then
|
|
56
|
+
(local.set $sb (if (result i32) (local.get $isSso)
|
|
57
|
+
(then (i32.and
|
|
58
|
+
(i32.shr_u (local.get $srcOff) (i32.shl (i32.add (local.get $start) (local.get $i)) (i32.const 3)))
|
|
59
|
+
(i32.const 0xFF)))
|
|
60
|
+
(else (i32.load8_u (i32.add (i32.add (local.get $srcOff) (local.get $start)) (local.get $i))))))
|
|
61
|
+
(br_if $heap8 (i32.ge_u (local.get $sb) (i32.const 0x80)))
|
|
62
|
+
(local.set $sp (i32.or (local.get $sp) (i32.shl (local.get $sb) (i32.shl (local.get $i) (i32.const 3)))))
|
|
63
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
64
|
+
(br $pk8))))
|
|
65
|
+
(return (call $__mkptr (i32.const ${PTR.STRING}) (i32.or (i32.const ${LAYOUT.SSO_BIT}) (local.get $nlen)) (local.get $sp))))))`
|
|
66
|
+
|
|
67
|
+
// Probe the static intern index (buildInternTable, compile/index.js): a 5..32
|
|
68
|
+
// byte slice whose content equals a static literal returns the CANONICAL
|
|
69
|
+
// static pointer, making every later comparison a bit-eq hit. Emitted only
|
|
70
|
+
// when the table exists ($__internBase declared — the stdlib body is a thunk
|
|
71
|
+
// evaluated at pullStdlib, after buildInternTable). FNV-1a must match
|
|
72
|
+
// __str_hash's heap branch. Locals: $ip src ptr, $h, $j, $slot, $cand, $k.
|
|
73
|
+
const internProbeWat = (ipExpr, guard = '(i32.const 1)') => ctx.scope.globals.has('__internBase') ? `
|
|
74
|
+
(if (i32.and (i32.le_u (local.get $nlen) (i32.const 32)) ${guard})
|
|
75
|
+
(then
|
|
76
|
+
(local.set $ip ${ipExpr})
|
|
77
|
+
(local.set $h (i32.const 0x811c9dc5))
|
|
78
|
+
(local.set $j (i32.const 0))
|
|
79
|
+
(block $hd (loop $hl
|
|
80
|
+
(br_if $hd (i32.ge_u (local.get $j) (local.get $nlen)))
|
|
81
|
+
(local.set $h (i32.mul
|
|
82
|
+
(i32.xor (local.get $h) (i32.load8_u (i32.add (local.get $ip) (local.get $j))))
|
|
83
|
+
(i32.const 0x01000193)))
|
|
84
|
+
(local.set $j (i32.add (local.get $j) (i32.const 1)))
|
|
85
|
+
(br $hl)))
|
|
86
|
+
(if (i32.le_s (local.get $h) (i32.const 1))
|
|
87
|
+
(then (local.set $h (i32.add (local.get $h) (i32.const 2)))))
|
|
88
|
+
(local.set $j (i32.and (local.get $h) (global.get $__internMask)))
|
|
89
|
+
(block $missI (loop $plI
|
|
90
|
+
(block $nextI
|
|
91
|
+
(local.set $slot (i32.add (global.get $__internBase) (i32.shl (local.get $j) (i32.const 3))))
|
|
92
|
+
(local.set $cand (i32.load (i32.add (local.get $slot) (i32.const 4))))
|
|
93
|
+
(br_if $missI (i32.eqz (local.get $cand)))
|
|
94
|
+
(br_if $nextI (i32.ne (i32.load (local.get $slot)) (local.get $h)))
|
|
95
|
+
(br_if $nextI (i32.ne (i32.load (i32.sub (local.get $cand) (i32.const 4))) (local.get $nlen)))
|
|
96
|
+
(local.set $k (i32.const 0))
|
|
97
|
+
(block $vd (loop $vl
|
|
98
|
+
(br_if $vd (i32.ge_u (local.get $k) (local.get $nlen)))
|
|
99
|
+
(br_if $nextI (i32.ne
|
|
100
|
+
(i32.load8_u (i32.add (local.get $ip) (local.get $k)))
|
|
101
|
+
(i32.load8_u (i32.add (local.get $cand) (local.get $k)))))
|
|
102
|
+
(local.set $k (i32.add (local.get $k) (i32.const 1)))
|
|
103
|
+
(br $vl)))
|
|
104
|
+
(return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${STR_INTERN_BIT}) (local.get $cand))))
|
|
105
|
+
(local.set $j (i32.and (i32.add (local.get $j) (i32.const 1)) (global.get $__internMask)))
|
|
106
|
+
(br $plI)))))` : ''
|
|
107
|
+
|
|
108
|
+
|
|
41
109
|
export default (ctx) => {
|
|
42
|
-
|
|
43
|
-
__str_concat: ['__to_str', '__str_byteLen', '__alloc', '__mkptr', '__str_copy'],
|
|
44
|
-
__str_concat_raw: ['__str_byteLen', '__alloc', '__mkptr', '__str_copy'],
|
|
45
|
-
__str_append_byte: ['__str_byteLen', '__alloc', '__mkptr', '__str_copy'],
|
|
110
|
+
deps({
|
|
111
|
+
__str_concat: ['__to_str', '__str_byteLen', '__alloc', '__memgrow', '__mkptr', '__str_copy'],
|
|
112
|
+
__str_concat_raw: ['__str_byteLen', '__alloc', '__memgrow', '__mkptr', '__str_copy'],
|
|
113
|
+
__str_append_byte: ['__str_byteLen', '__alloc', '__memgrow', '__mkptr', '__str_copy'],
|
|
46
114
|
__str_copy: [],
|
|
47
115
|
__str_slice: ['__str_byteLen', '__alloc'],
|
|
48
116
|
__str_slice_view: ['__str_byteLen', '__mkptr', '__str_slice'],
|
|
49
117
|
__str_indexof: ['__str_byteLen', '__to_str'],
|
|
118
|
+
__str_lastindexof: ['__str_byteLen', '__to_str'],
|
|
119
|
+
__wrap1: ['__alloc', '__mkptr'],
|
|
50
120
|
__str_substring: ['__str_slice'],
|
|
51
121
|
__str_startswith: ['__str_byteLen'],
|
|
52
122
|
__str_endswith: ['__str_byteLen'],
|
|
@@ -59,7 +129,8 @@ export default (ctx) => {
|
|
|
59
129
|
__str_replaceall: ['__str_indexof', '__str_slice', '__str_concat'],
|
|
60
130
|
__str_split: ['__str_slice', '__str_byteLen', '__char_at', '__alloc'],
|
|
61
131
|
__str_idx: [],
|
|
62
|
-
__str_eq: ['
|
|
132
|
+
__str_eq: ['__str_eq_cold'],
|
|
133
|
+
__str_eq_cold: ['__char_at', '__str_byteLen'],
|
|
63
134
|
__str_cmp: ['__char_at', '__str_byteLen'],
|
|
64
135
|
__str_range_eq: ['__char_at', '__str_byteLen'],
|
|
65
136
|
__str_substring_eq: ['__str_byteLen', '__str_range_eq'],
|
|
@@ -78,7 +149,7 @@ export default (ctx) => {
|
|
|
78
149
|
|
|
79
150
|
// === String literal: "abc" → SSO if ≤4 ASCII, else heap ===
|
|
80
151
|
|
|
81
|
-
|
|
152
|
+
bind('str', (str) => {
|
|
82
153
|
const MAX_SSO = 4
|
|
83
154
|
if (ctx.features.sso && str.length <= MAX_SSO && /^[\x00-\x7f]*$/.test(str)) {
|
|
84
155
|
let packed = 0
|
|
@@ -88,16 +159,33 @@ export default (ctx) => {
|
|
|
88
159
|
const bytes = new TextEncoder().encode(str)
|
|
89
160
|
const len = bytes.length
|
|
90
161
|
if (!ctx.memory.shared) {
|
|
91
|
-
// Own memory: place in static data segment (no runtime allocation)
|
|
162
|
+
// Own memory: place in static data segment (no runtime allocation).
|
|
163
|
+
// Under internStrings the layout is [hash u32][len u32][bytes] and the
|
|
164
|
+
// pointer carries STR_INTERN_BIT: statics are CANONICAL (deduped), so
|
|
165
|
+
// unequal canonicals are bit-unequal (__str_eq short-circuit) and
|
|
166
|
+
// __str_hash loads the cached FNV at -8 instead of re-hashing. The len
|
|
167
|
+
// header stays at -4 either way — no other reader changes.
|
|
168
|
+
const interned = !!ctx.transform.optimize?.internStrings
|
|
169
|
+
const hdr = interned ? 8 : 4
|
|
170
|
+
const aux = interned ? STR_INTERN_BIT : 0
|
|
92
171
|
if (!ctx.runtime.data) ctx.runtime.data = ''
|
|
93
172
|
const prior = ctx.runtime.dataDedup.get(str)
|
|
94
|
-
if (prior !== undefined) return mkPtrIR(PTR.STRING,
|
|
173
|
+
if (prior !== undefined) return mkPtrIR(PTR.STRING, aux, prior + hdr)
|
|
95
174
|
while (ctx.runtime.data.length % 4 !== 0) ctx.runtime.data += '\0'
|
|
96
175
|
const offset = ctx.runtime.data.length
|
|
176
|
+
if (interned) {
|
|
177
|
+
// byte-FNV + clamp — must equal __str_hash's output exactly (it hashes
|
|
178
|
+
// UTF-8 bytes, then clamps ≤1 → +2 for the empty/tombstone sentinels)
|
|
179
|
+
let h = 0x811c9dc5 | 0
|
|
180
|
+
for (let i = 0; i < len; i++) h = Math.imul(h ^ bytes[i], 0x01000193) | 0
|
|
181
|
+
if (h <= 1) h = (h + 2) | 0
|
|
182
|
+
h = h >>> 0
|
|
183
|
+
ctx.runtime.data += String.fromCharCode(h & 0xFF, (h >> 8) & 0xFF, (h >> 16) & 0xFF, (h >> 24) & 0xFF)
|
|
184
|
+
}
|
|
97
185
|
ctx.runtime.data += String.fromCharCode(len & 0xFF, (len >> 8) & 0xFF, (len >> 16) & 0xFF, (len >> 24) & 0xFF)
|
|
98
186
|
for (let i = 0; i < len; i++) ctx.runtime.data += String.fromCharCode(bytes[i])
|
|
99
187
|
ctx.runtime.dataDedup.set(str, offset)
|
|
100
|
-
return mkPtrIR(PTR.STRING,
|
|
188
|
+
return mkPtrIR(PTR.STRING, aux, offset + hdr)
|
|
101
189
|
}
|
|
102
190
|
// Shared memory: pack all string literals into one passive data segment with 4-byte
|
|
103
191
|
// length prefixes. At __start, alloc the whole pool once and memory.init it in a single
|
|
@@ -105,7 +193,7 @@ export default (ctx) => {
|
|
|
105
193
|
// use, independent of string length AND reused across uses.
|
|
106
194
|
if (!ctx.runtime.strPool) {
|
|
107
195
|
ctx.runtime.strPool = ''
|
|
108
|
-
|
|
196
|
+
declGlobal('__strBase', 'i32')
|
|
109
197
|
}
|
|
110
198
|
let off = ctx.runtime.strPoolDedup.get(str)
|
|
111
199
|
if (off === undefined) {
|
|
@@ -116,23 +204,23 @@ export default (ctx) => {
|
|
|
116
204
|
ctx.runtime.strPoolDedup.set(str, off)
|
|
117
205
|
}
|
|
118
206
|
return mkPtrIR(PTR.STRING, 0, ['i32.add', ['global.get', '$__strBase'], ['i32.const', off]])
|
|
119
|
-
}
|
|
207
|
+
})
|
|
120
208
|
|
|
121
209
|
// === WAT: char extraction ===
|
|
122
210
|
|
|
123
211
|
// SSO/STRING ptrs never have forwarding pointers (only ARRAY does), so we extract
|
|
124
212
|
// the raw offset directly instead of paying the __ptr_offset function-call overhead.
|
|
125
|
-
|
|
213
|
+
wat('__sso_char', `(func $__sso_char (param $ptr i64) (param $i i32) (result i32)
|
|
126
214
|
(i32.and
|
|
127
215
|
(i32.shr_u
|
|
128
216
|
(i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK})))
|
|
129
217
|
(i32.mul (local.get $i) (i32.const 8)))
|
|
130
|
-
(i32.const 0xFF)))`
|
|
218
|
+
(i32.const 0xFF)))`)
|
|
131
219
|
|
|
132
|
-
|
|
220
|
+
wat('__str_char', `(func $__str_char (param $ptr i64) (param $i i32) (result i32)
|
|
133
221
|
(i32.load8_u (i32.add
|
|
134
222
|
(i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK})))
|
|
135
|
-
(local.get $i))))`
|
|
223
|
+
(local.get $i))))`)
|
|
136
224
|
|
|
137
225
|
// Hot (~37M calls in watr self-host, ~40k/scan in tokenizer bench). Caller
|
|
138
226
|
// guarantees $ptr is a STRING; SSO bit picks inline-byte-extract vs heap memory
|
|
@@ -149,7 +237,7 @@ export default (ctx) => {
|
|
|
149
237
|
// load+bounds-check, beating call-site overhead. Repeated `i32.wrap_i64 +
|
|
150
238
|
// i64.and OFFSET_MASK` subexpressions rely on CSE in the consumer; both
|
|
151
239
|
// V8/TurboFan and watr's own propagate pass handle this.
|
|
152
|
-
|
|
240
|
+
wat('__char_at', `(func $__char_at (param $ptr i64) (param $i i32) (result i32)
|
|
153
241
|
(if (result i32)
|
|
154
242
|
(i64.ne (i64.and (local.get $ptr) (i64.const ${SSO_BIT_I64})) (i64.const 0))
|
|
155
243
|
(then
|
|
@@ -190,9 +278,9 @@ export default (ctx) => {
|
|
|
190
278
|
(i32.load8_u
|
|
191
279
|
(i32.add
|
|
192
280
|
(i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK})))
|
|
193
|
-
(local.get $i)))))))))`
|
|
281
|
+
(local.get $i)))))))))`)
|
|
194
282
|
|
|
195
|
-
|
|
283
|
+
wat('__str_idx', `(func $__str_idx (param $ptr i64) (param $i i32) (result f64)
|
|
196
284
|
(local $t i32) (local $off i32) (local $len i32) (local $isSso i32)
|
|
197
285
|
(local.set $t (i32.wrap_i64 (i64.and (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
198
286
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
@@ -221,21 +309,58 @@ export default (ctx) => {
|
|
|
221
309
|
(f64.reinterpret_i64
|
|
222
310
|
(i64.or
|
|
223
311
|
;; mkptr(STRING, SSO_BIT|1, 0) = NAN_PREFIX | (STRING<<TAG_SHIFT) | ((SSO_BIT|1)<<AUX_SHIFT)
|
|
224
|
-
(i64.const ${
|
|
312
|
+
(i64.const ${ptrNanHex(PTR.STRING, LAYOUT.SSO_BIT | 1)})
|
|
225
313
|
(i64.extend_i32_u
|
|
226
314
|
(if (result i32) (local.get $isSso)
|
|
227
315
|
(then (i32.and (i32.shr_u (local.get $off) (i32.mul (local.get $i) (i32.const 8))) (i32.const 0xFF)))
|
|
228
|
-
(else (i32.load8_u (i32.add (local.get $off) (local.get $i)))))))))))`
|
|
316
|
+
(else (i32.load8_u (i32.add (local.get $off) (local.get $i)))))))))))`)
|
|
229
317
|
|
|
230
318
|
// Hot: ~53M calls in watr self-host. Bit-eq covers identity. SSO/SSO with !bit-eq
|
|
231
319
|
// guarantees content differs (high 32 bits encode type+len; both equal → low 32 differs
|
|
232
320
|
// ⇒ bytes differ). Heap/heap uses raw load8_u — no per-byte function calls.
|
|
233
321
|
// Mixed SSO×heap is rare; falls back to __char_at.
|
|
234
|
-
|
|
322
|
+
// Hot/cold split: the prefix every comparison runs (bit-eq, both-SSO,
|
|
323
|
+
// both-canonical, heap length mismatch) is LOOP-FREE and small enough for
|
|
324
|
+
// the engine's wasm inliner — the call overhead disappears at every site
|
|
325
|
+
// while the byte-walk lives in __str_eq_cold. Mirrors the __ptr_offset_fwd
|
|
326
|
+
// split; a body containing a loop is excluded from V8's inliner.
|
|
327
|
+
wat('__str_eq', `(func $__str_eq (param $a i64) (param $b i64) (result i32)
|
|
328
|
+
(local $axA i32) (local $axB i32) (local $offA i32) (local $offB i32)
|
|
329
|
+
(if (i64.eq (local.get $a) (local.get $b))
|
|
330
|
+
(then (return (i32.const 1))))
|
|
331
|
+
(local.set $axA (i32.wrap_i64 (i64.shr_u (local.get $a) (i64.const ${LAYOUT.AUX_SHIFT}))))
|
|
332
|
+
(local.set $axB (i32.wrap_i64 (i64.shr_u (local.get $b) (i64.const ${LAYOUT.AUX_SHIFT}))))
|
|
333
|
+
;; both SSO ⇒ bit-ne already decided content-ne
|
|
334
|
+
(if (i32.and
|
|
335
|
+
(i32.and (local.get $axA) (i32.const ${LAYOUT.SSO_BIT}))
|
|
336
|
+
(i32.and (local.get $axB) (i32.const ${LAYOUT.SSO_BIT})))
|
|
337
|
+
(then (return (i32.const 0))))
|
|
338
|
+
;; both CANONICAL interned heap strings ⇒ bit-ne ⇒ content-ne
|
|
339
|
+
(if (i32.and
|
|
340
|
+
(i32.eq (i32.and (local.get $axA) (i32.const ${LAYOUT.SSO_BIT | LAYOUT.SLICE_BIT | 0x1})) (i32.const 0x1))
|
|
341
|
+
(i32.eq (i32.and (local.get $axB) (i32.const ${LAYOUT.SSO_BIT | LAYOUT.SLICE_BIT | 0x1})) (i32.const 0x1)))
|
|
342
|
+
(then (return (i32.const 0))))
|
|
343
|
+
;; both PLAIN heap (not SSO, not slice): length mismatch exits without bytes
|
|
344
|
+
(if (i32.eqz (i32.or
|
|
345
|
+
(i32.and (i32.or (local.get $axA) (local.get $axB)) (i32.const ${LAYOUT.SSO_BIT | LAYOUT.SLICE_BIT}))
|
|
346
|
+
(i32.const 0)))
|
|
347
|
+
(then
|
|
348
|
+
(local.set $offA (i32.wrap_i64 (i64.and (local.get $a) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
349
|
+
(local.set $offB (i32.wrap_i64 (i64.and (local.get $b) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
350
|
+
(if (i32.and (i32.ge_u (local.get $offA) (i32.const 4)) (i32.ge_u (local.get $offB) (i32.const 4)))
|
|
351
|
+
(then
|
|
352
|
+
(if (i32.ne
|
|
353
|
+
(i32.load (i32.sub (local.get $offA) (i32.const 4)))
|
|
354
|
+
(i32.load (i32.sub (local.get $offB) (i32.const 4))))
|
|
355
|
+
(then (return (i32.const 0))))))))
|
|
356
|
+
(call $__str_eq_cold (local.get $a) (local.get $b)))`)
|
|
357
|
+
|
|
358
|
+
wat('__str_eq_cold', `(func $__str_eq_cold (param $a i64) (param $b i64) (result i32)
|
|
235
359
|
(local $len i32) (local $lenB i32) (local $i i32)
|
|
236
360
|
(local $ta i32) (local $tb i32)
|
|
237
361
|
(local $offA i32) (local $offB i32)
|
|
238
362
|
(local $ssoA i32) (local $ssoB i32)
|
|
363
|
+
(local $axA i32) (local $axB i32)
|
|
239
364
|
(if (i64.eq (local.get $a) (local.get $b))
|
|
240
365
|
(then (return (i32.const 1))))
|
|
241
366
|
(local.set $ta (i32.wrap_i64 (i64.and (i64.shr_u (local.get $a) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
@@ -251,6 +376,19 @@ export default (ctx) => {
|
|
|
251
376
|
;; Both SSO with !bit-eq ⇒ content differs (high 32 bits hold tag+aux; both equal here).
|
|
252
377
|
(if (i32.and (local.get $ssoA) (local.get $ssoB))
|
|
253
378
|
(then (return (i32.const 0))))
|
|
379
|
+
;; Both CANONICAL interned heap strings (STR_INTERN_BIT, SSO/SLICE clear):
|
|
380
|
+
;; canonicals are deduped, so bit-ne ⇒ content-ne — the V8 pointer-compare
|
|
381
|
+
;; equivalent. This is the dominant unequal case in tag-dispatch chains
|
|
382
|
+
;; (op === 'literal' ladders over static literals).
|
|
383
|
+
(local.set $axA (i32.wrap_i64 (i64.shr_u (local.get $a) (i64.const ${LAYOUT.AUX_SHIFT}))))
|
|
384
|
+
(local.set $axB (i32.wrap_i64 (i64.shr_u (local.get $b) (i64.const ${LAYOUT.AUX_SHIFT}))))
|
|
385
|
+
(if (i32.and
|
|
386
|
+
(i32.and (i32.eq (local.get $ta) (i32.const ${PTR.STRING}))
|
|
387
|
+
(i32.eq (local.get $tb) (i32.const ${PTR.STRING})))
|
|
388
|
+
(i32.and
|
|
389
|
+
(i32.eq (i32.and (local.get $axA) (i32.const ${LAYOUT.SSO_BIT | LAYOUT.SLICE_BIT | STR_INTERN_BIT})) (i32.const ${STR_INTERN_BIT}))
|
|
390
|
+
(i32.eq (i32.and (local.get $axB) (i32.const ${LAYOUT.SSO_BIT | LAYOUT.SLICE_BIT | STR_INTERN_BIT})) (i32.const ${STR_INTERN_BIT}))))
|
|
391
|
+
(then (return (i32.const 0))))
|
|
254
392
|
;; Both heap STRING fast path: inline len from header. Chunk by 4 bytes via unaligned
|
|
255
393
|
;; i32.load (wasm guarantees unaligned-OK), then byte-tail. Most string comparisons fail
|
|
256
394
|
;; early on the first 4-byte word, so this collapses the per-byte branch overhead into a
|
|
@@ -296,7 +434,7 @@ export default (ctx) => {
|
|
|
296
434
|
(then (return (i32.const 0))))
|
|
297
435
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
298
436
|
(br $lm)))
|
|
299
|
-
(i32.const 1))`
|
|
437
|
+
(i32.const 1))`)
|
|
300
438
|
|
|
301
439
|
// Three-way byte-wise compare: -1 if a < b, 0 if equal, +1 if a > b. Returns
|
|
302
440
|
// i32 so callers can `i32.lt_s 0`, `i32.gt_s 0`, etc. without coercion.
|
|
@@ -305,7 +443,7 @@ export default (ctx) => {
|
|
|
305
443
|
// codepoint order for code points < 0x80 and well-formed strings. NOT locale-
|
|
306
444
|
// aware: this is the byte-wise variant suitable for sort-stability use cases,
|
|
307
445
|
// not human-language collation.
|
|
308
|
-
|
|
446
|
+
wat('__str_cmp', `(func $__str_cmp (param $a i64) (param $b i64) (result i32)
|
|
309
447
|
(local $lenA i32) (local $lenB i32) (local $minLen i32) (local $i i32)
|
|
310
448
|
(local $ca i32) (local $cb i32)
|
|
311
449
|
;; Bit-equal pointers (including same SSO inline form) ⇒ identical strings.
|
|
@@ -326,11 +464,11 @@ export default (ctx) => {
|
|
|
326
464
|
;; Common prefix matches — shorter string sorts first.
|
|
327
465
|
(if (i32.lt_s (local.get $lenA) (local.get $lenB)) (then (return (i32.const -1))))
|
|
328
466
|
(if (i32.gt_s (local.get $lenA) (local.get $lenB)) (then (return (i32.const 1))))
|
|
329
|
-
(i32.const 0))`
|
|
467
|
+
(i32.const 0))`)
|
|
330
468
|
|
|
331
469
|
// === WAT: unified byte length (SSO → aux low bits, heap → header) ===
|
|
332
470
|
|
|
333
|
-
|
|
471
|
+
wat('__str_byteLen', `(func $__str_byteLen (param $ptr i64) (result i32)
|
|
334
472
|
(local $t i32) (local $off i32) (local $aux i32)
|
|
335
473
|
(local.set $t (i32.wrap_i64 (i64.and (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
336
474
|
(if (result i32) (i32.eq (local.get $t) (i32.const ${PTR.STRING}))
|
|
@@ -349,15 +487,16 @@ export default (ctx) => {
|
|
|
349
487
|
(if (result i32) (i32.ge_u (local.get $off) (i32.const 4))
|
|
350
488
|
(then (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
351
489
|
(else (i32.const 0))))))))
|
|
352
|
-
(else (i32.const 0))))`
|
|
490
|
+
(else (i32.const 0))))`)
|
|
353
491
|
|
|
354
492
|
// === WAT: string methods ===
|
|
355
493
|
|
|
356
494
|
// SSO source uses an unrolled byte-extract loop (len ≤ 4); heap source uses memory.copy
|
|
357
495
|
// (single bulk op instead of nlen × __char_at).
|
|
358
|
-
|
|
496
|
+
wat('__str_slice', () => `(func $__str_slice (param $ptr i64) (param $start i32) (param $end i32) (result f64)
|
|
359
497
|
(local $len i32) (local $nlen i32) (local $off i32) (local $i i32)
|
|
360
|
-
(local $srcOff i32) (local $isSso i32)
|
|
498
|
+
(local $srcOff i32) (local $isSso i32) (local $sb i32) (local $sp i32)
|
|
499
|
+
(local $ip i32) (local $h i32) (local $j i32) (local $slot i32) (local $cand i32) (local $k i32)
|
|
361
500
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
362
501
|
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
363
502
|
(then (local.set $start (i32.add (local.get $len) (local.get $start)))))
|
|
@@ -374,6 +513,8 @@ export default (ctx) => {
|
|
|
374
513
|
(if (i32.ge_s (local.get $start) (local.get $end))
|
|
375
514
|
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
|
|
376
515
|
(local.set $nlen (i32.sub (local.get $end) (local.get $start)))
|
|
516
|
+
${sliceSsoPackWat()}
|
|
517
|
+
${internProbeWat('(i32.add (i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ' + LAYOUT.OFFSET_MASK + '))) (local.get $start))', '(i32.eqz (local.get $isSso))')}
|
|
377
518
|
(local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $nlen))))
|
|
378
519
|
(i32.store (local.get $off) (local.get $nlen))
|
|
379
520
|
(local.set $off (i32.add (local.get $off) (i32.const 4)))
|
|
@@ -393,7 +534,7 @@ export default (ctx) => {
|
|
|
393
534
|
(br $loop))))
|
|
394
535
|
(else
|
|
395
536
|
(memory.copy (local.get $off) (i32.add (local.get $srcOff) (local.get $start)) (local.get $nlen))))
|
|
396
|
-
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`
|
|
537
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`)
|
|
397
538
|
|
|
398
539
|
// No-copy slice: returns a VIEW into the receiver's buffer instead of copying
|
|
399
540
|
// bytes. Only emitted when escape analysis proves the result never outlives the
|
|
@@ -403,8 +544,9 @@ export default (ctx) => {
|
|
|
403
544
|
// (__str_slice) when the parent is SSO (no buffer to point into) or the result
|
|
404
545
|
// is longer than SLICE_LEN_MASK (aux can't hold the length). Clamping mirrors
|
|
405
546
|
// __str_slice; the fallback re-clamps idempotently.
|
|
406
|
-
|
|
547
|
+
wat('__str_slice_view', () => `(func $__str_slice_view (param $ptr i64) (param $start i32) (param $end i32) (result f64)
|
|
407
548
|
(local $len i32) (local $nlen i32) (local $srcOff i32) (local $tag i32)
|
|
549
|
+
(local $ip i32) (local $h i32) (local $j i32) (local $slot i32) (local $cand i32) (local $k i32)
|
|
408
550
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
409
551
|
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
410
552
|
(then (local.set $start (i32.add (local.get $len) (local.get $start)))))
|
|
@@ -422,22 +564,26 @@ export default (ctx) => {
|
|
|
422
564
|
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
|
|
423
565
|
(local.set $nlen (i32.sub (local.get $end) (local.get $start)))
|
|
424
566
|
(local.set $tag (i32.wrap_i64 (i64.and (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
425
|
-
;; View-eligible: STRING parent, not SSO, length fits aux[12:0].
|
|
567
|
+
;; View-eligible: STRING parent, not SSO, length fits aux[12:0]. ≤4-byte
|
|
568
|
+
;; results route to __str_slice's SSO pack instead (bit-equal short tokens).
|
|
426
569
|
(if (i32.and
|
|
427
570
|
(i32.and
|
|
428
|
-
(i32.
|
|
571
|
+
(i32.and
|
|
572
|
+
(i32.eq (local.get $tag) (i32.const ${PTR.STRING}))
|
|
573
|
+
(i32.gt_u (local.get $nlen) (i32.const 4)))
|
|
429
574
|
(i64.eqz (i64.and (local.get $ptr) (i64.const ${SSO_BIT_I64}))))
|
|
430
575
|
(i32.le_u (local.get $nlen) (i32.const ${LAYOUT.SLICE_LEN_MASK})))
|
|
431
576
|
(then
|
|
432
577
|
(local.set $srcOff (i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
578
|
+
${internProbeWat('(i32.add (local.get $srcOff) (local.get $start))')}
|
|
433
579
|
(return (call $__mkptr
|
|
434
580
|
(i32.const ${PTR.STRING})
|
|
435
581
|
(i32.or (i32.const ${LAYOUT.SLICE_BIT}) (local.get $nlen))
|
|
436
582
|
(i32.add (local.get $srcOff) (local.get $start))))))
|
|
437
583
|
;; Fallback: copy (SSO parent, or slice too long for the aux length field).
|
|
438
|
-
(call $__str_slice (local.get $ptr) (local.get $start) (local.get $end)))`
|
|
584
|
+
(call $__str_slice (local.get $ptr) (local.get $start) (local.get $end)))`)
|
|
439
585
|
|
|
440
|
-
|
|
586
|
+
wat('__str_substring', `(func $__str_substring (param $ptr i64) (param $start i32) (param $end i32) (result f64)
|
|
441
587
|
(local $len i32) (local $tmp i32)
|
|
442
588
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
443
589
|
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
@@ -453,7 +599,7 @@ export default (ctx) => {
|
|
|
453
599
|
(local.set $tmp (local.get $start))
|
|
454
600
|
(local.set $start (local.get $end))
|
|
455
601
|
(local.set $end (local.get $tmp))))
|
|
456
|
-
(call $__str_slice (local.get $ptr) (local.get $start) (local.get $end)))`
|
|
602
|
+
(call $__str_slice (local.get $ptr) (local.get $start) (local.get $end)))`)
|
|
457
603
|
|
|
458
604
|
// === WAT: fused substring-equality ===
|
|
459
605
|
//
|
|
@@ -468,7 +614,7 @@ export default (ctx) => {
|
|
|
468
614
|
// returns one) and type-checks only `other`, mirroring __eq's STRING-vs-?
|
|
469
615
|
// arm: a genuine number never equals a string, and a NaN-boxed non-STRING
|
|
470
616
|
// never does either (jz `==` is strict).
|
|
471
|
-
|
|
617
|
+
wat('__str_range_eq', `(func $__str_range_eq (param $ptr i64) (param $start i32) (param $end i32) (param $other i64) (result i32)
|
|
472
618
|
(local $n i32) (local $i i32) (local $fb f64)
|
|
473
619
|
;; A genuine number reinterprets to a non-NaN f64 (equals itself) — never a string.
|
|
474
620
|
(local.set $fb (f64.reinterpret_i64 (local.get $other)))
|
|
@@ -492,10 +638,10 @@ export default (ctx) => {
|
|
|
492
638
|
(then (return (i32.const 0))))
|
|
493
639
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
494
640
|
(br $next)))
|
|
495
|
-
(i32.const 1))`
|
|
641
|
+
(i32.const 1))`)
|
|
496
642
|
|
|
497
643
|
// Clamp mirrors __str_substring (negatives floor to 0, swap when start>end).
|
|
498
|
-
|
|
644
|
+
wat('__str_substring_eq', `(func $__str_substring_eq (param $ptr i64) (param $start i32) (param $end i32) (param $other i64) (result i32)
|
|
499
645
|
(local $len i32) (local $tmp i32)
|
|
500
646
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
501
647
|
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
@@ -511,11 +657,11 @@ export default (ctx) => {
|
|
|
511
657
|
(local.set $tmp (local.get $start))
|
|
512
658
|
(local.set $start (local.get $end))
|
|
513
659
|
(local.set $end (local.get $tmp))))
|
|
514
|
-
(call $__str_range_eq (local.get $ptr) (local.get $start) (local.get $end) (local.get $other)))`
|
|
660
|
+
(call $__str_range_eq (local.get $ptr) (local.get $start) (local.get $end) (local.get $other)))`)
|
|
515
661
|
|
|
516
662
|
// Clamp mirrors __str_slice (negatives count from the end; __str_range_eq
|
|
517
663
|
// floors a negative span to an empty match).
|
|
518
|
-
|
|
664
|
+
wat('__str_slice_eq', `(func $__str_slice_eq (param $ptr i64) (param $start i32) (param $end i32) (param $other i64) (result i32)
|
|
519
665
|
(local $len i32)
|
|
520
666
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
521
667
|
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
@@ -530,11 +676,11 @@ export default (ctx) => {
|
|
|
530
676
|
(then (local.set $end (i32.const 0))))
|
|
531
677
|
(if (i32.gt_s (local.get $end) (local.get $len))
|
|
532
678
|
(then (local.set $end (local.get $len))))
|
|
533
|
-
(call $__str_range_eq (local.get $ptr) (local.get $start) (local.get $end) (local.get $other)))`
|
|
679
|
+
(call $__str_range_eq (local.get $ptr) (local.get $start) (local.get $end) (local.get $other)))`)
|
|
534
680
|
|
|
535
681
|
// Hoist SSO/heap dispatch for hay and ndl out of the inner byte loop. Inner
|
|
536
682
|
// loop becomes (load8_u OR sso byte-extract) per side — no per-byte calls.
|
|
537
|
-
|
|
683
|
+
wat('__str_indexof', `(func $__str_indexof (param $hay i64) (param $ndl i64) (param $from i32) (result i32)
|
|
538
684
|
(local $hlen i32) (local $nlen i32) (local $i i32) (local $j i32) (local $match i32)
|
|
539
685
|
(local $hoff i32) (local $noff i32)
|
|
540
686
|
(local $hsso i32) (local $nsso i32) (local $hb i32) (local $nb i32) (local $k i32)
|
|
@@ -573,10 +719,63 @@ export default (ctx) => {
|
|
|
573
719
|
(if (local.get $match) (then (return (local.get $i))))
|
|
574
720
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
575
721
|
(br $outer)))
|
|
576
|
-
(i32.const -1))`
|
|
722
|
+
(i32.const -1))`)
|
|
723
|
+
|
|
724
|
+
// Mirror of __str_indexof but searches from the end. Returns the last byte-offset
|
|
725
|
+
// of `ndl` in `hay` at or before `from` (-1 if not found). Per JS spec step 4,
|
|
726
|
+
// ToString(searchValue) is applied first. SSO/heap dispatch hoisted exactly as
|
|
727
|
+
// in __str_indexof so the inner loop is cheap byte-fetches.
|
|
728
|
+
wat('__str_lastindexof', `(func $__str_lastindexof (param $hay i64) (param $ndl i64) (param $from i32) (result i32)
|
|
729
|
+
(local $hlen i32) (local $nlen i32) (local $i i32) (local $j i32) (local $match i32)
|
|
730
|
+
(local $hoff i32) (local $noff i32)
|
|
731
|
+
(local $hsso i32) (local $nsso i32) (local $hb i32) (local $nb i32) (local $k i32)
|
|
732
|
+
;; ToString the search value (21.1.3.10 step 4)
|
|
733
|
+
(local.set $ndl (call $__to_str (local.get $ndl)))
|
|
734
|
+
(local.set $hlen (call $__str_byteLen (local.get $hay)))
|
|
735
|
+
(local.set $nlen (call $__str_byteLen (local.get $ndl)))
|
|
736
|
+
;; Empty needle always matches at the clamp(from,0,hlen) position
|
|
737
|
+
(if (i32.eqz (local.get $nlen))
|
|
738
|
+
(then (return (select
|
|
739
|
+
(select (local.get $from) (local.get $hlen) (i32.lt_s (local.get $from) (local.get $hlen)))
|
|
740
|
+
(i32.const 0)
|
|
741
|
+
(i32.ge_s (local.get $from) (i32.const 0))))))
|
|
742
|
+
(if (i32.gt_s (local.get $nlen) (local.get $hlen)) (then (return (i32.const -1))))
|
|
743
|
+
(local.set $hoff (i32.wrap_i64 (i64.and (local.get $hay) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
744
|
+
(local.set $noff (i32.wrap_i64 (i64.and (local.get $ndl) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
745
|
+
(local.set $hsso (i32.and
|
|
746
|
+
(i32.wrap_i64 (i64.shr_u (local.get $hay) (i64.const ${LAYOUT.AUX_SHIFT})))
|
|
747
|
+
(i32.const ${LAYOUT.SSO_BIT})))
|
|
748
|
+
(local.set $nsso (i32.and
|
|
749
|
+
(i32.wrap_i64 (i64.shr_u (local.get $ndl) (i64.const ${LAYOUT.AUX_SHIFT})))
|
|
750
|
+
(i32.const ${LAYOUT.SSO_BIT})))
|
|
751
|
+
;; clamp start position: from defaults to hlen-nlen, capped at that ceiling
|
|
752
|
+
(local.set $i (i32.sub (local.get $hlen) (local.get $nlen)))
|
|
753
|
+
(if (i32.and (i32.ge_s (local.get $from) (i32.const 0)) (i32.lt_s (local.get $from) (local.get $i)))
|
|
754
|
+
(then (local.set $i (local.get $from))))
|
|
755
|
+
(block $done (loop $outer
|
|
756
|
+
(br_if $done (i32.lt_s (local.get $i) (i32.const 0)))
|
|
757
|
+
(local.set $match (i32.const 1))
|
|
758
|
+
(local.set $j (i32.const 0))
|
|
759
|
+
(block $nomatch (loop $inner
|
|
760
|
+
(br_if $nomatch (i32.ge_s (local.get $j) (local.get $nlen)))
|
|
761
|
+
(local.set $k (i32.add (local.get $i) (local.get $j)))
|
|
762
|
+
(local.set $hb (if (result i32) (local.get $hsso)
|
|
763
|
+
(then (i32.and (i32.shr_u (local.get $hoff) (i32.shl (local.get $k) (i32.const 3))) (i32.const 0xFF)))
|
|
764
|
+
(else (i32.load8_u (i32.add (local.get $hoff) (local.get $k))))))
|
|
765
|
+
(local.set $nb (if (result i32) (local.get $nsso)
|
|
766
|
+
(then (i32.and (i32.shr_u (local.get $noff) (i32.shl (local.get $j) (i32.const 3))) (i32.const 0xFF)))
|
|
767
|
+
(else (i32.load8_u (i32.add (local.get $noff) (local.get $j))))))
|
|
768
|
+
(if (i32.ne (local.get $hb) (local.get $nb))
|
|
769
|
+
(then (local.set $match (i32.const 0)) (br $nomatch)))
|
|
770
|
+
(local.set $j (i32.add (local.get $j) (i32.const 1)))
|
|
771
|
+
(br $inner)))
|
|
772
|
+
(if (local.get $match) (then (return (local.get $i))))
|
|
773
|
+
(local.set $i (i32.sub (local.get $i) (i32.const 1)))
|
|
774
|
+
(br $outer)))
|
|
775
|
+
(i32.const -1))`)
|
|
577
776
|
|
|
578
777
|
// SSO/heap dispatch hoisted; inner loop is two inlined byte-fetches and a compare.
|
|
579
|
-
|
|
778
|
+
wat('__str_startswith', `(func $__str_startswith (param $str i64) (param $pfx i64) (result i32)
|
|
580
779
|
(local $plen i32) (local $i i32)
|
|
581
780
|
(local $soff i32) (local $poff i32) (local $ssso i32) (local $psso i32)
|
|
582
781
|
(local.set $plen (call $__str_byteLen (local.get $pfx)))
|
|
@@ -602,9 +801,9 @@ export default (ctx) => {
|
|
|
602
801
|
(then (return (i32.const 0))))
|
|
603
802
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
604
803
|
(br $loop)))
|
|
605
|
-
(i32.const 1))`
|
|
804
|
+
(i32.const 1))`)
|
|
606
805
|
|
|
607
|
-
|
|
806
|
+
wat('__str_endswith', `(func $__str_endswith (param $str i64) (param $sfx i64) (result i32)
|
|
608
807
|
(local $slen i32) (local $flen i32) (local $off i32) (local $i i32) (local $k i32)
|
|
609
808
|
(local $soff i32) (local $foff i32) (local $ssso i32) (local $fsso i32)
|
|
610
809
|
(local.set $slen (call $__str_byteLen (local.get $str)))
|
|
@@ -633,10 +832,10 @@ export default (ctx) => {
|
|
|
633
832
|
(then (return (i32.const 0))))
|
|
634
833
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
635
834
|
(br $loop)))
|
|
636
|
-
(i32.const 1))`
|
|
835
|
+
(i32.const 1))`)
|
|
637
836
|
|
|
638
837
|
// Source SSO/heap dispatch hoisted out of the byte loop (was a per-byte __char_at).
|
|
639
|
-
|
|
838
|
+
wat('__str_case', `(func $__str_case (param $ptr i64) (param $lo i32) (param $hi i32) (param $delta i32) (result f64)
|
|
640
839
|
(local $len i32) (local $off i32) (local $i i32) (local $c i32)
|
|
641
840
|
(local $srcOff i32)
|
|
642
841
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
@@ -667,9 +866,9 @@ export default (ctx) => {
|
|
|
667
866
|
(i32.store8 (i32.add (local.get $off) (local.get $i)) (local.get $c))
|
|
668
867
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
669
868
|
(br $lh)))))
|
|
670
|
-
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`
|
|
869
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`)
|
|
671
870
|
|
|
672
|
-
|
|
871
|
+
wat('__str_trim', `(func $__str_trim (param $ptr i64) (result f64)
|
|
673
872
|
(local $len i32) (local $start i32) (local $end i32)
|
|
674
873
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
675
874
|
(local.set $start (i32.const 0))
|
|
@@ -684,9 +883,9 @@ export default (ctx) => {
|
|
|
684
883
|
(br_if $d2 (i32.gt_u (call $__char_at (local.get $ptr) (i32.sub (local.get $end) (i32.const 1))) (i32.const 32)))
|
|
685
884
|
(local.set $end (i32.sub (local.get $end) (i32.const 1)))
|
|
686
885
|
(br $l2)))
|
|
687
|
-
(call $__str_slice (local.get $ptr) (local.get $start) (local.get $end)))`
|
|
886
|
+
(call $__str_slice (local.get $ptr) (local.get $start) (local.get $end)))`)
|
|
688
887
|
|
|
689
|
-
|
|
888
|
+
wat('__str_trimStart', `(func $__str_trimStart (param $ptr i64) (result f64)
|
|
690
889
|
(local $len i32) (local $start i32)
|
|
691
890
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
692
891
|
(local.set $start (i32.const 0))
|
|
@@ -695,9 +894,9 @@ export default (ctx) => {
|
|
|
695
894
|
(br_if $done (i32.gt_u (call $__char_at (local.get $ptr) (local.get $start)) (i32.const 32)))
|
|
696
895
|
(local.set $start (i32.add (local.get $start) (i32.const 1)))
|
|
697
896
|
(br $loop)))
|
|
698
|
-
(call $__str_slice (local.get $ptr) (local.get $start) (local.get $len)))`
|
|
897
|
+
(call $__str_slice (local.get $ptr) (local.get $start) (local.get $len)))`)
|
|
699
898
|
|
|
700
|
-
|
|
899
|
+
wat('__str_trimEnd', `(func $__str_trimEnd (param $ptr i64) (result f64)
|
|
701
900
|
(local $len i32) (local $end i32)
|
|
702
901
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
703
902
|
(local.set $end (local.get $len))
|
|
@@ -706,11 +905,11 @@ export default (ctx) => {
|
|
|
706
905
|
(br_if $done (i32.gt_u (call $__char_at (local.get $ptr) (i32.sub (local.get $end) (i32.const 1))) (i32.const 32)))
|
|
707
906
|
(local.set $end (i32.sub (local.get $end) (i32.const 1)))
|
|
708
907
|
(br $loop)))
|
|
709
|
-
(call $__str_slice (local.get $ptr) (i32.const 0) (local.get $end)))`
|
|
908
|
+
(call $__str_slice (local.get $ptr) (i32.const 0) (local.get $end)))`)
|
|
710
909
|
|
|
711
910
|
// Materialize source bytes once via __str_copy (handles SSO/heap), then memory.copy
|
|
712
911
|
// each subsequent repetition (single bulk op vs len byte stores per copy).
|
|
713
|
-
|
|
912
|
+
wat('__str_repeat', `(func $__str_repeat (param $ptr i64) (param $n i32) (result f64)
|
|
714
913
|
(local $len i32) (local $total i32) (local $off i32) (local $i i32)
|
|
715
914
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
716
915
|
(if (i32.or (i32.eqz (local.get $n)) (i32.eqz (local.get $len)))
|
|
@@ -729,11 +928,11 @@ export default (ctx) => {
|
|
|
729
928
|
(local.get $len))
|
|
730
929
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
731
930
|
(br $loop)))
|
|
732
|
-
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`
|
|
931
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`)
|
|
733
932
|
|
|
734
933
|
// Coerce value to string: numbers → __ftoa, nullish → static strings,
|
|
735
934
|
// plain NaN → "NaN", arrays → join(","), other string-like pointers pass through.
|
|
736
|
-
|
|
935
|
+
wat('__to_str', `(func $__to_str (param $val i64) (result i64)
|
|
737
936
|
(local $type i32) (local $f f64)
|
|
738
937
|
(local.set $f (f64.reinterpret_i64 (local.get $val)))
|
|
739
938
|
;; Not NaN → number, convert
|
|
@@ -743,6 +942,10 @@ export default (ctx) => {
|
|
|
743
942
|
(then (return (i64.reinterpret_f64 (call $__static_str (i32.const 5))))))
|
|
744
943
|
(if (i64.eq (local.get $val) (i64.const ${UNDEF_NAN}))
|
|
745
944
|
(then (return (i64.reinterpret_f64 (call $__static_str (i32.const 6))))))
|
|
945
|
+
(if (i64.eq (local.get $val) (i64.const ${FALSE_NAN}))
|
|
946
|
+
(then (return (i64.reinterpret_f64 (call $__static_str (i32.const 4))))))
|
|
947
|
+
(if (i64.eq (local.get $val) (i64.const ${TRUE_NAN}))
|
|
948
|
+
(then (return (i64.reinterpret_f64 (call $__static_str (i32.const 3))))))
|
|
746
949
|
(local.set $type (call $__ptr_type (local.get $val)))
|
|
747
950
|
;; Plain NaN (type=0) → "NaN" string
|
|
748
951
|
(if (i32.eqz (local.get $type))
|
|
@@ -751,11 +954,11 @@ export default (ctx) => {
|
|
|
751
954
|
(if (i32.eq (local.get $type) (i32.const ${PTR.ARRAY}))
|
|
752
955
|
(then (return (i64.reinterpret_f64 (call $__str_join (local.get $val)
|
|
753
956
|
(i64.reinterpret_f64 (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT | 1}) (i32.const 44))))))))
|
|
754
|
-
(local.get $val))`
|
|
957
|
+
(local.get $val))`)
|
|
755
958
|
|
|
756
959
|
// Copy bytes of a string (SSO or heap) into memory at dst. Uses memory.copy for
|
|
757
960
|
// heap strings (single native op); unpacks SSO offset-packed bytes inline.
|
|
758
|
-
|
|
961
|
+
wat('__str_copy', `(func $__str_copy (param $src i64) (param $dst i32) (param $len i32)
|
|
759
962
|
(local $w i32)
|
|
760
963
|
(if (i64.ne (i64.and (local.get $src) (i64.const ${SSO_BIT_I64})) (i64.const 0))
|
|
761
964
|
(then
|
|
@@ -775,7 +978,7 @@ export default (ctx) => {
|
|
|
775
978
|
;; Heap STRING: memory.copy directly from string data
|
|
776
979
|
(memory.copy (local.get $dst)
|
|
777
980
|
(i32.wrap_i64 (i64.and (local.get $src) (i64.const ${LAYOUT.OFFSET_MASK})))
|
|
778
|
-
(local.get $len)))))`
|
|
981
|
+
(local.get $len)))))`)
|
|
779
982
|
|
|
780
983
|
// Bump-extend fast path: when `a` is a heap STRING sitting at the top of the
|
|
781
984
|
// bump allocator, extend its allocation in place instead of copying. Mutates
|
|
@@ -826,10 +1029,7 @@ export default (ctx) => {
|
|
|
826
1029
|
(then
|
|
827
1030
|
(local.set $newHeap
|
|
828
1031
|
(i32.and (i32.add (i32.add (local.get $aoff) (local.get $total)) (i32.const 7)) (i32.const -8)))
|
|
829
|
-
(
|
|
830
|
-
(then (if (i32.eq (memory.grow
|
|
831
|
-
(i32.shr_u (i32.add (i32.sub (local.get $newHeap) (i32.mul (memory.size) (i32.const 65536))) (i32.const 65535)) (i32.const 16)))
|
|
832
|
-
(i32.const -1)) (then (unreachable)))))
|
|
1032
|
+
(call $__memgrow (local.get $newHeap))
|
|
833
1033
|
(call $__str_copy (local.get $b)
|
|
834
1034
|
(i32.add (local.get $aoff) (local.get $alen))
|
|
835
1035
|
(local.get $blen))
|
|
@@ -841,7 +1041,7 @@ export default (ctx) => {
|
|
|
841
1041
|
// VAL.STRING and the rhs is a string-index. Skips __str_idx's 1-char SSO
|
|
842
1042
|
// construction and __str_concat's type-dispatch — byte goes directly from
|
|
843
1043
|
// __char_at to memory. Bump-extends in place when `a` is at heap top.
|
|
844
|
-
|
|
1044
|
+
wat('__str_append_byte', `(func $__str_append_byte (param $a i64) (param $byte i32) (result f64)
|
|
845
1045
|
(local $ta i32) (local $aoff i32) (local $alen i32)
|
|
846
1046
|
(local $newHeap i32) (local $off i32) (local $total i32)
|
|
847
1047
|
(local.set $ta (i32.wrap_i64 (i64.and (i64.shr_u (local.get $a) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
@@ -863,10 +1063,7 @@ export default (ctx) => {
|
|
|
863
1063
|
(then
|
|
864
1064
|
(local.set $newHeap
|
|
865
1065
|
(i32.and (i32.add (i32.add (local.get $aoff) (local.get $alen)) (i32.const 8)) (i32.const -8)))
|
|
866
|
-
(
|
|
867
|
-
(then (if (i32.eq (memory.grow
|
|
868
|
-
(i32.shr_u (i32.add (i32.sub (local.get $newHeap) (i32.mul (memory.size) (i32.const 65536))) (i32.const 65535)) (i32.const 16)))
|
|
869
|
-
(i32.const -1)) (then (unreachable)))))
|
|
1066
|
+
(call $__memgrow (local.get $newHeap))
|
|
870
1067
|
(i32.store8 (i32.add (local.get $aoff) (local.get $alen)) (local.get $byte))
|
|
871
1068
|
(i32.store (i32.sub (local.get $aoff) (i32.const 4)) (i32.add (local.get $alen) (i32.const 1)))
|
|
872
1069
|
(global.set $__heap (local.get $newHeap))
|
|
@@ -899,9 +1096,9 @@ export default (ctx) => {
|
|
|
899
1096
|
(local.set $off (i32.add (local.get $off) (i32.const 4)))
|
|
900
1097
|
(call $__str_copy (local.get $a) (local.get $off) (local.get $alen))
|
|
901
1098
|
(i32.store8 (i32.add (local.get $off) (local.get $alen)) (local.get $byte))
|
|
902
|
-
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`
|
|
1099
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`)
|
|
903
1100
|
|
|
904
|
-
|
|
1101
|
+
wat('__str_concat', `(func $__str_concat (param $a i64) (param $b i64) (result f64)
|
|
905
1102
|
(local $alen i32) (local $blen i32) (local $total i32) (local $off i32)
|
|
906
1103
|
(local $ta i32) (local $aoff i32) (local $newHeap i32)
|
|
907
1104
|
;; Coerce operands to strings if needed
|
|
@@ -919,9 +1116,9 @@ export default (ctx) => {
|
|
|
919
1116
|
(local.set $off (i32.add (local.get $off) (i32.const 4)))
|
|
920
1117
|
(call $__str_copy (local.get $a) (local.get $off) (local.get $alen))
|
|
921
1118
|
(call $__str_copy (local.get $b) (i32.add (local.get $off) (local.get $alen)) (local.get $blen))
|
|
922
|
-
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`
|
|
1119
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`)
|
|
923
1120
|
|
|
924
|
-
|
|
1121
|
+
wat('__str_concat_raw', `(func $__str_concat_raw (param $a i64) (param $b i64) (result f64)
|
|
925
1122
|
(local $alen i32) (local $blen i32) (local $total i32) (local $off i32)
|
|
926
1123
|
(local $ta i32) (local $aoff i32) (local $newHeap i32)
|
|
927
1124
|
(local.set $alen (call $__str_byteLen (local.get $a)))
|
|
@@ -936,9 +1133,9 @@ export default (ctx) => {
|
|
|
936
1133
|
(local.set $off (i32.add (local.get $off) (i32.const 4)))
|
|
937
1134
|
(call $__str_copy (local.get $a) (local.get $off) (local.get $alen))
|
|
938
1135
|
(call $__str_copy (local.get $b) (i32.add (local.get $off) (local.get $alen)) (local.get $blen))
|
|
939
|
-
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`
|
|
1136
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`)
|
|
940
1137
|
|
|
941
|
-
|
|
1138
|
+
wat('__str_replace', `(func $__str_replace (param $str i64) (param $search i64) (param $repl i64) (result f64)
|
|
942
1139
|
(local $idx i32) (local $slen i32)
|
|
943
1140
|
(local.set $idx (call $__str_indexof (local.get $str) (local.get $search) (i32.const 0)))
|
|
944
1141
|
(if (result f64) (i32.lt_s (local.get $idx) (i32.const 0))
|
|
@@ -950,9 +1147,9 @@ export default (ctx) => {
|
|
|
950
1147
|
(i64.reinterpret_f64 (call $__str_slice (local.get $str) (i32.const 0) (local.get $idx)))
|
|
951
1148
|
(local.get $repl)))
|
|
952
1149
|
(i64.reinterpret_f64 (call $__str_slice (local.get $str) (i32.add (local.get $idx) (local.get $slen))
|
|
953
|
-
(call $__str_byteLen (local.get $str))))))))`
|
|
1150
|
+
(call $__str_byteLen (local.get $str))))))))`)
|
|
954
1151
|
|
|
955
|
-
|
|
1152
|
+
wat('__str_replaceall', `(func $__str_replaceall (param $str i64) (param $search i64) (param $repl i64) (result f64)
|
|
956
1153
|
(local $idx i32) (local $slen i32) (local $pos i32) (local $result i64)
|
|
957
1154
|
(local.set $slen (call $__str_byteLen (local.get $search)))
|
|
958
1155
|
(local.set $result (local.get $str))
|
|
@@ -968,33 +1165,48 @@ export default (ctx) => {
|
|
|
968
1165
|
(call $__str_byteLen (local.get $result)))))))
|
|
969
1166
|
(local.set $pos (i32.add (local.get $idx) (call $__str_byteLen (local.get $repl))))
|
|
970
1167
|
(br $next)))
|
|
971
|
-
(f64.reinterpret_i64 (local.get $result)))`
|
|
972
|
-
|
|
973
|
-
//
|
|
974
|
-
// (
|
|
975
|
-
//
|
|
976
|
-
|
|
1168
|
+
(f64.reinterpret_i64 (local.get $result)))`)
|
|
1169
|
+
|
|
1170
|
+
// $limit ≥ 0: honour JS's optional limit arg. 0x7fffffff = "no limit"
|
|
1171
|
+
// (sentinel passed by the no-limit call site). limit=0 → []. limit=N → at
|
|
1172
|
+
// most N elements; the (N+1)th and later pieces are DISCARDED (not appended
|
|
1173
|
+
// as a remainder). Empty separator: split into individual byte-chars, up to
|
|
1174
|
+
// $limit chars ("abc".split("") → ["a","b","c"], "".split("") → []).
|
|
1175
|
+
wat('__str_split', `(func $__str_split (param $str i64) (param $sep i64) (param $limit i32) (result f64)
|
|
977
1176
|
(local $slen i32) (local $plen i32) (local $count i32)
|
|
978
1177
|
(local $i i32) (local $j i32) (local $match i32)
|
|
979
|
-
(local $arr i32) (local $piece_start i32) (local $piece_idx i32)
|
|
1178
|
+
(local $arr i32) (local $piece_start i32) (local $piece_idx i32) (local $hitlim i32)
|
|
980
1179
|
(local.set $slen (call $__str_byteLen (local.get $str)))
|
|
981
1180
|
(local.set $plen (call $__str_byteLen (local.get $sep)))
|
|
1181
|
+
;; limit=0 → empty array
|
|
1182
|
+
(if (i32.eqz (local.get $limit)) (then
|
|
1183
|
+
(local.set $arr (call $__alloc (i32.const 8)))
|
|
1184
|
+
(i32.store (local.get $arr) (i32.const 0))
|
|
1185
|
+
(i32.store (i32.add (local.get $arr) (i32.const 4)) (i32.const 0))
|
|
1186
|
+
(return (call $__mkptr (i32.const 1) (i32.const 0) (i32.add (local.get $arr) (i32.const 8))))))
|
|
982
1187
|
(if (i32.eqz (local.get $plen)) (then
|
|
983
|
-
|
|
984
|
-
(i32.
|
|
985
|
-
(
|
|
1188
|
+
;; Empty-separator: split into individual byte-chars, up to $limit
|
|
1189
|
+
(local.set $count (select (local.get $limit) (local.get $slen) (i32.lt_u (local.get $limit) (local.get $slen))))
|
|
1190
|
+
(local.set $arr (call $__alloc (i32.add (i32.const 8) (i32.shl (local.get $count) (i32.const 3)))))
|
|
1191
|
+
(i32.store (local.get $arr) (local.get $count))
|
|
1192
|
+
(i32.store (i32.add (local.get $arr) (i32.const 4)) (local.get $count))
|
|
986
1193
|
(local.set $arr (i32.add (local.get $arr) (i32.const 8)))
|
|
987
1194
|
(block $de (loop $le
|
|
988
|
-
(br_if $de (i32.ge_s (local.get $i) (local.get $
|
|
1195
|
+
(br_if $de (i32.ge_s (local.get $i) (local.get $count)))
|
|
989
1196
|
(f64.store (i32.add (local.get $arr) (i32.shl (local.get $i) (i32.const 3)))
|
|
990
1197
|
(call $__str_slice (local.get $str) (local.get $i) (i32.add (local.get $i) (i32.const 1))))
|
|
991
1198
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
992
1199
|
(br $le)))
|
|
993
1200
|
(return (call $__mkptr (i32.const 1) (i32.const 0) (local.get $arr)))))
|
|
1201
|
+
;; Count pass: tally pieces = separators+1, capped at $limit.
|
|
1202
|
+
;; We stop incrementing count once count reaches $limit (last sep found
|
|
1203
|
+
;; at that point produces piece #limit — a separator *after* piece limit-1,
|
|
1204
|
+
;; meaning we've already found all limit pieces and don't count more).
|
|
994
1205
|
(local.set $count (i32.const 1))
|
|
995
1206
|
(local.set $i (i32.const 0))
|
|
996
1207
|
(block $d1 (loop $l1
|
|
997
1208
|
(br_if $d1 (i32.gt_s (local.get $i) (i32.sub (local.get $slen) (local.get $plen))))
|
|
1209
|
+
(br_if $d1 (i32.ge_u (local.get $count) (local.get $limit)))
|
|
998
1210
|
(local.set $match (i32.const 1))
|
|
999
1211
|
(local.set $j (i32.const 0))
|
|
1000
1212
|
(block $n1 (loop $c1
|
|
@@ -1017,6 +1229,11 @@ export default (ctx) => {
|
|
|
1017
1229
|
(local.set $piece_start (i32.const 0))
|
|
1018
1230
|
(local.set $piece_idx (i32.const 0))
|
|
1019
1231
|
(local.set $i (i32.const 0))
|
|
1232
|
+
;; Fill pass: write pieces separated by $sep, up to $count total pieces.
|
|
1233
|
+
;; When a separator is found and piece_idx+1 reaches count, write that
|
|
1234
|
+
;; piece (before the sep) and exit WITHOUT appending the tail — this
|
|
1235
|
+
;; correctly discards the remainder when $limit truncates the result.
|
|
1236
|
+
(local.set $hitlim (i32.const 0))
|
|
1020
1237
|
(block $d2 (loop $l2
|
|
1021
1238
|
(br_if $d2 (i32.gt_s (local.get $i) (i32.sub (local.get $slen) (local.get $plen))))
|
|
1022
1239
|
(local.set $match (i32.const 1))
|
|
@@ -1034,14 +1251,27 @@ export default (ctx) => {
|
|
|
1034
1251
|
(local.set $piece_idx (i32.add (local.get $piece_idx) (i32.const 1)))
|
|
1035
1252
|
(local.set $i (i32.add (local.get $i) (local.get $plen)))
|
|
1036
1253
|
(local.set $piece_start (local.get $i))
|
|
1254
|
+
;; If we've emitted all $count pieces, mark limit-hit and stop (discard tail)
|
|
1255
|
+
(if (i32.ge_s (local.get $piece_idx) (local.get $count))
|
|
1256
|
+
(then (local.set $hitlim (i32.const 1)) (br $d2)))
|
|
1037
1257
|
(br $l2)))
|
|
1038
1258
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
1039
1259
|
(br $l2)))
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1260
|
+
;; Write tail only when the scan ended naturally (not truncated by limit)
|
|
1261
|
+
(if (i32.eqz (local.get $hitlim)) (then
|
|
1262
|
+
(f64.store (i32.add (local.get $arr) (i32.shl (local.get $piece_idx) (i32.const 3)))
|
|
1263
|
+
(call $__str_slice (local.get $str) (local.get $piece_start) (local.get $slen)))))
|
|
1264
|
+
(call $__mkptr (i32.const 1) (i32.const 0) (local.get $arr)))`)
|
|
1265
|
+
|
|
1266
|
+
// Array (type=1) → join(",") like JS Array.toString().
|
|
1267
|
+
// When the typedarray module is loaded, also handles PTR.TYPED (type=3) arrays:
|
|
1268
|
+
// promoteIntArrayLiterals rewrites [int,...] → new Int32Array([...]) internally,
|
|
1269
|
+
// so a.map(fn).join() may receive a PTR.TYPED result. Use __typed_idx to load
|
|
1270
|
+
// each element correctly (it returns f64 for any element type / stride).
|
|
1271
|
+
wat('__str_join', () => {
|
|
1272
|
+
if (!ctx.module.modules.typedarray) {
|
|
1273
|
+
// ARRAY-only fast path — no __typed_idx overhead.
|
|
1274
|
+
return `(func $__str_join (param $arr i64) (param $sep i64) (result f64)
|
|
1045
1275
|
(local $off i32) (local $len i32) (local $i i32) (local $result f64)
|
|
1046
1276
|
(local.set $off (call $__ptr_offset (local.get $arr)))
|
|
1047
1277
|
(local.set $len (call $__len (local.get $arr)))
|
|
@@ -1057,13 +1287,48 @@ export default (ctx) => {
|
|
|
1057
1287
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
1058
1288
|
(br $loop)))
|
|
1059
1289
|
(local.get $result))`
|
|
1290
|
+
}
|
|
1291
|
+
// ARRAY + TYPED path: runtime dispatch on ptr type.
|
|
1292
|
+
// PTR.TYPED (type=3): elements have typed-array stride; __typed_idx reads correctly.
|
|
1293
|
+
// PTR.ARRAY (type=1): elements are 8-byte NaN-boxed f64 slots; i64.load is correct.
|
|
1294
|
+
return `(func $__str_join (param $arr i64) (param $sep i64) (result f64)
|
|
1295
|
+
(local $off i32) (local $len i32) (local $i i32) (local $result f64) (local $isTyped i32)
|
|
1296
|
+
(local.set $isTyped
|
|
1297
|
+
(i32.eq
|
|
1298
|
+
(i32.and (i32.wrap_i64 (i64.shr_u (local.get $arr) (i64.const ${LAYOUT.TAG_SHIFT})))
|
|
1299
|
+
(i32.const ${LAYOUT.TAG_MASK}))
|
|
1300
|
+
(i32.const ${PTR.TYPED})))
|
|
1301
|
+
(local.set $off (call $__ptr_offset (local.get $arr)))
|
|
1302
|
+
(local.set $len (call $__len (local.get $arr)))
|
|
1303
|
+
(if (i32.eqz (local.get $len))
|
|
1304
|
+
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
|
|
1305
|
+
(local.set $result
|
|
1306
|
+
(f64.reinterpret_i64
|
|
1307
|
+
(call $__to_str
|
|
1308
|
+
(if (result i64) (local.get $isTyped)
|
|
1309
|
+
(then (i64.reinterpret_f64 (call $__typed_idx (local.get $arr) (i32.const 0))))
|
|
1310
|
+
(else (i64.load (local.get $off)))))))
|
|
1311
|
+
(local.set $i (i32.const 1))
|
|
1312
|
+
(block $done (loop $loop
|
|
1313
|
+
(br_if $done (i32.ge_s (local.get $i) (local.get $len)))
|
|
1314
|
+
(local.set $result (call $__str_concat (i64.reinterpret_f64 (local.get $result)) (local.get $sep)))
|
|
1315
|
+
(local.set $result
|
|
1316
|
+
(call $__str_concat
|
|
1317
|
+
(i64.reinterpret_f64 (local.get $result))
|
|
1318
|
+
(if (result i64) (local.get $isTyped)
|
|
1319
|
+
(then (i64.reinterpret_f64 (call $__typed_idx (local.get $arr) (local.get $i))))
|
|
1320
|
+
(else (i64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3))))))))
|
|
1321
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
1322
|
+
(br $loop)))
|
|
1323
|
+
(local.get $result))`
|
|
1324
|
+
})
|
|
1060
1325
|
|
|
1061
1326
|
// Source string copied via __str_copy (handles SSO/heap with memory.copy where possible).
|
|
1062
1327
|
// Pad fill loops a single tile of pad bytes — hoist pad dispatch out of the byte loop.
|
|
1063
|
-
|
|
1328
|
+
wat('__str_pad', `(func $__str_pad (param $str i64) (param $target i32) (param $pad i64) (param $before i32) (result f64)
|
|
1064
1329
|
(local $slen i32) (local $plen i32) (local $fill i32) (local $off i32) (local $i i32)
|
|
1065
1330
|
(local $str_off i32) (local $pad_off i32)
|
|
1066
|
-
(local $pbits i64) (local $poff i32) (local $psso i32)
|
|
1331
|
+
(local $pbits i64) (local $poff i32) (local $psso i32) (local $sp i32) (local $sb i32)
|
|
1067
1332
|
(local.set $slen (call $__str_byteLen (local.get $str)))
|
|
1068
1333
|
(if (i32.ge_s (local.get $slen) (local.get $target))
|
|
1069
1334
|
(then (return (f64.reinterpret_i64 (local.get $str)))))
|
|
@@ -1090,7 +1355,25 @@ export default (ctx) => {
|
|
|
1090
1355
|
(else (i32.load8_u (i32.add (local.get $poff) (i32.rem_u (local.get $i) (local.get $plen)))))))
|
|
1091
1356
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
1092
1357
|
(br $l2)))
|
|
1093
|
-
|
|
1358
|
+
${!ctx.memory.shared ? `
|
|
1359
|
+
;; SSO + reclaim for ≤4 ASCII results: pack into the pointer and free this pad
|
|
1360
|
+
;; allocation (at heap top) so padStart in a builder loop is allocation-neutral
|
|
1361
|
+
;; — the accumulator stays at heap top and keeps bump-extending (O(n)).
|
|
1362
|
+
(if (i32.le_u (local.get $target) (i32.const 4))
|
|
1363
|
+
(then
|
|
1364
|
+
(block $heap
|
|
1365
|
+
(local.set $i (i32.const 0)) (local.set $sp (i32.const 0))
|
|
1366
|
+
(loop $pk
|
|
1367
|
+
(if (i32.lt_u (local.get $i) (local.get $target))
|
|
1368
|
+
(then
|
|
1369
|
+
(local.set $sb (i32.load8_u (i32.add (local.get $off) (local.get $i))))
|
|
1370
|
+
(br_if $heap (i32.ge_u (local.get $sb) (i32.const 0x80)))
|
|
1371
|
+
(local.set $sp (i32.or (local.get $sp) (i32.shl (local.get $sb) (i32.shl (local.get $i) (i32.const 3)))))
|
|
1372
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
1373
|
+
(br $pk))))
|
|
1374
|
+
(global.set $__heap (i32.sub (local.get $off) (i32.const 4)))
|
|
1375
|
+
(return (call $__mkptr (i32.const ${PTR.STRING}) (i32.or (i32.const ${LAYOUT.SSO_BIT}) (local.get $target)) (local.get $sp))))))` : ''}
|
|
1376
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`)
|
|
1094
1377
|
|
|
1095
1378
|
// Base helpers (__sso_char/__str_char/__char_at/__str_byteLen) are referenced
|
|
1096
1379
|
// from other helpers' WAT bodies and from emit sites; their `stdlibDeps`
|
|
@@ -1103,16 +1386,16 @@ export default (ctx) => {
|
|
|
1103
1386
|
// (21.1.3.27/28). Typed forms cover the static-string case; generic forms
|
|
1104
1387
|
// pair with them so the dispatcher can pick a runtime ptr-type branch when
|
|
1105
1388
|
// the receiver type can't be statically inferred (e.g. a callback param).
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1389
|
+
bind('.string:toString', (str) => asF64(emit(str)))
|
|
1390
|
+
bind('.string:valueOf', (str) => asF64(emit(str)))
|
|
1391
|
+
bind('.toString', (val) => {
|
|
1109
1392
|
inc('__to_str')
|
|
1110
1393
|
return typed(['f64.reinterpret_i64', ['call', '$__to_str', asI64(emit(val))]], 'f64')
|
|
1111
|
-
}
|
|
1394
|
+
})
|
|
1112
1395
|
// Object.prototype.valueOf returns the receiver (per ES2024 20.1.3.7).
|
|
1113
1396
|
// Array/Object inherit this; only primitive wrappers (Number/Boolean/String)
|
|
1114
1397
|
// override to return the primitive — strings already covered by .string:valueOf.
|
|
1115
|
-
|
|
1398
|
+
bind('.valueOf', (val) => asF64(emit(val)))
|
|
1116
1399
|
|
|
1117
1400
|
// `.slice` lowering, parametrised on the backing helper: __str_slice copies
|
|
1118
1401
|
// bytes; __str_slice_view returns a no-copy SLICE_BIT view — used only when
|
|
@@ -1130,8 +1413,8 @@ export default (ctx) => {
|
|
|
1130
1413
|
['call', `$${fn}`, ['i64.reinterpret_f64', ['local.get', `$${t}`]], startIR,
|
|
1131
1414
|
['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]]], 'f64')
|
|
1132
1415
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1416
|
+
bind('.string:slice', sliceEmitter('__str_slice'))
|
|
1417
|
+
bind('.string:slice#view', sliceEmitter('__str_slice_view'))
|
|
1135
1418
|
|
|
1136
1419
|
// ToIntegerOrInfinity for a string-method position argument: ToNumber (so
|
|
1137
1420
|
// string / boolean / null / undefined positions coerce per spec) then trunc.
|
|
@@ -1148,14 +1431,25 @@ export default (ctx) => {
|
|
|
1148
1431
|
// a BOOL rides the 0/1 carrier (→ "0"/"1" not "true"/"false"), and an OBJECT
|
|
1149
1432
|
// needs compile-time ToPrimitive(string) (__to_str can't invoke user toString).
|
|
1150
1433
|
const searchArg = (search) =>
|
|
1151
|
-
valTypeOf(search) === VAL.BOOL ? asI64(
|
|
1434
|
+
valTypeOf(search) === VAL.BOOL ? asI64(bool(search)) :
|
|
1152
1435
|
valTypeOf(search) === VAL.OBJECT ? toStrI64(search, emit(search)) : asI64(emit(search))
|
|
1153
1436
|
|
|
1154
|
-
|
|
1437
|
+
bind('.string:indexOf', (str, search, from) => {
|
|
1155
1438
|
inc('__str_indexof')
|
|
1156
1439
|
const hay = asI64(emit(str)), ndl = searchArg(search)
|
|
1157
1440
|
return typed(['f64.convert_i32_s', ['call', '$__str_indexof', hay, ndl, posIndex(from)]], 'f64')
|
|
1158
|
-
}
|
|
1441
|
+
})
|
|
1442
|
+
|
|
1443
|
+
// String.prototype.lastIndexOf: search from the end, returning the last
|
|
1444
|
+
// byte-offset of `search` in `str` at or before `fromIndex` (or -1).
|
|
1445
|
+
// Per spec the `from` default is +∞ (search from the very end), which we
|
|
1446
|
+
// map to 0x7fffffff — __str_lastindexof clamps it to hlen-nlen anyway.
|
|
1447
|
+
bind('.string:lastIndexOf', (str, search, from) => {
|
|
1448
|
+
inc('__str_lastindexof')
|
|
1449
|
+
const hay = asI64(emit(str)), ndl = searchArg(search)
|
|
1450
|
+
const fromIR = from == null ? ['i32.const', 0x7fffffff] : asI32(emit(from))
|
|
1451
|
+
return typed(['f64.convert_i32_s', ['call', '$__str_lastindexof', hay, ndl, fromIR]], 'f64')
|
|
1452
|
+
})
|
|
1159
1453
|
|
|
1160
1454
|
// String.prototype.{includes,startsWith,endsWith} run IsRegExp(searchString)
|
|
1161
1455
|
// and throw a TypeError when it is a RegExp. Detect a regex-typed search arg
|
|
@@ -1166,16 +1460,16 @@ export default (ctx) => {
|
|
|
1166
1460
|
return typed(['block', ['result', 'f64'], ['throw', '$__jz_err', ['f64.const', 0]]], 'f64')
|
|
1167
1461
|
}
|
|
1168
1462
|
|
|
1169
|
-
|
|
1463
|
+
bind('.string:includes', (str, search, from) => {
|
|
1170
1464
|
const guard = regexpSearchGuard(search); if (guard) return guard
|
|
1171
1465
|
inc('__str_indexof')
|
|
1172
1466
|
const hay = asI64(emit(str)), ndl = searchArg(search)
|
|
1173
1467
|
return typed(['f64.convert_i32_s',
|
|
1174
1468
|
['i32.ge_s', ['call', '$__str_indexof', hay, ndl, posIndex(from)], ['i32.const', 0]]], 'f64')
|
|
1175
|
-
}
|
|
1469
|
+
})
|
|
1176
1470
|
|
|
1177
1471
|
// Generic (no collision)
|
|
1178
|
-
|
|
1472
|
+
bind('.substring', (str, start, end) => {
|
|
1179
1473
|
inc('__str_substring')
|
|
1180
1474
|
if (end != null) return typed(['call', '$__str_substring', asI64(emit(str)), asI32(emit(start)), asI32(emit(end))], 'f64')
|
|
1181
1475
|
const t = temp('t')
|
|
@@ -1183,13 +1477,13 @@ export default (ctx) => {
|
|
|
1183
1477
|
['local.set', `$${t}`, asF64(emit(str))],
|
|
1184
1478
|
['call', '$__str_substring', ['i64.reinterpret_f64', ['local.get', `$${t}`]], asI32(emit(start)),
|
|
1185
1479
|
['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]]], 'f64')
|
|
1186
|
-
}
|
|
1480
|
+
})
|
|
1187
1481
|
|
|
1188
1482
|
// .substr(start, length) — Annex B / legacy. Equivalent to substring(start, start+length).
|
|
1189
1483
|
// __str_substring clamps end to byteLen and start/end to [0, byteLen], so negative
|
|
1190
1484
|
// values are floored to 0 (matches v8 for length<0 → empty; for start<0 spec wants
|
|
1191
1485
|
// max(0, len+start), which we don't implement — rare in practice).
|
|
1192
|
-
|
|
1486
|
+
bind('.substr', (str, start, length) => {
|
|
1193
1487
|
inc('__str_substring')
|
|
1194
1488
|
if (length != null) {
|
|
1195
1489
|
const s = tempI32('substrS')
|
|
@@ -1205,15 +1499,6 @@ export default (ctx) => {
|
|
|
1205
1499
|
['local.set', `$${t}`, asF64(emit(str))],
|
|
1206
1500
|
['call', '$__str_substring', ['i64.reinterpret_f64', ['local.get', `$${t}`]], asI32(emit(start)),
|
|
1207
1501
|
['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]]], 'f64')
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
// Declarative emitter for regular `(recv, ...args) → call($stdlib, coerced...)` methods.
|
|
1211
|
-
// `coerce` is a per-argument code string ('I' i64 · 'F' f64 · 'i' i32); index 0 is the receiver.
|
|
1212
|
-
// `ret` is the helper's result type — 'f64' is returned directly, 'i32' is widened to the f64 ABI.
|
|
1213
|
-
const CO = { I: asI64, F: asF64, i: asI32 }
|
|
1214
|
-
const method = (stdlib, coerce, ret = 'f64') => emitter([stdlib], (...nodes) => {
|
|
1215
|
-
const c = ['call', `$${stdlib}`, ...nodes.map((n, i) => CO[coerce[i]](emit(n)))]
|
|
1216
|
-
return typed(ret === 'i32' ? ['f64.convert_i32_s', c] : c, 'f64')
|
|
1217
1502
|
})
|
|
1218
1503
|
|
|
1219
1504
|
// Search args go through ToString per spec — coerce non-string-typed args
|
|
@@ -1231,30 +1516,87 @@ export default (ctx) => {
|
|
|
1231
1516
|
}
|
|
1232
1517
|
return typed(['f64.convert_i32_s', ['call', `$${name}`, asI64(emit(str)), sfxArg]], 'f64')
|
|
1233
1518
|
}
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1519
|
+
bind('.startsWith', stringSearchMethod('__str_startswith'))
|
|
1520
|
+
bind('.endsWith', stringSearchMethod('__str_endswith'))
|
|
1521
|
+
bind('.trim', method('__str_trim', 'I'))
|
|
1522
|
+
bind('.trimStart', method('__str_trimStart', 'I'))
|
|
1523
|
+
bind('.trimEnd', method('__str_trimEnd', 'I'))
|
|
1524
|
+
bind('.repeat', method('__str_repeat', 'Ii'))
|
|
1525
|
+
// split(sep, limit): both args are optional.
|
|
1526
|
+
// - No args (undefined sep) → [str]: JS spec step 3 treats undefined separator
|
|
1527
|
+
// as returning a single-element array of the whole string (not splitting at all).
|
|
1528
|
+
// - 1 arg → no limit (sentinel 0x7fffffff = MAX_I32).
|
|
1529
|
+
// - 2 args → honour limit: 0 → [], N → at most N pieces.
|
|
1530
|
+
bind('.split', (str, sep, limit) => {
|
|
1531
|
+
if (sep === undefined) {
|
|
1532
|
+
// split() → [str]: wrap the whole string in a 1-element array
|
|
1533
|
+
inc('__wrap1')
|
|
1534
|
+
return typed(['call', '$__wrap1', asI64(emit(str))], 'f64')
|
|
1535
|
+
}
|
|
1536
|
+
inc('__str_split')
|
|
1537
|
+
const limitIR = limit === undefined
|
|
1538
|
+
? ['i32.const', 0x7fffffff]
|
|
1539
|
+
: ['i32.trunc_sat_f64_u', asF64(emit(limit))]
|
|
1540
|
+
return typed(['call', '$__str_split', asI64(emit(str)), asI64(emit(sep)), limitIR], 'f64')
|
|
1541
|
+
})
|
|
1542
|
+
|
|
1543
|
+
// replace(search, replacement). When `replacement` is a function, replace the
|
|
1544
|
+
// FIRST occurrence of the (string) search with ToString(fn(match)) — per spec a
|
|
1545
|
+
// string search matches once and the callback receives the matched substring.
|
|
1546
|
+
// (A regex search routes to `.string:replace` in the regex module, which also
|
|
1547
|
+
// handles the /g loop.) Without a closure runtime we can't invoke the callback,
|
|
1548
|
+
// so fall back to a clear error rather than silent data loss.
|
|
1549
|
+
bind('.replace', (str, search, repl) => {
|
|
1550
|
+
if (valTypeOf(repl) === VAL.CLOSURE) {
|
|
1551
|
+
if (!ctx.closure?.call) err('.replace(search, fn): no closure runtime available for the callback form')
|
|
1552
|
+
inc('__str_indexof', '__str_slice', '__str_concat', '__str_byteLen', '__to_str')
|
|
1553
|
+
const s = temp('rps'), q = temp('rpq'), fnL = temp('rpf')
|
|
1554
|
+
const idx = tempI32('rpi'), mlen = tempI32('rpm')
|
|
1555
|
+
const sI64 = () => ['i64.reinterpret_f64', ['local.get', `$${s}`]]
|
|
1556
|
+
const match = typed(['call', '$__str_slice', sI64(), ['local.get', `$${idx}`],
|
|
1557
|
+
['i32.add', ['local.get', `$${idx}`], ['local.get', `$${mlen}`]]], 'f64')
|
|
1558
|
+
const repIR = ['call', '$__to_str', asI64(ctx.closure.call(typed(['local.get', `$${fnL}`], 'f64'), [match]))]
|
|
1559
|
+
const head = typed(['call', '$__str_slice', sI64(), ['i32.const', 0], ['local.get', `$${idx}`]], 'f64')
|
|
1560
|
+
const tail = typed(['call', '$__str_slice', sI64(),
|
|
1561
|
+
['i32.add', ['local.get', `$${idx}`], ['local.get', `$${mlen}`]],
|
|
1562
|
+
['call', '$__str_byteLen', sI64()]], 'f64')
|
|
1563
|
+
return typed(['block', ['result', 'f64'],
|
|
1564
|
+
['local.set', `$${s}`, asF64(emit(str))],
|
|
1565
|
+
['local.set', `$${q}`, asF64(emit(search))],
|
|
1566
|
+
['local.set', `$${fnL}`, asF64(emit(repl))],
|
|
1567
|
+
['local.set', `$${mlen}`, ['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${q}`]]]],
|
|
1568
|
+
['local.set', `$${idx}`, ['call', '$__str_indexof', sI64(), ['i64.reinterpret_f64', ['local.get', `$${q}`]], ['i32.const', 0]]],
|
|
1569
|
+
['if', ['result', 'f64'], ['i32.lt_s', ['local.get', `$${idx}`], ['i32.const', 0]],
|
|
1570
|
+
['then', ['local.get', `$${s}`]],
|
|
1571
|
+
['else', typed(['call', '$__str_concat',
|
|
1572
|
+
asI64(typed(['call', '$__str_concat', asI64(head), repIR], 'f64')),
|
|
1573
|
+
asI64(tail)], 'f64')]]], 'f64')
|
|
1574
|
+
}
|
|
1575
|
+
inc('__str_replace')
|
|
1576
|
+
return typed(['call', '$__str_replace', asI64(emit(str)), asI64(emit(search)), asI64(emit(repl))], 'f64')
|
|
1577
|
+
})
|
|
1578
|
+
bind('.replaceAll', method('__str_replaceall', 'III'))
|
|
1248
1579
|
|
|
1249
|
-
|
|
1580
|
+
const caseMethod = (lo, hi, delta) => (str) => {
|
|
1250
1581
|
inc('__str_case')
|
|
1251
|
-
return typed(['call', '$__str_case', asI64(emit(str)), ['i32.const',
|
|
1582
|
+
return typed(['call', '$__str_case', asI64(emit(str)), ['i32.const', lo], ['i32.const', hi], ['i32.const', delta]], 'f64')
|
|
1252
1583
|
}
|
|
1584
|
+
bind('.toUpperCase', caseMethod(97, 122, -32))
|
|
1585
|
+
const _toLowerCase = caseMethod(65, 90, 32)
|
|
1586
|
+
bind('.toLowerCase', _toLowerCase)
|
|
1253
1587
|
|
|
1254
1588
|
// Locale-specific casing needs ICU/CLDR data. jz intentionally has no
|
|
1255
1589
|
// runtime, so this follows the existing ASCII-only lowercase helper and
|
|
1256
1590
|
// ignores optional locale arguments.
|
|
1257
|
-
|
|
1591
|
+
bind('.toLocaleLowerCase', _toLowerCase)
|
|
1592
|
+
|
|
1593
|
+
const padMethod = (start) => (str, len, pad) => {
|
|
1594
|
+
inc('__str_pad')
|
|
1595
|
+
const vpad = pad != null ? asI64(emit(pad)) : ['i64.reinterpret_f64', mkPtrIR(PTR.STRING, LAYOUT.SSO_BIT | 1, 32)]
|
|
1596
|
+
return typed(['call', '$__str_pad', asI64(emit(str)), asI32(emit(len)), vpad, ['i32.const', start]], 'f64')
|
|
1597
|
+
}
|
|
1598
|
+
bind('.padStart', padMethod(1))
|
|
1599
|
+
bind('.padEnd', padMethod(0))
|
|
1258
1600
|
|
|
1259
1601
|
// Byte-wise variant of String.prototype.localeCompare. Returns -1/0/1 from
|
|
1260
1602
|
// an unsigned byte-by-byte compare with shorter-string-sorts-first tiebreak.
|
|
@@ -1263,21 +1605,21 @@ export default (ctx) => {
|
|
|
1263
1605
|
// the spec exactly; for non-ASCII it follows UTF-8 byte order, which is
|
|
1264
1606
|
// codepoint order for well-formed strings — close enough for sort-stability
|
|
1265
1607
|
// use cases, wrong for human-language collation.
|
|
1266
|
-
|
|
1608
|
+
bind('.localeCompare', method('__str_cmp', 'II', 'i32'))
|
|
1267
1609
|
|
|
1268
|
-
|
|
1610
|
+
bind('.string:concat', (str, ...others) => {
|
|
1269
1611
|
inc('__str_concat')
|
|
1270
1612
|
let result = asF64(emit(str))
|
|
1271
1613
|
for (const other of others) result = typed(['call', '$__str_concat', ['i64.reinterpret_f64', result], asI64(emit(other))], 'f64')
|
|
1272
1614
|
return result
|
|
1273
|
-
}
|
|
1615
|
+
})
|
|
1274
1616
|
|
|
1275
1617
|
// A VAL.BOOL part rides the 0/1 carrier, so __to_str would render "1"/"0".
|
|
1276
|
-
//
|
|
1618
|
+
// bool selects the interned "true"/"false" literal (constant-folded
|
|
1277
1619
|
// when the operand is known); every other part goes through __to_str.
|
|
1278
|
-
const partStrI64 = (p) => valTypeOf(p) === VAL.BOOL ? asI64(
|
|
1620
|
+
const partStrI64 = (p) => valTypeOf(p) === VAL.BOOL ? asI64(bool(p)) : toStrI64(p, emit(p))
|
|
1279
1621
|
|
|
1280
|
-
|
|
1622
|
+
bind('strcat', (...parts) => {
|
|
1281
1623
|
inc('__to_str', '__str_byteLen', '__alloc', '__mkptr', '__str_copy')
|
|
1282
1624
|
if (!parts.length) return mkPtrIR(PTR.STRING, LAYOUT.SSO_BIT, 0)
|
|
1283
1625
|
if (parts.length === 1) return typed(['f64.reinterpret_i64', partStrI64(parts[0])], 'f64')
|
|
@@ -1311,29 +1653,33 @@ export default (ctx) => {
|
|
|
1311
1653
|
['then', mkPtrIR(PTR.STRING, LAYOUT.SSO_BIT, 0)],
|
|
1312
1654
|
['else', ['block', ['result', 'f64'], ...alloc]]])
|
|
1313
1655
|
return typed(['block', ['result', 'f64'], ...ir], 'f64')
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
ctx.core.emit['.padStart'] = (str, len, pad) => {
|
|
1317
|
-
inc('__str_pad')
|
|
1318
|
-
const vpad = pad != null ? asI64(emit(pad)) : ['i64.reinterpret_f64', mkPtrIR(PTR.STRING, LAYOUT.SSO_BIT | 1, 32)]
|
|
1319
|
-
return typed(['call', '$__str_pad', asI64(emit(str)), asI32(emit(len)), vpad, ['i32.const', 1]], 'f64')
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
ctx.core.emit['.padEnd'] = (str, len, pad) => {
|
|
1323
|
-
inc('__str_pad')
|
|
1324
|
-
const vpad = pad != null ? asI64(emit(pad)) : ['i64.reinterpret_f64', mkPtrIR(PTR.STRING, LAYOUT.SSO_BIT | 1, 32)]
|
|
1325
|
-
return typed(['call', '$__str_pad', asI64(emit(str)), asI32(emit(len)), vpad, ['i32.const', 0]], 'f64')
|
|
1326
|
-
}
|
|
1656
|
+
})
|
|
1327
1657
|
|
|
1328
|
-
//
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1658
|
+
// Shared: `$iLocal` in `[0, lenIR)` → a 1-byte SSO string of the char at that
|
|
1659
|
+
// index; otherwise `oobIR`. Without the bounds check `__char_at` returns 0 for an
|
|
1660
|
+
// out-of-range index, which would wrap to a bogus `"\x00"` string (charAt → "",
|
|
1661
|
+
// at → undefined). `sLocal` is the receiver as an f64 local.
|
|
1662
|
+
const charAtOr = (sLocal, iLocal, lenIR, oobIR) =>
|
|
1663
|
+
['if', ['result', 'f64'],
|
|
1664
|
+
['i32.and',
|
|
1665
|
+
['i32.ge_s', ['local.get', iLocal], ['i32.const', 0]],
|
|
1666
|
+
['i32.lt_s', ['local.get', iLocal], lenIR]],
|
|
1667
|
+
['then', mkPtrIR(PTR.STRING, LAYOUT.SSO_BIT | 1,
|
|
1668
|
+
['call', '$__char_at', ['i64.reinterpret_f64', ['local.get', sLocal]], ['local.get', iLocal]])],
|
|
1669
|
+
['else', oobIR]]
|
|
1670
|
+
const emptyStr = mkPtrIR(PTR.STRING, LAYOUT.SSO_BIT, 0)
|
|
1671
|
+
|
|
1672
|
+
// .charAt(i) → 1-char string at index i, or "" when out of range (JS spec: no
|
|
1673
|
+
// negative-index wraparound — a negative or >=length index yields "").
|
|
1674
|
+
bind('.charAt', (str, idx) => {
|
|
1675
|
+
inc('__char_at', '__str_byteLen')
|
|
1676
|
+
const s = temp('cs'), i = tempI32('ci')
|
|
1333
1677
|
return typed(['block', ['result', 'f64'],
|
|
1334
|
-
['local.set', `$${
|
|
1335
|
-
|
|
1336
|
-
|
|
1678
|
+
['local.set', `$${s}`, asF64(emit(str))],
|
|
1679
|
+
['local.set', `$${i}`, asI32(emit(idx))],
|
|
1680
|
+
charAtOr(`$${s}`, `$${i}`,
|
|
1681
|
+
['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${s}`]]], emptyStr)], 'f64')
|
|
1682
|
+
})
|
|
1337
1683
|
|
|
1338
1684
|
// .charCodeAt(i) → JS-spec char code: the UTF-16 code unit at `i`, or NaN
|
|
1339
1685
|
// when `i` is out of range (`i < 0 || i >= length`). Result is f64 because
|
|
@@ -1342,32 +1688,50 @@ export default (ctx) => {
|
|
|
1342
1688
|
// loop `while ((cc = s.charCodeAt(i++)) <= 32) {}` would never terminate
|
|
1343
1689
|
// (`0 <= 32` is true, `NaN <= 32` is false). The narrower may re-narrow the
|
|
1344
1690
|
// result to i32 where it can prove the index in-bounds.
|
|
1345
|
-
|
|
1346
|
-
typed(ctx.abi.string.ops.charCodeAt(asF64(emit(str)), asI32(emit(idx)), ctx, true), 'f64')
|
|
1691
|
+
bind('.charCodeAt', (str, idx) =>
|
|
1692
|
+
typed(ctx.abi.string.ops.charCodeAt(asF64(emit(str)), asI32(emit(idx)), ctx, true), 'f64'))
|
|
1693
|
+
|
|
1694
|
+
// String.prototype.codePointAt(i) — byte-indexed.
|
|
1695
|
+
// jz strings are UTF-8 byte-arrays; `i` is a byte offset, not a UTF-16 code-unit
|
|
1696
|
+
// index. For ASCII inputs (U+0000..U+007F) the result is the exact Unicode code
|
|
1697
|
+
// point. For multi-byte sequences the result is the value of the leading byte only
|
|
1698
|
+
// (not a full code-point decode). Out-of-range → undefined (NaN-boxed). This is a
|
|
1699
|
+
// documented byte-semantics limitation; it keeps the implementation allocation-free
|
|
1700
|
+
// and consistent with jz's byte-indexed string model throughout.
|
|
1701
|
+
bind('.codePointAt', (str, idx) =>
|
|
1702
|
+
typed(ctx.abi.string.ops.charCodeAt(asF64(emit(str)), asI32(emit(idx)), ctx, true), 'f64'))
|
|
1347
1703
|
|
|
1348
1704
|
// String.fromCharCode(code) → 1-char SSO string
|
|
1349
|
-
|
|
1705
|
+
bind('String', (value) => {
|
|
1350
1706
|
if (value === undefined) return emit(['str', ''])
|
|
1351
1707
|
if (valTypeOf(value) === VAL.STRING) return emit(value)
|
|
1352
|
-
if (valTypeOf(value) === VAL.BOOL) return
|
|
1708
|
+
if (valTypeOf(value) === VAL.BOOL) return bool(value)
|
|
1353
1709
|
if (valTypeOf(value) === VAL.NUMBER) {
|
|
1354
1710
|
inc('__ftoa')
|
|
1355
1711
|
return typed(['call', '$__ftoa', asF64(emit(value)), ['i32.const', 0], ['i32.const', 0]], 'f64')
|
|
1356
1712
|
}
|
|
1357
1713
|
return typed(['f64.reinterpret_i64', toStrI64(value, emit(value))], 'f64')
|
|
1358
|
-
}
|
|
1714
|
+
})
|
|
1359
1715
|
|
|
1360
|
-
|
|
1361
|
-
|
|
1716
|
+
// String.fromCharCode(...codes) — variadic; each arg is ToUint16(ToNumber(code))
|
|
1717
|
+
// → a 1-byte string, concatenated left to right (mirrors String.fromCodePoint).
|
|
1718
|
+
bind('String.fromCharCode', (...codes) => {
|
|
1719
|
+
if (codes.length === 0) return emit(['str', ''])
|
|
1362
1720
|
// ToUint16(ToNumber(code)): `toNumF64` performs ToPrimitive on an object
|
|
1363
1721
|
// argument, so a throwing valueOf/toString propagates per spec.
|
|
1364
|
-
|
|
1365
|
-
|
|
1722
|
+
const one = (node) => mkPtrIR(PTR.STRING, LAYOUT.SSO_BIT | 1, asI32(toNumF64(node, emit(node))))
|
|
1723
|
+
let r = one(codes[0])
|
|
1724
|
+
for (let i = 1; i < codes.length; i++) {
|
|
1725
|
+
inc('__str_concat_raw')
|
|
1726
|
+
r = typed(['call', '$__str_concat_raw', asI64(r), asI64(one(codes[i]))], 'f64')
|
|
1727
|
+
}
|
|
1728
|
+
return r
|
|
1729
|
+
})
|
|
1366
1730
|
|
|
1367
1731
|
// String.fromCodePoint(cp) → UTF-8 encoded string for one code point.
|
|
1368
1732
|
// Param is f64 (already ToNumber-coerced); throws RangeError ($__jz_err) when
|
|
1369
1733
|
// the value is not an integer in [0, 0x10FFFF] (22.1.2.2 step 5.d).
|
|
1370
|
-
|
|
1734
|
+
wat('__fromCodePoint', `(func $__fromCodePoint (param $cpf f64) (result f64)
|
|
1371
1735
|
(local $cp i32) (local $off i32) (local $len i32)
|
|
1372
1736
|
(if (i32.or
|
|
1373
1737
|
(i32.or
|
|
@@ -1398,11 +1762,11 @@ export default (ctx) => {
|
|
|
1398
1762
|
(i32.or (i32.const 0xF0) (i32.shr_u (local.get $cp) (i32.const 18)))
|
|
1399
1763
|
(i32.shl (i32.or (i32.const 0x80) (i32.and (i32.shr_u (local.get $cp) (i32.const 12)) (i32.const 0x3F))) (i32.const 8)))
|
|
1400
1764
|
(i32.shl (i32.or (i32.const 0x80) (i32.and (i32.shr_u (local.get $cp) (i32.const 6)) (i32.const 0x3F))) (i32.const 16)))
|
|
1401
|
-
(i32.shl (i32.or (i32.const 0x80) (i32.and (local.get $cp) (i32.const 0x3F))) (i32.const 24))))))`
|
|
1765
|
+
(i32.shl (i32.or (i32.const 0x80) (i32.and (local.get $cp) (i32.const 0x3F))) (i32.const 24))))))`)
|
|
1402
1766
|
|
|
1403
1767
|
// String.fromCodePoint(...codePoints) — variadic; each arg is ToNumber-coerced
|
|
1404
1768
|
// then validated/encoded by __fromCodePoint, results concatenated left to right.
|
|
1405
|
-
|
|
1769
|
+
bind('String.fromCodePoint', (...codes) => {
|
|
1406
1770
|
if (codes.length === 0) return emit(['str', ''])
|
|
1407
1771
|
ctx.runtime.throws = true
|
|
1408
1772
|
inc('__fromCodePoint')
|
|
@@ -1414,9 +1778,9 @@ export default (ctx) => {
|
|
|
1414
1778
|
r = typed(['call', '$__str_concat_raw', asI64(r), asI64(one(codes[i]))], 'f64')
|
|
1415
1779
|
}
|
|
1416
1780
|
return r
|
|
1417
|
-
}
|
|
1781
|
+
})
|
|
1418
1782
|
|
|
1419
|
-
|
|
1783
|
+
wat('__encodeURIComponent', `(func $__encodeURIComponent (param $val i64) (result f64)
|
|
1420
1784
|
(local $str i64) (local $slen i32) (local $base i32) (local $out i32)
|
|
1421
1785
|
(local $i i32) (local $j i32) (local $c i32) (local $hi i32) (local $lo i32)
|
|
1422
1786
|
(local.set $str (call $__to_str (local.get $val)))
|
|
@@ -1458,24 +1822,24 @@ export default (ctx) => {
|
|
|
1458
1822
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
1459
1823
|
(br $loop)))
|
|
1460
1824
|
(i32.store (local.get $base) (local.get $j))
|
|
1461
|
-
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $out)))`
|
|
1825
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $out)))`)
|
|
1462
1826
|
|
|
1463
|
-
|
|
1827
|
+
bind('encodeURIComponent', (value) => {
|
|
1464
1828
|
inc('__encodeURIComponent')
|
|
1465
1829
|
const input = value === undefined ? ['i64.const', UNDEF_NAN] : asI64(emit(value))
|
|
1466
1830
|
return typed(['call', '$__encodeURIComponent', input], 'f64')
|
|
1467
|
-
}
|
|
1831
|
+
})
|
|
1468
1832
|
|
|
1469
|
-
|
|
1833
|
+
wat('__uri_hex', `(func $__uri_hex (param $c i32) (result i32)
|
|
1470
1834
|
(if (result i32) (i32.and (i32.ge_u (local.get $c) (i32.const 48)) (i32.le_u (local.get $c) (i32.const 57)))
|
|
1471
1835
|
(then (i32.sub (local.get $c) (i32.const 48)))
|
|
1472
1836
|
(else (if (result i32) (i32.and (i32.ge_u (local.get $c) (i32.const 65)) (i32.le_u (local.get $c) (i32.const 70)))
|
|
1473
1837
|
(then (i32.sub (local.get $c) (i32.const 55)))
|
|
1474
1838
|
(else (if (result i32) (i32.and (i32.ge_u (local.get $c) (i32.const 97)) (i32.le_u (local.get $c) (i32.const 102)))
|
|
1475
1839
|
(then (i32.sub (local.get $c) (i32.const 87)))
|
|
1476
|
-
(else (i32.const -1))))))))`
|
|
1840
|
+
(else (i32.const -1))))))))`)
|
|
1477
1841
|
|
|
1478
|
-
|
|
1842
|
+
wat('__decodeURIComponent', `(func $__decodeURIComponent (param $v i64) (result f64)
|
|
1479
1843
|
(local $s i64) (local $len i32) (local $i i32)
|
|
1480
1844
|
(local $base i32) (local $dst i32) (local $outLen i32)
|
|
1481
1845
|
(local $c i32) (local $hi i32) (local $lo i32)
|
|
@@ -1551,38 +1915,40 @@ export default (ctx) => {
|
|
|
1551
1915
|
(local.set $outLen (i32.add (local.get $outLen) (i32.const 1)))))
|
|
1552
1916
|
(br $loop)))
|
|
1553
1917
|
(i32.store (local.get $base) (local.get $outLen))
|
|
1554
|
-
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $dst)))`
|
|
1918
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $dst)))`)
|
|
1555
1919
|
|
|
1556
|
-
|
|
1920
|
+
bind('decodeURIComponent', (value) => {
|
|
1557
1921
|
ctx.runtime.throws = true
|
|
1558
1922
|
inc('__decodeURIComponent')
|
|
1559
1923
|
return typed(['call', '$__decodeURIComponent',
|
|
1560
1924
|
value === undefined ? ['i64.const', UNDEF_NAN] : asI64(emit(value))], 'f64')
|
|
1561
|
-
}
|
|
1925
|
+
})
|
|
1562
1926
|
|
|
1563
|
-
// .at(i) →
|
|
1564
|
-
|
|
1927
|
+
// .at(i) → 1-char string at index i with negative-index support, or undefined
|
|
1928
|
+
// when out of range (JS spec: `i += length` for negative, then OOB → undefined).
|
|
1929
|
+
bind('.string:at', (str, idx) => {
|
|
1565
1930
|
inc('__char_at', '__str_byteLen')
|
|
1566
|
-
const t = tempI32('at'), s = temp('as')
|
|
1931
|
+
const t = tempI32('at'), s = temp('as'), len = tempI32('al')
|
|
1567
1932
|
return typed(['block', ['result', 'f64'],
|
|
1568
1933
|
['local.set', `$${s}`, asF64(emit(str))],
|
|
1934
|
+
['local.set', `$${len}`, ['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${s}`]]]],
|
|
1569
1935
|
['local.set', `$${t}`, asI32(emit(idx))],
|
|
1570
1936
|
// Negative index: t += length
|
|
1571
1937
|
['if', ['i32.lt_s', ['local.get', `$${t}`], ['i32.const', 0]],
|
|
1572
|
-
['then', ['local.set', `$${t}`, ['i32.add', ['local.get', `$${t}`],
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
}
|
|
1938
|
+
['then', ['local.set', `$${t}`, ['i32.add', ['local.get', `$${t}`], ['local.get', `$${len}`]]]]],
|
|
1939
|
+
charAtOr(`$${s}`, `$${t}`, ['local.get', `$${len}`],
|
|
1940
|
+
['f64.reinterpret_i64', ['i64.const', UNDEF_NAN]])], 'f64')
|
|
1941
|
+
})
|
|
1576
1942
|
|
|
1577
1943
|
// .search(str) → indexOf (same as indexOf for string args)
|
|
1578
|
-
|
|
1944
|
+
bind('.search', (str, search) => {
|
|
1579
1945
|
inc('__str_indexof')
|
|
1580
1946
|
return typed(['f64.convert_i32_s', ['call', '$__str_indexof', asI64(emit(str)), asI64(emit(search)), ['i32.const', 0]]], 'f64')
|
|
1581
|
-
}
|
|
1947
|
+
})
|
|
1582
1948
|
|
|
1583
1949
|
// .match(str) → [match] array if found, or 0 (null) if not
|
|
1584
1950
|
// For string args, returns single-element array with the matched substring
|
|
1585
|
-
|
|
1951
|
+
bind('.match', (str, search) => {
|
|
1586
1952
|
inc('__str_indexof', '__str_slice', '__wrap1')
|
|
1587
1953
|
const s = temp('ms'), q = temp('mq'), idx = tempI32('mi')
|
|
1588
1954
|
// indexOf, then if >= 0, create 1-element array with the match slice
|
|
@@ -1599,24 +1965,24 @@ export default (ctx) => {
|
|
|
1599
1965
|
['call', '$__str_slice', ['i64.reinterpret_f64', ['local.get', `$${s}`]],
|
|
1600
1966
|
['local.get', `$${idx}`],
|
|
1601
1967
|
['i32.add', ['local.get', `$${idx}`], ['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${q}`]]]]]]]]]], 'f64')
|
|
1602
|
-
}
|
|
1968
|
+
})
|
|
1603
1969
|
|
|
1604
1970
|
// __wrap1(val: i64) → f64 — create 1-element array [val]
|
|
1605
|
-
|
|
1971
|
+
wat('__wrap1', `(func $__wrap1 (param $val i64) (result f64)
|
|
1606
1972
|
(local $ptr i32)
|
|
1607
1973
|
(local.set $ptr (call $__alloc (i32.const 16)))
|
|
1608
1974
|
(i32.store (local.get $ptr) (i32.const 1))
|
|
1609
1975
|
(i32.store (i32.add (local.get $ptr) (i32.const 4)) (i32.const 1))
|
|
1610
1976
|
(i64.store (i32.add (local.get $ptr) (i32.const 8)) (local.get $val))
|
|
1611
|
-
(call $__mkptr (i32.const 1) (i32.const 0) (i32.add (local.get $ptr) (i32.const 8))))`
|
|
1977
|
+
(call $__mkptr (i32.const 1) (i32.const 0) (i32.add (local.get $ptr) (i32.const 8))))`)
|
|
1612
1978
|
|
|
1613
1979
|
// TextEncoder() / TextDecoder() → dummy values (methods do the work)
|
|
1614
|
-
|
|
1615
|
-
|
|
1980
|
+
bind('TextEncoder', () => typed(['f64.const', 1], 'f64'))
|
|
1981
|
+
bind('TextDecoder', () => typed(['f64.const', 2], 'f64'))
|
|
1616
1982
|
|
|
1617
1983
|
// .encode(str) → Uint8Array of string's UTF-8 bytes
|
|
1618
1984
|
// Copies bytes from string (SSO or heap) into a new Uint8Array
|
|
1619
|
-
|
|
1985
|
+
wat('__str_encode', `(func $__str_encode (param $str i64) (result f64)
|
|
1620
1986
|
(local $len i32) (local $dst i32)
|
|
1621
1987
|
(local.set $len (call $__str_byteLen (local.get $str)))
|
|
1622
1988
|
(local.set $dst (call $__alloc (i32.add (i32.const 8) (local.get $len))))
|
|
@@ -1624,15 +1990,20 @@ export default (ctx) => {
|
|
|
1624
1990
|
(i32.store (i32.add (local.get $dst) (i32.const 4)) (local.get $len))
|
|
1625
1991
|
(local.set $dst (i32.add (local.get $dst) (i32.const 8)))
|
|
1626
1992
|
(call $__str_copy (local.get $str) (local.get $dst) (local.get $len))
|
|
1627
|
-
(call $__mkptr (i32.const 3) (i32.const 1) (local.get $dst)))`
|
|
1993
|
+
(call $__mkptr (i32.const 3) (i32.const 1) (local.get $dst)))`)
|
|
1628
1994
|
|
|
1629
|
-
|
|
1995
|
+
bind('.encode', (obj, str) => {
|
|
1630
1996
|
inc('__str_encode')
|
|
1997
|
+
// .encode() yields a runtime PTR.TYPED/u8 array (see __mkptr above). Downstream
|
|
1998
|
+
// indexing/spread dispatch through __typed_idx, whose element-unaware fallback
|
|
1999
|
+
// (f64.load, stride 8) is only valid when no typed array can flow in. Enabling
|
|
2000
|
+
// the feature pulls the element-aware variant — same invariant `.length` follows.
|
|
2001
|
+
ctx.features.typedarray = true
|
|
1631
2002
|
return typed(['call', '$__str_encode', asI64(emit(str))], 'f64')
|
|
1632
|
-
}
|
|
2003
|
+
})
|
|
1633
2004
|
|
|
1634
2005
|
// .decode(uint8arr) → string from byte data
|
|
1635
|
-
|
|
2006
|
+
wat('__bytes_decode', `(func $__bytes_decode (param $arr i64) (result f64)
|
|
1636
2007
|
(local $off i32) (local $len i32) (local $dst i32)
|
|
1637
2008
|
(local.set $off (call $__ptr_offset (local.get $arr)))
|
|
1638
2009
|
(local.set $len (call $__len (local.get $arr)))
|
|
@@ -1640,10 +2011,10 @@ export default (ctx) => {
|
|
|
1640
2011
|
(i32.store (local.get $dst) (local.get $len))
|
|
1641
2012
|
(local.set $dst (i32.add (local.get $dst) (i32.const 4)))
|
|
1642
2013
|
(memory.copy (local.get $dst) (local.get $off) (local.get $len))
|
|
1643
|
-
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $dst)))`
|
|
2014
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $dst)))`)
|
|
1644
2015
|
|
|
1645
|
-
|
|
2016
|
+
bind('.decode', (obj, arr) => {
|
|
1646
2017
|
inc('__bytes_decode')
|
|
1647
2018
|
return typed(['call', '$__bytes_decode', asI64(emit(arr))], 'f64')
|
|
1648
|
-
}
|
|
2019
|
+
})
|
|
1649
2020
|
}
|