jz 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -33
- package/bench/README.md +176 -73
- package/bench/bench.svg +58 -71
- package/cli.js +12 -5
- package/dist/interop.js +1 -1
- package/dist/jz.js +1366 -1101
- package/index.js +40 -9
- package/interop.js +193 -138
- package/layout.js +29 -18
- package/module/array.js +49 -73
- package/module/collection.js +83 -25
- package/module/console.js +1 -1
- package/module/core.js +161 -15
- package/module/json.js +3 -3
- package/module/math.js +167 -117
- package/module/number.js +247 -13
- package/module/object.js +11 -5
- package/module/regex.js +8 -7
- package/module/string.js +295 -171
- package/module/typedarray.js +169 -105
- package/package.json +7 -3
- package/src/abi/string.js +40 -35
- package/src/ast.js +19 -2
- package/src/compile/analyze.js +64 -2
- package/src/compile/cse-load.js +200 -0
- package/src/compile/emit-assign.js +73 -14
- package/src/compile/emit.js +324 -34
- package/src/compile/index.js +204 -61
- package/src/compile/infer.js +8 -1
- package/src/compile/loop-divmod.js +12 -58
- package/src/compile/loop-model.js +91 -0
- package/src/compile/loop-recurrence.js +167 -0
- package/src/compile/loop-square.js +102 -0
- package/src/compile/narrow.js +180 -34
- package/src/compile/peel-stencil.js +18 -64
- package/src/compile/plan/common.js +29 -0
- package/src/compile/plan/index.js +4 -1
- package/src/compile/plan/inline.js +176 -21
- package/src/compile/plan/literals.js +93 -19
- package/src/ctx.js +51 -12
- package/src/helper-counters.js +137 -0
- package/src/ir.js +102 -13
- package/src/kind-traits.js +7 -3
- package/src/kind.js +14 -1
- package/src/op-policy.js +5 -2
- package/src/ops.js +119 -0
- package/src/optimize/index.js +1125 -136
- package/src/optimize/recurse.js +182 -0
- package/src/optimize/vectorize.js +1302 -144
- package/src/prepare/index.js +29 -12
- package/src/reps.js +4 -1
- package/src/type.js +53 -45
- package/src/wat/assemble.js +92 -9
- package/src/widen.js +21 -0
- package/src/wat/optimize.js +0 -3938
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
|
|
@@ -110,10 +144,17 @@ export default (ctx) => {
|
|
|
110
144
|
deps({
|
|
111
145
|
__str_concat: ['__to_str', '__str_byteLen', '__alloc', '__memgrow', '__mkptr', '__str_copy'],
|
|
112
146
|
__str_concat_raw: ['__str_byteLen', '__alloc', '__memgrow', '__mkptr', '__str_copy'],
|
|
147
|
+
__str_concat_fresh: ['__to_str', '__str_byteLen', '__alloc', '__mkptr', '__str_copy'],
|
|
148
|
+
__str_concat_raw_fresh: ['__str_byteLen', '__alloc', '__mkptr', '__str_copy'],
|
|
113
149
|
__str_append_byte: ['__str_byteLen', '__alloc', '__memgrow', '__mkptr', '__str_copy'],
|
|
114
150
|
__str_copy: [],
|
|
115
|
-
__str_slice:
|
|
116
|
-
|
|
151
|
+
// __str_slice/_view are FUNCTION templates: resolveIncludes' auto-dep scan realizes the
|
|
152
|
+
// factory (v()) to discover body calls, but that realization DIVERGES under self-host
|
|
153
|
+
// (jz.wasm) — so a body-called helper not also listed here is dropped from the kernel
|
|
154
|
+
// ("Unknown func $__clamp_idx" on `str.slice`). FN templates must declare body deps
|
|
155
|
+
// manually; pinned by test/selfhost-includes.js.
|
|
156
|
+
__str_slice: ['__str_byteLen', '__alloc', '__clamp_idx', '__mkptr'],
|
|
157
|
+
__str_slice_view: ['__str_byteLen', '__mkptr', '__str_slice', '__clamp_idx'],
|
|
117
158
|
__str_indexof: ['__str_byteLen'],
|
|
118
159
|
__str_lastindexof: ['__str_byteLen'],
|
|
119
160
|
__wrap1: ['__alloc', '__mkptr'],
|
|
@@ -128,15 +169,15 @@ export default (ctx) => {
|
|
|
128
169
|
__str_replace: ['__str_indexof', '__str_slice', '__str_concat'],
|
|
129
170
|
__str_replaceall: ['__str_indexof', '__str_slice', '__str_concat'],
|
|
130
171
|
__str_split: ['__str_slice', '__str_byteLen', '__char_at', '__alloc'],
|
|
131
|
-
__str_idx: [],
|
|
172
|
+
__str_idx: ['__char1byte'],
|
|
132
173
|
__str_eq: ['__str_eq_cold'],
|
|
133
174
|
__str_eq_cold: ['__char_at', '__str_byteLen'],
|
|
134
175
|
__str_cmp: ['__char_at', '__str_byteLen'],
|
|
135
176
|
__str_range_eq: ['__char_at', '__str_byteLen'],
|
|
136
177
|
__str_substring_eq: ['__str_byteLen', '__str_range_eq'],
|
|
137
|
-
__str_slice_eq: ['__str_byteLen', '__str_range_eq'],
|
|
178
|
+
__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
179
|
__str_pad: ['__str_byteLen', '__str_copy', '__alloc'],
|
|
139
|
-
__str_join: ['__str_concat', '__to_str', '__str_byteLen', '__len', '__ptr_offset'],
|
|
180
|
+
__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
181
|
__str_encode: ['__str_byteLen', '__str_copy'],
|
|
141
182
|
__encodeURIComponent: ['__to_str', '__str_byteLen', '__char_at', '__alloc', '__mkptr'],
|
|
142
183
|
__decodeURIComponent: ['__to_str', '__str_byteLen', '__char_at', '__alloc', '__mkptr', '__uri_hex'],
|
|
@@ -162,11 +203,9 @@ export default (ctx) => {
|
|
|
162
203
|
// === String literal: "abc" → SSO if ≤4 ASCII, else static data ===
|
|
163
204
|
|
|
164
205
|
bind('str', (str) => {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
for (let i = 0; i < str.length; i++) packed |= str.charCodeAt(i) << (i * 8)
|
|
169
|
-
return mkPtrIR(PTR.STRING, LAYOUT.SSO_BIT | str.length, packed)
|
|
206
|
+
if (ctx.features.sso) {
|
|
207
|
+
const sso = ssoEncode(str)
|
|
208
|
+
if (sso) return mkPtrIR(PTR.STRING, sso.aux, sso.offset)
|
|
170
209
|
}
|
|
171
210
|
const bytes = new TextEncoder().encode(str)
|
|
172
211
|
const len = bytes.length
|
|
@@ -223,11 +262,7 @@ export default (ctx) => {
|
|
|
223
262
|
// SSO/STRING ptrs never have forwarding pointers (only ARRAY does), so we extract
|
|
224
263
|
// the raw offset directly instead of paying the __ptr_offset function-call overhead.
|
|
225
264
|
wat('__sso_char', `(func $__sso_char (param $ptr i64) (param $i i32) (result i32)
|
|
226
|
-
(
|
|
227
|
-
(i32.shr_u
|
|
228
|
-
(i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK})))
|
|
229
|
-
(i32.mul (local.get $i) (i32.const 8)))
|
|
230
|
-
(i32.const 0xFF)))`)
|
|
265
|
+
${ssoCharWat('(local.get $ptr)', '(local.get $i)')})`)
|
|
231
266
|
|
|
232
267
|
wat('__str_char', `(func $__str_char (param $ptr i64) (param $i i32) (result i32)
|
|
233
268
|
(i32.load8_u (i32.add
|
|
@@ -254,16 +289,9 @@ export default (ctx) => {
|
|
|
254
289
|
(i64.ne (i64.and (local.get $ptr) (i64.const ${SSO_BIT_I64})) (i64.const 0))
|
|
255
290
|
(then
|
|
256
291
|
(if (result i32)
|
|
257
|
-
(i32.ge_u (local.get $i)
|
|
258
|
-
(i32.and
|
|
259
|
-
(i32.wrap_i64 (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.AUX_SHIFT})))
|
|
260
|
-
(i32.const ${LAYOUT.SSO_BIT - 1})))
|
|
292
|
+
(i32.ge_u (local.get $i) ${ssoLenWat('(local.get $ptr)')})
|
|
261
293
|
(then (i32.const 0))
|
|
262
|
-
(else (
|
|
263
|
-
(i32.shr_u
|
|
264
|
-
(i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK})))
|
|
265
|
-
(i32.mul (local.get $i) (i32.const 8)))
|
|
266
|
-
(i32.const 0xFF)))))
|
|
294
|
+
(else ${ssoCharWat('(local.get $ptr)', '(local.get $i)')})))
|
|
267
295
|
(else
|
|
268
296
|
(if (result i32)
|
|
269
297
|
(i32.ge_u (local.get $i)
|
|
@@ -301,9 +329,7 @@ export default (ctx) => {
|
|
|
301
329
|
(i32.const ${LAYOUT.SSO_BIT})))
|
|
302
330
|
(local.set $len
|
|
303
331
|
(if (result i32) (local.get $isSso)
|
|
304
|
-
(then (
|
|
305
|
-
(i32.wrap_i64 (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.AUX_SHIFT})))
|
|
306
|
-
(i32.const ${LAYOUT.SSO_BIT - 1})))
|
|
332
|
+
(then ${ssoLenWat('(local.get $ptr)')})
|
|
307
333
|
(else
|
|
308
334
|
(if (result i32)
|
|
309
335
|
(i64.ne (i64.and (local.get $ptr) (i64.const ${SLICE_BIT_I64})) (i64.const 0))
|
|
@@ -318,14 +344,15 @@ export default (ctx) => {
|
|
|
318
344
|
(i32.or (i32.lt_s (local.get $i) (i32.const 0)) (i32.ge_u (local.get $i) (local.get $len)))
|
|
319
345
|
(then (f64.const nan:${UNDEF_NAN}))
|
|
320
346
|
(else
|
|
321
|
-
(f64.
|
|
322
|
-
(
|
|
323
|
-
|
|
324
|
-
(i64.
|
|
325
|
-
|
|
326
|
-
(
|
|
327
|
-
|
|
328
|
-
|
|
347
|
+
(if (result f64) (local.get $isSso)
|
|
348
|
+
;; SSO source: chars are ASCII (<0x80) → inline 1-char SSO (hot path, no branch).
|
|
349
|
+
(then (f64.reinterpret_i64
|
|
350
|
+
(i64.or
|
|
351
|
+
(i64.const ${ptrNanHex(PTR.STRING, ssoAux(1))})
|
|
352
|
+
(i64.extend_i32_u ${ssoCharWat('(local.get $ptr)', '(local.get $i)')}))))
|
|
353
|
+
;; Heap source: the byte may be ≥0x80 (non-ASCII), which can't be a 7-bit SSO,
|
|
354
|
+
;; so __char1byte routes those to a heap 1-byte string.
|
|
355
|
+
(else (call $__char1byte (i32.load8_u (i32.add (local.get $off) (local.get $i)))))))))`)
|
|
329
356
|
|
|
330
357
|
// Hot: ~53M calls in watr self-host. Bit-eq covers identity. SSO/SSO with !bit-eq
|
|
331
358
|
// guarantees content differs (high 32 bits encode type+len; both equal → low 32 differs
|
|
@@ -372,9 +399,12 @@ export default (ctx) => {
|
|
|
372
399
|
(local $ta i32) (local $tb i32)
|
|
373
400
|
(local $offA i32) (local $offB i32)
|
|
374
401
|
(local $ssoA i32) (local $ssoB i32)
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
402
|
+
;; Sole caller is __str_eq, which already returned for bit-equal pointers, both-SSO
|
|
403
|
+
;; (bit-ne ⇒ content-ne), both-canonical-interned (bit-ne ⇒ content-ne) and both-plain-
|
|
404
|
+
;; heap length mismatch. Re-testing them here is dead work on every cold call — skip to
|
|
405
|
+
;; the byte walk. (__str_eq/__str_eq_cold are ~14% of self-host runtime; this trims the
|
|
406
|
+
;; per-call fixed cost.) The heap/mixed paths below still decide every case correctly
|
|
407
|
+
;; on their own, so this stays correct even if a future caller skips the prelude.
|
|
378
408
|
(local.set $ta (i32.wrap_i64 (i64.and (i64.shr_u (local.get $a) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
379
409
|
(local.set $tb (i32.wrap_i64 (i64.and (i64.shr_u (local.get $b) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
380
410
|
(local.set $offA (i32.wrap_i64 (i64.and (local.get $a) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
@@ -385,22 +415,6 @@ export default (ctx) => {
|
|
|
385
415
|
(local.set $ssoB (i32.and
|
|
386
416
|
(i32.wrap_i64 (i64.shr_u (local.get $b) (i64.const ${LAYOUT.AUX_SHIFT})))
|
|
387
417
|
(i32.const ${LAYOUT.SSO_BIT})))
|
|
388
|
-
;; Both SSO with !bit-eq ⇒ content differs (high 32 bits hold tag+aux; both equal here).
|
|
389
|
-
(if (i32.and (local.get $ssoA) (local.get $ssoB))
|
|
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))))
|
|
404
418
|
;; Both heap STRING fast path: inline len from header. Chunk by 4 bytes via unaligned
|
|
405
419
|
;; i32.load (wasm guarantees unaligned-OK), then byte-tail. Most string comparisons fail
|
|
406
420
|
;; early on the first 4-byte word, so this collapses the per-byte branch overhead into a
|
|
@@ -489,7 +503,7 @@ export default (ctx) => {
|
|
|
489
503
|
(i32.wrap_i64 (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.AUX_SHIFT})))
|
|
490
504
|
(i32.const ${LAYOUT.AUX_MASK})))
|
|
491
505
|
(if (result i32) (i32.and (local.get $aux) (i32.const ${LAYOUT.SSO_BIT}))
|
|
492
|
-
(then (
|
|
506
|
+
(then ${ssoLenFromAux('(local.get $aux)')})
|
|
493
507
|
(else
|
|
494
508
|
(if (result i32) (i32.and (local.get $aux) (i32.const ${LAYOUT.SLICE_BIT}))
|
|
495
509
|
;; view: length lives in aux[12:0], not a header.
|
|
@@ -507,21 +521,11 @@ export default (ctx) => {
|
|
|
507
521
|
// (single bulk op instead of nlen × __char_at).
|
|
508
522
|
wat('__str_slice', () => `(func $__str_slice (param $ptr i64) (param $start i32) (param $end i32) (result f64)
|
|
509
523
|
(local $len i32) (local $nlen i32) (local $off i32) (local $i i32)
|
|
510
|
-
(local $srcOff i32) (local $isSso i32) (local $sb i32) (local $sp i32)
|
|
524
|
+
(local $srcOff i32) (local $isSso i32) (local $sb i32) (local $sp i32) (local $sp64 i64)
|
|
511
525
|
(local $ip i32) (local $h i32) (local $j i32) (local $slot i32) (local $cand i32) (local $k i32)
|
|
512
526
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
513
|
-
(
|
|
514
|
-
|
|
515
|
-
(if (i32.lt_s (local.get $end) (i32.const 0))
|
|
516
|
-
(then (local.set $end (i32.add (local.get $len) (local.get $end)))))
|
|
517
|
-
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
518
|
-
(then (local.set $start (i32.const 0))))
|
|
519
|
-
(if (i32.gt_s (local.get $start) (local.get $len))
|
|
520
|
-
(then (local.set $start (local.get $len))))
|
|
521
|
-
(if (i32.lt_s (local.get $end) (i32.const 0))
|
|
522
|
-
(then (local.set $end (i32.const 0))))
|
|
523
|
-
(if (i32.gt_s (local.get $end) (local.get $len))
|
|
524
|
-
(then (local.set $end (local.get $len))))
|
|
527
|
+
(local.set $start (call $__clamp_idx (local.get $start) (local.get $len)))
|
|
528
|
+
(local.set $end (call $__clamp_idx (local.get $end) (local.get $len)))
|
|
525
529
|
(if (i32.ge_s (local.get $start) (local.get $end))
|
|
526
530
|
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
|
|
527
531
|
(local.set $nlen (i32.sub (local.get $end) (local.get $start)))
|
|
@@ -560,18 +564,8 @@ export default (ctx) => {
|
|
|
560
564
|
(local $len i32) (local $nlen i32) (local $srcOff i32) (local $tag i32)
|
|
561
565
|
(local $ip i32) (local $h i32) (local $j i32) (local $slot i32) (local $cand i32) (local $k i32)
|
|
562
566
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
563
|
-
(
|
|
564
|
-
|
|
565
|
-
(if (i32.lt_s (local.get $end) (i32.const 0))
|
|
566
|
-
(then (local.set $end (i32.add (local.get $len) (local.get $end)))))
|
|
567
|
-
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
568
|
-
(then (local.set $start (i32.const 0))))
|
|
569
|
-
(if (i32.gt_s (local.get $start) (local.get $len))
|
|
570
|
-
(then (local.set $start (local.get $len))))
|
|
571
|
-
(if (i32.lt_s (local.get $end) (i32.const 0))
|
|
572
|
-
(then (local.set $end (i32.const 0))))
|
|
573
|
-
(if (i32.gt_s (local.get $end) (local.get $len))
|
|
574
|
-
(then (local.set $end (local.get $len))))
|
|
567
|
+
(local.set $start (call $__clamp_idx (local.get $start) (local.get $len)))
|
|
568
|
+
(local.set $end (call $__clamp_idx (local.get $end) (local.get $len)))
|
|
575
569
|
(if (i32.ge_s (local.get $start) (local.get $end))
|
|
576
570
|
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
|
|
577
571
|
(local.set $nlen (i32.sub (local.get $end) (local.get $start)))
|
|
@@ -676,31 +670,33 @@ export default (ctx) => {
|
|
|
676
670
|
wat('__str_slice_eq', `(func $__str_slice_eq (param $ptr i64) (param $start i32) (param $end i32) (param $other i64) (result i32)
|
|
677
671
|
(local $len i32)
|
|
678
672
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
679
|
-
(
|
|
680
|
-
|
|
681
|
-
(if (i32.lt_s (local.get $end) (i32.const 0))
|
|
682
|
-
(then (local.set $end (i32.add (local.get $len) (local.get $end)))))
|
|
683
|
-
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
684
|
-
(then (local.set $start (i32.const 0))))
|
|
685
|
-
(if (i32.gt_s (local.get $start) (local.get $len))
|
|
686
|
-
(then (local.set $start (local.get $len))))
|
|
687
|
-
(if (i32.lt_s (local.get $end) (i32.const 0))
|
|
688
|
-
(then (local.set $end (i32.const 0))))
|
|
689
|
-
(if (i32.gt_s (local.get $end) (local.get $len))
|
|
690
|
-
(then (local.set $end (local.get $len))))
|
|
673
|
+
(local.set $start (call $__clamp_idx (local.get $start) (local.get $len)))
|
|
674
|
+
(local.set $end (call $__clamp_idx (local.get $end) (local.get $len)))
|
|
691
675
|
(call $__str_range_eq (local.get $ptr) (local.get $start) (local.get $end) (local.get $other)))`)
|
|
692
676
|
|
|
693
677
|
// Hoist SSO/heap dispatch for hay and ndl out of the inner byte loop. Inner
|
|
694
678
|
// loop becomes (load8_u OR sso byte-extract) per side — no per-byte calls.
|
|
679
|
+
// Needle byte j, SSO-aware (packed-bits extract vs heap load) — lets the SIMD
|
|
680
|
+
// haystack scan serve SSO needles too (the common short "," / "://" needle),
|
|
681
|
+
// not just heap×heap. `$nsso` is loop-invariant so the branch predicts away.
|
|
682
|
+
const nByte = (j) => `(if (result i32) (local.get $nsso)
|
|
683
|
+
(then ${ssoCharWat('(local.get $ndl)', j)})
|
|
684
|
+
(else (i32.load8_u (i32.add (local.get $noff) ${j}))))`
|
|
695
685
|
wat('__str_indexof', `(func $__str_indexof (param $hay i64) (param $ndl i64) (param $from i32) (result i32)
|
|
696
686
|
(local $hlen i32) (local $nlen i32) (local $i i32) (local $j i32) (local $match i32)
|
|
697
687
|
(local $hoff i32) (local $noff i32)
|
|
698
688
|
(local $hsso i32) (local $nsso i32) (local $hb i32) (local $nb i32) (local $k i32)
|
|
689
|
+
(local $splat v128) (local $mask i32) (local $scanEnd i32)
|
|
699
690
|
;; The search value is ToString'd at the call site (searchArg) per 21.1.3.9 step 4 —
|
|
700
691
|
;; a known-string needle passes raw, so this helper carries no float-pulling __to_str.
|
|
701
692
|
(local.set $hlen (call $__str_byteLen (local.get $hay)))
|
|
702
693
|
(local.set $nlen (call $__str_byteLen (local.get $ndl)))
|
|
703
|
-
|
|
694
|
+
;; Empty needle matches at clamp(from, 0, hlen) — per 21.1.3.9 step 6 (Min(Max(pos,0),len)).
|
|
695
|
+
(if (i32.eqz (local.get $nlen))
|
|
696
|
+
(then (return (select
|
|
697
|
+
(select (local.get $from) (local.get $hlen) (i32.lt_s (local.get $from) (local.get $hlen)))
|
|
698
|
+
(i32.const 0)
|
|
699
|
+
(i32.ge_s (local.get $from) (i32.const 0))))))
|
|
704
700
|
(if (i32.gt_s (local.get $nlen) (local.get $hlen)) (then (return (i32.const -1))))
|
|
705
701
|
(local.set $hoff (i32.wrap_i64 (i64.and (local.get $hay) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
706
702
|
(local.set $noff (i32.wrap_i64 (i64.and (local.get $ndl) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
@@ -711,6 +707,83 @@ export default (ctx) => {
|
|
|
711
707
|
(i32.wrap_i64 (i64.shr_u (local.get $ndl) (i64.const ${LAYOUT.AUX_SHIFT})))
|
|
712
708
|
(i32.const ${LAYOUT.SSO_BIT})))
|
|
713
709
|
(local.set $i (if (result i32) (i32.gt_s (local.get $from) (i32.const 0)) (then (local.get $from)) (else (i32.const 0))))
|
|
710
|
+
;; Single-byte needle (the common s.indexOf('/') / s.includes(' ')): scan for one byte without
|
|
711
|
+
;; the per-position match/j/k bookkeeping. Heap haystack gets a branchless load8_u loop (no
|
|
712
|
+
;; per-byte SSO test); a 16-wide SIMD scan here would only add splat/window setup that short
|
|
713
|
+
;; strings — the bulk of single-char searches — never amortize, so the scalar scan stays.
|
|
714
|
+
(if (i32.eq (local.get $nlen) (i32.const 1))
|
|
715
|
+
(then
|
|
716
|
+
(local.set $nb ${nByte('(i32.const 0)')})
|
|
717
|
+
(if (i32.eqz (local.get $hsso))
|
|
718
|
+
(then
|
|
719
|
+
(block $sd (loop $ss
|
|
720
|
+
(br_if $sd (i32.ge_s (local.get $i) (local.get $hlen)))
|
|
721
|
+
(if (i32.eq (i32.load8_u (i32.add (local.get $hoff) (local.get $i))) (local.get $nb))
|
|
722
|
+
(then (return (local.get $i))))
|
|
723
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
724
|
+
(br $ss)))
|
|
725
|
+
(return (i32.const -1)))
|
|
726
|
+
(else
|
|
727
|
+
(block $sd2 (loop $ss2
|
|
728
|
+
(br_if $sd2 (i32.ge_s (local.get $i) (local.get $hlen)))
|
|
729
|
+
(if (i32.eq ${ssoCharWat('(local.get $hay)', '(local.get $i)')} (local.get $nb))
|
|
730
|
+
(then (return (local.get $i))))
|
|
731
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
732
|
+
(br $ss2)))
|
|
733
|
+
(return (i32.const -1))))))
|
|
734
|
+
;; Multi-byte needle, HEAP haystack (needle SSO or heap): a SIMD first-byte memchr — broadcast
|
|
735
|
+
;; needle[0] across 16 lanes, i8x16.eq a 16-byte haystack window, and read the match-bitmask.
|
|
736
|
+
;; Only positions whose first byte matches reach the branchless verify; the rare match (V8's own
|
|
737
|
+
;; StringIndexOf is SIMD here) is what the scalar path lost on. The SIMD load only touches the
|
|
738
|
+
;; haystack, so an SSO needle (the common short "," / "://" / "TARGET") rides this path too —
|
|
739
|
+
;; its bytes are fetched SSO-aware via nByte(). scanEnd = hlen-nlen is the last viable start; the
|
|
740
|
+
;; window stops at hlen-16 (a full 16-byte load stays inside the string), a scalar tail covers
|
|
741
|
+
;; the rest. Only an SSO haystack (≤ MAX_SSO bytes — too short to scan 16-wide) falls through.
|
|
742
|
+
(if (i32.eqz (local.get $hsso))
|
|
743
|
+
(then
|
|
744
|
+
(local.set $nb ${nByte('(i32.const 0)')})
|
|
745
|
+
(local.set $scanEnd (i32.sub (local.get $hlen) (local.get $nlen)))
|
|
746
|
+
(local.set $splat (i8x16.splat (local.get $nb)))
|
|
747
|
+
(block $vd (loop $vo
|
|
748
|
+
(br_if $vd (i32.gt_s (local.get $i) (i32.sub (local.get $hlen) (i32.const 16))))
|
|
749
|
+
(local.set $mask (i8x16.bitmask
|
|
750
|
+
(i8x16.eq (v128.load (i32.add (local.get $hoff) (local.get $i))) (local.get $splat))))
|
|
751
|
+
(block $bd (loop $bo
|
|
752
|
+
(br_if $bd (i32.eqz (local.get $mask)))
|
|
753
|
+
(local.set $k (i32.add (local.get $i) (i32.ctz (local.get $mask)))) ;; candidate start
|
|
754
|
+
(br_if $bd (i32.gt_s (local.get $k) (local.get $scanEnd))) ;; positions only grow
|
|
755
|
+
(local.set $match (i32.const 1))
|
|
756
|
+
(local.set $j (i32.const 1))
|
|
757
|
+
(block $mn (loop $mi
|
|
758
|
+
(br_if $mn (i32.ge_s (local.get $j) (local.get $nlen)))
|
|
759
|
+
(if (i32.ne (i32.load8_u (i32.add (i32.add (local.get $hoff) (local.get $k)) (local.get $j)))
|
|
760
|
+
${nByte('(local.get $j)')})
|
|
761
|
+
(then (local.set $match (i32.const 0)) (br $mn)))
|
|
762
|
+
(local.set $j (i32.add (local.get $j) (i32.const 1)))
|
|
763
|
+
(br $mi)))
|
|
764
|
+
(if (local.get $match) (then (return (local.get $k))))
|
|
765
|
+
(local.set $mask (i32.and (local.get $mask) (i32.sub (local.get $mask) (i32.const 1)))) ;; clear lowest match
|
|
766
|
+
(br $bo)))
|
|
767
|
+
(local.set $i (i32.add (local.get $i) (i32.const 16)))
|
|
768
|
+
(br $vo)))
|
|
769
|
+
;; scalar tail: positions [i, scanEnd] the SIMD window couldn't cover
|
|
770
|
+
(block $md (loop $mo
|
|
771
|
+
(br_if $md (i32.gt_s (local.get $i) (local.get $scanEnd)))
|
|
772
|
+
(if (i32.eq (i32.load8_u (i32.add (local.get $hoff) (local.get $i))) (local.get $nb))
|
|
773
|
+
(then
|
|
774
|
+
(local.set $match (i32.const 1))
|
|
775
|
+
(local.set $j (i32.const 1))
|
|
776
|
+
(block $mn2 (loop $mi2
|
|
777
|
+
(br_if $mn2 (i32.ge_s (local.get $j) (local.get $nlen)))
|
|
778
|
+
(if (i32.ne (i32.load8_u (i32.add (i32.add (local.get $hoff) (local.get $i)) (local.get $j)))
|
|
779
|
+
${nByte('(local.get $j)')})
|
|
780
|
+
(then (local.set $match (i32.const 0)) (br $mn2)))
|
|
781
|
+
(local.set $j (i32.add (local.get $j) (i32.const 1)))
|
|
782
|
+
(br $mi2)))
|
|
783
|
+
(if (local.get $match) (then (return (local.get $i))))))
|
|
784
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
785
|
+
(br $mo)))
|
|
786
|
+
(return (i32.const -1))))
|
|
714
787
|
(block $done (loop $outer
|
|
715
788
|
(br_if $done (i32.gt_s (local.get $i) (i32.sub (local.get $hlen) (local.get $nlen))))
|
|
716
789
|
(local.set $match (i32.const 1))
|
|
@@ -719,10 +792,10 @@ export default (ctx) => {
|
|
|
719
792
|
(br_if $nomatch (i32.ge_s (local.get $j) (local.get $nlen)))
|
|
720
793
|
(local.set $k (i32.add (local.get $i) (local.get $j)))
|
|
721
794
|
(local.set $hb (if (result i32) (local.get $hsso)
|
|
722
|
-
(then (
|
|
795
|
+
(then ${ssoCharWat('(local.get $hay)', '(local.get $k)')})
|
|
723
796
|
(else (i32.load8_u (i32.add (local.get $hoff) (local.get $k))))))
|
|
724
797
|
(local.set $nb (if (result i32) (local.get $nsso)
|
|
725
|
-
(then (
|
|
798
|
+
(then ${ssoCharWat('(local.get $ndl)', '(local.get $j)')})
|
|
726
799
|
(else (i32.load8_u (i32.add (local.get $noff) (local.get $j))))))
|
|
727
800
|
(if (i32.ne (local.get $hb) (local.get $nb))
|
|
728
801
|
(then (local.set $match (i32.const 0)) (br $nomatch)))
|
|
@@ -771,10 +844,10 @@ export default (ctx) => {
|
|
|
771
844
|
(br_if $nomatch (i32.ge_s (local.get $j) (local.get $nlen)))
|
|
772
845
|
(local.set $k (i32.add (local.get $i) (local.get $j)))
|
|
773
846
|
(local.set $hb (if (result i32) (local.get $hsso)
|
|
774
|
-
(then (
|
|
847
|
+
(then ${ssoCharWat('(local.get $hay)', '(local.get $k)')})
|
|
775
848
|
(else (i32.load8_u (i32.add (local.get $hoff) (local.get $k))))))
|
|
776
849
|
(local.set $nb (if (result i32) (local.get $nsso)
|
|
777
|
-
(then (
|
|
850
|
+
(then ${ssoCharWat('(local.get $ndl)', '(local.get $j)')})
|
|
778
851
|
(else (i32.load8_u (i32.add (local.get $noff) (local.get $j))))))
|
|
779
852
|
(if (i32.ne (local.get $hb) (local.get $nb))
|
|
780
853
|
(then (local.set $match (i32.const 0)) (br $nomatch)))
|
|
@@ -804,10 +877,10 @@ export default (ctx) => {
|
|
|
804
877
|
(br_if $done (i32.ge_s (local.get $i) (local.get $plen)))
|
|
805
878
|
(if (i32.ne
|
|
806
879
|
(if (result i32) (local.get $ssso)
|
|
807
|
-
(then (
|
|
880
|
+
(then ${ssoCharWat('(local.get $str)', '(local.get $i)')})
|
|
808
881
|
(else (i32.load8_u (i32.add (local.get $soff) (local.get $i)))))
|
|
809
882
|
(if (result i32) (local.get $psso)
|
|
810
|
-
(then (
|
|
883
|
+
(then ${ssoCharWat('(local.get $pfx)', '(local.get $i)')})
|
|
811
884
|
(else (i32.load8_u (i32.add (local.get $poff) (local.get $i))))))
|
|
812
885
|
(then (return (i32.const 0))))
|
|
813
886
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
@@ -835,10 +908,10 @@ export default (ctx) => {
|
|
|
835
908
|
(local.set $k (i32.add (local.get $off) (local.get $i)))
|
|
836
909
|
(if (i32.ne
|
|
837
910
|
(if (result i32) (local.get $ssso)
|
|
838
|
-
(then (
|
|
911
|
+
(then ${ssoCharWat('(local.get $str)', '(local.get $k)')})
|
|
839
912
|
(else (i32.load8_u (i32.add (local.get $soff) (local.get $k)))))
|
|
840
913
|
(if (result i32) (local.get $fsso)
|
|
841
|
-
(then (
|
|
914
|
+
(then ${ssoCharWat('(local.get $sfx)', '(local.get $i)')})
|
|
842
915
|
(else (i32.load8_u (i32.add (local.get $foff) (local.get $i))))))
|
|
843
916
|
(then (return (i32.const 0))))
|
|
844
917
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
@@ -860,9 +933,7 @@ export default (ctx) => {
|
|
|
860
933
|
(then
|
|
861
934
|
(block $dsso (loop $lsso
|
|
862
935
|
(br_if $dsso (i32.ge_s (local.get $i) (local.get $len)))
|
|
863
|
-
(local.set $c (
|
|
864
|
-
(i32.shr_u (local.get $srcOff) (i32.shl (local.get $i) (i32.const 3)))
|
|
865
|
-
(i32.const 0xFF)))
|
|
936
|
+
(local.set $c ${ssoCharWat('(local.get $ptr)', '(local.get $i)')})
|
|
866
937
|
(if (i32.and (i32.ge_u (local.get $c) (local.get $lo)) (i32.le_u (local.get $c) (local.get $hi)))
|
|
867
938
|
(then (local.set $c (i32.add (local.get $c) (local.get $delta)))))
|
|
868
939
|
(i32.store8 (i32.add (local.get $off) (local.get $i)) (local.get $c))
|
|
@@ -964,7 +1035,7 @@ export default (ctx) => {
|
|
|
964
1035
|
;; Array (type=1) → join(",") like JS Array.toString()
|
|
965
1036
|
(if (i32.eq (local.get $type) (i32.const ${PTR.ARRAY}))
|
|
966
1037
|
(then (return (i64.reinterpret_f64 (call $__str_join (local.get $val)
|
|
967
|
-
(i64.reinterpret_f64 (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${
|
|
1038
|
+
(i64.reinterpret_f64 (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${ssoAux(1)}) (i32.const 44))))))))
|
|
968
1039
|
(local.get $val))`)
|
|
969
1040
|
|
|
970
1041
|
// Copy bytes of a string (SSO or heap) into memory at dst. Uses memory.copy for
|
|
@@ -973,18 +1044,15 @@ export default (ctx) => {
|
|
|
973
1044
|
(local $w i32)
|
|
974
1045
|
(if (i64.ne (i64.and (local.get $src) (i64.const ${SSO_BIT_I64})) (i64.const 0))
|
|
975
1046
|
(then
|
|
976
|
-
;; SSO:
|
|
977
|
-
;;
|
|
978
|
-
(local.set $w (i32.
|
|
979
|
-
(
|
|
980
|
-
(
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
(i32.store8 offset=1 (local.get $dst) (i32.shr_u (local.get $w) (i32.const 8)))
|
|
986
|
-
(if (i32.eq (local.get $len) (i32.const 2)) (then (return)))
|
|
987
|
-
(i32.store8 offset=2 (local.get $dst) (i32.shr_u (local.get $w) (i32.const 16))))))
|
|
1047
|
+
;; SSO: write $len ASCII chars, each 7-bit-packed at payload bit i*7 (chars 4-5
|
|
1048
|
+
;; span into aux, so read via the 7-bit codec, not the low 32 bits). $w = index.
|
|
1049
|
+
(local.set $w (i32.const 0))
|
|
1050
|
+
(loop $ssocp
|
|
1051
|
+
(if (i32.lt_u (local.get $w) (local.get $len))
|
|
1052
|
+
(then
|
|
1053
|
+
(i32.store8 (i32.add (local.get $dst) (local.get $w)) ${ssoCharWat('(local.get $src)', '(local.get $w)')})
|
|
1054
|
+
(local.set $w (i32.add (local.get $w) (i32.const 1)))
|
|
1055
|
+
(br $ssocp)))))
|
|
988
1056
|
(else
|
|
989
1057
|
;; Heap STRING: memory.copy directly from string data
|
|
990
1058
|
(memory.copy (local.get $dst)
|
|
@@ -1016,12 +1084,12 @@ export default (ctx) => {
|
|
|
1016
1084
|
(then
|
|
1017
1085
|
(return (call $__mkptr
|
|
1018
1086
|
(i32.const ${PTR.STRING})
|
|
1019
|
-
(i32.or (i32.const ${LAYOUT.SSO_BIT}) (local.get $total))
|
|
1087
|
+
(i32.or (i32.const ${LAYOUT.SSO_BIT}) (i32.shl (local.get $total) (i32.const ${SSO_LEN_SHIFT})))
|
|
1020
1088
|
(i32.or
|
|
1021
1089
|
(i32.wrap_i64 (i64.and (local.get $a) (i64.const 0xFFFFFFFF)))
|
|
1022
1090
|
(i32.shl
|
|
1023
1091
|
(i32.wrap_i64 (i64.and (local.get $b) (i64.const 0xFFFFFFFF)))
|
|
1024
|
-
(i32.
|
|
1092
|
+
(i32.mul (local.get $alen) (i32.const 7))))))))`
|
|
1025
1093
|
|
|
1026
1094
|
const concatFast = !ctx.memory.shared && ctx.transform.alloc !== false ? `
|
|
1027
1095
|
(local.set $ta (i32.wrap_i64 (i64.and (i64.shr_u (local.get $a) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
@@ -1048,6 +1116,18 @@ export default (ctx) => {
|
|
|
1048
1116
|
(global.set $__heap (local.get $newHeap))
|
|
1049
1117
|
(return (f64.reinterpret_i64 (local.get $a)))))` : ''
|
|
1050
1118
|
|
|
1119
|
+
// Always-fresh tail: allocate a new buffer and copy both operands. Shared by the
|
|
1120
|
+
// bump-extend concats (reached when `a` is NOT heap-top) and the `_fresh` variants
|
|
1121
|
+
// (which never bump-extend at all — emit routes a NON-self-accumulating `t = s + x`
|
|
1122
|
+
// here so it can't mutate the live `s` the way the heap-top extend would).
|
|
1123
|
+
const allocCopyTail = `
|
|
1124
|
+
(local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $total))))
|
|
1125
|
+
(i32.store (local.get $off) (local.get $total))
|
|
1126
|
+
(local.set $off (i32.add (local.get $off) (i32.const 4)))
|
|
1127
|
+
(call $__str_copy (local.get $a) (local.get $off) (local.get $alen))
|
|
1128
|
+
(call $__str_copy (local.get $b) (i32.add (local.get $off) (local.get $alen)) (local.get $blen))
|
|
1129
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`
|
|
1130
|
+
|
|
1051
1131
|
// Fused single-byte append: `buf += str[i]` lowers to this when both sides are
|
|
1052
1132
|
// VAL.STRING and the rhs is a string-index. Skips __str_idx's 1-char SSO
|
|
1053
1133
|
// construction and __str_concat's type-dispatch — byte goes directly from
|
|
@@ -1086,19 +1166,17 @@ export default (ctx) => {
|
|
|
1086
1166
|
(i32.wrap_i64 (i64.shr_u (local.get $a) (i64.const ${LAYOUT.AUX_SHIFT})))
|
|
1087
1167
|
(i32.const ${LAYOUT.SSO_BIT})))
|
|
1088
1168
|
(then
|
|
1089
|
-
(local.set $alen (
|
|
1090
|
-
(i32.wrap_i64 (i64.shr_u (local.get $a) (i64.const ${LAYOUT.AUX_SHIFT})))
|
|
1091
|
-
(i32.const ${LAYOUT.SSO_BIT - 1})))
|
|
1169
|
+
(local.set $alen ${ssoLenWat('(local.get $a)')})
|
|
1092
1170
|
(if (i32.and
|
|
1093
1171
|
(i32.lt_u (local.get $alen) (i32.const 4))
|
|
1094
1172
|
(i32.lt_u (local.get $byte) (i32.const 0x80)))
|
|
1095
1173
|
(then
|
|
1096
1174
|
(return (call $__mkptr
|
|
1097
1175
|
(i32.const ${PTR.STRING})
|
|
1098
|
-
(i32.or (i32.const ${LAYOUT.SSO_BIT}) (i32.add (local.get $alen) (i32.const 1)))
|
|
1176
|
+
(i32.or (i32.const ${LAYOUT.SSO_BIT}) (i32.shl (i32.add (local.get $alen) (i32.const 1)) (i32.const ${SSO_LEN_SHIFT})))
|
|
1099
1177
|
(i32.or
|
|
1100
1178
|
(local.get $aoff)
|
|
1101
|
-
(i32.shl (local.get $byte) (i32.
|
|
1179
|
+
(i32.shl (local.get $byte) (i32.mul (local.get $alen) (i32.const 7))))))))))
|
|
1102
1180
|
;; Slow path: allocate new heap STRING with original bytes + 1 new byte
|
|
1103
1181
|
(local.set $alen (call $__str_byteLen (local.get $a)))
|
|
1104
1182
|
(local.set $total (i32.add (local.get $alen) (i32.const 1)))
|
|
@@ -1109,6 +1187,10 @@ export default (ctx) => {
|
|
|
1109
1187
|
(i32.store8 (i32.add (local.get $off) (local.get $alen)) (local.get $byte))
|
|
1110
1188
|
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`)
|
|
1111
1189
|
|
|
1190
|
+
// __str_concat / __str_concat_raw bump-EXTEND `a` in place when it is the heap-top own string
|
|
1191
|
+
// (the O(N) accumulator path). Emit calls these ONLY when the source is a self-accumulation
|
|
1192
|
+
// `x = x + …` (so the mutated `a` is dead-after-reassign) or when `a` is a provably-fresh
|
|
1193
|
+
// module-internal temporary; a plain `t = s + x` over a live `s` routes to the _fresh twins.
|
|
1112
1194
|
wat('__str_concat', `(func $__str_concat (param $a i64) (param $b i64) (result f64)
|
|
1113
1195
|
(local $alen i32) (local $blen i32) (local $total i32) (local $off i32)
|
|
1114
1196
|
(local $ta i32) (local $aoff i32) (local $newHeap i32)
|
|
@@ -1121,13 +1203,7 @@ export default (ctx) => {
|
|
|
1121
1203
|
(if (i32.eqz (local.get $total))
|
|
1122
1204
|
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
|
|
1123
1205
|
${ssoResultFast}
|
|
1124
|
-
${concatFast}
|
|
1125
|
-
(local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $total))))
|
|
1126
|
-
(i32.store (local.get $off) (local.get $total))
|
|
1127
|
-
(local.set $off (i32.add (local.get $off) (i32.const 4)))
|
|
1128
|
-
(call $__str_copy (local.get $a) (local.get $off) (local.get $alen))
|
|
1129
|
-
(call $__str_copy (local.get $b) (i32.add (local.get $off) (local.get $alen)) (local.get $blen))
|
|
1130
|
-
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`)
|
|
1206
|
+
${concatFast}${allocCopyTail}`)
|
|
1131
1207
|
|
|
1132
1208
|
wat('__str_concat_raw', `(func $__str_concat_raw (param $a i64) (param $b i64) (result f64)
|
|
1133
1209
|
(local $alen i32) (local $blen i32) (local $total i32) (local $off i32)
|
|
@@ -1138,13 +1214,30 @@ export default (ctx) => {
|
|
|
1138
1214
|
(if (i32.eqz (local.get $total))
|
|
1139
1215
|
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
|
|
1140
1216
|
${ssoResultFast}
|
|
1141
|
-
${concatFast}
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
(
|
|
1217
|
+
${concatFast}${allocCopyTail}`)
|
|
1218
|
+
|
|
1219
|
+
// Non-mutating twins: same SSO-pair fast path, but NEVER bump-extend — always alloc+copy a fresh
|
|
1220
|
+
// buffer, leaving `a` untouched. The default for emit's user-level `+` (any `t = s + x` where the
|
|
1221
|
+
// result is not assigned straight back to `s`), so string immutability holds for live operands.
|
|
1222
|
+
wat('__str_concat_fresh', `(func $__str_concat_fresh (param $a i64) (param $b i64) (result f64)
|
|
1223
|
+
(local $alen i32) (local $blen i32) (local $total i32) (local $off i32)
|
|
1224
|
+
(local.set $a (call $__to_str (local.get $a)))
|
|
1225
|
+
(local.set $b (call $__to_str (local.get $b)))
|
|
1226
|
+
(local.set $alen (call $__str_byteLen (local.get $a)))
|
|
1227
|
+
(local.set $blen (call $__str_byteLen (local.get $b)))
|
|
1228
|
+
(local.set $total (i32.add (local.get $alen) (local.get $blen)))
|
|
1229
|
+
(if (i32.eqz (local.get $total))
|
|
1230
|
+
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
|
|
1231
|
+
${ssoResultFast}${allocCopyTail}`)
|
|
1232
|
+
|
|
1233
|
+
wat('__str_concat_raw_fresh', `(func $__str_concat_raw_fresh (param $a i64) (param $b i64) (result f64)
|
|
1234
|
+
(local $alen i32) (local $blen i32) (local $total i32) (local $off i32)
|
|
1235
|
+
(local.set $alen (call $__str_byteLen (local.get $a)))
|
|
1236
|
+
(local.set $blen (call $__str_byteLen (local.get $b)))
|
|
1237
|
+
(local.set $total (i32.add (local.get $alen) (local.get $blen)))
|
|
1238
|
+
(if (i32.eqz (local.get $total))
|
|
1239
|
+
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
|
|
1240
|
+
${ssoResultFast}${allocCopyTail}`)
|
|
1148
1241
|
|
|
1149
1242
|
wat('__str_replace', `(func $__str_replace (param $str i64) (param $search i64) (param $repl i64) (result f64)
|
|
1150
1243
|
(local $idx i32) (local $slen i32)
|
|
@@ -1379,11 +1472,11 @@ export default (ctx) => {
|
|
|
1379
1472
|
(then
|
|
1380
1473
|
(local.set $sb (i32.load8_u (i32.add (local.get $off) (local.get $i))))
|
|
1381
1474
|
(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.
|
|
1475
|
+
(local.set $sp (i32.or (local.get $sp) (i32.shl (local.get $sb) (i32.mul (local.get $i) (i32.const 7)))))
|
|
1383
1476
|
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
1384
1477
|
(br $pk))))
|
|
1385
1478
|
(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))))))` : ''}
|
|
1479
|
+
(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))))))` : ''}
|
|
1387
1480
|
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`)
|
|
1388
1481
|
|
|
1389
1482
|
// Base helpers (__sso_char/__str_char/__char_at/__str_byteLen) are referenced
|
|
@@ -1621,7 +1714,7 @@ export default (ctx) => {
|
|
|
1621
1714
|
|
|
1622
1715
|
const padMethod = (start) => (str, len, pad) => {
|
|
1623
1716
|
inc('__str_pad')
|
|
1624
|
-
const vpad = pad != null ? asI64(emit(pad)) : ['i64.reinterpret_f64', mkPtrIR(PTR.STRING,
|
|
1717
|
+
const vpad = pad != null ? asI64(emit(pad)) : ['i64.reinterpret_f64', mkPtrIR(PTR.STRING, ssoAux(1), 32)]
|
|
1625
1718
|
return typed(['call', '$__str_pad', asI64(emit(str)), asI32(emit(len)), vpad, ['i32.const', start]], 'f64')
|
|
1626
1719
|
}
|
|
1627
1720
|
bind('.padStart', padMethod(1))
|
|
@@ -1688,14 +1781,17 @@ export default (ctx) => {
|
|
|
1688
1781
|
// index; otherwise `oobIR`. Without the bounds check `__char_at` returns 0 for an
|
|
1689
1782
|
// out-of-range index, which would wrap to a bogus `"\x00"` string (charAt → "",
|
|
1690
1783
|
// at → undefined). `sLocal` is the receiver as an f64 local.
|
|
1691
|
-
const charAtOr = (sLocal, iLocal, lenIR, oobIR) =>
|
|
1784
|
+
const charAtOr = (sLocal, iLocal, lenIR, oobIR) => (
|
|
1785
|
+
inc('__char1byte'),
|
|
1692
1786
|
['if', ['result', 'f64'],
|
|
1693
1787
|
['i32.and',
|
|
1694
1788
|
['i32.ge_s', ['local.get', iLocal], ['i32.const', 0]],
|
|
1695
1789
|
['i32.lt_s', ['local.get', iLocal], lenIR]],
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
['
|
|
1790
|
+
// __char_at yields a raw byte (≥0x80 for non-ASCII heap strings) → __char1byte
|
|
1791
|
+
// builds an SSO byte when ASCII, else a heap 1-byte string.
|
|
1792
|
+
['then', ['call', '$__char1byte',
|
|
1793
|
+
['call', '$__char_at', ['i64.reinterpret_f64', ['local.get', sLocal]], ['local.get', iLocal]]]],
|
|
1794
|
+
['else', oobIR]])
|
|
1699
1795
|
const emptyStr = mkPtrIR(PTR.STRING, LAYOUT.SSO_BIT, 0)
|
|
1700
1796
|
|
|
1701
1797
|
// .charAt(i) → 1-char string at index i, or "" when out of range (JS spec: no
|
|
@@ -1742,13 +1838,27 @@ export default (ctx) => {
|
|
|
1742
1838
|
return typed(['f64.reinterpret_i64', toStrI64(value, emit(value))], 'f64')
|
|
1743
1839
|
})
|
|
1744
1840
|
|
|
1841
|
+
// 1-byte string from a raw byte: SSO when ASCII (<0x80, fits the 7-bit codec),
|
|
1842
|
+
// else a heap 1-byte string (the 7-bit SSO can't represent bytes ≥0x80).
|
|
1843
|
+
wat('__char1byte', `(func $__char1byte (param $b i32) (result f64)
|
|
1844
|
+
(local $off i32)
|
|
1845
|
+
(local.set $b (i32.and (local.get $b) (i32.const 0xFF)))
|
|
1846
|
+
(if (result f64) (i32.lt_u (local.get $b) (i32.const 0x80))
|
|
1847
|
+
(then (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${ssoAux(1)}) (local.get $b)))
|
|
1848
|
+
(else
|
|
1849
|
+
(local.set $off (call $__alloc (i32.const 8)))
|
|
1850
|
+
(i32.store (local.get $off) (i32.const 1))
|
|
1851
|
+
(i32.store8 (i32.add (local.get $off) (i32.const 4)) (local.get $b))
|
|
1852
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (i32.add (local.get $off) (i32.const 4))))))`)
|
|
1853
|
+
|
|
1745
1854
|
// String.fromCharCode(...codes) — variadic; each arg is ToUint16(ToNumber(code))
|
|
1746
1855
|
// → a 1-byte string, concatenated left to right (mirrors String.fromCodePoint).
|
|
1747
1856
|
bind('String.fromCharCode', (...codes) => {
|
|
1748
1857
|
if (codes.length === 0) return emit(['str', ''])
|
|
1749
1858
|
// ToUint16(ToNumber(code)): `toNumF64` performs ToPrimitive on an object
|
|
1750
|
-
// argument, so a throwing valueOf/toString propagates per spec.
|
|
1751
|
-
|
|
1859
|
+
// argument, so a throwing valueOf/toString propagates per spec. A byte ≥0x80
|
|
1860
|
+
// can't be a 7-bit-ASCII SSO, so __char1byte routes it to a heap 1-byte string.
|
|
1861
|
+
const one = (node) => { inc('__char1byte'); return typed(['call', '$__char1byte', asI32(toNumF64(node, emit(node)))], 'f64') }
|
|
1752
1862
|
let r = one(codes[0])
|
|
1753
1863
|
for (let i = 1; i < codes.length; i++) {
|
|
1754
1864
|
inc('__str_concat_raw')
|
|
@@ -1771,27 +1881,41 @@ export default (ctx) => {
|
|
|
1771
1881
|
(local.set $cp (i32.trunc_sat_f64_s (local.get $cpf)))
|
|
1772
1882
|
;; ASCII: 1 byte SSO
|
|
1773
1883
|
(if (i32.lt_u (local.get $cp) (i32.const 128))
|
|
1774
|
-
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${
|
|
1775
|
-
;;
|
|
1884
|
+
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${ssoAux(1)}) (local.get $cp)))))
|
|
1885
|
+
;; multi-byte (cp ≥ 0x80): UTF-8 bytes are ≥0x80 → can't be 7-bit ASCII SSO, so
|
|
1886
|
+
;; build a heap STRING (len header at $off, bytes at $off+4). Over-alloc to 8 so the
|
|
1887
|
+
;; i32.store of the packed bytes never runs past the buffer; the len word bounds reads.
|
|
1888
|
+
;; 2-byte: 0x80-0x7FF
|
|
1776
1889
|
(if (i32.lt_u (local.get $cp) (i32.const 0x800))
|
|
1777
|
-
(then
|
|
1778
|
-
(i32.
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1890
|
+
(then
|
|
1891
|
+
(local.set $off (call $__alloc (i32.const 8)))
|
|
1892
|
+
(i32.store (local.get $off) (i32.const 2))
|
|
1893
|
+
(i32.store (i32.add (local.get $off) (i32.const 4))
|
|
1894
|
+
(i32.or
|
|
1895
|
+
(i32.or (i32.const 0xC0) (i32.shr_u (local.get $cp) (i32.const 6)))
|
|
1896
|
+
(i32.shl (i32.or (i32.const 0x80) (i32.and (local.get $cp) (i32.const 0x3F))) (i32.const 8))))
|
|
1897
|
+
(return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (i32.add (local.get $off) (i32.const 4))))))
|
|
1898
|
+
;; 3-byte: 0x800-0xFFFF
|
|
1782
1899
|
(if (i32.lt_u (local.get $cp) (i32.const 0x10000))
|
|
1783
|
-
(then
|
|
1784
|
-
(
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
(i32.
|
|
1788
|
-
|
|
1789
|
-
|
|
1900
|
+
(then
|
|
1901
|
+
(local.set $off (call $__alloc (i32.const 8)))
|
|
1902
|
+
(i32.store (local.get $off) (i32.const 3))
|
|
1903
|
+
(i32.store (i32.add (local.get $off) (i32.const 4))
|
|
1904
|
+
(i32.or (i32.or
|
|
1905
|
+
(i32.or (i32.const 0xE0) (i32.shr_u (local.get $cp) (i32.const 12)))
|
|
1906
|
+
(i32.shl (i32.or (i32.const 0x80) (i32.and (i32.shr_u (local.get $cp) (i32.const 6)) (i32.const 0x3F))) (i32.const 8)))
|
|
1907
|
+
(i32.shl (i32.or (i32.const 0x80) (i32.and (local.get $cp) (i32.const 0x3F))) (i32.const 16))))
|
|
1908
|
+
(return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (i32.add (local.get $off) (i32.const 4))))))
|
|
1909
|
+
;; 4-byte: 0x10000-0x10FFFF
|
|
1910
|
+
(local.set $off (call $__alloc (i32.const 8)))
|
|
1911
|
+
(i32.store (local.get $off) (i32.const 4))
|
|
1912
|
+
(i32.store (i32.add (local.get $off) (i32.const 4))
|
|
1790
1913
|
(i32.or (i32.or (i32.or
|
|
1791
1914
|
(i32.or (i32.const 0xF0) (i32.shr_u (local.get $cp) (i32.const 18)))
|
|
1792
1915
|
(i32.shl (i32.or (i32.const 0x80) (i32.and (i32.shr_u (local.get $cp) (i32.const 12)) (i32.const 0x3F))) (i32.const 8)))
|
|
1793
1916
|
(i32.shl (i32.or (i32.const 0x80) (i32.and (i32.shr_u (local.get $cp) (i32.const 6)) (i32.const 0x3F))) (i32.const 16)))
|
|
1794
|
-
(i32.shl (i32.or (i32.const 0x80) (i32.and (local.get $cp) (i32.const 0x3F))) (i32.const 24))))
|
|
1917
|
+
(i32.shl (i32.or (i32.const 0x80) (i32.and (local.get $cp) (i32.const 0x3F))) (i32.const 24))))
|
|
1918
|
+
(return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (i32.add (local.get $off) (i32.const 4)))))`)
|
|
1795
1919
|
|
|
1796
1920
|
// String.fromCodePoint(...codePoints) — variadic; each arg is ToNumber-coerced
|
|
1797
1921
|
// then validated/encoded by __fromCodePoint, results concatenated left to right.
|