watr 4.6.6 → 4.6.7

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/polyfill.js CHANGED
@@ -6,117 +6,7 @@
6
6
  */
7
7
 
8
8
  import parse from './parse.js'
9
-
10
- /** Features that can be polyfilled */
11
- const FEATURES = {
12
- funcref: ['ref.func', 'call_ref', 'return_call_ref'],
13
- sign_ext: ['i32.extend8_s', 'i32.extend16_s', 'i64.extend8_s', 'i64.extend16_s', 'i64.extend32_s'],
14
- nontrapping: ['i32.trunc_sat_f32_s', 'i32.trunc_sat_f32_u', 'i32.trunc_sat_f64_s', 'i32.trunc_sat_f64_u',
15
- 'i64.trunc_sat_f32_s', 'i64.trunc_sat_f32_u', 'i64.trunc_sat_f64_s', 'i64.trunc_sat_f64_u'],
16
- bulk_memory: ['memory.copy', 'memory.fill'],
17
- return_call: ['return_call', 'return_call_indirect'],
18
- i31ref: ['ref.i31', 'i31.get_s', 'i31.get_u'],
19
- extended_const: ['global.get'], // in const context - detected specially
20
- multi_value: [], // detected by result count
21
- gc: ['struct.new', 'struct.get', 'struct.set', 'array.new', 'array.get', 'array.set', 'array.len',
22
- 'struct.new_default', 'array.new_default', 'array.new_fixed', 'array.copy'],
23
- ref_cast: ['ref.test', 'ref.cast', 'br_on_cast', 'br_on_cast_fail'],
24
- }
25
-
26
- /** All feature names */
27
- const ALL = Object.keys(FEATURES)
28
-
29
- /**
30
- * Normalize polyfill options to { feature: bool } map.
31
- * @param {boolean|string|Object} opts
32
- * @returns {Object} Normalized options
33
- */
34
- const normalize = (opts) => {
35
- if (opts === true) return Object.fromEntries(ALL.map(f => [f, true]))
36
- if (opts === false) return {}
37
- if (typeof opts === 'string') {
38
- const set = new Set(opts.split(/\s+/).filter(Boolean))
39
- return Object.fromEntries(ALL.map(f => [f, set.has(f) || set.has('all')]))
40
- }
41
- return { ...opts }
42
- }
43
-
44
- /**
45
- * Walk AST depth-first (pre-order), call fn on each node.
46
- * @param {any} node
47
- * @param {Function} fn - (node, parent, idx) => void
48
- * @param {any} [parent]
49
- * @param {number} [idx]
50
- */
51
- const walk = (node, fn, parent, idx) => {
52
- fn(node, parent, idx)
53
- if (Array.isArray(node)) for (let i = 0; i < node.length; i++) walk(node[i], fn, node, i)
54
- }
55
-
56
- /**
57
- * Walk AST depth-first (post-order), transform children before parent.
58
- * @param {any} node
59
- * @param {Function} fn - (node, parent, idx) => void
60
- * @param {any} [parent]
61
- * @param {number} [idx]
62
- */
63
- const walkPost = (node, fn, parent, idx) => {
64
- if (Array.isArray(node)) for (let i = 0; i < node.length; i++) walkPost(node[i], fn, node, i)
65
- fn(node, parent, idx)
66
- }
67
-
68
- /**
69
- * Detect which polyfillable features are used in AST.
70
- * @param {Array} ast
71
- * @returns {Set<string>} Set of feature names
72
- */
73
- const detect = (ast) => {
74
- const used = new Set()
75
-
76
- // Standard op detection
77
- walk(ast, node => {
78
- if (typeof node !== 'string') return
79
- for (const [feat, ops] of Object.entries(FEATURES)) {
80
- if (ops.some(op => node === op || node.startsWith(op + ' '))) used.add(feat)
81
- }
82
- })
83
-
84
- // Special: extended_const - global.get in global initializer with arithmetic
85
- walk(ast, node => {
86
- if (!Array.isArray(node) || node[0] !== 'global') return
87
- for (const init of node) {
88
- if (!Array.isArray(init)) continue
89
- if (init[0] === 'i32.add' || init[0] === 'i32.sub' || init[0] === 'i32.mul' ||
90
- init[0] === 'i64.add' || init[0] === 'i64.sub' || init[0] === 'i64.mul') {
91
- // Check if it contains global.get
92
- walk(init, inner => {
93
- if (Array.isArray(inner) && inner[0] === 'global.get') used.add('extended_const')
94
- })
95
- }
96
- }
97
- })
98
-
99
- // Special: multi_value - functions with >1 result
100
- walk(ast, node => {
101
- if (!Array.isArray(node) || node[0] !== 'func') return
102
- let resultCount = 0
103
- for (const part of node) {
104
- if (Array.isArray(part) && part[0] === 'result') {
105
- resultCount += part.length - 1
106
- }
107
- }
108
- if (resultCount > 1) used.add('multi_value')
109
- })
110
-
111
- return used
112
- }
113
-
114
- /**
115
- * Deep clone AST to avoid mutating original.
116
- * @param {any} node
117
- * @returns {any}
118
- */
119
- const clone = (node) => Array.isArray(node) ? node.map(clone) : node
9
+ import { walk, walkPost, clone } from './util.js'
120
10
 
121
11
  /**
122
12
  * Find module-level nodes by kind (func, table, etc).
@@ -228,9 +118,6 @@ const funcref = (ast, ctx) => {
228
118
  return ast
229
119
  }
230
120
 
231
- /** Feature transforms */
232
- const transforms = { funcref }
233
-
234
121
  // ============================================================================
235
122
  // SIGN EXTENSION POLYFILL
236
123
  // Transforms sign extension ops to shift pairs (shift left then arithmetic shift right).
@@ -265,8 +152,6 @@ const sign_ext = (ast, ctx) => {
265
152
  return ast
266
153
  }
267
154
 
268
- transforms.sign_ext = sign_ext
269
-
270
155
  // ============================================================================
271
156
  // NON-TRAPPING CONVERSIONS POLYFILL
272
157
  // Transforms trunc_sat to conditional clamp with NaN/infinity handling.
@@ -344,8 +229,6 @@ const nontrapping = (ast, ctx) => {
344
229
  return ast
345
230
  }
346
231
 
347
- transforms.nontrapping = nontrapping
348
-
349
232
  // ============================================================================
350
233
  // BULK MEMORY POLYFILL
351
234
  // Transforms memory.copy/fill to loop-based implementations.
@@ -451,8 +334,6 @@ const bulk_memory = (ast, ctx) => {
451
334
  return ast
452
335
  }
453
336
 
454
- transforms.bulk_memory = bulk_memory
455
-
456
337
  // ============================================================================
457
338
  // TAIL CALL POLYFILL
458
339
  // Transforms return_call/return_call_indirect to trampoline pattern.
@@ -490,8 +371,6 @@ const return_call_transform = (ast, ctx) => {
490
371
  return ast
491
372
  }
492
373
 
493
- transforms.return_call = return_call_transform
494
-
495
374
  // ============================================================================
496
375
  // I31REF POLYFILL
497
376
  // Transforms i31ref to i32 with masking.
@@ -526,8 +405,6 @@ const i31ref = (ast, ctx) => {
526
405
  return ast
527
406
  }
528
407
 
529
- transforms.i31ref = i31ref
530
-
531
408
  // ============================================================================
532
409
  // EXTENDED CONST POLYFILL
533
410
  // Evaluates extended constant expressions at compile time.
@@ -619,8 +496,6 @@ const extended_const = (ast, ctx) => {
619
496
  return ast
620
497
  }
621
498
 
622
- transforms.extended_const = extended_const
623
-
624
499
  // ============================================================================
625
500
  // MULTI-VALUE POLYFILL
626
501
  // Transforms multi-value returns to single value + memory/global storage.
@@ -695,8 +570,6 @@ const multi_value = (ast, ctx) => {
695
570
  return ast
696
571
  }
697
572
 
698
- transforms.multi_value = multi_value
699
-
700
573
  // ============================================================================
701
574
  // GC (STRUCT/ARRAY) POLYFILL
702
575
  // Transforms GC types to linear memory with bump allocator.
@@ -1017,8 +890,6 @@ const gc = (ast, ctx) => {
1017
890
  return ast
1018
891
  }
1019
892
 
1020
- transforms.gc = gc
1021
-
1022
893
  // ============================================================================
1023
894
  // REF.TEST / REF.CAST POLYFILL
1024
895
  // Runtime type checking using type tags stored at offset 0.
@@ -1174,7 +1045,93 @@ const ref_cast = (ast, ctx) => {
1174
1045
  return ast
1175
1046
  }
1176
1047
 
1177
- transforms.ref_cast = ref_cast
1048
+ /**
1049
+ * Polyfillable features, in the order their transforms apply. Each entry is
1050
+ * `[name, triggerOps, transform]` — the single source of truth for detection,
1051
+ * the public `FEATURES` catalogue, and the dispatch loop below.
1052
+ * - name — feature key callers toggle (`polyfill: 'funcref gc'`)
1053
+ * - triggerOps — instruction names whose presence means the feature is used
1054
+ * - transform — the lowering pass, `(ast, ctx) => ast`
1055
+ */
1056
+ const POLYFILLS = [
1057
+ ['funcref', ['ref.func', 'call_ref', 'return_call_ref'], funcref],
1058
+ ['sign_ext', ['i32.extend8_s', 'i32.extend16_s', 'i64.extend8_s', 'i64.extend16_s', 'i64.extend32_s'], sign_ext],
1059
+ ['nontrapping', ['i32.trunc_sat_f32_s', 'i32.trunc_sat_f32_u', 'i32.trunc_sat_f64_s', 'i32.trunc_sat_f64_u',
1060
+ 'i64.trunc_sat_f32_s', 'i64.trunc_sat_f32_u', 'i64.trunc_sat_f64_s', 'i64.trunc_sat_f64_u'], nontrapping],
1061
+ ['bulk_memory', ['memory.copy', 'memory.fill'], bulk_memory],
1062
+ ['return_call', ['return_call', 'return_call_indirect'], return_call_transform],
1063
+ ['i31ref', ['ref.i31', 'i31.get_s', 'i31.get_u'], i31ref],
1064
+ ['extended_const', ['global.get'], extended_const], // global.get in a const initializer — also detected specially
1065
+ ['multi_value', [], multi_value], // functions with >1 result — detected by result count
1066
+ ['gc', ['struct.new', 'struct.get', 'struct.set', 'array.new', 'array.get', 'array.set', 'array.len',
1067
+ 'struct.new_default', 'array.new_default', 'array.new_fixed', 'array.copy'], gc],
1068
+ ['ref_cast', ['ref.test', 'ref.cast', 'br_on_cast', 'br_on_cast_fail'], ref_cast],
1069
+ ]
1070
+
1071
+ /** Feature name → trigger-op list — the public catalogue of polyfillable features. */
1072
+ const FEATURES = Object.fromEntries(POLYFILLS.map(p => [p[0], p[1]]))
1073
+
1074
+ /**
1075
+ * Normalize polyfill options to a { feature: bool } map. `true` enables every
1076
+ * feature, a string enables only the named ones (or all via `'all'`), and an
1077
+ * explicit object is passed through untouched.
1078
+ *
1079
+ * @param {boolean|string|Object} opts
1080
+ * @returns {Object} Normalized options
1081
+ */
1082
+ const normalize = (opts) => {
1083
+ if (opts === false) return {}
1084
+ if (opts !== true && typeof opts !== 'string') return { ...opts }
1085
+ const set = typeof opts === 'string' ? new Set(opts.split(/\s+/).filter(Boolean)) : null
1086
+ const m = {}
1087
+ for (const p of POLYFILLS) m[p[0]] = set ? (set.has('all') || set.has(p[0])) : true
1088
+ return m
1089
+ }
1090
+
1091
+ /**
1092
+ * Detect which polyfillable features an AST uses.
1093
+ *
1094
+ * @param {Array} ast
1095
+ * @returns {Set<string>} Set of feature names
1096
+ */
1097
+ const detect = (ast) => {
1098
+ const used = new Set()
1099
+
1100
+ // Standard op detection: a trigger op anywhere means its feature is used.
1101
+ walk(ast, node => {
1102
+ if (typeof node !== 'string') return
1103
+ for (const p of POLYFILLS) {
1104
+ const ops = p[1]
1105
+ if (ops.some(op => node === op || node.startsWith(op + ' '))) used.add(p[0])
1106
+ }
1107
+ })
1108
+
1109
+ // Special: extended_const — global.get in a global initializer with arithmetic
1110
+ walk(ast, node => {
1111
+ if (!Array.isArray(node) || node[0] !== 'global') return
1112
+ for (const init of node) {
1113
+ if (!Array.isArray(init)) continue
1114
+ if (init[0] === 'i32.add' || init[0] === 'i32.sub' || init[0] === 'i32.mul' ||
1115
+ init[0] === 'i64.add' || init[0] === 'i64.sub' || init[0] === 'i64.mul') {
1116
+ walk(init, inner => {
1117
+ if (Array.isArray(inner) && inner[0] === 'global.get') used.add('extended_const')
1118
+ })
1119
+ }
1120
+ }
1121
+ })
1122
+
1123
+ // Special: multi_value — functions with >1 result
1124
+ walk(ast, node => {
1125
+ if (!Array.isArray(node) || node[0] !== 'func') return
1126
+ let resultCount = 0
1127
+ for (const part of node) {
1128
+ if (Array.isArray(part) && part[0] === 'result') resultCount += part.length - 1
1129
+ }
1130
+ if (resultCount > 1) used.add('multi_value')
1131
+ })
1132
+
1133
+ return used
1134
+ }
1178
1135
 
1179
1136
  /**
1180
1137
  * Apply polyfill transforms to AST.
@@ -1199,10 +1156,9 @@ export default function polyfill(ast, opts = true) {
1199
1156
  const used = detect(ast)
1200
1157
  const ctx = { uid: 0 }
1201
1158
 
1202
- for (const feat of ALL) {
1203
- if (used.has(feat) && opts[feat] !== false && transforms[feat]) {
1204
- ast = transforms[feat](ast, ctx)
1205
- }
1159
+ for (const p of POLYFILLS) {
1160
+ const fn = p[2]
1161
+ if (used.has(p[0]) && opts[p[0]] !== false) ast = fn(ast, ctx)
1206
1162
  }
1207
1163
 
1208
1164
  return ast
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Tagged-template `compile` / `watr` over swappable backend primitives.
3
+ *
4
+ * The tagged-template entry point (`.raw` detection), interpolated function
5
+ * imports, and `new WebAssembly.Module` instantiation are JS-host concerns the
6
+ * wasm boundary cannot express — so they live here, once, backend-agnostic.
7
+ * watr.js wires the JS-source backend; the wasm test runner wires wasm exports.
8
+ *
9
+ * jz constraint: backend primitives must be destructured into locals before
10
+ * being called — jz miscompiles a direct `backend.fn()` property call, and
11
+ * cannot host these as a nested factory closure. Hence top-level functions
12
+ * taking `backend` as a plain argument.
13
+ *
14
+ * @module watr/template
15
+ */
16
+
17
+ import { resultType } from './const.js'
18
+
19
+ /** Private Use Area character as placeholder for interpolation */
20
+ const PUA = '\uE000'
21
+
22
+ /**
23
+ * Infer type of an expression AST node.
24
+ * Used for auto-import parameter type inference.
25
+ *
26
+ * @param {any} node - AST node (array or primitive)
27
+ * @param {Object} [ctx={}] - Context with locals/funcs type info
28
+ * @returns {string|null} Type string or null if unknown
29
+ */
30
+ const exprType = (node, ctx = {}) => {
31
+ if (!Array.isArray(node)) {
32
+ // local.get $x - lookup type
33
+ if (typeof node === 'string' && node[0] === '$' && ctx.locals?.[node]) return ctx.locals[node]
34
+ return null
35
+ }
36
+ const [op, ...args] = node
37
+ // (i32.const 42) → i32
38
+ const rt = resultType(op)
39
+ if (rt) return rt
40
+ // (local.get $x) → lookup
41
+ if (op === 'local.get' && ctx.locals?.[args[0]]) return ctx.locals[args[0]]
42
+ // (call $fn ...) → lookup function result type
43
+ if (op === 'call' && ctx.funcs?.[args[0]]) return ctx.funcs[args[0]].result?.[0]
44
+ return null
45
+ }
46
+
47
+ /**
48
+ * Walk AST and transform nodes depth-first.
49
+ * Handles array splicing when child has `_splice` property.
50
+ *
51
+ * @param {any} node - AST node to walk
52
+ * @param {Function} fn - Transform function (node) => node
53
+ * @returns {any} Transformed node
54
+ */
55
+ function walk(node, fn) {
56
+ node = fn(node)
57
+ if (Array.isArray(node)) {
58
+ for (let i = 0; i < node.length; i++) {
59
+ let child = walk(node[i], fn)
60
+ if (child?._splice) node.splice(i, 1, ...child), i += child.length - 1
61
+ else node[i] = child
62
+ }
63
+ }
64
+ return node
65
+ }
66
+
67
+ /**
68
+ * Find function references in AST and infer import signatures.
69
+ * Scans for `(call fn args...)` where fn is a JS function,
70
+ * infers param types from arguments, generates import entries.
71
+ *
72
+ * @param {Array} ast - AST to scan
73
+ * @param {Function[]} funcs - Functions to look for
74
+ * @returns {Array<{idx: number, name: string, params: string[], fn: Function}>} Import entries
75
+ */
76
+ function inferImports(ast, funcs) {
77
+ const imports = []
78
+ const importMap = new Map() // fn → import index
79
+
80
+ walk(ast, node => {
81
+ if (!Array.isArray(node)) return node
82
+
83
+ // Find (call ${fn} args...) where fn is a function
84
+ if (node[0] === 'call' && typeof node[1] === 'function') {
85
+ const fn = node[1]
86
+
87
+ if (!importMap.has(fn)) {
88
+ // Infer param types from arguments
89
+ const params = []
90
+ for (let i = 2; i < node.length; i++) {
91
+ const t = exprType(node[i])
92
+ if (t) params.push(t)
93
+ }
94
+
95
+ // Create import entry
96
+ const idx = imports.length
97
+ const name = fn.name || `$fn${idx}`
98
+ importMap.set(fn, { idx, name: name.startsWith('$') ? name : '$' + name, params, fn })
99
+ imports.push(importMap.get(fn))
100
+ }
101
+
102
+ // Replace function with import reference
103
+ const imp = importMap.get(fn)
104
+ node[1] = imp.name
105
+ }
106
+
107
+ return node
108
+ })
109
+
110
+ return imports
111
+ }
112
+
113
+ /**
114
+ * Generate WAT import declarations from inferred imports.
115
+ *
116
+ * @param {Array<{name: string, params: string[]}>} imports - Import entries
117
+ * @returns {Array} AST nodes for import declarations
118
+ */
119
+ function genImports(imports) {
120
+ return imports.map(({ name, params }) =>
121
+ ['import', '"env"', `"${name.slice(1)}"`, ['func', name, ...params.map(t => ['param', t])]]
122
+ )
123
+ }
124
+
125
+ /**
126
+ * Compile WAT to binary. Supports string, AST, and tagged template.
127
+ *
128
+ * @param {Object} backend - { parse, compile, optimize, polyfill } primitives
129
+ * @param {string|Array|TemplateStringsArray} source - WAT source, AST, or template strings
130
+ * @param {any[]} values - Interpolation values (for template literal)
131
+ * Last value can be options object:
132
+ * - polyfill: true | 'funcref sign_ext' | { funcref: true }
133
+ * - optimize: true | 'fold treeshake' | { fold: true }
134
+ * @returns {Uint8Array} WebAssembly binary
135
+ */
136
+ export function compile(backend, source, values) {
137
+ // Destructure into locals: jz miscompiles a direct backend.fn() call.
138
+ const { parse, compile: emit, optimize, polyfill } = backend
139
+
140
+ // Options object as last argument (non-template call)
141
+ let opts = {}
142
+ if (!Array.isArray(source) && values.length && typeof values[values.length - 1] === 'object' && values[values.length - 1] !== null && !values[values.length - 1].byteLength) {
143
+ opts = values.pop()
144
+ }
145
+
146
+ // Template literal: source is TemplateStringsArray
147
+ if (Array.isArray(source) && source.raw) {
148
+ // Build source with placeholders
149
+ let src = source[0]
150
+ for (let i = 0; i < values.length; i++) {
151
+ src += PUA + source[i + 1]
152
+ }
153
+
154
+ // Parse to AST
155
+ let ast = parse(src)
156
+
157
+ // Collect functions for auto-import
158
+ const funcsToImport = []
159
+
160
+ // Replace placeholders with actual values
161
+ let idx = 0
162
+ ast = walk(ast, node => {
163
+ if (node === PUA) {
164
+ const value = values[idx++]
165
+ // Function → mark for import inference
166
+ if (typeof value === 'function') {
167
+ funcsToImport.push(value)
168
+ return value // keep function reference for now
169
+ }
170
+ // String containing WAT code → parse and splice
171
+ if (typeof value === 'string' && (value[0] === '(' || /^\s*\(/.test(value))) {
172
+ const parsed = parse(value)
173
+ if (Array.isArray(parsed) && Array.isArray(parsed[0])) {
174
+ parsed._splice = true
175
+ }
176
+ return parsed
177
+ }
178
+ // Uint8Array → convert to plain array for flat() compatibility
179
+ if (value?.byteLength !== undefined) return [...value]
180
+ // BigInt can't cross the wasm boundary as a value, and watr's i32
181
+ // encoder rejects it — a decimal string parses back for both i32/i64.
182
+ if (typeof value === 'bigint') return value.toString()
183
+ return value
184
+ }
185
+ return node
186
+ })
187
+
188
+ // If we have functions to import, infer and generate imports
189
+ let importObjs = null
190
+ if (funcsToImport.length) {
191
+ const imports = inferImports(ast, funcsToImport)
192
+ if (imports.length) {
193
+ // Insert import declarations at start of module
194
+ const importDecls = genImports(imports)
195
+ if (ast[0] === 'module') {
196
+ ast.splice(1, 0, ...importDecls)
197
+ } else if (typeof ast[0] === 'string') {
198
+ // Single top-level node like ['func', ...] - wrap in array with imports
199
+ ast = [...importDecls, ast]
200
+ } else {
201
+ // Multiple top-level nodes like [['func', ...], ['func', ...]]
202
+ ast.unshift(...importDecls)
203
+ }
204
+ // Build imports object for instantiation
205
+ importObjs = { env: {} }
206
+ for (const imp of imports) {
207
+ importObjs.env[imp.name.slice(1)] = imp.fn
208
+ }
209
+ }
210
+ }
211
+
212
+ // Apply transforms
213
+ if (opts.polyfill) ast = polyfill(ast, opts.polyfill)
214
+ if (opts.optimize) ast = optimize(ast, opts.optimize)
215
+
216
+ const binary = emit(ast)
217
+ // Attach imports for watr() to use
218
+ if (importObjs) binary._imports = importObjs
219
+ return binary
220
+ }
221
+
222
+ // String/AST source with options
223
+ if (opts.polyfill || opts.optimize) {
224
+ let ast = typeof source === 'string' ? parse(source) : source
225
+ if (opts.polyfill) ast = polyfill(ast, opts.polyfill)
226
+ if (opts.optimize) ast = optimize(ast, opts.optimize)
227
+ return emit(ast)
228
+ }
229
+ return emit(source)
230
+ }
231
+
232
+ /**
233
+ * Compile and instantiate WAT, returning exports.
234
+ *
235
+ * @param {Object} backend - { parse, compile, optimize, polyfill } primitives
236
+ * @param {string|Array|TemplateStringsArray} source - WAT source, AST, or template strings
237
+ * @param {any[]} values - Interpolation values (for template literal)
238
+ * @returns {WebAssembly.Exports} Module exports
239
+ */
240
+ export function watr(backend, source, values) {
241
+ const binary = compile(backend, source, values)
242
+ const module = new WebAssembly.Module(binary)
243
+ const instance = new WebAssembly.Instance(module, binary._imports)
244
+ return instance.exports
245
+ }
package/src/util.js CHANGED
@@ -19,13 +19,6 @@ export const err = (text, pos=err.loc) => {
19
19
  throw Error(text)
20
20
  }
21
21
 
22
- /**
23
- * Deep clone an array tree structure.
24
- * @param {Array} items - Array to clone
25
- * @returns {Array} Cloned array
26
- */
27
- export const clone = items => items.map(item => Array.isArray(item) ? clone(item) : item)
28
-
29
22
  /** Regex to detect invalid underscore placement in numbers */
30
23
  export const sepRE = /^_|_$|[^\da-f]_|_[^\da-f]/i
31
24
 
@@ -84,3 +77,46 @@ export const str = s => {
84
77
  * @returns {string} Unescaped string without quotes, e.g. 'hello\nworld'
85
78
  */
86
79
  export const unescape = s => tdec.decode(new Uint8Array(str(s)))
80
+
81
+
82
+ // AST traversal — every watr AST node is an s-expression array `[head, ...args]`.
83
+
84
+ /**
85
+ * Deep clone an AST node.
86
+ * @param {any} node
87
+ * @returns {any}
88
+ */
89
+ export const clone = (node) => Array.isArray(node) ? node.map(clone) : node
90
+
91
+ /**
92
+ * Walk AST depth-first (pre-order), call fn on each node. Read-only.
93
+ * @param {any} node
94
+ * @param {Function} fn - (node, parent, idx) => void
95
+ * @param {any} [parent]
96
+ * @param {number} [idx]
97
+ */
98
+ export const walk = (node, fn, parent, idx) => {
99
+ fn(node, parent, idx)
100
+ if (Array.isArray(node)) for (let i = 0; i < node.length; i++) walk(node[i], fn, node, i)
101
+ }
102
+
103
+ /**
104
+ * Walk AST depth-first (post-order): children are visited before their parent.
105
+ *
106
+ * A node is replaced either way a callback chooses to express it:
107
+ * - return a new node — walkPost writes it into `parent[idx]`
108
+ * - mutate `parent[idx]` in place and return undefined — walkPost leaves it
109
+ * so both the transforming (optimize) and mutating (polyfill) styles compose.
110
+ *
111
+ * @param {any} node
112
+ * @param {Function} fn - (node, parent, idx) => newNode | undefined
113
+ * @param {any} [parent]
114
+ * @param {number} [idx]
115
+ * @returns {any} The (possibly replaced) node
116
+ */
117
+ export const walkPost = (node, fn, parent, idx) => {
118
+ if (Array.isArray(node)) for (let i = 0; i < node.length; i++) walkPost(node[i], fn, node, i)
119
+ const result = fn(node, parent, idx)
120
+ if (result !== undefined && parent) parent[idx] = result
121
+ return result !== undefined ? result : node
122
+ }
@@ -1,4 +1,5 @@
1
1
  export const INSTR: (string | string[])[];
2
+ export function resultType(op: string): string | null;
2
3
  export namespace SECTION {
3
4
  export let custom: number;
4
5
  export let type: number;
@@ -1 +1 @@
1
- {"version":3,"file":"const.d.ts","sourceRoot":"","sources":["../../src/const.js"],"names":[],"mappings":"AAIA,0CA0KC"}
1
+ {"version":3,"file":"const.d.ts","sourceRoot":"","sources":["../../src/const.js"],"names":[],"mappings":"AAIA,0CA0KC;AAYM,+BAHI,MAAM,GACJ,MAAM,GAAC,IAAI,CAavB"}
@@ -105,39 +105,17 @@ export function inline(ast: any[]): any[];
105
105
  */
106
106
  export function inlineOnce(ast: any[]): any[];
107
107
  /**
108
- * Normalize options to { opt: bool } map.
108
+ * Normalize options to a { passName: bool } map. An explicit object is kept
109
+ * as-is (preserving `log`/`verbose`), with any unmentioned pass filled to its
110
+ * default; `true` selects the defaults; a string selects only the named
111
+ * passes (or all of them via `'all'`).
112
+ *
109
113
  * @param {boolean|string|Object} opts
110
114
  * @returns {Object}
111
115
  */
112
116
  export function normalize(opts: boolean | string | any): any;
113
- export namespace OPTS {
114
- let treeshake: boolean;
115
- let fold: boolean;
116
- let deadcode: boolean;
117
- let locals: boolean;
118
- let identity: boolean;
119
- let strength: boolean;
120
- let branch: boolean;
121
- let propagate: boolean;
122
- let inline: boolean;
123
- let inlineOnce: boolean;
124
- let vacuum: boolean;
125
- let mergeBlocks: boolean;
126
- let coalesce: boolean;
127
- let peephole: boolean;
128
- let globals: boolean;
129
- let offset: boolean;
130
- let unbranch: boolean;
131
- let loopify: boolean;
132
- let stripmut: boolean;
133
- let brif: boolean;
134
- let foldarms: boolean;
135
- let dedupe: boolean;
136
- let reorder: boolean;
137
- let dedupTypes: boolean;
138
- let packData: boolean;
139
- let minifyImports: boolean;
140
- }
117
+ /** Option name → default-on map — the public catalogue of passes. */
118
+ export const OPTS: any;
141
119
  /**
142
120
  * Remove no-op code: nops, drop of pure expressions, empty branches,
143
121
  * and select with identical arms.
@@ -1 +1 @@
1
- {"version":3,"file":"optimize.d.ts","sourceRoot":"","sources":["../../src/optimize.js"],"names":[],"mappings":"AA02FA;;;;;;;;;;;GAWG;AACH,sCATW,QAAM,MAAM,SACZ,OAAO,GAAC,MAAM,MAAO,SAkF/B;AAp5FD;;;;GAIG;AACH,4BAHW,GAAG,GACD,MAAM,CAOlB;AAED;;;;GAIG;AACH,wCAFa,MAAM,CAIlB;AAwGD;;;;;GAKG;AACH,6CAgJC;AAuMD;;;;GAIG;AACH,wCAwBC;AAoMD;;;;GAIG;AACH,4CAqBC;AA+CD;;;;;GAKG;AACH,8CAmDC;AA7QD;;;;GAIG;AACH,4CASC;AAID;;;;GAIG;AACH,4CA6DC;AAID;;;;GAIG;AACH,0CAuCC;AAoeD,yCAoCC;AAID;;;;GAIG;AACH,0CAiGC;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,8CAmOC;AAvgDD;;;;GAIG;AACH,gCAHW,OAAO,GAAC,MAAM,MAAO,OAa/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6vDD;;;;;GAKG;AACH,0CAgDC;AAyED;;;;GAIG;AACH,4CAQC;AA4BD;;;;;;;;;;;GAWG;AACH,2CAyDC;AAID,2EAA2E;AAC3E,sCAgEC;AAID;;;;GAIG;AACH,4CAyCC;AAID;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,2CAiDC;AAID;;;;;GAKG;AACH,4CAqBC;AAID;;;;;;GAMG;AACH,wCAmBC;AAID;;;;;GAKG;AACH,4CAiDC;AAoCD;;;;;GAKG;AACH,0CA8DC;AAiWD,uCAyBC;AAtXD;;;;;GAKG;AACH,8CAyEC;AAoID;;;;GAIG;AACH,4CAyDC;AAqBD;;;;;GAKG;AACH,iDAgBC;AA/qCD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,+CAiFC;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,kDAyFC"}
1
+ {"version":3,"file":"optimize.d.ts","sourceRoot":"","sources":["../../src/optimize.js"],"names":[],"mappings":"AA20FA;;;;;;;;;;;GAWG;AACH,sCATW,QAAM,MAAM,SACZ,OAAO,GAAC,MAAM,MAAO,SAyD/B;AA53FD;;;;GAIG;AACH,4BAHW,GAAG,GACD,MAAM,CAOlB;AAED;;;;GAIG;AACH,wCAFa,MAAM,CAIlB;AA8CD;;;;;GAKG;AACH,6CAgJC;AAuMD;;;;GAIG;AACH,wCAuBC;AAoMD;;;;GAIG;AACH,4CAqBC;AA+CD;;;;;GAKG;AACH,8CAmDC;AA7QD;;;;GAIG;AACH,4CASC;AAID;;;;GAIG;AACH,4CA6DC;AAID;;;;GAIG;AACH,0CAuCC;AAoeD,yCAoCC;AAID;;;;GAIG;AACH,0CAiGC;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,8CAmOC;AA0zCD;;;;;;;;GAQG;AACH,gCAHW,OAAO,GAAC,MAAM,MAAO,OAc/B;AAvBD,qEAAqE;AACrE,uBAA8D;AAnjC9D;;;;;GAKG;AACH,0CAgDC;AAyED;;;;GAIG;AACH,4CAQC;AA4BD;;;;;;;;;;;GAWG;AACH,2CAyDC;AAID,2EAA2E;AAC3E,sCAgEC;AAID;;;;GAIG;AACH,4CAyCC;AAID;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,2CAiDC;AAID;;;;;GAKG;AACH,4CAqBC;AAID;;;;;;GAMG;AACH,wCAmBC;AAID;;;;;GAKG;AACH,4CAiDC;AAoCD;;;;;GAKG;AACH,0CA8DC;AAiWD,uCAyBC;AAtXD;;;;;GAKG;AACH,8CAyEC;AAoID;;;;GAIG;AACH,4CAyDC;AAqBD;;;;;GAKG;AACH,iDAgBC;AA/qCD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,+CAiFC;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,kDAyFC"}