jz 0.3.1 → 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 +277 -258
- package/cli.js +10 -52
- package/index.js +181 -29
- package/{src/host.js → interop.js} +291 -88
- package/module/array.js +195 -76
- package/module/collection.js +249 -20
- package/module/console.js +1 -1
- package/module/core.js +267 -119
- package/module/date.js +9 -5
- package/module/function.js +11 -1
- package/module/json.js +367 -61
- package/module/math.js +568 -128
- package/module/number.js +390 -227
- package/module/object.js +352 -39
- package/module/regex.js +123 -16
- package/module/schema.js +15 -1
- package/module/string.js +555 -96
- package/module/timer.js +1 -1
- package/module/typedarray.js +674 -57
- package/package.json +12 -6
- 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 +1872 -487
- package/src/assemble.js +58 -20
- package/src/autoload.js +13 -1
- package/src/codegen.js +177 -0
- package/src/compile.js +462 -186
- package/src/ctx.js +85 -18
- package/src/emit.js +668 -197
- package/src/infer.js +548 -0
- package/src/ir.js +302 -27
- package/src/jzify.js +898 -259
- package/src/narrow.js +455 -246
- package/src/optimize.js +263 -99
- package/src/plan.js +713 -35
- package/src/prepare.js +818 -293
- 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/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))
|
|
@@ -752,12 +970,27 @@ export default (ctx) => {
|
|
|
752
970
|
(br $next)))
|
|
753
971
|
(f64.reinterpret_i64 (local.get $result)))`
|
|
754
972
|
|
|
973
|
+
// Empty separator: per JS spec, split into individual byte-chars
|
|
974
|
+
// ("abc".split("") -> ["a","b","c"], "".split("") -> []). Without this
|
|
975
|
+
// guard the main loop advances by plen=0 and spins forever.
|
|
755
976
|
ctx.core.stdlib['__str_split'] = `(func $__str_split (param $str i64) (param $sep i64) (result f64)
|
|
756
977
|
(local $slen i32) (local $plen i32) (local $count i32)
|
|
757
978
|
(local $i i32) (local $j i32) (local $match i32)
|
|
758
979
|
(local $arr i32) (local $piece_start i32) (local $piece_idx i32)
|
|
759
980
|
(local.set $slen (call $__str_byteLen (local.get $str)))
|
|
760
981
|
(local.set $plen (call $__str_byteLen (local.get $sep)))
|
|
982
|
+
(if (i32.eqz (local.get $plen)) (then
|
|
983
|
+
(local.set $arr (call $__alloc (i32.add (i32.const 8) (i32.shl (local.get $slen) (i32.const 3)))))
|
|
984
|
+
(i32.store (local.get $arr) (local.get $slen))
|
|
985
|
+
(i32.store (i32.add (local.get $arr) (i32.const 4)) (local.get $slen))
|
|
986
|
+
(local.set $arr (i32.add (local.get $arr) (i32.const 8)))
|
|
987
|
+
(block $de (loop $le
|
|
988
|
+
(br_if $de (i32.ge_s (local.get $i) (local.get $slen)))
|
|
989
|
+
(f64.store (i32.add (local.get $arr) (i32.shl (local.get $i) (i32.const 3)))
|
|
990
|
+
(call $__str_slice (local.get $str) (local.get $i) (i32.add (local.get $i) (i32.const 1))))
|
|
991
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
992
|
+
(br $le)))
|
|
993
|
+
(return (call $__mkptr (i32.const 1) (i32.const 0) (local.get $arr)))))
|
|
761
994
|
(local.set $count (i32.const 1))
|
|
762
995
|
(local.set $i (i32.const 0))
|
|
763
996
|
(block $d1 (loop $l1
|
|
@@ -881,26 +1114,64 @@ export default (ctx) => {
|
|
|
881
1114
|
// override to return the primitive — strings already covered by .string:valueOf.
|
|
882
1115
|
ctx.core.emit['.valueOf'] = (val) => asF64(emit(val))
|
|
883
1116
|
|
|
884
|
-
|
|
885
|
-
|
|
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)
|
|
886
1125
|
const startIR = start == null ? ['i32.const', 0] : asI32(emit(start))
|
|
887
|
-
if (end != null) return typed(['call',
|
|
1126
|
+
if (end != null) return typed(['call', `$${fn}`, asI64(emit(str)), startIR, asI32(emit(end))], 'f64')
|
|
888
1127
|
const t = temp('t')
|
|
889
1128
|
return typed(['block', ['result', 'f64'],
|
|
890
1129
|
['local.set', `$${t}`, asF64(emit(str))],
|
|
891
|
-
['call',
|
|
1130
|
+
['call', `$${fn}`, ['i64.reinterpret_f64', ['local.get', `$${t}`]], startIR,
|
|
892
1131
|
['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]]], 'f64')
|
|
893
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))
|
|
894
1153
|
|
|
895
1154
|
ctx.core.emit['.string:indexOf'] = (str, search, from) => {
|
|
896
1155
|
inc('__str_indexof')
|
|
897
|
-
|
|
1156
|
+
const hay = asI64(emit(str)), ndl = searchArg(search)
|
|
1157
|
+
return typed(['f64.convert_i32_s', ['call', '$__str_indexof', hay, ndl, posIndex(from)]], 'f64')
|
|
898
1158
|
}
|
|
899
1159
|
|
|
900
|
-
|
|
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
|
|
901
1171
|
inc('__str_indexof')
|
|
1172
|
+
const hay = asI64(emit(str)), ndl = searchArg(search)
|
|
902
1173
|
return typed(['f64.convert_i32_s',
|
|
903
|
-
['i32.ge_s', ['call', '$__str_indexof',
|
|
1174
|
+
['i32.ge_s', ['call', '$__str_indexof', hay, ndl, posIndex(from)], ['i32.const', 0]]], 'f64')
|
|
904
1175
|
}
|
|
905
1176
|
|
|
906
1177
|
// Generic (no collision)
|
|
@@ -914,20 +1185,47 @@ export default (ctx) => {
|
|
|
914
1185
|
['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]]], 'f64')
|
|
915
1186
|
}
|
|
916
1187
|
|
|
917
|
-
//
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
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')
|
|
923
1208
|
}
|
|
924
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
|
+
|
|
925
1219
|
// Search args go through ToString per spec — coerce non-string-typed args
|
|
926
1220
|
// via __to_str so the underlying byte-compare receives an actual string.
|
|
927
1221
|
const stringSearchMethod = (name) => (str, sfx) => {
|
|
1222
|
+
const guard = regexpSearchGuard(sfx); if (guard) return guard
|
|
928
1223
|
inc(name)
|
|
929
|
-
|
|
930
|
-
|
|
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) {
|
|
931
1229
|
inc('__to_str')
|
|
932
1230
|
sfxArg = ['call', '$__to_str', sfxArg]
|
|
933
1231
|
}
|
|
@@ -935,20 +1233,13 @@ export default (ctx) => {
|
|
|
935
1233
|
}
|
|
936
1234
|
ctx.core.emit['.startsWith'] = stringSearchMethod('__str_startswith')
|
|
937
1235
|
ctx.core.emit['.endsWith'] = stringSearchMethod('__str_endswith')
|
|
938
|
-
ctx.core.emit['.trim']
|
|
939
|
-
|
|
940
|
-
ctx.core.emit['.
|
|
941
|
-
|
|
942
|
-
ctx.core.emit['.
|
|
943
|
-
|
|
944
|
-
ctx.core.emit['.
|
|
945
|
-
typed(['call', '$__str_repeat', asI64(emit(str)), asI32(emit(n))], 'f64'))
|
|
946
|
-
ctx.core.emit['.split'] = (str, sep) => (inc('__str_split'),
|
|
947
|
-
typed(['call', '$__str_split', asI64(emit(str)), asI64(emit(sep))], 'f64'))
|
|
948
|
-
ctx.core.emit['.replace'] = (str, search, repl) => (inc('__str_replace'),
|
|
949
|
-
typed(['call', '$__str_replace', asI64(emit(str)), asI64(emit(search)), asI64(emit(repl))], 'f64'))
|
|
950
|
-
ctx.core.emit['.replaceAll'] = (str, search, repl) => (inc('__str_replaceall'),
|
|
951
|
-
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')
|
|
952
1243
|
|
|
953
1244
|
ctx.core.emit['.toUpperCase'] = (str) => {
|
|
954
1245
|
inc('__str_case')
|
|
@@ -972,10 +1263,7 @@ export default (ctx) => {
|
|
|
972
1263
|
// the spec exactly; for non-ASCII it follows UTF-8 byte order, which is
|
|
973
1264
|
// codepoint order for well-formed strings — close enough for sort-stability
|
|
974
1265
|
// use cases, wrong for human-language collation.
|
|
975
|
-
ctx.core.emit['.localeCompare'] = (
|
|
976
|
-
inc('__str_cmp')
|
|
977
|
-
return typed(['f64.convert_i32_s', ['call', '$__str_cmp', asI64(emit(str)), asI64(emit(other))]], 'f64')
|
|
978
|
-
}
|
|
1266
|
+
ctx.core.emit['.localeCompare'] = method('__str_cmp', 'II', 'i32')
|
|
979
1267
|
|
|
980
1268
|
ctx.core.emit['.string:concat'] = (str, ...others) => {
|
|
981
1269
|
inc('__str_concat')
|
|
@@ -984,10 +1272,15 @@ export default (ctx) => {
|
|
|
984
1272
|
return result
|
|
985
1273
|
}
|
|
986
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
|
+
|
|
987
1280
|
ctx.core.emit['strcat'] = (...parts) => {
|
|
988
1281
|
inc('__to_str', '__str_byteLen', '__alloc', '__mkptr', '__str_copy')
|
|
989
1282
|
if (!parts.length) return mkPtrIR(PTR.STRING, LAYOUT.SSO_BIT, 0)
|
|
990
|
-
if (parts.length === 1) return typed(['f64.reinterpret_i64',
|
|
1283
|
+
if (parts.length === 1) return typed(['f64.reinterpret_i64', partStrI64(parts[0])], 'f64')
|
|
991
1284
|
|
|
992
1285
|
const vals = parts.map(() => temp('s'))
|
|
993
1286
|
const lens = parts.map(() => tempI32('sl'))
|
|
@@ -997,7 +1290,7 @@ export default (ctx) => {
|
|
|
997
1290
|
const ir = []
|
|
998
1291
|
|
|
999
1292
|
for (let i = 0; i < parts.length; i++) {
|
|
1000
|
-
ir.push(['local.set', `$${vals[i]}`, ['f64.reinterpret_i64',
|
|
1293
|
+
ir.push(['local.set', `$${vals[i]}`, ['f64.reinterpret_i64', partStrI64(parts[i])]])
|
|
1001
1294
|
ir.push(['local.set', `$${lens[i]}`, ['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${vals[i]}`]]]])
|
|
1002
1295
|
}
|
|
1003
1296
|
ir.push(['local.set', `$${total}`, ['i32.const', 0]])
|
|
@@ -1042,35 +1335,47 @@ export default (ctx) => {
|
|
|
1042
1335
|
mkPtrIR(PTR.STRING, LAYOUT.SSO_BIT | 1, ['local.get', `$${t}`])], 'f64')
|
|
1043
1336
|
}
|
|
1044
1337
|
|
|
1045
|
-
// .charCodeAt(i) →
|
|
1046
|
-
//
|
|
1047
|
-
//
|
|
1048
|
-
//
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
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')
|
|
1053
1347
|
|
|
1054
1348
|
// String.fromCharCode(code) → 1-char SSO string
|
|
1055
1349
|
ctx.core.emit['String'] = (value) => {
|
|
1056
1350
|
if (value === undefined) return emit(['str', ''])
|
|
1057
1351
|
if (valTypeOf(value) === VAL.STRING) return emit(value)
|
|
1352
|
+
if (valTypeOf(value) === VAL.BOOL) return emitBoolStr(value)
|
|
1058
1353
|
if (valTypeOf(value) === VAL.NUMBER) {
|
|
1059
1354
|
inc('__ftoa')
|
|
1060
1355
|
return typed(['call', '$__ftoa', asF64(emit(value)), ['i32.const', 0], ['i32.const', 0]], 'f64')
|
|
1061
1356
|
}
|
|
1062
|
-
|
|
1063
|
-
return typed(['f64.reinterpret_i64', ['call', '$__to_str', asI64(emit(value))]], 'f64')
|
|
1357
|
+
return typed(['f64.reinterpret_i64', toStrI64(value, emit(value))], 'f64')
|
|
1064
1358
|
}
|
|
1065
1359
|
|
|
1066
1360
|
ctx.core.emit['String.fromCharCode'] = (code) => {
|
|
1067
1361
|
if (code === undefined) return emit(['str', ''])
|
|
1068
|
-
|
|
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))))
|
|
1069
1365
|
}
|
|
1070
1366
|
|
|
1071
|
-
// String.fromCodePoint(cp) → UTF-8 encoded string
|
|
1072
|
-
|
|
1073
|
-
|
|
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)))
|
|
1074
1379
|
;; ASCII: 1 byte SSO
|
|
1075
1380
|
(if (i32.lt_u (local.get $cp) (i32.const 128))
|
|
1076
1381
|
(then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT | 1}) (local.get $cp)))))
|
|
@@ -1095,10 +1400,164 @@ export default (ctx) => {
|
|
|
1095
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)))
|
|
1096
1401
|
(i32.shl (i32.or (i32.const 0x80) (i32.and (local.get $cp) (i32.const 0x3F))) (i32.const 24))))))`
|
|
1097
1402
|
|
|
1098
|
-
|
|
1099
|
-
|
|
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
|
|
1100
1408
|
inc('__fromCodePoint')
|
|
1101
|
-
|
|
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')
|
|
1102
1561
|
}
|
|
1103
1562
|
|
|
1104
1563
|
// .at(i) → charAt with negative index support
|