jz 0.9.1 → 0.9.2

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/module/string.js CHANGED
@@ -186,7 +186,7 @@ export default (ctx) => {
186
186
  __str_split: ['__str_slice', '__str_byteLen', '__char_at', '__alloc'],
187
187
  __str_idx: ['__char1byte'],
188
188
  __sso_norm: [],
189
- __bytes_decode: ['__ptr_offset', '__len', '__alloc', '__mkptr', '__sso_norm'],
189
+ __bytes_decode: ['__typed_data', '__len', '__alloc', '__mkptr', '__sso_norm'],
190
190
  __str_eq: ['__str_eq_cold'],
191
191
  __str_eq_cold: ['__char_at', '__str_byteLen'],
192
192
  __str_cmp: ['__char_at', '__str_byteLen'],
@@ -198,7 +198,22 @@ export default (ctx) => {
198
198
  __str_encode: ['__str_byteLen', '__str_copy'],
199
199
  __encodeURIComponent: ['__to_str', '__str_byteLen', '__char_at', '__alloc', '__mkptr', '__sso_norm'],
200
200
  __decodeURIComponent: ['__to_str', '__str_byteLen', '__char_at', '__alloc', '__mkptr', '__uri_hex', '__sso_norm'],
201
+ __encodeURI: ['__to_str', '__str_byteLen', '__char_at', '__alloc', '__mkptr', '__sso_norm'],
202
+ __decodeURI: ['__to_str', '__str_byteLen', '__char_at', '__alloc', '__mkptr', '__uri_hex', '__sso_norm'],
201
203
  __uri_hex: [],
204
+ // base64/hex codec family (Uint8Array.fromBase64/toBase64/…, atob/btoa)
205
+ __b64_enc: ['__alloc', '__mkptr', '__sso_norm'],
206
+ __b64_dec_raw: ['__str_byteLen', '__char_at'],
207
+ __btoa: ['__to_str', '__str_byteLen', '__str_copy', '__alloc', '__b64_enc'],
208
+ __atob: ['__to_str', '__str_byteLen', '__alloc', '__b64_dec_raw', '__mkptr', '__sso_norm'],
209
+ __b64_from: ['__to_str', '__str_byteLen', '__alloc', '__b64_dec_raw', '__mkptr'],
210
+ __b64_set: ['__b64_dec_raw', '__u8_data', '__len'],
211
+ __hex_enc: ['__alloc', '__mkptr', '__sso_norm'],
212
+ __hex_dec_raw: ['__str_byteLen', '__char_at', '__uri_hex'],
213
+ __hex_from: ['__to_str', '__str_byteLen', '__alloc', '__hex_dec_raw', '__mkptr'],
214
+ __hex_set: ['__hex_dec_raw', '__u8_data', '__len'],
215
+ __u8_data: ['__ptr_type', '__ptr_aux', '__typed_data'],
216
+ __str_encode_into: ['__str_byteLen', '__char_at', '__str_copy', '__len', '__typed_data', '__ptr_type', '__ptr_aux'],
202
217
  __to_str: ['__ftoa', '__static_str', '__str_join', '__mkptr'],
203
218
  __str_byteLen: ['__ptr_type', '__ptr_aux', '__str_len'],
204
219
  })
@@ -2147,19 +2162,18 @@ export default (ctx) => {
2147
2162
  return r
2148
2163
  })
2149
2164
 
2150
- wat('__encodeURIComponent', `(func $__encodeURIComponent (param $val i64) (result f64)
2151
- (local $str i64) (local $slen i32) (local $base i32) (local $out i32)
2152
- (local $i i32) (local $j i32) (local $c i32) (local $hi i32) (local $lo i32)
2153
- (local.set $str (call $__to_str (local.get $val)))
2154
- (local.set $slen (call $__str_byteLen (local.get $str)))
2155
- (if (i32.eqz (local.get $slen))
2156
- (then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
2157
- (local.set $base (call $__alloc (i32.add (i32.const 4) (i32.mul (local.get $slen) (i32.const 3)))))
2158
- (local.set $out (i32.add (local.get $base) (i32.const 4)))
2159
- (block $done (loop $loop
2160
- (br_if $done (i32.ge_u (local.get $i) (local.get $slen)))
2161
- (local.set $c (call $__char_at (local.get $str) (local.get $i)))
2162
- (if (i32.or
2165
+ // Percent-codec char classes (ECMA-262 19.2.2–19.2.5). Both encoders share one
2166
+ // loop shape and differ only in the always-unescaped set: encodeURIComponent
2167
+ // passes only the unreserved marks; encodeURI additionally leaves the URI
2168
+ // reserved set `; / ? : @ & = + $ , #` intact. Same split decoding: decode-
2169
+ // URIComponent decodes every escape, decodeURI copies the original `%XX`
2170
+ // triplet through for reserved bytes (case-preserving). Generated kernels —
2171
+ // the unused variant dead-strips like any stdlib body.
2172
+ const URI_RESERVED = [59, 47, 63, 58, 64, 38, 61, 43, 36, 44, 35] // ; / ? : @ & = + $ , #
2173
+ const uriReservedTest = URI_RESERVED
2174
+ .map(c => `(i32.eq (local.get $c) (i32.const ${c}))`)
2175
+ .reduce((a, b) => `(i32.or ${a} ${b})`)
2176
+ const uriSafeTest = `(i32.or
2163
2177
  (i32.or
2164
2178
  (i32.or
2165
2179
  (i32.and (i32.ge_u (local.get $c) (i32.const 65)) (i32.le_u (local.get $c) (i32.const 90)))
@@ -2173,7 +2187,21 @@ export default (ctx) => {
2173
2187
  (i32.or (i32.eq (local.get $c) (i32.const 126)) (i32.eq (local.get $c) (i32.const 42)))
2174
2188
  (i32.or
2175
2189
  (i32.eq (local.get $c) (i32.const 39))
2176
- (i32.or (i32.eq (local.get $c) (i32.const 40)) (i32.eq (local.get $c) (i32.const 41)))))))
2190
+ (i32.or (i32.eq (local.get $c) (i32.const 40)) (i32.eq (local.get $c) (i32.const 41)))))))`
2191
+
2192
+ const uriEncodeKernel = (name, keepReserved) => `(func $${name} (param $val i64) (result f64)
2193
+ (local $str i64) (local $slen i32) (local $base i32) (local $out i32)
2194
+ (local $i i32) (local $j i32) (local $c i32) (local $hi i32) (local $lo i32)
2195
+ (local.set $str (call $__to_str (local.get $val)))
2196
+ (local.set $slen (call $__str_byteLen (local.get $str)))
2197
+ (if (i32.eqz (local.get $slen))
2198
+ (then (return (call $__mkptr (i32.const ${PTR.STRING}) (i32.const ${LAYOUT.SSO_BIT}) (i32.const 0)))))
2199
+ (local.set $base (call $__alloc (i32.add (i32.const 4) (i32.mul (local.get $slen) (i32.const 3)))))
2200
+ (local.set $out (i32.add (local.get $base) (i32.const 4)))
2201
+ (block $done (loop $loop
2202
+ (br_if $done (i32.ge_u (local.get $i) (local.get $slen)))
2203
+ (local.set $c (call $__char_at (local.get $str) (local.get $i)))
2204
+ (if ${keepReserved ? `(i32.or ${uriSafeTest} ${uriReservedTest})` : uriSafeTest}
2177
2205
  (then
2178
2206
  (i32.store8 (i32.add (local.get $out) (local.get $j)) (local.get $c))
2179
2207
  (local.set $j (i32.add (local.get $j) (i32.const 1))))
@@ -2189,13 +2217,18 @@ export default (ctx) => {
2189
2217
  (local.set $i (i32.add (local.get $i) (i32.const 1)))
2190
2218
  (br $loop)))
2191
2219
  (i32.store (local.get $base) (local.get $j))
2192
- (call $__sso_norm (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $out))))`)
2220
+ (call $__sso_norm (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $out))))`
2221
+
2222
+ wat('__encodeURIComponent', uriEncodeKernel('__encodeURIComponent', false))
2223
+ wat('__encodeURI', uriEncodeKernel('__encodeURI', true))
2193
2224
 
2194
- bind('encodeURIComponent', (value) => {
2195
- inc('__encodeURIComponent')
2225
+ const uriEncodeBind = (kernel) => (value) => {
2226
+ inc(kernel)
2196
2227
  const input = value === undefined ? ['i64.const', UNDEF_NAN] : asI64(emit(value))
2197
- return typed(['call', '$__encodeURIComponent', input], 'f64')
2198
- })
2228
+ return typed(['call', `$${kernel}`, input], 'f64')
2229
+ }
2230
+ bind('encodeURIComponent', uriEncodeBind('__encodeURIComponent'))
2231
+ bind('encodeURI', uriEncodeBind('__encodeURI'))
2199
2232
 
2200
2233
  wat('__uri_hex', `(func $__uri_hex (param $c i32) (result i32)
2201
2234
  (if (result i32) (i32.and (i32.ge_u (local.get $c) (i32.const 48)) (i32.le_u (local.get $c) (i32.const 57)))
@@ -2206,7 +2239,19 @@ export default (ctx) => {
2206
2239
  (then (i32.sub (local.get $c) (i32.const 87)))
2207
2240
  (else (i32.const -1))))))))`)
2208
2241
 
2209
- wat('__decodeURIComponent', `(func $__decodeURIComponent (param $v i64) (result f64)
2242
+ // Reserved-byte passthrough for decodeURI: at this point $i still sits on the
2243
+ // `%`, the two hex chars are validated, and $c < 128 for every reserved code —
2244
+ // copy the ORIGINAL triplet (case-preserving per spec) and skip the store.
2245
+ const uriKeepReserved = `(if ${uriReservedTest} (then
2246
+ (i32.store8 (i32.add (local.get $dst) (local.get $outLen)) (i32.const 37))
2247
+ (i32.store8 (i32.add (local.get $dst) (i32.add (local.get $outLen) (i32.const 1)))
2248
+ (call $__char_at (local.get $s) (i32.add (local.get $i) (i32.const 1))))
2249
+ (i32.store8 (i32.add (local.get $dst) (i32.add (local.get $outLen) (i32.const 2)))
2250
+ (call $__char_at (local.get $s) (i32.add (local.get $i) (i32.const 2))))
2251
+ (local.set $outLen (i32.add (local.get $outLen) (i32.const 3)))
2252
+ (local.set $stored (i32.const 1))))`
2253
+
2254
+ const uriDecodeKernel = (name, keepReserved) => `(func $${name} (param $v i64) (result f64)
2210
2255
  (local $s i64) (local $len i32) (local $i i32)
2211
2256
  (local $base i32) (local $dst i32) (local $outLen i32)
2212
2257
  (local $c i32) (local $hi i32) (local $lo i32)
@@ -2228,6 +2273,7 @@ export default (ctx) => {
2228
2273
  (if (i32.or (i32.lt_s (local.get $hi) (i32.const 0)) (i32.lt_s (local.get $lo) (i32.const 0)))
2229
2274
  (then (throw $__jz_err (f64.const 0))))
2230
2275
  (local.set $c (i32.or (i32.shl (local.get $hi) (i32.const 4)) (local.get $lo)))
2276
+ ${keepReserved ? uriKeepReserved : ''}
2231
2277
  (local.set $i (i32.add (local.get $i) (i32.const 3)))
2232
2278
  (if (i32.ge_u (local.get $c) (i32.const 128))
2233
2279
  (then
@@ -2282,14 +2328,19 @@ export default (ctx) => {
2282
2328
  (local.set $outLen (i32.add (local.get $outLen) (i32.const 1)))))
2283
2329
  (br $loop)))
2284
2330
  (i32.store (local.get $base) (local.get $outLen))
2285
- (call $__sso_norm (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $dst))))`)
2331
+ (call $__sso_norm (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $dst))))`
2332
+
2333
+ wat('__decodeURIComponent', uriDecodeKernel('__decodeURIComponent', false))
2334
+ wat('__decodeURI', uriDecodeKernel('__decodeURI', true))
2286
2335
 
2287
- bind('decodeURIComponent', (value) => {
2336
+ const uriDecodeBind = (kernel) => (value) => {
2288
2337
  ctx.runtime.throws = true
2289
- inc('__decodeURIComponent')
2290
- return typed(['call', '$__decodeURIComponent',
2338
+ inc(kernel)
2339
+ return typed(['call', `$${kernel}`,
2291
2340
  value === undefined ? ['i64.const', UNDEF_NAN] : asI64(emit(value))], 'f64')
2292
- })
2341
+ }
2342
+ bind('decodeURIComponent', uriDecodeBind('__decodeURIComponent'))
2343
+ bind('decodeURI', uriDecodeBind('__decodeURI'))
2293
2344
 
2294
2345
  // .at(i) → 1-char string at index i with negative-index support, or undefined
2295
2346
  // when out of range (JS spec: `i += length` for negative, then OOB → undefined).
@@ -2344,8 +2395,26 @@ export default (ctx) => {
2344
2395
  (call $__mkptr (i32.const 1) (i32.const 0) (i32.add (local.get $ptr) (i32.const 8))))`)
2345
2396
 
2346
2397
  // TextEncoder() / TextDecoder() → dummy values (methods do the work)
2398
+ // UTF-8 is the only encoding jz ships (encoding tables are the same
2399
+ // multi-KB cost Intl was refused for) — any other label would silently
2400
+ // decode UTF-8, so reject it at compile time.
2401
+ const decoderLabel = (label) => {
2402
+ if (label == null) return // no arg (a no-arg call pads a null slot)
2403
+ const lit = Array.isArray(label) && label[0] === 'str' ? String(label[1]).trim().toLowerCase() : null
2404
+ if (lit == null) err(`TextDecoder label must be a literal string — jz resolves encodings at compile time`)
2405
+ else if (lit !== 'utf-8' && lit !== 'utf8' && lit !== 'unicode-1-1-utf-8')
2406
+ err(`TextDecoder: only 'utf-8' is supported — other encodings need tables jz doesn't ship`)
2407
+ }
2408
+ // A chained receiver (`new TextDecoder(l).decode(x)`) is never emitted — the
2409
+ // methods ignore the dummy value — so validate the label at the receiver too.
2410
+ const decoderRecvCheck = (node) => {
2411
+ if (Array.isArray(node) && node[0] === '()' && node[1] === 'TextDecoder') decoderLabel(node[2])
2412
+ }
2347
2413
  bind('TextEncoder', () => typed(['f64.const', 1], 'f64'))
2348
- bind('TextDecoder', () => typed(['f64.const', 2], 'f64'))
2414
+ bind('TextDecoder', (label) => {
2415
+ decoderLabel(label)
2416
+ return typed(['f64.const', 2], 'f64')
2417
+ })
2349
2418
 
2350
2419
  // .encode(str) → Uint8Array of string's UTF-8 bytes
2351
2420
  // Copies bytes from string (SSO or heap) into a new Uint8Array
@@ -2372,7 +2441,9 @@ export default (ctx) => {
2372
2441
  // .decode(uint8arr) → string from byte data
2373
2442
  wat('__bytes_decode', `(func $__bytes_decode (param $arr i64) (result f64)
2374
2443
  (local $off i32) (local $len i32) (local $dst i32)
2375
- (local.set $off (call $__ptr_offset (local.get $arr)))
2444
+ ;; __typed_data, not __ptr_offset a VIEW's offset is its descriptor, and
2445
+ ;; decoding it read the descriptor bytes as text (silent garbage)
2446
+ (local.set $off (call $__typed_data (local.get $arr)))
2376
2447
  (local.set $len (call $__len (local.get $arr)))
2377
2448
  (local.set $dst (call $__alloc (i32.add (i32.const 4) (local.get $len))))
2378
2449
  (i32.store (local.get $dst) (local.get $len))
@@ -2384,7 +2455,341 @@ export default (ctx) => {
2384
2455
  (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $dst)))`)
2385
2456
 
2386
2457
  bind('.decode', (obj, arr) => {
2458
+ decoderRecvCheck(obj)
2387
2459
  inc('__bytes_decode')
2388
2460
  return typed(['call', '$__bytes_decode', asI64(emit(arr))], 'f64')
2389
2461
  })
2462
+
2463
+ // .encodeInto(str, u8) → { read, written }. jz strings are UTF-8 bytes, so
2464
+ // read counts bytes (== written), not UTF-16 units — same divergence class as
2465
+ // `"中".length`. Never splits a multi-byte sequence: a truncated copy backs
2466
+ // off past continuation bytes to the previous boundary (spec: whole code
2467
+ // points only). Result is a dictionary object (HASH) — a one-shot small
2468
+ // record, not a hot-path value.
2469
+ wat('__str_encode_into', `(func $__str_encode_into (param $s i64) (param $dst i64) (result i32)
2470
+ (local $slen i32) (local $n i32)
2471
+ ;; receiver must be a Uint8Array — anything else is a TypeError in JS
2472
+ (if (i32.or
2473
+ (i32.ne (call $__ptr_type (local.get $dst)) (i32.const ${PTR.TYPED}))
2474
+ (i32.ne (i32.and (call $__ptr_aux (local.get $dst)) (i32.const 7)) (i32.const 1)))
2475
+ (then (throw $__jz_err (f64.const 0))))
2476
+ (local.set $slen (call $__str_byteLen (local.get $s)))
2477
+ (local.set $n (call $__len (local.get $dst)))
2478
+ (if (i32.ge_s (local.get $n) (local.get $slen))
2479
+ (then (local.set $n (local.get $slen)))
2480
+ (else ;; truncated: back off to a UTF-8 boundary (exclude the split sequence)
2481
+ (block $b (loop $l
2482
+ (br_if $b (i32.eqz (local.get $n)))
2483
+ (br_if $b (i32.ne
2484
+ (i32.and (call $__char_at (local.get $s) (local.get $n)) (i32.const 0xC0))
2485
+ (i32.const 0x80)))
2486
+ (local.set $n (i32.sub (local.get $n) (i32.const 1)))
2487
+ (br $l)))))
2488
+ (call $__str_copy (local.get $s) (call $__typed_data (local.get $dst)) (local.get $n))
2489
+ (local.get $n))`)
2490
+
2491
+ bind('.encodeInto', (obj, str, dst) => {
2492
+ ctx.runtime.throws = true
2493
+ ctx.features.typedarray = true
2494
+ inc('__str_encode_into', '__hash_new', '__hash_set')
2495
+ const n = tempI32('ein'), h = temp('eih')
2496
+ const hI64 = ['i64.reinterpret_f64', ['local.get', `$${h}`]]
2497
+ const nVal = ['i64.reinterpret_f64', ['f64.convert_i32_s', ['local.get', `$${n}`]]]
2498
+ return typed(['block', ['result', 'f64'],
2499
+ ['local.set', `$${n}`, ['call', '$__str_encode_into', asI64(emit(str)), asI64(emit(dst))]],
2500
+ ['local.set', `$${h}`, ['call', '$__hash_new']],
2501
+ ['local.set', `$${h}`, ['f64.reinterpret_i64',
2502
+ ['call', '$__hash_set', hI64, asI64(emit(['str', 'read'])), nVal]]],
2503
+ ['local.set', `$${h}`, ['f64.reinterpret_i64',
2504
+ ['call', '$__hash_set', hI64, asI64(emit(['str', 'written'])), nVal]]],
2505
+ ['local.get', `$${h}`]], 'f64')
2506
+ })
2507
+
2508
+ // === base64 / hex codecs ===
2509
+ // One encode loop and one decode loop serve the whole family: the ES2026
2510
+ // Uint8Array.fromBase64/toBase64/fromHex/toHex/setFrom* methods (emitters in
2511
+ // module/typedarray.js) and the legacy atob/btoa pair. Decode follows WHATWG
2512
+ // forgiving-base64 == TC39 lastChunkHandling:'loose': ASCII whitespace
2513
+ // skipped, padding optional but validated when present, len%4==1 rejected,
2514
+ // extra trailing bits ignored. Alphabet (std/url) and padding ride as i32
2515
+ // flags — resolved compile-time from literal options, so no per-call parsing.
2516
+
2517
+ // put one 6-bit value ($v pre-masked) as a base64 char at $out+$j
2518
+ const b64put = (bits) => `(local.set $v (i32.and ${bits} (i32.const 63)))
2519
+ (i32.store8 (i32.add (local.get $out) (local.get $j))
2520
+ (select (i32.add (local.get $v) (i32.const 65))
2521
+ (select (i32.add (local.get $v) (i32.const 71))
2522
+ (select (i32.add (local.get $v) (i32.const -4))
2523
+ (select (select (i32.const 45) (i32.const 43) (local.get $url))
2524
+ (select (i32.const 95) (i32.const 47) (local.get $url))
2525
+ (i32.eq (local.get $v) (i32.const 62)))
2526
+ (i32.lt_u (local.get $v) (i32.const 62)))
2527
+ (i32.lt_u (local.get $v) (i32.const 52)))
2528
+ (i32.lt_u (local.get $v) (i32.const 26))))
2529
+ (local.set $j (i32.add (local.get $j) (i32.const 1)))`
2530
+
2531
+ wat('__b64_enc', `(func $__b64_enc (param $src i32) (param $len i32) (param $url i32) (param $pad i32) (result f64)
2532
+ (local $g3 i32) (local $rem i32) (local $outLen i32) (local $base i32) (local $out i32)
2533
+ (local $i i32) (local $j i32) (local $w i32) (local $v i32)
2534
+ (local.set $g3 (i32.mul (i32.div_u (local.get $len) (i32.const 3)) (i32.const 3)))
2535
+ (local.set $rem (i32.sub (local.get $len) (local.get $g3)))
2536
+ (local.set $outLen (i32.mul (i32.div_u (local.get $len) (i32.const 3)) (i32.const 4)))
2537
+ (if (local.get $rem)
2538
+ (then (local.set $outLen (i32.add (local.get $outLen)
2539
+ (select (i32.const 4) (i32.add (local.get $rem) (i32.const 1)) (local.get $pad))))))
2540
+ (local.set $base (call $__alloc (i32.add (i32.const 4) (local.get $outLen))))
2541
+ (local.set $out (i32.add (local.get $base) (i32.const 4)))
2542
+ (block $gdone (loop $gloop
2543
+ (br_if $gdone (i32.ge_u (local.get $i) (local.get $g3)))
2544
+ (local.set $w (i32.or (i32.or
2545
+ (i32.shl (i32.load8_u (i32.add (local.get $src) (local.get $i))) (i32.const 16))
2546
+ (i32.shl (i32.load8_u (i32.add (local.get $src) (i32.add (local.get $i) (i32.const 1)))) (i32.const 8)))
2547
+ (i32.load8_u (i32.add (local.get $src) (i32.add (local.get $i) (i32.const 2))))))
2548
+ ${b64put('(i32.shr_u (local.get $w) (i32.const 18))')}
2549
+ ${b64put('(i32.shr_u (local.get $w) (i32.const 12))')}
2550
+ ${b64put('(i32.shr_u (local.get $w) (i32.const 6))')}
2551
+ ${b64put('(local.get $w)')}
2552
+ (local.set $i (i32.add (local.get $i) (i32.const 3)))
2553
+ (br $gloop)))
2554
+ (if (i32.eq (local.get $rem) (i32.const 1))
2555
+ (then
2556
+ (local.set $w (i32.shl (i32.load8_u (i32.add (local.get $src) (local.get $i))) (i32.const 16)))
2557
+ ${b64put('(i32.shr_u (local.get $w) (i32.const 18))')}
2558
+ ${b64put('(i32.shr_u (local.get $w) (i32.const 12))')}
2559
+ (if (local.get $pad) (then
2560
+ (i32.store8 (i32.add (local.get $out) (local.get $j)) (i32.const 61))
2561
+ (i32.store8 (i32.add (local.get $out) (i32.add (local.get $j) (i32.const 1))) (i32.const 61))
2562
+ (local.set $j (i32.add (local.get $j) (i32.const 2)))))))
2563
+ (if (i32.eq (local.get $rem) (i32.const 2))
2564
+ (then
2565
+ (local.set $w (i32.or
2566
+ (i32.shl (i32.load8_u (i32.add (local.get $src) (local.get $i))) (i32.const 16))
2567
+ (i32.shl (i32.load8_u (i32.add (local.get $src) (i32.add (local.get $i) (i32.const 1)))) (i32.const 8))))
2568
+ ${b64put('(i32.shr_u (local.get $w) (i32.const 18))')}
2569
+ ${b64put('(i32.shr_u (local.get $w) (i32.const 12))')}
2570
+ ${b64put('(i32.shr_u (local.get $w) (i32.const 6))')}
2571
+ (if (local.get $pad) (then
2572
+ (i32.store8 (i32.add (local.get $out) (local.get $j)) (i32.const 61))
2573
+ (local.set $j (i32.add (local.get $j) (i32.const 1)))))))
2574
+ (i32.store (local.get $base) (local.get $j))
2575
+ (call $__sso_norm (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $out))))`)
2576
+
2577
+ // Forgiving decode. Returns (read << 32) | written; read = chars consumed
2578
+ // (== full length unless the $cap byte budget stopped a chunk; then the
2579
+ // stopped chunk's start). Throws on: a non-alphabet char, len%4==1,
2580
+ // misplaced/unterminated padding, content after complete padding.
2581
+ wat('__b64_dec_raw', `(func $__b64_dec_raw (param $s i64) (param $dst i32) (param $cap i32) (param $url i32) (result i64)
2582
+ (local $slen i32) (local $i i32) (local $c i32) (local $v i32)
2583
+ (local $acc i32) (local $cnt i32) (local $pads i32) (local $done i32)
2584
+ (local $mark i32) (local $written i32) (local $stopped i32) (local $n i32)
2585
+ (local.set $slen (call $__str_byteLen (local.get $s)))
2586
+ (block $stop (loop $loop
2587
+ (br_if $stop (i32.ge_s (local.get $i) (local.get $slen)))
2588
+ (local.set $c (call $__char_at (local.get $s) (local.get $i)))
2589
+ ;; ASCII whitespace (tab LF FF CR space) skipped everywhere
2590
+ (if (i32.or
2591
+ (i32.or (i32.eq (local.get $c) (i32.const 9)) (i32.eq (local.get $c) (i32.const 10)))
2592
+ (i32.or
2593
+ (i32.or (i32.eq (local.get $c) (i32.const 12)) (i32.eq (local.get $c) (i32.const 13)))
2594
+ (i32.eq (local.get $c) (i32.const 32))))
2595
+ (then
2596
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
2597
+ (br $loop)))
2598
+ ;; after complete padding only whitespace may follow
2599
+ (if (local.get $done) (then (throw $__jz_err (f64.const 0))))
2600
+ (if (i32.eq (local.get $c) (i32.const 61)) ;; '='
2601
+ (then
2602
+ (if (i32.lt_s (local.get $cnt) (i32.const 2)) (then (throw $__jz_err (f64.const 0))))
2603
+ (local.set $pads (i32.add (local.get $pads) (i32.const 1)))
2604
+ (if (i32.eq (i32.add (local.get $cnt) (local.get $pads)) (i32.const 4))
2605
+ (then ;; flush the padded partial chunk: 2 chars → 1 byte, 3 → 2
2606
+ (local.set $n (i32.sub (local.get $cnt) (i32.const 1)))
2607
+ (if (i32.gt_s (i32.add (local.get $written) (local.get $n)) (local.get $cap))
2608
+ (then (local.set $stopped (i32.const 1)) (br $stop)))
2609
+ (i32.store8 (i32.add (local.get $dst) (local.get $written))
2610
+ (i32.and (i32.shr_u (local.get $acc)
2611
+ (select (i32.const 10) (i32.const 4) (i32.eq (local.get $cnt) (i32.const 3)))) (i32.const 255)))
2612
+ (if (i32.eq (local.get $cnt) (i32.const 3))
2613
+ (then (i32.store8 (i32.add (local.get $dst) (i32.add (local.get $written) (i32.const 1)))
2614
+ (i32.and (i32.shr_u (local.get $acc) (i32.const 2)) (i32.const 255)))))
2615
+ (local.set $written (i32.add (local.get $written) (local.get $n)))
2616
+ (local.set $mark (i32.add (local.get $i) (i32.const 1)))
2617
+ (local.set $cnt (i32.const 0))
2618
+ (local.set $acc (i32.const 0))
2619
+ (local.set $done (i32.const 1)))))
2620
+ (else
2621
+ ;; a value char while padding is open ("AB=C") is malformed
2622
+ (if (local.get $pads) (then (throw $__jz_err (f64.const 0))))
2623
+ (local.set $v (i32.const -1))
2624
+ (if (i32.and (i32.ge_u (local.get $c) (i32.const 65)) (i32.le_u (local.get $c) (i32.const 90)))
2625
+ (then (local.set $v (i32.sub (local.get $c) (i32.const 65)))))
2626
+ (if (i32.and (i32.ge_u (local.get $c) (i32.const 97)) (i32.le_u (local.get $c) (i32.const 122)))
2627
+ (then (local.set $v (i32.sub (local.get $c) (i32.const 71)))))
2628
+ (if (i32.and (i32.ge_u (local.get $c) (i32.const 48)) (i32.le_u (local.get $c) (i32.const 57)))
2629
+ (then (local.set $v (i32.add (local.get $c) (i32.const 4)))))
2630
+ (if (local.get $url)
2631
+ (then
2632
+ (if (i32.eq (local.get $c) (i32.const 45)) (then (local.set $v (i32.const 62))))
2633
+ (if (i32.eq (local.get $c) (i32.const 95)) (then (local.set $v (i32.const 63)))))
2634
+ (else
2635
+ (if (i32.eq (local.get $c) (i32.const 43)) (then (local.set $v (i32.const 62))))
2636
+ (if (i32.eq (local.get $c) (i32.const 47)) (then (local.set $v (i32.const 63))))))
2637
+ (if (i32.lt_s (local.get $v) (i32.const 0)) (then (throw $__jz_err (f64.const 0))))
2638
+ (local.set $acc (i32.or (i32.shl (local.get $acc) (i32.const 6)) (local.get $v)))
2639
+ (local.set $cnt (i32.add (local.get $cnt) (i32.const 1)))
2640
+ (if (i32.eq (local.get $cnt) (i32.const 4))
2641
+ (then
2642
+ (if (i32.gt_s (i32.add (local.get $written) (i32.const 3)) (local.get $cap))
2643
+ (then (local.set $stopped (i32.const 1)) (br $stop)))
2644
+ (i32.store8 (i32.add (local.get $dst) (local.get $written))
2645
+ (i32.shr_u (local.get $acc) (i32.const 16)))
2646
+ (i32.store8 (i32.add (local.get $dst) (i32.add (local.get $written) (i32.const 1)))
2647
+ (i32.and (i32.shr_u (local.get $acc) (i32.const 8)) (i32.const 255)))
2648
+ (i32.store8 (i32.add (local.get $dst) (i32.add (local.get $written) (i32.const 2)))
2649
+ (i32.and (local.get $acc) (i32.const 255)))
2650
+ (local.set $written (i32.add (local.get $written) (i32.const 3)))
2651
+ (local.set $mark (i32.add (local.get $i) (i32.const 1)))
2652
+ (local.set $cnt (i32.const 0))
2653
+ (local.set $acc (i32.const 0))))))
2654
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
2655
+ (br $loop)))
2656
+ ;; EOF: unterminated padding ("AA=") is malformed; a padless partial chunk
2657
+ ;; is the loose case — 1 leftover char is len%4==1 (malformed), 2 → 1 byte,
2658
+ ;; 3 → 2 bytes, extra bits ignored.
2659
+ (if (i32.eqz (local.get $stopped))
2660
+ (then
2661
+ (if (i32.and (i32.ne (local.get $pads) (i32.const 0)) (i32.eqz (local.get $done)))
2662
+ (then (throw $__jz_err (f64.const 0))))
2663
+ (if (i32.eq (local.get $cnt) (i32.const 1)) (then (throw $__jz_err (f64.const 0))))
2664
+ (if (i32.gt_s (local.get $cnt) (i32.const 1))
2665
+ (then
2666
+ (local.set $n (i32.sub (local.get $cnt) (i32.const 1)))
2667
+ (if (i32.gt_s (i32.add (local.get $written) (local.get $n)) (local.get $cap))
2668
+ (then (local.set $stopped (i32.const 1)))
2669
+ (else
2670
+ (i32.store8 (i32.add (local.get $dst) (local.get $written))
2671
+ (i32.and (i32.shr_u (local.get $acc)
2672
+ (select (i32.const 10) (i32.const 4) (i32.eq (local.get $cnt) (i32.const 3)))) (i32.const 255)))
2673
+ (if (i32.eq (local.get $cnt) (i32.const 3))
2674
+ (then (i32.store8 (i32.add (local.get $dst) (i32.add (local.get $written) (i32.const 1)))
2675
+ (i32.and (i32.shr_u (local.get $acc) (i32.const 2)) (i32.const 255)))))
2676
+ (local.set $written (i32.add (local.get $written) (local.get $n)))
2677
+ (local.set $mark (local.get $slen))))))))
2678
+ (i64.or
2679
+ (i64.shl (i64.extend_i32_u (select (local.get $mark) (local.get $slen) (local.get $stopped))) (i64.const 32))
2680
+ (i64.extend_i32_u (local.get $written))))`)
2681
+
2682
+ // Uint8Array receiver guard → data offset. u8-only per spec (TypeError otherwise).
2683
+ wat('__u8_data', `(func $__u8_data (param $ptr i64) (result i32)
2684
+ (if (i32.or
2685
+ (i32.ne (call $__ptr_type (local.get $ptr)) (i32.const ${PTR.TYPED}))
2686
+ (i32.ne (i32.and (call $__ptr_aux (local.get $ptr)) (i32.const 7)) (i32.const 1)))
2687
+ (then (throw $__jz_err (f64.const 0))))
2688
+ (call $__typed_data (local.get $ptr)))`)
2689
+
2690
+ wat('__btoa', `(func $__btoa (param $v i64) (result f64)
2691
+ (local $s i64) (local $len i32) (local $buf i32)
2692
+ (local.set $s (call $__to_str (local.get $v)))
2693
+ (local.set $len (call $__str_byteLen (local.get $s)))
2694
+ (local.set $buf (call $__alloc (local.get $len)))
2695
+ (call $__str_copy (local.get $s) (local.get $buf) (local.get $len))
2696
+ (call $__b64_enc (local.get $buf) (local.get $len) (i32.const 0) (i32.const 1)))`)
2697
+
2698
+ wat('__atob', `(func $__atob (param $v i64) (result f64)
2699
+ (local $s i64) (local $max i32) (local $base i32) (local $rw i64)
2700
+ (local.set $s (call $__to_str (local.get $v)))
2701
+ (local.set $max (i32.add (i32.mul (i32.add (i32.shr_u (call $__str_byteLen (local.get $s)) (i32.const 2)) (i32.const 1)) (i32.const 3)) (i32.const 3)))
2702
+ (local.set $base (call $__alloc (i32.add (i32.const 4) (local.get $max))))
2703
+ (local.set $rw (call $__b64_dec_raw (local.get $s) (i32.add (local.get $base) (i32.const 4)) (i32.const 2147483647) (i32.const 0)))
2704
+ (i32.store (local.get $base) (i32.wrap_i64 (local.get $rw)))
2705
+ (call $__sso_norm (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (i32.add (local.get $base) (i32.const 4)))))`)
2706
+
2707
+ wat('__b64_from', `(func $__b64_from (param $v i64) (param $url i32) (result f64)
2708
+ (local $s i64) (local $max i32) (local $base i32) (local $rw i64) (local $n i32)
2709
+ (local.set $s (call $__to_str (local.get $v)))
2710
+ (local.set $max (i32.add (i32.mul (i32.add (i32.shr_u (call $__str_byteLen (local.get $s)) (i32.const 2)) (i32.const 1)) (i32.const 3)) (i32.const 3)))
2711
+ (local.set $base (call $__alloc (i32.add (i32.const 8) (local.get $max))))
2712
+ (local.set $rw (call $__b64_dec_raw (local.get $s) (i32.add (local.get $base) (i32.const 8)) (i32.const 2147483647) (local.get $url)))
2713
+ (local.set $n (i32.wrap_i64 (local.get $rw)))
2714
+ (i32.store (local.get $base) (local.get $n))
2715
+ (i32.store (i32.add (local.get $base) (i32.const 4)) (local.get $n))
2716
+ (call $__mkptr (i32.const ${PTR.TYPED}) (i32.const 1) (i32.add (local.get $base) (i32.const 8))))`)
2717
+
2718
+ wat('__b64_set', `(func $__b64_set (param $dst i64) (param $s i64) (param $url i32) (result i64)
2719
+ (call $__b64_dec_raw (local.get $s)
2720
+ (call $__u8_data (local.get $dst))
2721
+ (call $__len (local.get $dst))
2722
+ (local.get $url)))`)
2723
+
2724
+ wat('__hex_enc', `(func $__hex_enc (param $src i32) (param $len i32) (result f64)
2725
+ (local $base i32) (local $out i32) (local $i i32) (local $b i32) (local $n i32)
2726
+ (local.set $base (call $__alloc (i32.add (i32.const 4) (i32.shl (local.get $len) (i32.const 1)))))
2727
+ (local.set $out (i32.add (local.get $base) (i32.const 4)))
2728
+ (block $d (loop $l
2729
+ (br_if $d (i32.ge_u (local.get $i) (local.get $len)))
2730
+ (local.set $b (i32.load8_u (i32.add (local.get $src) (local.get $i))))
2731
+ (local.set $n (i32.shr_u (local.get $b) (i32.const 4)))
2732
+ (i32.store8 (i32.add (local.get $out) (i32.shl (local.get $i) (i32.const 1)))
2733
+ (select (i32.add (local.get $n) (i32.const 87)) (i32.add (local.get $n) (i32.const 48)) (i32.gt_u (local.get $n) (i32.const 9))))
2734
+ (local.set $n (i32.and (local.get $b) (i32.const 15)))
2735
+ (i32.store8 (i32.add (i32.add (local.get $out) (i32.shl (local.get $i) (i32.const 1))) (i32.const 1))
2736
+ (select (i32.add (local.get $n) (i32.const 87)) (i32.add (local.get $n) (i32.const 48)) (i32.gt_u (local.get $n) (i32.const 9))))
2737
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
2738
+ (br $l)))
2739
+ (i32.store (local.get $base) (i32.shl (local.get $len) (i32.const 1)))
2740
+ (call $__sso_norm (call $__mkptr (i32.const ${PTR.STRING}) (i32.const 0) (local.get $out))))`)
2741
+
2742
+ // Hex decode: strict per spec — no whitespace, both nibble cases, odd length
2743
+ // or a non-hex char throw. Stops at $cap whole bytes (setFromHex).
2744
+ wat('__hex_dec_raw', `(func $__hex_dec_raw (param $s i64) (param $dst i32) (param $cap i32) (result i64)
2745
+ (local $slen i32) (local $i i32) (local $hi i32) (local $lo i32) (local $written i32)
2746
+ (local.set $slen (call $__str_byteLen (local.get $s)))
2747
+ (if (i32.and (local.get $slen) (i32.const 1)) (then (throw $__jz_err (f64.const 0))))
2748
+ (block $stop (loop $l
2749
+ (br_if $stop (i32.ge_s (local.get $i) (local.get $slen)))
2750
+ (br_if $stop (i32.ge_s (local.get $written) (local.get $cap)))
2751
+ (local.set $hi (call $__uri_hex (call $__char_at (local.get $s) (local.get $i))))
2752
+ (local.set $lo (call $__uri_hex (call $__char_at (local.get $s) (i32.add (local.get $i) (i32.const 1)))))
2753
+ (if (i32.or (i32.lt_s (local.get $hi) (i32.const 0)) (i32.lt_s (local.get $lo) (i32.const 0)))
2754
+ (then (throw $__jz_err (f64.const 0))))
2755
+ (i32.store8 (i32.add (local.get $dst) (local.get $written))
2756
+ (i32.or (i32.shl (local.get $hi) (i32.const 4)) (local.get $lo)))
2757
+ (local.set $written (i32.add (local.get $written) (i32.const 1)))
2758
+ (local.set $i (i32.add (local.get $i) (i32.const 2)))
2759
+ (br $l)))
2760
+ (i64.or (i64.shl (i64.extend_i32_u (local.get $i)) (i64.const 32))
2761
+ (i64.extend_i32_u (local.get $written))))`)
2762
+
2763
+ wat('__hex_from', `(func $__hex_from (param $v i64) (result f64)
2764
+ (local $s i64) (local $base i32) (local $rw i64) (local $n i32)
2765
+ (local.set $s (call $__to_str (local.get $v)))
2766
+ (local.set $base (call $__alloc (i32.add (i32.const 8) (i32.shr_u (call $__str_byteLen (local.get $s)) (i32.const 1)))))
2767
+ (local.set $rw (call $__hex_dec_raw (local.get $s) (i32.add (local.get $base) (i32.const 8)) (i32.const 2147483647)))
2768
+ (local.set $n (i32.wrap_i64 (local.get $rw)))
2769
+ (i32.store (local.get $base) (local.get $n))
2770
+ (i32.store (i32.add (local.get $base) (i32.const 4)) (local.get $n))
2771
+ (call $__mkptr (i32.const ${PTR.TYPED}) (i32.const 1) (i32.add (local.get $base) (i32.const 8))))`)
2772
+
2773
+ wat('__hex_set', `(func $__hex_set (param $dst i64) (param $s i64) (result i64)
2774
+ (call $__hex_dec_raw (local.get $s)
2775
+ (call $__u8_data (local.get $dst))
2776
+ (call $__len (local.get $dst))))`)
2777
+
2778
+ // btoa(s): base64 of the string's bytes. jz strings ARE bytes, so every char
2779
+ // qualifies — JS's InvalidCharacterError for >0xFF code units cannot arise;
2780
+ // non-ASCII text encodes its UTF-8 bytes (documented byte-string divergence).
2781
+ bind('btoa', (value) => {
2782
+ inc('__btoa')
2783
+ return typed(['call', '$__btoa',
2784
+ value === undefined ? ['i64.const', UNDEF_NAN] : asI64(emit(value))], 'f64')
2785
+ })
2786
+
2787
+ // atob(b64): decoded bytes as a byte-string — charCodeAt(i) reads the byte,
2788
+ // exactly what JS binary-string consumers observe.
2789
+ bind('atob', (value) => {
2790
+ ctx.runtime.throws = true
2791
+ inc('__atob')
2792
+ return typed(['call', '$__atob',
2793
+ value === undefined ? ['i64.const', UNDEF_NAN] : asI64(emit(value))], 'f64')
2794
+ })
2390
2795
  }
package/module/timer.js CHANGED
@@ -28,7 +28,7 @@
28
28
 
29
29
  import { typed, asF64, asI64, UNDEF_NAN, MAX_CLOSURE_ARITY, temp, tempI64 } from '../src/ir.js'
30
30
  import { emit, deps, hostImport } from '../src/bridge.js'
31
- import { inc, PTR, LAYOUT, declGlobal } from '../src/ctx.js'
31
+ import { inc, err, PTR, LAYOUT, declGlobal } from '../src/ctx.js'
32
32
 
33
33
  const MAX_TIMERS = 64
34
34
  const ENTRY_SIZE = 40
@@ -46,6 +46,19 @@ const invokeClosureFn = (exported) => `(func $__invoke_closure${exported ? ' (ex
46
46
  (i64.shr_u (local.get $clos) (i64.const ${LAYOUT.AUX_SHIFT}))
47
47
  (i64.const ${LAYOUT.AUX_MASK})))))`
48
48
 
49
+ // One-arg variant: first $ftN slot carries a real f64 (the rAF timestamp),
50
+ // the rest pad UNDEF_NAN. Exported so the host frame loop can pass the
51
+ // DOMHighResTimeStamp through to the callback.
52
+ const invokeClosure1Fn = (exported) => `(func $__invoke_closure1${exported ? ' (export "__invoke_closure1")' : ''} (param $clos i64) (param $a0 f64) (result f64)
53
+ (call_indirect (type \$ftN)
54
+ (f64.reinterpret_i64 (local.get $clos))
55
+ (i32.const 0)
56
+ (local.get $a0)
57
+ ${Array.from({length: MAX_CLOSURE_ARITY - 1}, () => `(f64.const nan:${UNDEF_NAN})`).join('\n ')}
58
+ (i32.wrap_i64 (i64.and
59
+ (i64.shr_u (local.get $clos) (i64.const ${LAYOUT.AUX_SHIFT}))
60
+ (i64.const ${LAYOUT.AUX_MASK})))))`
61
+
49
62
  const setupWasi = (ctx) => {
50
63
  // Always include init + tick + loop when timer module loads (structural, not per-emitter)
51
64
  inc('__timer_init', '__timer_tick', '__timer_loop')
@@ -263,6 +276,14 @@ const setupWasi = (ctx) => {
263
276
  inc('__timer_cancel')
264
277
  return typed(['call', '$__timer_cancel', asF64(emit(idExpr))], 'f64')
265
278
  }
279
+
280
+ // rAF is a display-loop service — it has no WASI meaning (and warning +
281
+ // declaring env.* here would break the one-import-namespace shape). Reject
282
+ // with the architecture that DOES work standalone: the embedder owns the
283
+ // frame loop and calls an exported step per frame.
284
+ const noWasiRaf = () => err(`requestAnimationFrame needs a JS host (host:'js') — under wasi export a per-frame function and drive it from the embedder's loop`)
285
+ ctx.core.emit['requestAnimationFrame'] = noWasiRaf
286
+ ctx.core.emit['cancelAnimationFrame'] = noWasiRaf
266
287
  }
267
288
 
268
289
  const setupJsHost = (ctx) => {
@@ -298,6 +319,26 @@ const setupJsHost = (ctx) => {
298
319
  }
299
320
  ctx.core.emit['clearTimeout'] = emitClear
300
321
  ctx.core.emit['clearInterval'] = emitClear
322
+
323
+ // requestAnimationFrame(cb) / cancelAnimationFrame(id) — the same env-service
324
+ // shape as setTimeout: the host schedules (real rAF in browsers, a 16 ms
325
+ // timer fallback elsewhere — interop.js) and fires the callback through the
326
+ // exported __invoke_closure1 trampoline with the frame timestamp.
327
+ const needRaf = () => hostImport('env', 'requestAnimationFrame',
328
+ ['func', '$__raf', ['param', 'i64'], ['result', 'f64']])
329
+ const needCancelRaf = () => hostImport('env', 'cancelAnimationFrame',
330
+ ['func', '$__craf', ['param', 'f64'], ['result', 'f64']])
331
+ ctx.core.stdlib['__invoke_closure1'] = invokeClosure1Fn(true)
332
+
333
+ ctx.core.emit['requestAnimationFrame'] = (cbExpr) => {
334
+ needRaf()
335
+ inc('__invoke_closure1')
336
+ return typed(['call', '$__raf', ['i64.reinterpret_f64', asF64(emit(cbExpr))]], 'f64')
337
+ }
338
+ ctx.core.emit['cancelAnimationFrame'] = (idExpr) => {
339
+ needCancelRaf()
340
+ return typed(['call', '$__craf', asF64(emit(idExpr))], 'f64')
341
+ }
301
342
  }
302
343
 
303
344
  export default (ctx) => {