jz 0.2.0 → 0.2.1
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 +2 -3
- package/index.js +0 -3
- package/module/date.js +99 -7
- package/module/string.js +5 -0
- package/package.json +1 -1
- package/src/analyze.js +2 -2
- package/src/autoload.js +2 -2
- package/src/emit.js +24 -0
- package/src/ir.js +6 -0
- package/src/plan.js +38 -3
- package/src/prepare.js +1 -0
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ JZ distills the modern functional core – the "good parts" ([Crockford](https:/
|
|
|
32
32
|
| Parsing / transforms | Async / I/O-heavy logic |
|
|
33
33
|
| WASM utilities | JavaScript runtime |
|
|
34
34
|
|
|
35
|
-
Inspired by [porffor](https://github.com/CanadaHonk/porffor) and [piezo](https://github.com/dy/piezo).
|
|
35
|
+
<!-- Inspired by [porffor](https://github.com/CanadaHonk/porffor) and [piezo](https://github.com/dy/piezo). -->
|
|
36
36
|
<!-- Used internally by: web-audio-api, color-space, audiojs -->
|
|
37
37
|
|
|
38
38
|
|
|
@@ -66,7 +66,7 @@ Options are passed as `jz(source, opts)` or `compile(source, opts)`. Common ones
|
|
|
66
66
|
| `jzify: true` | Accept broader JS patterns such as `var`, `function`, `switch`, `arguments`, `==`, and `undefined` by lowering them to the JZ subset. |
|
|
67
67
|
| `modules: { specifier: source }` | Bundle static ES imports into one WASM module. CLI import resolution does this from files automatically. |
|
|
68
68
|
| `imports: { mod: host }` | Wire host namespaces/functions used by `import { fn } from "mod"`; functions may be plain JS functions or `{ fn, returns }` specs. |
|
|
69
|
-
| `memory` | Pass `memory: N` to create owned memory with `N` initial pages, or pass `memory: jz.memory()` / `WebAssembly.Memory` to share memory across modules.
|
|
69
|
+
| `memory` | Pass `memory: N` to create owned memory with `N` initial pages, or pass `memory: jz.memory()` / `WebAssembly.Memory` to share memory across modules. |
|
|
70
70
|
| `host: 'js' \| 'wasi'` | Select runtime-service lowering. Default `js` uses small `env.*` imports auto-wired by `jz()`; `wasi` emits WASI Preview 1 imports for wasmtime/wasmer/deno. |
|
|
71
71
|
| `optimize` | `false`/`0` disables optimization, `1` keeps cheap size passes, `true`/`2` is the default, `3` enables aggressive experimental passes, object form overrides individual passes. |
|
|
72
72
|
| `strict: true` | Reject dynamic fallbacks such as unknown receiver method calls, `obj[k]`, and `for-in` instead of emitting JS-host dynamic dispatch. |
|
|
@@ -495,7 +495,6 @@ cc program.c -o program
|
|
|
495
495
|
| [poly](bench/poly/poly.js) | 0.27ms<br>1.4kB | 1.62ms<br>1014 B | fails | 0.73ms<br>1.3kB | 0.81ms<br>359 B | 0.57ms | 0.79ms | 0.89ms | 0.63ms | 0.60ms |
|
|
496
496
|
| [callback](bench/callback/callback.js) | 0.03ms<br>1.6kB | 0.69ms<br>828 B | fails | 1.04ms<br>1.9kB | 0.24ms<br>267 B | 0.08ms | 0.23ms | 0.01ms | 0.12ms | 1.78ms |
|
|
497
497
|
| [json](bench/json/json.js) | 0.24ms<br>11.0kB | 0.37ms<br>1.2kB | fails | — | — | 0.03ms | 1.05ms | <0.01ms | 0.03ms | 1.21ms |
|
|
498
|
-
| [json-dynamic](bench/json-dynamic/json-dynamic.js) | 0.24ms<br>11.3kB | 0.39ms<br>1.2kB | — | — | — | — | — | — | — | — |
|
|
499
498
|
| [watr](bench/watr/watr.js) | 1.04ms<br>169.8kB | 1.05ms<br>2.6kB | fails | — | — | — | — | — | — | — |
|
|
500
499
|
|
|
501
500
|
_Numbers from `node bench/bench.mjs` on Apple Silicon. Porffor cells were refreshed with `porf` 0.61.13; `fails` means the latest Porffor compiler/runtime did not complete that benchmark._
|
package/index.js
CHANGED
|
@@ -163,8 +163,6 @@ jz.memory = enhanceMemory
|
|
|
163
163
|
* receiver method calls) at compile time. Avoids pulling dynamic-dispatch stdlib
|
|
164
164
|
* into output; large size win for static programs.
|
|
165
165
|
* @param {WebAssembly.Memory|number} [opts.memory] - Shared memory import, or
|
|
166
|
-
* initial page count for owned memory when a number is passed.
|
|
167
|
-
* @param {number} [opts.memoryPages] - Legacy alias for owned/imported memory
|
|
168
166
|
* initial page count. Prefer `memory: N` for owned memory.
|
|
169
167
|
* @param {boolean} [opts.alloc=true] - Export raw allocator helpers
|
|
170
168
|
* (`_alloc`, `_clear`) for JS memory wrapping. Set false for standalone host-run
|
|
@@ -194,7 +192,6 @@ jz.compile = (code, opts = {}) => {
|
|
|
194
192
|
|
|
195
193
|
if (typeof opts.memory === 'number') ctx.memory.pages = opts.memory
|
|
196
194
|
else if (opts.memory) ctx.memory.shared = true
|
|
197
|
-
if (opts.memoryPages) ctx.memory.pages = opts.memoryPages
|
|
198
195
|
if (opts.modules) ctx.module.importSources = opts.modules
|
|
199
196
|
if (opts.imports) {
|
|
200
197
|
ctx.module.hostImports = opts.imports
|
package/module/date.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Date module — deterministic UTC algorithms first.
|
|
3
3
|
*
|
|
4
|
-
* Current scope: Date.UTC(...), Date object construction,
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Current scope: Date.UTC(...), Date object construction, timestamp/string
|
|
5
|
+
* construction, UTC getters/setters, UTC-backed local getters, toISOString,
|
|
6
|
+
* toUTCString. Timezone-aware local-time methods and locale-sensitive formatting
|
|
7
|
+
* are deliberately staged later.
|
|
7
8
|
*
|
|
8
9
|
* @module date
|
|
9
10
|
*/
|
|
@@ -23,6 +24,9 @@ export default (ctx) => {
|
|
|
23
24
|
__date_make_time: [],
|
|
24
25
|
__date_time_clip: [],
|
|
25
26
|
__date_utc: ['__date_make_day', '__date_make_time', '__date_time_clip'],
|
|
27
|
+
__date_digit: ['__char_at'],
|
|
28
|
+
__date_parse_iso_date: ['__str_byteLen', '__char_at', '__date_digit', '__date_make_day', '__date_time_clip'],
|
|
29
|
+
__date_from_value: ['__ptr_type', '__to_num', '__date_parse_iso_date'],
|
|
26
30
|
__date_day: [],
|
|
27
31
|
__date_time_within_day: ['__date_day'],
|
|
28
32
|
__date_weekday: ['__date_day'],
|
|
@@ -149,6 +153,66 @@ export default (ctx) => {
|
|
|
149
153
|
(f64.const ${MS_PER_DAY}))
|
|
150
154
|
(call $__date_make_time (local.get $hours) (local.get $minutes) (local.get $seconds) (local.get $ms)))))`
|
|
151
155
|
|
|
156
|
+
ctx.core.stdlib['__date_digit'] = `(func $__date_digit (param $str i64) (param $i i32) (result i32)
|
|
157
|
+
(local $c i32)
|
|
158
|
+
(local.set $c (call $__char_at (local.get $str) (local.get $i)))
|
|
159
|
+
(if (result i32)
|
|
160
|
+
(i32.and (i32.ge_s (local.get $c) (i32.const 48)) (i32.le_s (local.get $c) (i32.const 57)))
|
|
161
|
+
(then (i32.sub (local.get $c) (i32.const 48)))
|
|
162
|
+
(else (i32.const -1))))`
|
|
163
|
+
|
|
164
|
+
ctx.core.stdlib['__date_parse_iso_date'] = `(func $__date_parse_iso_date (param $str i64) (result f64)
|
|
165
|
+
(local $y f64) (local $m f64) (local $d f64)
|
|
166
|
+
(local $d0 i32) (local $d1 i32) (local $d2 i32) (local $d3 i32)
|
|
167
|
+
(if (i32.lt_s (call $__str_byteLen (local.get $str)) (i32.const 10))
|
|
168
|
+
(then (return (f64.const nan))))
|
|
169
|
+
(if (i32.ne (call $__char_at (local.get $str) (i32.const 4)) (i32.const 45))
|
|
170
|
+
(then (return (f64.const nan))))
|
|
171
|
+
(if (i32.ne (call $__char_at (local.get $str) (i32.const 7)) (i32.const 45))
|
|
172
|
+
(then (return (f64.const nan))))
|
|
173
|
+
|
|
174
|
+
(local.set $d0 (call $__date_digit (local.get $str) (i32.const 0)))
|
|
175
|
+
(local.set $d1 (call $__date_digit (local.get $str) (i32.const 1)))
|
|
176
|
+
(local.set $d2 (call $__date_digit (local.get $str) (i32.const 2)))
|
|
177
|
+
(local.set $d3 (call $__date_digit (local.get $str) (i32.const 3)))
|
|
178
|
+
(if (i32.or (i32.or (i32.lt_s (local.get $d0) (i32.const 0)) (i32.lt_s (local.get $d1) (i32.const 0)))
|
|
179
|
+
(i32.or (i32.lt_s (local.get $d2) (i32.const 0)) (i32.lt_s (local.get $d3) (i32.const 0))))
|
|
180
|
+
(then (return (f64.const nan))))
|
|
181
|
+
(local.set $y (f64.convert_i32_s
|
|
182
|
+
(i32.add
|
|
183
|
+
(i32.add (i32.mul (local.get $d0) (i32.const 1000)) (i32.mul (local.get $d1) (i32.const 100)))
|
|
184
|
+
(i32.add (i32.mul (local.get $d2) (i32.const 10)) (local.get $d3)))))
|
|
185
|
+
|
|
186
|
+
(local.set $d0 (call $__date_digit (local.get $str) (i32.const 5)))
|
|
187
|
+
(local.set $d1 (call $__date_digit (local.get $str) (i32.const 6)))
|
|
188
|
+
(if (i32.or (i32.lt_s (local.get $d0) (i32.const 0)) (i32.lt_s (local.get $d1) (i32.const 0)))
|
|
189
|
+
(then (return (f64.const nan))))
|
|
190
|
+
(local.set $m (f64.convert_i32_s (i32.add (i32.mul (local.get $d0) (i32.const 10)) (local.get $d1))))
|
|
191
|
+
(if (i32.or (f64.lt (local.get $m) (f64.const 1)) (f64.gt (local.get $m) (f64.const 12)))
|
|
192
|
+
(then (return (f64.const nan))))
|
|
193
|
+
|
|
194
|
+
(local.set $d0 (call $__date_digit (local.get $str) (i32.const 8)))
|
|
195
|
+
(local.set $d1 (call $__date_digit (local.get $str) (i32.const 9)))
|
|
196
|
+
(if (i32.or (i32.lt_s (local.get $d0) (i32.const 0)) (i32.lt_s (local.get $d1) (i32.const 0)))
|
|
197
|
+
(then (return (f64.const nan))))
|
|
198
|
+
(local.set $d (f64.convert_i32_s (i32.add (i32.mul (local.get $d0) (i32.const 10)) (local.get $d1))))
|
|
199
|
+
(if (i32.or (f64.lt (local.get $d) (f64.const 1)) (f64.gt (local.get $d) (f64.const 31)))
|
|
200
|
+
(then (return (f64.const nan))))
|
|
201
|
+
|
|
202
|
+
(call $__date_time_clip
|
|
203
|
+
(f64.mul
|
|
204
|
+
(call $__date_make_day (local.get $y) (f64.sub (local.get $m) (f64.const 1)) (local.get $d))
|
|
205
|
+
(f64.const ${MS_PER_DAY}))))`
|
|
206
|
+
|
|
207
|
+
ctx.core.stdlib['__date_from_value'] = `(func $__date_from_value (param $v i64) (result f64)
|
|
208
|
+
(local $f f64)
|
|
209
|
+
(local.set $f (f64.reinterpret_i64 (local.get $v)))
|
|
210
|
+
(if (f64.eq (local.get $f) (local.get $f))
|
|
211
|
+
(then (return (call $__date_time_clip (local.get $f)))))
|
|
212
|
+
(if (i32.eq (call $__ptr_type (local.get $v)) (i32.const ${PTR.STRING}))
|
|
213
|
+
(then (return (call $__date_parse_iso_date (local.get $v)))))
|
|
214
|
+
(call $__date_time_clip (call $__to_num (local.get $v))))`
|
|
215
|
+
|
|
152
216
|
// ── Time decomposition (ECMA-262 §21.4.1) ────────────────────────────────
|
|
153
217
|
|
|
154
218
|
ctx.core.stdlib['__date_day'] = `(func $__date_day (param $t f64) (result f64)
|
|
@@ -513,13 +577,26 @@ export default (ctx) => {
|
|
|
513
577
|
// Represented as PTR.OBJECT with a single f64 slot at offset 0 (the time value).
|
|
514
578
|
// No schemaId (aux=0); dynamic property access falls through to undefined.
|
|
515
579
|
|
|
516
|
-
ctx.core.emit['new.Date'] = (ms) => {
|
|
580
|
+
ctx.core.emit['new.Date'] = (ms, month, date, hours, minutes, seconds, millis) => {
|
|
517
581
|
let timeVal
|
|
518
582
|
if (ms === undefined || ms === null) {
|
|
519
|
-
|
|
583
|
+
const now = ctx.core.emit['Date.now']
|
|
584
|
+
if (!now) throw new Error('Date constructor requires Date.now support')
|
|
585
|
+
timeVal = now()
|
|
586
|
+
} else if (!missingArg(month)) {
|
|
587
|
+
inc('__date_utc')
|
|
588
|
+
timeVal = typed(['call', '$__date_utc',
|
|
589
|
+
asF64(dateArg(ms, NaN, true)),
|
|
590
|
+
asF64(dateArg(month, 0)),
|
|
591
|
+
asF64(dateArg(date, 1)),
|
|
592
|
+
asF64(dateArg(hours, 0)),
|
|
593
|
+
asF64(dateArg(minutes, 0)),
|
|
594
|
+
asF64(dateArg(seconds, 0)),
|
|
595
|
+
asF64(dateArg(millis, 0)),
|
|
596
|
+
], 'f64')
|
|
520
597
|
} else {
|
|
521
|
-
inc('
|
|
522
|
-
timeVal = typed(['call', '$
|
|
598
|
+
inc('__date_from_value')
|
|
599
|
+
timeVal = typed(['call', '$__date_from_value', ['i64.reinterpret_f64', asF64(emit(ms))]], 'f64')
|
|
523
600
|
}
|
|
524
601
|
const out = allocPtr({ type: PTR.OBJECT, len: 1, cap: 1, stride: 8, tag: 'date' })
|
|
525
602
|
return typed(['block', ['result', 'f64'],
|
|
@@ -585,6 +662,21 @@ export default (ctx) => {
|
|
|
585
662
|
ctx.core.emit['.getUTCMilliseconds'] = dateGetter('__date_ms_from_time')
|
|
586
663
|
ctx.core.emit[`.${VAL.DATE}:getUTCMilliseconds`] = dateGetter('__date_ms_from_time')
|
|
587
664
|
|
|
665
|
+
// UTC-backed local getters. Full timezone-aware local time is intentionally
|
|
666
|
+
// staged separately; these aliases make deterministic/server Date use cases
|
|
667
|
+
// available without a timezone database.
|
|
668
|
+
ctx.core.emit['.getFullYear'] = ctx.core.emit['.getUTCFullYear']
|
|
669
|
+
ctx.core.emit[`.${VAL.DATE}:getFullYear`] = ctx.core.emit[`.${VAL.DATE}:getUTCFullYear`]
|
|
670
|
+
|
|
671
|
+
ctx.core.emit['.getMonth'] = ctx.core.emit['.getUTCMonth']
|
|
672
|
+
ctx.core.emit[`.${VAL.DATE}:getMonth`] = ctx.core.emit[`.${VAL.DATE}:getUTCMonth`]
|
|
673
|
+
|
|
674
|
+
ctx.core.emit['.getDate'] = ctx.core.emit['.getUTCDate']
|
|
675
|
+
ctx.core.emit[`.${VAL.DATE}:getDate`] = ctx.core.emit[`.${VAL.DATE}:getUTCDate`]
|
|
676
|
+
|
|
677
|
+
ctx.core.emit['.getDay'] = ctx.core.emit['.getUTCDay']
|
|
678
|
+
ctx.core.emit[`.${VAL.DATE}:getDay`] = ctx.core.emit[`.${VAL.DATE}:getUTCDay`]
|
|
679
|
+
|
|
588
680
|
// ── UTC setter emit handlers ──────────────────────────────────────────────
|
|
589
681
|
|
|
590
682
|
const emitDatePtr = (dateExpr) =>
|
package/module/string.js
CHANGED
|
@@ -959,6 +959,11 @@ export default (ctx) => {
|
|
|
959
959
|
return typed(['call', '$__str_case', asI64(emit(str)), ['i32.const', 65], ['i32.const', 90], ['i32.const', 32]], 'f64')
|
|
960
960
|
}
|
|
961
961
|
|
|
962
|
+
// Locale-specific casing needs ICU/CLDR data. jz intentionally has no
|
|
963
|
+
// runtime, so this follows the existing ASCII-only lowercase helper and
|
|
964
|
+
// ignores optional locale arguments.
|
|
965
|
+
ctx.core.emit['.toLocaleLowerCase'] = ctx.core.emit['.toLowerCase']
|
|
966
|
+
|
|
962
967
|
// Byte-wise variant of String.prototype.localeCompare. Returns -1/0/1 from
|
|
963
968
|
// an unsigned byte-by-byte compare with shorter-string-sorts-first tiebreak.
|
|
964
969
|
// NOT locale-aware: real localeCompare is ICU-driven (CLDR collation, case
|
package/package.json
CHANGED
package/src/analyze.js
CHANGED
|
@@ -359,7 +359,7 @@ export function valTypeOf(expr) {
|
|
|
359
359
|
if (method === 'add' || method === 'delete') return VAL.SET
|
|
360
360
|
if (method === 'set') return VAL.MAP
|
|
361
361
|
// String-returning methods
|
|
362
|
-
if (['toUpperCase', 'toLowerCase', 'trim', 'trimStart', 'trimEnd',
|
|
362
|
+
if (['toUpperCase', 'toLowerCase', 'toLocaleLowerCase', 'trim', 'trimStart', 'trimEnd',
|
|
363
363
|
'repeat', 'padStart', 'padEnd', 'replace', 'replaceAll', 'charAt', 'substring'].includes(method)) return VAL.STRING
|
|
364
364
|
if (method === 'split') return VAL.ARRAY
|
|
365
365
|
// slice/concat preserve caller type (string.slice → string, array.slice → array)
|
|
@@ -1263,7 +1263,7 @@ export function invalidateLocalsCache(body) {
|
|
|
1263
1263
|
// concat) that strings share with arrays/typed-arrays.
|
|
1264
1264
|
const STRING_ONLY_METHODS = new Set([
|
|
1265
1265
|
'charCodeAt', 'charAt', 'codePointAt', 'startsWith', 'endsWith',
|
|
1266
|
-
'toUpperCase', 'toLowerCase', 'normalize', 'localeCompare',
|
|
1266
|
+
'toUpperCase', 'toLowerCase', 'toLocaleLowerCase', 'normalize', 'localeCompare',
|
|
1267
1267
|
'padStart', 'padEnd', 'repeat', 'trimStart', 'trimEnd', 'trim',
|
|
1268
1268
|
'matchAll', 'match', 'replace', 'replaceAll', 'split',
|
|
1269
1269
|
])
|
package/src/autoload.js
CHANGED
|
@@ -14,7 +14,7 @@ export const PROP_MODULES = Object.assign(Object.create(null), {
|
|
|
14
14
|
every: ['core', 'array'], some: ['core', 'array'], flat: ['core', 'array'], flatMap: ['core', 'array'],
|
|
15
15
|
join: ['core', 'array'], copyWithin: ['core', 'array'], at: ['core', 'array'],
|
|
16
16
|
charAt: ['core', 'string'], charCodeAt: ['core', 'string'], codePointAt: ['core', 'string'],
|
|
17
|
-
toUpperCase: ['core', 'string'], toLowerCase: ['core', 'string'], trim: ['core', 'string'],
|
|
17
|
+
toUpperCase: ['core', 'string'], toLowerCase: ['core', 'string'], toLocaleLowerCase: ['core', 'string'], trim: ['core', 'string'],
|
|
18
18
|
trimStart: ['core', 'string'], trimEnd: ['core', 'string'],
|
|
19
19
|
split: ['core', 'string'], replace: ['core', 'string'], replaceAll: ['core', 'string'],
|
|
20
20
|
repeat: ['core', 'string'], startsWith: ['core', 'string'], endsWith: ['core', 'string'],
|
|
@@ -168,7 +168,7 @@ export const includeForRuntimeCtor = name => {
|
|
|
168
168
|
const kind = runtimeCtorKind(name)
|
|
169
169
|
if (kind === 'typedarray') includeMods('core', 'typedarray')
|
|
170
170
|
else if (kind === 'collection') includeMods('core', 'collection')
|
|
171
|
-
else if (kind === 'date') includeMods('core', 'date')
|
|
171
|
+
else if (kind === 'date') includeMods('core', 'console', 'date')
|
|
172
172
|
return kind
|
|
173
173
|
}
|
|
174
174
|
|
package/src/emit.js
CHANGED
|
@@ -867,6 +867,20 @@ const cmpOp = (i32op, f64op, fn) => (a, b) => {
|
|
|
867
867
|
inc('__str_cmp')
|
|
868
868
|
return typed([`i32.${i32op}`, ['call', '$__str_cmp', asI64(va), asI64(vb)], ['i32.const', 0]], 'i32')
|
|
869
869
|
}
|
|
870
|
+
if (vta === VAL.DATE || vtb === VAL.DATE) {
|
|
871
|
+
const dateNum = (node, v, vt) => {
|
|
872
|
+
if (vt !== VAL.DATE) return toNumF64(node, v)
|
|
873
|
+
const ptr = v.ptrKind === VAL.DATE
|
|
874
|
+
? v
|
|
875
|
+
: ['i32.wrap_i64', ['i64.reinterpret_f64', asF64(v)]]
|
|
876
|
+
return typed(['f64.load', ptr], 'f64')
|
|
877
|
+
}
|
|
878
|
+
return typed([`f64.${f64op}`, dateNum(a, va, vta), dateNum(b, vb, vtb)], 'i32')
|
|
879
|
+
}
|
|
880
|
+
if (vtb === VAL.NUMBER && needsRelationalToNumber(a, vta))
|
|
881
|
+
return typed([`f64.${f64op}`, toNumF64(a, va), asF64(vb)], 'i32')
|
|
882
|
+
if (vta === VAL.NUMBER && needsRelationalToNumber(b, vtb))
|
|
883
|
+
return typed([`f64.${f64op}`, asF64(va), toNumF64(b, vb)], 'i32')
|
|
870
884
|
const ai = intConstValue(a), bi = intConstValue(b)
|
|
871
885
|
if (va.type === 'i32' && bi != null) return typed([`i32.${i32op}`, va, ['i32.const', bi]], 'i32')
|
|
872
886
|
if (vb.type === 'i32' && ai != null) return typed([`i32.${i32op}`, ['i32.const', ai], vb], 'i32')
|
|
@@ -874,6 +888,16 @@ const cmpOp = (i32op, f64op, fn) => (a, b) => {
|
|
|
874
888
|
? typed([`i32.${i32op}`, va, vb], 'i32') : typed([`f64.${f64op}`, asF64(va), asF64(vb)], 'i32')
|
|
875
889
|
}
|
|
876
890
|
|
|
891
|
+
function needsRelationalToNumber(expr, vt) {
|
|
892
|
+
if (vt === VAL.STRING) return true
|
|
893
|
+
if (vt != null) return false
|
|
894
|
+
return mayReadBoxedValue(expr)
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function mayReadBoxedValue(expr) {
|
|
898
|
+
return Array.isArray(expr) && (expr[0] === '.' || expr[0] === '[]' || expr[0] === '?.' || expr[0] === '?.[]')
|
|
899
|
+
}
|
|
900
|
+
|
|
877
901
|
function intConstValue(expr) {
|
|
878
902
|
if (typeof expr === 'number' && Number.isInteger(expr)) return expr
|
|
879
903
|
if (Array.isArray(expr) && expr[0] == null && typeof expr[1] === 'number' && Number.isInteger(expr[1])) return expr[1]
|
package/src/ir.js
CHANGED
|
@@ -347,6 +347,12 @@ export function toNumF64(node, v) {
|
|
|
347
347
|
if (v.type === 'i32' || isLit(v)) return asF64(v)
|
|
348
348
|
const vt = keyValType(node)
|
|
349
349
|
if (vt === VAL.NUMBER || vt === VAL.BIGINT) return asF64(v)
|
|
350
|
+
if (vt === VAL.DATE) {
|
|
351
|
+
const ptr = v.ptrKind === VAL.DATE
|
|
352
|
+
? v
|
|
353
|
+
: ['i32.wrap_i64', ['i64.reinterpret_f64', asF64(v)]]
|
|
354
|
+
return typed(['f64.load', ptr], 'f64')
|
|
355
|
+
}
|
|
350
356
|
// intCertain locals: every reachable def is integer-valued, so the binding
|
|
351
357
|
// never carries a NaN-boxed pointer — skip the __to_num wrapper.
|
|
352
358
|
if (typeof node === 'string' && repOf(node)?.intCertain === true) return asF64(v)
|
package/src/plan.js
CHANGED
|
@@ -329,6 +329,24 @@ const collectScalarTypedArrayWrites = (node, name, len, out = new Set()) => {
|
|
|
329
329
|
return out
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
+
const hasScalarTypedArrayRead = (node, name) => {
|
|
333
|
+
if (!Array.isArray(node)) return false
|
|
334
|
+
const op = node[0]
|
|
335
|
+
const isTarget = target => Array.isArray(target) && target[0] === '[]' && target[1] === name
|
|
336
|
+
if ((op === '++' || op === '--') && isTarget(node[1])) return true
|
|
337
|
+
if (ASSIGN_TARGET_OPS.has(op)) {
|
|
338
|
+
if (isTarget(node[1])) {
|
|
339
|
+
if (op !== '=') return true
|
|
340
|
+
for (let i = 2; i < node.length; i++) if (hasScalarTypedArrayRead(node[i], name)) return true
|
|
341
|
+
return false
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (op === '[]' && node[1] === name) return true
|
|
345
|
+
if (op === '=>') return false
|
|
346
|
+
for (let i = 1; i < node.length; i++) if (hasScalarTypedArrayRead(node[i], name)) return true
|
|
347
|
+
return false
|
|
348
|
+
}
|
|
349
|
+
|
|
332
350
|
const scalarizeTypedArrayLiteralSeq = (seq) => {
|
|
333
351
|
if (!Array.isArray(seq) || seq[0] !== ';') return { node: seq, changed: false }
|
|
334
352
|
let changed = false
|
|
@@ -553,6 +571,7 @@ const scalarTypedParamCandidates = (func, sites, fixedByFunc) => {
|
|
|
553
571
|
|
|
554
572
|
const scalarizeTypedArrayParams = (func, paramCands) => {
|
|
555
573
|
for (const [name, c] of [...paramCands]) if (!safeScalarTypedArrayUse(func.body, name, c.len)) paramCands.delete(name)
|
|
574
|
+
for (const [name] of [...paramCands]) if (!hasScalarTypedArrayRead(func.body, name)) paramCands.delete(name)
|
|
556
575
|
if (!paramCands.size) return { body: func.body, changed: false }
|
|
557
576
|
const arrays = new Map()
|
|
558
577
|
for (const [name, c] of paramCands) {
|
|
@@ -920,6 +939,7 @@ const inlineHotInternalCalls = (programFacts, ast) => {
|
|
|
920
939
|
if (cfg && cfg.sourceInline === false) return false
|
|
921
940
|
|
|
922
941
|
const fixedByFunc = new Map(ctx.func.list.map(func => [func, fixedTypedArraysInBody(func.body)]))
|
|
942
|
+
const typedByFunc = new Map(ctx.func.list.map(func => [func, analyzeBody(func.body).typedElems]))
|
|
923
943
|
const sitesByCallee = new Map()
|
|
924
944
|
for (const cs of programFacts.callSites) {
|
|
925
945
|
const list = sitesByCallee.get(cs.callee)
|
|
@@ -942,8 +962,22 @@ const inlineHotInternalCalls = (programFacts, ast) => {
|
|
|
942
962
|
return typeof arg === 'string' && fixedByFunc.get(site.callerFunc)?.has(arg)
|
|
943
963
|
}))
|
|
944
964
|
}
|
|
945
|
-
const
|
|
946
|
-
|
|
965
|
+
const hasFullyFixedTypedArraySites = (func, sites) => {
|
|
966
|
+
const params = func.sig?.params || []
|
|
967
|
+
if (!sites?.length) return false
|
|
968
|
+
let sawTypedArg = false
|
|
969
|
+
for (const site of sites) {
|
|
970
|
+
const typed = typedByFunc.get(site.callerFunc)
|
|
971
|
+
const fixed = fixedByFunc.get(site.callerFunc)
|
|
972
|
+
for (let i = 0; i < params.length; i++) {
|
|
973
|
+
const arg = site.argList[i]
|
|
974
|
+
if (typeof arg !== 'string' || !typed?.has(arg)) continue
|
|
975
|
+
sawTypedArg = true
|
|
976
|
+
if (!fixed?.has(arg)) return false
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return sawTypedArg
|
|
980
|
+
}
|
|
947
981
|
|
|
948
982
|
const candidates = new Map()
|
|
949
983
|
for (const func of ctx.func.list) {
|
|
@@ -951,6 +985,7 @@ const inlineHotInternalCalls = (programFacts, ast) => {
|
|
|
951
985
|
if (func.defaults && Object.keys(func.defaults).length) continue
|
|
952
986
|
const sites = sitesByCallee.get(func.name)
|
|
953
987
|
const fixedTypedArraySite = hasFixedTypedArraySites(func, sites)
|
|
988
|
+
const fullyFixedTypedArraySite = hasFullyFixedTypedArraySites(func, sites)
|
|
954
989
|
if (!sites || sites.length < 1 || (!fixedTypedArraySite && sites.length > 2) || sites.length > 8) continue
|
|
955
990
|
const stmts = blockStmts(func.body)
|
|
956
991
|
// Expression-bodied arrow funcs (`(c) => expr`) have no block — body IS the
|
|
@@ -980,7 +1015,7 @@ const inlineHotInternalCalls = (programFacts, ast) => {
|
|
|
980
1015
|
// loop carries most of the cost. Inlining them into a host that V8 can't
|
|
981
1016
|
// tier up (e.g. a once-called wrapper) freezes the kernel in baseline.
|
|
982
1017
|
// Keep them as standalone functions so V8 wasm tier-up can warm them.
|
|
983
|
-
if (loopDepth(func.body, 0) >= 2 && !
|
|
1018
|
+
if (loopDepth(func.body, 0) >= 2 && !fullyFixedTypedArraySite) continue
|
|
984
1019
|
// Factory functions that allocate pointers (`new TypedArray`, `new Array`,
|
|
985
1020
|
// object/array literals returned) break downstream pointer-ABI specialization
|
|
986
1021
|
// when inlined: narrow.js can't trace the post-inline alias chain back to a
|
package/src/prepare.js
CHANGED
|
@@ -1475,6 +1475,7 @@ const handlers = {
|
|
|
1475
1475
|
'new'(ctor, ...args) {
|
|
1476
1476
|
let name = ctor, ctorArgs = args
|
|
1477
1477
|
if (Array.isArray(ctor) && ctor[0] === '()') { name = ctor[1]; ctorArgs = ctor.slice(2) }
|
|
1478
|
+
if (name === 'Date' && ctorArgs.length === 1 && ctorArgs[0] == null) ctorArgs = []
|
|
1478
1479
|
// Flatten comma-grouped args: [',', a, b, c] → [a, b, c]
|
|
1479
1480
|
if (ctorArgs.length === 1 && Array.isArray(ctorArgs[0]) && ctorArgs[0][0] === ',')
|
|
1480
1481
|
ctorArgs = ctorArgs[0].slice(1)
|