jz 0.5.1 → 0.7.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 +315 -318
- package/bench/README.md +369 -0
- package/bench/bench.svg +102 -0
- package/cli.js +104 -30
- package/dist/interop.js +1 -0
- package/dist/jz.js +6024 -0
- package/index.d.ts +126 -0
- package/index.js +362 -84
- package/interop.js +159 -186
- 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 +184 -0
- package/module/array.js +377 -176
- package/module/collection.js +639 -145
- package/module/console.js +55 -43
- package/module/core.js +264 -153
- 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 +551 -187
- package/module/number.js +327 -60
- package/module/object.js +474 -184
- package/module/regex.js +255 -25
- package/module/schema.js +24 -6
- package/module/simd.js +117 -0
- package/module/string.js +621 -226
- package/module/symbol.js +1 -1
- package/module/timer.js +9 -14
- package/module/typedarray.js +459 -73
- package/package.json +58 -14
- package/src/abi/index.js +39 -23
- package/src/abi/string.js +38 -41
- package/src/ast.js +460 -0
- package/src/autoload.js +29 -24
- package/src/bridge.js +111 -0
- package/src/compile/analyze-scans.js +824 -0
- package/src/compile/analyze.js +1600 -0
- package/src/compile/emit-assign.js +411 -0
- package/src/compile/emit.js +3512 -0
- package/src/compile/flow-types.js +103 -0
- package/src/{compile.js → compile/index.js} +778 -128
- package/src/{infer.js → compile/infer.js} +62 -98
- package/src/compile/loop-divmod.js +155 -0
- package/src/{narrow.js → compile/narrow.js} +409 -106
- package/src/compile/peel-stencil.js +261 -0
- package/src/compile/plan/advise.js +370 -0
- package/src/compile/plan/common.js +150 -0
- package/src/compile/plan/index.js +122 -0
- package/src/compile/plan/inline.js +682 -0
- package/src/compile/plan/literals.js +1199 -0
- package/src/compile/plan/loops.js +472 -0
- package/src/compile/plan/scope.js +649 -0
- package/src/compile/program-facts.js +423 -0
- package/src/ctx.js +220 -64
- package/src/ir.js +589 -172
- package/src/kind-traits.js +132 -0
- package/src/kind.js +524 -0
- package/src/op-policy.js +57 -0
- package/src/{optimize.js → optimize/index.js} +1432 -473
- package/src/optimize/vectorize.js +4660 -0
- package/src/param-reps.js +65 -0
- package/src/parse.js +44 -0
- package/src/{prepare.js → prepare/index.js} +649 -205
- package/src/prepare/lift-iife.js +149 -0
- package/src/reps.js +116 -0
- package/src/resolve.js +12 -3
- package/src/static.js +208 -0
- package/src/type.js +651 -0
- package/src/{assemble.js → wat/assemble.js} +275 -55
- package/src/wat/optimize.js +3938 -0
- package/transform.js +21 -0
- package/wasi.js +47 -5
- package/src/analyze.js +0 -3818
- package/src/emit.js +0 -3040
- package/src/jzify.js +0 -1580
- 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
|
-
__str_indexof: ['__str_byteLen'
|
|
117
|
+
__str_indexof: ['__str_byteLen'],
|
|
118
|
+
__str_lastindexof: ['__str_byteLen'],
|
|
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'],
|
|
@@ -74,11 +145,23 @@ export default (ctx) => {
|
|
|
74
145
|
__str_byteLen: ['__ptr_type', '__ptr_aux', '__str_len'],
|
|
75
146
|
})
|
|
76
147
|
|
|
148
|
+
// String *runtime* construction (concat/slice/split/`String(n)`/shared-memory
|
|
149
|
+
// pools) allocates and boxes pointers, so the module eagerly declares its core
|
|
150
|
+
// heap deps — same as object.js / typedarray.js / array.js. Several such helpers
|
|
151
|
+
// (`__str_slice`, `__str_split`, `__str_case`, …) call `$__mkptr`/`$__alloc` only
|
|
152
|
+
// through nested template thunks (sliceSsoPackWat/internProbeWat); resolveIncludes'
|
|
153
|
+
// auto-derivation realizes those thunks to recover the dep, but that re-realization
|
|
154
|
+
// is not reliable under self-host (the jz.wasm kernel re-runs the thunk late, with
|
|
155
|
+
// intern/heap state the native run never reaches), so a runtime string op would
|
|
156
|
+
// emit `call $__mkptr` with no definition. Declaring the deps explicitly here makes
|
|
157
|
+
// inclusion independent of thunk re-realization. Reachability pruning still drops
|
|
158
|
+
// both for a literals-only module (no `__mkptr`/`__alloc` call site → unreachable),
|
|
159
|
+
// so a heap-free program stays heap-free — no allocator, no memory, no exports.
|
|
77
160
|
inc('__mkptr', '__alloc')
|
|
78
161
|
|
|
79
|
-
// === String literal: "abc" → SSO if ≤4 ASCII, else
|
|
162
|
+
// === String literal: "abc" → SSO if ≤4 ASCII, else static data ===
|
|
80
163
|
|
|
81
|
-
|
|
164
|
+
bind('str', (str) => {
|
|
82
165
|
const MAX_SSO = 4
|
|
83
166
|
if (ctx.features.sso && str.length <= MAX_SSO && /^[\x00-\x7f]*$/.test(str)) {
|
|
84
167
|
let packed = 0
|
|
@@ -88,16 +171,33 @@ export default (ctx) => {
|
|
|
88
171
|
const bytes = new TextEncoder().encode(str)
|
|
89
172
|
const len = bytes.length
|
|
90
173
|
if (!ctx.memory.shared) {
|
|
91
|
-
// Own memory: place in static data segment (no runtime allocation)
|
|
174
|
+
// Own memory: place in static data segment (no runtime allocation).
|
|
175
|
+
// Under internStrings the layout is [hash u32][len u32][bytes] and the
|
|
176
|
+
// pointer carries STR_INTERN_BIT: statics are CANONICAL (deduped), so
|
|
177
|
+
// unequal canonicals are bit-unequal (__str_eq short-circuit) and
|
|
178
|
+
// __str_hash loads the cached FNV at -8 instead of re-hashing. The len
|
|
179
|
+
// header stays at -4 either way — no other reader changes.
|
|
180
|
+
const interned = !!ctx.transform.optimize?.internStrings
|
|
181
|
+
const hdr = interned ? 8 : 4
|
|
182
|
+
const aux = interned ? STR_INTERN_BIT : 0
|
|
92
183
|
if (!ctx.runtime.data) ctx.runtime.data = ''
|
|
93
184
|
const prior = ctx.runtime.dataDedup.get(str)
|
|
94
|
-
if (prior !== undefined) return mkPtrIR(PTR.STRING,
|
|
185
|
+
if (prior !== undefined) return mkPtrIR(PTR.STRING, aux, prior + hdr)
|
|
95
186
|
while (ctx.runtime.data.length % 4 !== 0) ctx.runtime.data += '\0'
|
|
96
187
|
const offset = ctx.runtime.data.length
|
|
188
|
+
if (interned) {
|
|
189
|
+
// byte-FNV + clamp — must equal __str_hash's output exactly (it hashes
|
|
190
|
+
// UTF-8 bytes, then clamps ≤1 → +2 for the empty/tombstone sentinels)
|
|
191
|
+
let h = 0x811c9dc5 | 0
|
|
192
|
+
for (let i = 0; i < len; i++) h = Math.imul(h ^ bytes[i], 0x01000193) | 0
|
|
193
|
+
if (h <= 1) h = (h + 2) | 0
|
|
194
|
+
h = h >>> 0
|
|
195
|
+
ctx.runtime.data += String.fromCharCode(h & 0xFF, (h >> 8) & 0xFF, (h >> 16) & 0xFF, (h >> 24) & 0xFF)
|
|
196
|
+
}
|
|
97
197
|
ctx.runtime.data += String.fromCharCode(len & 0xFF, (len >> 8) & 0xFF, (len >> 16) & 0xFF, (len >> 24) & 0xFF)
|
|
98
198
|
for (let i = 0; i < len; i++) ctx.runtime.data += String.fromCharCode(bytes[i])
|
|
99
199
|
ctx.runtime.dataDedup.set(str, offset)
|
|
100
|
-
return mkPtrIR(PTR.STRING,
|
|
200
|
+
return mkPtrIR(PTR.STRING, aux, offset + hdr)
|
|
101
201
|
}
|
|
102
202
|
// Shared memory: pack all string literals into one passive data segment with 4-byte
|
|
103
203
|
// length prefixes. At __start, alloc the whole pool once and memory.init it in a single
|
|
@@ -105,7 +205,7 @@ export default (ctx) => {
|
|
|
105
205
|
// use, independent of string length AND reused across uses.
|
|
106
206
|
if (!ctx.runtime.strPool) {
|
|
107
207
|
ctx.runtime.strPool = ''
|
|
108
|
-
|
|
208
|
+
declGlobal('__strBase', 'i32')
|
|
109
209
|
}
|
|
110
210
|
let off = ctx.runtime.strPoolDedup.get(str)
|
|
111
211
|
if (off === undefined) {
|
|
@@ -116,23 +216,23 @@ export default (ctx) => {
|
|
|
116
216
|
ctx.runtime.strPoolDedup.set(str, off)
|
|
117
217
|
}
|
|
118
218
|
return mkPtrIR(PTR.STRING, 0, ['i32.add', ['global.get', '$__strBase'], ['i32.const', off]])
|
|
119
|
-
}
|
|
219
|
+
})
|
|
120
220
|
|
|
121
221
|
// === WAT: char extraction ===
|
|
122
222
|
|
|
123
223
|
// SSO/STRING ptrs never have forwarding pointers (only ARRAY does), so we extract
|
|
124
224
|
// the raw offset directly instead of paying the __ptr_offset function-call overhead.
|
|
125
|
-
|
|
225
|
+
wat('__sso_char', `(func $__sso_char (param $ptr i64) (param $i i32) (result i32)
|
|
126
226
|
(i32.and
|
|
127
227
|
(i32.shr_u
|
|
128
228
|
(i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK})))
|
|
129
229
|
(i32.mul (local.get $i) (i32.const 8)))
|
|
130
|
-
(i32.const 0xFF)))`
|
|
230
|
+
(i32.const 0xFF)))`)
|
|
131
231
|
|
|
132
|
-
|
|
232
|
+
wat('__str_char', `(func $__str_char (param $ptr i64) (param $i i32) (result i32)
|
|
133
233
|
(i32.load8_u (i32.add
|
|
134
234
|
(i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK})))
|
|
135
|
-
(local.get $i))))`
|
|
235
|
+
(local.get $i))))`)
|
|
136
236
|
|
|
137
237
|
// Hot (~37M calls in watr self-host, ~40k/scan in tokenizer bench). Caller
|
|
138
238
|
// guarantees $ptr is a STRING; SSO bit picks inline-byte-extract vs heap memory
|
|
@@ -149,7 +249,7 @@ export default (ctx) => {
|
|
|
149
249
|
// load+bounds-check, beating call-site overhead. Repeated `i32.wrap_i64 +
|
|
150
250
|
// i64.and OFFSET_MASK` subexpressions rely on CSE in the consumer; both
|
|
151
251
|
// V8/TurboFan and watr's own propagate pass handle this.
|
|
152
|
-
|
|
252
|
+
wat('__char_at', `(func $__char_at (param $ptr i64) (param $i i32) (result i32)
|
|
153
253
|
(if (result i32)
|
|
154
254
|
(i64.ne (i64.and (local.get $ptr) (i64.const ${SSO_BIT_I64})) (i64.const 0))
|
|
155
255
|
(then
|
|
@@ -190,9 +290,9 @@ export default (ctx) => {
|
|
|
190
290
|
(i32.load8_u
|
|
191
291
|
(i32.add
|
|
192
292
|
(i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK})))
|
|
193
|
-
(local.get $i)))))))))`
|
|
293
|
+
(local.get $i)))))))))`)
|
|
194
294
|
|
|
195
|
-
|
|
295
|
+
wat('__str_idx', `(func $__str_idx (param $ptr i64) (param $i i32) (result f64)
|
|
196
296
|
(local $t i32) (local $off i32) (local $len i32) (local $isSso i32)
|
|
197
297
|
(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
298
|
(local.set $off (i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
@@ -221,21 +321,58 @@ export default (ctx) => {
|
|
|
221
321
|
(f64.reinterpret_i64
|
|
222
322
|
(i64.or
|
|
223
323
|
;; mkptr(STRING, SSO_BIT|1, 0) = NAN_PREFIX | (STRING<<TAG_SHIFT) | ((SSO_BIT|1)<<AUX_SHIFT)
|
|
224
|
-
(i64.const ${
|
|
324
|
+
(i64.const ${ptrNanHex(PTR.STRING, LAYOUT.SSO_BIT | 1)})
|
|
225
325
|
(i64.extend_i32_u
|
|
226
326
|
(if (result i32) (local.get $isSso)
|
|
227
327
|
(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)))))))))))`
|
|
328
|
+
(else (i32.load8_u (i32.add (local.get $off) (local.get $i)))))))))))`)
|
|
229
329
|
|
|
230
330
|
// Hot: ~53M calls in watr self-host. Bit-eq covers identity. SSO/SSO with !bit-eq
|
|
231
331
|
// guarantees content differs (high 32 bits encode type+len; both equal → low 32 differs
|
|
232
332
|
// ⇒ bytes differ). Heap/heap uses raw load8_u — no per-byte function calls.
|
|
233
333
|
// Mixed SSO×heap is rare; falls back to __char_at.
|
|
234
|
-
|
|
334
|
+
// Hot/cold split: the prefix every comparison runs (bit-eq, both-SSO,
|
|
335
|
+
// both-canonical, heap length mismatch) is LOOP-FREE and small enough for
|
|
336
|
+
// the engine's wasm inliner — the call overhead disappears at every site
|
|
337
|
+
// while the byte-walk lives in __str_eq_cold. Mirrors the __ptr_offset_fwd
|
|
338
|
+
// split; a body containing a loop is excluded from V8's inliner.
|
|
339
|
+
wat('__str_eq', `(func $__str_eq (param $a i64) (param $b i64) (result i32)
|
|
340
|
+
(local $axA i32) (local $axB i32) (local $offA i32) (local $offB i32)
|
|
341
|
+
(if (i64.eq (local.get $a) (local.get $b))
|
|
342
|
+
(then (return (i32.const 1))))
|
|
343
|
+
(local.set $axA (i32.wrap_i64 (i64.shr_u (local.get $a) (i64.const ${LAYOUT.AUX_SHIFT}))))
|
|
344
|
+
(local.set $axB (i32.wrap_i64 (i64.shr_u (local.get $b) (i64.const ${LAYOUT.AUX_SHIFT}))))
|
|
345
|
+
;; both SSO ⇒ bit-ne already decided content-ne
|
|
346
|
+
(if (i32.and
|
|
347
|
+
(i32.and (local.get $axA) (i32.const ${LAYOUT.SSO_BIT}))
|
|
348
|
+
(i32.and (local.get $axB) (i32.const ${LAYOUT.SSO_BIT})))
|
|
349
|
+
(then (return (i32.const 0))))
|
|
350
|
+
;; both CANONICAL interned heap strings ⇒ bit-ne ⇒ content-ne
|
|
351
|
+
(if (i32.and
|
|
352
|
+
(i32.eq (i32.and (local.get $axA) (i32.const ${LAYOUT.SSO_BIT | LAYOUT.SLICE_BIT | 0x1})) (i32.const 0x1))
|
|
353
|
+
(i32.eq (i32.and (local.get $axB) (i32.const ${LAYOUT.SSO_BIT | LAYOUT.SLICE_BIT | 0x1})) (i32.const 0x1)))
|
|
354
|
+
(then (return (i32.const 0))))
|
|
355
|
+
;; both PLAIN heap (not SSO, not slice): length mismatch exits without bytes
|
|
356
|
+
(if (i32.eqz (i32.or
|
|
357
|
+
(i32.and (i32.or (local.get $axA) (local.get $axB)) (i32.const ${LAYOUT.SSO_BIT | LAYOUT.SLICE_BIT}))
|
|
358
|
+
(i32.const 0)))
|
|
359
|
+
(then
|
|
360
|
+
(local.set $offA (i32.wrap_i64 (i64.and (local.get $a) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
361
|
+
(local.set $offB (i32.wrap_i64 (i64.and (local.get $b) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
362
|
+
(if (i32.and (i32.ge_u (local.get $offA) (i32.const 4)) (i32.ge_u (local.get $offB) (i32.const 4)))
|
|
363
|
+
(then
|
|
364
|
+
(if (i32.ne
|
|
365
|
+
(i32.load (i32.sub (local.get $offA) (i32.const 4)))
|
|
366
|
+
(i32.load (i32.sub (local.get $offB) (i32.const 4))))
|
|
367
|
+
(then (return (i32.const 0))))))))
|
|
368
|
+
(call $__str_eq_cold (local.get $a) (local.get $b)))`)
|
|
369
|
+
|
|
370
|
+
wat('__str_eq_cold', `(func $__str_eq_cold (param $a i64) (param $b i64) (result i32)
|
|
235
371
|
(local $len i32) (local $lenB i32) (local $i i32)
|
|
236
372
|
(local $ta i32) (local $tb i32)
|
|
237
373
|
(local $offA i32) (local $offB i32)
|
|
238
374
|
(local $ssoA i32) (local $ssoB i32)
|
|
375
|
+
(local $axA i32) (local $axB i32)
|
|
239
376
|
(if (i64.eq (local.get $a) (local.get $b))
|
|
240
377
|
(then (return (i32.const 1))))
|
|
241
378
|
(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 +388,19 @@ export default (ctx) => {
|
|
|
251
388
|
;; Both SSO with !bit-eq ⇒ content differs (high 32 bits hold tag+aux; both equal here).
|
|
252
389
|
(if (i32.and (local.get $ssoA) (local.get $ssoB))
|
|
253
390
|
(then (return (i32.const 0))))
|
|
391
|
+
;; Both CANONICAL interned heap strings (STR_INTERN_BIT, SSO/SLICE clear):
|
|
392
|
+
;; canonicals are deduped, so bit-ne ⇒ content-ne — the V8 pointer-compare
|
|
393
|
+
;; equivalent. This is the dominant unequal case in tag-dispatch chains
|
|
394
|
+
;; (op === 'literal' ladders over static literals).
|
|
395
|
+
(local.set $axA (i32.wrap_i64 (i64.shr_u (local.get $a) (i64.const ${LAYOUT.AUX_SHIFT}))))
|
|
396
|
+
(local.set $axB (i32.wrap_i64 (i64.shr_u (local.get $b) (i64.const ${LAYOUT.AUX_SHIFT}))))
|
|
397
|
+
(if (i32.and
|
|
398
|
+
(i32.and (i32.eq (local.get $ta) (i32.const ${PTR.STRING}))
|
|
399
|
+
(i32.eq (local.get $tb) (i32.const ${PTR.STRING})))
|
|
400
|
+
(i32.and
|
|
401
|
+
(i32.eq (i32.and (local.get $axA) (i32.const ${LAYOUT.SSO_BIT | LAYOUT.SLICE_BIT | STR_INTERN_BIT})) (i32.const ${STR_INTERN_BIT}))
|
|
402
|
+
(i32.eq (i32.and (local.get $axB) (i32.const ${LAYOUT.SSO_BIT | LAYOUT.SLICE_BIT | STR_INTERN_BIT})) (i32.const ${STR_INTERN_BIT}))))
|
|
403
|
+
(then (return (i32.const 0))))
|
|
254
404
|
;; Both heap STRING fast path: inline len from header. Chunk by 4 bytes via unaligned
|
|
255
405
|
;; i32.load (wasm guarantees unaligned-OK), then byte-tail. Most string comparisons fail
|
|
256
406
|
;; early on the first 4-byte word, so this collapses the per-byte branch overhead into a
|
|
@@ -296,7 +446,7 @@ export default (ctx) => {
|
|
|
296
446
|
(then (return (i32.const 0))))
|
|
297
447
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
298
448
|
(br $lm)))
|
|
299
|
-
(i32.const 1))`
|
|
449
|
+
(i32.const 1))`)
|
|
300
450
|
|
|
301
451
|
// Three-way byte-wise compare: -1 if a < b, 0 if equal, +1 if a > b. Returns
|
|
302
452
|
// i32 so callers can `i32.lt_s 0`, `i32.gt_s 0`, etc. without coercion.
|
|
@@ -305,7 +455,7 @@ export default (ctx) => {
|
|
|
305
455
|
// codepoint order for code points < 0x80 and well-formed strings. NOT locale-
|
|
306
456
|
// aware: this is the byte-wise variant suitable for sort-stability use cases,
|
|
307
457
|
// not human-language collation.
|
|
308
|
-
|
|
458
|
+
wat('__str_cmp', `(func $__str_cmp (param $a i64) (param $b i64) (result i32)
|
|
309
459
|
(local $lenA i32) (local $lenB i32) (local $minLen i32) (local $i i32)
|
|
310
460
|
(local $ca i32) (local $cb i32)
|
|
311
461
|
;; Bit-equal pointers (including same SSO inline form) ⇒ identical strings.
|
|
@@ -326,11 +476,11 @@ export default (ctx) => {
|
|
|
326
476
|
;; Common prefix matches — shorter string sorts first.
|
|
327
477
|
(if (i32.lt_s (local.get $lenA) (local.get $lenB)) (then (return (i32.const -1))))
|
|
328
478
|
(if (i32.gt_s (local.get $lenA) (local.get $lenB)) (then (return (i32.const 1))))
|
|
329
|
-
(i32.const 0))`
|
|
479
|
+
(i32.const 0))`)
|
|
330
480
|
|
|
331
481
|
// === WAT: unified byte length (SSO → aux low bits, heap → header) ===
|
|
332
482
|
|
|
333
|
-
|
|
483
|
+
wat('__str_byteLen', `(func $__str_byteLen (param $ptr i64) (result i32)
|
|
334
484
|
(local $t i32) (local $off i32) (local $aux i32)
|
|
335
485
|
(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
486
|
(if (result i32) (i32.eq (local.get $t) (i32.const ${PTR.STRING}))
|
|
@@ -349,15 +499,16 @@ export default (ctx) => {
|
|
|
349
499
|
(if (result i32) (i32.ge_u (local.get $off) (i32.const 4))
|
|
350
500
|
(then (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
351
501
|
(else (i32.const 0))))))))
|
|
352
|
-
(else (i32.const 0))))`
|
|
502
|
+
(else (i32.const 0))))`)
|
|
353
503
|
|
|
354
504
|
// === WAT: string methods ===
|
|
355
505
|
|
|
356
506
|
// SSO source uses an unrolled byte-extract loop (len ≤ 4); heap source uses memory.copy
|
|
357
507
|
// (single bulk op instead of nlen × __char_at).
|
|
358
|
-
|
|
508
|
+
wat('__str_slice', () => `(func $__str_slice (param $ptr i64) (param $start i32) (param $end i32) (result f64)
|
|
359
509
|
(local $len i32) (local $nlen i32) (local $off i32) (local $i i32)
|
|
360
|
-
(local $srcOff i32) (local $isSso i32)
|
|
510
|
+
(local $srcOff i32) (local $isSso i32) (local $sb i32) (local $sp i32)
|
|
511
|
+
(local $ip i32) (local $h i32) (local $j i32) (local $slot i32) (local $cand i32) (local $k i32)
|
|
361
512
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
362
513
|
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
363
514
|
(then (local.set $start (i32.add (local.get $len) (local.get $start)))))
|
|
@@ -374,6 +525,8 @@ export default (ctx) => {
|
|
|
374
525
|
(if (i32.ge_s (local.get $start) (local.get $end))
|
|
375
526
|
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
|
|
376
527
|
(local.set $nlen (i32.sub (local.get $end) (local.get $start)))
|
|
528
|
+
${sliceSsoPackWat()}
|
|
529
|
+
${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
530
|
(local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $nlen))))
|
|
378
531
|
(i32.store (local.get $off) (local.get $nlen))
|
|
379
532
|
(local.set $off (i32.add (local.get $off) (i32.const 4)))
|
|
@@ -393,7 +546,7 @@ export default (ctx) => {
|
|
|
393
546
|
(br $loop))))
|
|
394
547
|
(else
|
|
395
548
|
(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)))`
|
|
549
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`)
|
|
397
550
|
|
|
398
551
|
// No-copy slice: returns a VIEW into the receiver's buffer instead of copying
|
|
399
552
|
// bytes. Only emitted when escape analysis proves the result never outlives the
|
|
@@ -403,8 +556,9 @@ export default (ctx) => {
|
|
|
403
556
|
// (__str_slice) when the parent is SSO (no buffer to point into) or the result
|
|
404
557
|
// is longer than SLICE_LEN_MASK (aux can't hold the length). Clamping mirrors
|
|
405
558
|
// __str_slice; the fallback re-clamps idempotently.
|
|
406
|
-
|
|
559
|
+
wat('__str_slice_view', () => `(func $__str_slice_view (param $ptr i64) (param $start i32) (param $end i32) (result f64)
|
|
407
560
|
(local $len i32) (local $nlen i32) (local $srcOff i32) (local $tag i32)
|
|
561
|
+
(local $ip i32) (local $h i32) (local $j i32) (local $slot i32) (local $cand i32) (local $k i32)
|
|
408
562
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
409
563
|
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
410
564
|
(then (local.set $start (i32.add (local.get $len) (local.get $start)))))
|
|
@@ -422,22 +576,26 @@ export default (ctx) => {
|
|
|
422
576
|
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
|
|
423
577
|
(local.set $nlen (i32.sub (local.get $end) (local.get $start)))
|
|
424
578
|
(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].
|
|
579
|
+
;; View-eligible: STRING parent, not SSO, length fits aux[12:0]. ≤4-byte
|
|
580
|
+
;; results route to __str_slice's SSO pack instead (bit-equal short tokens).
|
|
426
581
|
(if (i32.and
|
|
427
582
|
(i32.and
|
|
428
|
-
(i32.
|
|
583
|
+
(i32.and
|
|
584
|
+
(i32.eq (local.get $tag) (i32.const ${PTR.STRING}))
|
|
585
|
+
(i32.gt_u (local.get $nlen) (i32.const 4)))
|
|
429
586
|
(i64.eqz (i64.and (local.get $ptr) (i64.const ${SSO_BIT_I64}))))
|
|
430
587
|
(i32.le_u (local.get $nlen) (i32.const ${LAYOUT.SLICE_LEN_MASK})))
|
|
431
588
|
(then
|
|
432
589
|
(local.set $srcOff (i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
590
|
+
${internProbeWat('(i32.add (local.get $srcOff) (local.get $start))')}
|
|
433
591
|
(return (call $__mkptr
|
|
434
592
|
(i32.const ${PTR.STRING})
|
|
435
593
|
(i32.or (i32.const ${LAYOUT.SLICE_BIT}) (local.get $nlen))
|
|
436
594
|
(i32.add (local.get $srcOff) (local.get $start))))))
|
|
437
595
|
;; 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)))`
|
|
596
|
+
(call $__str_slice (local.get $ptr) (local.get $start) (local.get $end)))`)
|
|
439
597
|
|
|
440
|
-
|
|
598
|
+
wat('__str_substring', `(func $__str_substring (param $ptr i64) (param $start i32) (param $end i32) (result f64)
|
|
441
599
|
(local $len i32) (local $tmp i32)
|
|
442
600
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
443
601
|
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
@@ -453,7 +611,7 @@ export default (ctx) => {
|
|
|
453
611
|
(local.set $tmp (local.get $start))
|
|
454
612
|
(local.set $start (local.get $end))
|
|
455
613
|
(local.set $end (local.get $tmp))))
|
|
456
|
-
(call $__str_slice (local.get $ptr) (local.get $start) (local.get $end)))`
|
|
614
|
+
(call $__str_slice (local.get $ptr) (local.get $start) (local.get $end)))`)
|
|
457
615
|
|
|
458
616
|
// === WAT: fused substring-equality ===
|
|
459
617
|
//
|
|
@@ -468,7 +626,7 @@ export default (ctx) => {
|
|
|
468
626
|
// returns one) and type-checks only `other`, mirroring __eq's STRING-vs-?
|
|
469
627
|
// arm: a genuine number never equals a string, and a NaN-boxed non-STRING
|
|
470
628
|
// never does either (jz `==` is strict).
|
|
471
|
-
|
|
629
|
+
wat('__str_range_eq', `(func $__str_range_eq (param $ptr i64) (param $start i32) (param $end i32) (param $other i64) (result i32)
|
|
472
630
|
(local $n i32) (local $i i32) (local $fb f64)
|
|
473
631
|
;; A genuine number reinterprets to a non-NaN f64 (equals itself) — never a string.
|
|
474
632
|
(local.set $fb (f64.reinterpret_i64 (local.get $other)))
|
|
@@ -492,10 +650,10 @@ export default (ctx) => {
|
|
|
492
650
|
(then (return (i32.const 0))))
|
|
493
651
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
494
652
|
(br $next)))
|
|
495
|
-
(i32.const 1))`
|
|
653
|
+
(i32.const 1))`)
|
|
496
654
|
|
|
497
655
|
// Clamp mirrors __str_substring (negatives floor to 0, swap when start>end).
|
|
498
|
-
|
|
656
|
+
wat('__str_substring_eq', `(func $__str_substring_eq (param $ptr i64) (param $start i32) (param $end i32) (param $other i64) (result i32)
|
|
499
657
|
(local $len i32) (local $tmp i32)
|
|
500
658
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
501
659
|
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
@@ -511,11 +669,11 @@ export default (ctx) => {
|
|
|
511
669
|
(local.set $tmp (local.get $start))
|
|
512
670
|
(local.set $start (local.get $end))
|
|
513
671
|
(local.set $end (local.get $tmp))))
|
|
514
|
-
(call $__str_range_eq (local.get $ptr) (local.get $start) (local.get $end) (local.get $other)))`
|
|
672
|
+
(call $__str_range_eq (local.get $ptr) (local.get $start) (local.get $end) (local.get $other)))`)
|
|
515
673
|
|
|
516
674
|
// Clamp mirrors __str_slice (negatives count from the end; __str_range_eq
|
|
517
675
|
// floors a negative span to an empty match).
|
|
518
|
-
|
|
676
|
+
wat('__str_slice_eq', `(func $__str_slice_eq (param $ptr i64) (param $start i32) (param $end i32) (param $other i64) (result i32)
|
|
519
677
|
(local $len i32)
|
|
520
678
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
521
679
|
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
@@ -530,16 +688,16 @@ export default (ctx) => {
|
|
|
530
688
|
(then (local.set $end (i32.const 0))))
|
|
531
689
|
(if (i32.gt_s (local.get $end) (local.get $len))
|
|
532
690
|
(then (local.set $end (local.get $len))))
|
|
533
|
-
(call $__str_range_eq (local.get $ptr) (local.get $start) (local.get $end) (local.get $other)))`
|
|
691
|
+
(call $__str_range_eq (local.get $ptr) (local.get $start) (local.get $end) (local.get $other)))`)
|
|
534
692
|
|
|
535
693
|
// Hoist SSO/heap dispatch for hay and ndl out of the inner byte loop. Inner
|
|
536
694
|
// loop becomes (load8_u OR sso byte-extract) per side — no per-byte calls.
|
|
537
|
-
|
|
695
|
+
wat('__str_indexof', `(func $__str_indexof (param $hay i64) (param $ndl i64) (param $from i32) (result i32)
|
|
538
696
|
(local $hlen i32) (local $nlen i32) (local $i i32) (local $j i32) (local $match i32)
|
|
539
697
|
(local $hoff i32) (local $noff i32)
|
|
540
698
|
(local $hsso i32) (local $nsso i32) (local $hb i32) (local $nb i32) (local $k i32)
|
|
541
|
-
;; ToString the
|
|
542
|
-
|
|
699
|
+
;; The search value is ToString'd at the call site (searchArg) per 21.1.3.9 step 4 —
|
|
700
|
+
;; a known-string needle passes raw, so this helper carries no float-pulling __to_str.
|
|
543
701
|
(local.set $hlen (call $__str_byteLen (local.get $hay)))
|
|
544
702
|
(local.set $nlen (call $__str_byteLen (local.get $ndl)))
|
|
545
703
|
(if (i32.eqz (local.get $nlen)) (then (return (local.get $from))))
|
|
@@ -573,10 +731,62 @@ export default (ctx) => {
|
|
|
573
731
|
(if (local.get $match) (then (return (local.get $i))))
|
|
574
732
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
575
733
|
(br $outer)))
|
|
576
|
-
(i32.const -1))`
|
|
734
|
+
(i32.const -1))`)
|
|
735
|
+
|
|
736
|
+
// Mirror of __str_indexof but searches from the end. Returns the last byte-offset
|
|
737
|
+
// of `ndl` in `hay` at or before `from` (-1 if not found). Per JS spec step 4,
|
|
738
|
+
// ToString(searchValue) is applied first. SSO/heap dispatch hoisted exactly as
|
|
739
|
+
// in __str_indexof so the inner loop is cheap byte-fetches.
|
|
740
|
+
wat('__str_lastindexof', `(func $__str_lastindexof (param $hay i64) (param $ndl i64) (param $from i32) (result i32)
|
|
741
|
+
(local $hlen i32) (local $nlen i32) (local $i i32) (local $j i32) (local $match i32)
|
|
742
|
+
(local $hoff i32) (local $noff i32)
|
|
743
|
+
(local $hsso i32) (local $nsso i32) (local $hb i32) (local $nb i32) (local $k i32)
|
|
744
|
+
;; The search value is ToString'd at the call site (searchArg) per 21.1.3.10 step 4.
|
|
745
|
+
(local.set $hlen (call $__str_byteLen (local.get $hay)))
|
|
746
|
+
(local.set $nlen (call $__str_byteLen (local.get $ndl)))
|
|
747
|
+
;; Empty needle always matches at the clamp(from,0,hlen) position
|
|
748
|
+
(if (i32.eqz (local.get $nlen))
|
|
749
|
+
(then (return (select
|
|
750
|
+
(select (local.get $from) (local.get $hlen) (i32.lt_s (local.get $from) (local.get $hlen)))
|
|
751
|
+
(i32.const 0)
|
|
752
|
+
(i32.ge_s (local.get $from) (i32.const 0))))))
|
|
753
|
+
(if (i32.gt_s (local.get $nlen) (local.get $hlen)) (then (return (i32.const -1))))
|
|
754
|
+
(local.set $hoff (i32.wrap_i64 (i64.and (local.get $hay) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
755
|
+
(local.set $noff (i32.wrap_i64 (i64.and (local.get $ndl) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
756
|
+
(local.set $hsso (i32.and
|
|
757
|
+
(i32.wrap_i64 (i64.shr_u (local.get $hay) (i64.const ${LAYOUT.AUX_SHIFT})))
|
|
758
|
+
(i32.const ${LAYOUT.SSO_BIT})))
|
|
759
|
+
(local.set $nsso (i32.and
|
|
760
|
+
(i32.wrap_i64 (i64.shr_u (local.get $ndl) (i64.const ${LAYOUT.AUX_SHIFT})))
|
|
761
|
+
(i32.const ${LAYOUT.SSO_BIT})))
|
|
762
|
+
;; clamp start position: from defaults to hlen-nlen, capped at that ceiling
|
|
763
|
+
(local.set $i (i32.sub (local.get $hlen) (local.get $nlen)))
|
|
764
|
+
(if (i32.and (i32.ge_s (local.get $from) (i32.const 0)) (i32.lt_s (local.get $from) (local.get $i)))
|
|
765
|
+
(then (local.set $i (local.get $from))))
|
|
766
|
+
(block $done (loop $outer
|
|
767
|
+
(br_if $done (i32.lt_s (local.get $i) (i32.const 0)))
|
|
768
|
+
(local.set $match (i32.const 1))
|
|
769
|
+
(local.set $j (i32.const 0))
|
|
770
|
+
(block $nomatch (loop $inner
|
|
771
|
+
(br_if $nomatch (i32.ge_s (local.get $j) (local.get $nlen)))
|
|
772
|
+
(local.set $k (i32.add (local.get $i) (local.get $j)))
|
|
773
|
+
(local.set $hb (if (result i32) (local.get $hsso)
|
|
774
|
+
(then (i32.and (i32.shr_u (local.get $hoff) (i32.shl (local.get $k) (i32.const 3))) (i32.const 0xFF)))
|
|
775
|
+
(else (i32.load8_u (i32.add (local.get $hoff) (local.get $k))))))
|
|
776
|
+
(local.set $nb (if (result i32) (local.get $nsso)
|
|
777
|
+
(then (i32.and (i32.shr_u (local.get $noff) (i32.shl (local.get $j) (i32.const 3))) (i32.const 0xFF)))
|
|
778
|
+
(else (i32.load8_u (i32.add (local.get $noff) (local.get $j))))))
|
|
779
|
+
(if (i32.ne (local.get $hb) (local.get $nb))
|
|
780
|
+
(then (local.set $match (i32.const 0)) (br $nomatch)))
|
|
781
|
+
(local.set $j (i32.add (local.get $j) (i32.const 1)))
|
|
782
|
+
(br $inner)))
|
|
783
|
+
(if (local.get $match) (then (return (local.get $i))))
|
|
784
|
+
(local.set $i (i32.sub (local.get $i) (i32.const 1)))
|
|
785
|
+
(br $outer)))
|
|
786
|
+
(i32.const -1))`)
|
|
577
787
|
|
|
578
788
|
// SSO/heap dispatch hoisted; inner loop is two inlined byte-fetches and a compare.
|
|
579
|
-
|
|
789
|
+
wat('__str_startswith', `(func $__str_startswith (param $str i64) (param $pfx i64) (result i32)
|
|
580
790
|
(local $plen i32) (local $i i32)
|
|
581
791
|
(local $soff i32) (local $poff i32) (local $ssso i32) (local $psso i32)
|
|
582
792
|
(local.set $plen (call $__str_byteLen (local.get $pfx)))
|
|
@@ -602,9 +812,9 @@ export default (ctx) => {
|
|
|
602
812
|
(then (return (i32.const 0))))
|
|
603
813
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
604
814
|
(br $loop)))
|
|
605
|
-
(i32.const 1))`
|
|
815
|
+
(i32.const 1))`)
|
|
606
816
|
|
|
607
|
-
|
|
817
|
+
wat('__str_endswith', `(func $__str_endswith (param $str i64) (param $sfx i64) (result i32)
|
|
608
818
|
(local $slen i32) (local $flen i32) (local $off i32) (local $i i32) (local $k i32)
|
|
609
819
|
(local $soff i32) (local $foff i32) (local $ssso i32) (local $fsso i32)
|
|
610
820
|
(local.set $slen (call $__str_byteLen (local.get $str)))
|
|
@@ -633,10 +843,10 @@ export default (ctx) => {
|
|
|
633
843
|
(then (return (i32.const 0))))
|
|
634
844
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
635
845
|
(br $loop)))
|
|
636
|
-
(i32.const 1))`
|
|
846
|
+
(i32.const 1))`)
|
|
637
847
|
|
|
638
848
|
// Source SSO/heap dispatch hoisted out of the byte loop (was a per-byte __char_at).
|
|
639
|
-
|
|
849
|
+
wat('__str_case', `(func $__str_case (param $ptr i64) (param $lo i32) (param $hi i32) (param $delta i32) (result f64)
|
|
640
850
|
(local $len i32) (local $off i32) (local $i i32) (local $c i32)
|
|
641
851
|
(local $srcOff i32)
|
|
642
852
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
@@ -667,9 +877,9 @@ export default (ctx) => {
|
|
|
667
877
|
(i32.store8 (i32.add (local.get $off) (local.get $i)) (local.get $c))
|
|
668
878
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
669
879
|
(br $lh)))))
|
|
670
|
-
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`
|
|
880
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`)
|
|
671
881
|
|
|
672
|
-
|
|
882
|
+
wat('__str_trim', `(func $__str_trim (param $ptr i64) (result f64)
|
|
673
883
|
(local $len i32) (local $start i32) (local $end i32)
|
|
674
884
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
675
885
|
(local.set $start (i32.const 0))
|
|
@@ -684,9 +894,9 @@ export default (ctx) => {
|
|
|
684
894
|
(br_if $d2 (i32.gt_u (call $__char_at (local.get $ptr) (i32.sub (local.get $end) (i32.const 1))) (i32.const 32)))
|
|
685
895
|
(local.set $end (i32.sub (local.get $end) (i32.const 1)))
|
|
686
896
|
(br $l2)))
|
|
687
|
-
(call $__str_slice (local.get $ptr) (local.get $start) (local.get $end)))`
|
|
897
|
+
(call $__str_slice (local.get $ptr) (local.get $start) (local.get $end)))`)
|
|
688
898
|
|
|
689
|
-
|
|
899
|
+
wat('__str_trimStart', `(func $__str_trimStart (param $ptr i64) (result f64)
|
|
690
900
|
(local $len i32) (local $start i32)
|
|
691
901
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
692
902
|
(local.set $start (i32.const 0))
|
|
@@ -695,9 +905,9 @@ export default (ctx) => {
|
|
|
695
905
|
(br_if $done (i32.gt_u (call $__char_at (local.get $ptr) (local.get $start)) (i32.const 32)))
|
|
696
906
|
(local.set $start (i32.add (local.get $start) (i32.const 1)))
|
|
697
907
|
(br $loop)))
|
|
698
|
-
(call $__str_slice (local.get $ptr) (local.get $start) (local.get $len)))`
|
|
908
|
+
(call $__str_slice (local.get $ptr) (local.get $start) (local.get $len)))`)
|
|
699
909
|
|
|
700
|
-
|
|
910
|
+
wat('__str_trimEnd', `(func $__str_trimEnd (param $ptr i64) (result f64)
|
|
701
911
|
(local $len i32) (local $end i32)
|
|
702
912
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
703
913
|
(local.set $end (local.get $len))
|
|
@@ -706,11 +916,11 @@ export default (ctx) => {
|
|
|
706
916
|
(br_if $done (i32.gt_u (call $__char_at (local.get $ptr) (i32.sub (local.get $end) (i32.const 1))) (i32.const 32)))
|
|
707
917
|
(local.set $end (i32.sub (local.get $end) (i32.const 1)))
|
|
708
918
|
(br $loop)))
|
|
709
|
-
(call $__str_slice (local.get $ptr) (i32.const 0) (local.get $end)))`
|
|
919
|
+
(call $__str_slice (local.get $ptr) (i32.const 0) (local.get $end)))`)
|
|
710
920
|
|
|
711
921
|
// Materialize source bytes once via __str_copy (handles SSO/heap), then memory.copy
|
|
712
922
|
// each subsequent repetition (single bulk op vs len byte stores per copy).
|
|
713
|
-
|
|
923
|
+
wat('__str_repeat', `(func $__str_repeat (param $ptr i64) (param $n i32) (result f64)
|
|
714
924
|
(local $len i32) (local $total i32) (local $off i32) (local $i i32)
|
|
715
925
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
716
926
|
(if (i32.or (i32.eqz (local.get $n)) (i32.eqz (local.get $len)))
|
|
@@ -729,11 +939,11 @@ export default (ctx) => {
|
|
|
729
939
|
(local.get $len))
|
|
730
940
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
731
941
|
(br $loop)))
|
|
732
|
-
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`
|
|
942
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`)
|
|
733
943
|
|
|
734
944
|
// Coerce value to string: numbers → __ftoa, nullish → static strings,
|
|
735
945
|
// plain NaN → "NaN", arrays → join(","), other string-like pointers pass through.
|
|
736
|
-
|
|
946
|
+
wat('__to_str', `(func $__to_str (param $val i64) (result i64)
|
|
737
947
|
(local $type i32) (local $f f64)
|
|
738
948
|
(local.set $f (f64.reinterpret_i64 (local.get $val)))
|
|
739
949
|
;; Not NaN → number, convert
|
|
@@ -743,6 +953,10 @@ export default (ctx) => {
|
|
|
743
953
|
(then (return (i64.reinterpret_f64 (call $__static_str (i32.const 5))))))
|
|
744
954
|
(if (i64.eq (local.get $val) (i64.const ${UNDEF_NAN}))
|
|
745
955
|
(then (return (i64.reinterpret_f64 (call $__static_str (i32.const 6))))))
|
|
956
|
+
(if (i64.eq (local.get $val) (i64.const ${FALSE_NAN}))
|
|
957
|
+
(then (return (i64.reinterpret_f64 (call $__static_str (i32.const 4))))))
|
|
958
|
+
(if (i64.eq (local.get $val) (i64.const ${TRUE_NAN}))
|
|
959
|
+
(then (return (i64.reinterpret_f64 (call $__static_str (i32.const 3))))))
|
|
746
960
|
(local.set $type (call $__ptr_type (local.get $val)))
|
|
747
961
|
;; Plain NaN (type=0) → "NaN" string
|
|
748
962
|
(if (i32.eqz (local.get $type))
|
|
@@ -751,11 +965,11 @@ export default (ctx) => {
|
|
|
751
965
|
(if (i32.eq (local.get $type) (i32.const ${PTR.ARRAY}))
|
|
752
966
|
(then (return (i64.reinterpret_f64 (call $__str_join (local.get $val)
|
|
753
967
|
(i64.reinterpret_f64 (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT | 1}) (i32.const 44))))))))
|
|
754
|
-
(local.get $val))`
|
|
968
|
+
(local.get $val))`)
|
|
755
969
|
|
|
756
970
|
// Copy bytes of a string (SSO or heap) into memory at dst. Uses memory.copy for
|
|
757
971
|
// heap strings (single native op); unpacks SSO offset-packed bytes inline.
|
|
758
|
-
|
|
972
|
+
wat('__str_copy', `(func $__str_copy (param $src i64) (param $dst i32) (param $len i32)
|
|
759
973
|
(local $w i32)
|
|
760
974
|
(if (i64.ne (i64.and (local.get $src) (i64.const ${SSO_BIT_I64})) (i64.const 0))
|
|
761
975
|
(then
|
|
@@ -775,7 +989,7 @@ export default (ctx) => {
|
|
|
775
989
|
;; Heap STRING: memory.copy directly from string data
|
|
776
990
|
(memory.copy (local.get $dst)
|
|
777
991
|
(i32.wrap_i64 (i64.and (local.get $src) (i64.const ${LAYOUT.OFFSET_MASK})))
|
|
778
|
-
(local.get $len)))))`
|
|
992
|
+
(local.get $len)))))`)
|
|
779
993
|
|
|
780
994
|
// Bump-extend fast path: when `a` is a heap STRING sitting at the top of the
|
|
781
995
|
// bump allocator, extend its allocation in place instead of copying. Mutates
|
|
@@ -826,10 +1040,7 @@ export default (ctx) => {
|
|
|
826
1040
|
(then
|
|
827
1041
|
(local.set $newHeap
|
|
828
1042
|
(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)))))
|
|
1043
|
+
(call $__memgrow (local.get $newHeap))
|
|
833
1044
|
(call $__str_copy (local.get $b)
|
|
834
1045
|
(i32.add (local.get $aoff) (local.get $alen))
|
|
835
1046
|
(local.get $blen))
|
|
@@ -841,7 +1052,7 @@ export default (ctx) => {
|
|
|
841
1052
|
// VAL.STRING and the rhs is a string-index. Skips __str_idx's 1-char SSO
|
|
842
1053
|
// construction and __str_concat's type-dispatch — byte goes directly from
|
|
843
1054
|
// __char_at to memory. Bump-extends in place when `a` is at heap top.
|
|
844
|
-
|
|
1055
|
+
wat('__str_append_byte', `(func $__str_append_byte (param $a i64) (param $byte i32) (result f64)
|
|
845
1056
|
(local $ta i32) (local $aoff i32) (local $alen i32)
|
|
846
1057
|
(local $newHeap i32) (local $off i32) (local $total i32)
|
|
847
1058
|
(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 +1074,7 @@ export default (ctx) => {
|
|
|
863
1074
|
(then
|
|
864
1075
|
(local.set $newHeap
|
|
865
1076
|
(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)))))
|
|
1077
|
+
(call $__memgrow (local.get $newHeap))
|
|
870
1078
|
(i32.store8 (i32.add (local.get $aoff) (local.get $alen)) (local.get $byte))
|
|
871
1079
|
(i32.store (i32.sub (local.get $aoff) (i32.const 4)) (i32.add (local.get $alen) (i32.const 1)))
|
|
872
1080
|
(global.set $__heap (local.get $newHeap))
|
|
@@ -899,9 +1107,9 @@ export default (ctx) => {
|
|
|
899
1107
|
(local.set $off (i32.add (local.get $off) (i32.const 4)))
|
|
900
1108
|
(call $__str_copy (local.get $a) (local.get $off) (local.get $alen))
|
|
901
1109
|
(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)))`
|
|
1110
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`)
|
|
903
1111
|
|
|
904
|
-
|
|
1112
|
+
wat('__str_concat', `(func $__str_concat (param $a i64) (param $b i64) (result f64)
|
|
905
1113
|
(local $alen i32) (local $blen i32) (local $total i32) (local $off i32)
|
|
906
1114
|
(local $ta i32) (local $aoff i32) (local $newHeap i32)
|
|
907
1115
|
;; Coerce operands to strings if needed
|
|
@@ -919,9 +1127,9 @@ export default (ctx) => {
|
|
|
919
1127
|
(local.set $off (i32.add (local.get $off) (i32.const 4)))
|
|
920
1128
|
(call $__str_copy (local.get $a) (local.get $off) (local.get $alen))
|
|
921
1129
|
(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)))`
|
|
1130
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`)
|
|
923
1131
|
|
|
924
|
-
|
|
1132
|
+
wat('__str_concat_raw', `(func $__str_concat_raw (param $a i64) (param $b i64) (result f64)
|
|
925
1133
|
(local $alen i32) (local $blen i32) (local $total i32) (local $off i32)
|
|
926
1134
|
(local $ta i32) (local $aoff i32) (local $newHeap i32)
|
|
927
1135
|
(local.set $alen (call $__str_byteLen (local.get $a)))
|
|
@@ -936,9 +1144,9 @@ export default (ctx) => {
|
|
|
936
1144
|
(local.set $off (i32.add (local.get $off) (i32.const 4)))
|
|
937
1145
|
(call $__str_copy (local.get $a) (local.get $off) (local.get $alen))
|
|
938
1146
|
(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)))`
|
|
1147
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`)
|
|
940
1148
|
|
|
941
|
-
|
|
1149
|
+
wat('__str_replace', `(func $__str_replace (param $str i64) (param $search i64) (param $repl i64) (result f64)
|
|
942
1150
|
(local $idx i32) (local $slen i32)
|
|
943
1151
|
(local.set $idx (call $__str_indexof (local.get $str) (local.get $search) (i32.const 0)))
|
|
944
1152
|
(if (result f64) (i32.lt_s (local.get $idx) (i32.const 0))
|
|
@@ -950,9 +1158,9 @@ export default (ctx) => {
|
|
|
950
1158
|
(i64.reinterpret_f64 (call $__str_slice (local.get $str) (i32.const 0) (local.get $idx)))
|
|
951
1159
|
(local.get $repl)))
|
|
952
1160
|
(i64.reinterpret_f64 (call $__str_slice (local.get $str) (i32.add (local.get $idx) (local.get $slen))
|
|
953
|
-
(call $__str_byteLen (local.get $str))))))))`
|
|
1161
|
+
(call $__str_byteLen (local.get $str))))))))`)
|
|
954
1162
|
|
|
955
|
-
|
|
1163
|
+
wat('__str_replaceall', `(func $__str_replaceall (param $str i64) (param $search i64) (param $repl i64) (result f64)
|
|
956
1164
|
(local $idx i32) (local $slen i32) (local $pos i32) (local $result i64)
|
|
957
1165
|
(local.set $slen (call $__str_byteLen (local.get $search)))
|
|
958
1166
|
(local.set $result (local.get $str))
|
|
@@ -968,33 +1176,48 @@ export default (ctx) => {
|
|
|
968
1176
|
(call $__str_byteLen (local.get $result)))))))
|
|
969
1177
|
(local.set $pos (i32.add (local.get $idx) (call $__str_byteLen (local.get $repl))))
|
|
970
1178
|
(br $next)))
|
|
971
|
-
(f64.reinterpret_i64 (local.get $result)))`
|
|
972
|
-
|
|
973
|
-
//
|
|
974
|
-
// (
|
|
975
|
-
//
|
|
976
|
-
|
|
1179
|
+
(f64.reinterpret_i64 (local.get $result)))`)
|
|
1180
|
+
|
|
1181
|
+
// $limit ≥ 0: honour JS's optional limit arg. 0x7fffffff = "no limit"
|
|
1182
|
+
// (sentinel passed by the no-limit call site). limit=0 → []. limit=N → at
|
|
1183
|
+
// most N elements; the (N+1)th and later pieces are DISCARDED (not appended
|
|
1184
|
+
// as a remainder). Empty separator: split into individual byte-chars, up to
|
|
1185
|
+
// $limit chars ("abc".split("") → ["a","b","c"], "".split("") → []).
|
|
1186
|
+
wat('__str_split', `(func $__str_split (param $str i64) (param $sep i64) (param $limit i32) (result f64)
|
|
977
1187
|
(local $slen i32) (local $plen i32) (local $count i32)
|
|
978
1188
|
(local $i i32) (local $j i32) (local $match i32)
|
|
979
|
-
(local $arr i32) (local $piece_start i32) (local $piece_idx i32)
|
|
1189
|
+
(local $arr i32) (local $piece_start i32) (local $piece_idx i32) (local $hitlim i32)
|
|
980
1190
|
(local.set $slen (call $__str_byteLen (local.get $str)))
|
|
981
1191
|
(local.set $plen (call $__str_byteLen (local.get $sep)))
|
|
1192
|
+
;; limit=0 → empty array
|
|
1193
|
+
(if (i32.eqz (local.get $limit)) (then
|
|
1194
|
+
(local.set $arr (call $__alloc (i32.const 8)))
|
|
1195
|
+
(i32.store (local.get $arr) (i32.const 0))
|
|
1196
|
+
(i32.store (i32.add (local.get $arr) (i32.const 4)) (i32.const 0))
|
|
1197
|
+
(return (call $__mkptr (i32.const 1) (i32.const 0) (i32.add (local.get $arr) (i32.const 8))))))
|
|
982
1198
|
(if (i32.eqz (local.get $plen)) (then
|
|
983
|
-
|
|
984
|
-
(i32.
|
|
985
|
-
(
|
|
1199
|
+
;; Empty-separator: split into individual byte-chars, up to $limit
|
|
1200
|
+
(local.set $count (select (local.get $limit) (local.get $slen) (i32.lt_u (local.get $limit) (local.get $slen))))
|
|
1201
|
+
(local.set $arr (call $__alloc (i32.add (i32.const 8) (i32.shl (local.get $count) (i32.const 3)))))
|
|
1202
|
+
(i32.store (local.get $arr) (local.get $count))
|
|
1203
|
+
(i32.store (i32.add (local.get $arr) (i32.const 4)) (local.get $count))
|
|
986
1204
|
(local.set $arr (i32.add (local.get $arr) (i32.const 8)))
|
|
987
1205
|
(block $de (loop $le
|
|
988
|
-
(br_if $de (i32.ge_s (local.get $i) (local.get $
|
|
1206
|
+
(br_if $de (i32.ge_s (local.get $i) (local.get $count)))
|
|
989
1207
|
(f64.store (i32.add (local.get $arr) (i32.shl (local.get $i) (i32.const 3)))
|
|
990
1208
|
(call $__str_slice (local.get $str) (local.get $i) (i32.add (local.get $i) (i32.const 1))))
|
|
991
1209
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
992
1210
|
(br $le)))
|
|
993
1211
|
(return (call $__mkptr (i32.const 1) (i32.const 0) (local.get $arr)))))
|
|
1212
|
+
;; Count pass: tally pieces = separators+1, capped at $limit.
|
|
1213
|
+
;; We stop incrementing count once count reaches $limit (last sep found
|
|
1214
|
+
;; at that point produces piece #limit — a separator *after* piece limit-1,
|
|
1215
|
+
;; meaning we've already found all limit pieces and don't count more).
|
|
994
1216
|
(local.set $count (i32.const 1))
|
|
995
1217
|
(local.set $i (i32.const 0))
|
|
996
1218
|
(block $d1 (loop $l1
|
|
997
1219
|
(br_if $d1 (i32.gt_s (local.get $i) (i32.sub (local.get $slen) (local.get $plen))))
|
|
1220
|
+
(br_if $d1 (i32.ge_u (local.get $count) (local.get $limit)))
|
|
998
1221
|
(local.set $match (i32.const 1))
|
|
999
1222
|
(local.set $j (i32.const 0))
|
|
1000
1223
|
(block $n1 (loop $c1
|
|
@@ -1017,6 +1240,11 @@ export default (ctx) => {
|
|
|
1017
1240
|
(local.set $piece_start (i32.const 0))
|
|
1018
1241
|
(local.set $piece_idx (i32.const 0))
|
|
1019
1242
|
(local.set $i (i32.const 0))
|
|
1243
|
+
;; Fill pass: write pieces separated by $sep, up to $count total pieces.
|
|
1244
|
+
;; When a separator is found and piece_idx+1 reaches count, write that
|
|
1245
|
+
;; piece (before the sep) and exit WITHOUT appending the tail — this
|
|
1246
|
+
;; correctly discards the remainder when $limit truncates the result.
|
|
1247
|
+
(local.set $hitlim (i32.const 0))
|
|
1020
1248
|
(block $d2 (loop $l2
|
|
1021
1249
|
(br_if $d2 (i32.gt_s (local.get $i) (i32.sub (local.get $slen) (local.get $plen))))
|
|
1022
1250
|
(local.set $match (i32.const 1))
|
|
@@ -1034,14 +1262,27 @@ export default (ctx) => {
|
|
|
1034
1262
|
(local.set $piece_idx (i32.add (local.get $piece_idx) (i32.const 1)))
|
|
1035
1263
|
(local.set $i (i32.add (local.get $i) (local.get $plen)))
|
|
1036
1264
|
(local.set $piece_start (local.get $i))
|
|
1265
|
+
;; If we've emitted all $count pieces, mark limit-hit and stop (discard tail)
|
|
1266
|
+
(if (i32.ge_s (local.get $piece_idx) (local.get $count))
|
|
1267
|
+
(then (local.set $hitlim (i32.const 1)) (br $d2)))
|
|
1037
1268
|
(br $l2)))
|
|
1038
1269
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
1039
1270
|
(br $l2)))
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1271
|
+
;; Write tail only when the scan ended naturally (not truncated by limit)
|
|
1272
|
+
(if (i32.eqz (local.get $hitlim)) (then
|
|
1273
|
+
(f64.store (i32.add (local.get $arr) (i32.shl (local.get $piece_idx) (i32.const 3)))
|
|
1274
|
+
(call $__str_slice (local.get $str) (local.get $piece_start) (local.get $slen)))))
|
|
1275
|
+
(call $__mkptr (i32.const 1) (i32.const 0) (local.get $arr)))`)
|
|
1276
|
+
|
|
1277
|
+
// Array (type=1) → join(",") like JS Array.toString().
|
|
1278
|
+
// When the typedarray module is loaded, also handles PTR.TYPED (type=3) arrays:
|
|
1279
|
+
// promoteIntArrayLiterals rewrites [int,...] → new Int32Array([...]) internally,
|
|
1280
|
+
// so a.map(fn).join() may receive a PTR.TYPED result. Use __typed_idx to load
|
|
1281
|
+
// each element correctly (it returns f64 for any element type / stride).
|
|
1282
|
+
wat('__str_join', () => {
|
|
1283
|
+
if (!ctx.module.modules.typedarray) {
|
|
1284
|
+
// ARRAY-only fast path — no __typed_idx overhead.
|
|
1285
|
+
return `(func $__str_join (param $arr i64) (param $sep i64) (result f64)
|
|
1045
1286
|
(local $off i32) (local $len i32) (local $i i32) (local $result f64)
|
|
1046
1287
|
(local.set $off (call $__ptr_offset (local.get $arr)))
|
|
1047
1288
|
(local.set $len (call $__len (local.get $arr)))
|
|
@@ -1057,13 +1298,48 @@ export default (ctx) => {
|
|
|
1057
1298
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
1058
1299
|
(br $loop)))
|
|
1059
1300
|
(local.get $result))`
|
|
1301
|
+
}
|
|
1302
|
+
// ARRAY + TYPED path: runtime dispatch on ptr type.
|
|
1303
|
+
// PTR.TYPED (type=3): elements have typed-array stride; __typed_idx reads correctly.
|
|
1304
|
+
// PTR.ARRAY (type=1): elements are 8-byte NaN-boxed f64 slots; i64.load is correct.
|
|
1305
|
+
return `(func $__str_join (param $arr i64) (param $sep i64) (result f64)
|
|
1306
|
+
(local $off i32) (local $len i32) (local $i i32) (local $result f64) (local $isTyped i32)
|
|
1307
|
+
(local.set $isTyped
|
|
1308
|
+
(i32.eq
|
|
1309
|
+
(i32.and (i32.wrap_i64 (i64.shr_u (local.get $arr) (i64.const ${LAYOUT.TAG_SHIFT})))
|
|
1310
|
+
(i32.const ${LAYOUT.TAG_MASK}))
|
|
1311
|
+
(i32.const ${PTR.TYPED})))
|
|
1312
|
+
(local.set $off (call $__ptr_offset (local.get $arr)))
|
|
1313
|
+
(local.set $len (call $__len (local.get $arr)))
|
|
1314
|
+
(if (i32.eqz (local.get $len))
|
|
1315
|
+
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
|
|
1316
|
+
(local.set $result
|
|
1317
|
+
(f64.reinterpret_i64
|
|
1318
|
+
(call $__to_str
|
|
1319
|
+
(if (result i64) (local.get $isTyped)
|
|
1320
|
+
(then (i64.reinterpret_f64 (call $__typed_idx (local.get $arr) (i32.const 0))))
|
|
1321
|
+
(else (i64.load (local.get $off)))))))
|
|
1322
|
+
(local.set $i (i32.const 1))
|
|
1323
|
+
(block $done (loop $loop
|
|
1324
|
+
(br_if $done (i32.ge_s (local.get $i) (local.get $len)))
|
|
1325
|
+
(local.set $result (call $__str_concat (i64.reinterpret_f64 (local.get $result)) (local.get $sep)))
|
|
1326
|
+
(local.set $result
|
|
1327
|
+
(call $__str_concat
|
|
1328
|
+
(i64.reinterpret_f64 (local.get $result))
|
|
1329
|
+
(if (result i64) (local.get $isTyped)
|
|
1330
|
+
(then (i64.reinterpret_f64 (call $__typed_idx (local.get $arr) (local.get $i))))
|
|
1331
|
+
(else (i64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3))))))))
|
|
1332
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
1333
|
+
(br $loop)))
|
|
1334
|
+
(local.get $result))`
|
|
1335
|
+
})
|
|
1060
1336
|
|
|
1061
1337
|
// Source string copied via __str_copy (handles SSO/heap with memory.copy where possible).
|
|
1062
1338
|
// Pad fill loops a single tile of pad bytes — hoist pad dispatch out of the byte loop.
|
|
1063
|
-
|
|
1339
|
+
wat('__str_pad', `(func $__str_pad (param $str i64) (param $target i32) (param $pad i64) (param $before i32) (result f64)
|
|
1064
1340
|
(local $slen i32) (local $plen i32) (local $fill i32) (local $off i32) (local $i i32)
|
|
1065
1341
|
(local $str_off i32) (local $pad_off i32)
|
|
1066
|
-
(local $pbits i64) (local $poff i32) (local $psso i32)
|
|
1342
|
+
(local $pbits i64) (local $poff i32) (local $psso i32) (local $sp i32) (local $sb i32)
|
|
1067
1343
|
(local.set $slen (call $__str_byteLen (local.get $str)))
|
|
1068
1344
|
(if (i32.ge_s (local.get $slen) (local.get $target))
|
|
1069
1345
|
(then (return (f64.reinterpret_i64 (local.get $str)))))
|
|
@@ -1090,7 +1366,25 @@ export default (ctx) => {
|
|
|
1090
1366
|
(else (i32.load8_u (i32.add (local.get $poff) (i32.rem_u (local.get $i) (local.get $plen)))))))
|
|
1091
1367
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
1092
1368
|
(br $l2)))
|
|
1093
|
-
|
|
1369
|
+
${!ctx.memory.shared ? `
|
|
1370
|
+
;; SSO + reclaim for ≤4 ASCII results: pack into the pointer and free this pad
|
|
1371
|
+
;; allocation (at heap top) so padStart in a builder loop is allocation-neutral
|
|
1372
|
+
;; — the accumulator stays at heap top and keeps bump-extending (O(n)).
|
|
1373
|
+
(if (i32.le_u (local.get $target) (i32.const 4))
|
|
1374
|
+
(then
|
|
1375
|
+
(block $heap
|
|
1376
|
+
(local.set $i (i32.const 0)) (local.set $sp (i32.const 0))
|
|
1377
|
+
(loop $pk
|
|
1378
|
+
(if (i32.lt_u (local.get $i) (local.get $target))
|
|
1379
|
+
(then
|
|
1380
|
+
(local.set $sb (i32.load8_u (i32.add (local.get $off) (local.get $i))))
|
|
1381
|
+
(br_if $heap (i32.ge_u (local.get $sb) (i32.const 0x80)))
|
|
1382
|
+
(local.set $sp (i32.or (local.get $sp) (i32.shl (local.get $sb) (i32.shl (local.get $i) (i32.const 3)))))
|
|
1383
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
1384
|
+
(br $pk))))
|
|
1385
|
+
(global.set $__heap (i32.sub (local.get $off) (i32.const 4)))
|
|
1386
|
+
(return (call $__mkptr (i32.const ${PTR.STRING}) (i32.or (i32.const ${LAYOUT.SSO_BIT}) (local.get $target)) (local.get $sp))))))` : ''}
|
|
1387
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`)
|
|
1094
1388
|
|
|
1095
1389
|
// Base helpers (__sso_char/__str_char/__char_at/__str_byteLen) are referenced
|
|
1096
1390
|
// from other helpers' WAT bodies and from emit sites; their `stdlibDeps`
|
|
@@ -1103,16 +1397,16 @@ export default (ctx) => {
|
|
|
1103
1397
|
// (21.1.3.27/28). Typed forms cover the static-string case; generic forms
|
|
1104
1398
|
// pair with them so the dispatcher can pick a runtime ptr-type branch when
|
|
1105
1399
|
// the receiver type can't be statically inferred (e.g. a callback param).
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1400
|
+
bind('.string:toString', (str) => asF64(emit(str)))
|
|
1401
|
+
bind('.string:valueOf', (str) => asF64(emit(str)))
|
|
1402
|
+
bind('.toString', (val) => {
|
|
1109
1403
|
inc('__to_str')
|
|
1110
1404
|
return typed(['f64.reinterpret_i64', ['call', '$__to_str', asI64(emit(val))]], 'f64')
|
|
1111
|
-
}
|
|
1405
|
+
})
|
|
1112
1406
|
// Object.prototype.valueOf returns the receiver (per ES2024 20.1.3.7).
|
|
1113
1407
|
// Array/Object inherit this; only primitive wrappers (Number/Boolean/String)
|
|
1114
1408
|
// override to return the primitive — strings already covered by .string:valueOf.
|
|
1115
|
-
|
|
1409
|
+
bind('.valueOf', (val) => asF64(emit(val)))
|
|
1116
1410
|
|
|
1117
1411
|
// `.slice` lowering, parametrised on the backing helper: __str_slice copies
|
|
1118
1412
|
// bytes; __str_slice_view returns a no-copy SLICE_BIT view — used only when
|
|
@@ -1130,8 +1424,8 @@ export default (ctx) => {
|
|
|
1130
1424
|
['call', `$${fn}`, ['i64.reinterpret_f64', ['local.get', `$${t}`]], startIR,
|
|
1131
1425
|
['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]]], 'f64')
|
|
1132
1426
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1427
|
+
bind('.string:slice', sliceEmitter('__str_slice'))
|
|
1428
|
+
bind('.string:slice#view', sliceEmitter('__str_slice_view'))
|
|
1135
1429
|
|
|
1136
1430
|
// ToIntegerOrInfinity for a string-method position argument: ToNumber (so
|
|
1137
1431
|
// string / boolean / null / undefined positions coerce per spec) then trunc.
|
|
@@ -1147,15 +1441,39 @@ export default (ctx) => {
|
|
|
1147
1441
|
// covers string/number/null/undefined needles, but two cases need help here:
|
|
1148
1442
|
// a BOOL rides the 0/1 carrier (→ "0"/"1" not "true"/"false"), and an OBJECT
|
|
1149
1443
|
// needs compile-time ToPrimitive(string) (__to_str can't invoke user toString).
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1444
|
+
// Coerce the search operand to a string AT THE CALL SITE (21.1.3.x ToString step),
|
|
1445
|
+
// so the `__str_indexof` family carries no embedded `__to_str`. A known-STRING arg
|
|
1446
|
+
// (the overwhelmingly common `s.indexOf("x")` / `s.indexOf(t)` shape) passes raw —
|
|
1447
|
+
// dropping the whole ToString → float-formatter dep tree (~4 KB) that an internal,
|
|
1448
|
+
// unconditional coercion forced into every search-method program. Mirrors
|
|
1449
|
+
// `stringSearchMethod` (startsWith/endsWith), which has always coerced here.
|
|
1450
|
+
const searchArg = (search) => {
|
|
1451
|
+
const vt = valTypeOf(search)
|
|
1452
|
+
if (vt === VAL.STRING) return asI64(emit(search))
|
|
1453
|
+
if (vt === VAL.BOOL) return asI64(bool(search))
|
|
1454
|
+
if (vt === VAL.OBJECT) return toStrI64(search, emit(search))
|
|
1455
|
+
inc('__to_str')
|
|
1456
|
+
return ['call', '$__to_str', asI64(emit(search))]
|
|
1457
|
+
}
|
|
1458
|
+
// Replacement operand of .replace/.replaceAll — same call-site ToString as searchArg.
|
|
1459
|
+
const strReplArg = searchArg
|
|
1153
1460
|
|
|
1154
|
-
|
|
1461
|
+
bind('.string:indexOf', (str, search, from) => {
|
|
1155
1462
|
inc('__str_indexof')
|
|
1156
1463
|
const hay = asI64(emit(str)), ndl = searchArg(search)
|
|
1157
1464
|
return typed(['f64.convert_i32_s', ['call', '$__str_indexof', hay, ndl, posIndex(from)]], 'f64')
|
|
1158
|
-
}
|
|
1465
|
+
})
|
|
1466
|
+
|
|
1467
|
+
// String.prototype.lastIndexOf: search from the end, returning the last
|
|
1468
|
+
// byte-offset of `search` in `str` at or before `fromIndex` (or -1).
|
|
1469
|
+
// Per spec the `from` default is +∞ (search from the very end), which we
|
|
1470
|
+
// map to 0x7fffffff — __str_lastindexof clamps it to hlen-nlen anyway.
|
|
1471
|
+
bind('.string:lastIndexOf', (str, search, from) => {
|
|
1472
|
+
inc('__str_lastindexof')
|
|
1473
|
+
const hay = asI64(emit(str)), ndl = searchArg(search)
|
|
1474
|
+
const fromIR = from == null ? ['i32.const', 0x7fffffff] : asI32(emit(from))
|
|
1475
|
+
return typed(['f64.convert_i32_s', ['call', '$__str_lastindexof', hay, ndl, fromIR]], 'f64')
|
|
1476
|
+
})
|
|
1159
1477
|
|
|
1160
1478
|
// String.prototype.{includes,startsWith,endsWith} run IsRegExp(searchString)
|
|
1161
1479
|
// and throw a TypeError when it is a RegExp. Detect a regex-typed search arg
|
|
@@ -1166,16 +1484,16 @@ export default (ctx) => {
|
|
|
1166
1484
|
return typed(['block', ['result', 'f64'], ['throw', '$__jz_err', ['f64.const', 0]]], 'f64')
|
|
1167
1485
|
}
|
|
1168
1486
|
|
|
1169
|
-
|
|
1487
|
+
bind('.string:includes', (str, search, from) => {
|
|
1170
1488
|
const guard = regexpSearchGuard(search); if (guard) return guard
|
|
1171
1489
|
inc('__str_indexof')
|
|
1172
1490
|
const hay = asI64(emit(str)), ndl = searchArg(search)
|
|
1173
1491
|
return typed(['f64.convert_i32_s',
|
|
1174
1492
|
['i32.ge_s', ['call', '$__str_indexof', hay, ndl, posIndex(from)], ['i32.const', 0]]], 'f64')
|
|
1175
|
-
}
|
|
1493
|
+
})
|
|
1176
1494
|
|
|
1177
1495
|
// Generic (no collision)
|
|
1178
|
-
|
|
1496
|
+
bind('.substring', (str, start, end) => {
|
|
1179
1497
|
inc('__str_substring')
|
|
1180
1498
|
if (end != null) return typed(['call', '$__str_substring', asI64(emit(str)), asI32(emit(start)), asI32(emit(end))], 'f64')
|
|
1181
1499
|
const t = temp('t')
|
|
@@ -1183,13 +1501,13 @@ export default (ctx) => {
|
|
|
1183
1501
|
['local.set', `$${t}`, asF64(emit(str))],
|
|
1184
1502
|
['call', '$__str_substring', ['i64.reinterpret_f64', ['local.get', `$${t}`]], asI32(emit(start)),
|
|
1185
1503
|
['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]]], 'f64')
|
|
1186
|
-
}
|
|
1504
|
+
})
|
|
1187
1505
|
|
|
1188
1506
|
// .substr(start, length) — Annex B / legacy. Equivalent to substring(start, start+length).
|
|
1189
1507
|
// __str_substring clamps end to byteLen and start/end to [0, byteLen], so negative
|
|
1190
1508
|
// values are floored to 0 (matches v8 for length<0 → empty; for start<0 spec wants
|
|
1191
1509
|
// max(0, len+start), which we don't implement — rare in practice).
|
|
1192
|
-
|
|
1510
|
+
bind('.substr', (str, start, length) => {
|
|
1193
1511
|
inc('__str_substring')
|
|
1194
1512
|
if (length != null) {
|
|
1195
1513
|
const s = tempI32('substrS')
|
|
@@ -1205,15 +1523,6 @@ export default (ctx) => {
|
|
|
1205
1523
|
['local.set', `$${t}`, asF64(emit(str))],
|
|
1206
1524
|
['call', '$__str_substring', ['i64.reinterpret_f64', ['local.get', `$${t}`]], asI32(emit(start)),
|
|
1207
1525
|
['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
1526
|
})
|
|
1218
1527
|
|
|
1219
1528
|
// Search args go through ToString per spec — coerce non-string-typed args
|
|
@@ -1231,30 +1540,92 @@ export default (ctx) => {
|
|
|
1231
1540
|
}
|
|
1232
1541
|
return typed(['f64.convert_i32_s', ['call', `$${name}`, asI64(emit(str)), sfxArg]], 'f64')
|
|
1233
1542
|
}
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1543
|
+
bind('.startsWith', stringSearchMethod('__str_startswith'))
|
|
1544
|
+
bind('.endsWith', stringSearchMethod('__str_endswith'))
|
|
1545
|
+
bind('.trim', method('__str_trim', 'I'))
|
|
1546
|
+
bind('.trimStart', method('__str_trimStart', 'I'))
|
|
1547
|
+
bind('.trimEnd', method('__str_trimEnd', 'I'))
|
|
1548
|
+
bind('.repeat', method('__str_repeat', 'Ii'))
|
|
1549
|
+
// split(sep, limit): both args are optional.
|
|
1550
|
+
// - No args (undefined sep) → [str]: JS spec step 3 treats undefined separator
|
|
1551
|
+
// as returning a single-element array of the whole string (not splitting at all).
|
|
1552
|
+
// - 1 arg → no limit (sentinel 0x7fffffff = MAX_I32).
|
|
1553
|
+
// - 2 args → honour limit: 0 → [], N → at most N pieces.
|
|
1554
|
+
bind('.split', (str, sep, limit) => {
|
|
1555
|
+
if (sep === undefined) {
|
|
1556
|
+
// split() → [str]: wrap the whole string in a 1-element array
|
|
1557
|
+
inc('__wrap1')
|
|
1558
|
+
return typed(['call', '$__wrap1', asI64(emit(str))], 'f64')
|
|
1559
|
+
}
|
|
1560
|
+
inc('__str_split')
|
|
1561
|
+
const limitIR = limit === undefined
|
|
1562
|
+
? ['i32.const', 0x7fffffff]
|
|
1563
|
+
: ['i32.trunc_sat_f64_u', asF64(emit(limit))]
|
|
1564
|
+
return typed(['call', '$__str_split', asI64(emit(str)), asI64(emit(sep)), limitIR], 'f64')
|
|
1565
|
+
})
|
|
1248
1566
|
|
|
1249
|
-
|
|
1567
|
+
// replace(search, replacement). When `replacement` is a function, replace the
|
|
1568
|
+
// FIRST occurrence of the (string) search with ToString(fn(match)) — per spec a
|
|
1569
|
+
// string search matches once and the callback receives the matched substring.
|
|
1570
|
+
// (A regex search routes to `.string:replace` in the regex module, which also
|
|
1571
|
+
// handles the /g loop.) Without a closure runtime we can't invoke the callback,
|
|
1572
|
+
// so fall back to a clear error rather than silent data loss.
|
|
1573
|
+
bind('.replace', (str, search, repl) => {
|
|
1574
|
+
if (valTypeOf(repl) === VAL.CLOSURE) {
|
|
1575
|
+
if (!ctx.closure?.call) err('.replace(search, fn): no closure runtime available for the callback form')
|
|
1576
|
+
inc('__str_indexof', '__str_slice', '__str_concat', '__str_byteLen', '__to_str')
|
|
1577
|
+
const s = temp('rps'), q = temp('rpq'), fnL = temp('rpf')
|
|
1578
|
+
const idx = tempI32('rpi'), mlen = tempI32('rpm')
|
|
1579
|
+
const sI64 = () => ['i64.reinterpret_f64', ['local.get', `$${s}`]]
|
|
1580
|
+
const match = typed(['call', '$__str_slice', sI64(), ['local.get', `$${idx}`],
|
|
1581
|
+
['i32.add', ['local.get', `$${idx}`], ['local.get', `$${mlen}`]]], 'f64')
|
|
1582
|
+
const repIR = ['call', '$__to_str', asI64(ctx.closure.call(typed(['local.get', `$${fnL}`], 'f64'), [match]))]
|
|
1583
|
+
const head = typed(['call', '$__str_slice', sI64(), ['i32.const', 0], ['local.get', `$${idx}`]], 'f64')
|
|
1584
|
+
const tail = typed(['call', '$__str_slice', sI64(),
|
|
1585
|
+
['i32.add', ['local.get', `$${idx}`], ['local.get', `$${mlen}`]],
|
|
1586
|
+
['call', '$__str_byteLen', sI64()]], 'f64')
|
|
1587
|
+
return typed(['block', ['result', 'f64'],
|
|
1588
|
+
['local.set', `$${s}`, asF64(emit(str))],
|
|
1589
|
+
['local.set', `$${q}`, asF64(emit(search))],
|
|
1590
|
+
['local.set', `$${fnL}`, asF64(emit(repl))],
|
|
1591
|
+
['local.set', `$${mlen}`, ['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${q}`]]]],
|
|
1592
|
+
['local.set', `$${idx}`, ['call', '$__str_indexof', sI64(), ['i64.reinterpret_f64', ['local.get', `$${q}`]], ['i32.const', 0]]],
|
|
1593
|
+
['if', ['result', 'f64'], ['i32.lt_s', ['local.get', `$${idx}`], ['i32.const', 0]],
|
|
1594
|
+
['then', ['local.get', `$${s}`]],
|
|
1595
|
+
['else', typed(['call', '$__str_concat',
|
|
1596
|
+
asI64(typed(['call', '$__str_concat', asI64(head), repIR], 'f64')),
|
|
1597
|
+
asI64(tail)], 'f64')]]], 'f64')
|
|
1598
|
+
}
|
|
1599
|
+
inc('__str_replace')
|
|
1600
|
+
// search/repl ToString'd at the call site (searchArg) — __str_replace's __str_indexof
|
|
1601
|
+
// no longer coerces internally, so a non-string search must be stringified here.
|
|
1602
|
+
return typed(['call', '$__str_replace', asI64(emit(str)), searchArg(search), strReplArg(repl)], 'f64')
|
|
1603
|
+
})
|
|
1604
|
+
bind('.replaceAll', (str, search, repl) => {
|
|
1605
|
+
inc('__str_replaceall')
|
|
1606
|
+
return typed(['call', '$__str_replaceall', asI64(emit(str)), searchArg(search), strReplArg(repl)], 'f64')
|
|
1607
|
+
})
|
|
1608
|
+
|
|
1609
|
+
const caseMethod = (lo, hi, delta) => (str) => {
|
|
1250
1610
|
inc('__str_case')
|
|
1251
|
-
return typed(['call', '$__str_case', asI64(emit(str)), ['i32.const',
|
|
1611
|
+
return typed(['call', '$__str_case', asI64(emit(str)), ['i32.const', lo], ['i32.const', hi], ['i32.const', delta]], 'f64')
|
|
1252
1612
|
}
|
|
1613
|
+
bind('.toUpperCase', caseMethod(97, 122, -32))
|
|
1614
|
+
const _toLowerCase = caseMethod(65, 90, 32)
|
|
1615
|
+
bind('.toLowerCase', _toLowerCase)
|
|
1253
1616
|
|
|
1254
1617
|
// Locale-specific casing needs ICU/CLDR data. jz intentionally has no
|
|
1255
1618
|
// runtime, so this follows the existing ASCII-only lowercase helper and
|
|
1256
1619
|
// ignores optional locale arguments.
|
|
1257
|
-
|
|
1620
|
+
bind('.toLocaleLowerCase', _toLowerCase)
|
|
1621
|
+
|
|
1622
|
+
const padMethod = (start) => (str, len, pad) => {
|
|
1623
|
+
inc('__str_pad')
|
|
1624
|
+
const vpad = pad != null ? asI64(emit(pad)) : ['i64.reinterpret_f64', mkPtrIR(PTR.STRING, LAYOUT.SSO_BIT | 1, 32)]
|
|
1625
|
+
return typed(['call', '$__str_pad', asI64(emit(str)), asI32(emit(len)), vpad, ['i32.const', start]], 'f64')
|
|
1626
|
+
}
|
|
1627
|
+
bind('.padStart', padMethod(1))
|
|
1628
|
+
bind('.padEnd', padMethod(0))
|
|
1258
1629
|
|
|
1259
1630
|
// Byte-wise variant of String.prototype.localeCompare. Returns -1/0/1 from
|
|
1260
1631
|
// an unsigned byte-by-byte compare with shorter-string-sorts-first tiebreak.
|
|
@@ -1263,21 +1634,21 @@ export default (ctx) => {
|
|
|
1263
1634
|
// the spec exactly; for non-ASCII it follows UTF-8 byte order, which is
|
|
1264
1635
|
// codepoint order for well-formed strings — close enough for sort-stability
|
|
1265
1636
|
// use cases, wrong for human-language collation.
|
|
1266
|
-
|
|
1637
|
+
bind('.localeCompare', method('__str_cmp', 'II', 'i32'))
|
|
1267
1638
|
|
|
1268
|
-
|
|
1639
|
+
bind('.string:concat', (str, ...others) => {
|
|
1269
1640
|
inc('__str_concat')
|
|
1270
1641
|
let result = asF64(emit(str))
|
|
1271
1642
|
for (const other of others) result = typed(['call', '$__str_concat', ['i64.reinterpret_f64', result], asI64(emit(other))], 'f64')
|
|
1272
1643
|
return result
|
|
1273
|
-
}
|
|
1644
|
+
})
|
|
1274
1645
|
|
|
1275
1646
|
// A VAL.BOOL part rides the 0/1 carrier, so __to_str would render "1"/"0".
|
|
1276
|
-
//
|
|
1647
|
+
// bool selects the interned "true"/"false" literal (constant-folded
|
|
1277
1648
|
// when the operand is known); every other part goes through __to_str.
|
|
1278
|
-
const partStrI64 = (p) => valTypeOf(p) === VAL.BOOL ? asI64(
|
|
1649
|
+
const partStrI64 = (p) => valTypeOf(p) === VAL.BOOL ? asI64(bool(p)) : toStrI64(p, emit(p))
|
|
1279
1650
|
|
|
1280
|
-
|
|
1651
|
+
bind('strcat', (...parts) => {
|
|
1281
1652
|
inc('__to_str', '__str_byteLen', '__alloc', '__mkptr', '__str_copy')
|
|
1282
1653
|
if (!parts.length) return mkPtrIR(PTR.STRING, LAYOUT.SSO_BIT, 0)
|
|
1283
1654
|
if (parts.length === 1) return typed(['f64.reinterpret_i64', partStrI64(parts[0])], 'f64')
|
|
@@ -1311,29 +1682,33 @@ export default (ctx) => {
|
|
|
1311
1682
|
['then', mkPtrIR(PTR.STRING, LAYOUT.SSO_BIT, 0)],
|
|
1312
1683
|
['else', ['block', ['result', 'f64'], ...alloc]]])
|
|
1313
1684
|
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
|
-
}
|
|
1685
|
+
})
|
|
1327
1686
|
|
|
1328
|
-
//
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1687
|
+
// Shared: `$iLocal` in `[0, lenIR)` → a 1-byte SSO string of the char at that
|
|
1688
|
+
// index; otherwise `oobIR`. Without the bounds check `__char_at` returns 0 for an
|
|
1689
|
+
// out-of-range index, which would wrap to a bogus `"\x00"` string (charAt → "",
|
|
1690
|
+
// at → undefined). `sLocal` is the receiver as an f64 local.
|
|
1691
|
+
const charAtOr = (sLocal, iLocal, lenIR, oobIR) =>
|
|
1692
|
+
['if', ['result', 'f64'],
|
|
1693
|
+
['i32.and',
|
|
1694
|
+
['i32.ge_s', ['local.get', iLocal], ['i32.const', 0]],
|
|
1695
|
+
['i32.lt_s', ['local.get', iLocal], lenIR]],
|
|
1696
|
+
['then', mkPtrIR(PTR.STRING, LAYOUT.SSO_BIT | 1,
|
|
1697
|
+
['call', '$__char_at', ['i64.reinterpret_f64', ['local.get', sLocal]], ['local.get', iLocal]])],
|
|
1698
|
+
['else', oobIR]]
|
|
1699
|
+
const emptyStr = mkPtrIR(PTR.STRING, LAYOUT.SSO_BIT, 0)
|
|
1700
|
+
|
|
1701
|
+
// .charAt(i) → 1-char string at index i, or "" when out of range (JS spec: no
|
|
1702
|
+
// negative-index wraparound — a negative or >=length index yields "").
|
|
1703
|
+
bind('.charAt', (str, idx) => {
|
|
1704
|
+
inc('__char_at', '__str_byteLen')
|
|
1705
|
+
const s = temp('cs'), i = tempI32('ci')
|
|
1333
1706
|
return typed(['block', ['result', 'f64'],
|
|
1334
|
-
['local.set', `$${
|
|
1335
|
-
|
|
1336
|
-
|
|
1707
|
+
['local.set', `$${s}`, asF64(emit(str))],
|
|
1708
|
+
['local.set', `$${i}`, asI32(emit(idx))],
|
|
1709
|
+
charAtOr(`$${s}`, `$${i}`,
|
|
1710
|
+
['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${s}`]]], emptyStr)], 'f64')
|
|
1711
|
+
})
|
|
1337
1712
|
|
|
1338
1713
|
// .charCodeAt(i) → JS-spec char code: the UTF-16 code unit at `i`, or NaN
|
|
1339
1714
|
// when `i` is out of range (`i < 0 || i >= length`). Result is f64 because
|
|
@@ -1342,32 +1717,50 @@ export default (ctx) => {
|
|
|
1342
1717
|
// loop `while ((cc = s.charCodeAt(i++)) <= 32) {}` would never terminate
|
|
1343
1718
|
// (`0 <= 32` is true, `NaN <= 32` is false). The narrower may re-narrow the
|
|
1344
1719
|
// 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')
|
|
1720
|
+
bind('.charCodeAt', (str, idx) =>
|
|
1721
|
+
typed(ctx.abi.string.ops.charCodeAt(asF64(emit(str)), asI32(emit(idx)), ctx, true), 'f64'))
|
|
1722
|
+
|
|
1723
|
+
// String.prototype.codePointAt(i) — byte-indexed.
|
|
1724
|
+
// jz strings are UTF-8 byte-arrays; `i` is a byte offset, not a UTF-16 code-unit
|
|
1725
|
+
// index. For ASCII inputs (U+0000..U+007F) the result is the exact Unicode code
|
|
1726
|
+
// point. For multi-byte sequences the result is the value of the leading byte only
|
|
1727
|
+
// (not a full code-point decode). Out-of-range → undefined (NaN-boxed). This is a
|
|
1728
|
+
// documented byte-semantics limitation; it keeps the implementation allocation-free
|
|
1729
|
+
// and consistent with jz's byte-indexed string model throughout.
|
|
1730
|
+
bind('.codePointAt', (str, idx) =>
|
|
1731
|
+
typed(ctx.abi.string.ops.charCodeAt(asF64(emit(str)), asI32(emit(idx)), ctx, true), 'f64'))
|
|
1347
1732
|
|
|
1348
1733
|
// String.fromCharCode(code) → 1-char SSO string
|
|
1349
|
-
|
|
1734
|
+
bind('String', (value) => {
|
|
1350
1735
|
if (value === undefined) return emit(['str', ''])
|
|
1351
1736
|
if (valTypeOf(value) === VAL.STRING) return emit(value)
|
|
1352
|
-
if (valTypeOf(value) === VAL.BOOL) return
|
|
1737
|
+
if (valTypeOf(value) === VAL.BOOL) return bool(value)
|
|
1353
1738
|
if (valTypeOf(value) === VAL.NUMBER) {
|
|
1354
1739
|
inc('__ftoa')
|
|
1355
1740
|
return typed(['call', '$__ftoa', asF64(emit(value)), ['i32.const', 0], ['i32.const', 0]], 'f64')
|
|
1356
1741
|
}
|
|
1357
1742
|
return typed(['f64.reinterpret_i64', toStrI64(value, emit(value))], 'f64')
|
|
1358
|
-
}
|
|
1743
|
+
})
|
|
1359
1744
|
|
|
1360
|
-
|
|
1361
|
-
|
|
1745
|
+
// String.fromCharCode(...codes) — variadic; each arg is ToUint16(ToNumber(code))
|
|
1746
|
+
// → a 1-byte string, concatenated left to right (mirrors String.fromCodePoint).
|
|
1747
|
+
bind('String.fromCharCode', (...codes) => {
|
|
1748
|
+
if (codes.length === 0) return emit(['str', ''])
|
|
1362
1749
|
// ToUint16(ToNumber(code)): `toNumF64` performs ToPrimitive on an object
|
|
1363
1750
|
// argument, so a throwing valueOf/toString propagates per spec.
|
|
1364
|
-
|
|
1365
|
-
|
|
1751
|
+
const one = (node) => mkPtrIR(PTR.STRING, LAYOUT.SSO_BIT | 1, asI32(toNumF64(node, emit(node))))
|
|
1752
|
+
let r = one(codes[0])
|
|
1753
|
+
for (let i = 1; i < codes.length; i++) {
|
|
1754
|
+
inc('__str_concat_raw')
|
|
1755
|
+
r = typed(['call', '$__str_concat_raw', asI64(r), asI64(one(codes[i]))], 'f64')
|
|
1756
|
+
}
|
|
1757
|
+
return r
|
|
1758
|
+
})
|
|
1366
1759
|
|
|
1367
1760
|
// String.fromCodePoint(cp) → UTF-8 encoded string for one code point.
|
|
1368
1761
|
// Param is f64 (already ToNumber-coerced); throws RangeError ($__jz_err) when
|
|
1369
1762
|
// the value is not an integer in [0, 0x10FFFF] (22.1.2.2 step 5.d).
|
|
1370
|
-
|
|
1763
|
+
wat('__fromCodePoint', `(func $__fromCodePoint (param $cpf f64) (result f64)
|
|
1371
1764
|
(local $cp i32) (local $off i32) (local $len i32)
|
|
1372
1765
|
(if (i32.or
|
|
1373
1766
|
(i32.or
|
|
@@ -1398,11 +1791,11 @@ export default (ctx) => {
|
|
|
1398
1791
|
(i32.or (i32.const 0xF0) (i32.shr_u (local.get $cp) (i32.const 18)))
|
|
1399
1792
|
(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
1793
|
(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))))))`
|
|
1794
|
+
(i32.shl (i32.or (i32.const 0x80) (i32.and (local.get $cp) (i32.const 0x3F))) (i32.const 24))))))`)
|
|
1402
1795
|
|
|
1403
1796
|
// String.fromCodePoint(...codePoints) — variadic; each arg is ToNumber-coerced
|
|
1404
1797
|
// then validated/encoded by __fromCodePoint, results concatenated left to right.
|
|
1405
|
-
|
|
1798
|
+
bind('String.fromCodePoint', (...codes) => {
|
|
1406
1799
|
if (codes.length === 0) return emit(['str', ''])
|
|
1407
1800
|
ctx.runtime.throws = true
|
|
1408
1801
|
inc('__fromCodePoint')
|
|
@@ -1414,9 +1807,9 @@ export default (ctx) => {
|
|
|
1414
1807
|
r = typed(['call', '$__str_concat_raw', asI64(r), asI64(one(codes[i]))], 'f64')
|
|
1415
1808
|
}
|
|
1416
1809
|
return r
|
|
1417
|
-
}
|
|
1810
|
+
})
|
|
1418
1811
|
|
|
1419
|
-
|
|
1812
|
+
wat('__encodeURIComponent', `(func $__encodeURIComponent (param $val i64) (result f64)
|
|
1420
1813
|
(local $str i64) (local $slen i32) (local $base i32) (local $out i32)
|
|
1421
1814
|
(local $i i32) (local $j i32) (local $c i32) (local $hi i32) (local $lo i32)
|
|
1422
1815
|
(local.set $str (call $__to_str (local.get $val)))
|
|
@@ -1458,24 +1851,24 @@ export default (ctx) => {
|
|
|
1458
1851
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
1459
1852
|
(br $loop)))
|
|
1460
1853
|
(i32.store (local.get $base) (local.get $j))
|
|
1461
|
-
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $out)))`
|
|
1854
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $out)))`)
|
|
1462
1855
|
|
|
1463
|
-
|
|
1856
|
+
bind('encodeURIComponent', (value) => {
|
|
1464
1857
|
inc('__encodeURIComponent')
|
|
1465
1858
|
const input = value === undefined ? ['i64.const', UNDEF_NAN] : asI64(emit(value))
|
|
1466
1859
|
return typed(['call', '$__encodeURIComponent', input], 'f64')
|
|
1467
|
-
}
|
|
1860
|
+
})
|
|
1468
1861
|
|
|
1469
|
-
|
|
1862
|
+
wat('__uri_hex', `(func $__uri_hex (param $c i32) (result i32)
|
|
1470
1863
|
(if (result i32) (i32.and (i32.ge_u (local.get $c) (i32.const 48)) (i32.le_u (local.get $c) (i32.const 57)))
|
|
1471
1864
|
(then (i32.sub (local.get $c) (i32.const 48)))
|
|
1472
1865
|
(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
1866
|
(then (i32.sub (local.get $c) (i32.const 55)))
|
|
1474
1867
|
(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
1868
|
(then (i32.sub (local.get $c) (i32.const 87)))
|
|
1476
|
-
(else (i32.const -1))))))))`
|
|
1869
|
+
(else (i32.const -1))))))))`)
|
|
1477
1870
|
|
|
1478
|
-
|
|
1871
|
+
wat('__decodeURIComponent', `(func $__decodeURIComponent (param $v i64) (result f64)
|
|
1479
1872
|
(local $s i64) (local $len i32) (local $i i32)
|
|
1480
1873
|
(local $base i32) (local $dst i32) (local $outLen i32)
|
|
1481
1874
|
(local $c i32) (local $hi i32) (local $lo i32)
|
|
@@ -1551,38 +1944,40 @@ export default (ctx) => {
|
|
|
1551
1944
|
(local.set $outLen (i32.add (local.get $outLen) (i32.const 1)))))
|
|
1552
1945
|
(br $loop)))
|
|
1553
1946
|
(i32.store (local.get $base) (local.get $outLen))
|
|
1554
|
-
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $dst)))`
|
|
1947
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $dst)))`)
|
|
1555
1948
|
|
|
1556
|
-
|
|
1949
|
+
bind('decodeURIComponent', (value) => {
|
|
1557
1950
|
ctx.runtime.throws = true
|
|
1558
1951
|
inc('__decodeURIComponent')
|
|
1559
1952
|
return typed(['call', '$__decodeURIComponent',
|
|
1560
1953
|
value === undefined ? ['i64.const', UNDEF_NAN] : asI64(emit(value))], 'f64')
|
|
1561
|
-
}
|
|
1954
|
+
})
|
|
1562
1955
|
|
|
1563
|
-
// .at(i) →
|
|
1564
|
-
|
|
1956
|
+
// .at(i) → 1-char string at index i with negative-index support, or undefined
|
|
1957
|
+
// when out of range (JS spec: `i += length` for negative, then OOB → undefined).
|
|
1958
|
+
bind('.string:at', (str, idx) => {
|
|
1565
1959
|
inc('__char_at', '__str_byteLen')
|
|
1566
|
-
const t = tempI32('at'), s = temp('as')
|
|
1960
|
+
const t = tempI32('at'), s = temp('as'), len = tempI32('al')
|
|
1567
1961
|
return typed(['block', ['result', 'f64'],
|
|
1568
1962
|
['local.set', `$${s}`, asF64(emit(str))],
|
|
1963
|
+
['local.set', `$${len}`, ['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${s}`]]]],
|
|
1569
1964
|
['local.set', `$${t}`, asI32(emit(idx))],
|
|
1570
1965
|
// Negative index: t += length
|
|
1571
1966
|
['if', ['i32.lt_s', ['local.get', `$${t}`], ['i32.const', 0]],
|
|
1572
|
-
['then', ['local.set', `$${t}`, ['i32.add', ['local.get', `$${t}`],
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
}
|
|
1967
|
+
['then', ['local.set', `$${t}`, ['i32.add', ['local.get', `$${t}`], ['local.get', `$${len}`]]]]],
|
|
1968
|
+
charAtOr(`$${s}`, `$${t}`, ['local.get', `$${len}`],
|
|
1969
|
+
['f64.reinterpret_i64', ['i64.const', UNDEF_NAN]])], 'f64')
|
|
1970
|
+
})
|
|
1576
1971
|
|
|
1577
1972
|
// .search(str) → indexOf (same as indexOf for string args)
|
|
1578
|
-
|
|
1973
|
+
bind('.search', (str, search) => {
|
|
1579
1974
|
inc('__str_indexof')
|
|
1580
1975
|
return typed(['f64.convert_i32_s', ['call', '$__str_indexof', asI64(emit(str)), asI64(emit(search)), ['i32.const', 0]]], 'f64')
|
|
1581
|
-
}
|
|
1976
|
+
})
|
|
1582
1977
|
|
|
1583
1978
|
// .match(str) → [match] array if found, or 0 (null) if not
|
|
1584
1979
|
// For string args, returns single-element array with the matched substring
|
|
1585
|
-
|
|
1980
|
+
bind('.match', (str, search) => {
|
|
1586
1981
|
inc('__str_indexof', '__str_slice', '__wrap1')
|
|
1587
1982
|
const s = temp('ms'), q = temp('mq'), idx = tempI32('mi')
|
|
1588
1983
|
// indexOf, then if >= 0, create 1-element array with the match slice
|
|
@@ -1599,24 +1994,24 @@ export default (ctx) => {
|
|
|
1599
1994
|
['call', '$__str_slice', ['i64.reinterpret_f64', ['local.get', `$${s}`]],
|
|
1600
1995
|
['local.get', `$${idx}`],
|
|
1601
1996
|
['i32.add', ['local.get', `$${idx}`], ['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${q}`]]]]]]]]]], 'f64')
|
|
1602
|
-
}
|
|
1997
|
+
})
|
|
1603
1998
|
|
|
1604
1999
|
// __wrap1(val: i64) → f64 — create 1-element array [val]
|
|
1605
|
-
|
|
2000
|
+
wat('__wrap1', `(func $__wrap1 (param $val i64) (result f64)
|
|
1606
2001
|
(local $ptr i32)
|
|
1607
2002
|
(local.set $ptr (call $__alloc (i32.const 16)))
|
|
1608
2003
|
(i32.store (local.get $ptr) (i32.const 1))
|
|
1609
2004
|
(i32.store (i32.add (local.get $ptr) (i32.const 4)) (i32.const 1))
|
|
1610
2005
|
(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))))`
|
|
2006
|
+
(call $__mkptr (i32.const 1) (i32.const 0) (i32.add (local.get $ptr) (i32.const 8))))`)
|
|
1612
2007
|
|
|
1613
2008
|
// TextEncoder() / TextDecoder() → dummy values (methods do the work)
|
|
1614
|
-
|
|
1615
|
-
|
|
2009
|
+
bind('TextEncoder', () => typed(['f64.const', 1], 'f64'))
|
|
2010
|
+
bind('TextDecoder', () => typed(['f64.const', 2], 'f64'))
|
|
1616
2011
|
|
|
1617
2012
|
// .encode(str) → Uint8Array of string's UTF-8 bytes
|
|
1618
2013
|
// Copies bytes from string (SSO or heap) into a new Uint8Array
|
|
1619
|
-
|
|
2014
|
+
wat('__str_encode', `(func $__str_encode (param $str i64) (result f64)
|
|
1620
2015
|
(local $len i32) (local $dst i32)
|
|
1621
2016
|
(local.set $len (call $__str_byteLen (local.get $str)))
|
|
1622
2017
|
(local.set $dst (call $__alloc (i32.add (i32.const 8) (local.get $len))))
|
|
@@ -1624,9 +2019,9 @@ export default (ctx) => {
|
|
|
1624
2019
|
(i32.store (i32.add (local.get $dst) (i32.const 4)) (local.get $len))
|
|
1625
2020
|
(local.set $dst (i32.add (local.get $dst) (i32.const 8)))
|
|
1626
2021
|
(call $__str_copy (local.get $str) (local.get $dst) (local.get $len))
|
|
1627
|
-
(call $__mkptr (i32.const 3) (i32.const 1) (local.get $dst)))`
|
|
2022
|
+
(call $__mkptr (i32.const 3) (i32.const 1) (local.get $dst)))`)
|
|
1628
2023
|
|
|
1629
|
-
|
|
2024
|
+
bind('.encode', (obj, str) => {
|
|
1630
2025
|
inc('__str_encode')
|
|
1631
2026
|
// .encode() yields a runtime PTR.TYPED/u8 array (see __mkptr above). Downstream
|
|
1632
2027
|
// indexing/spread dispatch through __typed_idx, whose element-unaware fallback
|
|
@@ -1634,10 +2029,10 @@ export default (ctx) => {
|
|
|
1634
2029
|
// the feature pulls the element-aware variant — same invariant `.length` follows.
|
|
1635
2030
|
ctx.features.typedarray = true
|
|
1636
2031
|
return typed(['call', '$__str_encode', asI64(emit(str))], 'f64')
|
|
1637
|
-
}
|
|
2032
|
+
})
|
|
1638
2033
|
|
|
1639
2034
|
// .decode(uint8arr) → string from byte data
|
|
1640
|
-
|
|
2035
|
+
wat('__bytes_decode', `(func $__bytes_decode (param $arr i64) (result f64)
|
|
1641
2036
|
(local $off i32) (local $len i32) (local $dst i32)
|
|
1642
2037
|
(local.set $off (call $__ptr_offset (local.get $arr)))
|
|
1643
2038
|
(local.set $len (call $__len (local.get $arr)))
|
|
@@ -1645,10 +2040,10 @@ export default (ctx) => {
|
|
|
1645
2040
|
(i32.store (local.get $dst) (local.get $len))
|
|
1646
2041
|
(local.set $dst (i32.add (local.get $dst) (i32.const 4)))
|
|
1647
2042
|
(memory.copy (local.get $dst) (local.get $off) (local.get $len))
|
|
1648
|
-
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $dst)))`
|
|
2043
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $dst)))`)
|
|
1649
2044
|
|
|
1650
|
-
|
|
2045
|
+
bind('.decode', (obj, arr) => {
|
|
1651
2046
|
inc('__bytes_decode')
|
|
1652
2047
|
return typed(['call', '$__bytes_decode', asI64(emit(arr))], 'f64')
|
|
1653
|
-
}
|
|
2048
|
+
})
|
|
1654
2049
|
}
|