watr 1.3.2 → 1.4.1

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