jz 0.9.0 → 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/README.md +19 -7
- package/bench/bench.svg +18 -18
- package/dist/interop.js +1 -1
- package/dist/jz.js +2026 -1429
- package/interop.js +73 -6
- package/jzify/index.js +7 -1
- package/jzify/transform.js +21 -2
- package/jzify/webrt.js +145 -0
- package/layout.js +13 -3
- package/module/array.js +49 -42
- package/module/collection.js +222 -111
- package/module/console.js +4 -0
- package/module/core.js +86 -7
- package/module/crypto.js +124 -0
- package/module/index.js +3 -1
- package/module/math.js +5 -0
- package/module/navigator.js +26 -0
- package/module/schema.js +11 -3
- package/module/string.js +433 -28
- package/module/timer.js +42 -1
- package/module/typedarray.js +370 -60
- package/package.json +3 -3
- package/src/abi/array.js +24 -16
- package/src/abi/index.js +2 -2
- package/src/abi/object.js +17 -0
- package/src/ast.js +53 -0
- package/src/autoload.js +19 -3
- package/src/compile/analyze.js +190 -21
- package/src/compile/emit-assign.js +111 -7
- package/src/compile/emit.js +84 -20
- package/src/compile/index.js +37 -4
- package/src/compile/inplace-store.js +10 -9
- package/src/compile/narrow.js +71 -1
- package/src/compile/plan/index.js +5 -0
- package/src/compile/plan/literals.js +11 -2
- package/src/ctx.js +14 -0
- package/src/ir.js +26 -1
- package/src/optimize/index.js +1 -0
- package/src/optimize/vectorize.js +80 -6
- package/src/prepare/index.js +6 -0
- package/src/static.js +31 -0
- package/src/type.js +356 -47
package/interop.js
CHANGED
|
@@ -203,6 +203,9 @@ const ELEMS = {
|
|
|
203
203
|
Uint32Array: [5, 4, 'getUint32', 'setUint32'],
|
|
204
204
|
Float32Array: [6, 4, 'getFloat32', 'setFloat32'],
|
|
205
205
|
Float64Array: [7, 8, 'getFloat64', 'setFloat64'],
|
|
206
|
+
// flag-carrying kinds: elemId = base code | flag (32 = f16, 64 = clamped)
|
|
207
|
+
Float16Array: [35, 2, 'getFloat16', 'setFloat16'],
|
|
208
|
+
Uint8ClampedArray: [65, 1, 'getUint8', 'setUint8'],
|
|
206
209
|
}
|
|
207
210
|
// Pre-built lookup by element ID (avoids Object.values on each access)
|
|
208
211
|
const ELEM_BY_ID = Object.values(ELEMS)
|
|
@@ -400,9 +403,12 @@ export const memory = (src) => {
|
|
|
400
403
|
const entries = Object.entries(obj)
|
|
401
404
|
let cap = 8
|
|
402
405
|
while (entries.length * 4 >= cap * 3) cap <<= 1 // stay under the 75% grow trigger
|
|
403
|
-
|
|
406
|
+
// cap × (24-B entry + 4-B probe hash lane) — collection.js's exact layout;
|
|
407
|
+
// the lane (after the entries) is what wasm probes walk
|
|
408
|
+
const off = hdr(entries.length, cap, cap * 28)
|
|
404
409
|
// Stage every slot as i64 bits (empty = 0) — same NaN-canonicalization dodge as mem.Array.
|
|
405
410
|
const staged = new BigInt64Array(cap * 3)
|
|
411
|
+
const lane = new Int32Array(cap)
|
|
406
412
|
entries.forEach(([k, v], seq) => {
|
|
407
413
|
const keyBox = mem.String(k)
|
|
408
414
|
const h = jzStrHash(keyBox)
|
|
@@ -411,9 +417,11 @@ export const memory = (src) => {
|
|
|
411
417
|
staged[idx * 3] = (BigInt(seq) << 32n) | BigInt(h >>> 0)
|
|
412
418
|
staged[idx * 3 + 1] = bits(keyBox)
|
|
413
419
|
staged[idx * 3 + 2] = bits(mem.wrapVal(v))
|
|
420
|
+
lane[idx] = h | 0
|
|
414
421
|
})
|
|
415
422
|
const dst = new BigInt64Array(mem.buffer, off, cap * 3)
|
|
416
423
|
dst.set(staged)
|
|
424
|
+
new Int32Array(mem.buffer, off + cap * 24, cap).set(lane)
|
|
417
425
|
return ptr(7, 0, off)
|
|
418
426
|
}
|
|
419
427
|
|
|
@@ -474,7 +482,10 @@ export const memory = (src) => {
|
|
|
474
482
|
if (t === 3) { // TYPED
|
|
475
483
|
const elem = a & 7
|
|
476
484
|
const [, stride] = ELEM_BY_ID[elem]
|
|
477
|
-
const Ctor =
|
|
485
|
+
const Ctor = (a & 32)
|
|
486
|
+
? (globalThis.Float16Array ?? (() => { throw new Error('decoding a Float16Array result needs a host with Float16Array (Node ≥ 24 / modern browsers)') })())
|
|
487
|
+
: (a & 64) ? Uint8ClampedArray
|
|
488
|
+
: [Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array][elem]
|
|
478
489
|
if (a & 8) {
|
|
479
490
|
const byteLen = m.getInt32(off, true), dataOff = m.getInt32(off + 4, true)
|
|
480
491
|
return new Ctor(mem.buffer, dataOff, byteLen / stride)
|
|
@@ -574,6 +585,8 @@ export const memory = (src) => {
|
|
|
574
585
|
// the target (same stride), use .set() for a fast memcpy instead of
|
|
575
586
|
// per-element DataView writes. Falls back to DataView for mismatched types.
|
|
576
587
|
const TA = [Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array]
|
|
588
|
+
TA[65] = Uint8ClampedArray
|
|
589
|
+
if (globalThis.Float16Array) TA[35] = globalThis.Float16Array
|
|
577
590
|
for (const [name, [elemId, stride, , setter]] of Object.entries(ELEMS)) {
|
|
578
591
|
mem[name] = (data) => {
|
|
579
592
|
const n = data.length, bytes = n * stride, off = hdr(bytes, bytes, bytes)
|
|
@@ -844,13 +857,24 @@ const prepareInterop = (opts) => {
|
|
|
844
857
|
// in JS) — see module/collection.js header for rationale. f64 returns are
|
|
845
858
|
// wrapped back to BigInt so the wasm side reinterprets a non-canonicalized
|
|
846
859
|
// bit pattern.
|
|
847
|
-
|
|
860
|
+
// A dynamic member op can reach the host with a receiver that is NOT an
|
|
861
|
+
// external handle (extMap[0] is null — e.g. a builtin's placeholder value, or
|
|
862
|
+
// a number that inference couldn't type whose method jz doesn't implement).
|
|
863
|
+
// Without the guard that surfaces as a bare host TypeError ("Cannot read
|
|
864
|
+
// properties of null") — a mystery. Name the actual failure instead.
|
|
865
|
+
const extRecv = (objBig, prop, what) => {
|
|
848
866
|
const obj = state.extMap[offset(objBig)]
|
|
867
|
+
if (obj == null) throw new Error(`'${prop}' — jz dispatched this ${what} to the host, but the receiver is not a host object (an unsupported builtin method, or a receiver type jz couldn't resolve)`)
|
|
868
|
+
return obj
|
|
869
|
+
}
|
|
870
|
+
opts._interp.__ext_prop = (objBig, propBig) => {
|
|
849
871
|
const prop = state.mem.read(propBig)
|
|
872
|
+
const obj = extRecv(objBig, prop, 'property read')
|
|
850
873
|
return bits(state.mem.wrapVal(typeof obj[prop] === 'function' ? obj[prop].bind(obj) : obj[prop]))
|
|
851
874
|
}
|
|
852
875
|
opts._interp.__ext_has = (objBig, propBig) => {
|
|
853
|
-
|
|
876
|
+
const prop = state.mem.read(propBig)
|
|
877
|
+
return (prop in extRecv(objBig, prop, 'membership test')) ? 1 : 0
|
|
854
878
|
}
|
|
855
879
|
opts._interp.__ext_set = (objBig, propBig, valBig) => {
|
|
856
880
|
let v = state.mem.read(valBig)
|
|
@@ -867,13 +891,16 @@ const prepareInterop = (opts) => {
|
|
|
867
891
|
// same-ctor copy — exactly `new Ctor(view)` with no manual size/offset
|
|
868
892
|
// bookkeeping — and a no-op for every other decoded value shape.
|
|
869
893
|
if (ArrayBuffer.isView(v)) v = v.slice()
|
|
870
|
-
state.
|
|
894
|
+
const prop = state.mem.read(propBig)
|
|
895
|
+
extRecv(objBig, prop, 'property write')[prop] = v
|
|
871
896
|
return 1
|
|
872
897
|
}
|
|
873
898
|
opts._interp.__ext_call = (objBig, propBig, argsBig) => {
|
|
874
|
-
const obj = state.extMap[offset(objBig)]
|
|
875
899
|
const prop = state.mem.read(propBig)
|
|
900
|
+
const obj = extRecv(objBig, prop, 'method call')
|
|
876
901
|
const args = state.mem.read(argsBig)
|
|
902
|
+
if (typeof obj[prop] !== 'function')
|
|
903
|
+
throw new Error(`'${prop}' is not a function on this host ${obj?.constructor?.name ?? 'object'}`)
|
|
877
904
|
return hostRet(state, obj[prop].apply(obj, args))
|
|
878
905
|
}
|
|
879
906
|
return state
|
|
@@ -935,6 +962,19 @@ const installDefaultEnvImports = (mod, imports, state) => {
|
|
|
935
962
|
return a[0] | 0
|
|
936
963
|
}
|
|
937
964
|
}
|
|
965
|
+
// Byte-fill entropy for crypto.getRandomValues/randomUUID (module/crypto.js).
|
|
966
|
+
// Fills wasm linear memory directly; the view is created per call — never
|
|
967
|
+
// cached — so a Memory.grow between calls can't leave a detached view.
|
|
968
|
+
if (envFns.has('random') && !imports.env.random) {
|
|
969
|
+
imports.env.random = (off, len) => {
|
|
970
|
+
const view = new Uint8Array(state.mem.buffer, off, len)
|
|
971
|
+
if (globalThis.crypto?.getRandomValues) globalThis.crypto.getRandomValues(view)
|
|
972
|
+
else for (let i = 0; i < len; i++) view[i] = (Math.random() * 256) >>> 0
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
if (envFns.has('hardwareConcurrency') && !imports.env.hardwareConcurrency) {
|
|
976
|
+
imports.env.hardwareConcurrency = () => globalThis.navigator?.hardwareConcurrency ?? 1
|
|
977
|
+
}
|
|
938
978
|
if (envFns.has('parseFloat') && !imports.env.parseFloat) {
|
|
939
979
|
imports.env.parseFloat = (valBig) => {
|
|
940
980
|
const s = readArgBits(state, valBig)
|
|
@@ -975,6 +1015,31 @@ const installDefaultEnvImports = (mod, imports, state) => {
|
|
|
975
1015
|
return 0
|
|
976
1016
|
}
|
|
977
1017
|
}
|
|
1018
|
+
// requestAnimationFrame wiring: real rAF where the host has one; a 16 ms
|
|
1019
|
+
// timer elsewhere (Node) so frame-driven modules still run — the callback
|
|
1020
|
+
// receives a real timestamp either way via __invoke_closure1.
|
|
1021
|
+
if (envFns.has('requestAnimationFrame') || envFns.has('cancelAnimationFrame')) {
|
|
1022
|
+
const cancel = new Map()
|
|
1023
|
+
let nextId = 1
|
|
1024
|
+
if (envFns.has('requestAnimationFrame') && !imports.env.requestAnimationFrame) imports.env.requestAnimationFrame = (cbBig) => {
|
|
1025
|
+
const id = nextId++
|
|
1026
|
+
const fire = (t) => { cancel.delete(id); state.invoke1?.(cbBig, t); state.afterTick?.() }
|
|
1027
|
+
const raf = globalThis.requestAnimationFrame
|
|
1028
|
+
if (typeof raf === 'function') {
|
|
1029
|
+
const h = raf(fire)
|
|
1030
|
+
cancel.set(id, () => globalThis.cancelAnimationFrame?.(h))
|
|
1031
|
+
} else {
|
|
1032
|
+
const h = setTimeout(() => fire(typeof performance !== 'undefined' ? performance.now() : Date.now()), 16)
|
|
1033
|
+
cancel.set(id, () => clearTimeout(h))
|
|
1034
|
+
}
|
|
1035
|
+
return id
|
|
1036
|
+
}
|
|
1037
|
+
if (envFns.has('cancelAnimationFrame') && !imports.env.cancelAnimationFrame) imports.env.cancelAnimationFrame = (id) => {
|
|
1038
|
+
const c = cancel.get(id)
|
|
1039
|
+
if (c) { c(); cancel.delete(id) }
|
|
1040
|
+
return 0
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
978
1043
|
}
|
|
979
1044
|
|
|
980
1045
|
// JS-side polyfills for `wasm:js-string` builtins. Used when the engine does
|
|
@@ -1085,6 +1150,8 @@ const finishInstantiation = (mod, inst, imports, needsWasi, opts, state) => {
|
|
|
1085
1150
|
|
|
1086
1151
|
// Trampoline used by env.setTimeout/clearTimeout to fire scheduled closures.
|
|
1087
1152
|
state.invoke = inst.exports.__invoke_closure || null
|
|
1153
|
+
// One-arg variant — env.requestAnimationFrame passes the frame timestamp.
|
|
1154
|
+
state.invoke1 = inst.exports.__invoke_closure1 || null
|
|
1088
1155
|
|
|
1089
1156
|
// Drive WASM timer queue via JS scheduling (non-blocking, no-op if absent).
|
|
1090
1157
|
attachTimers(inst)
|
package/jzify/index.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { JZIFY_CLASS_ERRORS as JC } from '../src/op-policy.js'
|
|
11
11
|
import { parse } from '../src/parse.js'
|
|
12
12
|
import { createAsyncLowering, ASYNC_RUNTIME, ASYNC_GEN_RUNTIME } from './async.js'
|
|
13
|
+
import { USP_RUNTIME } from './webrt.js'
|
|
13
14
|
import { createNames } from './names.js'
|
|
14
15
|
import { foldStaticExportHelpers, foldStaticBundlerHelpers, canonicalizeObjectIdioms } from './bundler.js'
|
|
15
16
|
import { createSwitchLowering, normalizeCaseBody } from './switch.js'
|
|
@@ -47,7 +48,9 @@ const iterProto = { on: false }
|
|
|
47
48
|
const genErr = (msg) => { throw new Error('jzify: ' + msg) }
|
|
48
49
|
const { lowerGenerator, desugarForOfGenerator, desugarForOfProtocol, unwindChain, fuseTerminal, fusedLoop, isTerminal } = createGeneratorLowering({ transform, err: genErr, generatorNames, genTemp: (t) => names.genTemp(t), iterProto })
|
|
49
50
|
const { lowerAsync, lowerAsyncGen, noteAsync, asyncUsed, agenUsed, resetAsync } = createAsyncLowering({ genTemp: (t) => names.genTemp(t), err: genErr })
|
|
50
|
-
|
|
51
|
+
// Web-runtime splice flags (URLSearchParams, …) — reset per transform run.
|
|
52
|
+
const webrt = { usp: false }
|
|
53
|
+
bindGenerators({ lowerGenerator, desugarForOfGenerator, desugarForOfProtocol, lowerAsync, lowerAsyncGen, noteAsync, generatorNames, iterProto, unwindChain, fuseTerminal, fusedLoop, isTerminal, webrt })
|
|
51
54
|
transformSwitch = createSwitchLowering(transform, names)
|
|
52
55
|
|
|
53
56
|
// Spread normalization for iterator values — injected only when a spread site
|
|
@@ -140,6 +143,7 @@ export default function jzify(ast) {
|
|
|
140
143
|
if (Array.isArray(ast) && ast[0] === ';') ast = [';', ...foldPseudoClassical(ast.slice(1))]
|
|
141
144
|
resetAsync()
|
|
142
145
|
iterProto.drain = false
|
|
146
|
+
webrt.usp = false
|
|
143
147
|
let out = transformScope(ast)
|
|
144
148
|
const prepend = (src) => {
|
|
145
149
|
// runtimes ride the same well-known-symbol canonicalization as user code
|
|
@@ -159,6 +163,8 @@ export default function jzify(ast) {
|
|
|
159
163
|
// [Symbol.iterator]() mints (__it_from) → splice the decorated-iterator
|
|
160
164
|
// factory. Programs without value-position helpers never take this shape.
|
|
161
165
|
if (iterProto.helpersUsed || iterProto.fromUsed) prepend(ITER_HELPERS_RUNTIME)
|
|
166
|
+
// URLSearchParams sites → splice the jz-source implementation (webrt.js).
|
|
167
|
+
if (webrt.usp) prepend(USP_RUNTIME)
|
|
162
168
|
// async generators → splice the tagged-yield driver (before the promise
|
|
163
169
|
// runtime prepend so it lands AFTER it in module order — it calls __p_*).
|
|
164
170
|
if (agenUsed()) prepend(ASYNC_GEN_RUNTIME)
|
package/jzify/transform.js
CHANGED
|
@@ -9,8 +9,8 @@ import { isDestructurePat } from './hoist-vars.js'
|
|
|
9
9
|
|
|
10
10
|
const ERROR_INSTANCEOF = new Set(['Error', 'TypeError', 'SyntaxError', 'RangeError', 'ReferenceError', 'URIError', 'EvalError'])
|
|
11
11
|
|
|
12
|
-
const TYPED_ARRAYS = new Set(['Float64Array','Float32Array','Int32Array','Uint32Array',
|
|
13
|
-
'Int16Array','Uint16Array','Int8Array','Uint8Array',
|
|
12
|
+
const TYPED_ARRAYS = new Set(['Float64Array','Float32Array','Float16Array','Int32Array','Uint32Array',
|
|
13
|
+
'Int16Array','Uint16Array','Int8Array','Uint8Array','Uint8ClampedArray',
|
|
14
14
|
'ArrayBuffer','BigInt64Array','BigUint64Array','DataView'])
|
|
15
15
|
|
|
16
16
|
const isProto = n => Array.isArray(n) && n[0] === '.' && Array.isArray(n[1]) && n[1][0] === '.' && n[1][2] === 'prototype'
|
|
@@ -263,6 +263,19 @@ export function createTransform(opts) {
|
|
|
263
263
|
_gen.noteAsync()
|
|
264
264
|
return ['()', P_STATIC[callee[2]], ...rest.map(a => a == null ? a : transform(a))]
|
|
265
265
|
}
|
|
266
|
+
// queueMicrotask(fn) IS the runtime's job queue: push onto __mt, drained
|
|
267
|
+
// at the same host boundaries promise jobs are. Evaluates to undefined
|
|
268
|
+
// per spec (push's return length is discarded by the comma).
|
|
269
|
+
if (callee === 'queueMicrotask' && rest.length) {
|
|
270
|
+
_gen.noteAsync()
|
|
271
|
+
return [',', ['()', ['.', '__mt', 'push'], ...rest.map(a => a == null ? a : transform(a))], 'undefined']
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// URLSearchParams rides a spliced jz-source runtime (jzify/webrt.js);
|
|
275
|
+
// `new URLSearchParams(x)` unwraps to this same call via the `new` handler.
|
|
276
|
+
if (callee === 'URLSearchParams' && _gen?.webrt) {
|
|
277
|
+
_gen.webrt.usp = true
|
|
278
|
+
return ['()', '__usp_new', ...rest.map(a => a == null ? a : transform(a))]
|
|
266
279
|
}
|
|
267
280
|
// Terminal iterator helper (toArray/reduce/forEach/some/every/find) on a
|
|
268
281
|
// chain rooted at a known generator call → fused IIFE loop.
|
|
@@ -448,6 +461,12 @@ export function createTransform(opts) {
|
|
|
448
461
|
// form, and `new URL(rel, import.meta.url)` lowers to a static href string there.
|
|
449
462
|
// User classes (lowered to factory arrows by jzify) become plain calls below.
|
|
450
463
|
if (typeof name === 'string' && (TYPED_ARRAYS.has(name) || name === 'Array' || name === 'RegExp' || name === 'URL')) return ['new', transform(ctor), ...cargs.map(transform)]
|
|
464
|
+
// paren-less `new URLSearchParams` (ctor arrives as a bare string —
|
|
465
|
+
// the call-node form unwraps through the '()' handler below)
|
|
466
|
+
if (name === 'URLSearchParams' && typeof ctor === 'string' && _gen?.webrt) {
|
|
467
|
+
_gen.webrt.usp = true
|
|
468
|
+
return ['()', '__usp_new']
|
|
469
|
+
}
|
|
451
470
|
if (Array.isArray(ctor) && ctor[0] === '()') return transform(ctor)
|
|
452
471
|
return ['()', transform(ctor), ...(cargs.length ? cargs.map(transform) : [null])]
|
|
453
472
|
},
|
package/jzify/webrt.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web-platform runtime pieces, as readable jz source — spliced ahead of user
|
|
3
|
+
* code by jzify when used (the ASYNC_RUNTIME pattern; pay-per-use, programs
|
|
4
|
+
* without them compile byte-identically).
|
|
5
|
+
*
|
|
6
|
+
* URLSearchParams: `new URLSearchParams(init)` canonicalizes to __usp_new —
|
|
7
|
+
* a fixed-shape object with closure methods over parallel key/value arrays
|
|
8
|
+
* (the promise-runtime object shape). WHATWG semantics: forgiving percent
|
|
9
|
+
* decode ('+' is space, malformed escapes pass through as literals — never a
|
|
10
|
+
* throw), application/x-www-form-urlencoded escaping on toString (space→'+',
|
|
11
|
+
* only [A-Za-z0-9*-._] bare, UTF-8 bytes %XX'd — jz's byte-strings make that
|
|
12
|
+
* exact). Divergences (documented): keys()/values()/entries() return arrays
|
|
13
|
+
* (iterable with for-of, not live iterators), sort() compares UTF-8 bytes
|
|
14
|
+
* (spec: UTF-16 units — differs only past ASCII), and the object is not
|
|
15
|
+
* itself iterable — iterate `.entries()`.
|
|
16
|
+
*
|
|
17
|
+
* @module jzify/webrt
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export const USP_RUNTIME = `
|
|
21
|
+
let __usp_hex = (c) => c >= 48 && c <= 57 ? c - 48 : c >= 65 && c <= 70 ? c - 55 : c >= 97 && c <= 102 ? c - 87 : -1
|
|
22
|
+
let __usp_lt = (a, b) => {
|
|
23
|
+
let n = a.length < b.length ? a.length : b.length
|
|
24
|
+
for (let i = 0; i < n; i++) {
|
|
25
|
+
let x = a.charCodeAt(i)
|
|
26
|
+
let y = b.charCodeAt(i)
|
|
27
|
+
if (x !== y) return x < y
|
|
28
|
+
}
|
|
29
|
+
return a.length < b.length
|
|
30
|
+
}
|
|
31
|
+
let __usp_dec = (s) => {
|
|
32
|
+
let n = s.length
|
|
33
|
+
let buf = new Uint8Array(n)
|
|
34
|
+
let j = 0
|
|
35
|
+
for (let i = 0; i < n; i++) {
|
|
36
|
+
let c = s.charCodeAt(i)
|
|
37
|
+
if (c === 43) { buf[j] = 32; j = j + 1 }
|
|
38
|
+
else if (c === 37 && i + 2 < n) {
|
|
39
|
+
let h = __usp_hex(s.charCodeAt(i + 1))
|
|
40
|
+
let l = __usp_hex(s.charCodeAt(i + 2))
|
|
41
|
+
if (h >= 0 && l >= 0) { buf[j] = h * 16 + l; j = j + 1; i = i + 2 }
|
|
42
|
+
else { buf[j] = c; j = j + 1 }
|
|
43
|
+
} else { buf[j] = c; j = j + 1 }
|
|
44
|
+
}
|
|
45
|
+
return new TextDecoder().decode(buf.slice(0, j))
|
|
46
|
+
}
|
|
47
|
+
let __usp_esc = (s) => {
|
|
48
|
+
let out = ''
|
|
49
|
+
let n = s.length
|
|
50
|
+
for (let i = 0; i < n; i++) {
|
|
51
|
+
let c = s.charCodeAt(i)
|
|
52
|
+
if (c === 32) out = out + '+'
|
|
53
|
+
else if ((c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c === 42 || c === 45 || c === 46 || c === 95) out = out + s[i]
|
|
54
|
+
else {
|
|
55
|
+
let h = c >> 4
|
|
56
|
+
let l = c & 15
|
|
57
|
+
out = out + '%' + String.fromCharCode(h < 10 ? 48 + h : 55 + h) + String.fromCharCode(l < 10 ? 48 + l : 55 + l)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return out
|
|
61
|
+
}
|
|
62
|
+
let __usp_new = (init) => {
|
|
63
|
+
let ks = []
|
|
64
|
+
let vs = []
|
|
65
|
+
let self = { __usp: 1, size: 0, get: undefined, getAll: undefined, has: undefined, append: undefined, set: undefined, delete: undefined, forEach: undefined, keys: undefined, values: undefined, entries: undefined, sort: undefined, toString: undefined }
|
|
66
|
+
let find = (k) => { for (let i = 0; i < ks.length; i++) { if (ks[i] === k) return i } return -1 }
|
|
67
|
+
self.get = (k) => { let i = find(String(k)); return i < 0 ? null : vs[i] }
|
|
68
|
+
self.getAll = (k) => { let key = String(k); let out = []; for (let i = 0; i < ks.length; i++) { if (ks[i] === key) out.push(vs[i]) } return out }
|
|
69
|
+
self.has = (k, v) => { let key = String(k); for (let i = 0; i < ks.length; i++) { if (ks[i] === key && (v === undefined || vs[i] === String(v))) return true } return false }
|
|
70
|
+
self.append = (k, v) => { ks.push(String(k)); vs.push(String(v)); self.size = ks.length; return undefined }
|
|
71
|
+
self.set = (k, v) => {
|
|
72
|
+
let key = String(k)
|
|
73
|
+
let i = find(key)
|
|
74
|
+
if (i < 0) { self.append(key, v); return undefined }
|
|
75
|
+
vs[i] = String(v)
|
|
76
|
+
let w = i + 1
|
|
77
|
+
for (let r = i + 1; r < ks.length; r++) {
|
|
78
|
+
if (ks[r] !== key) { ks[w] = ks[r]; vs[w] = vs[r]; w = w + 1 }
|
|
79
|
+
}
|
|
80
|
+
ks.splice(w)
|
|
81
|
+
vs.splice(w)
|
|
82
|
+
self.size = ks.length
|
|
83
|
+
return undefined
|
|
84
|
+
}
|
|
85
|
+
self.delete = (k, v) => {
|
|
86
|
+
let key = String(k)
|
|
87
|
+
let w = 0
|
|
88
|
+
for (let r = 0; r < ks.length; r++) {
|
|
89
|
+
if (!(ks[r] === key && (v === undefined || vs[r] === String(v)))) { ks[w] = ks[r]; vs[w] = vs[r]; w = w + 1 }
|
|
90
|
+
}
|
|
91
|
+
ks.splice(w)
|
|
92
|
+
vs.splice(w)
|
|
93
|
+
self.size = ks.length
|
|
94
|
+
return undefined
|
|
95
|
+
}
|
|
96
|
+
self.forEach = (fn) => { for (let i = 0; i < ks.length; i++) fn(vs[i], ks[i], self); return undefined }
|
|
97
|
+
self.keys = () => ks.slice()
|
|
98
|
+
self.values = () => vs.slice()
|
|
99
|
+
self.entries = () => { let out = []; for (let i = 0; i < ks.length; i++) out.push([ks[i], vs[i]]); return out }
|
|
100
|
+
self.sort = () => {
|
|
101
|
+
for (let i = 1; i < ks.length; i++) {
|
|
102
|
+
let k = ks[i]
|
|
103
|
+
let v = vs[i]
|
|
104
|
+
let j = i - 1
|
|
105
|
+
while (j >= 0 && __usp_lt(k, ks[j])) { ks[j + 1] = ks[j]; vs[j + 1] = vs[j]; j = j - 1 }
|
|
106
|
+
ks[j + 1] = k
|
|
107
|
+
vs[j + 1] = v
|
|
108
|
+
}
|
|
109
|
+
return undefined
|
|
110
|
+
}
|
|
111
|
+
self.toString = () => {
|
|
112
|
+
let out = ''
|
|
113
|
+
for (let i = 0; i < ks.length; i++) {
|
|
114
|
+
if (i > 0) out = out + '&'
|
|
115
|
+
out = out + __usp_esc(ks[i]) + '=' + __usp_esc(vs[i])
|
|
116
|
+
}
|
|
117
|
+
return out
|
|
118
|
+
}
|
|
119
|
+
if (init != null) {
|
|
120
|
+
if (typeof init === 'string') {
|
|
121
|
+
let s = init
|
|
122
|
+
if (s[0] === '?') s = s.slice(1)
|
|
123
|
+
if (s.length > 0) {
|
|
124
|
+
let parts = s.split('&')
|
|
125
|
+
for (let pi = 0; pi < parts.length; pi++) {
|
|
126
|
+
let part = parts[pi]
|
|
127
|
+
if (part.length > 0) {
|
|
128
|
+
let eq = part.indexOf('=')
|
|
129
|
+
if (eq < 0) self.append(__usp_dec(part), '')
|
|
130
|
+
else self.append(__usp_dec(part.slice(0, eq)), __usp_dec(part.slice(eq + 1)))
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} else if (Array.isArray(init)) {
|
|
135
|
+
for (let pi = 0; pi < init.length; pi++) { let pair = init[pi]; self.append(pair[0], pair[1]) }
|
|
136
|
+
} else if (init.__usp === 1) {
|
|
137
|
+
init.forEach((v, k) => self.append(k, v))
|
|
138
|
+
} else {
|
|
139
|
+
let es = Object.entries(init)
|
|
140
|
+
for (let ei = 0; ei < es.length; ei++) { let e = es[ei]; self.append(e[0], e[1]) }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return self
|
|
144
|
+
}
|
|
145
|
+
`
|
package/layout.js
CHANGED
|
@@ -80,14 +80,20 @@ export const ptrBits = (type, aux = 0, offset = 0) =>
|
|
|
80
80
|
// `module/typedarray` stdlib.
|
|
81
81
|
// =============================================================================
|
|
82
82
|
|
|
83
|
-
/** Base element-type codes for PTR.TYPED aux (0–7).
|
|
83
|
+
/** Base element-type codes for PTR.TYPED aux (0–7). Flag-sharing kinds ride a
|
|
84
|
+
* base code (which fixes stride/shift) plus a discriminator bit: BigInt ctors
|
|
85
|
+
* share 7 + BIGINT_FLAG, Float16Array shares 3 (u16 storage) + F16_FLAG,
|
|
86
|
+
* Uint8ClampedArray shares 1 (u8 storage) + CLAMPED_FLAG. */
|
|
84
87
|
export const TYPED_ELEM_CODE = {
|
|
85
88
|
Int8Array: 0, Uint8Array: 1, Int16Array: 2, Uint16Array: 3,
|
|
86
89
|
Int32Array: 4, Uint32Array: 5, Float32Array: 6, Float64Array: 7,
|
|
87
90
|
BigInt64Array: 7, BigUint64Array: 7,
|
|
91
|
+
Float16Array: 3, Uint8ClampedArray: 1,
|
|
88
92
|
}
|
|
89
93
|
export const TYPED_ELEM_VIEW_FLAG = 8
|
|
90
94
|
export const TYPED_ELEM_BIGINT_FLAG = 16
|
|
95
|
+
export const TYPED_ELEM_F16_FLAG = 32
|
|
96
|
+
export const TYPED_ELEM_CLAMPED_FLAG = 64
|
|
91
97
|
|
|
92
98
|
export const TYPED_ELEM_NAMES = ['Int8Array', 'Uint8Array', 'Int16Array', 'Uint16Array',
|
|
93
99
|
'Int32Array', 'Uint32Array', 'Float32Array', 'Float64Array']
|
|
@@ -97,7 +103,9 @@ export function encodeTypedElemAux(name, isView = false) {
|
|
|
97
103
|
const et = TYPED_ELEM_CODE[name]
|
|
98
104
|
if (et == null) return null
|
|
99
105
|
return et | (isView ? TYPED_ELEM_VIEW_FLAG : 0) |
|
|
100
|
-
(name === 'BigInt64Array' || name === 'BigUint64Array' ? TYPED_ELEM_BIGINT_FLAG : 0)
|
|
106
|
+
(name === 'BigInt64Array' || name === 'BigUint64Array' ? TYPED_ELEM_BIGINT_FLAG : 0) |
|
|
107
|
+
(name === 'Float16Array' ? TYPED_ELEM_F16_FLAG : 0) |
|
|
108
|
+
(name === 'Uint8ClampedArray' ? TYPED_ELEM_CLAMPED_FLAG : 0)
|
|
101
109
|
}
|
|
102
110
|
|
|
103
111
|
/** Encode a `typedElemCtor` string ('new.Int32Array' | 'new.Int32Array.view') to the 4-bit
|
|
@@ -116,7 +124,9 @@ export function typedElemAux(ctor) {
|
|
|
116
124
|
export function ctorFromElemAux(aux) {
|
|
117
125
|
if (aux == null) return null
|
|
118
126
|
const isView = (aux & 8) !== 0
|
|
119
|
-
const name = (aux &
|
|
127
|
+
const name = (aux & TYPED_ELEM_F16_FLAG) !== 0 ? 'Float16Array'
|
|
128
|
+
: (aux & TYPED_ELEM_CLAMPED_FLAG) !== 0 ? 'Uint8ClampedArray'
|
|
129
|
+
: (aux & 16) !== 0 ? 'BigInt64Array' : TYPED_ELEM_NAMES[aux & 7]
|
|
120
130
|
if (!name) return null
|
|
121
131
|
return isView ? `new.${name}.view` : `new.${name}`
|
|
122
132
|
}
|
package/module/array.js
CHANGED
|
@@ -13,7 +13,7 @@ import { inBoundsArrIdx } from '../src/type.js'
|
|
|
13
13
|
import { emit, spread, deps, idx as emitIndex } from '../src/bridge.js'
|
|
14
14
|
import { valTypeOf } from '../src/kind.js'
|
|
15
15
|
import { extractParams, classifyParam, ASSIGN_OPS, refsName, REFS_IN_EXPR } from '../src/ast.js'
|
|
16
|
-
import { staticPropertyKey, staticObjectProps, inlineArraySid, staticIndexKey, intLiteralValue } from '../src/static.js'
|
|
16
|
+
import { staticPropertyKey, staticObjectProps, inlineArraySid, staticIndexKey, intLiteralValue, structLiteralFields } from '../src/static.js'
|
|
17
17
|
import { VAL, lookupValType, lookupNotString, updateRep } from '../src/reps.js'
|
|
18
18
|
import { structInline } from '../src/abi/index.js'
|
|
19
19
|
import { ctx, inc, err, warnDeopt, PTR, LAYOUT, followForwardingWat } from '../src/ctx.js'
|
|
@@ -62,25 +62,6 @@ function hoistArrayValue(arr) {
|
|
|
62
62
|
|
|
63
63
|
const arrayLenFromPtr = ptr => ['i32.load', ['i32.sub', ['local.get', `$${ptr}`], ['i32.const', 8]]]
|
|
64
64
|
|
|
65
|
-
/** K schema-ordered field-value AST nodes of an object literal `{S}` for a
|
|
66
|
-
* structInline `.push({S})`, or null if `lit` is not a plain static-key `{}`
|
|
67
|
-
* literal carrying exactly schema `sid`'s fields. Mapped by name into schema
|
|
68
|
-
* order so push sites with differing key order flatten to the same cell run. */
|
|
69
|
-
function structLiteralFields(lit, sid) {
|
|
70
|
-
if (!Array.isArray(lit) || lit[0] !== '{}') return null
|
|
71
|
-
const parsed = staticObjectProps(lit.slice(1))
|
|
72
|
-
const schema = ctx.schema.list[sid]
|
|
73
|
-
if (!parsed || parsed.names.length !== schema.length) return null
|
|
74
|
-
const byName = new Map()
|
|
75
|
-
for (let i = 0; i < parsed.names.length; i++) byName.set(parsed.names[i], parsed.values[i])
|
|
76
|
-
const out = []
|
|
77
|
-
for (const name of schema) {
|
|
78
|
-
if (!byName.has(name)) return null
|
|
79
|
-
out.push(byName.get(name))
|
|
80
|
-
}
|
|
81
|
-
return out
|
|
82
|
-
}
|
|
83
|
-
|
|
84
65
|
// Pure-expression check: no statements, binders, control flow, or assignments.
|
|
85
66
|
// Inlining is only safe for these — anything else needs the full closure machinery.
|
|
86
67
|
const NOT_PURE_OPS = new Set([
|
|
@@ -854,11 +835,16 @@ export default (ctx) => {
|
|
|
854
835
|
if (inlSid != null) {
|
|
855
836
|
const baseI32 = tempI32('ab')
|
|
856
837
|
const K = ctx.schema.list[inlSid].length
|
|
857
|
-
const
|
|
838
|
+
const packed = ctx.schema.inlineCellI32?.has(inlSid)
|
|
839
|
+
const cell = typed(structInline(K, packed).ops.elemAddr(
|
|
858
840
|
['local.tee', `$${baseI32}`, arrBase()],
|
|
859
841
|
vi), 'i32')
|
|
860
842
|
cell.ptrKind = VAL.OBJECT
|
|
861
843
|
cell.ptrAux = inlSid
|
|
844
|
+
// Packed i32 cells: slot access through this node (and through cursor
|
|
845
|
+
// locals bound to it — readVar re-derives via inlineCellCursors) must
|
|
846
|
+
// pick the packedI32 ops.
|
|
847
|
+
if (packed) cell.cellI32 = true
|
|
862
848
|
return cell
|
|
863
849
|
}
|
|
864
850
|
// Known-ARRAY → __arr_idx (single forwarding follow + inline bounds check),
|
|
@@ -1010,14 +996,18 @@ export default (ctx) => {
|
|
|
1010
996
|
// statement-position hint now so a dropped `xs.push(v)` can skip computing
|
|
1011
997
|
// the JS return length while still performing the mutation/writeback.
|
|
1012
998
|
const void_ = ctx.func._expect === 'void'
|
|
1013
|
-
// structInline Array<S>: `.push({S})` writes the K schema fields as
|
|
1014
|
-
// consecutive
|
|
999
|
+
// structInline Array<S>: `.push({S})` writes the K schema fields as
|
|
1000
|
+
// consecutive cells. Flatten the struct literal into K schema-ordered
|
|
1015
1001
|
// field-value nodes and fall through to the general multi-value store path
|
|
1016
|
-
// — `len`/`cap` count physical cells, so `__arr_grow_known` and the
|
|
1017
|
-
// loop are reused untouched; `.push` then returns the logical element
|
|
1018
|
-
// (`len /
|
|
1002
|
+
// — `len`/`cap` count physical 8-byte cells, so `__arr_grow_known` and the
|
|
1003
|
+
// cell loop are reused untouched; `.push` then returns the logical element
|
|
1004
|
+
// count (`len / cellsPerElem`). K=1 stays a single value → the
|
|
1005
|
+
// `__arr_push1` fast path. Packed schemas (inlineCellI32) store K raw i32
|
|
1006
|
+
// fields into ⌈K/2⌉ cells — the store loop below branches per layout.
|
|
1019
1007
|
const inlSid = inlineArraySid(arr)
|
|
1020
1008
|
const inlK = inlSid != null ? ctx.schema.list[inlSid].length : 0
|
|
1009
|
+
const inlPacked = inlSid != null && ctx.schema.inlineCellI32?.has(inlSid)
|
|
1010
|
+
const inlCpe = inlSid != null ? structInline(inlK, inlPacked).cpe : 1
|
|
1021
1011
|
if (inlSid != null) {
|
|
1022
1012
|
const flat = []
|
|
1023
1013
|
for (const v of vals) {
|
|
@@ -1027,6 +1017,8 @@ export default (ctx) => {
|
|
|
1027
1017
|
}
|
|
1028
1018
|
vals = flat
|
|
1029
1019
|
}
|
|
1020
|
+
// Physical cells this push appends (grow target + len increment basis).
|
|
1021
|
+
const pushCells = inlPacked ? inlCpe * (vals.length / inlK) : vals.length
|
|
1030
1022
|
// Out-of-line fast path: single value, named known-ARRAY receiver. One call +
|
|
1031
1023
|
// var update instead of ~30 inlined instructions — the dominant size cost of
|
|
1032
1024
|
// push-heavy code (e.g. watr's WASM emitter).
|
|
@@ -1071,31 +1063,45 @@ export default (ctx) => {
|
|
|
1071
1063
|
['if',
|
|
1072
1064
|
['i32.lt_s',
|
|
1073
1065
|
['i32.load', ['i32.sub', ['local.get', `$${pushBase}`], ['i32.const', 4]]],
|
|
1074
|
-
['i32.add', ['local.get', `$${len}`], ['i32.const',
|
|
1066
|
+
['i32.add', ['local.get', `$${len}`], ['i32.const', pushCells]]],
|
|
1075
1067
|
['then',
|
|
1076
1068
|
['local.set', `$${t}`, ['call', `$${grow}`, ['i64.reinterpret_f64', ['local.get', `$${t}`]],
|
|
1077
|
-
['i32.add', ['local.get', `$${len}`], ['i32.const',
|
|
1069
|
+
['i32.add', ['local.get', `$${len}`], ['i32.const', pushCells]]]],
|
|
1078
1070
|
['local.set', `$${pushBase}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]]]],
|
|
1079
1071
|
)
|
|
1080
1072
|
} else {
|
|
1081
1073
|
body.push(
|
|
1082
1074
|
['local.set', `$${len}`, ['call', '$__len', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
1083
|
-
// Grow if needed: ensure cap >= len +
|
|
1075
|
+
// Grow if needed: ensure cap >= len + pushCells
|
|
1084
1076
|
['local.set', `$${t}`, ['call', `$${grow}`, ['i64.reinterpret_f64', ['local.get', `$${t}`]],
|
|
1085
|
-
['i32.add', ['local.get', `$${len}`], ['i32.const',
|
|
1077
|
+
['i32.add', ['local.get', `$${len}`], ['i32.const', pushCells]]]],
|
|
1086
1078
|
['local.set', `$${pushBase}`, ['call', '$__ptr_offset', ['i64.reinterpret_f64', ['local.get', `$${t}`]]]],
|
|
1087
1079
|
)
|
|
1088
1080
|
}
|
|
1089
1081
|
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1082
|
+
if (inlPacked) {
|
|
1083
|
+
// Packed i32 cells: per element, K raw i32 stores at `base + len*8 + j*4`
|
|
1084
|
+
// (schema order — structLiteralFields already ordered the flatten), then
|
|
1085
|
+
// len advances by ⌈K/2⌉ physical cells. Values are exact by the
|
|
1086
|
+
// slotI32Certain census (packing precondition).
|
|
1087
|
+
for (let e = 0; e < vals.length; e += inlK) {
|
|
1088
|
+
for (let j = 0; j < inlK; j++) {
|
|
1089
|
+
const off = ['i32.add', ['local.get', `$${pushBase}`], ['i32.shl', ['local.get', `$${len}`], ['i32.const', 3]]]
|
|
1090
|
+
body.push(['i32.store', j === 0 ? off : ['i32.add', off, ['i32.const', j * 4]], asI32(emit(vals[e + j]))])
|
|
1091
|
+
}
|
|
1092
|
+
body.push(['local.set', `$${len}`, ['i32.add', ['local.get', `$${len}`], ['i32.const', inlCpe]]])
|
|
1093
|
+
}
|
|
1094
|
+
} else {
|
|
1095
|
+
// Store each value and increment len
|
|
1096
|
+
for (const val of vals) {
|
|
1097
|
+
const vv = carrierF64(val, emit(val))
|
|
1098
|
+
body.push(
|
|
1099
|
+
['f64.store',
|
|
1100
|
+
['i32.add', ['local.get', `$${pushBase}`], ['i32.shl', ['local.get', `$${len}`], ['i32.const', 3]]],
|
|
1101
|
+
vv],
|
|
1102
|
+
['local.set', `$${len}`, ['i32.add', ['local.get', `$${len}`], ['i32.const', 1]]]
|
|
1103
|
+
)
|
|
1104
|
+
}
|
|
1099
1105
|
}
|
|
1100
1106
|
|
|
1101
1107
|
// Update length header (write directly via the offset we already hold —
|
|
@@ -1113,9 +1119,10 @@ export default (ctx) => {
|
|
|
1113
1119
|
body.push(['local.set', `$${arr}`, ['local.get', `$${t}`]])
|
|
1114
1120
|
}
|
|
1115
1121
|
// structInline: `len` counts physical cells — `.push` returns the JS array
|
|
1116
|
-
// length, i.e. the logical element count `len /
|
|
1117
|
-
|
|
1118
|
-
|
|
1122
|
+
// length, i.e. the logical element count `len / cellsPerElem`.
|
|
1123
|
+
const lenDiv = inlPacked ? inlCpe : inlK
|
|
1124
|
+
body.push(['f64.convert_i32_s', lenDiv > 1
|
|
1125
|
+
? ['i32.div_s', ['local.get', `$${len}`], ['i32.const', lenDiv]]
|
|
1119
1126
|
: ['local.get', `$${len}`]])
|
|
1120
1127
|
|
|
1121
1128
|
return typed(['block', ['result', 'f64'], ...body], 'f64')
|