jz 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -9
- package/bench/README.md +121 -50
- package/bench/bench.svg +27 -27
- package/cli.js +3 -1
- package/dist/interop.js +1 -1
- package/dist/jz.js +7541 -6271
- package/index.js +165 -74
- package/interop.js +189 -17
- package/jzify/arguments.js +32 -1
- package/jzify/async.js +409 -0
- package/jzify/classes.js +136 -18
- package/jzify/generators.js +639 -0
- package/jzify/hoist-vars.js +73 -1
- package/jzify/index.js +123 -4
- package/jzify/names.js +1 -0
- package/jzify/transform.js +239 -1
- package/layout.js +48 -3
- package/module/array.js +299 -44
- package/module/atomics.js +144 -0
- package/module/collection.js +1429 -155
- package/module/core.js +431 -40
- package/module/date.js +142 -120
- package/module/fs.js +144 -0
- package/module/function.js +6 -3
- package/module/index.js +4 -1
- package/module/json.js +270 -49
- package/module/math.js +892 -32
- package/module/number.js +532 -163
- package/module/object.js +353 -95
- package/module/regex.js +157 -7
- package/module/schema.js +100 -2
- package/module/string.js +301 -84
- package/module/typedarray.js +264 -72
- package/module/web.js +36 -0
- package/package.json +5 -5
- package/src/abi/string.js +71 -5
- package/src/ast.js +11 -5
- package/src/autoload.js +28 -1
- package/src/bridge.js +7 -2
- package/src/compile/analyze-scans.js +22 -7
- package/src/compile/analyze.js +136 -30
- package/src/compile/dyn-closure-tables.js +277 -0
- package/src/compile/emit-assign.js +281 -15
- package/src/compile/emit.js +979 -54
- package/src/compile/index.js +271 -29
- package/src/compile/infer.js +34 -3
- package/src/compile/inplace-store.js +329 -0
- package/src/compile/narrow.js +543 -15
- package/src/compile/peel-stencil.js +5 -0
- package/src/compile/plan/index.js +45 -3
- package/src/compile/plan/inline.js +8 -1
- package/src/compile/plan/literals.js +39 -0
- package/src/compile/plan/scope.js +430 -23
- package/src/compile/program-facts.js +732 -38
- package/src/ctx.js +64 -9
- package/src/helper-counters.js +7 -1
- package/src/ir.js +113 -5
- package/src/kind-traits.js +66 -3
- package/src/kind.js +84 -12
- package/src/op-policy.js +7 -4
- package/src/optimize/index.js +1060 -750
- package/src/optimize/recurse.js +2 -2
- package/src/optimize/vectorize.js +972 -67
- package/src/prepare/index.js +792 -63
- package/src/prepare/math-kernel.js +331 -0
- package/src/prepare/pre-eval.js +714 -0
- package/src/snapshot.js +194 -0
- package/src/type.js +1170 -56
- package/src/wat/assemble.js +403 -65
- package/src/wat/codegen.js +74 -14
- package/transform.js +113 -4
- package/wasi.js +3 -0
|
@@ -206,6 +206,11 @@ function tryPeel(stmt, cm) {
|
|
|
206
206
|
// for: append the step to each split loop's body; while: body already increments.
|
|
207
207
|
const mk = (B, bod) => ['while', ['<', iv, B], step ? [';', ...bod.slice(1), step] : bod]
|
|
208
208
|
const loops = [mk(xs, body), mk(xe, interiorBody), mk(bound, body)]
|
|
209
|
+
// Range theorem for the interval proof (typedIdxProven class 5): the peel's own
|
|
210
|
+
// bit-exactness argument — for interior iv, `ci = iv + k ∈ [0, bound-1]` WITHOUT
|
|
211
|
+
// the clamp. Stamped on the interior loop's FINAL body node (mk may rebuild it);
|
|
212
|
+
// the interval walk intersects ci's env writes with it while inside the subtree.
|
|
213
|
+
loops[1][2]._rangeFacts = [[clamp.ci, bound]]
|
|
209
214
|
return init ? [init, seed, ...loops] : [seed, ...loops]
|
|
210
215
|
}
|
|
211
216
|
|
|
@@ -25,17 +25,19 @@
|
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
import { ctx } from '../../ctx.js'
|
|
28
|
-
import {
|
|
28
|
+
import { invalidateLocalsCache } from '../analyze.js'
|
|
29
|
+
import { collectProgramFacts, refreshProgramFacts, analyzeSchemaSlotIntCertain, observeProgramSlots, analyzeParamNeverGrown } from '../program-facts.js'
|
|
29
30
|
import narrowSignatures, {
|
|
30
|
-
specializeBimorphicTyped, refineDynKeys,
|
|
31
|
+
specializeBimorphicTyped, speculateTypedParams, refineDynKeys,
|
|
31
32
|
applyJsstringBoundaryCarrierStandalone, narrowBoolResults,
|
|
32
33
|
strictBoundaryTypeCheck,
|
|
33
34
|
} from '../narrow.js'
|
|
34
35
|
|
|
35
36
|
import { optimizing } from './common.js'
|
|
36
37
|
import { adviseProgram } from './advise.js'
|
|
38
|
+
import { scanInplaceStores } from '../inplace-store.js'
|
|
37
39
|
import {
|
|
38
|
-
inferModuleLetTypes, unboxConstTypedGlobals, inferModuleIntGlobals,
|
|
40
|
+
inferModuleLetTypes, inferModuleGlobalValTypes, unboxConstTypedGlobals, inferModuleIntGlobals, refineFieldProvenance,
|
|
39
41
|
flattenFuncNamespaces, devirtGlobalCalls,
|
|
40
42
|
materializeAutoBoxSchemas, resolveClosureWidth, canSkipWholeProgramNarrowing,
|
|
41
43
|
} from './scope.js'
|
|
@@ -59,6 +61,10 @@ export default function plan(ast, profiler) {
|
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
t('inferModuleLetTypes', () => inferModuleLetTypes(ast))
|
|
64
|
+
// Pass 1 (no call-site param facts yet): literal/alias/global-to-global
|
|
65
|
+
// evidence only. Early enough that a freshly-proven NUMBER global still
|
|
66
|
+
// reaches inferModuleIntGlobals's candidacy check below.
|
|
67
|
+
t('inferModuleGlobalValTypes', () => inferModuleGlobalValTypes(ast))
|
|
62
68
|
t('unboxConstTypedGlobals', unboxConstTypedGlobals)
|
|
63
69
|
t('inferModuleIntGlobals', () => inferModuleIntGlobals(ast))
|
|
64
70
|
|
|
@@ -97,6 +103,10 @@ export default function plan(ast, profiler) {
|
|
|
97
103
|
ctx.types.dynKeyVars = programFacts.dynVars
|
|
98
104
|
ctx.types.dynWriteVars = programFacts.dynWriteVars
|
|
99
105
|
ctx.types.anyDynKey = programFacts.anyDyn
|
|
106
|
+
ctx.types.literalWriteKeys = programFacts.literalWriteKeys
|
|
107
|
+
ctx.types.writtenProps = programFacts.writtenProps
|
|
108
|
+
ctx.types.arrResized = programFacts.arrResized
|
|
109
|
+
ctx.types.nameEscapes = programFacts.nameEscapes
|
|
100
110
|
|
|
101
111
|
t('materializeAutoBoxSchemas', () => materializeAutoBoxSchemas(programFacts))
|
|
102
112
|
t('resolveClosureWidth', () => resolveClosureWidth(programFacts))
|
|
@@ -113,11 +123,43 @@ export default function plan(ast, profiler) {
|
|
|
113
123
|
}
|
|
114
124
|
|
|
115
125
|
t('narrowSignatures', () => narrowSignatures(programFacts, ast))
|
|
126
|
+
// Pass 2: narrowSignatures has now settled `programFacts.paramReps`, so a
|
|
127
|
+
// global written from a bare parameter alias (`cur = s`, subscript's parse-
|
|
128
|
+
// state shape) resolves — pass 1 saw only an untyped param and poisoned it.
|
|
129
|
+
// Idempotent: names pass 1 already claimed are skipped.
|
|
130
|
+
t('inferModuleGlobalValTypes2', () => inferModuleGlobalValTypes(ast, programFacts.paramReps))
|
|
116
131
|
// After narrowSignatures (params now carry ptrKind): mark typed-array params that every call
|
|
117
132
|
// site passes a distinct fresh buffer for → enables alias-aware LICM in the optimizer.
|
|
118
133
|
if (optimizing()) t('analyzeParamDistinctness', () => analyzeParamDistinctness(programFacts))
|
|
134
|
+
// Slot-kind census REBUILD with post-narrowing receiver resolution: the early
|
|
135
|
+
// hazard scan can't type params (`re[j] = tr` on a then-unnarrowed TYPED param
|
|
136
|
+
// read as a world-poisoning keyed write), so recompute hazards with paramReps
|
|
137
|
+
// and rebuild slotTypes/slotTypedCtors fresh BEFORE their consumers below
|
|
138
|
+
// (inplace sweep, bimorphic split, typed-param speculation) and at emit.
|
|
139
|
+
t('refineSlotKindCensus', () => observeProgramSlots(ast, { fresh: true, paramReps: programFacts.paramReps }))
|
|
140
|
+
// Cross-function neverGrown for read-only array PARAMS (growth-free callee
|
|
141
|
+
// closure + safeReads) — the raw-base element read skips __ptr_offset.
|
|
142
|
+
if (optimizing()) t('analyzeParamNeverGrown', () => analyzeParamNeverGrown(programFacts.paramReps))
|
|
143
|
+
// Whole-program alias sweep for in-place replace-stores (`arr[i] = {lit}` →
|
|
144
|
+
// overwrite the old element's slots) — needs the settled arrayElemSchema
|
|
145
|
+
// facts, so it runs after the signature fixpoint.
|
|
146
|
+
if (optimizing()) t('scanInplaceStores', () => scanInplaceStores(programFacts))
|
|
119
147
|
t('specializeBimorphicTyped', () => specializeBimorphicTyped(programFacts))
|
|
148
|
+
if (optimizing()) t('speculateTypedParams', () => speculateTypedParams(programFacts, ast))
|
|
120
149
|
t('refineDynKeys', () => refineDynKeys(programFacts))
|
|
150
|
+
// Late: return sids (narrowSignatures) + the slot/write censuses are complete —
|
|
151
|
+
// bind module consts' schemas from returned objects, then re-run the module-let
|
|
152
|
+
// ctor fixpoint whose FIELD evidence (slotTypedCtorAt, write-gated) resolves
|
|
153
|
+
// only now. Upgrade-only: strictly more evidence than the early run.
|
|
154
|
+
t('refineFieldProvenance', () => refineFieldProvenance(ast))
|
|
155
|
+
t('refineModuleLetTypes', () => inferModuleLetTypes(ast))
|
|
156
|
+
// Late slot-int census: rebuild FRESH with body-local element-alias sids
|
|
157
|
+
// (`const p = ps[i]` through the param's arrayElemSchema — knowledge that
|
|
158
|
+
// exists only after narrowing). Consumers read at emit, after this.
|
|
159
|
+
t('refineSlotIntCensus', () => analyzeSchemaSlotIntCertain(ast, { paramReps: programFacts.paramReps }))
|
|
160
|
+
// The late upgrades land through analyzeBody's trackers — drop the cached
|
|
161
|
+
// walks so emit-time re-analysis sees the new field kinds.
|
|
162
|
+
for (const f of ctx.func.list) if (f.body && !f.raw) invalidateLocalsCache(f.body)
|
|
121
163
|
strictBoundaryTypeCheck(programFacts)
|
|
122
164
|
|
|
123
165
|
adviseProgram(programFacts)
|
|
@@ -465,7 +465,14 @@ export const inlineHotInternalCalls = (programFacts, ast) => {
|
|
|
465
465
|
// The 2-site non-tiny-leaf cap would otherwise outline a hot helper like noise's `grad`
|
|
466
466
|
// (~30 nodes, called 4× from perlin) and freeze the call overhead per pixel.
|
|
467
467
|
const isSmallLeaf = !hasLoop && nodeSize(func.body) <= 48
|
|
468
|
-
|
|
468
|
+
// Leaf site cap scales with body size — the cost of inlining N sites is
|
|
469
|
+
// N·size nodes, not N: a 30-node pure leaf hammered from 9 sites (colorpq's
|
|
470
|
+
// spow) is 270 spliced nodes, cheaper than 9 call frames per pixel, while a
|
|
471
|
+
// 48-node body keeps the old 8-site bound (360/48 → 8). Full inlining also
|
|
472
|
+
// restores shape identity for downstream CSE — a PARTIAL split (some sites
|
|
473
|
+
// inlined, some calls) makes duplicate pure subtrees structurally unequal.
|
|
474
|
+
const leafSiteCap = (isTinyLeaf || isSmallLeaf) ? Math.max(8, Math.floor(360 / Math.max(1, nodeSize(func.body)))) : 8
|
|
475
|
+
if (!sites || sites.length < 1 || (!isTinyLeaf && !isSmallLeaf && !fixedTypedArraySite && sites.length > 2) || sites.length > leafSiteCap) continue
|
|
469
476
|
const stmts = blockStmts(func.body)
|
|
470
477
|
// Expression-bodied arrow funcs (`(c) => expr`) have no block — body IS the
|
|
471
478
|
// return value. Treat as a "tiny leaf" branch handled below; force hasLoop=false.
|
|
@@ -947,6 +947,33 @@ export const scalarizeFunctionArrayLiterals = () => {
|
|
|
947
947
|
// re-dispatch via emit.js:2211's `.typed:<m>` lookup (VAL.TYPED ⇒ typed
|
|
948
948
|
// emitter). Methods missing here (.join, .sort, .reverse, .subarray, .fill,
|
|
949
949
|
// .toString, .copyWithin, …) lack a typed emitter — disqualify the candidate.
|
|
950
|
+
// Does an inline arrow callback provably yield a NUMBER for every element?
|
|
951
|
+
// Conservative: only shapes whose result is structurally numeric qualify —
|
|
952
|
+
// arithmetic/bitwise/compare ops, numeric literals, the element param itself,
|
|
953
|
+
// Math.* calls. A call to a user fn, an object/array/string literal, or any
|
|
954
|
+
// unknown shape returns false (the caller then keeps the plain-array rep).
|
|
955
|
+
const _NUM_OPS = new Set(['+', '-', '*', '/', '%', '**', '|', '&', '^', '<<', '>>', '>>>',
|
|
956
|
+
'<', '<=', '>', '>=', '==', '!=', '===', '!==', '!', '~', 'u+', 'u-'])
|
|
957
|
+
const _numericCallbackBody = (fn) => {
|
|
958
|
+
if (!Array.isArray(fn) || fn[0] !== '=>') return false
|
|
959
|
+
const params = new Set()
|
|
960
|
+
const raw = fn[1]
|
|
961
|
+
for (const p of Array.isArray(raw) && raw[0] === ',' ? raw.slice(1) : raw != null ? [raw] : [])
|
|
962
|
+
if (typeof p === 'string') params.add(p)
|
|
963
|
+
const numeric = (n) => {
|
|
964
|
+
if (typeof n === 'number') return true
|
|
965
|
+
if (typeof n === 'string') return params.has(n) // element/index param — numeric under a promoted receiver
|
|
966
|
+
if (!Array.isArray(n)) return false
|
|
967
|
+
if (n[0] == null) return typeof n[1] === 'number' // number node — wrapper is null OR undefined in the live AST
|
|
968
|
+
if (_NUM_OPS.has(n[0])) return n.slice(1).every(a => a == null || numeric(a))
|
|
969
|
+
if (n[0] === '()' && typeof n[1] === 'string' && n[1].startsWith('Math.')) return true
|
|
970
|
+
if (n[0] === '?' && n.length === 4) return numeric(n[2]) && numeric(n[3])
|
|
971
|
+
return false
|
|
972
|
+
}
|
|
973
|
+
// expression body only; block bodies ({…return…}) stay conservative
|
|
974
|
+
return numeric(fn[2])
|
|
975
|
+
}
|
|
976
|
+
|
|
950
977
|
const _TYPED_SAFE_METHODS = new Set([
|
|
951
978
|
'set',
|
|
952
979
|
'map', 'filter', 'slice',
|
|
@@ -1059,11 +1086,23 @@ const _disqualifyPromotion = (node, candidates, disqualified, initSet, valTypes)
|
|
|
1059
1086
|
if (Array.isArray(callee) && (callee[0] === '.' || callee[0] === '?.') &&
|
|
1060
1087
|
typeof callee[1] === 'string' && candidates.has(callee[1])) {
|
|
1061
1088
|
if (!_TYPED_SAFE_METHODS.has(callee[2])) disqualified.add(callee[1])
|
|
1089
|
+
// `.map` is the one whitelisted method that writes CALLBACK RESULTS into the
|
|
1090
|
+
// typed output: `.typed:map` ToNumber-coerces them (spec for a REAL typed
|
|
1091
|
+
// receiver), but a plain array's map must return the callback's values
|
|
1092
|
+
// verbatim — `[10,20].map(s => mk(s))` yields objects. Promote across map
|
|
1093
|
+
// only when the callback provably yields numbers; anything else (object/
|
|
1094
|
+
// string/unknown call results) keeps the plain-array representation.
|
|
1095
|
+
else if (callee[2] === 'map' && !_numericCallbackBody(node[2])) disqualified.add(callee[1])
|
|
1062
1096
|
// Walk method args (skip the receiver — already validated above).
|
|
1063
1097
|
for (let i = 2; i < node.length; i++) _disqualifyPromotion(node[i], candidates, disqualified, initSet, valTypes)
|
|
1064
1098
|
return
|
|
1065
1099
|
}
|
|
1066
1100
|
// Array.isArray flips true→false under promotion.
|
|
1101
|
+
// KNOWN GAP (recorded in .work/todo.md, extension-surface archive): a DERIVED value of a
|
|
1102
|
+
// promoted array (`s = a.slice(0); Array.isArray(s)`) is not tracked here,
|
|
1103
|
+
// so promotion survives and isArray answers false at O2+. Fixing it needs
|
|
1104
|
+
// derived-name flow, not a blanket disqualifier (a blanket one regressed
|
|
1105
|
+
// the __to_num elision pin) — a focused optimizer-session item.
|
|
1067
1106
|
if (callee === 'Array.isArray') {
|
|
1068
1107
|
const raw = node[2]
|
|
1069
1108
|
const list = raw == null ? [] : (Array.isArray(raw) && raw[0] === ',') ? raw.slice(1) : [raw]
|