watr 4.6.6 → 4.6.8
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/dist/watr.js +340 -405
- package/dist/watr.min.js +6 -6
- package/dist/watr.wasm +0 -0
- package/package.json +1 -1
- package/src/const.js +23 -0
- package/src/encode.js +1 -1
- package/src/optimize.js +179 -236
- package/src/polyfill.js +91 -135
- package/src/template.js +245 -0
- package/src/util.js +43 -7
- package/types/src/const.d.ts +1 -0
- package/types/src/const.d.ts.map +1 -1
- package/types/src/optimize.d.ts +7 -29
- package/types/src/optimize.d.ts.map +1 -1
- package/types/src/polyfill.d.ts +8 -14
- package/types/src/polyfill.d.ts.map +1 -1
- package/types/src/template.d.ts +22 -0
- package/types/src/template.d.ts.map +1 -0
- package/types/src/util.d.ts +3 -1
- package/types/src/util.d.ts.map +1 -1
- package/types/watr.d.ts +4 -10
- package/types/watr.d.ts.map +1 -1
- package/watr.js +9 -224
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
|
-
|
|
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
|
|
1203
|
-
|
|
1204
|
-
|
|
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
|
package/src/template.js
ADDED
|
@@ -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
|
+
}
|
package/types/src/const.d.ts
CHANGED
package/types/src/const.d.ts.map
CHANGED
|
@@ -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"}
|
package/types/src/optimize.d.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
114
|
-
|
|
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":"
|
|
1
|
+
{"version":3,"file":"optimize.d.ts","sourceRoot":"","sources":["../../src/optimize.js"],"names":[],"mappings":"AA00FA;;;;;;;;;;;GAWG;AACH,sCATW,QAAM,MAAM,SACZ,OAAO,GAAC,MAAM,MAAO,SAyD/B;AA13FD;;;;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,0CAgGC;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,8CAmOC;AAyzCD;;;;;;;;GAQG;AACH,gCAHW,OAAO,GAAC,MAAM,MAAO,OAc/B;AAvBD,qEAAqE;AACrE,uBAA8D;AAljC9D;;;;;GAKG;AACH,0CAgDC;AAwED;;;;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;AA9qCD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,+CAiFC;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,kDAyFC"}
|