jz 0.6.0 → 0.8.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 +99 -72
- package/bench/README.md +253 -100
- package/bench/bench.svg +58 -81
- package/cli.js +85 -12
- package/dist/interop.js +1 -0
- package/dist/jz.js +6196 -0
- package/index.d.ts +126 -0
- package/index.js +222 -35
- package/interop.js +240 -141
- package/layout.js +34 -18
- package/module/array.js +99 -111
- package/module/collection.js +124 -30
- package/module/console.js +1 -1
- package/module/core.js +163 -19
- package/module/json.js +3 -3
- package/module/math.js +162 -3
- package/module/number.js +268 -13
- package/module/object.js +37 -5
- package/module/regex.js +8 -7
- package/module/simd.js +37 -5
- package/module/string.js +203 -168
- package/module/typedarray.js +565 -112
- package/package.json +26 -7
- package/src/abi/string.js +29 -29
- package/src/ast.js +19 -2
- package/src/autoload.js +3 -0
- package/src/compile/analyze-scans.js +174 -11
- package/src/compile/analyze.js +101 -4
- package/src/compile/cse-load.js +200 -0
- package/src/compile/emit-assign.js +82 -20
- package/src/compile/emit.js +592 -51
- package/src/compile/index.js +504 -89
- package/src/compile/infer.js +36 -1
- package/src/compile/loop-divmod.js +109 -0
- package/src/compile/loop-model.js +91 -0
- package/src/compile/loop-square.js +102 -0
- package/src/compile/narrow.js +275 -41
- package/src/compile/peel-stencil.js +215 -0
- package/src/compile/plan/advise.js +55 -1
- package/src/compile/plan/common.js +29 -0
- package/src/compile/plan/index.js +8 -1
- package/src/compile/plan/inline.js +180 -22
- package/src/compile/plan/literals.js +313 -24
- package/src/compile/plan/scope.js +115 -39
- package/src/compile/program-facts.js +21 -2
- package/src/ctx.js +96 -19
- package/src/helper-counters.js +137 -0
- package/src/ir.js +157 -20
- package/src/kind-traits.js +34 -3
- package/src/kind.js +79 -4
- package/src/op-policy.js +5 -2
- package/src/ops.js +119 -0
- package/src/optimize/index.js +1274 -151
- package/src/optimize/recurse.js +182 -0
- package/src/optimize/vectorize.js +4187 -253
- package/src/prepare/index.js +75 -14
- package/src/prepare/lift-iife.js +149 -0
- package/src/reps.js +6 -2
- package/src/static.js +9 -0
- package/src/type.js +63 -51
- package/src/wat/assemble.js +286 -21
- package/src/widen.js +21 -0
- package/src/wat/optimize.js +0 -3760
package/module/string.js
CHANGED
|
@@ -22,6 +22,38 @@ import { ssoBitI64Hex, sliceBitI64Hex, ptrNanHex, STR_INTERN_BIT } from '../layo
|
|
|
22
22
|
const SSO_BIT_I64 = ssoBitI64Hex()
|
|
23
23
|
const SLICE_BIT_I64 = sliceBitI64Hex()
|
|
24
24
|
|
|
25
|
+
// === SSO codec — single source of truth for the inline-string bit layout ===
|
|
26
|
+
// ASCII chars packed at 7 bits each into the NaN-box payload: char i at payload bit
|
|
27
|
+
// i*7 (uniform `(ptr >> i*7) & 0x7f`), length at payload bits 42-44, SSO_BIT at bit 46
|
|
28
|
+
// (SLICE_BIT at 45 stays 0). 6 chars fit (6*7=42, + 3-bit len). ASCII-only — a byte
|
|
29
|
+
// ≥0x80 falls back to a heap string. The 7-bit-uniform layout makes equal short strings
|
|
30
|
+
// content-bit-equal (so `op === 'tag'` is a bare i64.eq) and never touches memory.
|
|
31
|
+
const MAX_SSO = 6
|
|
32
|
+
const SSO_LEN_SHIFT = 10 // length occupies aux bits 10-12 (= payload bits 42-44)
|
|
33
|
+
// JS: ASCII string → { aux, offset }, or null when ineligible (too long / non-ASCII).
|
|
34
|
+
// BigInt-free (i32 ops only) so the self-hosted compiler — which runs this to encode
|
|
35
|
+
// every string literal — stays on jz's numeric core. offset = payload bits 0-31, aux =
|
|
36
|
+
// bits 32-46; char i at bit i*7 (chars 0-3 fit the offset, char 4 straddles, char 5 → aux).
|
|
37
|
+
const ssoEncode = (str) => {
|
|
38
|
+
if (str.length > MAX_SSO || !/^[\x00-\x7f]*$/.test(str)) return null
|
|
39
|
+
let offset = 0, auxChars = 0
|
|
40
|
+
for (let i = 0; i < str.length; i++) {
|
|
41
|
+
const c = str.charCodeAt(i), bit = i * 7
|
|
42
|
+
if (bit <= 24) offset |= c << bit // chars 0-3 (bits 0-27), wholly in offset
|
|
43
|
+
else if (bit < 32) { offset |= (c & 0xF) << 28; auxChars |= c >> 4 } // char 4: bits 28-34 straddle
|
|
44
|
+
else auxChars |= c << (bit - 32) // char 5: bits 35-41 → aux bits 3-9
|
|
45
|
+
}
|
|
46
|
+
return { aux: LAYOUT.SSO_BIT | (str.length << SSO_LEN_SHIFT) | auxChars, offset: offset >>> 0 }
|
|
47
|
+
}
|
|
48
|
+
// aux for an SSO string whose chars all fit in the offset (len ≤ 4: 4*7=28 ≤ 32 bits).
|
|
49
|
+
const ssoAux = (len) => LAYOUT.SSO_BIT | (len << SSO_LEN_SHIFT)
|
|
50
|
+
// WAT: char i (i32 expr) of SSO ptr (i64 expr) — 7-bit at payload bit i*7.
|
|
51
|
+
const ssoCharWat = (ptr, i) => `(i32.wrap_i64 (i64.and (i64.shr_u ${ptr} (i64.mul (i64.extend_i32_u ${i}) (i64.const 7))) (i64.const 0x7f)))`
|
|
52
|
+
// WAT: length (i32) of SSO ptr (i64 expr) — payload bits 42-44.
|
|
53
|
+
const ssoLenWat = (ptr) => `(i32.wrap_i64 (i64.and (i64.shr_u ${ptr} (i64.const 42)) (i64.const 7)))`
|
|
54
|
+
// WAT: length (i32) from an already-extracted aux (i32 expr).
|
|
55
|
+
const ssoLenFromAux = (aux) => `(i32.and (i32.shr_u ${aux} (i32.const ${SSO_LEN_SHIFT})) (i32.const 7))`
|
|
56
|
+
|
|
25
57
|
// WAT (no-locals expression): byte length of a heap STRING given its raw $-local
|
|
26
58
|
// names for the offset and the i64 ptr. A view (SLICE_BIT) carries its length in
|
|
27
59
|
// aux[12:0]; an own heap string reads the i32 header at off-4. Callers must have
|
|
@@ -43,26 +75,28 @@ const heapLenExpr = (ptrLocal, offLocal) => `(if (result i32)
|
|
|
43
75
|
// bit-equal (SSO content IS the bits). Bails to the caller's heap path on a
|
|
44
76
|
// non-ASCII byte (SSO is ASCII-only). Locals: $sb scratch byte, $sp packed.
|
|
45
77
|
const sliceSsoPackWat = () => `
|
|
46
|
-
(if (i32.le_u (local.get $nlen) (i32.const
|
|
78
|
+
(if (i32.le_u (local.get $nlen) (i32.const ${MAX_SSO}))
|
|
47
79
|
(then (block $heap8
|
|
48
80
|
(local.set $srcOff (i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
49
81
|
(local.set $isSso (i32.wrap_i64 (i64.shr_u
|
|
50
82
|
(i64.and (local.get $ptr) (i64.const ${SSO_BIT_I64}))
|
|
51
83
|
(i64.const ${LAYOUT.AUX_SHIFT}))))
|
|
52
|
-
(local.set $i (i32.const 0)) (local.set $
|
|
53
|
-
(loop $
|
|
84
|
+
(local.set $i (i32.const 0)) (local.set $sp64 (i64.const 0))
|
|
85
|
+
(loop $pk7
|
|
54
86
|
(if (i32.lt_u (local.get $i) (local.get $nlen))
|
|
55
87
|
(then
|
|
56
88
|
(local.set $sb (if (result i32) (local.get $isSso)
|
|
57
|
-
(then (i32.
|
|
58
|
-
(i32.shr_u (local.get $srcOff) (i32.shl (i32.add (local.get $start) (local.get $i)) (i32.const 3)))
|
|
59
|
-
(i32.const 0xFF)))
|
|
89
|
+
(then ${ssoCharWat('(local.get $ptr)', '(i32.add (local.get $start) (local.get $i))')})
|
|
60
90
|
(else (i32.load8_u (i32.add (i32.add (local.get $srcOff) (local.get $start)) (local.get $i))))))
|
|
61
91
|
(br_if $heap8 (i32.ge_u (local.get $sb) (i32.const 0x80)))
|
|
62
|
-
(local.set $
|
|
92
|
+
(local.set $sp64 (i64.or (local.get $sp64)
|
|
93
|
+
(i64.shl (i64.extend_i32_u (local.get $sb)) (i64.mul (i64.extend_i32_u (local.get $i)) (i64.const 7)))))
|
|
63
94
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
64
|
-
(br $
|
|
65
|
-
(return (
|
|
95
|
+
(br $pk7))))
|
|
96
|
+
(return (f64.reinterpret_i64 (i64.or (i64.or
|
|
97
|
+
(i64.const ${ptrNanHex(PTR.STRING, LAYOUT.SSO_BIT)})
|
|
98
|
+
(i64.shl (i64.extend_i32_u (local.get $nlen)) (i64.const 42)))
|
|
99
|
+
(local.get $sp64)))))))`
|
|
66
100
|
|
|
67
101
|
// Probe the static intern index (buildInternTable, compile/index.js): a 5..32
|
|
68
102
|
// byte slice whose content equals a static literal returns the CANONICAL
|
|
@@ -112,10 +146,15 @@ export default (ctx) => {
|
|
|
112
146
|
__str_concat_raw: ['__str_byteLen', '__alloc', '__memgrow', '__mkptr', '__str_copy'],
|
|
113
147
|
__str_append_byte: ['__str_byteLen', '__alloc', '__memgrow', '__mkptr', '__str_copy'],
|
|
114
148
|
__str_copy: [],
|
|
115
|
-
__str_slice:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
149
|
+
// __str_slice/_view are FUNCTION templates: resolveIncludes' auto-dep scan realizes the
|
|
150
|
+
// factory (v()) to discover body calls, but that realization DIVERGES under self-host
|
|
151
|
+
// (jz.wasm) — so a body-called helper not also listed here is dropped from the kernel
|
|
152
|
+
// ("Unknown func $__clamp_idx" on `str.slice`). FN templates must declare body deps
|
|
153
|
+
// manually; pinned by test/selfhost-includes.js.
|
|
154
|
+
__str_slice: ['__str_byteLen', '__alloc', '__clamp_idx', '__mkptr'],
|
|
155
|
+
__str_slice_view: ['__str_byteLen', '__mkptr', '__str_slice', '__clamp_idx'],
|
|
156
|
+
__str_indexof: ['__str_byteLen'],
|
|
157
|
+
__str_lastindexof: ['__str_byteLen'],
|
|
119
158
|
__wrap1: ['__alloc', '__mkptr'],
|
|
120
159
|
__str_substring: ['__str_slice'],
|
|
121
160
|
__str_startswith: ['__str_byteLen'],
|
|
@@ -128,15 +167,15 @@ export default (ctx) => {
|
|
|
128
167
|
__str_replace: ['__str_indexof', '__str_slice', '__str_concat'],
|
|
129
168
|
__str_replaceall: ['__str_indexof', '__str_slice', '__str_concat'],
|
|
130
169
|
__str_split: ['__str_slice', '__str_byteLen', '__char_at', '__alloc'],
|
|
131
|
-
__str_idx: [],
|
|
170
|
+
__str_idx: ['__char1byte'],
|
|
132
171
|
__str_eq: ['__str_eq_cold'],
|
|
133
172
|
__str_eq_cold: ['__char_at', '__str_byteLen'],
|
|
134
173
|
__str_cmp: ['__char_at', '__str_byteLen'],
|
|
135
174
|
__str_range_eq: ['__char_at', '__str_byteLen'],
|
|
136
175
|
__str_substring_eq: ['__str_byteLen', '__str_range_eq'],
|
|
137
|
-
__str_slice_eq: ['__str_byteLen', '__str_range_eq'],
|
|
176
|
+
__str_slice_eq: ['__str_byteLen', '__str_range_eq', '__clamp_idx'], // body-calls __clamp_idx; declare it (self-host auto-scan unreliable — test/selfhost-includes.js)
|
|
138
177
|
__str_pad: ['__str_byteLen', '__str_copy', '__alloc'],
|
|
139
|
-
__str_join: ['__str_concat', '__to_str', '__str_byteLen', '__len', '__ptr_offset'],
|
|
178
|
+
__str_join: ['__str_concat', '__to_str', '__str_byteLen', '__len', '__ptr_offset', '__mkptr'], // FN template: __mkptr body-called, must be manual (self-host auto-scan diverges)
|
|
140
179
|
__str_encode: ['__str_byteLen', '__str_copy'],
|
|
141
180
|
__encodeURIComponent: ['__to_str', '__str_byteLen', '__char_at', '__alloc', '__mkptr'],
|
|
142
181
|
__decodeURIComponent: ['__to_str', '__str_byteLen', '__char_at', '__alloc', '__mkptr', '__uri_hex'],
|
|
@@ -145,16 +184,26 @@ export default (ctx) => {
|
|
|
145
184
|
__str_byteLen: ['__ptr_type', '__ptr_aux', '__str_len'],
|
|
146
185
|
})
|
|
147
186
|
|
|
187
|
+
// String *runtime* construction (concat/slice/split/`String(n)`/shared-memory
|
|
188
|
+
// pools) allocates and boxes pointers, so the module eagerly declares its core
|
|
189
|
+
// heap deps — same as object.js / typedarray.js / array.js. Several such helpers
|
|
190
|
+
// (`__str_slice`, `__str_split`, `__str_case`, …) call `$__mkptr`/`$__alloc` only
|
|
191
|
+
// through nested template thunks (sliceSsoPackWat/internProbeWat); resolveIncludes'
|
|
192
|
+
// auto-derivation realizes those thunks to recover the dep, but that re-realization
|
|
193
|
+
// is not reliable under self-host (the jz.wasm kernel re-runs the thunk late, with
|
|
194
|
+
// intern/heap state the native run never reaches), so a runtime string op would
|
|
195
|
+
// emit `call $__mkptr` with no definition. Declaring the deps explicitly here makes
|
|
196
|
+
// inclusion independent of thunk re-realization. Reachability pruning still drops
|
|
197
|
+
// both for a literals-only module (no `__mkptr`/`__alloc` call site → unreachable),
|
|
198
|
+
// so a heap-free program stays heap-free — no allocator, no memory, no exports.
|
|
148
199
|
inc('__mkptr', '__alloc')
|
|
149
200
|
|
|
150
|
-
// === String literal: "abc" → SSO if ≤4 ASCII, else
|
|
201
|
+
// === String literal: "abc" → SSO if ≤4 ASCII, else static data ===
|
|
151
202
|
|
|
152
203
|
bind('str', (str) => {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
for (let i = 0; i < str.length; i++) packed |= str.charCodeAt(i) << (i * 8)
|
|
157
|
-
return mkPtrIR(PTR.STRING, LAYOUT.SSO_BIT | str.length, packed)
|
|
204
|
+
if (ctx.features.sso) {
|
|
205
|
+
const sso = ssoEncode(str)
|
|
206
|
+
if (sso) return mkPtrIR(PTR.STRING, sso.aux, sso.offset)
|
|
158
207
|
}
|
|
159
208
|
const bytes = new TextEncoder().encode(str)
|
|
160
209
|
const len = bytes.length
|
|
@@ -211,11 +260,7 @@ export default (ctx) => {
|
|
|
211
260
|
// SSO/STRING ptrs never have forwarding pointers (only ARRAY does), so we extract
|
|
212
261
|
// the raw offset directly instead of paying the __ptr_offset function-call overhead.
|
|
213
262
|
wat('__sso_char', `(func $__sso_char (param $ptr i64) (param $i i32) (result i32)
|
|
214
|
-
(
|
|
215
|
-
(i32.shr_u
|
|
216
|
-
(i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK})))
|
|
217
|
-
(i32.mul (local.get $i) (i32.const 8)))
|
|
218
|
-
(i32.const 0xFF)))`)
|
|
263
|
+
${ssoCharWat('(local.get $ptr)', '(local.get $i)')})`)
|
|
219
264
|
|
|
220
265
|
wat('__str_char', `(func $__str_char (param $ptr i64) (param $i i32) (result i32)
|
|
221
266
|
(i32.load8_u (i32.add
|
|
@@ -242,16 +287,9 @@ export default (ctx) => {
|
|
|
242
287
|
(i64.ne (i64.and (local.get $ptr) (i64.const ${SSO_BIT_I64})) (i64.const 0))
|
|
243
288
|
(then
|
|
244
289
|
(if (result i32)
|
|
245
|
-
(i32.ge_u (local.get $i)
|
|
246
|
-
(i32.and
|
|
247
|
-
(i32.wrap_i64 (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.AUX_SHIFT})))
|
|
248
|
-
(i32.const ${LAYOUT.SSO_BIT - 1})))
|
|
290
|
+
(i32.ge_u (local.get $i) ${ssoLenWat('(local.get $ptr)')})
|
|
249
291
|
(then (i32.const 0))
|
|
250
|
-
(else (
|
|
251
|
-
(i32.shr_u
|
|
252
|
-
(i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK})))
|
|
253
|
-
(i32.mul (local.get $i) (i32.const 8)))
|
|
254
|
-
(i32.const 0xFF)))))
|
|
292
|
+
(else ${ssoCharWat('(local.get $ptr)', '(local.get $i)')})))
|
|
255
293
|
(else
|
|
256
294
|
(if (result i32)
|
|
257
295
|
(i32.ge_u (local.get $i)
|
|
@@ -289,9 +327,7 @@ export default (ctx) => {
|
|
|
289
327
|
(i32.const ${LAYOUT.SSO_BIT})))
|
|
290
328
|
(local.set $len
|
|
291
329
|
(if (result i32) (local.get $isSso)
|
|
292
|
-
(then (
|
|
293
|
-
(i32.wrap_i64 (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.AUX_SHIFT})))
|
|
294
|
-
(i32.const ${LAYOUT.SSO_BIT - 1})))
|
|
330
|
+
(then ${ssoLenWat('(local.get $ptr)')})
|
|
295
331
|
(else
|
|
296
332
|
(if (result i32)
|
|
297
333
|
(i64.ne (i64.and (local.get $ptr) (i64.const ${SLICE_BIT_I64})) (i64.const 0))
|
|
@@ -306,14 +342,15 @@ export default (ctx) => {
|
|
|
306
342
|
(i32.or (i32.lt_s (local.get $i) (i32.const 0)) (i32.ge_u (local.get $i) (local.get $len)))
|
|
307
343
|
(then (f64.const nan:${UNDEF_NAN}))
|
|
308
344
|
(else
|
|
309
|
-
(f64.
|
|
310
|
-
(
|
|
311
|
-
|
|
312
|
-
(i64.
|
|
313
|
-
|
|
314
|
-
(
|
|
315
|
-
|
|
316
|
-
|
|
345
|
+
(if (result f64) (local.get $isSso)
|
|
346
|
+
;; SSO source: chars are ASCII (<0x80) → inline 1-char SSO (hot path, no branch).
|
|
347
|
+
(then (f64.reinterpret_i64
|
|
348
|
+
(i64.or
|
|
349
|
+
(i64.const ${ptrNanHex(PTR.STRING, ssoAux(1))})
|
|
350
|
+
(i64.extend_i32_u ${ssoCharWat('(local.get $ptr)', '(local.get $i)')}))))
|
|
351
|
+
;; Heap source: the byte may be ≥0x80 (non-ASCII), which can't be a 7-bit SSO,
|
|
352
|
+
;; so __char1byte routes those to a heap 1-byte string.
|
|
353
|
+
(else (call $__char1byte (i32.load8_u (i32.add (local.get $off) (local.get $i)))))))))`)
|
|
317
354
|
|
|
318
355
|
// Hot: ~53M calls in watr self-host. Bit-eq covers identity. SSO/SSO with !bit-eq
|
|
319
356
|
// guarantees content differs (high 32 bits encode type+len; both equal → low 32 differs
|
|
@@ -360,9 +397,12 @@ export default (ctx) => {
|
|
|
360
397
|
(local $ta i32) (local $tb i32)
|
|
361
398
|
(local $offA i32) (local $offB i32)
|
|
362
399
|
(local $ssoA i32) (local $ssoB i32)
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
400
|
+
;; Sole caller is __str_eq, which already returned for bit-equal pointers, both-SSO
|
|
401
|
+
;; (bit-ne ⇒ content-ne), both-canonical-interned (bit-ne ⇒ content-ne) and both-plain-
|
|
402
|
+
;; heap length mismatch. Re-testing them here is dead work on every cold call — skip to
|
|
403
|
+
;; the byte walk. (__str_eq/__str_eq_cold are ~14% of self-host runtime; this trims the
|
|
404
|
+
;; per-call fixed cost.) The heap/mixed paths below still decide every case correctly
|
|
405
|
+
;; on their own, so this stays correct even if a future caller skips the prelude.
|
|
366
406
|
(local.set $ta (i32.wrap_i64 (i64.and (i64.shr_u (local.get $a) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
367
407
|
(local.set $tb (i32.wrap_i64 (i64.and (i64.shr_u (local.get $b) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
368
408
|
(local.set $offA (i32.wrap_i64 (i64.and (local.get $a) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
@@ -373,22 +413,6 @@ export default (ctx) => {
|
|
|
373
413
|
(local.set $ssoB (i32.and
|
|
374
414
|
(i32.wrap_i64 (i64.shr_u (local.get $b) (i64.const ${LAYOUT.AUX_SHIFT})))
|
|
375
415
|
(i32.const ${LAYOUT.SSO_BIT})))
|
|
376
|
-
;; Both SSO with !bit-eq ⇒ content differs (high 32 bits hold tag+aux; both equal here).
|
|
377
|
-
(if (i32.and (local.get $ssoA) (local.get $ssoB))
|
|
378
|
-
(then (return (i32.const 0))))
|
|
379
|
-
;; Both CANONICAL interned heap strings (STR_INTERN_BIT, SSO/SLICE clear):
|
|
380
|
-
;; canonicals are deduped, so bit-ne ⇒ content-ne — the V8 pointer-compare
|
|
381
|
-
;; equivalent. This is the dominant unequal case in tag-dispatch chains
|
|
382
|
-
;; (op === 'literal' ladders over static literals).
|
|
383
|
-
(local.set $axA (i32.wrap_i64 (i64.shr_u (local.get $a) (i64.const ${LAYOUT.AUX_SHIFT}))))
|
|
384
|
-
(local.set $axB (i32.wrap_i64 (i64.shr_u (local.get $b) (i64.const ${LAYOUT.AUX_SHIFT}))))
|
|
385
|
-
(if (i32.and
|
|
386
|
-
(i32.and (i32.eq (local.get $ta) (i32.const ${PTR.STRING}))
|
|
387
|
-
(i32.eq (local.get $tb) (i32.const ${PTR.STRING})))
|
|
388
|
-
(i32.and
|
|
389
|
-
(i32.eq (i32.and (local.get $axA) (i32.const ${LAYOUT.SSO_BIT | LAYOUT.SLICE_BIT | STR_INTERN_BIT})) (i32.const ${STR_INTERN_BIT}))
|
|
390
|
-
(i32.eq (i32.and (local.get $axB) (i32.const ${LAYOUT.SSO_BIT | LAYOUT.SLICE_BIT | STR_INTERN_BIT})) (i32.const ${STR_INTERN_BIT}))))
|
|
391
|
-
(then (return (i32.const 0))))
|
|
392
416
|
;; Both heap STRING fast path: inline len from header. Chunk by 4 bytes via unaligned
|
|
393
417
|
;; i32.load (wasm guarantees unaligned-OK), then byte-tail. Most string comparisons fail
|
|
394
418
|
;; early on the first 4-byte word, so this collapses the per-byte branch overhead into a
|
|
@@ -477,7 +501,7 @@ export default (ctx) => {
|
|
|
477
501
|
(i32.wrap_i64 (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.AUX_SHIFT})))
|
|
478
502
|
(i32.const ${LAYOUT.AUX_MASK})))
|
|
479
503
|
(if (result i32) (i32.and (local.get $aux) (i32.const ${LAYOUT.SSO_BIT}))
|
|
480
|
-
(then (
|
|
504
|
+
(then ${ssoLenFromAux('(local.get $aux)')})
|
|
481
505
|
(else
|
|
482
506
|
(if (result i32) (i32.and (local.get $aux) (i32.const ${LAYOUT.SLICE_BIT}))
|
|
483
507
|
;; view: length lives in aux[12:0], not a header.
|
|
@@ -495,21 +519,11 @@ export default (ctx) => {
|
|
|
495
519
|
// (single bulk op instead of nlen × __char_at).
|
|
496
520
|
wat('__str_slice', () => `(func $__str_slice (param $ptr i64) (param $start i32) (param $end i32) (result f64)
|
|
497
521
|
(local $len i32) (local $nlen i32) (local $off i32) (local $i i32)
|
|
498
|
-
(local $srcOff i32) (local $isSso i32) (local $sb i32) (local $sp i32)
|
|
522
|
+
(local $srcOff i32) (local $isSso i32) (local $sb i32) (local $sp i32) (local $sp64 i64)
|
|
499
523
|
(local $ip i32) (local $h i32) (local $j i32) (local $slot i32) (local $cand i32) (local $k i32)
|
|
500
524
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
501
|
-
(
|
|
502
|
-
|
|
503
|
-
(if (i32.lt_s (local.get $end) (i32.const 0))
|
|
504
|
-
(then (local.set $end (i32.add (local.get $len) (local.get $end)))))
|
|
505
|
-
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
506
|
-
(then (local.set $start (i32.const 0))))
|
|
507
|
-
(if (i32.gt_s (local.get $start) (local.get $len))
|
|
508
|
-
(then (local.set $start (local.get $len))))
|
|
509
|
-
(if (i32.lt_s (local.get $end) (i32.const 0))
|
|
510
|
-
(then (local.set $end (i32.const 0))))
|
|
511
|
-
(if (i32.gt_s (local.get $end) (local.get $len))
|
|
512
|
-
(then (local.set $end (local.get $len))))
|
|
525
|
+
(local.set $start (call $__clamp_idx (local.get $start) (local.get $len)))
|
|
526
|
+
(local.set $end (call $__clamp_idx (local.get $end) (local.get $len)))
|
|
513
527
|
(if (i32.ge_s (local.get $start) (local.get $end))
|
|
514
528
|
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
|
|
515
529
|
(local.set $nlen (i32.sub (local.get $end) (local.get $start)))
|
|
@@ -548,18 +562,8 @@ export default (ctx) => {
|
|
|
548
562
|
(local $len i32) (local $nlen i32) (local $srcOff i32) (local $tag i32)
|
|
549
563
|
(local $ip i32) (local $h i32) (local $j i32) (local $slot i32) (local $cand i32) (local $k i32)
|
|
550
564
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
551
|
-
(
|
|
552
|
-
|
|
553
|
-
(if (i32.lt_s (local.get $end) (i32.const 0))
|
|
554
|
-
(then (local.set $end (i32.add (local.get $len) (local.get $end)))))
|
|
555
|
-
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
556
|
-
(then (local.set $start (i32.const 0))))
|
|
557
|
-
(if (i32.gt_s (local.get $start) (local.get $len))
|
|
558
|
-
(then (local.set $start (local.get $len))))
|
|
559
|
-
(if (i32.lt_s (local.get $end) (i32.const 0))
|
|
560
|
-
(then (local.set $end (i32.const 0))))
|
|
561
|
-
(if (i32.gt_s (local.get $end) (local.get $len))
|
|
562
|
-
(then (local.set $end (local.get $len))))
|
|
565
|
+
(local.set $start (call $__clamp_idx (local.get $start) (local.get $len)))
|
|
566
|
+
(local.set $end (call $__clamp_idx (local.get $end) (local.get $len)))
|
|
563
567
|
(if (i32.ge_s (local.get $start) (local.get $end))
|
|
564
568
|
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
|
|
565
569
|
(local.set $nlen (i32.sub (local.get $end) (local.get $start)))
|
|
@@ -664,18 +668,8 @@ export default (ctx) => {
|
|
|
664
668
|
wat('__str_slice_eq', `(func $__str_slice_eq (param $ptr i64) (param $start i32) (param $end i32) (param $other i64) (result i32)
|
|
665
669
|
(local $len i32)
|
|
666
670
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
667
|
-
(
|
|
668
|
-
|
|
669
|
-
(if (i32.lt_s (local.get $end) (i32.const 0))
|
|
670
|
-
(then (local.set $end (i32.add (local.get $len) (local.get $end)))))
|
|
671
|
-
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
672
|
-
(then (local.set $start (i32.const 0))))
|
|
673
|
-
(if (i32.gt_s (local.get $start) (local.get $len))
|
|
674
|
-
(then (local.set $start (local.get $len))))
|
|
675
|
-
(if (i32.lt_s (local.get $end) (i32.const 0))
|
|
676
|
-
(then (local.set $end (i32.const 0))))
|
|
677
|
-
(if (i32.gt_s (local.get $end) (local.get $len))
|
|
678
|
-
(then (local.set $end (local.get $len))))
|
|
671
|
+
(local.set $start (call $__clamp_idx (local.get $start) (local.get $len)))
|
|
672
|
+
(local.set $end (call $__clamp_idx (local.get $end) (local.get $len)))
|
|
679
673
|
(call $__str_range_eq (local.get $ptr) (local.get $start) (local.get $end) (local.get $other)))`)
|
|
680
674
|
|
|
681
675
|
// Hoist SSO/heap dispatch for hay and ndl out of the inner byte loop. Inner
|
|
@@ -684,8 +678,8 @@ export default (ctx) => {
|
|
|
684
678
|
(local $hlen i32) (local $nlen i32) (local $i i32) (local $j i32) (local $match i32)
|
|
685
679
|
(local $hoff i32) (local $noff i32)
|
|
686
680
|
(local $hsso i32) (local $nsso i32) (local $hb i32) (local $nb i32) (local $k i32)
|
|
687
|
-
;; ToString the
|
|
688
|
-
|
|
681
|
+
;; The search value is ToString'd at the call site (searchArg) per 21.1.3.9 step 4 —
|
|
682
|
+
;; a known-string needle passes raw, so this helper carries no float-pulling __to_str.
|
|
689
683
|
(local.set $hlen (call $__str_byteLen (local.get $hay)))
|
|
690
684
|
(local.set $nlen (call $__str_byteLen (local.get $ndl)))
|
|
691
685
|
(if (i32.eqz (local.get $nlen)) (then (return (local.get $from))))
|
|
@@ -707,10 +701,10 @@ export default (ctx) => {
|
|
|
707
701
|
(br_if $nomatch (i32.ge_s (local.get $j) (local.get $nlen)))
|
|
708
702
|
(local.set $k (i32.add (local.get $i) (local.get $j)))
|
|
709
703
|
(local.set $hb (if (result i32) (local.get $hsso)
|
|
710
|
-
(then (
|
|
704
|
+
(then ${ssoCharWat('(local.get $hay)', '(local.get $k)')})
|
|
711
705
|
(else (i32.load8_u (i32.add (local.get $hoff) (local.get $k))))))
|
|
712
706
|
(local.set $nb (if (result i32) (local.get $nsso)
|
|
713
|
-
(then (
|
|
707
|
+
(then ${ssoCharWat('(local.get $ndl)', '(local.get $j)')})
|
|
714
708
|
(else (i32.load8_u (i32.add (local.get $noff) (local.get $j))))))
|
|
715
709
|
(if (i32.ne (local.get $hb) (local.get $nb))
|
|
716
710
|
(then (local.set $match (i32.const 0)) (br $nomatch)))
|
|
@@ -729,8 +723,7 @@ export default (ctx) => {
|
|
|
729
723
|
(local $hlen i32) (local $nlen i32) (local $i i32) (local $j i32) (local $match i32)
|
|
730
724
|
(local $hoff i32) (local $noff i32)
|
|
731
725
|
(local $hsso i32) (local $nsso i32) (local $hb i32) (local $nb i32) (local $k i32)
|
|
732
|
-
;; ToString the
|
|
733
|
-
(local.set $ndl (call $__to_str (local.get $ndl)))
|
|
726
|
+
;; The search value is ToString'd at the call site (searchArg) per 21.1.3.10 step 4.
|
|
734
727
|
(local.set $hlen (call $__str_byteLen (local.get $hay)))
|
|
735
728
|
(local.set $nlen (call $__str_byteLen (local.get $ndl)))
|
|
736
729
|
;; Empty needle always matches at the clamp(from,0,hlen) position
|
|
@@ -760,10 +753,10 @@ export default (ctx) => {
|
|
|
760
753
|
(br_if $nomatch (i32.ge_s (local.get $j) (local.get $nlen)))
|
|
761
754
|
(local.set $k (i32.add (local.get $i) (local.get $j)))
|
|
762
755
|
(local.set $hb (if (result i32) (local.get $hsso)
|
|
763
|
-
(then (
|
|
756
|
+
(then ${ssoCharWat('(local.get $hay)', '(local.get $k)')})
|
|
764
757
|
(else (i32.load8_u (i32.add (local.get $hoff) (local.get $k))))))
|
|
765
758
|
(local.set $nb (if (result i32) (local.get $nsso)
|
|
766
|
-
(then (
|
|
759
|
+
(then ${ssoCharWat('(local.get $ndl)', '(local.get $j)')})
|
|
767
760
|
(else (i32.load8_u (i32.add (local.get $noff) (local.get $j))))))
|
|
768
761
|
(if (i32.ne (local.get $hb) (local.get $nb))
|
|
769
762
|
(then (local.set $match (i32.const 0)) (br $nomatch)))
|
|
@@ -793,10 +786,10 @@ export default (ctx) => {
|
|
|
793
786
|
(br_if $done (i32.ge_s (local.get $i) (local.get $plen)))
|
|
794
787
|
(if (i32.ne
|
|
795
788
|
(if (result i32) (local.get $ssso)
|
|
796
|
-
(then (
|
|
789
|
+
(then ${ssoCharWat('(local.get $str)', '(local.get $i)')})
|
|
797
790
|
(else (i32.load8_u (i32.add (local.get $soff) (local.get $i)))))
|
|
798
791
|
(if (result i32) (local.get $psso)
|
|
799
|
-
(then (
|
|
792
|
+
(then ${ssoCharWat('(local.get $pfx)', '(local.get $i)')})
|
|
800
793
|
(else (i32.load8_u (i32.add (local.get $poff) (local.get $i))))))
|
|
801
794
|
(then (return (i32.const 0))))
|
|
802
795
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
@@ -824,10 +817,10 @@ export default (ctx) => {
|
|
|
824
817
|
(local.set $k (i32.add (local.get $off) (local.get $i)))
|
|
825
818
|
(if (i32.ne
|
|
826
819
|
(if (result i32) (local.get $ssso)
|
|
827
|
-
(then (
|
|
820
|
+
(then ${ssoCharWat('(local.get $str)', '(local.get $k)')})
|
|
828
821
|
(else (i32.load8_u (i32.add (local.get $soff) (local.get $k)))))
|
|
829
822
|
(if (result i32) (local.get $fsso)
|
|
830
|
-
(then (
|
|
823
|
+
(then ${ssoCharWat('(local.get $sfx)', '(local.get $i)')})
|
|
831
824
|
(else (i32.load8_u (i32.add (local.get $foff) (local.get $i))))))
|
|
832
825
|
(then (return (i32.const 0))))
|
|
833
826
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
@@ -849,9 +842,7 @@ export default (ctx) => {
|
|
|
849
842
|
(then
|
|
850
843
|
(block $dsso (loop $lsso
|
|
851
844
|
(br_if $dsso (i32.ge_s (local.get $i) (local.get $len)))
|
|
852
|
-
(local.set $c (
|
|
853
|
-
(i32.shr_u (local.get $srcOff) (i32.shl (local.get $i) (i32.const 3)))
|
|
854
|
-
(i32.const 0xFF)))
|
|
845
|
+
(local.set $c ${ssoCharWat('(local.get $ptr)', '(local.get $i)')})
|
|
855
846
|
(if (i32.and (i32.ge_u (local.get $c) (local.get $lo)) (i32.le_u (local.get $c) (local.get $hi)))
|
|
856
847
|
(then (local.set $c (i32.add (local.get $c) (local.get $delta)))))
|
|
857
848
|
(i32.store8 (i32.add (local.get $off) (local.get $i)) (local.get $c))
|
|
@@ -953,7 +944,7 @@ export default (ctx) => {
|
|
|
953
944
|
;; Array (type=1) → join(",") like JS Array.toString()
|
|
954
945
|
(if (i32.eq (local.get $type) (i32.const ${PTR.ARRAY}))
|
|
955
946
|
(then (return (i64.reinterpret_f64 (call $__str_join (local.get $val)
|
|
956
|
-
(i64.reinterpret_f64 (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${
|
|
947
|
+
(i64.reinterpret_f64 (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${ssoAux(1)}) (i32.const 44))))))))
|
|
957
948
|
(local.get $val))`)
|
|
958
949
|
|
|
959
950
|
// Copy bytes of a string (SSO or heap) into memory at dst. Uses memory.copy for
|
|
@@ -962,18 +953,15 @@ export default (ctx) => {
|
|
|
962
953
|
(local $w i32)
|
|
963
954
|
(if (i64.ne (i64.and (local.get $src) (i64.const ${SSO_BIT_I64})) (i64.const 0))
|
|
964
955
|
(then
|
|
965
|
-
;; SSO:
|
|
966
|
-
;;
|
|
967
|
-
(local.set $w (i32.
|
|
968
|
-
(
|
|
969
|
-
(
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
(i32.store8 offset=1 (local.get $dst) (i32.shr_u (local.get $w) (i32.const 8)))
|
|
975
|
-
(if (i32.eq (local.get $len) (i32.const 2)) (then (return)))
|
|
976
|
-
(i32.store8 offset=2 (local.get $dst) (i32.shr_u (local.get $w) (i32.const 16))))))
|
|
956
|
+
;; SSO: write $len ASCII chars, each 7-bit-packed at payload bit i*7 (chars 4-5
|
|
957
|
+
;; span into aux, so read via the 7-bit codec, not the low 32 bits). $w = index.
|
|
958
|
+
(local.set $w (i32.const 0))
|
|
959
|
+
(loop $ssocp
|
|
960
|
+
(if (i32.lt_u (local.get $w) (local.get $len))
|
|
961
|
+
(then
|
|
962
|
+
(i32.store8 (i32.add (local.get $dst) (local.get $w)) ${ssoCharWat('(local.get $src)', '(local.get $w)')})
|
|
963
|
+
(local.set $w (i32.add (local.get $w) (i32.const 1)))
|
|
964
|
+
(br $ssocp)))))
|
|
977
965
|
(else
|
|
978
966
|
;; Heap STRING: memory.copy directly from string data
|
|
979
967
|
(memory.copy (local.get $dst)
|
|
@@ -1005,12 +993,12 @@ export default (ctx) => {
|
|
|
1005
993
|
(then
|
|
1006
994
|
(return (call $__mkptr
|
|
1007
995
|
(i32.const ${PTR.STRING})
|
|
1008
|
-
(i32.or (i32.const ${LAYOUT.SSO_BIT}) (local.get $total))
|
|
996
|
+
(i32.or (i32.const ${LAYOUT.SSO_BIT}) (i32.shl (local.get $total) (i32.const ${SSO_LEN_SHIFT})))
|
|
1009
997
|
(i32.or
|
|
1010
998
|
(i32.wrap_i64 (i64.and (local.get $a) (i64.const 0xFFFFFFFF)))
|
|
1011
999
|
(i32.shl
|
|
1012
1000
|
(i32.wrap_i64 (i64.and (local.get $b) (i64.const 0xFFFFFFFF)))
|
|
1013
|
-
(i32.
|
|
1001
|
+
(i32.mul (local.get $alen) (i32.const 7))))))))`
|
|
1014
1002
|
|
|
1015
1003
|
const concatFast = !ctx.memory.shared && ctx.transform.alloc !== false ? `
|
|
1016
1004
|
(local.set $ta (i32.wrap_i64 (i64.and (i64.shr_u (local.get $a) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
@@ -1075,19 +1063,17 @@ export default (ctx) => {
|
|
|
1075
1063
|
(i32.wrap_i64 (i64.shr_u (local.get $a) (i64.const ${LAYOUT.AUX_SHIFT})))
|
|
1076
1064
|
(i32.const ${LAYOUT.SSO_BIT})))
|
|
1077
1065
|
(then
|
|
1078
|
-
(local.set $alen (
|
|
1079
|
-
(i32.wrap_i64 (i64.shr_u (local.get $a) (i64.const ${LAYOUT.AUX_SHIFT})))
|
|
1080
|
-
(i32.const ${LAYOUT.SSO_BIT - 1})))
|
|
1066
|
+
(local.set $alen ${ssoLenWat('(local.get $a)')})
|
|
1081
1067
|
(if (i32.and
|
|
1082
1068
|
(i32.lt_u (local.get $alen) (i32.const 4))
|
|
1083
1069
|
(i32.lt_u (local.get $byte) (i32.const 0x80)))
|
|
1084
1070
|
(then
|
|
1085
1071
|
(return (call $__mkptr
|
|
1086
1072
|
(i32.const ${PTR.STRING})
|
|
1087
|
-
(i32.or (i32.const ${LAYOUT.SSO_BIT}) (i32.add (local.get $alen) (i32.const 1)))
|
|
1073
|
+
(i32.or (i32.const ${LAYOUT.SSO_BIT}) (i32.shl (i32.add (local.get $alen) (i32.const 1)) (i32.const ${SSO_LEN_SHIFT})))
|
|
1088
1074
|
(i32.or
|
|
1089
1075
|
(local.get $aoff)
|
|
1090
|
-
(i32.shl (local.get $byte) (i32.
|
|
1076
|
+
(i32.shl (local.get $byte) (i32.mul (local.get $alen) (i32.const 7))))))))))
|
|
1091
1077
|
;; Slow path: allocate new heap STRING with original bytes + 1 new byte
|
|
1092
1078
|
(local.set $alen (call $__str_byteLen (local.get $a)))
|
|
1093
1079
|
(local.set $total (i32.add (local.get $alen) (i32.const 1)))
|
|
@@ -1368,11 +1354,11 @@ export default (ctx) => {
|
|
|
1368
1354
|
(then
|
|
1369
1355
|
(local.set $sb (i32.load8_u (i32.add (local.get $off) (local.get $i))))
|
|
1370
1356
|
(br_if $heap (i32.ge_u (local.get $sb) (i32.const 0x80)))
|
|
1371
|
-
(local.set $sp (i32.or (local.get $sp) (i32.shl (local.get $sb) (i32.
|
|
1357
|
+
(local.set $sp (i32.or (local.get $sp) (i32.shl (local.get $sb) (i32.mul (local.get $i) (i32.const 7)))))
|
|
1372
1358
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
1373
1359
|
(br $pk))))
|
|
1374
1360
|
(global.set $__heap (i32.sub (local.get $off) (i32.const 4)))
|
|
1375
|
-
(return (call $__mkptr (i32.const ${PTR.STRING}) (i32.or (i32.const ${LAYOUT.SSO_BIT}) (local.get $target)) (local.get $sp))))))` : ''}
|
|
1361
|
+
(return (call $__mkptr (i32.const ${PTR.STRING}) (i32.or (i32.const ${LAYOUT.SSO_BIT}) (i32.shl (local.get $target) (i32.const 10))) (local.get $sp))))))` : ''}
|
|
1376
1362
|
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`)
|
|
1377
1363
|
|
|
1378
1364
|
// Base helpers (__sso_char/__str_char/__char_at/__str_byteLen) are referenced
|
|
@@ -1430,9 +1416,22 @@ export default (ctx) => {
|
|
|
1430
1416
|
// covers string/number/null/undefined needles, but two cases need help here:
|
|
1431
1417
|
// a BOOL rides the 0/1 carrier (→ "0"/"1" not "true"/"false"), and an OBJECT
|
|
1432
1418
|
// needs compile-time ToPrimitive(string) (__to_str can't invoke user toString).
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1419
|
+
// Coerce the search operand to a string AT THE CALL SITE (21.1.3.x ToString step),
|
|
1420
|
+
// so the `__str_indexof` family carries no embedded `__to_str`. A known-STRING arg
|
|
1421
|
+
// (the overwhelmingly common `s.indexOf("x")` / `s.indexOf(t)` shape) passes raw —
|
|
1422
|
+
// dropping the whole ToString → float-formatter dep tree (~4 KB) that an internal,
|
|
1423
|
+
// unconditional coercion forced into every search-method program. Mirrors
|
|
1424
|
+
// `stringSearchMethod` (startsWith/endsWith), which has always coerced here.
|
|
1425
|
+
const searchArg = (search) => {
|
|
1426
|
+
const vt = valTypeOf(search)
|
|
1427
|
+
if (vt === VAL.STRING) return asI64(emit(search))
|
|
1428
|
+
if (vt === VAL.BOOL) return asI64(bool(search))
|
|
1429
|
+
if (vt === VAL.OBJECT) return toStrI64(search, emit(search))
|
|
1430
|
+
inc('__to_str')
|
|
1431
|
+
return ['call', '$__to_str', asI64(emit(search))]
|
|
1432
|
+
}
|
|
1433
|
+
// Replacement operand of .replace/.replaceAll — same call-site ToString as searchArg.
|
|
1434
|
+
const strReplArg = searchArg
|
|
1436
1435
|
|
|
1437
1436
|
bind('.string:indexOf', (str, search, from) => {
|
|
1438
1437
|
inc('__str_indexof')
|
|
@@ -1573,9 +1572,14 @@ export default (ctx) => {
|
|
|
1573
1572
|
asI64(tail)], 'f64')]]], 'f64')
|
|
1574
1573
|
}
|
|
1575
1574
|
inc('__str_replace')
|
|
1576
|
-
|
|
1575
|
+
// search/repl ToString'd at the call site (searchArg) — __str_replace's __str_indexof
|
|
1576
|
+
// no longer coerces internally, so a non-string search must be stringified here.
|
|
1577
|
+
return typed(['call', '$__str_replace', asI64(emit(str)), searchArg(search), strReplArg(repl)], 'f64')
|
|
1578
|
+
})
|
|
1579
|
+
bind('.replaceAll', (str, search, repl) => {
|
|
1580
|
+
inc('__str_replaceall')
|
|
1581
|
+
return typed(['call', '$__str_replaceall', asI64(emit(str)), searchArg(search), strReplArg(repl)], 'f64')
|
|
1577
1582
|
})
|
|
1578
|
-
bind('.replaceAll', method('__str_replaceall', 'III'))
|
|
1579
1583
|
|
|
1580
1584
|
const caseMethod = (lo, hi, delta) => (str) => {
|
|
1581
1585
|
inc('__str_case')
|
|
@@ -1592,7 +1596,7 @@ export default (ctx) => {
|
|
|
1592
1596
|
|
|
1593
1597
|
const padMethod = (start) => (str, len, pad) => {
|
|
1594
1598
|
inc('__str_pad')
|
|
1595
|
-
const vpad = pad != null ? asI64(emit(pad)) : ['i64.reinterpret_f64', mkPtrIR(PTR.STRING,
|
|
1599
|
+
const vpad = pad != null ? asI64(emit(pad)) : ['i64.reinterpret_f64', mkPtrIR(PTR.STRING, ssoAux(1), 32)]
|
|
1596
1600
|
return typed(['call', '$__str_pad', asI64(emit(str)), asI32(emit(len)), vpad, ['i32.const', start]], 'f64')
|
|
1597
1601
|
}
|
|
1598
1602
|
bind('.padStart', padMethod(1))
|
|
@@ -1659,14 +1663,17 @@ export default (ctx) => {
|
|
|
1659
1663
|
// index; otherwise `oobIR`. Without the bounds check `__char_at` returns 0 for an
|
|
1660
1664
|
// out-of-range index, which would wrap to a bogus `"\x00"` string (charAt → "",
|
|
1661
1665
|
// at → undefined). `sLocal` is the receiver as an f64 local.
|
|
1662
|
-
const charAtOr = (sLocal, iLocal, lenIR, oobIR) =>
|
|
1666
|
+
const charAtOr = (sLocal, iLocal, lenIR, oobIR) => (
|
|
1667
|
+
inc('__char1byte'),
|
|
1663
1668
|
['if', ['result', 'f64'],
|
|
1664
1669
|
['i32.and',
|
|
1665
1670
|
['i32.ge_s', ['local.get', iLocal], ['i32.const', 0]],
|
|
1666
1671
|
['i32.lt_s', ['local.get', iLocal], lenIR]],
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
['
|
|
1672
|
+
// __char_at yields a raw byte (≥0x80 for non-ASCII heap strings) → __char1byte
|
|
1673
|
+
// builds an SSO byte when ASCII, else a heap 1-byte string.
|
|
1674
|
+
['then', ['call', '$__char1byte',
|
|
1675
|
+
['call', '$__char_at', ['i64.reinterpret_f64', ['local.get', sLocal]], ['local.get', iLocal]]]],
|
|
1676
|
+
['else', oobIR]])
|
|
1670
1677
|
const emptyStr = mkPtrIR(PTR.STRING, LAYOUT.SSO_BIT, 0)
|
|
1671
1678
|
|
|
1672
1679
|
// .charAt(i) → 1-char string at index i, or "" when out of range (JS spec: no
|
|
@@ -1713,13 +1720,27 @@ export default (ctx) => {
|
|
|
1713
1720
|
return typed(['f64.reinterpret_i64', toStrI64(value, emit(value))], 'f64')
|
|
1714
1721
|
})
|
|
1715
1722
|
|
|
1723
|
+
// 1-byte string from a raw byte: SSO when ASCII (<0x80, fits the 7-bit codec),
|
|
1724
|
+
// else a heap 1-byte string (the 7-bit SSO can't represent bytes ≥0x80).
|
|
1725
|
+
wat('__char1byte', `(func $__char1byte (param $b i32) (result f64)
|
|
1726
|
+
(local $off i32)
|
|
1727
|
+
(local.set $b (i32.and (local.get $b) (i32.const 0xFF)))
|
|
1728
|
+
(if (result f64) (i32.lt_u (local.get $b) (i32.const 0x80))
|
|
1729
|
+
(then (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${ssoAux(1)}) (local.get $b)))
|
|
1730
|
+
(else
|
|
1731
|
+
(local.set $off (call $__alloc (i32.const 8)))
|
|
1732
|
+
(i32.store (local.get $off) (i32.const 1))
|
|
1733
|
+
(i32.store8 (i32.add (local.get $off) (i32.const 4)) (local.get $b))
|
|
1734
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (i32.add (local.get $off) (i32.const 4))))))`)
|
|
1735
|
+
|
|
1716
1736
|
// String.fromCharCode(...codes) — variadic; each arg is ToUint16(ToNumber(code))
|
|
1717
1737
|
// → a 1-byte string, concatenated left to right (mirrors String.fromCodePoint).
|
|
1718
1738
|
bind('String.fromCharCode', (...codes) => {
|
|
1719
1739
|
if (codes.length === 0) return emit(['str', ''])
|
|
1720
1740
|
// ToUint16(ToNumber(code)): `toNumF64` performs ToPrimitive on an object
|
|
1721
|
-
// argument, so a throwing valueOf/toString propagates per spec.
|
|
1722
|
-
|
|
1741
|
+
// argument, so a throwing valueOf/toString propagates per spec. A byte ≥0x80
|
|
1742
|
+
// can't be a 7-bit-ASCII SSO, so __char1byte routes it to a heap 1-byte string.
|
|
1743
|
+
const one = (node) => { inc('__char1byte'); return typed(['call', '$__char1byte', asI32(toNumF64(node, emit(node)))], 'f64') }
|
|
1723
1744
|
let r = one(codes[0])
|
|
1724
1745
|
for (let i = 1; i < codes.length; i++) {
|
|
1725
1746
|
inc('__str_concat_raw')
|
|
@@ -1742,27 +1763,41 @@ export default (ctx) => {
|
|
|
1742
1763
|
(local.set $cp (i32.trunc_sat_f64_s (local.get $cpf)))
|
|
1743
1764
|
;; ASCII: 1 byte SSO
|
|
1744
1765
|
(if (i32.lt_u (local.get $cp) (i32.const 128))
|
|
1745
|
-
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${
|
|
1746
|
-
;;
|
|
1766
|
+
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${ssoAux(1)}) (local.get $cp)))))
|
|
1767
|
+
;; multi-byte (cp ≥ 0x80): UTF-8 bytes are ≥0x80 → can't be 7-bit ASCII SSO, so
|
|
1768
|
+
;; build a heap STRING (len header at $off, bytes at $off+4). Over-alloc to 8 so the
|
|
1769
|
+
;; i32.store of the packed bytes never runs past the buffer; the len word bounds reads.
|
|
1770
|
+
;; 2-byte: 0x80-0x7FF
|
|
1747
1771
|
(if (i32.lt_u (local.get $cp) (i32.const 0x800))
|
|
1748
|
-
(then
|
|
1749
|
-
(i32.
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1772
|
+
(then
|
|
1773
|
+
(local.set $off (call $__alloc (i32.const 8)))
|
|
1774
|
+
(i32.store (local.get $off) (i32.const 2))
|
|
1775
|
+
(i32.store (i32.add (local.get $off) (i32.const 4))
|
|
1776
|
+
(i32.or
|
|
1777
|
+
(i32.or (i32.const 0xC0) (i32.shr_u (local.get $cp) (i32.const 6)))
|
|
1778
|
+
(i32.shl (i32.or (i32.const 0x80) (i32.and (local.get $cp) (i32.const 0x3F))) (i32.const 8))))
|
|
1779
|
+
(return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (i32.add (local.get $off) (i32.const 4))))))
|
|
1780
|
+
;; 3-byte: 0x800-0xFFFF
|
|
1753
1781
|
(if (i32.lt_u (local.get $cp) (i32.const 0x10000))
|
|
1754
|
-
(then
|
|
1755
|
-
(
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
(i32.
|
|
1759
|
-
|
|
1760
|
-
|
|
1782
|
+
(then
|
|
1783
|
+
(local.set $off (call $__alloc (i32.const 8)))
|
|
1784
|
+
(i32.store (local.get $off) (i32.const 3))
|
|
1785
|
+
(i32.store (i32.add (local.get $off) (i32.const 4))
|
|
1786
|
+
(i32.or (i32.or
|
|
1787
|
+
(i32.or (i32.const 0xE0) (i32.shr_u (local.get $cp) (i32.const 12)))
|
|
1788
|
+
(i32.shl (i32.or (i32.const 0x80) (i32.and (i32.shr_u (local.get $cp) (i32.const 6)) (i32.const 0x3F))) (i32.const 8)))
|
|
1789
|
+
(i32.shl (i32.or (i32.const 0x80) (i32.and (local.get $cp) (i32.const 0x3F))) (i32.const 16))))
|
|
1790
|
+
(return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (i32.add (local.get $off) (i32.const 4))))))
|
|
1791
|
+
;; 4-byte: 0x10000-0x10FFFF
|
|
1792
|
+
(local.set $off (call $__alloc (i32.const 8)))
|
|
1793
|
+
(i32.store (local.get $off) (i32.const 4))
|
|
1794
|
+
(i32.store (i32.add (local.get $off) (i32.const 4))
|
|
1761
1795
|
(i32.or (i32.or (i32.or
|
|
1762
1796
|
(i32.or (i32.const 0xF0) (i32.shr_u (local.get $cp) (i32.const 18)))
|
|
1763
1797
|
(i32.shl (i32.or (i32.const 0x80) (i32.and (i32.shr_u (local.get $cp) (i32.const 12)) (i32.const 0x3F))) (i32.const 8)))
|
|
1764
1798
|
(i32.shl (i32.or (i32.const 0x80) (i32.and (i32.shr_u (local.get $cp) (i32.const 6)) (i32.const 0x3F))) (i32.const 16)))
|
|
1765
|
-
(i32.shl (i32.or (i32.const 0x80) (i32.and (local.get $cp) (i32.const 0x3F))) (i32.const 24))))
|
|
1799
|
+
(i32.shl (i32.or (i32.const 0x80) (i32.and (local.get $cp) (i32.const 0x3F))) (i32.const 24))))
|
|
1800
|
+
(return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (i32.add (local.get $off) (i32.const 4)))))`)
|
|
1766
1801
|
|
|
1767
1802
|
// String.fromCodePoint(...codePoints) — variadic; each arg is ToNumber-coerced
|
|
1768
1803
|
// then validated/encoded by __fromCodePoint, results concatenated left to right.
|