jz 0.4.0 → 0.5.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 +275 -255
- package/cli.js +8 -51
- package/index.js +166 -31
- package/{src/host.js → interop.js} +291 -88
- package/module/array.js +181 -71
- package/module/collection.js +222 -8
- package/module/console.js +1 -1
- package/module/core.js +251 -118
- package/module/date.js +9 -5
- package/module/function.js +11 -1
- package/module/json.js +361 -55
- package/module/math.js +551 -128
- package/module/number.js +390 -227
- package/module/object.js +252 -34
- package/module/regex.js +123 -16
- package/module/schema.js +15 -1
- package/module/string.js +540 -96
- package/module/timer.js +1 -1
- package/module/typedarray.js +674 -57
- package/package.json +10 -7
- package/src/abi/array.js +89 -0
- package/src/abi/index.js +35 -0
- package/src/abi/number.js +137 -0
- package/src/abi/object.js +57 -0
- package/src/abi/string.js +529 -0
- package/src/analyze.js +1870 -485
- package/src/assemble.js +27 -12
- package/src/autoload.js +13 -1
- package/src/compile.js +446 -179
- package/src/ctx.js +85 -18
- package/src/emit.js +662 -196
- package/src/infer.js +548 -0
- package/src/ir.js +291 -23
- package/src/jzify.js +786 -94
- package/src/narrow.js +433 -251
- package/src/optimize.js +126 -122
- package/src/plan.js +703 -34
- package/src/prepare.js +822 -150
- package/src/resolve.js +85 -0
- package/src/vectorize.js +99 -18
- package/src/ast.js +0 -160
- package/src/auto-config.js +0 -120
- package/src/fuse.js +0 -159
package/module/string.js
CHANGED
|
@@ -12,14 +12,30 @@
|
|
|
12
12
|
* @module string
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { typed, asF64, asI32, asI64, NULL_NAN, UNDEF_NAN, mkPtrIR, temp, tempI32 } from '../src/ir.js'
|
|
16
|
-
import { emit } from '../src/emit.js'
|
|
15
|
+
import { typed, asF64, asI32, asI64, NULL_NAN, UNDEF_NAN, mkPtrIR, temp, tempI32, toNumF64, toStrI64 } from '../src/ir.js'
|
|
16
|
+
import { emit, emitBoolStr } from '../src/emit.js'
|
|
17
17
|
import { valTypeOf, VAL } from '../src/analyze.js'
|
|
18
|
-
import { inc, PTR, LAYOUT } from '../src/ctx.js'
|
|
18
|
+
import { inc, emitter, PTR, LAYOUT } from '../src/ctx.js'
|
|
19
19
|
|
|
20
20
|
// SSO discriminator bit pre-shifted to its slot in the full i64 ptr (bit 46).
|
|
21
21
|
// Used as `i64.and ptr SSO_BIT_I64` for branch-without-extracting-aux.
|
|
22
22
|
const SSO_BIT_I64 = '0x' + (BigInt(LAYOUT.SSO_BIT) << BigInt(LAYOUT.AUX_SHIFT)).toString(16).toUpperCase().padStart(16, '0')
|
|
23
|
+
// Slice/view discriminator bit, pre-shifted to bit 45 — same branch-without-aux trick.
|
|
24
|
+
const SLICE_BIT_I64 = '0x' + (BigInt(LAYOUT.SLICE_BIT) << BigInt(LAYOUT.AUX_SHIFT)).toString(16).toUpperCase().padStart(16, '0')
|
|
25
|
+
|
|
26
|
+
// WAT (no-locals expression): byte length of a heap STRING given its raw $-local
|
|
27
|
+
// names for the offset and the i64 ptr. A view (SLICE_BIT) carries its length in
|
|
28
|
+
// aux[12:0]; an own heap string reads the i32 header at off-4. Callers must have
|
|
29
|
+
// already excluded SSO. Used inline by the hot char/eq helpers so a slice never
|
|
30
|
+
// reads a bogus length out of a parent buffer's bytes.
|
|
31
|
+
const heapLenExpr = (ptrLocal, offLocal) => `(if (result i32)
|
|
32
|
+
(i64.ne (i64.and (local.get ${ptrLocal}) (i64.const ${SLICE_BIT_I64})) (i64.const 0))
|
|
33
|
+
(then (i32.and
|
|
34
|
+
(i32.wrap_i64 (i64.shr_u (local.get ${ptrLocal}) (i64.const ${LAYOUT.AUX_SHIFT})))
|
|
35
|
+
(i32.const ${LAYOUT.SLICE_LEN_MASK})))
|
|
36
|
+
(else (if (result i32) (i32.ge_u (local.get ${offLocal}) (i32.const 4))
|
|
37
|
+
(then (i32.load (i32.sub (local.get ${offLocal}) (i32.const 4))))
|
|
38
|
+
(else (i32.const 0)))))`
|
|
23
39
|
|
|
24
40
|
|
|
25
41
|
export default (ctx) => {
|
|
@@ -29,7 +45,8 @@ export default (ctx) => {
|
|
|
29
45
|
__str_append_byte: ['__str_byteLen', '__alloc', '__mkptr', '__str_copy'],
|
|
30
46
|
__str_copy: [],
|
|
31
47
|
__str_slice: ['__str_byteLen', '__alloc'],
|
|
32
|
-
|
|
48
|
+
__str_slice_view: ['__str_byteLen', '__mkptr', '__str_slice'],
|
|
49
|
+
__str_indexof: ['__str_byteLen', '__to_str'],
|
|
33
50
|
__str_substring: ['__str_slice'],
|
|
34
51
|
__str_startswith: ['__str_byteLen'],
|
|
35
52
|
__str_endswith: ['__str_byteLen'],
|
|
@@ -42,11 +59,17 @@ export default (ctx) => {
|
|
|
42
59
|
__str_replaceall: ['__str_indexof', '__str_slice', '__str_concat'],
|
|
43
60
|
__str_split: ['__str_slice', '__str_byteLen', '__char_at', '__alloc'],
|
|
44
61
|
__str_idx: [],
|
|
45
|
-
__str_eq: ['__char_at'],
|
|
62
|
+
__str_eq: ['__char_at', '__str_byteLen'],
|
|
46
63
|
__str_cmp: ['__char_at', '__str_byteLen'],
|
|
64
|
+
__str_range_eq: ['__char_at', '__str_byteLen'],
|
|
65
|
+
__str_substring_eq: ['__str_byteLen', '__str_range_eq'],
|
|
66
|
+
__str_slice_eq: ['__str_byteLen', '__str_range_eq'],
|
|
47
67
|
__str_pad: ['__str_byteLen', '__str_copy', '__alloc'],
|
|
48
68
|
__str_join: ['__str_concat', '__to_str', '__str_byteLen', '__len', '__ptr_offset'],
|
|
49
69
|
__str_encode: ['__str_byteLen', '__str_copy'],
|
|
70
|
+
__encodeURIComponent: ['__to_str', '__str_byteLen', '__char_at', '__alloc', '__mkptr'],
|
|
71
|
+
__decodeURIComponent: ['__to_str', '__str_byteLen', '__char_at', '__alloc', '__mkptr', '__uri_hex'],
|
|
72
|
+
__uri_hex: [],
|
|
50
73
|
__to_str: ['__ftoa', '__static_str', '__str_join', '__mkptr'],
|
|
51
74
|
__str_byteLen: ['__ptr_type', '__ptr_aux', '__str_len'],
|
|
52
75
|
})
|
|
@@ -111,19 +134,63 @@ export default (ctx) => {
|
|
|
111
134
|
(i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK})))
|
|
112
135
|
(local.get $i))))`
|
|
113
136
|
|
|
114
|
-
// Hot (~37M calls in watr self-host
|
|
115
|
-
// SSO bit picks inline-byte-extract vs heap memory
|
|
137
|
+
// Hot (~37M calls in watr self-host, ~40k/scan in tokenizer bench). Caller
|
|
138
|
+
// guarantees $ptr is a STRING; SSO bit picks inline-byte-extract vs heap memory
|
|
139
|
+
// load. Returns 0 for OOB — internal tokenizer callers (number/json/regex
|
|
140
|
+
// parsers) rely on this sentinel to terminate `while (c > 32)`-shape loops
|
|
141
|
+
// past end-of-string. The SSO bounds check is essential: i32.shr_u wraps shift
|
|
142
|
+
// count mod 32, so without it `'a'.charCodeAt(4)` would return 'a' again
|
|
143
|
+
// (shift 32→0).
|
|
144
|
+
//
|
|
145
|
+
// Body written as a single nested-if expression with NO locals so watr's
|
|
146
|
+
// inliner picks it up (gate: no-locals + ≤4 params + body.length===1). After
|
|
147
|
+
// inlining into a hot loop, V8's LICM hoists the SSO-bit test, offset
|
|
148
|
+
// extraction and heap-length load out — the per-iteration cost collapses to a
|
|
149
|
+
// load+bounds-check, beating call-site overhead. Repeated `i32.wrap_i64 +
|
|
150
|
+
// i64.and OFFSET_MASK` subexpressions rely on CSE in the consumer; both
|
|
151
|
+
// V8/TurboFan and watr's own propagate pass handle this.
|
|
116
152
|
ctx.core.stdlib['__char_at'] = `(func $__char_at (param $ptr i64) (param $i i32) (result i32)
|
|
117
|
-
(local $off i32)
|
|
118
|
-
(local.set $off (i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
119
153
|
(if (result i32)
|
|
120
154
|
(i64.ne (i64.and (local.get $ptr) (i64.const ${SSO_BIT_I64})) (i64.const 0))
|
|
121
155
|
(then
|
|
122
|
-
(i32
|
|
123
|
-
(i32.
|
|
124
|
-
|
|
156
|
+
(if (result i32)
|
|
157
|
+
(i32.ge_u (local.get $i)
|
|
158
|
+
(i32.and
|
|
159
|
+
(i32.wrap_i64 (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.AUX_SHIFT})))
|
|
160
|
+
(i32.const ${LAYOUT.SSO_BIT - 1})))
|
|
161
|
+
(then (i32.const 0))
|
|
162
|
+
(else (i32.and
|
|
163
|
+
(i32.shr_u
|
|
164
|
+
(i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK})))
|
|
165
|
+
(i32.mul (local.get $i) (i32.const 8)))
|
|
166
|
+
(i32.const 0xFF)))))
|
|
125
167
|
(else
|
|
126
|
-
(
|
|
168
|
+
(if (result i32)
|
|
169
|
+
(i32.ge_u (local.get $i)
|
|
170
|
+
;; non-SSO length: view → aux[12:0]; own heap string → header at off-4
|
|
171
|
+
;; (off<4 sentinel guards the literal-data-segment edge). Both arms
|
|
172
|
+
;; are loop-invariant — V8 LICM hoists the whole select.
|
|
173
|
+
(if (result i32)
|
|
174
|
+
(i64.ne (i64.and (local.get $ptr) (i64.const ${SLICE_BIT_I64})) (i64.const 0))
|
|
175
|
+
(then (i32.and
|
|
176
|
+
(i32.wrap_i64 (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.AUX_SHIFT})))
|
|
177
|
+
(i32.const ${LAYOUT.SLICE_LEN_MASK})))
|
|
178
|
+
(else
|
|
179
|
+
(if (result i32)
|
|
180
|
+
(i32.lt_u
|
|
181
|
+
(i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK})))
|
|
182
|
+
(i32.const 4))
|
|
183
|
+
(then (i32.const 0))
|
|
184
|
+
(else (i32.load
|
|
185
|
+
(i32.sub
|
|
186
|
+
(i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK})))
|
|
187
|
+
(i32.const 4))))))))
|
|
188
|
+
(then (i32.const 0))
|
|
189
|
+
(else
|
|
190
|
+
(i32.load8_u
|
|
191
|
+
(i32.add
|
|
192
|
+
(i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK})))
|
|
193
|
+
(local.get $i)))))))))`
|
|
127
194
|
|
|
128
195
|
ctx.core.stdlib['__str_idx'] = `(func $__str_idx (param $ptr i64) (param $i i32) (result f64)
|
|
129
196
|
(local $t i32) (local $off i32) (local $len i32) (local $isSso i32)
|
|
@@ -138,9 +205,15 @@ export default (ctx) => {
|
|
|
138
205
|
(i32.wrap_i64 (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.AUX_SHIFT})))
|
|
139
206
|
(i32.const ${LAYOUT.SSO_BIT - 1})))
|
|
140
207
|
(else
|
|
141
|
-
(if (result i32)
|
|
142
|
-
(
|
|
143
|
-
(
|
|
208
|
+
(if (result i32)
|
|
209
|
+
(i64.ne (i64.and (local.get $ptr) (i64.const ${SLICE_BIT_I64})) (i64.const 0))
|
|
210
|
+
(then (i32.and
|
|
211
|
+
(i32.wrap_i64 (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.AUX_SHIFT})))
|
|
212
|
+
(i32.const ${LAYOUT.SLICE_LEN_MASK})))
|
|
213
|
+
(else
|
|
214
|
+
(if (result i32) (i32.and (i32.eq (local.get $t) (i32.const ${PTR.STRING})) (i32.ge_u (local.get $off) (i32.const 4)))
|
|
215
|
+
(then (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
216
|
+
(else (i32.const 0))))))))
|
|
144
217
|
(if (result f64)
|
|
145
218
|
(i32.or (i32.lt_s (local.get $i) (i32.const 0)) (i32.ge_u (local.get $i) (local.get $len)))
|
|
146
219
|
(then (f64.const nan:${UNDEF_NAN}))
|
|
@@ -188,8 +261,8 @@ export default (ctx) => {
|
|
|
188
261
|
(then
|
|
189
262
|
(if (i32.or (i32.lt_u (local.get $offA) (i32.const 4)) (i32.lt_u (local.get $offB) (i32.const 4)))
|
|
190
263
|
(then (return (i32.const 0))))
|
|
191
|
-
(local.set $len (
|
|
192
|
-
(local.set $lenB (
|
|
264
|
+
(local.set $len ${heapLenExpr('$a', '$offA')})
|
|
265
|
+
(local.set $lenB ${heapLenExpr('$b', '$offB')})
|
|
193
266
|
(if (i32.ne (local.get $len) (local.get $lenB))
|
|
194
267
|
(then (return (i32.const 0))))
|
|
195
268
|
(local.set $lenB (i32.and (local.get $len) (i32.const -4)))
|
|
@@ -211,20 +284,9 @@ export default (ctx) => {
|
|
|
211
284
|
(br $lh)))
|
|
212
285
|
(return (i32.const 1))))
|
|
213
286
|
;; Mixed (SSO×heap) or anything else: compute len per side then per-byte via __char_at.
|
|
214
|
-
(
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
(i32.const ${LAYOUT.SSO_BIT - 1}))))
|
|
218
|
-
(else
|
|
219
|
-
(if (i32.and (i32.eq (local.get $ta) (i32.const ${PTR.STRING})) (i32.ge_u (local.get $offA) (i32.const 4)))
|
|
220
|
-
(then (local.set $len (i32.load (i32.sub (local.get $offA) (i32.const 4))))))))
|
|
221
|
-
(if (local.get $ssoB)
|
|
222
|
-
(then (local.set $lenB (i32.and
|
|
223
|
-
(i32.wrap_i64 (i64.shr_u (local.get $b) (i64.const ${LAYOUT.AUX_SHIFT})))
|
|
224
|
-
(i32.const ${LAYOUT.SSO_BIT - 1}))))
|
|
225
|
-
(else
|
|
226
|
-
(if (i32.and (i32.eq (local.get $tb) (i32.const ${PTR.STRING})) (i32.ge_u (local.get $offB) (i32.const 4)))
|
|
227
|
-
(then (local.set $lenB (i32.load (i32.sub (local.get $offB) (i32.const 4))))))))
|
|
287
|
+
;; __str_byteLen handles SSO, slice (SLICE_BIT) and own-heap encodings uniformly.
|
|
288
|
+
(local.set $len (call $__str_byteLen (local.get $a)))
|
|
289
|
+
(local.set $lenB (call $__str_byteLen (local.get $b)))
|
|
228
290
|
(if (i32.ne (local.get $len) (local.get $lenB))
|
|
229
291
|
(then (return (i32.const 0))))
|
|
230
292
|
(block $dm (loop $lm
|
|
@@ -279,10 +341,14 @@ export default (ctx) => {
|
|
|
279
341
|
(if (result i32) (i32.and (local.get $aux) (i32.const ${LAYOUT.SSO_BIT}))
|
|
280
342
|
(then (i32.and (local.get $aux) (i32.const ${LAYOUT.SSO_BIT - 1})))
|
|
281
343
|
(else
|
|
282
|
-
(
|
|
283
|
-
|
|
284
|
-
(then (i32.
|
|
285
|
-
(else
|
|
344
|
+
(if (result i32) (i32.and (local.get $aux) (i32.const ${LAYOUT.SLICE_BIT}))
|
|
345
|
+
;; view: length lives in aux[12:0], not a header.
|
|
346
|
+
(then (i32.and (local.get $aux) (i32.const ${LAYOUT.SLICE_LEN_MASK})))
|
|
347
|
+
(else
|
|
348
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
349
|
+
(if (result i32) (i32.ge_u (local.get $off) (i32.const 4))
|
|
350
|
+
(then (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
351
|
+
(else (i32.const 0))))))))
|
|
286
352
|
(else (i32.const 0))))`
|
|
287
353
|
|
|
288
354
|
// === WAT: string methods ===
|
|
@@ -329,6 +395,48 @@ export default (ctx) => {
|
|
|
329
395
|
(memory.copy (local.get $off) (i32.add (local.get $srcOff) (local.get $start)) (local.get $nlen))))
|
|
330
396
|
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`
|
|
331
397
|
|
|
398
|
+
// No-copy slice: returns a VIEW into the receiver's buffer instead of copying
|
|
399
|
+
// bytes. Only emitted when escape analysis proves the result never outlives the
|
|
400
|
+
// parent (a non-escaping local). A view is a heap STRING with SLICE_BIT set and
|
|
401
|
+
// its length in aux[12:0]; the offset points straight into the parent's bytes,
|
|
402
|
+
// so it stays valid as long as the parent does. Falls back to a real copy
|
|
403
|
+
// (__str_slice) when the parent is SSO (no buffer to point into) or the result
|
|
404
|
+
// is longer than SLICE_LEN_MASK (aux can't hold the length). Clamping mirrors
|
|
405
|
+
// __str_slice; the fallback re-clamps idempotently.
|
|
406
|
+
ctx.core.stdlib['__str_slice_view'] = `(func $__str_slice_view (param $ptr i64) (param $start i32) (param $end i32) (result f64)
|
|
407
|
+
(local $len i32) (local $nlen i32) (local $srcOff i32) (local $tag i32)
|
|
408
|
+
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
409
|
+
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
410
|
+
(then (local.set $start (i32.add (local.get $len) (local.get $start)))))
|
|
411
|
+
(if (i32.lt_s (local.get $end) (i32.const 0))
|
|
412
|
+
(then (local.set $end (i32.add (local.get $len) (local.get $end)))))
|
|
413
|
+
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
414
|
+
(then (local.set $start (i32.const 0))))
|
|
415
|
+
(if (i32.gt_s (local.get $start) (local.get $len))
|
|
416
|
+
(then (local.set $start (local.get $len))))
|
|
417
|
+
(if (i32.lt_s (local.get $end) (i32.const 0))
|
|
418
|
+
(then (local.set $end (i32.const 0))))
|
|
419
|
+
(if (i32.gt_s (local.get $end) (local.get $len))
|
|
420
|
+
(then (local.set $end (local.get $len))))
|
|
421
|
+
(if (i32.ge_s (local.get $start) (local.get $end))
|
|
422
|
+
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
|
|
423
|
+
(local.set $nlen (i32.sub (local.get $end) (local.get $start)))
|
|
424
|
+
(local.set $tag (i32.wrap_i64 (i64.and (i64.shr_u (local.get $ptr) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
425
|
+
;; View-eligible: STRING parent, not SSO, length fits aux[12:0].
|
|
426
|
+
(if (i32.and
|
|
427
|
+
(i32.and
|
|
428
|
+
(i32.eq (local.get $tag) (i32.const ${PTR.STRING}))
|
|
429
|
+
(i64.eqz (i64.and (local.get $ptr) (i64.const ${SSO_BIT_I64}))))
|
|
430
|
+
(i32.le_u (local.get $nlen) (i32.const ${LAYOUT.SLICE_LEN_MASK})))
|
|
431
|
+
(then
|
|
432
|
+
(local.set $srcOff (i32.wrap_i64 (i64.and (local.get $ptr) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
433
|
+
(return (call $__mkptr
|
|
434
|
+
(i32.const ${PTR.STRING})
|
|
435
|
+
(i32.or (i32.const ${LAYOUT.SLICE_BIT}) (local.get $nlen))
|
|
436
|
+
(i32.add (local.get $srcOff) (local.get $start))))))
|
|
437
|
+
;; Fallback: copy (SSO parent, or slice too long for the aux length field).
|
|
438
|
+
(call $__str_slice (local.get $ptr) (local.get $start) (local.get $end)))`
|
|
439
|
+
|
|
332
440
|
ctx.core.stdlib['__str_substring'] = `(func $__str_substring (param $ptr i64) (param $start i32) (param $end i32) (result f64)
|
|
333
441
|
(local $len i32) (local $tmp i32)
|
|
334
442
|
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
@@ -347,12 +455,91 @@ export default (ctx) => {
|
|
|
347
455
|
(local.set $end (local.get $tmp))))
|
|
348
456
|
(call $__str_slice (local.get $ptr) (local.get $start) (local.get $end)))`
|
|
349
457
|
|
|
458
|
+
// === WAT: fused substring-equality ===
|
|
459
|
+
//
|
|
460
|
+
// `<str>.{substr,substring,slice}(...) === <other>` consumed only by the
|
|
461
|
+
// equality materialises a transient substring (an __alloc + byte copy) just
|
|
462
|
+
// to feed __eq. emit.js's emitSubstringEqCmp peepholes that pair to these
|
|
463
|
+
// helpers, which clamp the range exactly like __str_substring / __str_slice
|
|
464
|
+
// then byte-compare it against `other` in place — zero allocation. Motivating
|
|
465
|
+
// hot path: the parser keyword scan, `cur.substr(i,l) === keyword`.
|
|
466
|
+
//
|
|
467
|
+
// __str_range_eq assumes the receiver is a STRING (every substring method
|
|
468
|
+
// returns one) and type-checks only `other`, mirroring __eq's STRING-vs-?
|
|
469
|
+
// arm: a genuine number never equals a string, and a NaN-boxed non-STRING
|
|
470
|
+
// never does either (jz `==` is strict).
|
|
471
|
+
ctx.core.stdlib['__str_range_eq'] = `(func $__str_range_eq (param $ptr i64) (param $start i32) (param $end i32) (param $other i64) (result i32)
|
|
472
|
+
(local $n i32) (local $i i32) (local $fb f64)
|
|
473
|
+
;; A genuine number reinterprets to a non-NaN f64 (equals itself) — never a string.
|
|
474
|
+
(local.set $fb (f64.reinterpret_i64 (local.get $other)))
|
|
475
|
+
(if (f64.eq (local.get $fb) (local.get $fb))
|
|
476
|
+
(then (return (i32.const 0))))
|
|
477
|
+
;; NaN-boxed but not STRING-tagged ⇒ not a string ⇒ not equal.
|
|
478
|
+
(if (i32.ne
|
|
479
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $other) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK})))
|
|
480
|
+
(i32.const ${PTR.STRING}))
|
|
481
|
+
(then (return (i32.const 0))))
|
|
482
|
+
(local.set $n (i32.sub (local.get $end) (local.get $start)))
|
|
483
|
+
(if (i32.lt_s (local.get $n) (i32.const 0))
|
|
484
|
+
(then (local.set $n (i32.const 0))))
|
|
485
|
+
(if (i32.ne (local.get $n) (call $__str_byteLen (local.get $other)))
|
|
486
|
+
(then (return (i32.const 0))))
|
|
487
|
+
(block $done (loop $next
|
|
488
|
+
(br_if $done (i32.ge_s (local.get $i) (local.get $n)))
|
|
489
|
+
(if (i32.ne
|
|
490
|
+
(call $__char_at (local.get $ptr) (i32.add (local.get $start) (local.get $i)))
|
|
491
|
+
(call $__char_at (local.get $other) (local.get $i)))
|
|
492
|
+
(then (return (i32.const 0))))
|
|
493
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
494
|
+
(br $next)))
|
|
495
|
+
(i32.const 1))`
|
|
496
|
+
|
|
497
|
+
// Clamp mirrors __str_substring (negatives floor to 0, swap when start>end).
|
|
498
|
+
ctx.core.stdlib['__str_substring_eq'] = `(func $__str_substring_eq (param $ptr i64) (param $start i32) (param $end i32) (param $other i64) (result i32)
|
|
499
|
+
(local $len i32) (local $tmp i32)
|
|
500
|
+
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
501
|
+
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
502
|
+
(then (local.set $start (i32.const 0))))
|
|
503
|
+
(if (i32.lt_s (local.get $end) (i32.const 0))
|
|
504
|
+
(then (local.set $end (i32.const 0))))
|
|
505
|
+
(if (i32.gt_s (local.get $start) (local.get $len))
|
|
506
|
+
(then (local.set $start (local.get $len))))
|
|
507
|
+
(if (i32.gt_s (local.get $end) (local.get $len))
|
|
508
|
+
(then (local.set $end (local.get $len))))
|
|
509
|
+
(if (i32.gt_s (local.get $start) (local.get $end))
|
|
510
|
+
(then
|
|
511
|
+
(local.set $tmp (local.get $start))
|
|
512
|
+
(local.set $start (local.get $end))
|
|
513
|
+
(local.set $end (local.get $tmp))))
|
|
514
|
+
(call $__str_range_eq (local.get $ptr) (local.get $start) (local.get $end) (local.get $other)))`
|
|
515
|
+
|
|
516
|
+
// Clamp mirrors __str_slice (negatives count from the end; __str_range_eq
|
|
517
|
+
// floors a negative span to an empty match).
|
|
518
|
+
ctx.core.stdlib['__str_slice_eq'] = `(func $__str_slice_eq (param $ptr i64) (param $start i32) (param $end i32) (param $other i64) (result i32)
|
|
519
|
+
(local $len i32)
|
|
520
|
+
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
521
|
+
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
522
|
+
(then (local.set $start (i32.add (local.get $len) (local.get $start)))))
|
|
523
|
+
(if (i32.lt_s (local.get $end) (i32.const 0))
|
|
524
|
+
(then (local.set $end (i32.add (local.get $len) (local.get $end)))))
|
|
525
|
+
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
526
|
+
(then (local.set $start (i32.const 0))))
|
|
527
|
+
(if (i32.gt_s (local.get $start) (local.get $len))
|
|
528
|
+
(then (local.set $start (local.get $len))))
|
|
529
|
+
(if (i32.lt_s (local.get $end) (i32.const 0))
|
|
530
|
+
(then (local.set $end (i32.const 0))))
|
|
531
|
+
(if (i32.gt_s (local.get $end) (local.get $len))
|
|
532
|
+
(then (local.set $end (local.get $len))))
|
|
533
|
+
(call $__str_range_eq (local.get $ptr) (local.get $start) (local.get $end) (local.get $other)))`
|
|
534
|
+
|
|
350
535
|
// Hoist SSO/heap dispatch for hay and ndl out of the inner byte loop. Inner
|
|
351
536
|
// loop becomes (load8_u OR sso byte-extract) per side — no per-byte calls.
|
|
352
537
|
ctx.core.stdlib['__str_indexof'] = `(func $__str_indexof (param $hay i64) (param $ndl i64) (param $from i32) (result i32)
|
|
353
538
|
(local $hlen i32) (local $nlen i32) (local $i i32) (local $j i32) (local $match i32)
|
|
354
539
|
(local $hoff i32) (local $noff i32)
|
|
355
540
|
(local $hsso i32) (local $nsso i32) (local $hb i32) (local $nb i32) (local $k i32)
|
|
541
|
+
;; ToString the search value (21.1.3.9 step 4) — coerces undefined/null/number/etc.
|
|
542
|
+
(local.set $ndl (call $__to_str (local.get $ndl)))
|
|
356
543
|
(local.set $hlen (call $__str_byteLen (local.get $hay)))
|
|
357
544
|
(local.set $nlen (call $__str_byteLen (local.get $ndl)))
|
|
358
545
|
(if (i32.eqz (local.get $nlen)) (then (return (local.get $from))))
|
|
@@ -598,15 +785,41 @@ export default (ctx) => {
|
|
|
598
785
|
// length too, so this departs from strict JS string immutability for the rare
|
|
599
786
|
// `let b = a; a += x` aliasing case. The fast path can't trigger when other
|
|
600
787
|
// allocations have happened since `a` was created (it's no longer at heap top).
|
|
601
|
-
// Only emitted for own-memory mode
|
|
602
|
-
|
|
788
|
+
// Only emitted for own-memory mode with a $__heap global; shared memory and
|
|
789
|
+
// alloc:false (which routes the heap pointer through memory[1020]) fall back.
|
|
790
|
+
// SSO-result fast path: both operands SSO and total ≤ 4 → repack inline,
|
|
791
|
+
// no alloc, no copy. Combined offset field = a's bytes | (b's bytes shifted
|
|
792
|
+
// by alen*8). New aux = SSO_BIT | total. Mode-independent (pointer arith
|
|
793
|
+
// only, no heap); fires whenever the upstream `'+'` couldn't fold at
|
|
794
|
+
// compile time (one or both operands runtime values). Critical for the
|
|
795
|
+
// identifier-style `'$' + x` / `prefix + s` patterns hot in parsers.
|
|
796
|
+
const ssoResultFast = `
|
|
797
|
+
(if (i32.and
|
|
798
|
+
(i32.and
|
|
799
|
+
(i64.ne (i64.and (local.get $a) (i64.const ${SSO_BIT_I64})) (i64.const 0))
|
|
800
|
+
(i64.ne (i64.and (local.get $b) (i64.const ${SSO_BIT_I64})) (i64.const 0)))
|
|
801
|
+
(i32.le_u (local.get $total) (i32.const 4)))
|
|
802
|
+
(then
|
|
803
|
+
(return (call $__mkptr
|
|
804
|
+
(i32.const ${PTR.STRING})
|
|
805
|
+
(i32.or (i32.const ${LAYOUT.SSO_BIT}) (local.get $total))
|
|
806
|
+
(i32.or
|
|
807
|
+
(i32.wrap_i64 (i64.and (local.get $a) (i64.const 0xFFFFFFFF)))
|
|
808
|
+
(i32.shl
|
|
809
|
+
(i32.wrap_i64 (i64.and (local.get $b) (i64.const 0xFFFFFFFF)))
|
|
810
|
+
(i32.shl (local.get $alen) (i32.const 3))))))))`
|
|
811
|
+
|
|
812
|
+
const concatFast = !ctx.memory.shared && ctx.transform.alloc !== false ? `
|
|
603
813
|
(local.set $ta (i32.wrap_i64 (i64.and (i64.shr_u (local.get $a) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
604
814
|
(local.set $aoff (i32.wrap_i64 (i64.and (local.get $a) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
605
|
-
;; Bump-extend requires heap STRING
|
|
815
|
+
;; Bump-extend requires an OWN heap STRING — not SSO (offset holds packed bytes)
|
|
816
|
+
;; and not a slice/view (bumping would corrupt the parent buffer it points into).
|
|
606
817
|
(if (i32.and
|
|
607
818
|
(i32.and
|
|
608
819
|
(i32.eq (local.get $ta) (i32.const ${PTR.STRING}))
|
|
609
|
-
(
|
|
820
|
+
(i32.and
|
|
821
|
+
(i64.eqz (i64.and (local.get $a) (i64.const ${SSO_BIT_I64})))
|
|
822
|
+
(i64.eqz (i64.and (local.get $a) (i64.const ${SLICE_BIT_I64})))))
|
|
610
823
|
(i32.eq
|
|
611
824
|
(i32.and (i32.add (i32.add (local.get $aoff) (local.get $alen)) (i32.const 7)) (i32.const -8))
|
|
612
825
|
(global.get $__heap)))
|
|
@@ -633,12 +846,15 @@ export default (ctx) => {
|
|
|
633
846
|
(local $newHeap i32) (local $off i32) (local $total i32)
|
|
634
847
|
(local.set $ta (i32.wrap_i64 (i64.and (i64.shr_u (local.get $a) (i64.const ${LAYOUT.TAG_SHIFT})) (i64.const ${LAYOUT.TAG_MASK}))))
|
|
635
848
|
(local.set $aoff (i32.wrap_i64 (i64.and (local.get $a) (i64.const ${LAYOUT.OFFSET_MASK}))))
|
|
636
|
-
;; Heap STRING at heap top: bump-extend by 1 byte (own-memory mode only).
|
|
637
|
-
;; Gate on STRING tag AND !SSO_BIT
|
|
638
|
-
$
|
|
849
|
+
;; Heap STRING at heap top: bump-extend by 1 byte (own-memory mode w/ $__heap global only).
|
|
850
|
+
;; Gate on STRING tag AND !SSO_BIT ($aoff would hold packed bytes) AND !SLICE_BIT
|
|
851
|
+
;; (a view's $aoff points into a parent buffer — bumping it would corrupt the parent).
|
|
852
|
+
${!ctx.memory.shared && ctx.transform.alloc !== false ? `
|
|
639
853
|
(if (i32.and
|
|
640
854
|
(i32.eq (local.get $ta) (i32.const ${PTR.STRING}))
|
|
641
|
-
(
|
|
855
|
+
(i32.and
|
|
856
|
+
(i64.eqz (i64.and (local.get $a) (i64.const ${SSO_BIT_I64})))
|
|
857
|
+
(i64.eqz (i64.and (local.get $a) (i64.const ${SLICE_BIT_I64})))))
|
|
642
858
|
(then
|
|
643
859
|
(local.set $alen (i32.load (i32.sub (local.get $aoff) (i32.const 4))))
|
|
644
860
|
(if (i32.eq
|
|
@@ -696,6 +912,7 @@ export default (ctx) => {
|
|
|
696
912
|
(local.set $total (i32.add (local.get $alen) (local.get $blen)))
|
|
697
913
|
(if (i32.eqz (local.get $total))
|
|
698
914
|
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
|
|
915
|
+
${ssoResultFast}
|
|
699
916
|
${concatFast}
|
|
700
917
|
(local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $total))))
|
|
701
918
|
(i32.store (local.get $off) (local.get $total))
|
|
@@ -712,6 +929,7 @@ export default (ctx) => {
|
|
|
712
929
|
(local.set $total (i32.add (local.get $alen) (local.get $blen)))
|
|
713
930
|
(if (i32.eqz (local.get $total))
|
|
714
931
|
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
|
|
932
|
+
${ssoResultFast}
|
|
715
933
|
${concatFast}
|
|
716
934
|
(local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $total))))
|
|
717
935
|
(i32.store (local.get $off) (local.get $total))
|
|
@@ -896,26 +1114,64 @@ export default (ctx) => {
|
|
|
896
1114
|
// override to return the primitive — strings already covered by .string:valueOf.
|
|
897
1115
|
ctx.core.emit['.valueOf'] = (val) => asF64(emit(val))
|
|
898
1116
|
|
|
899
|
-
|
|
900
|
-
|
|
1117
|
+
// `.slice` lowering, parametrised on the backing helper: __str_slice copies
|
|
1118
|
+
// bytes; __str_slice_view returns a no-copy SLICE_BIT view — used only when
|
|
1119
|
+
// escape analysis proved the result never outlives its parent buffer (see
|
|
1120
|
+
// scanSliceViews / emitDecl). The `#view` key cannot be produced by method
|
|
1121
|
+
// dispatch (`#` is not a legal identifier char), so the view variant is
|
|
1122
|
+
// reachable only through the explicit emitDecl route.
|
|
1123
|
+
const sliceEmitter = (fn) => (str, start, end) => {
|
|
1124
|
+
inc(fn)
|
|
901
1125
|
const startIR = start == null ? ['i32.const', 0] : asI32(emit(start))
|
|
902
|
-
if (end != null) return typed(['call',
|
|
1126
|
+
if (end != null) return typed(['call', `$${fn}`, asI64(emit(str)), startIR, asI32(emit(end))], 'f64')
|
|
903
1127
|
const t = temp('t')
|
|
904
1128
|
return typed(['block', ['result', 'f64'],
|
|
905
1129
|
['local.set', `$${t}`, asF64(emit(str))],
|
|
906
|
-
['call',
|
|
1130
|
+
['call', `$${fn}`, ['i64.reinterpret_f64', ['local.get', `$${t}`]], startIR,
|
|
907
1131
|
['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]]], 'f64')
|
|
908
1132
|
}
|
|
1133
|
+
ctx.core.emit['.string:slice'] = sliceEmitter('__str_slice')
|
|
1134
|
+
ctx.core.emit['.string:slice#view'] = sliceEmitter('__str_slice_view')
|
|
1135
|
+
|
|
1136
|
+
// ToIntegerOrInfinity for a string-method position argument: ToNumber (so
|
|
1137
|
+
// string / boolean / null / undefined positions coerce per spec) then trunc.
|
|
1138
|
+
// trunc_sat maps NaN→0 and ±∞→±maxint — both clamp correctly downstream.
|
|
1139
|
+
// `toNumF64` routes an object position through `valueOf`/`toString`
|
|
1140
|
+
// (ToPrimitive), so a throwing method propagates as an abrupt completion.
|
|
1141
|
+
const posIndex = (node) => {
|
|
1142
|
+
if (node == null) return ['i32.const', 0]
|
|
1143
|
+
return asI32(toNumF64(node, emit(node)))
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// ToString(searchString) per spec step 3. __str_indexof's internal __to_str
|
|
1147
|
+
// covers string/number/null/undefined needles, but two cases need help here:
|
|
1148
|
+
// a BOOL rides the 0/1 carrier (→ "0"/"1" not "true"/"false"), and an OBJECT
|
|
1149
|
+
// needs compile-time ToPrimitive(string) (__to_str can't invoke user toString).
|
|
1150
|
+
const searchArg = (search) =>
|
|
1151
|
+
valTypeOf(search) === VAL.BOOL ? asI64(emitBoolStr(search)) :
|
|
1152
|
+
valTypeOf(search) === VAL.OBJECT ? toStrI64(search, emit(search)) : asI64(emit(search))
|
|
909
1153
|
|
|
910
1154
|
ctx.core.emit['.string:indexOf'] = (str, search, from) => {
|
|
911
1155
|
inc('__str_indexof')
|
|
912
|
-
|
|
1156
|
+
const hay = asI64(emit(str)), ndl = searchArg(search)
|
|
1157
|
+
return typed(['f64.convert_i32_s', ['call', '$__str_indexof', hay, ndl, posIndex(from)]], 'f64')
|
|
913
1158
|
}
|
|
914
1159
|
|
|
915
|
-
|
|
1160
|
+
// String.prototype.{includes,startsWith,endsWith} run IsRegExp(searchString)
|
|
1161
|
+
// and throw a TypeError when it is a RegExp. Detect a regex-typed search arg
|
|
1162
|
+
// at compile time and lower to a $__jz_err throw.
|
|
1163
|
+
const regexpSearchGuard = (search) => {
|
|
1164
|
+
if (valTypeOf(search) !== VAL.REGEX) return null
|
|
1165
|
+
ctx.runtime.throws = true
|
|
1166
|
+
return typed(['block', ['result', 'f64'], ['throw', '$__jz_err', ['f64.const', 0]]], 'f64')
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
ctx.core.emit['.string:includes'] = (str, search, from) => {
|
|
1170
|
+
const guard = regexpSearchGuard(search); if (guard) return guard
|
|
916
1171
|
inc('__str_indexof')
|
|
1172
|
+
const hay = asI64(emit(str)), ndl = searchArg(search)
|
|
917
1173
|
return typed(['f64.convert_i32_s',
|
|
918
|
-
['i32.ge_s', ['call', '$__str_indexof',
|
|
1174
|
+
['i32.ge_s', ['call', '$__str_indexof', hay, ndl, posIndex(from)], ['i32.const', 0]]], 'f64')
|
|
919
1175
|
}
|
|
920
1176
|
|
|
921
1177
|
// Generic (no collision)
|
|
@@ -929,20 +1185,47 @@ export default (ctx) => {
|
|
|
929
1185
|
['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]]], 'f64')
|
|
930
1186
|
}
|
|
931
1187
|
|
|
932
|
-
//
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1188
|
+
// .substr(start, length) — Annex B / legacy. Equivalent to substring(start, start+length).
|
|
1189
|
+
// __str_substring clamps end to byteLen and start/end to [0, byteLen], so negative
|
|
1190
|
+
// values are floored to 0 (matches v8 for length<0 → empty; for start<0 spec wants
|
|
1191
|
+
// max(0, len+start), which we don't implement — rare in practice).
|
|
1192
|
+
ctx.core.emit['.substr'] = (str, start, length) => {
|
|
1193
|
+
inc('__str_substring')
|
|
1194
|
+
if (length != null) {
|
|
1195
|
+
const s = tempI32('substrS')
|
|
1196
|
+
return typed(['block', ['result', 'f64'],
|
|
1197
|
+
['local.set', `$${s}`, asI32(emit(start))],
|
|
1198
|
+
['call', '$__str_substring', asI64(emit(str)),
|
|
1199
|
+
['local.get', `$${s}`],
|
|
1200
|
+
['i32.add', ['local.get', `$${s}`], asI32(emit(length))]]
|
|
1201
|
+
], 'f64')
|
|
1202
|
+
}
|
|
1203
|
+
const t = temp('t')
|
|
1204
|
+
return typed(['block', ['result', 'f64'],
|
|
1205
|
+
['local.set', `$${t}`, asF64(emit(str))],
|
|
1206
|
+
['call', '$__str_substring', ['i64.reinterpret_f64', ['local.get', `$${t}`]], asI32(emit(start)),
|
|
1207
|
+
['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]]], 'f64')
|
|
938
1208
|
}
|
|
939
1209
|
|
|
1210
|
+
// Declarative emitter for regular `(recv, ...args) → call($stdlib, coerced...)` methods.
|
|
1211
|
+
// `coerce` is a per-argument code string ('I' i64 · 'F' f64 · 'i' i32); index 0 is the receiver.
|
|
1212
|
+
// `ret` is the helper's result type — 'f64' is returned directly, 'i32' is widened to the f64 ABI.
|
|
1213
|
+
const CO = { I: asI64, F: asF64, i: asI32 }
|
|
1214
|
+
const method = (stdlib, coerce, ret = 'f64') => emitter([stdlib], (...nodes) => {
|
|
1215
|
+
const c = ['call', `$${stdlib}`, ...nodes.map((n, i) => CO[coerce[i]](emit(n)))]
|
|
1216
|
+
return typed(ret === 'i32' ? ['f64.convert_i32_s', c] : c, 'f64')
|
|
1217
|
+
})
|
|
1218
|
+
|
|
940
1219
|
// Search args go through ToString per spec — coerce non-string-typed args
|
|
941
1220
|
// via __to_str so the underlying byte-compare receives an actual string.
|
|
942
1221
|
const stringSearchMethod = (name) => (str, sfx) => {
|
|
1222
|
+
const guard = regexpSearchGuard(sfx); if (guard) return guard
|
|
943
1223
|
inc(name)
|
|
944
|
-
|
|
945
|
-
|
|
1224
|
+
const esfx = emit(sfx)
|
|
1225
|
+
let sfxArg = asI64(esfx)
|
|
1226
|
+
if (valTypeOf(sfx) === VAL.OBJECT) {
|
|
1227
|
+
sfxArg = toStrI64(sfx, esfx)
|
|
1228
|
+
} else if (valTypeOf(sfx) !== VAL.STRING) {
|
|
946
1229
|
inc('__to_str')
|
|
947
1230
|
sfxArg = ['call', '$__to_str', sfxArg]
|
|
948
1231
|
}
|
|
@@ -950,20 +1233,13 @@ export default (ctx) => {
|
|
|
950
1233
|
}
|
|
951
1234
|
ctx.core.emit['.startsWith'] = stringSearchMethod('__str_startswith')
|
|
952
1235
|
ctx.core.emit['.endsWith'] = stringSearchMethod('__str_endswith')
|
|
953
|
-
ctx.core.emit['.trim']
|
|
954
|
-
|
|
955
|
-
ctx.core.emit['.
|
|
956
|
-
|
|
957
|
-
ctx.core.emit['.
|
|
958
|
-
|
|
959
|
-
ctx.core.emit['.
|
|
960
|
-
typed(['call', '$__str_repeat', asI64(emit(str)), asI32(emit(n))], 'f64'))
|
|
961
|
-
ctx.core.emit['.split'] = (str, sep) => (inc('__str_split'),
|
|
962
|
-
typed(['call', '$__str_split', asI64(emit(str)), asI64(emit(sep))], 'f64'))
|
|
963
|
-
ctx.core.emit['.replace'] = (str, search, repl) => (inc('__str_replace'),
|
|
964
|
-
typed(['call', '$__str_replace', asI64(emit(str)), asI64(emit(search)), asI64(emit(repl))], 'f64'))
|
|
965
|
-
ctx.core.emit['.replaceAll'] = (str, search, repl) => (inc('__str_replaceall'),
|
|
966
|
-
typed(['call', '$__str_replaceall', asI64(emit(str)), asI64(emit(search)), asI64(emit(repl))], 'f64'))
|
|
1236
|
+
ctx.core.emit['.trim'] = method('__str_trim', 'I')
|
|
1237
|
+
ctx.core.emit['.trimStart'] = method('__str_trimStart', 'I')
|
|
1238
|
+
ctx.core.emit['.trimEnd'] = method('__str_trimEnd', 'I')
|
|
1239
|
+
ctx.core.emit['.repeat'] = method('__str_repeat', 'Ii')
|
|
1240
|
+
ctx.core.emit['.split'] = method('__str_split', 'II')
|
|
1241
|
+
ctx.core.emit['.replace'] = method('__str_replace', 'III')
|
|
1242
|
+
ctx.core.emit['.replaceAll'] = method('__str_replaceall', 'III')
|
|
967
1243
|
|
|
968
1244
|
ctx.core.emit['.toUpperCase'] = (str) => {
|
|
969
1245
|
inc('__str_case')
|
|
@@ -987,10 +1263,7 @@ export default (ctx) => {
|
|
|
987
1263
|
// the spec exactly; for non-ASCII it follows UTF-8 byte order, which is
|
|
988
1264
|
// codepoint order for well-formed strings — close enough for sort-stability
|
|
989
1265
|
// use cases, wrong for human-language collation.
|
|
990
|
-
ctx.core.emit['.localeCompare'] = (
|
|
991
|
-
inc('__str_cmp')
|
|
992
|
-
return typed(['f64.convert_i32_s', ['call', '$__str_cmp', asI64(emit(str)), asI64(emit(other))]], 'f64')
|
|
993
|
-
}
|
|
1266
|
+
ctx.core.emit['.localeCompare'] = method('__str_cmp', 'II', 'i32')
|
|
994
1267
|
|
|
995
1268
|
ctx.core.emit['.string:concat'] = (str, ...others) => {
|
|
996
1269
|
inc('__str_concat')
|
|
@@ -999,10 +1272,15 @@ export default (ctx) => {
|
|
|
999
1272
|
return result
|
|
1000
1273
|
}
|
|
1001
1274
|
|
|
1275
|
+
// A VAL.BOOL part rides the 0/1 carrier, so __to_str would render "1"/"0".
|
|
1276
|
+
// emitBoolStr selects the interned "true"/"false" literal (constant-folded
|
|
1277
|
+
// when the operand is known); every other part goes through __to_str.
|
|
1278
|
+
const partStrI64 = (p) => valTypeOf(p) === VAL.BOOL ? asI64(emitBoolStr(p)) : toStrI64(p, emit(p))
|
|
1279
|
+
|
|
1002
1280
|
ctx.core.emit['strcat'] = (...parts) => {
|
|
1003
1281
|
inc('__to_str', '__str_byteLen', '__alloc', '__mkptr', '__str_copy')
|
|
1004
1282
|
if (!parts.length) return mkPtrIR(PTR.STRING, LAYOUT.SSO_BIT, 0)
|
|
1005
|
-
if (parts.length === 1) return typed(['f64.reinterpret_i64',
|
|
1283
|
+
if (parts.length === 1) return typed(['f64.reinterpret_i64', partStrI64(parts[0])], 'f64')
|
|
1006
1284
|
|
|
1007
1285
|
const vals = parts.map(() => temp('s'))
|
|
1008
1286
|
const lens = parts.map(() => tempI32('sl'))
|
|
@@ -1012,7 +1290,7 @@ export default (ctx) => {
|
|
|
1012
1290
|
const ir = []
|
|
1013
1291
|
|
|
1014
1292
|
for (let i = 0; i < parts.length; i++) {
|
|
1015
|
-
ir.push(['local.set', `$${vals[i]}`, ['f64.reinterpret_i64',
|
|
1293
|
+
ir.push(['local.set', `$${vals[i]}`, ['f64.reinterpret_i64', partStrI64(parts[i])]])
|
|
1016
1294
|
ir.push(['local.set', `$${lens[i]}`, ['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${vals[i]}`]]]])
|
|
1017
1295
|
}
|
|
1018
1296
|
ir.push(['local.set', `$${total}`, ['i32.const', 0]])
|
|
@@ -1057,35 +1335,47 @@ export default (ctx) => {
|
|
|
1057
1335
|
mkPtrIR(PTR.STRING, LAYOUT.SSO_BIT | 1, ['local.get', `$${t}`])], 'f64')
|
|
1058
1336
|
}
|
|
1059
1337
|
|
|
1060
|
-
// .charCodeAt(i) →
|
|
1061
|
-
//
|
|
1062
|
-
//
|
|
1063
|
-
//
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1338
|
+
// .charCodeAt(i) → JS-spec char code: the UTF-16 code unit at `i`, or NaN
|
|
1339
|
+
// when `i` is out of range (`i < 0 || i >= length`). Result is f64 because
|
|
1340
|
+
// NaN is not representable as i32 — an i32 `0` sentinel for OOB silently
|
|
1341
|
+
// miscompiles any reader that distinguishes 0 from NaN, e.g. the parser hot
|
|
1342
|
+
// loop `while ((cc = s.charCodeAt(i++)) <= 32) {}` would never terminate
|
|
1343
|
+
// (`0 <= 32` is true, `NaN <= 32` is false). The narrower may re-narrow the
|
|
1344
|
+
// result to i32 where it can prove the index in-bounds.
|
|
1345
|
+
ctx.core.emit['.charCodeAt'] = (str, idx) =>
|
|
1346
|
+
typed(ctx.abi.string.ops.charCodeAt(asF64(emit(str)), asI32(emit(idx)), ctx, true), 'f64')
|
|
1068
1347
|
|
|
1069
1348
|
// String.fromCharCode(code) → 1-char SSO string
|
|
1070
1349
|
ctx.core.emit['String'] = (value) => {
|
|
1071
1350
|
if (value === undefined) return emit(['str', ''])
|
|
1072
1351
|
if (valTypeOf(value) === VAL.STRING) return emit(value)
|
|
1352
|
+
if (valTypeOf(value) === VAL.BOOL) return emitBoolStr(value)
|
|
1073
1353
|
if (valTypeOf(value) === VAL.NUMBER) {
|
|
1074
1354
|
inc('__ftoa')
|
|
1075
1355
|
return typed(['call', '$__ftoa', asF64(emit(value)), ['i32.const', 0], ['i32.const', 0]], 'f64')
|
|
1076
1356
|
}
|
|
1077
|
-
|
|
1078
|
-
return typed(['f64.reinterpret_i64', ['call', '$__to_str', asI64(emit(value))]], 'f64')
|
|
1357
|
+
return typed(['f64.reinterpret_i64', toStrI64(value, emit(value))], 'f64')
|
|
1079
1358
|
}
|
|
1080
1359
|
|
|
1081
1360
|
ctx.core.emit['String.fromCharCode'] = (code) => {
|
|
1082
1361
|
if (code === undefined) return emit(['str', ''])
|
|
1083
|
-
|
|
1362
|
+
// ToUint16(ToNumber(code)): `toNumF64` performs ToPrimitive on an object
|
|
1363
|
+
// argument, so a throwing valueOf/toString propagates per spec.
|
|
1364
|
+
return mkPtrIR(PTR.STRING, LAYOUT.SSO_BIT | 1, asI32(toNumF64(code, emit(code))))
|
|
1084
1365
|
}
|
|
1085
1366
|
|
|
1086
|
-
// String.fromCodePoint(cp) → UTF-8 encoded string
|
|
1087
|
-
|
|
1088
|
-
|
|
1367
|
+
// String.fromCodePoint(cp) → UTF-8 encoded string for one code point.
|
|
1368
|
+
// Param is f64 (already ToNumber-coerced); throws RangeError ($__jz_err) when
|
|
1369
|
+
// the value is not an integer in [0, 0x10FFFF] (22.1.2.2 step 5.d).
|
|
1370
|
+
ctx.core.stdlib['__fromCodePoint'] = `(func $__fromCodePoint (param $cpf f64) (result f64)
|
|
1371
|
+
(local $cp i32) (local $off i32) (local $len i32)
|
|
1372
|
+
(if (i32.or
|
|
1373
|
+
(i32.or
|
|
1374
|
+
(f64.ne (f64.trunc (local.get $cpf)) (local.get $cpf))
|
|
1375
|
+
(f64.lt (local.get $cpf) (f64.const 0)))
|
|
1376
|
+
(f64.gt (local.get $cpf) (f64.const 0x10FFFF)))
|
|
1377
|
+
(then (throw $__jz_err (f64.const 0))))
|
|
1378
|
+
(local.set $cp (i32.trunc_sat_f64_s (local.get $cpf)))
|
|
1089
1379
|
;; ASCII: 1 byte SSO
|
|
1090
1380
|
(if (i32.lt_u (local.get $cp) (i32.const 128))
|
|
1091
1381
|
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT | 1}) (local.get $cp)))))
|
|
@@ -1110,10 +1400,164 @@ export default (ctx) => {
|
|
|
1110
1400
|
(i32.shl (i32.or (i32.const 0x80) (i32.and (i32.shr_u (local.get $cp) (i32.const 6)) (i32.const 0x3F))) (i32.const 16)))
|
|
1111
1401
|
(i32.shl (i32.or (i32.const 0x80) (i32.and (local.get $cp) (i32.const 0x3F))) (i32.const 24))))))`
|
|
1112
1402
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1403
|
+
// String.fromCodePoint(...codePoints) — variadic; each arg is ToNumber-coerced
|
|
1404
|
+
// then validated/encoded by __fromCodePoint, results concatenated left to right.
|
|
1405
|
+
ctx.core.emit['String.fromCodePoint'] = (...codes) => {
|
|
1406
|
+
if (codes.length === 0) return emit(['str', ''])
|
|
1407
|
+
ctx.runtime.throws = true
|
|
1115
1408
|
inc('__fromCodePoint')
|
|
1116
|
-
|
|
1409
|
+
const one = (node) => typed(['call', '$__fromCodePoint',
|
|
1410
|
+
toNumF64(node, emit(node))], 'f64')
|
|
1411
|
+
let r = one(codes[0])
|
|
1412
|
+
for (let i = 1; i < codes.length; i++) {
|
|
1413
|
+
inc('__str_concat_raw')
|
|
1414
|
+
r = typed(['call', '$__str_concat_raw', asI64(r), asI64(one(codes[i]))], 'f64')
|
|
1415
|
+
}
|
|
1416
|
+
return r
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
ctx.core.stdlib['__encodeURIComponent'] = `(func $__encodeURIComponent (param $val i64) (result f64)
|
|
1420
|
+
(local $str i64) (local $slen i32) (local $base i32) (local $out i32)
|
|
1421
|
+
(local $i i32) (local $j i32) (local $c i32) (local $hi i32) (local $lo i32)
|
|
1422
|
+
(local.set $str (call $__to_str (local.get $val)))
|
|
1423
|
+
(local.set $slen (call $__str_byteLen (local.get $str)))
|
|
1424
|
+
(if (i32.eqz (local.get $slen))
|
|
1425
|
+
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
|
|
1426
|
+
(local.set $base (call $__alloc (i32.add (i32.const 4) (i32.mul (local.get $slen) (i32.const 3)))))
|
|
1427
|
+
(local.set $out (i32.add (local.get $base) (i32.const 4)))
|
|
1428
|
+
(block $done (loop $loop
|
|
1429
|
+
(br_if $done (i32.ge_u (local.get $i) (local.get $slen)))
|
|
1430
|
+
(local.set $c (call $__char_at (local.get $str) (local.get $i)))
|
|
1431
|
+
(if (i32.or
|
|
1432
|
+
(i32.or
|
|
1433
|
+
(i32.or
|
|
1434
|
+
(i32.and (i32.ge_u (local.get $c) (i32.const 65)) (i32.le_u (local.get $c) (i32.const 90)))
|
|
1435
|
+
(i32.and (i32.ge_u (local.get $c) (i32.const 97)) (i32.le_u (local.get $c) (i32.const 122))))
|
|
1436
|
+
(i32.and (i32.ge_u (local.get $c) (i32.const 48)) (i32.le_u (local.get $c) (i32.const 57))))
|
|
1437
|
+
(i32.or
|
|
1438
|
+
(i32.or
|
|
1439
|
+
(i32.or (i32.eq (local.get $c) (i32.const 45)) (i32.eq (local.get $c) (i32.const 95)))
|
|
1440
|
+
(i32.or (i32.eq (local.get $c) (i32.const 46)) (i32.eq (local.get $c) (i32.const 33))))
|
|
1441
|
+
(i32.or
|
|
1442
|
+
(i32.or (i32.eq (local.get $c) (i32.const 126)) (i32.eq (local.get $c) (i32.const 42)))
|
|
1443
|
+
(i32.or
|
|
1444
|
+
(i32.eq (local.get $c) (i32.const 39))
|
|
1445
|
+
(i32.or (i32.eq (local.get $c) (i32.const 40)) (i32.eq (local.get $c) (i32.const 41)))))))
|
|
1446
|
+
(then
|
|
1447
|
+
(i32.store8 (i32.add (local.get $out) (local.get $j)) (local.get $c))
|
|
1448
|
+
(local.set $j (i32.add (local.get $j) (i32.const 1))))
|
|
1449
|
+
(else
|
|
1450
|
+
(local.set $hi (i32.shr_u (local.get $c) (i32.const 4)))
|
|
1451
|
+
(local.set $lo (i32.and (local.get $c) (i32.const 15)))
|
|
1452
|
+
(i32.store8 (i32.add (local.get $out) (local.get $j)) (i32.const 37))
|
|
1453
|
+
(i32.store8 (i32.add (local.get $out) (i32.add (local.get $j) (i32.const 1)))
|
|
1454
|
+
(i32.add (local.get $hi) (select (i32.const 55) (i32.const 48) (i32.gt_u (local.get $hi) (i32.const 9)))))
|
|
1455
|
+
(i32.store8 (i32.add (local.get $out) (i32.add (local.get $j) (i32.const 2)))
|
|
1456
|
+
(i32.add (local.get $lo) (select (i32.const 55) (i32.const 48) (i32.gt_u (local.get $lo) (i32.const 9)))))
|
|
1457
|
+
(local.set $j (i32.add (local.get $j) (i32.const 3)))))
|
|
1458
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
1459
|
+
(br $loop)))
|
|
1460
|
+
(i32.store (local.get $base) (local.get $j))
|
|
1461
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $out)))`
|
|
1462
|
+
|
|
1463
|
+
ctx.core.emit['encodeURIComponent'] = (value) => {
|
|
1464
|
+
inc('__encodeURIComponent')
|
|
1465
|
+
const input = value === undefined ? ['i64.const', UNDEF_NAN] : asI64(emit(value))
|
|
1466
|
+
return typed(['call', '$__encodeURIComponent', input], 'f64')
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
ctx.core.stdlib['__uri_hex'] = `(func $__uri_hex (param $c i32) (result i32)
|
|
1470
|
+
(if (result i32) (i32.and (i32.ge_u (local.get $c) (i32.const 48)) (i32.le_u (local.get $c) (i32.const 57)))
|
|
1471
|
+
(then (i32.sub (local.get $c) (i32.const 48)))
|
|
1472
|
+
(else (if (result i32) (i32.and (i32.ge_u (local.get $c) (i32.const 65)) (i32.le_u (local.get $c) (i32.const 70)))
|
|
1473
|
+
(then (i32.sub (local.get $c) (i32.const 55)))
|
|
1474
|
+
(else (if (result i32) (i32.and (i32.ge_u (local.get $c) (i32.const 97)) (i32.le_u (local.get $c) (i32.const 102)))
|
|
1475
|
+
(then (i32.sub (local.get $c) (i32.const 87)))
|
|
1476
|
+
(else (i32.const -1))))))))`
|
|
1477
|
+
|
|
1478
|
+
ctx.core.stdlib['__decodeURIComponent'] = `(func $__decodeURIComponent (param $v i64) (result f64)
|
|
1479
|
+
(local $s i64) (local $len i32) (local $i i32)
|
|
1480
|
+
(local $base i32) (local $dst i32) (local $outLen i32)
|
|
1481
|
+
(local $c i32) (local $hi i32) (local $lo i32)
|
|
1482
|
+
(local $b i32) (local $n i32) (local $j i32) (local $cp i32) (local $min i32) (local $stored i32)
|
|
1483
|
+
(local.set $s (call $__to_str (local.get $v)))
|
|
1484
|
+
(local.set $len (call $__str_byteLen (local.get $s)))
|
|
1485
|
+
(local.set $base (call $__alloc (i32.add (i32.const 4) (local.get $len))))
|
|
1486
|
+
(local.set $dst (i32.add (local.get $base) (i32.const 4)))
|
|
1487
|
+
(block $done (loop $loop
|
|
1488
|
+
(br_if $done (i32.ge_s (local.get $i) (local.get $len)))
|
|
1489
|
+
(local.set $stored (i32.const 0))
|
|
1490
|
+
(local.set $c (call $__char_at (local.get $s) (local.get $i)))
|
|
1491
|
+
(if (i32.eq (local.get $c) (i32.const 37))
|
|
1492
|
+
(then
|
|
1493
|
+
(if (i32.ge_s (i32.add (local.get $i) (i32.const 2)) (local.get $len))
|
|
1494
|
+
(then (throw $__jz_err (f64.const 0))))
|
|
1495
|
+
(local.set $hi (call $__uri_hex (call $__char_at (local.get $s) (i32.add (local.get $i) (i32.const 1)))))
|
|
1496
|
+
(local.set $lo (call $__uri_hex (call $__char_at (local.get $s) (i32.add (local.get $i) (i32.const 2)))))
|
|
1497
|
+
(if (i32.or (i32.lt_s (local.get $hi) (i32.const 0)) (i32.lt_s (local.get $lo) (i32.const 0)))
|
|
1498
|
+
(then (throw $__jz_err (f64.const 0))))
|
|
1499
|
+
(local.set $c (i32.or (i32.shl (local.get $hi) (i32.const 4)) (local.get $lo)))
|
|
1500
|
+
(local.set $i (i32.add (local.get $i) (i32.const 3)))
|
|
1501
|
+
(if (i32.ge_u (local.get $c) (i32.const 128))
|
|
1502
|
+
(then
|
|
1503
|
+
(if (i32.and (i32.ge_u (local.get $c) (i32.const 0xC2)) (i32.le_u (local.get $c) (i32.const 0xDF)))
|
|
1504
|
+
(then
|
|
1505
|
+
(local.set $n (i32.const 2))
|
|
1506
|
+
(local.set $cp (i32.and (local.get $c) (i32.const 0x1F)))
|
|
1507
|
+
(local.set $min (i32.const 0x80)))
|
|
1508
|
+
(else (if (i32.and (i32.ge_u (local.get $c) (i32.const 0xE0)) (i32.le_u (local.get $c) (i32.const 0xEF)))
|
|
1509
|
+
(then
|
|
1510
|
+
(local.set $n (i32.const 3))
|
|
1511
|
+
(local.set $cp (i32.and (local.get $c) (i32.const 0x0F)))
|
|
1512
|
+
(local.set $min (i32.const 0x800)))
|
|
1513
|
+
(else (if (i32.and (i32.ge_u (local.get $c) (i32.const 0xF0)) (i32.le_u (local.get $c) (i32.const 0xF4)))
|
|
1514
|
+
(then
|
|
1515
|
+
(local.set $n (i32.const 4))
|
|
1516
|
+
(local.set $cp (i32.and (local.get $c) (i32.const 0x07)))
|
|
1517
|
+
(local.set $min (i32.const 0x10000)))
|
|
1518
|
+
(else (throw $__jz_err (f64.const 0))))))))
|
|
1519
|
+
(i32.store8 (i32.add (local.get $dst) (local.get $outLen)) (local.get $c))
|
|
1520
|
+
(local.set $outLen (i32.add (local.get $outLen) (i32.const 1)))
|
|
1521
|
+
(local.set $j (i32.const 1))
|
|
1522
|
+
(block $seqDone (loop $seq
|
|
1523
|
+
(br_if $seqDone (i32.ge_s (local.get $j) (local.get $n)))
|
|
1524
|
+
(if (i32.ge_s (i32.add (local.get $i) (i32.const 2)) (local.get $len))
|
|
1525
|
+
(then (throw $__jz_err (f64.const 0))))
|
|
1526
|
+
(if (i32.ne (call $__char_at (local.get $s) (local.get $i)) (i32.const 37))
|
|
1527
|
+
(then (throw $__jz_err (f64.const 0))))
|
|
1528
|
+
(local.set $hi (call $__uri_hex (call $__char_at (local.get $s) (i32.add (local.get $i) (i32.const 1)))))
|
|
1529
|
+
(local.set $lo (call $__uri_hex (call $__char_at (local.get $s) (i32.add (local.get $i) (i32.const 2)))))
|
|
1530
|
+
(if (i32.or (i32.lt_s (local.get $hi) (i32.const 0)) (i32.lt_s (local.get $lo) (i32.const 0)))
|
|
1531
|
+
(then (throw $__jz_err (f64.const 0))))
|
|
1532
|
+
(local.set $b (i32.or (i32.shl (local.get $hi) (i32.const 4)) (local.get $lo)))
|
|
1533
|
+
(if (i32.or (i32.lt_u (local.get $b) (i32.const 0x80)) (i32.gt_u (local.get $b) (i32.const 0xBF)))
|
|
1534
|
+
(then (throw $__jz_err (f64.const 0))))
|
|
1535
|
+
(local.set $cp (i32.or (i32.shl (local.get $cp) (i32.const 6)) (i32.and (local.get $b) (i32.const 0x3F))))
|
|
1536
|
+
(i32.store8 (i32.add (local.get $dst) (local.get $outLen)) (local.get $b))
|
|
1537
|
+
(local.set $outLen (i32.add (local.get $outLen) (i32.const 1)))
|
|
1538
|
+
(local.set $i (i32.add (local.get $i) (i32.const 3)))
|
|
1539
|
+
(local.set $j (i32.add (local.get $j) (i32.const 1)))
|
|
1540
|
+
(br $seq)))
|
|
1541
|
+
(if (i32.or
|
|
1542
|
+
(i32.or (i32.lt_u (local.get $cp) (local.get $min)) (i32.gt_u (local.get $cp) (i32.const 0x10FFFF)))
|
|
1543
|
+
(i32.and (i32.ge_u (local.get $cp) (i32.const 0xD800)) (i32.le_u (local.get $cp) (i32.const 0xDFFF))))
|
|
1544
|
+
(then (throw $__jz_err (f64.const 0))))
|
|
1545
|
+
(local.set $stored (i32.const 1)))))
|
|
1546
|
+
(else
|
|
1547
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))))
|
|
1548
|
+
(if (i32.eqz (local.get $stored))
|
|
1549
|
+
(then
|
|
1550
|
+
(i32.store8 (i32.add (local.get $dst) (local.get $outLen)) (local.get $c))
|
|
1551
|
+
(local.set $outLen (i32.add (local.get $outLen) (i32.const 1)))))
|
|
1552
|
+
(br $loop)))
|
|
1553
|
+
(i32.store (local.get $base) (local.get $outLen))
|
|
1554
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $dst)))`
|
|
1555
|
+
|
|
1556
|
+
ctx.core.emit['decodeURIComponent'] = (value) => {
|
|
1557
|
+
ctx.runtime.throws = true
|
|
1558
|
+
inc('__decodeURIComponent')
|
|
1559
|
+
return typed(['call', '$__decodeURIComponent',
|
|
1560
|
+
value === undefined ? ['i64.const', UNDEF_NAN] : asI64(emit(value))], 'f64')
|
|
1117
1561
|
}
|
|
1118
1562
|
|
|
1119
1563
|
// .at(i) → charAt with negative index support
|