jz 0.2.1 → 0.3.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/src/ast.js ADDED
@@ -0,0 +1,160 @@
1
+ /**
2
+ * AST analysis predicates — pure functions over jz AST arrays.
3
+ *
4
+ * Extracted from emit.js to reduce the God File and enable reuse.
5
+ * These functions walk the jz AST (array-of-atoms format) and answer
6
+ * structural questions without emitting any IR.
7
+ *
8
+ * # Convention
9
+ * AST nodes are either:
10
+ * - strings (identifiers / bare names)
11
+ * - numbers (numeric literals)
12
+ * - arrays where [0] is the operator tag: ['+', a, b], ['let', ...], ['=>', params, body], etc.
13
+ * - [null, value] for parenthesized/boxed literals
14
+ *
15
+ * @module ast
16
+ */
17
+
18
+ import { ASSIGN_OPS, intLiteralValue } from './analyze.js'
19
+ import { ctx } from './ctx.js'
20
+
21
+ /** Detect whether `name` is written to (=, +=, ++, --, etc.) anywhere within `body`.
22
+ * Conservative over-reject: if unsure, treat as written.
23
+ * `let`/`const` declarations are NOT reassignments — only the initializer expressions
24
+ * inside them are scanned. */
25
+ export function isReassigned(body, name) {
26
+ if (!Array.isArray(body)) return false
27
+ const op = body[0]
28
+ if (ASSIGN_OPS.has(op) && body[1] === name) return true
29
+ if ((op === '++' || op === '--') && body[1] === name) return true
30
+ if (op === 'let' || op === 'const') {
31
+ for (let i = 1; i < body.length; i++) {
32
+ const d = body[i]
33
+ if (Array.isArray(d) && d[0] === '=' && d[2] != null && isReassigned(d[2], name)) return true
34
+ }
35
+ return false
36
+ }
37
+ for (let i = 1; i < body.length; i++) if (isReassigned(body[i], name)) return true
38
+ return false
39
+ }
40
+
41
+ /** Does `body` contain a `continue` that targets THIS loop?
42
+ * A `continue` inside a nested `for`/`while`/`do` targets the inner loop, so we don't count it. */
43
+ export function hasOwnContinue(body) {
44
+ if (!Array.isArray(body)) return false
45
+ const op = body[0]
46
+ if (op === 'continue') return true
47
+ if (op === 'for' || op === 'while' || op === 'do') return false
48
+ for (let i = 1; i < body.length; i++) if (hasOwnContinue(body[i])) return true
49
+ return false
50
+ }
51
+
52
+ export function hasOwnBreakOrContinue(body) {
53
+ if (!Array.isArray(body)) return false
54
+ const op = body[0]
55
+ if (op === 'break' || op === 'continue') return true
56
+ if (op === 'for' || op === 'while' || op === 'do' || op === '=>') return false
57
+ for (let i = 1; i < body.length; i++) if (hasOwnBreakOrContinue(body[i])) return true
58
+ return false
59
+ }
60
+
61
+ export function containsNestedClosure(body) {
62
+ if (!Array.isArray(body)) return false
63
+ if (body[0] === '=>') return true
64
+ for (let i = 1; i < body.length; i++) if (containsNestedClosure(body[i])) return true
65
+ return false
66
+ }
67
+
68
+ export function containsNestedLoop(body) {
69
+ if (!Array.isArray(body)) return false
70
+ const op = body[0]
71
+ if (op === 'for' || op === 'while' || op === 'do') return true
72
+ if (op === '=>') return false
73
+ for (let i = 1; i < body.length; i++) if (containsNestedLoop(body[i])) return true
74
+ return false
75
+ }
76
+
77
+ /** Recursive loop size estimator — product of trip counts for nested `for (let i=0; i<N; i++)` loops. */
78
+ export function nestedSmallLoopBudget(body) {
79
+ if (!Array.isArray(body)) return 1
80
+ if (body[0] === '=>') return 1
81
+ if (body[0] === 'for') {
82
+ const [, init, cond, step, loopBody] = body
83
+ const n = smallConstForTripCount(init, cond, step)
84
+ return n == null ? MAX_NESTED_FOR_UNROLL + 1 : n * nestedSmallLoopBudget(loopBody)
85
+ }
86
+ let max = 1
87
+ for (let i = 1; i < body.length; i++) max = Math.max(max, nestedSmallLoopBudget(body[i]))
88
+ return max
89
+ }
90
+
91
+ export function containsDeclOf(body, name) {
92
+ if (!Array.isArray(body)) return false
93
+ const op = body[0]
94
+ if (op === '=>') return false
95
+ if (op === 'let' || op === 'const') {
96
+ for (let i = 1; i < body.length; i++) {
97
+ const d = body[i]
98
+ if (d === name) return true
99
+ if (Array.isArray(d) && d[0] === '=' && d[1] === name) return true
100
+ }
101
+ }
102
+ for (let i = 1; i < body.length; i++) if (containsDeclOf(body[i], name)) return true
103
+ return false
104
+ }
105
+
106
+ /** Clone AST node, substituting bare-name matches with [null, value]. Skips into closures. */
107
+ export function cloneWithSubst(node, name, value) {
108
+ if (node === name) return [null, value]
109
+ if (!Array.isArray(node)) return node
110
+ if (node[0] === '=>') return node
111
+ return node.map(x => cloneWithSubst(x, name, value))
112
+ }
113
+
114
+ export const MAX_SMALL_FOR_UNROLL = 8
115
+ export const MAX_NESTED_FOR_UNROLL = 64
116
+
117
+ /** Does `body` access a typed-array element by string name known to the type system? */
118
+ export function containsKnownTypedArrayIndex(body) {
119
+ if (!Array.isArray(body)) return false
120
+ if (body[0] === '=>') return false
121
+ if (body[0] === '[]' && typeof body[1] === 'string' && ctx.types.typedElem?.has(body[1])) return true
122
+ for (let i = 1; i < body.length; i++) if (containsKnownTypedArrayIndex(body[i])) return true
123
+ return false
124
+ }
125
+
126
+ /** Analyze `for (let i=0; i<N; i++)` trip count. Returns N if structurally matches, else null. */
127
+ export function smallConstForTripCount(init, cond, step) {
128
+ if (!Array.isArray(init) || init[0] !== 'let' || init.length !== 2) return null
129
+ const decl = init[1]
130
+ if (!Array.isArray(decl) || decl[0] !== '=' || typeof decl[1] !== 'string') return null
131
+ const name = decl[1]
132
+ const start = intLiteralValue(decl[2])
133
+ if (start !== 0) return null
134
+
135
+ if (!Array.isArray(cond) || cond[0] !== '<' || cond[1] !== name) return null
136
+ const end = intLiteralValue(cond[2])
137
+ if (end == null || end < 0 || end > MAX_SMALL_FOR_UNROLL) return null
138
+
139
+ const stepOk = Array.isArray(step) && (
140
+ (step[0] === '++' && step[1] === name) ||
141
+ (step[0] === '-' && Array.isArray(step[1]) && step[1][0] === '++' && step[1][1] === name && intLiteralValue(step[2]) === 1)
142
+ )
143
+ return stepOk ? end : null
144
+ }
145
+
146
+ /** Does `body` always exit the enclosing scope (return / throw / break / continue)? */
147
+ export function isTerminator(body) {
148
+ if (!Array.isArray(body)) return false
149
+ const op = body[0]
150
+ if (op === 'return' || op === 'throw' || op === 'break' || op === 'continue') return true
151
+ if (op === '{}' || op === ';') {
152
+ for (let i = body.length - 1; i >= 1; i--) {
153
+ const s = body[i]
154
+ if (s == null) continue
155
+ return isTerminator(s)
156
+ }
157
+ return false
158
+ }
159
+ return false
160
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Auto-detect optimization tuning from source characteristics.
3
+ *
4
+ * Scans the prepared AST + ctx.func.list to infer program properties
5
+ * and returns suggested optimization overrides. When the user does not
6
+ * explicitly configure individual passes, these suggestions are merged
7
+ * in before resolveOptimize() so the compiler self-tunes.
8
+ *
9
+ * @module auto-config
10
+ */
11
+
12
+ import { ctx } from './ctx.js'
13
+
14
+ const LOOP_OPS = new Set(['for', 'while', 'do', 'do-while'])
15
+ const TYPED_CTORS = new Set([
16
+ 'new.Float32Array', 'new.Float64Array', 'new.Int8Array', 'new.Int16Array',
17
+ 'new.Int32Array', 'new.Uint8Array', 'new.Uint16Array', 'new.Uint32Array',
18
+ 'new.Uint8ClampedArray',
19
+ ])
20
+
21
+ function nodeSize(node) {
22
+ if (!Array.isArray(node)) return 1
23
+ let n = 1
24
+ for (let i = 1; i < node.length; i++) n += nodeSize(node[i])
25
+ return n
26
+ }
27
+
28
+ function scanNode(node, stats, loopDepth) {
29
+ if (!Array.isArray(node)) return
30
+ const op = node[0]
31
+
32
+ if (LOOP_OPS.has(op)) {
33
+ stats.loopCount++
34
+ const d = loopDepth + 1
35
+ if (d > stats.maxLoopDepth) stats.maxLoopDepth = d
36
+ for (let i = 1; i < node.length; i++) scanNode(node[i], stats, d)
37
+ return
38
+ }
39
+
40
+ if (op === '()') {
41
+ stats.callSites++
42
+ const callee = node[1]
43
+ if (typeof callee === 'string' && TYPED_CTORS.has(callee)) {
44
+ stats.typedArrayCount++
45
+ const args = node[2]
46
+ const argList = args == null ? [] : (Array.isArray(args) && args[0] === ',') ? args.slice(1) : [args]
47
+ const lenLit = typeof argList[0] === 'number' ? argList[0] : null
48
+ if (lenLit != null && lenLit > stats.maxTypedArrayLen) stats.maxTypedArrayLen = lenLit
49
+ }
50
+ }
51
+
52
+ if (op === 'str') stats.stringLiteralCount++
53
+ if (op === '=>') stats.closureCount++
54
+
55
+ for (let i = 1; i < node.length; i++) scanNode(node[i], stats, loopDepth)
56
+ }
57
+
58
+ function scanStats(ast, code) {
59
+ const stats = {
60
+ sourceChars: code?.length || 0,
61
+ funcCount: 0,
62
+ maxFuncBodySize: 0,
63
+ loopCount: 0,
64
+ maxLoopDepth: 0,
65
+ typedArrayCount: 0,
66
+ maxTypedArrayLen: 0,
67
+ stringLiteralCount: 0,
68
+ closureCount: 0,
69
+ callSites: 0,
70
+ }
71
+
72
+ if (ctx.func?.list) {
73
+ stats.funcCount = ctx.func.list.length
74
+ for (const f of ctx.func.list) {
75
+ if (f.body) {
76
+ const sz = nodeSize(f.body)
77
+ if (sz > stats.maxFuncBodySize) stats.maxFuncBodySize = sz
78
+ scanNode(f.body, stats, 0)
79
+ }
80
+ }
81
+ }
82
+ if (ast) scanNode(ast, stats, 0)
83
+ return stats
84
+ }
85
+
86
+ /**
87
+ * Detect optimization config from source characteristics.
88
+ * Returns an object of pass overrides; empty object means "use defaults".
89
+ */
90
+ export function detectOptimizeConfig(ast, code) {
91
+ const s = scanStats(ast, code)
92
+ const cfg = {}
93
+
94
+ // Machine-generated or large code: watr's WAT-level CSE/DCE/inline fights
95
+ // jz's already-optimized IR and inflates output. Disable it automatically.
96
+ const isLarge = s.sourceChars > 4000 || s.funcCount > 40 || s.maxFuncBodySize > 300
97
+ const isMachineLike = s.callSites > 300 && s.stringLiteralCount < 10
98
+ if (isLarge || isMachineLike) cfg.watr = false
99
+
100
+ // Typed-array heavy: tighten scalarization thresholds when we see large
101
+ // fixed-size arrays; keep defaults for small/dynamic ones.
102
+ if (s.typedArrayCount > 0 && s.maxTypedArrayLen > 0) {
103
+ cfg.scalarTypedArrayLen = Math.min(32, Math.max(8, s.maxTypedArrayLen + 4))
104
+ cfg.scalarTypedLoopUnroll = s.maxLoopDepth > 1 ? 8 : 16
105
+ cfg.scalarTypedNestedUnroll = s.maxLoopDepth > 1 ? 32 : 128
106
+ }
107
+
108
+ // String-heavy: ensure pool sorting is on (already default, but explicit).
109
+ if (s.stringLiteralCount > 30) {
110
+ cfg.sortStrPoolByFreq = true
111
+ }
112
+
113
+ // Closure-heavy: ptr hoists pay off.
114
+ if (s.closureCount > 4) {
115
+ cfg.hoistPtrType = true
116
+ cfg.hoistInvariantPtrOffset = true
117
+ }
118
+
119
+ return cfg
120
+ }
package/src/autoload.js CHANGED
@@ -66,7 +66,9 @@ export const CALL_MODULES = dict({
66
66
  'Object.entries': ['core', 'object', 'string'],
67
67
  'Object.assign': ['core', 'object'],
68
68
  'Object.create': ['core', 'object'],
69
+ 'Object.defineProperty': ['core', 'object'],
69
70
  'Date.UTC': ['core', 'date'],
71
+ 'Date.parse': ['core', 'date'],
70
72
  'Date.now': ['core', 'console'],
71
73
  'performance.now': ['core', 'console'],
72
74
  'readStdin': ['core', 'console'],
@@ -162,13 +164,14 @@ export const includeForProperty = prop => {
162
164
  }
163
165
 
164
166
  export const runtimeCtorKind = name =>
165
- TYPED_CTORS.includes(name) ? 'typedarray' : COLLECTION_CTORS.includes(name) ? 'collection' : name === 'Date' ? 'date' : null
167
+ TYPED_CTORS.includes(name) ? 'typedarray' : COLLECTION_CTORS.includes(name) ? 'collection' : name === 'Date' ? 'date' : name === 'Array' ? 'array' : null
166
168
 
167
169
  export const includeForRuntimeCtor = name => {
168
170
  const kind = runtimeCtorKind(name)
169
171
  if (kind === 'typedarray') includeMods('core', 'typedarray')
170
172
  else if (kind === 'collection') includeMods('core', 'collection')
171
173
  else if (kind === 'date') includeMods('core', 'console', 'date')
174
+ else if (kind === 'array') includeMods('core', 'array')
172
175
  return kind
173
176
  }
174
177