watr 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/readme.md +183 -151
  3. package/watr.js +564 -564
package/watr.js CHANGED
@@ -1,575 +1,575 @@
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
- ];
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
102
50
  }
103
51
 
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
106
- const OP = [
107
- 'unreachable', 'nop', 'block', 'loop', 'if', 'else', ,,,,,
108
- 'end', 'br', 'br_if', 'br_table', 'return', 'call', 'call_indirect', ,,,,,,,,
109
- 'drop', 'select', ,,,,
110
- 'local.get', 'local.set', 'local.tee', 'global.get', 'global.set', ,,,
111
- 'i32.load', 'i64.load', 'f32.load', 'f64.load',
112
- 'i32.load8_s', 'i32.load8_u', 'i32.load16_s', 'i32.load16_u',
113
- 'i64.load8_s', 'i64.load8_u', 'i64.load16_s', 'i64.load16_u', 'i64.load32_s', 'i64.load32_u',
114
- 'i32.store', 'i64.store', 'f32.store', 'f64.store',
115
- 'i32.store8', 'i32.store16', 'i64.store8', 'i64.store16', 'i64.store32',
116
- 'memory.size', 'memory.grow',
117
- 'i32.const', 'i64.const', 'f32.const', 'f64.const',
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',
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',
120
- 'f32.eq', 'f32.ne', 'f32.lt', 'f32.gt', 'f32.le', 'f32.ge',
121
- 'f64.eq', 'f64.ne', 'f64.lt', 'f64.gt', 'f64.le', 'f64.ge',
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',
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',
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',
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',
126
- 'i32.wrap_i64',
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',
128
- 'i64.trunc_f32_s', 'i64.trunc_f32_u', 'i64.trunc_f64_s', 'i64.trunc_f64_u',
129
- 'f32.convert_i32_s', 'f32.convert_i32_u', 'f32.convert_i64_s', 'f32.convert_i64_u', 'f32.demote_f64',
130
- 'f64.convert_i32_s', 'f64.convert_i32_u', 'f64.convert_i64_s', 'f64.convert_i64_u', 'f64.promote_f32',
131
- 'i32.reinterpret_f32', 'i64.reinterpret_f64', 'f32.reinterpret_i32', 'f64.reinterpret_i64',
132
- ],
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 },
136
- ALIGN = {
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,
142
- };
143
-
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
+
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
106
+ const OP = [
107
+ 'unreachable', 'nop', 'block', 'loop', 'if', 'else', ,,,,,
108
+ 'end', 'br', 'br_if', 'br_table', 'return', 'call', 'call_indirect', ,,,,,,,,
109
+ 'drop', 'select', ,,,,
110
+ 'local.get', 'local.set', 'local.tee', 'global.get', 'global.set', ,,,
111
+ 'i32.load', 'i64.load', 'f32.load', 'f64.load',
112
+ 'i32.load8_s', 'i32.load8_u', 'i32.load16_s', 'i32.load16_u',
113
+ 'i64.load8_s', 'i64.load8_u', 'i64.load16_s', 'i64.load16_u', 'i64.load32_s', 'i64.load32_u',
114
+ 'i32.store', 'i64.store', 'f32.store', 'f64.store',
115
+ 'i32.store8', 'i32.store16', 'i64.store8', 'i64.store16', 'i64.store32',
116
+ 'memory.size', 'memory.grow',
117
+ 'i32.const', 'i64.const', 'f32.const', 'f64.const',
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',
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',
120
+ 'f32.eq', 'f32.ne', 'f32.lt', 'f32.gt', 'f32.le', 'f32.ge',
121
+ 'f64.eq', 'f64.ne', 'f64.lt', 'f64.gt', 'f64.le', 'f64.ge',
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',
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',
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',
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',
126
+ 'i32.wrap_i64',
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',
128
+ 'i64.trunc_f32_s', 'i64.trunc_f32_u', 'i64.trunc_f64_s', 'i64.trunc_f64_u',
129
+ 'f32.convert_i32_s', 'f32.convert_i32_u', 'f32.convert_i64_s', 'f32.convert_i64_u', 'f32.demote_f64',
130
+ 'f64.convert_i32_s', 'f64.convert_i32_u', 'f64.convert_i64_s', 'f64.convert_i64_u', 'f64.promote_f32',
131
+ 'i32.reinterpret_f32', 'i64.reinterpret_f64', 'f32.reinterpret_i32', 'f64.reinterpret_i64',
132
+ ],
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 },
136
+ ALIGN = {
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,
142
+ };
143
+
144
144
  OP.map((op,i)=>OP[op]=i); // init op names
145
145
 
146
- // some inlinable instructions
147
- const INLINE = {loop: 1, block: 1, if: 1, end: -1, return: -1};
148
-
149
- // convert wat tree to wasm binary
150
- var compile = (nodes) => {
151
- // IR. Alias is stored directly to section array by key, eg. section.func.$name = idx
152
- let sections = {
153
- type: [], import: [], func: [], table: [], memory: [], global: [], export: [], start: [], elem: [], code: [], data: []
154
- }, binary = [
155
- 0x00, 0x61, 0x73, 0x6d, // magic
156
- 0x01, 0x00, 0x00, 0x00, // version
157
- ];
158
-
159
- // 1. transform tree
160
- // (func) → [(func)]
161
- if (typeof nodes[0] === 'string' && nodes[0] !== 'module') nodes = [nodes];
162
-
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) {
181
- let remaining = [];
182
- for (let node of nodes) {
183
- node[0] === name ? postcall.push(build[name](node, sections)) : remaining.push(node);
184
- }
185
-
186
- nodes = remaining;
187
- }
188
-
189
- // code must be compiled after all definitions
190
- for (let cb of postcall) cb && cb.call && cb();
191
-
192
-
193
- // 3. build binary
194
- for (let name in sections) {
195
- let items=sections[name];
196
- if (items.importc) items = items.slice(items.importc); // discard imported functions/globals
197
- if (!items.length) continue
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);
202
- }
203
-
204
- return new Uint8Array(binary)
205
- };
206
-
207
- const build = {
208
- // (type $name? (func (param $x i32) (param i64 i32) (result i32 i64)))
209
- // signature part is identical to function
210
- // FIXME: handle non-function types
211
- type([, typeName, decl], ctx) {
212
- if (typeName[0]!=='$') decl=typeName, typeName=null;
213
- let params = [], result = [], [kind,...sig] = decl, idx, bytes;
214
-
215
- if (kind==='func') {
216
- // collect params
217
- while (sig[0]?.[0] === 'param') {
218
- let [, ...types] = sig.shift();
219
- if (types[0]?.[0] === '$') params[types.shift()] = params.length;
220
- params.push(...types.map(t => TYPE[t]));
221
- }
222
-
223
- // collect result type
224
- if (sig[0]?.[0] === 'result') result = sig.shift().slice(1).map(t => TYPE[t]);
225
-
226
- // reuse existing type or register new one
227
- bytes = [TYPE.func, ...uleb(params.length), ...params, ...uleb(result.length), ...result];
228
-
229
- idx = ctx.type.findIndex((prevType) => prevType.every((byte, i) => byte === bytes[i]));
230
- if (idx < 0) idx = ctx.type.push(bytes)-1;
231
- }
232
-
233
- if (typeName) ctx.type[typeName] = idx;
234
-
235
- return [idx, params, result]
236
- },
237
-
238
- // (func $name? ...params result ...body)
239
- func([,...body], ctx) {
240
- let locals=[], // list of local variables
241
- callstack=[];
242
-
243
- // fn name
244
- if (body[0]?.[0] === '$') ctx.func[body.shift()] = ctx.func.length;
245
-
246
- // export binding
247
- if (body[0]?.[0] === 'export') build.export([...body.shift(), ['func', ctx.func.length]], ctx);
248
-
249
- // register type
250
- let [typeIdx, params, result] = build.type([,['func',...body]], ctx);
251
- // FIXME: try merging with build.type: it should be able to consume body
252
- while (body[0]?.[0] === 'param' || body[0]?.[0] === 'result') body.shift();
253
- ctx.func.push([typeIdx]);
254
-
255
- // collect locals
256
- while (body[0]?.[0] === 'local') {
257
- let [, ...types] = body.shift(), name;
258
- if (types[0][0]==='$')
259
- params[name=types.shift()] ? err('Ambiguous name '+name) :
260
- locals[name] = params.length + locals.length;
261
- locals.push(...types.map(t => TYPE[t]));
262
- }
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
-
267
- // map code instruction into bytes: [args, opCode, immediates]
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;
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
- }
295
-
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
- }
300
-
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
- }
306
-
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
- }
312
-
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
- }
320
-
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
- }
328
-
329
- // FIXME (memory.grow $idx?)
330
- else if (opCode==63||opCode==64) {
331
- after = [0];
332
- argc = 1;
333
- }
334
-
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; }
357
-
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
369
- }
370
-
371
- // (end)
372
- else if (opCode==0x0b) callstack.pop();
373
-
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
- }
381
-
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
- }
389
-
390
- else if (opCode==null) err(`Unknown instruction \`${op}\``);
391
- }
392
-
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]
399
- };
400
-
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
- };
420
-
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
- }
426
- },
427
-
428
- // (memory min max shared)
429
- // (memory $name min max shared)
430
- // (memory (export "mem") 5)
431
- memory([, ...parts], ctx) {
432
- if (parts[0][0]==='$') ctx.memory[parts.shift()] = ctx.memory.length;
433
- if (parts[0][0] === 'export') build.export([...parts.shift(), ['memory', ctx.memory.length]], ctx);
434
- ctx.memory.push(range(parts));
435
- },
436
-
437
- // (global i32 (i32.const 42))
438
- // (global $id i32 (i32.const 42))
439
- // (global $id (mut i32) (i32.const 42))
440
- global([, ...args], ctx) {
441
- let name = args[0][0]==='$' && args.shift();
442
- if (name) ctx.global[name] = ctx.global.length;
443
- let [type, init] = args, mut = type[0] === 'mut' ? 1 : 0;
444
- ctx.global.push([TYPE[mut ? type[1] : type], mut, ...iinit(init)]);
445
- },
446
-
447
- // (table 1 2? funcref)
448
- // (table $name 1 2? funcref)
449
- table([, ...args], ctx) {
450
- let name = args[0][0]==='$' && args.shift();
451
- if (name) ctx.table[name] = ctx.table.length;
452
- let lims = range(args);
453
- ctx.table.push([TYPE[args.pop()], ...lims]);
454
- },
455
-
456
- // (elem (i32.const 0) $f1 $f2), (elem (global.get 0) $f1 $f2)
457
- elem([, offset, ...elems], ctx) {
458
- const tableIdx = 0; // FIXME: table index can be defined
459
- ctx.elem.push([tableIdx, ...iinit(offset, ctx), ...uleb(elems.length), ...elems.flatMap(el => uleb(el[0]==='$' ? ctx.func[el] : el))]);
460
- },
461
-
462
- // (export "name" (kind $name|idx))
463
- export([, name, [kind, idx]], ctx) {
464
- if (idx[0]==='$') idx = ctx[kind][idx];
465
- ctx.export.push([...str(name), KIND[kind], ...uleb(idx)]);
466
- },
467
-
468
- // (import "math" "add" (func $add (param i32 i32 externref) (result i32)))
469
- // (import "js" "mem" (memory 1))
470
- // (import "js" "mem" (memory $name 1))
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();
475
-
476
- if (kind==='func') {
477
- // we track imported funcs in func section to share namespace, and skip them on final build
478
- if (name) ctx.func[name] = ctx.func.length;
479
- let [typeIdx] = build.type([, ['func', ...parts]], ctx);
480
- ctx.func.push(details = uleb(typeIdx));
481
- ctx.func.importc = (ctx.func.importc||0)+1;
482
- }
483
- else if (kind==='memory') {
484
- if (name) ctx.memory[name] = ctx.memory.length;
485
- details = range(parts);
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)
496
-
497
- ctx.import.push([...str(mod), ...str(field), KIND[kind], ...details]);
498
- },
499
-
500
- // (data (i32.const 0) "\aa" "\bb"?)
501
- data([, offset, ...inits], ctx) {
502
- // FIXME: first is mem index
503
- ctx.data.push([0, ...iinit(offset,ctx), ...str(inits.map(i=>i[0]==='"'?i.slice(1,-1):i).join(''))]);
504
- },
505
-
506
- // (start $main)
507
- start([, name],ctx) {
508
- if (!ctx.start.length) ctx.start.push([name[0]==='$' ? ctx.func[name] : name]);
509
- }
510
- };
511
-
512
- // (i32.const 0) - instantiation time initializer
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};
518
-
519
- // build string binary
520
- const str = str => {
521
- str = str[0]==='"' ? str.slice(1,-1) : str;
522
- let res = [], i = 0, c, BSLASH=92;
523
- // spec https://webassembly.github.io/spec/core/text/values.html#strings
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));
530
- return res
531
- };
532
-
533
- // build range/limits sequence (non-consuming)
534
- const range = ([min, max, shared]) => isNaN(parseInt(max)) ? [0, ...uleb(min)] : [shared==='shared'?3:1, ...uleb(min), ...uleb(max)];
535
-
146
+ // some inlinable instructions
147
+ const INLINE = {loop: 1, block: 1, if: 1, end: -1, return: -1};
148
+
149
+ // convert wat tree to wasm binary
150
+ var compile = (nodes) => {
151
+ // IR. Alias is stored directly to section array by key, eg. section.func.$name = idx
152
+ let sections = {
153
+ type: [], import: [], func: [], table: [], memory: [], global: [], export: [], start: [], elem: [], code: [], data: []
154
+ }, binary = [
155
+ 0x00, 0x61, 0x73, 0x6d, // magic
156
+ 0x01, 0x00, 0x00, 0x00, // version
157
+ ];
158
+
159
+ // 1. transform tree
160
+ // (func) → [(func)]
161
+ if (typeof nodes[0] === 'string' && nodes[0] !== 'module') nodes = [nodes];
162
+
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) {
181
+ let remaining = [];
182
+ for (let node of nodes) {
183
+ node[0] === name ? postcall.push(build[name](node, sections)) : remaining.push(node);
184
+ }
185
+
186
+ nodes = remaining;
187
+ }
188
+
189
+ // code must be compiled after all definitions
190
+ for (let cb of postcall) cb && cb.call && cb();
191
+
192
+
193
+ // 3. build binary
194
+ for (let name in sections) {
195
+ let items=sections[name];
196
+ if (items.importc) items = items.slice(items.importc); // discard imported functions/globals
197
+ if (!items.length) continue
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);
202
+ }
203
+
204
+ return new Uint8Array(binary)
205
+ };
206
+
207
+ const build = {
208
+ // (type $name? (func (param $x i32) (param i64 i32) (result i32 i64)))
209
+ // signature part is identical to function
210
+ // FIXME: handle non-function types
211
+ type([, typeName, decl], ctx) {
212
+ if (typeName[0]!=='$') decl=typeName, typeName=null;
213
+ let params = [], result = [], [kind,...sig] = decl, idx, bytes;
214
+
215
+ if (kind==='func') {
216
+ // collect params
217
+ while (sig[0]?.[0] === 'param') {
218
+ let [, ...types] = sig.shift();
219
+ if (types[0]?.[0] === '$') params[types.shift()] = params.length;
220
+ params.push(...types.map(t => TYPE[t]));
221
+ }
222
+
223
+ // collect result type
224
+ if (sig[0]?.[0] === 'result') result = sig.shift().slice(1).map(t => TYPE[t]);
225
+
226
+ // reuse existing type or register new one
227
+ bytes = [TYPE.func, ...uleb(params.length), ...params, ...uleb(result.length), ...result];
228
+
229
+ idx = ctx.type.findIndex((prevType) => prevType.every((byte, i) => byte === bytes[i]));
230
+ if (idx < 0) idx = ctx.type.push(bytes)-1;
231
+ }
232
+
233
+ if (typeName) ctx.type[typeName] = idx;
234
+
235
+ return [idx, params, result]
236
+ },
237
+
238
+ // (func $name? ...params result ...body)
239
+ func([,...body], ctx) {
240
+ let locals=[], // list of local variables
241
+ callstack=[];
242
+
243
+ // fn name
244
+ if (body[0]?.[0] === '$') ctx.func[body.shift()] = ctx.func.length;
245
+
246
+ // export binding
247
+ if (body[0]?.[0] === 'export') build.export([...body.shift(), ['func', ctx.func.length]], ctx);
248
+
249
+ // register type
250
+ let [typeIdx, params, result] = build.type([,['func',...body]], ctx);
251
+ // FIXME: try merging with build.type: it should be able to consume body
252
+ while (body[0]?.[0] === 'param' || body[0]?.[0] === 'result') body.shift();
253
+ ctx.func.push([typeIdx]);
254
+
255
+ // collect locals
256
+ while (body[0]?.[0] === 'local') {
257
+ let [, ...types] = body.shift(), name;
258
+ if (types[0][0]==='$')
259
+ params[name=types.shift()] ? err('Ambiguous name '+name) :
260
+ locals[name] = params.length + locals.length;
261
+ locals.push(...types.map(t => TYPE[t]));
262
+ }
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
+
267
+ // map code instruction into bytes: [args, opCode, immediates]
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;
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
+ }
295
+
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
+ }
300
+
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
+ }
306
+
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
+ }
312
+
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
+ }
320
+
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
+ }
328
+
329
+ // FIXME (memory.grow $idx?)
330
+ else if (opCode==63||opCode==64) {
331
+ after = [0];
332
+ argc = 1;
333
+ }
334
+
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; }
357
+
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
369
+ }
370
+
371
+ // (end)
372
+ else if (opCode==0x0b) callstack.pop();
373
+
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
+ }
381
+
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
+ }
389
+
390
+ else if (opCode==null) err(`Unknown instruction \`${op}\``);
391
+ }
392
+
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]
399
+ };
400
+
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
+ };
420
+
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
+ }
426
+ },
427
+
428
+ // (memory min max shared)
429
+ // (memory $name min max shared)
430
+ // (memory (export "mem") 5)
431
+ memory([, ...parts], ctx) {
432
+ if (parts[0][0]==='$') ctx.memory[parts.shift()] = ctx.memory.length;
433
+ if (parts[0][0] === 'export') build.export([...parts.shift(), ['memory', ctx.memory.length]], ctx);
434
+ ctx.memory.push(range(parts));
435
+ },
436
+
437
+ // (global i32 (i32.const 42))
438
+ // (global $id i32 (i32.const 42))
439
+ // (global $id (mut i32) (i32.const 42))
440
+ global([, ...args], ctx) {
441
+ let name = args[0][0]==='$' && args.shift();
442
+ if (name) ctx.global[name] = ctx.global.length;
443
+ let [type, init] = args, mut = type[0] === 'mut' ? 1 : 0;
444
+ ctx.global.push([TYPE[mut ? type[1] : type], mut, ...iinit(init)]);
445
+ },
446
+
447
+ // (table 1 2? funcref)
448
+ // (table $name 1 2? funcref)
449
+ table([, ...args], ctx) {
450
+ let name = args[0][0]==='$' && args.shift();
451
+ if (name) ctx.table[name] = ctx.table.length;
452
+ let lims = range(args);
453
+ ctx.table.push([TYPE[args.pop()], ...lims]);
454
+ },
455
+
456
+ // (elem (i32.const 0) $f1 $f2), (elem (global.get 0) $f1 $f2)
457
+ elem([, offset, ...elems], ctx) {
458
+ const tableIdx = 0; // FIXME: table index can be defined
459
+ ctx.elem.push([tableIdx, ...iinit(offset, ctx), ...uleb(elems.length), ...elems.flatMap(el => uleb(el[0]==='$' ? ctx.func[el] : el))]);
460
+ },
461
+
462
+ // (export "name" (kind $name|idx))
463
+ export([, name, [kind, idx]], ctx) {
464
+ if (idx[0]==='$') idx = ctx[kind][idx];
465
+ ctx.export.push([...str(name), KIND[kind], ...uleb(idx)]);
466
+ },
467
+
468
+ // (import "math" "add" (func $add (param i32 i32 externref) (result i32)))
469
+ // (import "js" "mem" (memory 1))
470
+ // (import "js" "mem" (memory $name 1))
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();
475
+
476
+ if (kind==='func') {
477
+ // we track imported funcs in func section to share namespace, and skip them on final build
478
+ if (name) ctx.func[name] = ctx.func.length;
479
+ let [typeIdx] = build.type([, ['func', ...parts]], ctx);
480
+ ctx.func.push(details = uleb(typeIdx));
481
+ ctx.func.importc = (ctx.func.importc||0)+1;
482
+ }
483
+ else if (kind==='memory') {
484
+ if (name) ctx.memory[name] = ctx.memory.length;
485
+ details = range(parts);
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)
496
+
497
+ ctx.import.push([...str(mod), ...str(field), KIND[kind], ...details]);
498
+ },
499
+
500
+ // (data (i32.const 0) "\aa" "\bb"?)
501
+ data([, offset, ...inits], ctx) {
502
+ // FIXME: first is mem index
503
+ ctx.data.push([0, ...iinit(offset,ctx), ...str(inits.map(i=>i[0]==='"'?i.slice(1,-1):i).join(''))]);
504
+ },
505
+
506
+ // (start $main)
507
+ start([, name],ctx) {
508
+ if (!ctx.start.length) ctx.start.push([name[0]==='$' ? ctx.func[name] : name]);
509
+ }
510
+ };
511
+
512
+ // (i32.const 0) - instantiation time initializer
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};
518
+
519
+ // build string binary
520
+ const str = str => {
521
+ str = str[0]==='"' ? str.slice(1,-1) : str;
522
+ let res = [], i = 0, c, BSLASH=92;
523
+ // spec https://webassembly.github.io/spec/core/text/values.html#strings
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));
530
+ return res
531
+ };
532
+
533
+ // build range/limits sequence (non-consuming)
534
+ const range = ([min, max, shared]) => isNaN(parseInt(max)) ? [0, ...uleb(min)] : [shared==='shared'?3:1, ...uleb(min), ...uleb(max)];
535
+
536
536
  const err = text => { throw Error(text) };
537
537
 
538
- const OPAREN=40, CPAREN=41, SPACE=32, DQUOTE=34, SEMIC=59;
539
-
540
- var parse = (str) => {
541
- let i = 0, level = [], buf='';
542
-
543
- const commit = () => buf && (
544
- level.push(buf[0]!=='"' && ~buf.indexOf('=') ? buf.split('=') : buf),
545
- buf = ''
546
- );
547
-
548
- const parseLevel = () => {
549
- for (let c, root; i < str.length; ) {
550
- c = str.charCodeAt(i);
551
- if (c === DQUOTE) commit(), buf = str.slice(i++, i=str.indexOf('"', i)+1), commit();
552
- else if (c === OPAREN) {
553
- if (str.charCodeAt(i+1) === SEMIC) i=str.indexOf(';)', i)+2; // (; ... ;)
554
- else commit(), i++, (root=level).push(level=[]), parseLevel(), level=root;
555
- }
556
- else if (c === SEMIC) i=str.indexOf('\n', i)+1; // ; ...
557
- else if (c <= SPACE) commit(), i++;
558
- else if (c === CPAREN) return commit(), i++
559
- else buf+=str[i++];
560
- }
561
-
562
- commit();
563
- };
564
-
565
- parseLevel();
566
-
567
- return level.length>1 ? level : level[0]
538
+ const OPAREN=40, CPAREN=41, SPACE=32, DQUOTE=34, SEMIC=59;
539
+
540
+ var parse = (str) => {
541
+ let i = 0, level = [], buf='';
542
+
543
+ const commit = () => buf && (
544
+ level.push(buf[0]!=='"' && ~buf.indexOf('=') ? buf.split('=') : buf),
545
+ buf = ''
546
+ );
547
+
548
+ const parseLevel = () => {
549
+ for (let c, root; i < str.length; ) {
550
+ c = str.charCodeAt(i);
551
+ if (c === DQUOTE) commit(), buf = str.slice(i++, i=str.indexOf('"', i)+1), commit();
552
+ else if (c === OPAREN) {
553
+ if (str.charCodeAt(i+1) === SEMIC) i=str.indexOf(';)', i)+2; // (; ... ;)
554
+ else commit(), i++, (root=level).push(level=[]), parseLevel(), level=root;
555
+ }
556
+ else if (c === SEMIC) i=str.indexOf('\n', i)+1; // ; ...
557
+ else if (c <= SPACE) commit(), i++;
558
+ else if (c === CPAREN) return commit(), i++
559
+ else buf+=str[i++];
560
+ }
561
+
562
+ commit();
563
+ };
564
+
565
+ parseLevel();
566
+
567
+ return level.length>1 ? level : level[0]
568
568
  };
569
569
 
570
- var watr = src => (
571
- src = typeof src === 'string' ? parse(src) : src,
572
- compile(src)
570
+ var watr = src => (
571
+ src = typeof src === 'string' ? parse(src) : src,
572
+ compile(src)
573
573
  );
574
574
 
575
575
  export { compile, watr as default, parse };