watr 1.0.0 → 1.2.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/package.json +8 -4
- package/readme.md +16 -10
- package/watr.js +358 -211
- package/watr.min.js +1 -1
- package/.gitattributes +0 -2
- package/example/amp.wasm +0 -0
- package/example/amp.wat +0 -81
- package/example/array.wat +0 -26
- package/example/global.wasm +0 -0
- package/example/global.wat +0 -28
- package/example/loops.wasm +0 -0
- package/example/loops.wat +0 -34
- package/example/memory.wasm +0 -0
- package/example/memory.wat +0 -34
- package/example/multivar.wasm +0 -0
- package/example/multivar.wat +0 -13
- package/example/stack.wasm +0 -0
- package/example/stack.wat +0 -38
- package/example/table.wasm +0 -0
- package/example/table.wat +0 -24
- package/example/types.wasm +0 -0
- package/example/types.wat +0 -21
- package/lib/wabt.js +0 -36
- package/lib/wat-compiler.js +0 -1
- package/plan.md +0 -21
- package/repl.html +0 -44
- package/src/compile.js +0 -456
- package/src/parse.js +0 -31
- package/src/watr.js +0 -8
- package/test/compile.js +0 -1090
- package/test/examples.js +0 -155
- package/test/index.html +0 -14
- package/test/index.js +0 -2
- package/test/parse.js +0 -179
package/watr.js
CHANGED
|
@@ -1,112 +1,204 @@
|
|
|
1
|
+
// encoding ref: https://github.com/j-s-n/WebBS/blob/master/compiler/byteCode.js
|
|
2
|
+
const uleb = (number, buffer=[]) => {
|
|
3
|
+
if (typeof number === 'string') number = parseInt(number.replaceAll('_',''));
|
|
4
|
+
|
|
5
|
+
let byte = number & 0b01111111;
|
|
6
|
+
number = number >>> 7;
|
|
7
|
+
|
|
8
|
+
if (number === 0) {
|
|
9
|
+
buffer.push(byte);
|
|
10
|
+
return buffer;
|
|
11
|
+
} else {
|
|
12
|
+
buffer.push(byte | 0b10000000);
|
|
13
|
+
return uleb(number, buffer);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function leb (n, buffer=[]) {
|
|
18
|
+
if (typeof n === 'string') n = parseInt(n.replaceAll('_',''));
|
|
19
|
+
|
|
20
|
+
while (true) {
|
|
21
|
+
const byte = Number(n & 0x7F);
|
|
22
|
+
n >>= 7;
|
|
23
|
+
if ((n === 0 && (byte & 0x40) === 0) || (n === -1 && (byte & 0x40) !== 0)) {
|
|
24
|
+
buffer.push(byte);
|
|
25
|
+
break
|
|
26
|
+
}
|
|
27
|
+
buffer.push((byte | 0x80));
|
|
28
|
+
}
|
|
29
|
+
return buffer
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function bigleb(n, buffer=[]) {
|
|
33
|
+
if (typeof n === 'string') {
|
|
34
|
+
n = n.replaceAll('_','');
|
|
35
|
+
n = n[0]==='-'?-BigInt(n.slice(1)):BigInt(n);
|
|
36
|
+
byteView.setBigInt64(0, n);
|
|
37
|
+
n = byteView.getBigInt64(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
while (true) {
|
|
41
|
+
const byte = Number(n & 0x7Fn);
|
|
42
|
+
n >>= 7n;
|
|
43
|
+
if ((n === 0n && (byte & 0x40) === 0) || (n === -1n && (byte & 0x40) !== 0)) {
|
|
44
|
+
buffer.push(byte);
|
|
45
|
+
break
|
|
46
|
+
}
|
|
47
|
+
buffer.push((byte | 0x80));
|
|
48
|
+
}
|
|
49
|
+
return buffer
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// generalized float cases parser
|
|
53
|
+
const flt = input => input==='nan'||input==='+nan'?NaN:input==='-nan'?-NaN:
|
|
54
|
+
input==='inf'||input==='+inf'?Infinity:input==='-inf'?-Infinity:parseFloat(input.replaceAll('_',''));
|
|
55
|
+
|
|
56
|
+
const byteView = new DataView(new BigInt64Array(1).buffer);
|
|
57
|
+
|
|
58
|
+
const F32_SIGN = 0x80000000, F32_NAN = 0x7f800000;
|
|
59
|
+
function f32 (input, value, idx) {
|
|
60
|
+
if (~(idx=input.indexOf('nan:'))) {
|
|
61
|
+
value = parseInt(input.slice(idx+4));
|
|
62
|
+
value |= F32_NAN;
|
|
63
|
+
if (input[0] === '-') value |= F32_SIGN;
|
|
64
|
+
byteView.setInt32(0, value);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
value=typeof input === 'string' ? flt(input) : input;
|
|
68
|
+
byteView.setFloat32(0, value);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return [
|
|
72
|
+
byteView.getUint8(3),
|
|
73
|
+
byteView.getUint8(2),
|
|
74
|
+
byteView.getUint8(1),
|
|
75
|
+
byteView.getUint8(0)
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const F64_SIGN = 0x8000000000000000n, F64_NAN = 0x7ff0000000000000n;
|
|
80
|
+
function f64 (input, value, idx) {
|
|
81
|
+
if (~(idx=input.indexOf('nan:'))) {
|
|
82
|
+
value = BigInt(input.slice(idx+4));
|
|
83
|
+
value |= F64_NAN;
|
|
84
|
+
if (input[0] === '-') value |= F64_SIGN;
|
|
85
|
+
byteView.setBigInt64(0, value);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
value=typeof input === 'string' ? flt(input) : input;
|
|
89
|
+
byteView.setFloat64(0, value);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return [
|
|
93
|
+
byteView.getUint8(7),
|
|
94
|
+
byteView.getUint8(6),
|
|
95
|
+
byteView.getUint8(5),
|
|
96
|
+
byteView.getUint8(4),
|
|
97
|
+
byteView.getUint8(3),
|
|
98
|
+
byteView.getUint8(2),
|
|
99
|
+
byteView.getUint8(1),
|
|
100
|
+
byteView.getUint8(0)
|
|
101
|
+
];
|
|
102
|
+
}
|
|
103
|
+
|
|
1
104
|
// ref: https://github.com/stagas/wat-compiler/blob/main/lib/const.js
|
|
105
|
+
// NOTE: squashing into a string doesn't save up gzipped size
|
|
2
106
|
const OP = [
|
|
3
107
|
'unreachable', 'nop', 'block', 'loop', 'if', 'else', ,,,,,
|
|
4
|
-
|
|
5
108
|
'end', 'br', 'br_if', 'br_table', 'return', 'call', 'call_indirect', ,,,,,,,,
|
|
6
|
-
|
|
7
109
|
'drop', 'select', ,,,,
|
|
8
|
-
|
|
9
110
|
'local.get', 'local.set', 'local.tee', 'global.get', 'global.set', ,,,
|
|
10
|
-
|
|
11
111
|
'i32.load', 'i64.load', 'f32.load', 'f64.load',
|
|
12
112
|
'i32.load8_s', 'i32.load8_u', 'i32.load16_s', 'i32.load16_u',
|
|
13
113
|
'i64.load8_s', 'i64.load8_u', 'i64.load16_s', 'i64.load16_u', 'i64.load32_s', 'i64.load32_u',
|
|
14
|
-
|
|
15
114
|
'i32.store', 'i64.store', 'f32.store', 'f64.store',
|
|
16
115
|
'i32.store8', 'i32.store16', 'i64.store8', 'i64.store16', 'i64.store32',
|
|
17
|
-
|
|
18
116
|
'memory.size', 'memory.grow',
|
|
19
|
-
|
|
20
117
|
'i32.const', 'i64.const', 'f32.const', 'f64.const',
|
|
21
118
|
'i32.eqz', 'i32.eq', 'i32.ne', 'i32.lt_s', 'i32.lt_u', 'i32.gt_s', 'i32.gt_u', 'i32.le_s', 'i32.le_u', 'i32.ge_s', 'i32.ge_u',
|
|
22
119
|
'i64.eqz', 'i64.eq', 'i64.ne', 'i64.lt_s', 'i64.lt_u', 'i64.gt_s', 'i64.gt_u', 'i64.le_s', 'i64.le_u', 'i64.ge_s', 'i64.ge_u',
|
|
23
120
|
'f32.eq', 'f32.ne', 'f32.lt', 'f32.gt', 'f32.le', 'f32.ge',
|
|
24
121
|
'f64.eq', 'f64.ne', 'f64.lt', 'f64.gt', 'f64.le', 'f64.ge',
|
|
25
|
-
|
|
26
122
|
'i32.clz', 'i32.ctz', 'i32.popcnt', 'i32.add', 'i32.sub', 'i32.mul', 'i32.div_s', 'i32.div_u', 'i32.rem_s', 'i32.rem_u', 'i32.and', 'i32.or', 'i32.xor', 'i32.shl', 'i32.shr_s', 'i32.shr_u', 'i32.rotl', 'i32.rotr',
|
|
27
123
|
'i64.clz', 'i64.ctz', 'i64.popcnt', 'i64.add', 'i64.sub', 'i64.mul', 'i64.div_s', 'i64.div_u', 'i64.rem_s', 'i64.rem_u', 'i64.and', 'i64.or', 'i64.xor', 'i64.shl', 'i64.shr_s', 'i64.shr_u', 'i64.rotl', 'i64.rotr',
|
|
28
|
-
|
|
29
124
|
'f32.abs', 'f32.neg', 'f32.ceil', 'f32.floor', 'f32.trunc', 'f32.nearest', 'f32.sqrt', 'f32.add', 'f32.sub', 'f32.mul', 'f32.div', 'f32.min', 'f32.max', 'f32.copysign',
|
|
30
125
|
'f64.abs', 'f64.neg', 'f64.ceil', 'f64.floor', 'f64.trunc', 'f64.nearest', 'f64.sqrt', 'f64.add', 'f64.sub', 'f64.mul', 'f64.div', 'f64.min', 'f64.max', 'f64.copysign',
|
|
31
|
-
|
|
32
126
|
'i32.wrap_i64',
|
|
33
|
-
|
|
34
127
|
'i32.trunc_f32_s', 'i32.trunc_f32_u', 'i32.trunc_f64_s', 'i32.trunc_f64_u', 'i64.extend_i32_s', 'i64.extend_i32_u',
|
|
35
128
|
'i64.trunc_f32_s', 'i64.trunc_f32_u', 'i64.trunc_f64_s', 'i64.trunc_f64_u',
|
|
36
|
-
|
|
37
129
|
'f32.convert_i32_s', 'f32.convert_i32_u', 'f32.convert_i64_s', 'f32.convert_i64_u', 'f32.demote_f64',
|
|
38
130
|
'f64.convert_i32_s', 'f64.convert_i32_u', 'f64.convert_i64_s', 'f64.convert_i64_u', 'f64.promote_f32',
|
|
39
|
-
|
|
40
131
|
'i32.reinterpret_f32', 'i64.reinterpret_f64', 'f32.reinterpret_i32', 'f64.reinterpret_i64',
|
|
41
132
|
],
|
|
42
|
-
SECTION = {type:1, import:2, func:3, table:4, memory:5, global:6, export:7, start:8, elem:9, code:10, data:11},
|
|
43
|
-
TYPE = {i32:0x7f, i64:0x7e, f32:0x7d, f64:0x7c, void:0x40, func:0x60, funcref:0x70},
|
|
44
|
-
KIND = {func: 0, table: 1, memory: 2, global: 3},
|
|
133
|
+
SECTION = { type:1, import:2, func:3, table:4, memory:5, global:6, export:7, start:8, elem:9, code:10, data:11 },
|
|
134
|
+
TYPE = { i32:0x7f, i64:0x7e, f32:0x7d, f64:0x7c, void:0x40, func:0x60, funcref:0x70 },
|
|
135
|
+
KIND = { func: 0, table: 1, memory: 2, global: 3 },
|
|
45
136
|
ALIGN = {
|
|
46
|
-
'i32.load': 4,
|
|
47
|
-
'
|
|
48
|
-
'
|
|
49
|
-
'f64.
|
|
50
|
-
|
|
51
|
-
'i32.load8_s': 1,
|
|
52
|
-
'i32.load8_u': 1,
|
|
53
|
-
'i32.load16_s': 2,
|
|
54
|
-
'i32.load16_u': 2,
|
|
55
|
-
|
|
56
|
-
'i64.load8_s': 1,
|
|
57
|
-
'i64.load8_u': 1,
|
|
58
|
-
'i64.load16_s': 2,
|
|
59
|
-
'i64.load16_u': 2,
|
|
60
|
-
'i64.load32_s': 4,
|
|
61
|
-
'i64.load32_u': 4,
|
|
62
|
-
|
|
63
|
-
'i32.store': 4,
|
|
64
|
-
'i64.store': 8,
|
|
65
|
-
'f32.store': 4,
|
|
66
|
-
'f64.store': 8,
|
|
67
|
-
|
|
68
|
-
'i32.store8': 1,
|
|
69
|
-
'i32.store16': 2,
|
|
70
|
-
'i64.store8': 1,
|
|
71
|
-
'i64.store16': 2,
|
|
72
|
-
'i64.store32': 4,
|
|
137
|
+
'i32.load': 4, 'i64.load': 8, 'f32.load': 4, 'f64.load': 8,
|
|
138
|
+
'i32.load8_s': 1, 'i32.load8_u': 1, 'i32.load16_s': 2, 'i32.load16_u': 2,
|
|
139
|
+
'i64.load8_s': 1, 'i64.load8_u': 1, 'i64.load16_s': 2, 'i64.load16_u': 2, 'i64.load32_s': 4, 'i64.load32_u': 4, 'i32.store': 4,
|
|
140
|
+
'i64.store': 8, 'f32.store': 4, 'f64.store': 8,
|
|
141
|
+
'i32.store8': 1, 'i32.store16': 2, 'i64.store8': 1, 'i64.store16': 2, 'i64.store32': 4,
|
|
73
142
|
};
|
|
74
143
|
|
|
75
|
-
OP.map((op,i)=>OP[op]=i); // init op names
|
|
144
|
+
OP.map((op,i)=>OP[op]=i); // init op names
|
|
145
|
+
|
|
146
|
+
// some inlinable instructions
|
|
147
|
+
const INLINE = {loop: 1, block: 1, if: 1, end: -1, return: -1};
|
|
76
148
|
|
|
77
149
|
// convert wat tree to wasm binary
|
|
78
150
|
var compile = (nodes) => {
|
|
79
|
-
//
|
|
151
|
+
// IR. Alias is stored directly to section array by key, eg. section.func.$name = idx
|
|
80
152
|
let sections = {
|
|
81
153
|
type: [], import: [], func: [], table: [], memory: [], global: [], export: [], start: [], elem: [], code: [], data: []
|
|
82
|
-
},
|
|
83
|
-
binary = [
|
|
154
|
+
}, binary = [
|
|
84
155
|
0x00, 0x61, 0x73, 0x6d, // magic
|
|
85
156
|
0x01, 0x00, 0x00, 0x00, // version
|
|
86
157
|
];
|
|
87
158
|
|
|
159
|
+
// 1. transform tree
|
|
88
160
|
// (func) → [(func)]
|
|
89
161
|
if (typeof nodes[0] === 'string' && nodes[0] !== 'module') nodes = [nodes];
|
|
90
162
|
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
|
|
163
|
+
// (global $a (import "a" "b") (mut i32)) → (import "a" "b" (global $a (mut i32)))
|
|
164
|
+
// (memory (import "a" "b") min max shared) → (import "a" "b" (memory min max shared))
|
|
165
|
+
nodes = nodes.map(node => {
|
|
166
|
+
if (node[2]?.[0]==='import') {
|
|
167
|
+
let [kind, name, imp, ...args] = node;
|
|
168
|
+
return [...imp, [kind, name, ...args]]
|
|
169
|
+
}
|
|
170
|
+
else if (node[1]?.[0]==='import') {
|
|
171
|
+
let [kind, imp, ...args] = node;
|
|
172
|
+
return [...imp, [kind, ...args]]
|
|
173
|
+
}
|
|
174
|
+
return node
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// 2. build IR. import must be initialized first, global before func, elem after func
|
|
178
|
+
let order = ['type', 'import', 'table', 'memory', 'global', 'func', 'export', 'start', 'elem', 'data'], postcall = [];
|
|
179
|
+
|
|
180
|
+
for (let name of order) {
|
|
94
181
|
let remaining = [];
|
|
95
|
-
for (let node of nodes)
|
|
182
|
+
for (let node of nodes) {
|
|
183
|
+
node[0] === name ? postcall.push(build[name](node, sections)) : remaining.push(node);
|
|
184
|
+
}
|
|
185
|
+
|
|
96
186
|
nodes = remaining;
|
|
97
187
|
}
|
|
98
188
|
|
|
99
|
-
//
|
|
100
|
-
|
|
189
|
+
// code must be compiled after all definitions
|
|
190
|
+
for (let cb of postcall) cb && cb.call && cb();
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
// 3. build binary
|
|
101
194
|
for (let name in sections) {
|
|
102
195
|
let items=sections[name];
|
|
103
|
-
if (items.importc) items = items.slice(items.importc); // discard imported functions
|
|
196
|
+
if (items.importc) items = items.slice(items.importc); // discard imported functions/globals
|
|
104
197
|
if (!items.length) continue
|
|
105
|
-
let
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
binary[sizePtr] = binary.length - sizePtr - 1;
|
|
198
|
+
let sectionCode = SECTION[name], bytes = [];
|
|
199
|
+
if (sectionCode!==8) bytes.push(items.length); // skip start section count
|
|
200
|
+
for (let item of items) bytes.push(...item);
|
|
201
|
+
binary.push(sectionCode, ...uleb(bytes.length), ...bytes);
|
|
110
202
|
}
|
|
111
203
|
|
|
112
204
|
return new Uint8Array(binary)
|
|
@@ -118,21 +210,21 @@ const build = {
|
|
|
118
210
|
// FIXME: handle non-function types
|
|
119
211
|
type([, typeName, decl], ctx) {
|
|
120
212
|
if (typeName[0]!=='$') decl=typeName, typeName=null;
|
|
121
|
-
let params = [], result = [], kind = decl
|
|
213
|
+
let params = [], result = [], [kind,...sig] = decl, idx, bytes;
|
|
122
214
|
|
|
123
215
|
if (kind==='func') {
|
|
124
216
|
// collect params
|
|
125
|
-
while (
|
|
126
|
-
let [, ...types] =
|
|
217
|
+
while (sig[0]?.[0] === 'param') {
|
|
218
|
+
let [, ...types] = sig.shift();
|
|
127
219
|
if (types[0]?.[0] === '$') params[types.shift()] = params.length;
|
|
128
220
|
params.push(...types.map(t => TYPE[t]));
|
|
129
221
|
}
|
|
130
222
|
|
|
131
223
|
// collect result type
|
|
132
|
-
if (
|
|
224
|
+
if (sig[0]?.[0] === 'result') result = sig.shift().slice(1).map(t => TYPE[t]);
|
|
133
225
|
|
|
134
226
|
// reuse existing type or register new one
|
|
135
|
-
bytes = [TYPE.func, params.length, ...params, result.length, ...result];
|
|
227
|
+
bytes = [TYPE.func, ...uleb(params.length), ...params, ...uleb(result.length), ...result];
|
|
136
228
|
|
|
137
229
|
idx = ctx.type.findIndex((prevType) => prevType.every((byte, i) => byte === bytes[i]));
|
|
138
230
|
if (idx < 0) idx = ctx.type.push(bytes)-1;
|
|
@@ -145,14 +237,14 @@ const build = {
|
|
|
145
237
|
|
|
146
238
|
// (func $name? ...params result ...body)
|
|
147
239
|
func([,...body], ctx) {
|
|
148
|
-
let
|
|
149
|
-
|
|
240
|
+
let locals=[], // list of local variables
|
|
241
|
+
callstack=[];
|
|
150
242
|
|
|
151
243
|
// fn name
|
|
152
|
-
if (body[0]?.[0] === '$') ctx.func[body.shift()] =
|
|
244
|
+
if (body[0]?.[0] === '$') ctx.func[body.shift()] = ctx.func.length;
|
|
153
245
|
|
|
154
246
|
// export binding
|
|
155
|
-
if (body[0]?.[0] === 'export') build.export([...body.shift(), ['func',
|
|
247
|
+
if (body[0]?.[0] === 'export') build.export([...body.shift(), ['func', ctx.func.length]], ctx);
|
|
156
248
|
|
|
157
249
|
// register type
|
|
158
250
|
let [typeIdx, params, result] = build.type([,['func',...body]], ctx);
|
|
@@ -162,136 +254,193 @@ const build = {
|
|
|
162
254
|
|
|
163
255
|
// collect locals
|
|
164
256
|
while (body[0]?.[0] === 'local') {
|
|
165
|
-
let [, ...
|
|
166
|
-
if (
|
|
167
|
-
params[name=
|
|
257
|
+
let [, ...types] = body.shift(), name;
|
|
258
|
+
if (types[0][0]==='$')
|
|
259
|
+
params[name=types.shift()] ? err('Ambiguous name '+name) :
|
|
168
260
|
locals[name] = params.length + locals.length;
|
|
169
|
-
|
|
261
|
+
locals.push(...types.map(t => TYPE[t]));
|
|
170
262
|
}
|
|
171
263
|
|
|
264
|
+
// squash local types
|
|
265
|
+
let locTypes = locals.reduce((a, type) => (type==a[a.length-1] ? a[a.length-2]++ : a.push(1,type), a), []);
|
|
266
|
+
|
|
172
267
|
// map code instruction into bytes: [args, opCode, immediates]
|
|
173
|
-
const instr = (
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
268
|
+
const instr = (group) => {
|
|
269
|
+
let [op, ...nodes] = group;
|
|
270
|
+
let opCode = OP[op], argc=0, before=[], after=[], id;
|
|
271
|
+
|
|
272
|
+
// NOTE: we could reorganize ops by groups and detect signature as `op in STORE`
|
|
273
|
+
// but numeric comparison is faster than generic hash lookup
|
|
274
|
+
// FIXME: we often use OP.end or alike: what if we had list of global constants?
|
|
275
|
+
|
|
276
|
+
// binary/unary
|
|
277
|
+
if (opCode>=69) {
|
|
278
|
+
argc = opCode>=167 ||
|
|
279
|
+
(opCode<=159 && opCode>=153) ||
|
|
280
|
+
(opCode<=145 && opCode>=139) ||
|
|
281
|
+
(opCode<=123 && opCode>=121) ||
|
|
282
|
+
(opCode<=105 && opCode>=103) ||
|
|
283
|
+
opCode==80 || opCode==69 ? 1 : 2;
|
|
186
284
|
}
|
|
285
|
+
// instruction
|
|
286
|
+
else {
|
|
287
|
+
// (i32.store align=n offset=m at value)
|
|
288
|
+
if (opCode>=40&&opCode<=62) {
|
|
289
|
+
// FIXME: figure out point in Math.log2 aligns
|
|
290
|
+
let o = {align: ALIGN[op], offset: 0}, p;
|
|
291
|
+
while (nodes[0]?.[0] in o) p = nodes.shift(), o[p[0]] = +p[1];
|
|
292
|
+
after = [Math.log2(o.align), ...uleb(o.offset)];
|
|
293
|
+
argc = opCode >= 54 ? 2 : 1;
|
|
294
|
+
}
|
|
187
295
|
|
|
188
|
-
|
|
189
|
-
|
|
296
|
+
// (i32.const 123)
|
|
297
|
+
else if (opCode>=65&&opCode<=68) {
|
|
298
|
+
after = (opCode==65?leb:opCode==66?bigleb:opCode==67?f32:f64)(nodes.shift());
|
|
299
|
+
}
|
|
190
300
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
301
|
+
// (local.get $id), (local.tee $id x)
|
|
302
|
+
else if (opCode>=32&&opCode<=34) {
|
|
303
|
+
after = uleb(nodes[0]?.[0]==='$' ? params[id=nodes.shift()] || locals[id] : nodes.shift());
|
|
304
|
+
if (opCode>32) argc = 1;
|
|
305
|
+
}
|
|
197
306
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
307
|
+
// (global.get id), (global.set id)
|
|
308
|
+
else if (opCode==35||opCode==36) {
|
|
309
|
+
after = uleb(nodes[0]?.[0]==='$' ? ctx.global[nodes.shift()] : nodes.shift());
|
|
310
|
+
if (opCode>35) argc = 1;
|
|
311
|
+
}
|
|
204
312
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
313
|
+
// (call id ...nodes)
|
|
314
|
+
else if (opCode==16) {
|
|
315
|
+
let fnName = nodes.shift();
|
|
316
|
+
after = uleb(id = fnName[0]==='$' ? ctx.func[fnName] ?? err('Unknown function `' + fnName + '`') : fnName);
|
|
317
|
+
// FIXME: how to get signature of imported function
|
|
318
|
+
[,argc] = ctx.type[ctx.func[id][0]];
|
|
319
|
+
}
|
|
212
320
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
321
|
+
// (call_indirect (type $typeName) (idx) ...nodes)
|
|
322
|
+
else if (opCode==17) {
|
|
323
|
+
let typeId = nodes.shift()[1];
|
|
324
|
+
[,argc] = ctx.type[typeId = typeId[0]==='$'?ctx.type[typeId]:typeId];
|
|
325
|
+
argc++;
|
|
326
|
+
after = uleb(typeId), after.push(0); // extra afterediate indicates table idx (reserved)
|
|
327
|
+
}
|
|
220
328
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
329
|
+
// FIXME (memory.grow $idx?)
|
|
330
|
+
else if (opCode==63||opCode==64) {
|
|
331
|
+
after = [0];
|
|
332
|
+
argc = 1;
|
|
333
|
+
}
|
|
226
334
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
335
|
+
// (if (result i32)? (local.get 0) (then a b) (else a b)?)
|
|
336
|
+
else if (opCode==4) {
|
|
337
|
+
callstack.push(opCode);
|
|
338
|
+
let [,type] = nodes[0][0]==='result' ? nodes.shift() : [,'void'];
|
|
339
|
+
after=[TYPE[type]];
|
|
340
|
+
|
|
341
|
+
argc = 0, before.push(...instr(nodes.shift()));
|
|
342
|
+
let body;
|
|
343
|
+
if (nodes[0]?.[0]==='then') [,...body] = nodes.shift(); else body = nodes;
|
|
344
|
+
after.push(...consume(body));
|
|
345
|
+
|
|
346
|
+
callstack.pop(), callstack.push(OP.else);
|
|
347
|
+
if (nodes[0]?.[0]==='else') {
|
|
348
|
+
[,...body] = nodes.shift();
|
|
349
|
+
if (body.length) after.push(OP.else,...consume(body));
|
|
350
|
+
}
|
|
351
|
+
callstack.pop();
|
|
352
|
+
after.push(OP.end);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// (drop arg?), (return arg?)
|
|
356
|
+
else if (opCode==0x1a || opCode==0x0f) { argc = nodes.length?1:0; }
|
|
231
357
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
imm.push(...body.flatMap(instr));
|
|
244
|
-
|
|
245
|
-
if (stack[0][0]==='else') {
|
|
246
|
-
[,...body] = stack.shift();
|
|
247
|
-
imm.push(OP.else, ...body.flatMap(instr));
|
|
358
|
+
// (select a b cond)
|
|
359
|
+
else if (opCode==0x1b) { argc = 3; }
|
|
360
|
+
|
|
361
|
+
// (block ...), (loop ...)
|
|
362
|
+
else if (opCode==2||opCode==3) {
|
|
363
|
+
callstack.push(opCode);
|
|
364
|
+
if (nodes[0]?.[0]==='$') (callstack[nodes.shift()] = callstack.length);
|
|
365
|
+
let [,type] = nodes[0]?.[0]==='result' ? nodes.shift() : [,'void'];
|
|
366
|
+
after=[TYPE[type], ...consume(nodes)];
|
|
367
|
+
|
|
368
|
+
if (!group.inline) callstack.pop(), after.push(OP.end); // inline loop/block expects end to be separately provided
|
|
248
369
|
}
|
|
249
370
|
|
|
250
|
-
|
|
251
|
-
|
|
371
|
+
// (end)
|
|
372
|
+
else if (opCode==0x0b) callstack.pop();
|
|
252
373
|
|
|
253
|
-
|
|
254
|
-
|
|
374
|
+
// (br $label result?)
|
|
375
|
+
// (br_if $label cond result?)
|
|
376
|
+
else if (opCode==0x0c||opCode==0x0d) {
|
|
377
|
+
// br index indicates how many callstack items to pop
|
|
378
|
+
after = uleb(nodes[0]?.[0]==='$' ? callstack.length-callstack[nodes.shift()] : nodes.shift());
|
|
379
|
+
argc = (opCode==0x0d ? 1 + (nodes.length > 1) : !!nodes.length);
|
|
380
|
+
}
|
|
255
381
|
|
|
256
|
-
|
|
382
|
+
// (br_table 1 2 3 4 0 selector result?)
|
|
383
|
+
else if (opCode==0x0e) {
|
|
384
|
+
after = [];
|
|
385
|
+
while (!Array.isArray(nodes[0])) id=nodes.shift(), after.push(...uleb(id[0][0]==='$'?callstack.length-callstack[id]:id));
|
|
386
|
+
after.unshift(...uleb(after.length-1));
|
|
387
|
+
argc = 1 + (nodes.length>1);
|
|
388
|
+
}
|
|
257
389
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (stack.length) err(`Too many arguments for \`${op}\`.`);
|
|
390
|
+
else if (opCode==null) err(`Unknown instruction \`${op}\``);
|
|
391
|
+
}
|
|
261
392
|
|
|
262
|
-
|
|
393
|
+
// consume arguments
|
|
394
|
+
if (nodes.length < argc) err(`Stack arguments are not supported at \`${op}\``);
|
|
395
|
+
while (argc--) before.push(...instr(nodes.shift()));
|
|
396
|
+
if (nodes.length) err(`Too many arguments for \`${op}\`.`);
|
|
397
|
+
|
|
398
|
+
return [...before, opCode, ...after]
|
|
263
399
|
};
|
|
264
400
|
|
|
265
|
-
|
|
401
|
+
// consume sequence of nodes
|
|
402
|
+
const consume = nodes => {
|
|
403
|
+
let result = [];
|
|
404
|
+
while (nodes.length) {
|
|
405
|
+
let node = nodes.shift(), c;
|
|
406
|
+
|
|
407
|
+
if (typeof node === 'string') {
|
|
408
|
+
// permit some inline instructions: loop $label ... end, br $label, arg return
|
|
409
|
+
if (c=INLINE[node]) {
|
|
410
|
+
node = [node], node.inline = true;
|
|
411
|
+
if (c>0) nodes[0]?.[0]==='$' && node.push(nodes.shift());
|
|
412
|
+
}
|
|
413
|
+
else err(`Inline instruction \`${node}\` is not supported`);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
node && result.push(...instr(node));
|
|
417
|
+
}
|
|
418
|
+
return result
|
|
419
|
+
};
|
|
266
420
|
|
|
267
|
-
//
|
|
268
|
-
|
|
421
|
+
// evaluates after all definitions
|
|
422
|
+
return () => {
|
|
423
|
+
let code = consume(body);
|
|
424
|
+
ctx.code.push([...uleb(code.length+2+locTypes.length), ...uleb(locTypes.length>>1), ...locTypes, ...code, OP.end]);
|
|
425
|
+
}
|
|
269
426
|
},
|
|
270
427
|
|
|
271
428
|
// (memory min max shared)
|
|
272
429
|
// (memory $name min max shared)
|
|
273
|
-
// (memory (
|
|
430
|
+
// (memory (export "mem") 5)
|
|
274
431
|
memory([, ...parts], ctx) {
|
|
275
432
|
if (parts[0][0]==='$') ctx.memory[parts.shift()] = ctx.memory.length;
|
|
276
|
-
if (parts[0][0] === '
|
|
277
|
-
let [imp, ...limits] = parts;
|
|
278
|
-
// (import "js" "mem" (memory 1))
|
|
279
|
-
return build.import([...imp, ['memory', ...limits]], ctx)
|
|
280
|
-
}
|
|
281
|
-
|
|
433
|
+
if (parts[0][0] === 'export') build.export([...parts.shift(), ['memory', ctx.memory.length]], ctx);
|
|
282
434
|
ctx.memory.push(range(parts));
|
|
283
435
|
},
|
|
284
436
|
|
|
285
437
|
// (global i32 (i32.const 42))
|
|
286
438
|
// (global $id i32 (i32.const 42))
|
|
287
439
|
// (global $id (mut i32) (i32.const 42))
|
|
288
|
-
// FIXME (global $g1 (import "js" "g1") (mut i32)) ;; import from js
|
|
289
440
|
global([, ...args], ctx) {
|
|
290
441
|
let name = args[0][0]==='$' && args.shift();
|
|
291
442
|
if (name) ctx.global[name] = ctx.global.length;
|
|
292
|
-
|
|
293
|
-
let [type, init] = args, mut = type[0] === 'mut';
|
|
294
|
-
|
|
443
|
+
let [type, init] = args, mut = type[0] === 'mut' ? 1 : 0;
|
|
295
444
|
ctx.global.push([TYPE[mut ? type[1] : type], mut, ...iinit(init)]);
|
|
296
445
|
},
|
|
297
446
|
|
|
@@ -300,7 +449,6 @@ const build = {
|
|
|
300
449
|
table([, ...args], ctx) {
|
|
301
450
|
let name = args[0][0]==='$' && args.shift();
|
|
302
451
|
if (name) ctx.table[name] = ctx.table.length;
|
|
303
|
-
|
|
304
452
|
let lims = range(args);
|
|
305
453
|
ctx.table.push([TYPE[args.pop()], ...lims]);
|
|
306
454
|
},
|
|
@@ -308,42 +456,51 @@ const build = {
|
|
|
308
456
|
// (elem (i32.const 0) $f1 $f2), (elem (global.get 0) $f1 $f2)
|
|
309
457
|
elem([, offset, ...elems], ctx) {
|
|
310
458
|
const tableIdx = 0; // FIXME: table index can be defined
|
|
311
|
-
ctx.elem.push([tableIdx, ...iinit(offset, ctx), elems.length, ...elems.
|
|
459
|
+
ctx.elem.push([tableIdx, ...iinit(offset, ctx), ...uleb(elems.length), ...elems.flatMap(el => uleb(el[0]==='$' ? ctx.func[el] : el))]);
|
|
312
460
|
},
|
|
313
461
|
|
|
314
462
|
// (export "name" (kind $name|idx))
|
|
315
463
|
export([, name, [kind, idx]], ctx) {
|
|
316
464
|
if (idx[0]==='$') idx = ctx[kind][idx];
|
|
317
|
-
ctx.export.push([...str(name), KIND[kind],
|
|
465
|
+
ctx.export.push([...str(name), KIND[kind], ...uleb(idx)]);
|
|
318
466
|
},
|
|
319
467
|
|
|
320
468
|
// (import "math" "add" (func $add (param i32 i32 externref) (result i32)))
|
|
321
469
|
// (import "js" "mem" (memory 1))
|
|
322
470
|
// (import "js" "mem" (memory $name 1))
|
|
323
|
-
import(
|
|
324
|
-
|
|
325
|
-
|
|
471
|
+
// (import "js" "v" (global $name (mut f64)))
|
|
472
|
+
import([, mod, field, ref], ctx) {
|
|
473
|
+
let details, [kind, ...parts] = ref,
|
|
474
|
+
name = parts[0]?.[0]==='$' && parts.shift();
|
|
326
475
|
|
|
327
|
-
let details, [kind, ...parts] = ref;
|
|
328
476
|
if (kind==='func') {
|
|
329
477
|
// we track imported funcs in func section to share namespace, and skip them on final build
|
|
330
|
-
if (
|
|
478
|
+
if (name) ctx.func[name] = ctx.func.length;
|
|
331
479
|
let [typeIdx] = build.type([, ['func', ...parts]], ctx);
|
|
332
|
-
ctx.func.push(details =
|
|
480
|
+
ctx.func.push(details = uleb(typeIdx));
|
|
333
481
|
ctx.func.importc = (ctx.func.importc||0)+1;
|
|
334
482
|
}
|
|
335
483
|
else if (kind==='memory') {
|
|
336
|
-
if (
|
|
484
|
+
if (name) ctx.memory[name] = ctx.memory.length;
|
|
337
485
|
details = range(parts);
|
|
338
486
|
}
|
|
487
|
+
else if (kind==='global') {
|
|
488
|
+
// imported globals share namespace with internal globals - we skip them in final build
|
|
489
|
+
if (name) ctx.global[name] = ctx.global.length;
|
|
490
|
+
let [type] = parts, mut = type[0] === 'mut' ? 1 : 0;
|
|
491
|
+
details = [TYPE[mut ? type[1] : type], mut];
|
|
492
|
+
ctx.global.push(details);
|
|
493
|
+
ctx.global.importc = (ctx.global.importc||0)+1;
|
|
494
|
+
}
|
|
495
|
+
else throw Error('Unimplemented ' + kind)
|
|
339
496
|
|
|
340
|
-
ctx.import.push([...str(mod), ...str(
|
|
497
|
+
ctx.import.push([...str(mod), ...str(field), KIND[kind], ...details]);
|
|
341
498
|
},
|
|
342
499
|
|
|
343
|
-
// (data (i32.const 0) "\
|
|
344
|
-
data([, offset,
|
|
500
|
+
// (data (i32.const 0) "\aa" "\bb"?)
|
|
501
|
+
data([, offset, ...inits], ctx) {
|
|
345
502
|
// FIXME: first is mem index
|
|
346
|
-
ctx.data.push([0, ...iinit(offset,ctx), ...str(
|
|
503
|
+
ctx.data.push([0, ...iinit(offset,ctx), ...str(inits.map(i=>i[0]==='"'?i.slice(1,-1):i).join(''))]);
|
|
347
504
|
},
|
|
348
505
|
|
|
349
506
|
// (start $main)
|
|
@@ -353,62 +510,52 @@ const build = {
|
|
|
353
510
|
};
|
|
354
511
|
|
|
355
512
|
// (i32.const 0) - instantiation time initializer
|
|
356
|
-
const iinit = ([op, literal], ctx) =>
|
|
357
|
-
[OP[op], ...
|
|
513
|
+
const iinit = ([op, literal], ctx) => op[0]==='f' ?
|
|
514
|
+
[OP[op], ...(op[1]==='3'?f32:f64)(literal), OP.end] :
|
|
515
|
+
[OP[op], ...(op[1]==='3'?leb:bigleb)(literal[0] === '$' ? ctx.global[literal] : literal), OP.end];
|
|
516
|
+
|
|
517
|
+
const escape = {n:10, r:13, t:9, v:1};
|
|
358
518
|
|
|
359
519
|
// build string binary
|
|
360
520
|
const str = str => {
|
|
361
521
|
str = str[0]==='"' ? str.slice(1,-1) : str;
|
|
362
|
-
let res = [
|
|
522
|
+
let res = [], i = 0, c, BSLASH=92;
|
|
363
523
|
// spec https://webassembly.github.io/spec/core/text/values.html#strings
|
|
364
|
-
for (;
|
|
365
|
-
|
|
524
|
+
for (;i < str.length;) {
|
|
525
|
+
c=str.charCodeAt(i++);
|
|
526
|
+
res.push(c===BSLASH ? escape[str[i++]] || parseInt(str.slice(i-1,++i), 16) : c);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
res.unshift(...uleb(res.length));
|
|
366
530
|
return res
|
|
367
531
|
};
|
|
368
532
|
|
|
369
533
|
// build range/limits sequence (non-consuming)
|
|
370
|
-
const range = ([min, max, shared]) => isNaN(parseInt(max)) ? [0,
|
|
534
|
+
const range = ([min, max, shared]) => isNaN(parseInt(max)) ? [0, ...uleb(min)] : [shared==='shared'?3:1, ...uleb(min), ...uleb(max)];
|
|
371
535
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const result = [];
|
|
376
|
-
while (true) {
|
|
377
|
-
const byte_ = value & 0x7f;
|
|
378
|
-
value >>= 7;
|
|
379
|
-
if (
|
|
380
|
-
(value === 0 && (byte_ & 0x40) === 0) ||
|
|
381
|
-
(value === -1 && (byte_ & 0x40) !== 0)
|
|
382
|
-
) {
|
|
383
|
-
result.push(byte_);
|
|
384
|
-
return result;
|
|
385
|
-
}
|
|
386
|
-
result.push(byte_ | 0x80);
|
|
387
|
-
}
|
|
388
|
-
};
|
|
389
|
-
|
|
390
|
-
const err = text => { throw Error(text) };
|
|
391
|
-
|
|
392
|
-
const OPAREN=40, CPAREN=41, SPACE=32, SEMIC=59;
|
|
536
|
+
const err = text => { throw Error(text) };
|
|
537
|
+
|
|
538
|
+
const OPAREN=40, CPAREN=41, SPACE=32, DQUOTE=34, SEMIC=59;
|
|
393
539
|
|
|
394
540
|
var parse = (str) => {
|
|
395
541
|
let i = 0, level = [], buf='';
|
|
396
542
|
|
|
397
543
|
const commit = () => buf && (
|
|
398
|
-
level.push(~buf.indexOf('=') ? buf.split('=') : buf),
|
|
544
|
+
level.push(buf[0]!=='"' && ~buf.indexOf('=') ? buf.split('=') : buf),
|
|
399
545
|
buf = ''
|
|
400
546
|
);
|
|
401
547
|
|
|
402
548
|
const parseLevel = () => {
|
|
403
549
|
for (let c, root; i < str.length; ) {
|
|
404
550
|
c = str.charCodeAt(i);
|
|
405
|
-
if (c ===
|
|
551
|
+
if (c === DQUOTE) commit(), buf = str.slice(i++, i=str.indexOf('"', i)+1), commit();
|
|
552
|
+
else if (c === OPAREN) {
|
|
406
553
|
if (str.charCodeAt(i+1) === SEMIC) i=str.indexOf(';)', i)+2; // (; ... ;)
|
|
407
|
-
else i++, (root=level).push(level=[]), parseLevel(), level=root;
|
|
554
|
+
else commit(), i++, (root=level).push(level=[]), parseLevel(), level=root;
|
|
408
555
|
}
|
|
409
556
|
else if (c === SEMIC) i=str.indexOf('\n', i)+1; // ; ...
|
|
410
557
|
else if (c <= SPACE) commit(), i++;
|
|
411
|
-
else if (c === CPAREN) return commit(), i
|
|
558
|
+
else if (c === CPAREN) return commit(), i++
|
|
412
559
|
else buf+=str[i++];
|
|
413
560
|
}
|
|
414
561
|
|
|
@@ -418,11 +565,11 @@ var parse = (str) => {
|
|
|
418
565
|
parseLevel();
|
|
419
566
|
|
|
420
567
|
return level.length>1 ? level : level[0]
|
|
421
|
-
};
|
|
422
|
-
|
|
568
|
+
};
|
|
569
|
+
|
|
423
570
|
var watr = src => (
|
|
424
571
|
src = typeof src === 'string' ? parse(src) : src,
|
|
425
572
|
compile(src)
|
|
426
|
-
);
|
|
427
|
-
|
|
428
|
-
export { compile, watr as default, parse };
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
export { compile, watr as default, parse };
|