jz 0.0.0 → 0.1.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/LICENSE +21 -0
- package/README.md +381 -0
- package/cli.js +163 -0
- package/index.js +217 -0
- package/module/array.js +1317 -0
- package/module/collection.js +791 -0
- package/module/console.js +190 -0
- package/module/core.js +642 -0
- package/module/function.js +180 -0
- package/module/index.js +15 -0
- package/module/json.js +504 -0
- package/module/math.js +389 -0
- package/module/number.js +605 -0
- package/module/object.js +357 -0
- package/module/regex.js +913 -0
- package/module/schema.js +104 -0
- package/module/string.js +928 -0
- package/module/symbol.js +54 -0
- package/module/timer.js +253 -0
- package/module/typedarray.js +711 -0
- package/package.json +54 -5
- package/src/analyze.js +1906 -0
- package/src/compile.js +2175 -0
- package/src/ctx.js +243 -0
- package/src/emit.js +2095 -0
- package/src/host.js +524 -0
- package/src/ir.js +649 -0
- package/src/jzify.js +391 -0
- package/src/optimize.js +1352 -0
- package/src/prepare.js +1598 -0
- package/wasi.js +74 -0
package/module/string.js
ADDED
|
@@ -0,0 +1,928 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String module — literals, char access, and string methods.
|
|
3
|
+
*
|
|
4
|
+
* Type=4 (STRING): heap-allocated, length in header [-4:len].
|
|
5
|
+
* Type=5 (STRING_SSO): ≤4 ASCII chars packed in pointer offset (no memory).
|
|
6
|
+
*
|
|
7
|
+
* Methods use type-qualified keys (.string:slice) for array-colliding names,
|
|
8
|
+
* generic keys (.toUpperCase) for non-colliding ones.
|
|
9
|
+
*
|
|
10
|
+
* @module string
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { emit, typed, asF64, asI32, UNDEF_NAN, mkPtrIR, temp, tempI32 } from '../src/compile.js'
|
|
14
|
+
import { inc, PTR } from '../src/ctx.js'
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
export default (ctx) => {
|
|
18
|
+
Object.assign(ctx.core.stdlibDeps, {
|
|
19
|
+
__str_concat: ['__to_str', '__str_byteLen', '__alloc', '__mkptr', '__str_copy'],
|
|
20
|
+
__str_concat_raw: ['__str_byteLen', '__alloc', '__mkptr', '__str_copy'],
|
|
21
|
+
__str_copy: [],
|
|
22
|
+
__str_slice: ['__str_byteLen', '__alloc'],
|
|
23
|
+
__str_indexof: ['__str_byteLen'],
|
|
24
|
+
__str_substring: ['__str_slice'],
|
|
25
|
+
__str_startswith: ['__str_byteLen'],
|
|
26
|
+
__str_endswith: ['__str_byteLen'],
|
|
27
|
+
__str_case: ['__str_byteLen', '__alloc'],
|
|
28
|
+
__str_trim: ['__str_slice', '__str_byteLen', '__char_at'],
|
|
29
|
+
__str_trimStart: ['__str_slice', '__str_byteLen', '__char_at'],
|
|
30
|
+
__str_trimEnd: ['__str_slice', '__str_byteLen', '__char_at'],
|
|
31
|
+
__str_repeat: ['__str_byteLen', '__str_copy', '__alloc'],
|
|
32
|
+
__str_replace: ['__str_indexof', '__str_slice', '__str_concat'],
|
|
33
|
+
__str_replaceall: ['__str_indexof', '__str_slice', '__str_concat'],
|
|
34
|
+
__str_split: ['__str_slice', '__str_byteLen', '__char_at', '__alloc'],
|
|
35
|
+
__str_idx: ['__str_byteLen', '__char_at', '__mkptr'],
|
|
36
|
+
__str_eq: ['__char_at'],
|
|
37
|
+
__str_pad: ['__str_byteLen', '__str_copy', '__alloc'],
|
|
38
|
+
__str_join: ['__str_concat', '__to_str', '__str_byteLen', '__len', '__ptr_offset'],
|
|
39
|
+
__str_encode: ['__str_byteLen', '__str_copy'],
|
|
40
|
+
__to_str: ['__ftoa', '__static_str', '__str_join', '__mkptr'],
|
|
41
|
+
__str_byteLen: ['__ptr_type', '__ptr_aux', '__str_len'],
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
inc('__mkptr', '__alloc')
|
|
45
|
+
|
|
46
|
+
// === String literal: "abc" → SSO if ≤4 ASCII, else heap ===
|
|
47
|
+
|
|
48
|
+
ctx.core.emit['str'] = (str) => {
|
|
49
|
+
const MAX_SSO = 4
|
|
50
|
+
if (ctx.features.sso && str.length <= MAX_SSO && /^[\x00-\x7f]*$/.test(str)) {
|
|
51
|
+
let packed = 0
|
|
52
|
+
for (let i = 0; i < str.length; i++) packed |= str.charCodeAt(i) << (i * 8)
|
|
53
|
+
return mkPtrIR(PTR.SSO, str.length, packed)
|
|
54
|
+
}
|
|
55
|
+
const len = str.length
|
|
56
|
+
if (!ctx.memory.shared) {
|
|
57
|
+
// Own memory: place in static data segment (no runtime allocation)
|
|
58
|
+
if (!ctx.runtime.data) ctx.runtime.data = ''
|
|
59
|
+
const prior = ctx.runtime.dataDedup.get(str)
|
|
60
|
+
if (prior !== undefined) return mkPtrIR(PTR.STRING, 0, prior + 4)
|
|
61
|
+
while (ctx.runtime.data.length % 4 !== 0) ctx.runtime.data += '\0'
|
|
62
|
+
const offset = ctx.runtime.data.length
|
|
63
|
+
ctx.runtime.data += String.fromCharCode(len & 0xFF, (len >> 8) & 0xFF, (len >> 16) & 0xFF, (len >> 24) & 0xFF)
|
|
64
|
+
for (let i = 0; i < len; i++) ctx.runtime.data += String.fromCharCode(str.charCodeAt(i))
|
|
65
|
+
ctx.runtime.dataDedup.set(str, offset)
|
|
66
|
+
return mkPtrIR(PTR.STRING, 0, offset + 4)
|
|
67
|
+
}
|
|
68
|
+
// Shared memory: pack all string literals into one passive data segment with 4-byte
|
|
69
|
+
// length prefixes. At __start, alloc the whole pool once and memory.init it in a single
|
|
70
|
+
// call. Each use site resolves to `strBase + compile-time-offset` — O(1) IR nodes per
|
|
71
|
+
// use, independent of string length AND reused across uses.
|
|
72
|
+
if (!ctx.runtime.strPool) {
|
|
73
|
+
ctx.runtime.strPool = ''
|
|
74
|
+
ctx.scope.globals.set('__strBase', '(global $__strBase (mut i32) (i32.const 0))')
|
|
75
|
+
}
|
|
76
|
+
let off = ctx.runtime.strPoolDedup.get(str)
|
|
77
|
+
if (off === undefined) {
|
|
78
|
+
// Pack length header then bytes; offset points PAST the length (at the data).
|
|
79
|
+
ctx.runtime.strPool += String.fromCharCode(len & 0xFF, (len >> 8) & 0xFF, (len >> 16) & 0xFF, (len >> 24) & 0xFF)
|
|
80
|
+
off = ctx.runtime.strPool.length
|
|
81
|
+
ctx.runtime.strPool += str
|
|
82
|
+
ctx.runtime.strPoolDedup.set(str, off)
|
|
83
|
+
}
|
|
84
|
+
return mkPtrIR(PTR.STRING, 0, ['i32.add', ['global.get', '$__strBase'], ['i32.const', off]])
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// === WAT: char extraction ===
|
|
88
|
+
|
|
89
|
+
// SSO/STRING ptrs never have forwarding pointers (only ARRAY does), so we extract
|
|
90
|
+
// the raw offset directly instead of paying the __ptr_offset function-call overhead.
|
|
91
|
+
ctx.core.stdlib['__sso_char'] = `(func $__sso_char (param $ptr f64) (param $i i32) (result i32)
|
|
92
|
+
(i32.and
|
|
93
|
+
(i32.shr_u
|
|
94
|
+
(i32.wrap_i64 (i64.and (i64.reinterpret_f64 (local.get $ptr)) (i64.const 0xFFFFFFFF)))
|
|
95
|
+
(i32.mul (local.get $i) (i32.const 8)))
|
|
96
|
+
(i32.const 0xFF)))`
|
|
97
|
+
|
|
98
|
+
ctx.core.stdlib['__str_char'] = `(func $__str_char (param $ptr f64) (param $i i32) (result i32)
|
|
99
|
+
(i32.load8_u (i32.add
|
|
100
|
+
(i32.wrap_i64 (i64.and (i64.reinterpret_f64 (local.get $ptr)) (i64.const 0xFFFFFFFF)))
|
|
101
|
+
(local.get $i))))`
|
|
102
|
+
|
|
103
|
+
// Hot (~37M calls in watr self-host). Type+offset extracted once from $bits;
|
|
104
|
+
// SSO/STRING bodies merged inline to skip 2 function calls per char fetch.
|
|
105
|
+
ctx.core.stdlib['__char_at'] = `(func $__char_at (param $ptr f64) (param $i i32) (result i32)
|
|
106
|
+
(local $bits i64) (local $off i32)
|
|
107
|
+
(local.set $bits (i64.reinterpret_f64 (local.get $ptr)))
|
|
108
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const 0xFFFFFFFF))))
|
|
109
|
+
(if (result i32)
|
|
110
|
+
(i32.eq
|
|
111
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 47)) (i64.const 0xF)))
|
|
112
|
+
(i32.const ${PTR.SSO}))
|
|
113
|
+
(then
|
|
114
|
+
(i32.and
|
|
115
|
+
(i32.shr_u (local.get $off) (i32.mul (local.get $i) (i32.const 8)))
|
|
116
|
+
(i32.const 0xFF)))
|
|
117
|
+
(else
|
|
118
|
+
(i32.load8_u (i32.add (local.get $off) (local.get $i))))))`
|
|
119
|
+
|
|
120
|
+
ctx.core.stdlib['__str_idx'] = `(func $__str_idx (param $ptr f64) (param $i i32) (result f64)
|
|
121
|
+
(local $len i32)
|
|
122
|
+
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
123
|
+
(if (result f64)
|
|
124
|
+
(i32.or
|
|
125
|
+
(i32.lt_s (local.get $i) (i32.const 0))
|
|
126
|
+
(i32.ge_u (local.get $i) (local.get $len)))
|
|
127
|
+
(then (f64.const nan:${UNDEF_NAN}))
|
|
128
|
+
(else
|
|
129
|
+
(call $__mkptr
|
|
130
|
+
(i32.const ${PTR.SSO})
|
|
131
|
+
(i32.const 1)
|
|
132
|
+
(call $__char_at (local.get $ptr) (local.get $i))))))`
|
|
133
|
+
|
|
134
|
+
// Hot: ~53M calls in watr self-host. Bit-eq covers identity. SSO/SSO with !bit-eq
|
|
135
|
+
// guarantees content differs (high 32 bits encode type+len; both equal → low 32 differs
|
|
136
|
+
// ⇒ bytes differ). STRING/STRING uses raw load8_u — no per-byte function calls.
|
|
137
|
+
// Mixed SSO×STRING is rare; falls back to __char_at.
|
|
138
|
+
ctx.core.stdlib['__str_eq'] = `(func $__str_eq (param $a f64) (param $b f64) (result i32)
|
|
139
|
+
(local $len i32) (local $lenB i32) (local $i i32)
|
|
140
|
+
(local $ba i64) (local $bb i64) (local $ta i32) (local $tb i32)
|
|
141
|
+
(local $offA i32) (local $offB i32)
|
|
142
|
+
(local.set $ba (i64.reinterpret_f64 (local.get $a)))
|
|
143
|
+
(local.set $bb (i64.reinterpret_f64 (local.get $b)))
|
|
144
|
+
(if (i64.eq (local.get $ba) (local.get $bb))
|
|
145
|
+
(then (return (i32.const 1))))
|
|
146
|
+
(local.set $ta (i32.wrap_i64 (i64.and (i64.shr_u (local.get $ba) (i64.const 47)) (i64.const 0xF))))
|
|
147
|
+
(local.set $tb (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bb) (i64.const 47)) (i64.const 0xF))))
|
|
148
|
+
(local.set $offA (i32.wrap_i64 (i64.and (local.get $ba) (i64.const 0xFFFFFFFF))))
|
|
149
|
+
(local.set $offB (i32.wrap_i64 (i64.and (local.get $bb) (i64.const 0xFFFFFFFF))))
|
|
150
|
+
;; Both SSO with !bit-eq ⇒ content differs (high 32 bits hold type+len; both equal here).
|
|
151
|
+
(if (i32.and (i32.eq (local.get $ta) (i32.const ${PTR.SSO})) (i32.eq (local.get $tb) (i32.const ${PTR.SSO})))
|
|
152
|
+
(then (return (i32.const 0))))
|
|
153
|
+
;; Both STRING fast path: inline len from header. Chunk by 4 bytes via unaligned i32.load
|
|
154
|
+
;; (wasm guarantees unaligned-OK), then byte-tail. Most string comparisons fail early on
|
|
155
|
+
;; the first 4-byte word, so this collapses the per-byte branch overhead into a single
|
|
156
|
+
;; 32-bit equality.
|
|
157
|
+
(if (i32.and (i32.eq (local.get $ta) (i32.const ${PTR.STRING})) (i32.eq (local.get $tb) (i32.const ${PTR.STRING})))
|
|
158
|
+
(then
|
|
159
|
+
(if (i32.or (i32.lt_u (local.get $offA) (i32.const 4)) (i32.lt_u (local.get $offB) (i32.const 4)))
|
|
160
|
+
(then (return (i32.const 0))))
|
|
161
|
+
(local.set $len (i32.load (i32.sub (local.get $offA) (i32.const 4))))
|
|
162
|
+
(local.set $lenB (i32.load (i32.sub (local.get $offB) (i32.const 4))))
|
|
163
|
+
(if (i32.ne (local.get $len) (local.get $lenB))
|
|
164
|
+
(then (return (i32.const 0))))
|
|
165
|
+
(local.set $lenB (i32.and (local.get $len) (i32.const -4)))
|
|
166
|
+
(block $d4 (loop $l4
|
|
167
|
+
(br_if $d4 (i32.ge_s (local.get $i) (local.get $lenB)))
|
|
168
|
+
(if (i32.ne
|
|
169
|
+
(i32.load (i32.add (local.get $offA) (local.get $i)))
|
|
170
|
+
(i32.load (i32.add (local.get $offB) (local.get $i))))
|
|
171
|
+
(then (return (i32.const 0))))
|
|
172
|
+
(local.set $i (i32.add (local.get $i) (i32.const 4)))
|
|
173
|
+
(br $l4)))
|
|
174
|
+
(block $dh (loop $lh
|
|
175
|
+
(br_if $dh (i32.ge_s (local.get $i) (local.get $len)))
|
|
176
|
+
(if (i32.ne
|
|
177
|
+
(i32.load8_u (i32.add (local.get $offA) (local.get $i)))
|
|
178
|
+
(i32.load8_u (i32.add (local.get $offB) (local.get $i))))
|
|
179
|
+
(then (return (i32.const 0))))
|
|
180
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
181
|
+
(br $lh)))
|
|
182
|
+
(return (i32.const 1))))
|
|
183
|
+
;; Mixed (SSO×STRING) or anything else: compute len per side then per-byte via __char_at.
|
|
184
|
+
(if (i32.eq (local.get $ta) (i32.const ${PTR.SSO}))
|
|
185
|
+
(then (local.set $len (i32.wrap_i64 (i64.and (i64.shr_u (local.get $ba) (i64.const 32)) (i64.const 0x7FFF)))))
|
|
186
|
+
(else
|
|
187
|
+
(if (i32.and (i32.eq (local.get $ta) (i32.const ${PTR.STRING})) (i32.ge_u (local.get $offA) (i32.const 4)))
|
|
188
|
+
(then (local.set $len (i32.load (i32.sub (local.get $offA) (i32.const 4))))))))
|
|
189
|
+
(if (i32.eq (local.get $tb) (i32.const ${PTR.SSO}))
|
|
190
|
+
(then (local.set $lenB (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bb) (i64.const 32)) (i64.const 0x7FFF)))))
|
|
191
|
+
(else
|
|
192
|
+
(if (i32.and (i32.eq (local.get $tb) (i32.const ${PTR.STRING})) (i32.ge_u (local.get $offB) (i32.const 4)))
|
|
193
|
+
(then (local.set $lenB (i32.load (i32.sub (local.get $offB) (i32.const 4))))))))
|
|
194
|
+
(if (i32.ne (local.get $len) (local.get $lenB))
|
|
195
|
+
(then (return (i32.const 0))))
|
|
196
|
+
(block $dm (loop $lm
|
|
197
|
+
(br_if $dm (i32.ge_s (local.get $i) (local.get $len)))
|
|
198
|
+
(if (i32.ne (call $__char_at (local.get $a) (local.get $i))
|
|
199
|
+
(call $__char_at (local.get $b) (local.get $i)))
|
|
200
|
+
(then (return (i32.const 0))))
|
|
201
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
202
|
+
(br $lm)))
|
|
203
|
+
(i32.const 1))`
|
|
204
|
+
|
|
205
|
+
// === WAT: unified byte length (SSO → aux, heap → header) ===
|
|
206
|
+
|
|
207
|
+
ctx.core.stdlib['__str_byteLen'] = `(func $__str_byteLen (param $ptr f64) (result i32)
|
|
208
|
+
(local $bits i64) (local $t i32) (local $off i32)
|
|
209
|
+
(local.set $bits (i64.reinterpret_f64 (local.get $ptr)))
|
|
210
|
+
(local.set $t (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 47)) (i64.const 0xF))))
|
|
211
|
+
(if (result i32) (i32.eq (local.get $t) (i32.const ${PTR.SSO}))
|
|
212
|
+
(then (i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 32)) (i64.const 0x7FFF))))
|
|
213
|
+
(else
|
|
214
|
+
(local.set $off (i32.wrap_i64 (i64.and (local.get $bits) (i64.const 0xFFFFFFFF))))
|
|
215
|
+
(if (result i32)
|
|
216
|
+
(i32.and (i32.eq (local.get $t) (i32.const ${PTR.STRING})) (i32.ge_u (local.get $off) (i32.const 4)))
|
|
217
|
+
(then (i32.load (i32.sub (local.get $off) (i32.const 4))))
|
|
218
|
+
(else (i32.const 0))))))`
|
|
219
|
+
|
|
220
|
+
// === WAT: string methods ===
|
|
221
|
+
|
|
222
|
+
// SSO source uses an unrolled byte-extract loop (len ≤ 4); heap source uses memory.copy
|
|
223
|
+
// (single bulk op instead of nlen × __char_at).
|
|
224
|
+
ctx.core.stdlib['__str_slice'] = `(func $__str_slice (param $ptr f64) (param $start i32) (param $end i32) (result f64)
|
|
225
|
+
(local $len i32) (local $nlen i32) (local $off i32) (local $i i32)
|
|
226
|
+
(local $bits i64) (local $srcOff i32) (local $isSso i32)
|
|
227
|
+
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
228
|
+
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
229
|
+
(then (local.set $start (i32.add (local.get $len) (local.get $start)))))
|
|
230
|
+
(if (i32.lt_s (local.get $end) (i32.const 0))
|
|
231
|
+
(then (local.set $end (i32.add (local.get $len) (local.get $end)))))
|
|
232
|
+
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
233
|
+
(then (local.set $start (i32.const 0))))
|
|
234
|
+
(if (i32.gt_s (local.get $start) (local.get $len))
|
|
235
|
+
(then (local.set $start (local.get $len))))
|
|
236
|
+
(if (i32.lt_s (local.get $end) (i32.const 0))
|
|
237
|
+
(then (local.set $end (i32.const 0))))
|
|
238
|
+
(if (i32.gt_s (local.get $end) (local.get $len))
|
|
239
|
+
(then (local.set $end (local.get $len))))
|
|
240
|
+
(if (i32.ge_s (local.get $start) (local.get $end))
|
|
241
|
+
(then (return (call $__mkptr (i32.const ${PTR.SSO}) (i32.const 0) (i32.const 0)))))
|
|
242
|
+
(local.set $nlen (i32.sub (local.get $end) (local.get $start)))
|
|
243
|
+
(local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $nlen))))
|
|
244
|
+
(i32.store (local.get $off) (local.get $nlen))
|
|
245
|
+
(local.set $off (i32.add (local.get $off) (i32.const 4)))
|
|
246
|
+
(local.set $bits (i64.reinterpret_f64 (local.get $ptr)))
|
|
247
|
+
(local.set $srcOff (i32.wrap_i64 (i64.and (local.get $bits) (i64.const 0xFFFFFFFF))))
|
|
248
|
+
(local.set $isSso (i32.eq
|
|
249
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 47)) (i64.const 0xF)))
|
|
250
|
+
(i32.const ${PTR.SSO})))
|
|
251
|
+
(if (local.get $isSso)
|
|
252
|
+
(then
|
|
253
|
+
(block $done (loop $loop
|
|
254
|
+
(br_if $done (i32.ge_s (local.get $i) (local.get $nlen)))
|
|
255
|
+
(i32.store8 (i32.add (local.get $off) (local.get $i))
|
|
256
|
+
(i32.and (i32.shr_u (local.get $srcOff)
|
|
257
|
+
(i32.shl (i32.add (local.get $start) (local.get $i)) (i32.const 3)))
|
|
258
|
+
(i32.const 0xFF)))
|
|
259
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
260
|
+
(br $loop))))
|
|
261
|
+
(else
|
|
262
|
+
(memory.copy (local.get $off) (i32.add (local.get $srcOff) (local.get $start)) (local.get $nlen))))
|
|
263
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`
|
|
264
|
+
|
|
265
|
+
ctx.core.stdlib['__str_substring'] = `(func $__str_substring (param $ptr f64) (param $start i32) (param $end i32) (result f64)
|
|
266
|
+
(local $len i32) (local $tmp i32)
|
|
267
|
+
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
268
|
+
(if (i32.lt_s (local.get $start) (i32.const 0))
|
|
269
|
+
(then (local.set $start (i32.const 0))))
|
|
270
|
+
(if (i32.lt_s (local.get $end) (i32.const 0))
|
|
271
|
+
(then (local.set $end (i32.const 0))))
|
|
272
|
+
(if (i32.gt_s (local.get $start) (local.get $len))
|
|
273
|
+
(then (local.set $start (local.get $len))))
|
|
274
|
+
(if (i32.gt_s (local.get $end) (local.get $len))
|
|
275
|
+
(then (local.set $end (local.get $len))))
|
|
276
|
+
(if (i32.gt_s (local.get $start) (local.get $end))
|
|
277
|
+
(then
|
|
278
|
+
(local.set $tmp (local.get $start))
|
|
279
|
+
(local.set $start (local.get $end))
|
|
280
|
+
(local.set $end (local.get $tmp))))
|
|
281
|
+
(call $__str_slice (local.get $ptr) (local.get $start) (local.get $end)))`
|
|
282
|
+
|
|
283
|
+
// Hoist SSO/heap dispatch for hay and ndl out of the inner byte loop. Inner
|
|
284
|
+
// loop becomes (load8_u OR sso byte-extract) per side — no per-byte calls.
|
|
285
|
+
ctx.core.stdlib['__str_indexof'] = `(func $__str_indexof (param $hay f64) (param $ndl f64) (param $from i32) (result i32)
|
|
286
|
+
(local $hlen i32) (local $nlen i32) (local $i i32) (local $j i32) (local $match i32)
|
|
287
|
+
(local $hbits i64) (local $nbits i64) (local $hoff i32) (local $noff i32)
|
|
288
|
+
(local $hsso i32) (local $nsso i32) (local $hb i32) (local $nb i32) (local $k i32)
|
|
289
|
+
(local.set $hlen (call $__str_byteLen (local.get $hay)))
|
|
290
|
+
(local.set $nlen (call $__str_byteLen (local.get $ndl)))
|
|
291
|
+
(if (i32.eqz (local.get $nlen)) (then (return (local.get $from))))
|
|
292
|
+
(if (i32.gt_s (local.get $nlen) (local.get $hlen)) (then (return (i32.const -1))))
|
|
293
|
+
(local.set $hbits (i64.reinterpret_f64 (local.get $hay)))
|
|
294
|
+
(local.set $nbits (i64.reinterpret_f64 (local.get $ndl)))
|
|
295
|
+
(local.set $hoff (i32.wrap_i64 (i64.and (local.get $hbits) (i64.const 0xFFFFFFFF))))
|
|
296
|
+
(local.set $noff (i32.wrap_i64 (i64.and (local.get $nbits) (i64.const 0xFFFFFFFF))))
|
|
297
|
+
(local.set $hsso (i32.eq
|
|
298
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $hbits) (i64.const 47)) (i64.const 0xF)))
|
|
299
|
+
(i32.const ${PTR.SSO})))
|
|
300
|
+
(local.set $nsso (i32.eq
|
|
301
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $nbits) (i64.const 47)) (i64.const 0xF)))
|
|
302
|
+
(i32.const ${PTR.SSO})))
|
|
303
|
+
(local.set $i (if (result i32) (i32.gt_s (local.get $from) (i32.const 0)) (then (local.get $from)) (else (i32.const 0))))
|
|
304
|
+
(block $done (loop $outer
|
|
305
|
+
(br_if $done (i32.gt_s (local.get $i) (i32.sub (local.get $hlen) (local.get $nlen))))
|
|
306
|
+
(local.set $match (i32.const 1))
|
|
307
|
+
(local.set $j (i32.const 0))
|
|
308
|
+
(block $nomatch (loop $inner
|
|
309
|
+
(br_if $nomatch (i32.ge_s (local.get $j) (local.get $nlen)))
|
|
310
|
+
(local.set $k (i32.add (local.get $i) (local.get $j)))
|
|
311
|
+
(local.set $hb (if (result i32) (local.get $hsso)
|
|
312
|
+
(then (i32.and (i32.shr_u (local.get $hoff) (i32.shl (local.get $k) (i32.const 3))) (i32.const 0xFF)))
|
|
313
|
+
(else (i32.load8_u (i32.add (local.get $hoff) (local.get $k))))))
|
|
314
|
+
(local.set $nb (if (result i32) (local.get $nsso)
|
|
315
|
+
(then (i32.and (i32.shr_u (local.get $noff) (i32.shl (local.get $j) (i32.const 3))) (i32.const 0xFF)))
|
|
316
|
+
(else (i32.load8_u (i32.add (local.get $noff) (local.get $j))))))
|
|
317
|
+
(if (i32.ne (local.get $hb) (local.get $nb))
|
|
318
|
+
(then (local.set $match (i32.const 0)) (br $nomatch)))
|
|
319
|
+
(local.set $j (i32.add (local.get $j) (i32.const 1)))
|
|
320
|
+
(br $inner)))
|
|
321
|
+
(if (local.get $match) (then (return (local.get $i))))
|
|
322
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
323
|
+
(br $outer)))
|
|
324
|
+
(i32.const -1))`
|
|
325
|
+
|
|
326
|
+
// SSO/heap dispatch hoisted; inner loop is two inlined byte-fetches and a compare.
|
|
327
|
+
ctx.core.stdlib['__str_startswith'] = `(func $__str_startswith (param $str f64) (param $pfx f64) (result i32)
|
|
328
|
+
(local $plen i32) (local $i i32)
|
|
329
|
+
(local $sbits i64) (local $pbits i64) (local $soff i32) (local $poff i32) (local $ssso i32) (local $psso i32)
|
|
330
|
+
(local.set $plen (call $__str_byteLen (local.get $pfx)))
|
|
331
|
+
(if (i32.gt_s (local.get $plen) (call $__str_byteLen (local.get $str)))
|
|
332
|
+
(then (return (i32.const 0))))
|
|
333
|
+
(local.set $sbits (i64.reinterpret_f64 (local.get $str)))
|
|
334
|
+
(local.set $pbits (i64.reinterpret_f64 (local.get $pfx)))
|
|
335
|
+
(local.set $soff (i32.wrap_i64 (i64.and (local.get $sbits) (i64.const 0xFFFFFFFF))))
|
|
336
|
+
(local.set $poff (i32.wrap_i64 (i64.and (local.get $pbits) (i64.const 0xFFFFFFFF))))
|
|
337
|
+
(local.set $ssso (i32.eq
|
|
338
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $sbits) (i64.const 47)) (i64.const 0xF)))
|
|
339
|
+
(i32.const ${PTR.SSO})))
|
|
340
|
+
(local.set $psso (i32.eq
|
|
341
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $pbits) (i64.const 47)) (i64.const 0xF)))
|
|
342
|
+
(i32.const ${PTR.SSO})))
|
|
343
|
+
(block $done (loop $loop
|
|
344
|
+
(br_if $done (i32.ge_s (local.get $i) (local.get $plen)))
|
|
345
|
+
(if (i32.ne
|
|
346
|
+
(if (result i32) (local.get $ssso)
|
|
347
|
+
(then (i32.and (i32.shr_u (local.get $soff) (i32.shl (local.get $i) (i32.const 3))) (i32.const 0xFF)))
|
|
348
|
+
(else (i32.load8_u (i32.add (local.get $soff) (local.get $i)))))
|
|
349
|
+
(if (result i32) (local.get $psso)
|
|
350
|
+
(then (i32.and (i32.shr_u (local.get $poff) (i32.shl (local.get $i) (i32.const 3))) (i32.const 0xFF)))
|
|
351
|
+
(else (i32.load8_u (i32.add (local.get $poff) (local.get $i))))))
|
|
352
|
+
(then (return (i32.const 0))))
|
|
353
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
354
|
+
(br $loop)))
|
|
355
|
+
(i32.const 1))`
|
|
356
|
+
|
|
357
|
+
ctx.core.stdlib['__str_endswith'] = `(func $__str_endswith (param $str f64) (param $sfx f64) (result i32)
|
|
358
|
+
(local $slen i32) (local $flen i32) (local $off i32) (local $i i32) (local $k i32)
|
|
359
|
+
(local $sbits i64) (local $fbits i64) (local $soff i32) (local $foff i32) (local $ssso i32) (local $fsso i32)
|
|
360
|
+
(local.set $slen (call $__str_byteLen (local.get $str)))
|
|
361
|
+
(local.set $flen (call $__str_byteLen (local.get $sfx)))
|
|
362
|
+
(if (i32.gt_s (local.get $flen) (local.get $slen))
|
|
363
|
+
(then (return (i32.const 0))))
|
|
364
|
+
(local.set $off (i32.sub (local.get $slen) (local.get $flen)))
|
|
365
|
+
(local.set $sbits (i64.reinterpret_f64 (local.get $str)))
|
|
366
|
+
(local.set $fbits (i64.reinterpret_f64 (local.get $sfx)))
|
|
367
|
+
(local.set $soff (i32.wrap_i64 (i64.and (local.get $sbits) (i64.const 0xFFFFFFFF))))
|
|
368
|
+
(local.set $foff (i32.wrap_i64 (i64.and (local.get $fbits) (i64.const 0xFFFFFFFF))))
|
|
369
|
+
(local.set $ssso (i32.eq
|
|
370
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $sbits) (i64.const 47)) (i64.const 0xF)))
|
|
371
|
+
(i32.const ${PTR.SSO})))
|
|
372
|
+
(local.set $fsso (i32.eq
|
|
373
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $fbits) (i64.const 47)) (i64.const 0xF)))
|
|
374
|
+
(i32.const ${PTR.SSO})))
|
|
375
|
+
(block $done (loop $loop
|
|
376
|
+
(br_if $done (i32.ge_s (local.get $i) (local.get $flen)))
|
|
377
|
+
(local.set $k (i32.add (local.get $off) (local.get $i)))
|
|
378
|
+
(if (i32.ne
|
|
379
|
+
(if (result i32) (local.get $ssso)
|
|
380
|
+
(then (i32.and (i32.shr_u (local.get $soff) (i32.shl (local.get $k) (i32.const 3))) (i32.const 0xFF)))
|
|
381
|
+
(else (i32.load8_u (i32.add (local.get $soff) (local.get $k)))))
|
|
382
|
+
(if (result i32) (local.get $fsso)
|
|
383
|
+
(then (i32.and (i32.shr_u (local.get $foff) (i32.shl (local.get $i) (i32.const 3))) (i32.const 0xFF)))
|
|
384
|
+
(else (i32.load8_u (i32.add (local.get $foff) (local.get $i))))))
|
|
385
|
+
(then (return (i32.const 0))))
|
|
386
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
387
|
+
(br $loop)))
|
|
388
|
+
(i32.const 1))`
|
|
389
|
+
|
|
390
|
+
// Source SSO/heap dispatch hoisted out of the byte loop (was a per-byte __char_at).
|
|
391
|
+
ctx.core.stdlib['__str_case'] = `(func $__str_case (param $ptr f64) (param $lo i32) (param $hi i32) (param $delta i32) (result f64)
|
|
392
|
+
(local $len i32) (local $off i32) (local $i i32) (local $c i32)
|
|
393
|
+
(local $bits i64) (local $srcOff i32)
|
|
394
|
+
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
395
|
+
(if (i32.eqz (local.get $len))
|
|
396
|
+
(then (return (call $__mkptr (i32.const ${PTR.SSO}) (i32.const 0) (i32.const 0)))))
|
|
397
|
+
(local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $len))))
|
|
398
|
+
(i32.store (local.get $off) (local.get $len))
|
|
399
|
+
(local.set $off (i32.add (local.get $off) (i32.const 4)))
|
|
400
|
+
(local.set $bits (i64.reinterpret_f64 (local.get $ptr)))
|
|
401
|
+
(local.set $srcOff (i32.wrap_i64 (i64.and (local.get $bits) (i64.const 0xFFFFFFFF))))
|
|
402
|
+
(if (i32.eq
|
|
403
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 47)) (i64.const 0xF)))
|
|
404
|
+
(i32.const ${PTR.SSO}))
|
|
405
|
+
(then
|
|
406
|
+
(block $dsso (loop $lsso
|
|
407
|
+
(br_if $dsso (i32.ge_s (local.get $i) (local.get $len)))
|
|
408
|
+
(local.set $c (i32.and
|
|
409
|
+
(i32.shr_u (local.get $srcOff) (i32.shl (local.get $i) (i32.const 3)))
|
|
410
|
+
(i32.const 0xFF)))
|
|
411
|
+
(if (i32.and (i32.ge_u (local.get $c) (local.get $lo)) (i32.le_u (local.get $c) (local.get $hi)))
|
|
412
|
+
(then (local.set $c (i32.add (local.get $c) (local.get $delta)))))
|
|
413
|
+
(i32.store8 (i32.add (local.get $off) (local.get $i)) (local.get $c))
|
|
414
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
415
|
+
(br $lsso))))
|
|
416
|
+
(else
|
|
417
|
+
(block $dh (loop $lh
|
|
418
|
+
(br_if $dh (i32.ge_s (local.get $i) (local.get $len)))
|
|
419
|
+
(local.set $c (i32.load8_u (i32.add (local.get $srcOff) (local.get $i))))
|
|
420
|
+
(if (i32.and (i32.ge_u (local.get $c) (local.get $lo)) (i32.le_u (local.get $c) (local.get $hi)))
|
|
421
|
+
(then (local.set $c (i32.add (local.get $c) (local.get $delta)))))
|
|
422
|
+
(i32.store8 (i32.add (local.get $off) (local.get $i)) (local.get $c))
|
|
423
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
424
|
+
(br $lh)))))
|
|
425
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`
|
|
426
|
+
|
|
427
|
+
ctx.core.stdlib['__str_trim'] = `(func $__str_trim (param $ptr f64) (result f64)
|
|
428
|
+
(local $len i32) (local $start i32) (local $end i32)
|
|
429
|
+
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
430
|
+
(local.set $start (i32.const 0))
|
|
431
|
+
(local.set $end (local.get $len))
|
|
432
|
+
(block $d1 (loop $l1
|
|
433
|
+
(br_if $d1 (i32.ge_s (local.get $start) (local.get $end)))
|
|
434
|
+
(br_if $d1 (i32.gt_u (call $__char_at (local.get $ptr) (local.get $start)) (i32.const 32)))
|
|
435
|
+
(local.set $start (i32.add (local.get $start) (i32.const 1)))
|
|
436
|
+
(br $l1)))
|
|
437
|
+
(block $d2 (loop $l2
|
|
438
|
+
(br_if $d2 (i32.le_s (local.get $end) (local.get $start)))
|
|
439
|
+
(br_if $d2 (i32.gt_u (call $__char_at (local.get $ptr) (i32.sub (local.get $end) (i32.const 1))) (i32.const 32)))
|
|
440
|
+
(local.set $end (i32.sub (local.get $end) (i32.const 1)))
|
|
441
|
+
(br $l2)))
|
|
442
|
+
(call $__str_slice (local.get $ptr) (local.get $start) (local.get $end)))`
|
|
443
|
+
|
|
444
|
+
ctx.core.stdlib['__str_trimStart'] = `(func $__str_trimStart (param $ptr f64) (result f64)
|
|
445
|
+
(local $len i32) (local $start i32)
|
|
446
|
+
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
447
|
+
(local.set $start (i32.const 0))
|
|
448
|
+
(block $done (loop $loop
|
|
449
|
+
(br_if $done (i32.ge_s (local.get $start) (local.get $len)))
|
|
450
|
+
(br_if $done (i32.gt_u (call $__char_at (local.get $ptr) (local.get $start)) (i32.const 32)))
|
|
451
|
+
(local.set $start (i32.add (local.get $start) (i32.const 1)))
|
|
452
|
+
(br $loop)))
|
|
453
|
+
(call $__str_slice (local.get $ptr) (local.get $start) (local.get $len)))`
|
|
454
|
+
|
|
455
|
+
ctx.core.stdlib['__str_trimEnd'] = `(func $__str_trimEnd (param $ptr f64) (result f64)
|
|
456
|
+
(local $len i32) (local $end i32)
|
|
457
|
+
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
458
|
+
(local.set $end (local.get $len))
|
|
459
|
+
(block $done (loop $loop
|
|
460
|
+
(br_if $done (i32.le_s (local.get $end) (i32.const 0)))
|
|
461
|
+
(br_if $done (i32.gt_u (call $__char_at (local.get $ptr) (i32.sub (local.get $end) (i32.const 1))) (i32.const 32)))
|
|
462
|
+
(local.set $end (i32.sub (local.get $end) (i32.const 1)))
|
|
463
|
+
(br $loop)))
|
|
464
|
+
(call $__str_slice (local.get $ptr) (i32.const 0) (local.get $end)))`
|
|
465
|
+
|
|
466
|
+
// Materialize source bytes once via __str_copy (handles SSO/heap), then memory.copy
|
|
467
|
+
// each subsequent repetition (single bulk op vs len byte stores per copy).
|
|
468
|
+
ctx.core.stdlib['__str_repeat'] = `(func $__str_repeat (param $ptr f64) (param $n i32) (result f64)
|
|
469
|
+
(local $len i32) (local $total i32) (local $off i32) (local $i i32)
|
|
470
|
+
(local.set $len (call $__str_byteLen (local.get $ptr)))
|
|
471
|
+
(if (i32.or (i32.eqz (local.get $n)) (i32.eqz (local.get $len)))
|
|
472
|
+
(then (return (call $__mkptr (i32.const ${PTR.SSO}) (i32.const 0) (i32.const 0)))))
|
|
473
|
+
(local.set $total (i32.mul (local.get $len) (local.get $n)))
|
|
474
|
+
(local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $total))))
|
|
475
|
+
(i32.store (local.get $off) (local.get $total))
|
|
476
|
+
(local.set $off (i32.add (local.get $off) (i32.const 4)))
|
|
477
|
+
(call $__str_copy (local.get $ptr) (local.get $off) (local.get $len))
|
|
478
|
+
(local.set $i (i32.const 1))
|
|
479
|
+
(block $done (loop $loop
|
|
480
|
+
(br_if $done (i32.ge_s (local.get $i) (local.get $n)))
|
|
481
|
+
(memory.copy
|
|
482
|
+
(i32.add (local.get $off) (i32.mul (local.get $i) (local.get $len)))
|
|
483
|
+
(local.get $off)
|
|
484
|
+
(local.get $len))
|
|
485
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
486
|
+
(br $loop)))
|
|
487
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`
|
|
488
|
+
|
|
489
|
+
// Coerce value to string: numbers → __ftoa, plain NaN → "NaN", arrays → join(","), other pointers pass through
|
|
490
|
+
ctx.core.stdlib['__to_str'] = `(func $__to_str (param $val f64) (result f64)
|
|
491
|
+
(local $type i32)
|
|
492
|
+
;; Not NaN → number, convert
|
|
493
|
+
(if (f64.eq (local.get $val) (local.get $val))
|
|
494
|
+
(then (return (call $__ftoa (local.get $val) (i32.const 0) (i32.const 0)))))
|
|
495
|
+
(local.set $type (call $__ptr_type (local.get $val)))
|
|
496
|
+
;; Plain NaN (type=0) → "NaN" string
|
|
497
|
+
(if (i32.eqz (local.get $type))
|
|
498
|
+
(then (return (call $__static_str (i32.const 0)))))
|
|
499
|
+
;; Array (type=1) → join(",") like JS Array.toString()
|
|
500
|
+
(if (i32.eq (local.get $type) (i32.const ${PTR.ARRAY}))
|
|
501
|
+
(then (return (call $__str_join (local.get $val)
|
|
502
|
+
(call $__mkptr (i32.const ${PTR.SSO}) (i32.const 1) (i32.const 44))))))
|
|
503
|
+
(local.get $val))`
|
|
504
|
+
|
|
505
|
+
// Copy bytes of a string (SSO or heap) into memory at dst. Uses memory.copy for
|
|
506
|
+
// heap strings (single native op); unpacks SSO aux-packed bytes inline.
|
|
507
|
+
ctx.core.stdlib['__str_copy'] = `(func $__str_copy (param $src f64) (param $dst i32) (param $len i32)
|
|
508
|
+
(local $bits i64) (local $w i32)
|
|
509
|
+
(local.set $bits (i64.reinterpret_f64 (local.get $src)))
|
|
510
|
+
(if (i32.eq
|
|
511
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $bits) (i64.const 47)) (i64.const 0xF)))
|
|
512
|
+
(i32.const ${PTR.SSO}))
|
|
513
|
+
(then
|
|
514
|
+
;; SSO: up to 4 chars packed in low 32 bits (LE byte order). Unroll: write 1/2/3/4 bytes
|
|
515
|
+
;; depending on len. (len > 4 is rare/disallowed in practice — fallback handles up to 4.)
|
|
516
|
+
(local.set $w (i32.wrap_i64 (local.get $bits)))
|
|
517
|
+
(if (i32.ge_u (local.get $len) (i32.const 4))
|
|
518
|
+
(then (i32.store (local.get $dst) (local.get $w)))
|
|
519
|
+
(else
|
|
520
|
+
(if (i32.eq (local.get $len) (i32.const 0)) (then (return)))
|
|
521
|
+
(i32.store8 (local.get $dst) (local.get $w))
|
|
522
|
+
(if (i32.eq (local.get $len) (i32.const 1)) (then (return)))
|
|
523
|
+
(i32.store8 offset=1 (local.get $dst) (i32.shr_u (local.get $w) (i32.const 8)))
|
|
524
|
+
(if (i32.eq (local.get $len) (i32.const 2)) (then (return)))
|
|
525
|
+
(i32.store8 offset=2 (local.get $dst) (i32.shr_u (local.get $w) (i32.const 16))))))
|
|
526
|
+
(else
|
|
527
|
+
;; Heap STRING: memory.copy directly from string data
|
|
528
|
+
(memory.copy (local.get $dst)
|
|
529
|
+
(i32.wrap_i64 (i64.and (local.get $bits) (i64.const 0xFFFFFFFF)))
|
|
530
|
+
(local.get $len)))))`
|
|
531
|
+
|
|
532
|
+
ctx.core.stdlib['__str_concat'] = `(func $__str_concat (param $a f64) (param $b f64) (result f64)
|
|
533
|
+
(local $alen i32) (local $blen i32) (local $total i32) (local $off i32)
|
|
534
|
+
;; Coerce operands to strings if needed
|
|
535
|
+
(local.set $a (call $__to_str (local.get $a)))
|
|
536
|
+
(local.set $b (call $__to_str (local.get $b)))
|
|
537
|
+
(local.set $alen (call $__str_byteLen (local.get $a)))
|
|
538
|
+
(local.set $blen (call $__str_byteLen (local.get $b)))
|
|
539
|
+
(local.set $total (i32.add (local.get $alen) (local.get $blen)))
|
|
540
|
+
(if (i32.eqz (local.get $total))
|
|
541
|
+
(then (return (call $__mkptr (i32.const ${PTR.SSO}) (i32.const 0) (i32.const 0)))))
|
|
542
|
+
(local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $total))))
|
|
543
|
+
(i32.store (local.get $off) (local.get $total))
|
|
544
|
+
(local.set $off (i32.add (local.get $off) (i32.const 4)))
|
|
545
|
+
(call $__str_copy (local.get $a) (local.get $off) (local.get $alen))
|
|
546
|
+
(call $__str_copy (local.get $b) (i32.add (local.get $off) (local.get $alen)) (local.get $blen))
|
|
547
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`
|
|
548
|
+
|
|
549
|
+
ctx.core.stdlib['__str_concat_raw'] = `(func $__str_concat_raw (param $a f64) (param $b f64) (result f64)
|
|
550
|
+
(local $alen i32) (local $blen i32) (local $total i32) (local $off i32)
|
|
551
|
+
(local.set $alen (call $__str_byteLen (local.get $a)))
|
|
552
|
+
(local.set $blen (call $__str_byteLen (local.get $b)))
|
|
553
|
+
(local.set $total (i32.add (local.get $alen) (local.get $blen)))
|
|
554
|
+
(if (i32.eqz (local.get $total))
|
|
555
|
+
(then (return (call $__mkptr (i32.const ${PTR.SSO}) (i32.const 0) (i32.const 0)))))
|
|
556
|
+
(local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $total))))
|
|
557
|
+
(i32.store (local.get $off) (local.get $total))
|
|
558
|
+
(local.set $off (i32.add (local.get $off) (i32.const 4)))
|
|
559
|
+
(call $__str_copy (local.get $a) (local.get $off) (local.get $alen))
|
|
560
|
+
(call $__str_copy (local.get $b) (i32.add (local.get $off) (local.get $alen)) (local.get $blen))
|
|
561
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`
|
|
562
|
+
|
|
563
|
+
ctx.core.stdlib['__str_replace'] = `(func $__str_replace (param $str f64) (param $search f64) (param $repl f64) (result f64)
|
|
564
|
+
(local $idx i32) (local $slen i32)
|
|
565
|
+
(local.set $idx (call $__str_indexof (local.get $str) (local.get $search) (i32.const 0)))
|
|
566
|
+
(if (result f64) (i32.lt_s (local.get $idx) (i32.const 0))
|
|
567
|
+
(then (local.get $str))
|
|
568
|
+
(else
|
|
569
|
+
(local.set $slen (call $__str_byteLen (local.get $search)))
|
|
570
|
+
(call $__str_concat
|
|
571
|
+
(call $__str_concat
|
|
572
|
+
(call $__str_slice (local.get $str) (i32.const 0) (local.get $idx))
|
|
573
|
+
(local.get $repl))
|
|
574
|
+
(call $__str_slice (local.get $str) (i32.add (local.get $idx) (local.get $slen))
|
|
575
|
+
(call $__str_byteLen (local.get $str)))))))`
|
|
576
|
+
|
|
577
|
+
ctx.core.stdlib['__str_replaceall'] = `(func $__str_replaceall (param $str f64) (param $search f64) (param $repl f64) (result f64)
|
|
578
|
+
(local $idx i32) (local $slen i32) (local $pos i32) (local $result f64)
|
|
579
|
+
(local.set $slen (call $__str_byteLen (local.get $search)))
|
|
580
|
+
(local.set $result (local.get $str))
|
|
581
|
+
(local.set $pos (i32.const 0))
|
|
582
|
+
(block $done (loop $next
|
|
583
|
+
(local.set $idx (call $__str_indexof (local.get $result) (local.get $search) (local.get $pos)))
|
|
584
|
+
(br_if $done (i32.lt_s (local.get $idx) (i32.const 0)))
|
|
585
|
+
(local.set $result (call $__str_concat
|
|
586
|
+
(call $__str_concat
|
|
587
|
+
(call $__str_slice (local.get $result) (i32.const 0) (local.get $idx))
|
|
588
|
+
(local.get $repl))
|
|
589
|
+
(call $__str_slice (local.get $result) (i32.add (local.get $idx) (local.get $slen))
|
|
590
|
+
(call $__str_byteLen (local.get $result)))))
|
|
591
|
+
(local.set $pos (i32.add (local.get $idx) (call $__str_byteLen (local.get $repl))))
|
|
592
|
+
(br $next)))
|
|
593
|
+
(local.get $result))`
|
|
594
|
+
|
|
595
|
+
ctx.core.stdlib['__str_split'] = `(func $__str_split (param $str f64) (param $sep f64) (result f64)
|
|
596
|
+
(local $slen i32) (local $plen i32) (local $count i32)
|
|
597
|
+
(local $i i32) (local $j i32) (local $match i32)
|
|
598
|
+
(local $arr i32) (local $piece_start i32) (local $piece_idx i32)
|
|
599
|
+
(local.set $slen (call $__str_byteLen (local.get $str)))
|
|
600
|
+
(local.set $plen (call $__str_byteLen (local.get $sep)))
|
|
601
|
+
(local.set $count (i32.const 1))
|
|
602
|
+
(local.set $i (i32.const 0))
|
|
603
|
+
(block $d1 (loop $l1
|
|
604
|
+
(br_if $d1 (i32.gt_s (local.get $i) (i32.sub (local.get $slen) (local.get $plen))))
|
|
605
|
+
(local.set $match (i32.const 1))
|
|
606
|
+
(local.set $j (i32.const 0))
|
|
607
|
+
(block $n1 (loop $c1
|
|
608
|
+
(br_if $n1 (i32.ge_s (local.get $j) (local.get $plen)))
|
|
609
|
+
(if (i32.ne (call $__char_at (local.get $str) (i32.add (local.get $i) (local.get $j)))
|
|
610
|
+
(call $__char_at (local.get $sep) (local.get $j)))
|
|
611
|
+
(then (local.set $match (i32.const 0)) (br $n1)))
|
|
612
|
+
(local.set $j (i32.add (local.get $j) (i32.const 1)))
|
|
613
|
+
(br $c1)))
|
|
614
|
+
(if (local.get $match) (then
|
|
615
|
+
(local.set $count (i32.add (local.get $count) (i32.const 1)))
|
|
616
|
+
(local.set $i (i32.add (local.get $i) (local.get $plen)))
|
|
617
|
+
(br $l1)))
|
|
618
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
619
|
+
(br $l1)))
|
|
620
|
+
(local.set $arr (call $__alloc (i32.add (i32.const 8) (i32.shl (local.get $count) (i32.const 3)))))
|
|
621
|
+
(i32.store (local.get $arr) (local.get $count))
|
|
622
|
+
(i32.store (i32.add (local.get $arr) (i32.const 4)) (local.get $count))
|
|
623
|
+
(local.set $arr (i32.add (local.get $arr) (i32.const 8)))
|
|
624
|
+
(local.set $piece_start (i32.const 0))
|
|
625
|
+
(local.set $piece_idx (i32.const 0))
|
|
626
|
+
(local.set $i (i32.const 0))
|
|
627
|
+
(block $d2 (loop $l2
|
|
628
|
+
(br_if $d2 (i32.gt_s (local.get $i) (i32.sub (local.get $slen) (local.get $plen))))
|
|
629
|
+
(local.set $match (i32.const 1))
|
|
630
|
+
(local.set $j (i32.const 0))
|
|
631
|
+
(block $n2 (loop $c2
|
|
632
|
+
(br_if $n2 (i32.ge_s (local.get $j) (local.get $plen)))
|
|
633
|
+
(if (i32.ne (call $__char_at (local.get $str) (i32.add (local.get $i) (local.get $j)))
|
|
634
|
+
(call $__char_at (local.get $sep) (local.get $j)))
|
|
635
|
+
(then (local.set $match (i32.const 0)) (br $n2)))
|
|
636
|
+
(local.set $j (i32.add (local.get $j) (i32.const 1)))
|
|
637
|
+
(br $c2)))
|
|
638
|
+
(if (local.get $match) (then
|
|
639
|
+
(f64.store (i32.add (local.get $arr) (i32.shl (local.get $piece_idx) (i32.const 3)))
|
|
640
|
+
(call $__str_slice (local.get $str) (local.get $piece_start) (local.get $i)))
|
|
641
|
+
(local.set $piece_idx (i32.add (local.get $piece_idx) (i32.const 1)))
|
|
642
|
+
(local.set $i (i32.add (local.get $i) (local.get $plen)))
|
|
643
|
+
(local.set $piece_start (local.get $i))
|
|
644
|
+
(br $l2)))
|
|
645
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
646
|
+
(br $l2)))
|
|
647
|
+
(f64.store (i32.add (local.get $arr) (i32.shl (local.get $piece_idx) (i32.const 3)))
|
|
648
|
+
(call $__str_slice (local.get $str) (local.get $piece_start) (local.get $slen)))
|
|
649
|
+
(call $__mkptr (i32.const 1) (i32.const 0) (local.get $arr)))`
|
|
650
|
+
|
|
651
|
+
ctx.core.stdlib['__str_join'] = `(func $__str_join (param $arr f64) (param $sep f64) (result f64)
|
|
652
|
+
(local $off i32) (local $len i32) (local $i i32) (local $result f64)
|
|
653
|
+
(local.set $off (call $__ptr_offset (local.get $arr)))
|
|
654
|
+
(local.set $len (call $__len (local.get $arr)))
|
|
655
|
+
(if (i32.eqz (local.get $len))
|
|
656
|
+
(then (return (call $__mkptr (i32.const ${PTR.SSO}) (i32.const 0) (i32.const 0)))))
|
|
657
|
+
(local.set $result (f64.load (local.get $off)))
|
|
658
|
+
(local.set $i (i32.const 1))
|
|
659
|
+
(block $done (loop $loop
|
|
660
|
+
(br_if $done (i32.ge_s (local.get $i) (local.get $len)))
|
|
661
|
+
(local.set $result (call $__str_concat (local.get $result) (local.get $sep)))
|
|
662
|
+
(local.set $result (call $__str_concat (local.get $result)
|
|
663
|
+
(f64.load (i32.add (local.get $off) (i32.shl (local.get $i) (i32.const 3))))))
|
|
664
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
665
|
+
(br $loop)))
|
|
666
|
+
(local.get $result))`
|
|
667
|
+
|
|
668
|
+
// Source string copied via __str_copy (handles SSO/heap with memory.copy where possible).
|
|
669
|
+
// Pad fill loops a single tile of pad bytes — hoist pad dispatch out of the byte loop.
|
|
670
|
+
ctx.core.stdlib['__str_pad'] = `(func $__str_pad (param $str f64) (param $target i32) (param $pad f64) (param $before i32) (result f64)
|
|
671
|
+
(local $slen i32) (local $plen i32) (local $fill i32) (local $off i32) (local $i i32)
|
|
672
|
+
(local $str_off i32) (local $pad_off i32)
|
|
673
|
+
(local $pbits i64) (local $poff i32) (local $psso i32)
|
|
674
|
+
(local.set $slen (call $__str_byteLen (local.get $str)))
|
|
675
|
+
(if (i32.ge_s (local.get $slen) (local.get $target))
|
|
676
|
+
(then (return (local.get $str))))
|
|
677
|
+
(local.set $plen (call $__str_byteLen (local.get $pad)))
|
|
678
|
+
(local.set $fill (i32.sub (local.get $target) (local.get $slen)))
|
|
679
|
+
(local.set $off (call $__alloc (i32.add (i32.const 4) (local.get $target))))
|
|
680
|
+
(i32.store (local.get $off) (local.get $target))
|
|
681
|
+
(local.set $off (i32.add (local.get $off) (i32.const 4)))
|
|
682
|
+
(local.set $str_off (select (local.get $fill) (i32.const 0) (local.get $before)))
|
|
683
|
+
(local.set $pad_off (select (i32.const 0) (local.get $slen) (local.get $before)))
|
|
684
|
+
(call $__str_copy (local.get $str) (i32.add (local.get $off) (local.get $str_off)) (local.get $slen))
|
|
685
|
+
(local.set $pbits (i64.reinterpret_f64 (local.get $pad)))
|
|
686
|
+
(local.set $poff (i32.wrap_i64 (i64.and (local.get $pbits) (i64.const 0xFFFFFFFF))))
|
|
687
|
+
(local.set $psso (i32.eq
|
|
688
|
+
(i32.wrap_i64 (i64.and (i64.shr_u (local.get $pbits) (i64.const 47)) (i64.const 0xF)))
|
|
689
|
+
(i32.const ${PTR.SSO})))
|
|
690
|
+
(block $d2 (loop $l2
|
|
691
|
+
(br_if $d2 (i32.ge_s (local.get $i) (local.get $fill)))
|
|
692
|
+
(i32.store8 (i32.add (local.get $off) (i32.add (local.get $pad_off) (local.get $i)))
|
|
693
|
+
(if (result i32) (local.get $psso)
|
|
694
|
+
(then (i32.and
|
|
695
|
+
(i32.shr_u (local.get $poff) (i32.shl (i32.rem_u (local.get $i) (local.get $plen)) (i32.const 3)))
|
|
696
|
+
(i32.const 0xFF)))
|
|
697
|
+
(else (i32.load8_u (i32.add (local.get $poff) (i32.rem_u (local.get $i) (local.get $plen)))))))
|
|
698
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
699
|
+
(br $l2)))
|
|
700
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $off)))`
|
|
701
|
+
|
|
702
|
+
// Base helpers (__sso_char/__str_char/__char_at/__str_byteLen) are referenced
|
|
703
|
+
// from other helpers' WAT bodies and from emit sites; their `stdlibDeps`
|
|
704
|
+
// entries pull them transitively when actually used. No unconditional inc.
|
|
705
|
+
|
|
706
|
+
// === Method emitters ===
|
|
707
|
+
|
|
708
|
+
// Type-qualified (collide with array: slice, indexOf, includes)
|
|
709
|
+
ctx.core.emit['.string:slice'] = (str, start, end) => {
|
|
710
|
+
inc('__str_slice')
|
|
711
|
+
if (end != null) return typed(['call', '$__str_slice', asF64(emit(str)), asI32(emit(start)), asI32(emit(end))], 'f64')
|
|
712
|
+
const t = temp('t')
|
|
713
|
+
return typed(['block', ['result', 'f64'],
|
|
714
|
+
['local.set', `$${t}`, asF64(emit(str))],
|
|
715
|
+
['call', '$__str_slice', ['local.get', `$${t}`], asI32(emit(start)),
|
|
716
|
+
['call', '$__str_byteLen', ['local.get', `$${t}`]]]], 'f64')
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
ctx.core.emit['.string:indexOf'] = (str, search, from) => {
|
|
720
|
+
inc('__str_indexof')
|
|
721
|
+
return typed(['f64.convert_i32_s', ['call', '$__str_indexof', asF64(emit(str)), asF64(emit(search)), from ? asI32(emit(from)) : ['i32.const', 0]]], 'f64')
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
ctx.core.emit['.string:includes'] = (str, search) => {
|
|
725
|
+
inc('__str_indexof')
|
|
726
|
+
return typed(['f64.convert_i32_s',
|
|
727
|
+
['i32.ge_s', ['call', '$__str_indexof', asF64(emit(str)), asF64(emit(search)), ['i32.const', 0]], ['i32.const', 0]]], 'f64')
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// Generic (no collision)
|
|
731
|
+
ctx.core.emit['.substring'] = (str, start, end) => {
|
|
732
|
+
inc('__str_substring')
|
|
733
|
+
if (end != null) return typed(['call', '$__str_substring', asF64(emit(str)), asI32(emit(start)), asI32(emit(end))], 'f64')
|
|
734
|
+
const t = temp('t')
|
|
735
|
+
return typed(['block', ['result', 'f64'],
|
|
736
|
+
['local.set', `$${t}`, asF64(emit(str))],
|
|
737
|
+
['call', '$__str_substring', ['local.get', `$${t}`], asI32(emit(start)),
|
|
738
|
+
['call', '$__str_byteLen', ['local.get', `$${t}`]]]], 'f64')
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Factory for simple str→call patterns: [emitKey, stdlibName, argCoercions, i32Result?]
|
|
742
|
+
const coerce = { f: asF64, i: asI32 }
|
|
743
|
+
const strMethod = (name, args, i32Result) => (str, ...params) => {
|
|
744
|
+
inc(name)
|
|
745
|
+
const call = ['call', `$${name}`, asF64(emit(str)), ...params.map((p, i) => coerce[args[i]](emit(p)))]
|
|
746
|
+
return typed(i32Result ? ['f64.convert_i32_s', call] : call, 'f64')
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Simple str methods: [emitKey, stdlibName, argCoercions, i32Result?]
|
|
750
|
+
ctx.core.emit['.startsWith'] = strMethod('__str_startswith', ['f'], true)
|
|
751
|
+
ctx.core.emit['.endsWith'] = strMethod('__str_endswith', ['f'], true)
|
|
752
|
+
ctx.core.emit['.trim'] = strMethod('__str_trim', [])
|
|
753
|
+
ctx.core.emit['.trimStart'] = strMethod('__str_trimStart', [])
|
|
754
|
+
ctx.core.emit['.trimEnd'] = strMethod('__str_trimEnd', [])
|
|
755
|
+
ctx.core.emit['.repeat'] = strMethod('__str_repeat', ['i'])
|
|
756
|
+
ctx.core.emit['.split'] = strMethod('__str_split', ['f'])
|
|
757
|
+
ctx.core.emit['.replace'] = strMethod('__str_replace', ['f', 'f'])
|
|
758
|
+
ctx.core.emit['.replaceAll'] = strMethod('__str_replaceall', ['f', 'f'])
|
|
759
|
+
|
|
760
|
+
ctx.core.emit['.toUpperCase'] = (str) => {
|
|
761
|
+
inc('__str_case')
|
|
762
|
+
return typed(['call', '$__str_case', asF64(emit(str)), ['i32.const', 97], ['i32.const', 122], ['i32.const', -32]], 'f64')
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
ctx.core.emit['.toLowerCase'] = (str) => {
|
|
766
|
+
inc('__str_case')
|
|
767
|
+
return typed(['call', '$__str_case', asF64(emit(str)), ['i32.const', 65], ['i32.const', 90], ['i32.const', 32]], 'f64')
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
ctx.core.emit['.string:concat'] = (str, ...others) => {
|
|
771
|
+
inc('__str_concat')
|
|
772
|
+
let result = asF64(emit(str))
|
|
773
|
+
for (const other of others) result = typed(['call', '$__str_concat', result, asF64(emit(other))], 'f64')
|
|
774
|
+
return result
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
ctx.core.emit['.padStart'] = (str, len, pad) => {
|
|
778
|
+
inc('__str_pad')
|
|
779
|
+
const vpad = pad != null ? asF64(emit(pad)) : mkPtrIR(PTR.SSO, 1, 32)
|
|
780
|
+
return typed(['call', '$__str_pad', asF64(emit(str)), asI32(emit(len)), vpad, ['i32.const', 1]], 'f64')
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
ctx.core.emit['.padEnd'] = (str, len, pad) => {
|
|
784
|
+
inc('__str_pad')
|
|
785
|
+
const vpad = pad != null ? asF64(emit(pad)) : mkPtrIR(PTR.SSO, 1, 32)
|
|
786
|
+
return typed(['call', '$__str_pad', asF64(emit(str)), asI32(emit(len)), vpad, ['i32.const', 0]], 'f64')
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// .charAt(i) → 1-char string from char code at index i
|
|
790
|
+
ctx.core.emit['.charAt'] = (str, idx) => {
|
|
791
|
+
inc('__char_at')
|
|
792
|
+
const t = tempI32('ch')
|
|
793
|
+
// Get char code, create SSO string with 1 byte
|
|
794
|
+
return typed(['block', ['result', 'f64'],
|
|
795
|
+
['local.set', `$${t}`, ['call', '$__char_at', asF64(emit(str)), asI32(emit(idx))]],
|
|
796
|
+
mkPtrIR(PTR.SSO, 1, ['local.get', `$${t}`])], 'f64')
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// .charCodeAt(i) → integer char code (0..255 for ASCII bytes — unsigned, always
|
|
800
|
+
// representable as i32). Returning i32 directly lets `let c = s.charCodeAt(i)`
|
|
801
|
+
// stay on the i32 ABI: chained comparisons (`c >= 48 && c <= 57`), bit-ops, and
|
|
802
|
+
// `c - 48` arithmetic skip the per-iteration f64 widen + i32 trunc round-trip.
|
|
803
|
+
ctx.core.emit['.charCodeAt'] = (str, idx) => {
|
|
804
|
+
inc('__char_at')
|
|
805
|
+
return typed(['call', '$__char_at', asF64(emit(str)), asI32(emit(idx))], 'i32')
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// String.fromCharCode(code) → 1-char SSO string
|
|
809
|
+
ctx.core.emit['String.fromCharCode'] = (code) => mkPtrIR(PTR.SSO, 1, asI32(emit(code)))
|
|
810
|
+
|
|
811
|
+
// String.fromCodePoint(cp) → UTF-8 encoded string
|
|
812
|
+
ctx.core.stdlib['__fromCodePoint'] = `(func $__fromCodePoint (param $cp i32) (result f64)
|
|
813
|
+
(local $off i32) (local $len i32)
|
|
814
|
+
;; ASCII: 1 byte SSO
|
|
815
|
+
(if (i32.lt_u (local.get $cp) (i32.const 128))
|
|
816
|
+
(then (return (call $__mkptr (i32.const ${PTR.SSO}) (i32.const 1) (local.get $cp)))))
|
|
817
|
+
;; 2-byte: 0x80-0x7FF → SSO
|
|
818
|
+
(if (i32.lt_u (local.get $cp) (i32.const 0x800))
|
|
819
|
+
(then (return (call $__mkptr (i32.const ${PTR.SSO}) (i32.const 2)
|
|
820
|
+
(i32.or
|
|
821
|
+
(i32.or (i32.const 0xC0) (i32.shr_u (local.get $cp) (i32.const 6)))
|
|
822
|
+
(i32.shl (i32.or (i32.const 0x80) (i32.and (local.get $cp) (i32.const 0x3F))) (i32.const 8)))))))
|
|
823
|
+
;; 3-byte: 0x800-0xFFFF → SSO (3 bytes fits)
|
|
824
|
+
(if (i32.lt_u (local.get $cp) (i32.const 0x10000))
|
|
825
|
+
(then (return (call $__mkptr (i32.const ${PTR.SSO}) (i32.const 3)
|
|
826
|
+
(i32.or (i32.or
|
|
827
|
+
(i32.or (i32.const 0xE0) (i32.shr_u (local.get $cp) (i32.const 12)))
|
|
828
|
+
(i32.shl (i32.or (i32.const 0x80) (i32.and (i32.shr_u (local.get $cp) (i32.const 6)) (i32.const 0x3F))) (i32.const 8)))
|
|
829
|
+
(i32.shl (i32.or (i32.const 0x80) (i32.and (local.get $cp) (i32.const 0x3F))) (i32.const 16)))))))
|
|
830
|
+
;; 4-byte: 0x10000-0x10FFFF → SSO (4 bytes fits)
|
|
831
|
+
(return (call $__mkptr (i32.const ${PTR.SSO}) (i32.const 4)
|
|
832
|
+
(i32.or (i32.or (i32.or
|
|
833
|
+
(i32.or (i32.const 0xF0) (i32.shr_u (local.get $cp) (i32.const 18)))
|
|
834
|
+
(i32.shl (i32.or (i32.const 0x80) (i32.and (i32.shr_u (local.get $cp) (i32.const 12)) (i32.const 0x3F))) (i32.const 8)))
|
|
835
|
+
(i32.shl (i32.or (i32.const 0x80) (i32.and (i32.shr_u (local.get $cp) (i32.const 6)) (i32.const 0x3F))) (i32.const 16)))
|
|
836
|
+
(i32.shl (i32.or (i32.const 0x80) (i32.and (local.get $cp) (i32.const 0x3F))) (i32.const 24))))))`
|
|
837
|
+
|
|
838
|
+
ctx.core.emit['String.fromCodePoint'] = (code) => {
|
|
839
|
+
inc('__fromCodePoint')
|
|
840
|
+
return typed(['call', '$__fromCodePoint', asI32(emit(code))], 'f64')
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// .at(i) → charAt with negative index support
|
|
844
|
+
ctx.core.emit['.at'] = (str, idx) => {
|
|
845
|
+
inc('__char_at', '__str_byteLen')
|
|
846
|
+
const t = tempI32('at'), s = temp('as')
|
|
847
|
+
return typed(['block', ['result', 'f64'],
|
|
848
|
+
['local.set', `$${s}`, asF64(emit(str))],
|
|
849
|
+
['local.set', `$${t}`, asI32(emit(idx))],
|
|
850
|
+
// Negative index: t += length
|
|
851
|
+
['if', ['i32.lt_s', ['local.get', `$${t}`], ['i32.const', 0]],
|
|
852
|
+
['then', ['local.set', `$${t}`, ['i32.add', ['local.get', `$${t}`],
|
|
853
|
+
['call', '$__str_byteLen', ['local.get', `$${s}`]]]]]],
|
|
854
|
+
mkPtrIR(PTR.SSO, 1, ['call', '$__char_at', ['local.get', `$${s}`], ['local.get', `$${t}`]])], 'f64')
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// .search(str) → indexOf (same as indexOf for string args)
|
|
858
|
+
ctx.core.emit['.search'] = (str, search) => {
|
|
859
|
+
inc('__str_indexof')
|
|
860
|
+
return typed(['f64.convert_i32_s', ['call', '$__str_indexof', asF64(emit(str)), asF64(emit(search)), ['i32.const', 0]]], 'f64')
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// .match(str) → [match] array if found, or 0 (null) if not
|
|
864
|
+
// For string args, returns single-element array with the matched substring
|
|
865
|
+
ctx.core.emit['.match'] = (str, search) => {
|
|
866
|
+
inc('__str_indexof', '__str_slice', '__wrap1')
|
|
867
|
+
const s = temp('ms'), q = temp('mq'), idx = tempI32('mi')
|
|
868
|
+
// indexOf, then if >= 0, create 1-element array with the match slice
|
|
869
|
+
return typed(['block', ['result', 'f64'],
|
|
870
|
+
['local.set', `$${s}`, asF64(emit(str))],
|
|
871
|
+
['local.set', `$${q}`, asF64(emit(search))],
|
|
872
|
+
['local.set', `$${idx}`, ['call', '$__str_indexof', ['local.get', `$${s}`], ['local.get', `$${q}`], ['i32.const', 0]]],
|
|
873
|
+
['if', ['result', 'f64'], ['i32.lt_s', ['local.get', `$${idx}`], ['i32.const', 0]],
|
|
874
|
+
['then', ['f64.const', 0]], // null
|
|
875
|
+
['else',
|
|
876
|
+
// Build 1-element array containing the search string
|
|
877
|
+
['call', '$__wrap1',
|
|
878
|
+
['call', '$__str_slice', ['local.get', `$${s}`],
|
|
879
|
+
['local.get', `$${idx}`],
|
|
880
|
+
['i32.add', ['local.get', `$${idx}`], ['call', '$__str_byteLen', ['local.get', `$${q}`]]]]]]]], 'f64')
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// __wrap1(val: f64) → f64 — create 1-element array [val]
|
|
884
|
+
ctx.core.stdlib['__wrap1'] = `(func $__wrap1 (param $val f64) (result f64)
|
|
885
|
+
(local $ptr i32)
|
|
886
|
+
(local.set $ptr (call $__alloc (i32.const 16)))
|
|
887
|
+
(i32.store (local.get $ptr) (i32.const 1))
|
|
888
|
+
(i32.store (i32.add (local.get $ptr) (i32.const 4)) (i32.const 1))
|
|
889
|
+
(f64.store (i32.add (local.get $ptr) (i32.const 8)) (local.get $val))
|
|
890
|
+
(call $__mkptr (i32.const 1) (i32.const 0) (i32.add (local.get $ptr) (i32.const 8))))`
|
|
891
|
+
|
|
892
|
+
// TextEncoder() / TextDecoder() → dummy values (methods do the work)
|
|
893
|
+
ctx.core.emit['TextEncoder'] = () => typed(['f64.const', 1], 'f64')
|
|
894
|
+
ctx.core.emit['TextDecoder'] = () => typed(['f64.const', 2], 'f64')
|
|
895
|
+
|
|
896
|
+
// .encode(str) → Uint8Array of string's UTF-8 bytes
|
|
897
|
+
// Copies bytes from string (SSO or heap) into a new Uint8Array
|
|
898
|
+
ctx.core.stdlib['__str_encode'] = `(func $__str_encode (param $str f64) (result f64)
|
|
899
|
+
(local $len i32) (local $dst i32)
|
|
900
|
+
(local.set $len (call $__str_byteLen (local.get $str)))
|
|
901
|
+
(local.set $dst (call $__alloc (i32.add (i32.const 8) (local.get $len))))
|
|
902
|
+
(i32.store (local.get $dst) (local.get $len))
|
|
903
|
+
(i32.store (i32.add (local.get $dst) (i32.const 4)) (local.get $len))
|
|
904
|
+
(local.set $dst (i32.add (local.get $dst) (i32.const 8)))
|
|
905
|
+
(call $__str_copy (local.get $str) (local.get $dst) (local.get $len))
|
|
906
|
+
(call $__mkptr (i32.const 3) (i32.const 1) (local.get $dst)))`
|
|
907
|
+
|
|
908
|
+
ctx.core.emit['.encode'] = (obj, str) => {
|
|
909
|
+
inc('__str_encode')
|
|
910
|
+
return typed(['call', '$__str_encode', asF64(emit(str))], 'f64')
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// .decode(uint8arr) → string from byte data
|
|
914
|
+
ctx.core.stdlib['__bytes_decode'] = `(func $__bytes_decode (param $arr f64) (result f64)
|
|
915
|
+
(local $off i32) (local $len i32) (local $dst i32)
|
|
916
|
+
(local.set $off (call $__ptr_offset (local.get $arr)))
|
|
917
|
+
(local.set $len (call $__len (local.get $arr)))
|
|
918
|
+
(local.set $dst (call $__alloc (i32.add (i32.const 4) (local.get $len))))
|
|
919
|
+
(i32.store (local.get $dst) (local.get $len))
|
|
920
|
+
(local.set $dst (i32.add (local.get $dst) (i32.const 4)))
|
|
921
|
+
(memory.copy (local.get $dst) (local.get $off) (local.get $len))
|
|
922
|
+
(call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $dst)))`
|
|
923
|
+
|
|
924
|
+
ctx.core.emit['.decode'] = (obj, arr) => {
|
|
925
|
+
inc('__bytes_decode')
|
|
926
|
+
return typed(['call', '$__bytes_decode', asF64(emit(arr))], 'f64')
|
|
927
|
+
}
|
|
928
|
+
}
|