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.
Files changed (72) hide show
  1. package/README.md +56 -9
  2. package/bench/README.md +121 -50
  3. package/bench/bench.svg +27 -27
  4. package/cli.js +3 -1
  5. package/dist/interop.js +1 -1
  6. package/dist/jz.js +7541 -6271
  7. package/index.js +165 -74
  8. package/interop.js +189 -17
  9. package/jzify/arguments.js +32 -1
  10. package/jzify/async.js +409 -0
  11. package/jzify/classes.js +136 -18
  12. package/jzify/generators.js +639 -0
  13. package/jzify/hoist-vars.js +73 -1
  14. package/jzify/index.js +123 -4
  15. package/jzify/names.js +1 -0
  16. package/jzify/transform.js +239 -1
  17. package/layout.js +48 -3
  18. package/module/array.js +299 -44
  19. package/module/atomics.js +144 -0
  20. package/module/collection.js +1429 -155
  21. package/module/core.js +431 -40
  22. package/module/date.js +142 -120
  23. package/module/fs.js +144 -0
  24. package/module/function.js +6 -3
  25. package/module/index.js +4 -1
  26. package/module/json.js +270 -49
  27. package/module/math.js +892 -32
  28. package/module/number.js +532 -163
  29. package/module/object.js +353 -95
  30. package/module/regex.js +157 -7
  31. package/module/schema.js +100 -2
  32. package/module/string.js +301 -84
  33. package/module/typedarray.js +264 -72
  34. package/module/web.js +36 -0
  35. package/package.json +5 -5
  36. package/src/abi/string.js +71 -5
  37. package/src/ast.js +11 -5
  38. package/src/autoload.js +28 -1
  39. package/src/bridge.js +7 -2
  40. package/src/compile/analyze-scans.js +22 -7
  41. package/src/compile/analyze.js +136 -30
  42. package/src/compile/dyn-closure-tables.js +277 -0
  43. package/src/compile/emit-assign.js +281 -15
  44. package/src/compile/emit.js +979 -54
  45. package/src/compile/index.js +271 -29
  46. package/src/compile/infer.js +34 -3
  47. package/src/compile/inplace-store.js +329 -0
  48. package/src/compile/narrow.js +543 -15
  49. package/src/compile/peel-stencil.js +5 -0
  50. package/src/compile/plan/index.js +45 -3
  51. package/src/compile/plan/inline.js +8 -1
  52. package/src/compile/plan/literals.js +39 -0
  53. package/src/compile/plan/scope.js +430 -23
  54. package/src/compile/program-facts.js +732 -38
  55. package/src/ctx.js +64 -9
  56. package/src/helper-counters.js +7 -1
  57. package/src/ir.js +113 -5
  58. package/src/kind-traits.js +66 -3
  59. package/src/kind.js +84 -12
  60. package/src/op-policy.js +7 -4
  61. package/src/optimize/index.js +1060 -750
  62. package/src/optimize/recurse.js +2 -2
  63. package/src/optimize/vectorize.js +972 -67
  64. package/src/prepare/index.js +792 -63
  65. package/src/prepare/math-kernel.js +331 -0
  66. package/src/prepare/pre-eval.js +714 -0
  67. package/src/snapshot.js +194 -0
  68. package/src/type.js +1170 -56
  69. package/src/wat/assemble.js +403 -65
  70. package/src/wat/codegen.js +74 -14
  71. package/transform.js +113 -4
  72. 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 { collectProgramFacts, refreshProgramFacts } from '../program-facts.js'
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
- if (!sites || sites.length < 1 || (!isTinyLeaf && !isSmallLeaf && !fixedTypedArraySite && sites.length > 2) || sites.length > 8) continue
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]