watr 2.4.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "watr",
3
- "version": "2.4.1",
3
+ "version": "3.0.0",
4
4
  "description": "Ligth & fast WAT compiler",
5
5
  "main": "watr.js",
6
6
  "exports": {
@@ -11,7 +11,8 @@
11
11
  },
12
12
  "type": "module",
13
13
  "scripts": {
14
- "test": "node test"
14
+ "test": "node test",
15
+ "postinstall": "git submodule update --init --recursive"
15
16
  },
16
17
  "repository": {
17
18
  "type": "git",
@@ -30,7 +31,9 @@
30
31
  "wabt",
31
32
  "pretty-print",
32
33
  "webassembly",
33
- "wasm-text"
34
+ "wasm-text",
35
+ "wast",
36
+ "wat-compiler"
34
37
  ],
35
38
  "author": "Dmitry Iv",
36
39
  "license": "MIT",
package/readme.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # watr [![test](https://github.com/audio-lab/watr/actions/workflows/test.js.yml/badge.svg)](https://github.com/audio-lab/watr/actions/workflows/test.js.yml) [![npm bundle size](https://img.shields.io/bundlephobia/minzip/watr/latest?color=brightgreen&label=gzip)](https://bundlephobia.com/package/watr) [![npm](https://img.shields.io/npm/v/watr?color=red)](https://npmjs.org/watr)
2
2
 
3
- Bare minimum wasm text compiler/formatter. A light & fast alternative to [wat2wasm](https://github.com/AssemblyScript/wabt.js).<br/>
3
+ Light & fast WASM compiler. An alternative to [wabt/wat2wasm](https://github.com/AssemblyScript/wabt.js).<br/>
4
4
  Useful for hi-level languages or dynamic (in-browser) compilation.<br>
5
5
 
6
6
  ## Usage
@@ -10,7 +10,7 @@ Useful for hi-level languages or dynamic (in-browser) compilation.<br>
10
10
  Compile wasm text or syntax tree into wasm binary.
11
11
 
12
12
  ```js
13
- import compile from 'watr' // or `import { compile } from 'watr'`
13
+ import { compile } from 'watr'
14
14
 
15
15
  const buffer = compile(`(func (export "double")
16
16
  (param f64) (result f64)
@@ -23,6 +23,20 @@ const {double} = instance.exports
23
23
  double(108) // 216
24
24
  ```
25
25
 
26
+ ### Parse
27
+
28
+ Parse input wasm text into syntax tree.
29
+
30
+ ```js
31
+ import { parse } from 'watr'
32
+
33
+ parse(`(func (export "double") (param f64) (result f64) (f64.mul (local.get 0) (f64.const 2)))`)
34
+ // [
35
+ // 'func', ['export', '"double"'], ['param', 'f64'], ['result', 'f64'],
36
+ // ['f64.mul', ['local.get', 0], ['f64.const', 2]]
37
+ // ]
38
+ ```
39
+
26
40
  ### Print
27
41
 
28
42
  Format input wasm text or syntax tree into minified or pretty form.
@@ -54,54 +68,55 @@ print(src, {
54
68
  // (func (export "double")(param f64)(result f64)(f64.mul (local.get 0)(f64.const 2)))
55
69
  ```
56
70
 
57
- ### Parse
58
-
59
- Parse input wasm text into syntax tree.
60
-
61
- ```js
62
- import { parse } from 'watr'
63
-
64
- parse(`(func (export "double") (param f64) (result f64) (f64.mul (local.get 0) (f64.const 2)))`)
65
- // [
66
- // 'func', ['export', '"double"'], ['param', 'f64'], ['result', 'f64'],
67
- // ['f64.mul', ['local.get', 0], ['f64.const', 2]]
68
- // ]
69
- ```
70
-
71
71
  <!-- See [REPL](https://audio-lab.github.io/watr/repl.html).-->
72
72
 
73
73
  ## Status
74
74
 
75
75
  * [x] core
76
+ * [x] [mutable_globals](https://github.com/WebAssembly/mutable-global)
77
+ * [x] [extended const](https://github.com/WebAssembly/extended-const/blob/main/proposals/extended-const/Overview.md)
78
+ * [ ] [sat_float_to_int](https://github.com/WebAssembly/nontrapping-float-to-int-conversions)
79
+ * [ ] [sign_extension](https://github.com/WebAssembly/sign-extension-ops)
76
80
  * [x] [multi-value](https://github.com/WebAssembly/spec/blob/master/proposals/multi-value/Overview.md)
77
81
  * [x] [bulk memory ops](https://github.com/WebAssembly/bulk-memory-operations/blob/master/proposals/bulk-memory-operations/Overview.md)
82
+ * [ ] [memory64](https://github.com/WebAssembly/memory64)
83
+ * [x] [multiple memories](https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md)
78
84
  * [x] [simd](https://github.com/WebAssembly/simd/blob/master/proposals/simd/SIMD.md)
79
- * [x] [extended const](https://github.com/WebAssembly/extended-const/blob/main/proposals/extended-const/Overview.md)
80
- * [ ] [multiple memories](https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md)
81
- * [ ] [func refs](https://github.com/WebAssembly/function-references/blob/main/proposals/function-references/Overview.md)
85
+ * [ ] [relaxed_simd](https://github.com/WebAssembly/relaxed-simd)
86
+ * [x] [ref types](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md)
87
+ * [x] [func refs](https://github.com/WebAssembly/function-references/blob/main/proposals/function-references/Overview.md)
82
88
  * [ ] [gc](https://github.com/WebAssembly/gc)
89
+ * [ ] [exceptions](https://github.com/WebAssembly/exception-handling)
90
+ * [ ] [threads](https://github.com/WebAssembly/threads)
91
+ * [ ] [tail_call](https://github.com/WebAssembly/tail-call)
92
+ * [ ] [annotations](https://github.com/WebAssembly/annotations)
93
+ * [ ] [code_metadata](https://github.com/WebAssembly/tool-conventions/blob/main/CodeMetadata.md)
83
94
 
84
95
  ## Alternatives
85
96
 
86
97
  &nbsp; | Size (gzipped) | Performance (op/s)
87
98
  ---|---|---
88
99
  watr | 5 kb | 6000
89
- [wat-compiler](https://github.com/stagas/wat-compiler) | 6 kb | 348
90
100
  [wabt](https://github.com/AssemblyScript/wabt.js) | 300 kb | 574
101
+ [wat-compiler](https://github.com/stagas/wat-compiler) | 6 kb | 348
91
102
  <!-- [wassemble](https://github.com/wingo/wassemble) | ? kb | ? -->
92
103
 
104
+ <!-- Watr has better syntax support than wabt & produces more compact output due to types squash. -->
105
+
93
106
  <!--
94
107
  ## Projects using watr
95
108
 
96
- * [auro](https://github.com/audio-lab/auro) – audio processing language
109
+ * [piezo](https://github.com/audio-lab/piezo) – audio processing language
97
110
  -->
98
111
 
112
+ <!--
99
113
  ## Useful links
100
114
 
101
115
  * [watlings](https://github.com/EmNudge/watlings) – learn Wasm text by examples.
102
116
  * [MDN: control flow](https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Control_flow)
103
117
  * [WASM reference manual](https://github.com/sunfishcode/wasm-reference-manual/blob/master/WebAssembly.md#loop)
104
118
  * [WASM binary encoding](https://github.com/WebAssembly/design/blob/main/BinaryEncoding.md)
119
+ -->
105
120
 
106
121
  <!--
107
122
  ## Refs
package/src/compile.js CHANGED
@@ -1,140 +1,203 @@
1
1
  import * as encode from './encode.js'
2
2
  import { uleb } from './encode.js'
3
- import { OP, SECTION, ALIGN, TYPE, KIND } from './const.js'
3
+ import { SECTION, ALIGN, TYPE, KIND, INSTR } from './const.js'
4
4
  import parse from './parse.js'
5
- import { err } from './util.js'
6
5
 
6
+ // build instructions index
7
+ INSTR.forEach((instr, i) => {
8
+ let [op, ...imm] = instr.split(':'), a, b
9
+
10
+ // TODO
11
+ // wrap codes
12
+ // const code = i >= 0x10f ? [0xfd, i - 0x10f] : i >= 0xfc ? [0xfc, i - 0xfc] : i
13
+ INSTR[op] = i
14
+
15
+ // // handle immediates
16
+ // INSTR[op] = !imm.length ? () => code :
17
+ // imm.length === 1 ? (a = immedname(imm[0]), nodes => [...code, ...a(nodes)]) :
18
+ // (imm = imm.map(immedname), nodes => [...code, ...imm.flatMap(imm => imm(nodes))])
19
+ })
7
20
 
8
21
  /**
9
- * Converts a WebAssembly Text Format (WAT) tree to a WebAssembly binary format (Wasm).
22
+ * Converts a WebAssembly Text Format (WAT) tree to a WebAssembly binary format (WASM).
10
23
  *
11
- * @param {string|Array} nodes - The WAT tree or string to be compiled to Wasm binary.
12
- * @returns {Uint8Array} The compiled Wasm binary data.
24
+ * @param {string|Array} nodes - The WAT tree or string to be compiled to WASM binary.
25
+ * @returns {Uint8Array} The compiled WASM binary data.
13
26
  */
14
27
  export default (nodes) => {
15
- if (typeof nodes === 'string') nodes = parse(nodes);
28
+ // normalize to (module ...) form
29
+ if (typeof nodes === 'string') nodes = parse(nodes); else nodes = [...nodes]
30
+
31
+ // module abbr https://webassembly.github.io/spec/core/text/modules.html#id10
32
+ if (nodes[0] === 'module') nodes.shift(), id(nodes)
33
+ else if (typeof nodes[0] === 'string') nodes = [nodes]
34
+
35
+ // Scopes are stored directly on section array by key, eg. section.func.$name = idx
36
+ // FIXME: make direct binary instead (faster)
37
+ const sections = []
38
+ for (let kind in SECTION) sections.push(sections[kind] = [])
16
39
 
17
- // IR. Alias is stored directly to section array by key, eg. section.func.$name = idx
18
- let sections = {
19
- type: [], import: [], func: [], table: [], memory: [], global: [], export: [], start: [], elem: [], code: [], data: []
20
- }, binary = [
40
+ const binary = [
21
41
  0x00, 0x61, 0x73, 0x6d, // magic
22
42
  0x01, 0x00, 0x00, 0x00, // version
23
43
  ]
24
44
 
25
- // 1. transform tree
26
- // (func) [(func)]
27
- if (typeof nodes[0] === 'string' && nodes[0] !== 'module') nodes = [nodes]
28
-
29
- // (global $a (import "a" "b") (mut i32)) → (import "a" "b" (global $a (mut i32)))
30
- // (memory (import "a" "b") min max shared) → (import "a" "b" (memory min max shared))
31
- nodes = nodes.map(node => {
32
- if (node[2]?.[0] === 'import') {
33
- let [kind, name, imp, ...args] = node
34
- return [...imp, [kind, name, ...args]]
45
+ // sort nodes by sections
46
+ // TODO: make this more elegant
47
+ let nodeGroups = []
48
+ for (let kind in SECTION) nodeGroups.push(nodeGroups[kind] = [])
49
+
50
+ for (let [kind, ...node] of nodes) {
51
+ // index, alias
52
+ let name = id(node), idx = nodeGroups[kind].length;
53
+ if (name) sections[kind][name] = idx; // save alias
54
+
55
+ // export abbr
56
+ // (table|memory|global|func id? (export n)* ...) -> (table|memory|global|func id ...) (export n (table|memory|global|func id))
57
+ while (node[0]?.[0] === 'export') nodeGroups.export.push([node.shift()[1], [kind, idx]])
58
+
59
+ // import abbr
60
+ // (table|memory|global|func id? (import m n) type) -> (import m n (table|memory|global|func id? type))
61
+ if (node[0]?.[0] === 'import') node = [...node.shift(), [kind, ...(name ? [name] : []), ...node]], kind = node.shift()
62
+
63
+ // table abbr
64
+ // (table id? reftype (elem ...{n})) -> (table id? n n reftype) (elem (table id) (i32.const 0) reftype ...)
65
+ if (node[1]?.[0] === 'elem') {
66
+ let [reftype, [, ...els]] = node
67
+ node = [els.length, els.length, reftype]
68
+ nodeGroups.elem.push([['table', name || nodeGroups.table.length], ['i32.const', '0'], typeof els[0] === 'string' ? 'func' : reftype, ...els])
35
69
  }
36
- else if (node[1]?.[0] === 'import') {
37
- let [kind, imp, ...args] = node
38
- return [...imp, [kind, ...args]]
70
+
71
+ // data abbr
72
+ // (memory id? (data str)) -> (memory id? n n) (data (memory id) (i32.const 0) str)
73
+ if (node[0]?.[0] === 'data') {
74
+ let [,...data] = node.shift(), m = ''+Math.ceil(data.map(s => s.slice(1,-1)).join('').length / 65536) // FIXME: figure out actual data size
75
+ nodeGroups.data.push([['memory', idx], ['i32.const',0], ...data])
76
+ node = [m, m]
39
77
  }
40
- return node
41
- })
42
78
 
43
- // 2. build IR. import must be initialized first, global before func, elem after func
44
- let order = ['type', 'import', 'table', 'memory', 'global', 'func', 'export', 'start', 'elem', 'data'], postcall = []
45
79
 
46
- for (let name of order) {
47
- let remaining = []
48
- for (let node of nodes) {
49
- node[0] === name ? postcall.push(build[name](node, sections)) : remaining.push(node)
80
+ // import increments corresponding section index
81
+ // FIXME: can be turned into shallow node
82
+ if (kind === 'import') {
83
+ let [mod, field, [kind, ...dfn]] = node
84
+ let name = id(dfn)
85
+ if (name) sections[kind][name] = nodeGroups[kind].length
86
+ nodeGroups[kind].length++
87
+ node[2] = [kind, ...dfn]
50
88
  }
51
- nodes = remaining
89
+ else if (kind === 'start') {name && node.unshift(name);}
90
+
91
+ nodeGroups[kind].push(node)
52
92
  }
53
93
 
54
- // code must be compiled after all definitions
55
- for (let cb of postcall) cb?.()
56
-
57
- // 3. build binary
58
- for (let name in sections) {
59
- let items = sections[name]
60
- if (items.importc) items = items.slice(items.importc) // discard imported functions/globals
61
- if (!items.length) continue
62
- let sectionCode = SECTION[name], bytes = []
63
- if (sectionCode !== 8) bytes.push(items.length) // skip start section count
64
- for (let item of items) bytes.push(...item)
65
- binary.push(sectionCode, ...uleb(bytes.length), ...bytes)
94
+ // build sections binaries
95
+ for (let kind in SECTION) nodeGroups[kind].map((node,i) => !node ? [] : build[kind](i, node, sections))
96
+
97
+ // build final binary
98
+ for (let secCode = 0; secCode < sections.length; secCode++) {
99
+ let items = sections[secCode], bytes = [], count = 0
100
+ for (let item of items) {
101
+ if (!item) { continue } // ignore empty items (like import placeholders)
102
+ count++ // count number of items in section
103
+ bytes.push(...item)
104
+ }
105
+ // ignore empty sections
106
+ if (!bytes.length) continue
107
+ // skip start section count - write length
108
+ if (secCode !== 8) bytes.unshift(...uleb(count))
109
+ binary.push(secCode, ...vec(bytes))
66
110
  }
67
111
 
68
112
  return new Uint8Array(binary)
69
113
  }
70
114
 
115
+ // consume $id
116
+ const id = nodes => nodes[0]?.[0] === '$' && nodes.shift()
117
+
118
+ // build section binary (non consuming)
71
119
  const build = {
72
- // (type $name? (func (param $x i32) (param i64 i32) (result i32 i64)))
73
- // signature part is identical to function
74
- // FIXME: handle non-function types
75
- type([, typeName, [kind, ...sig]], ctx) {
76
- if (kind !== 'func') err(`Unknown type kind '${kind}'`)
77
- const [idx] = consumeType(sig, ctx)
78
- if (typeName) ctx.type[typeName] = idx
120
+ // (type $id? (func params result))
121
+ // we cannot squash types since indices can refer to them
122
+ type(idx, [...node], ctx) {
123
+ let [, ...sig] = node?.[0] || [], [param, result] = paramres(sig)
124
+
125
+ ctx.type[idx] = Object.assign(
126
+ [TYPE.func, ...vec(param.map(t => TYPE[t])), ...vec(result.map(t => TYPE[t]))],
127
+ { param, result } // save params for the type name
128
+ )
129
+ ctx.type[param + '>' + result] ??= idx // alias for quick search (don't increment if exists)
79
130
  },
80
131
 
81
- // (func $name? ...params result ...body)
82
- func([, ...body], ctx) {
83
- let locals = [], // list of local variables
84
- blocks = [] // control instructions / blocks stack
132
+ // (import "math" "add" (func|table|global|memory typedef?))
133
+ import(_, [mod, field, [kind, ...dfn]], ctx) {
134
+ let details
85
135
 
86
- // fn name
87
- if (body[0]?.[0] === '$') ctx.func[body.shift()] = ctx.func.length
136
+ if (kind === 'func') {
137
+ // we track imported funcs in func section to share namespace, and skip them on final build
138
+ let [typeIdx] = typeuse(dfn, ctx)
139
+ details = uleb(typeIdx)
140
+ }
141
+ else if (kind === 'memory') {
142
+ details = limits(dfn)
143
+ }
144
+ else if (kind === 'global') {
145
+ let [type] = dfn, mut = type[0] === 'mut' ? 1 : 0
146
+ details = [TYPE[mut ? type[1] : type], mut]
147
+ }
148
+ else if (kind === 'table') {
149
+ details = [TYPE[dfn.pop()], ...limits(dfn)]
150
+ }
151
+
152
+ ctx.import.push([...str(mod), ...str(field), KIND[kind], ...details])
153
+ },
88
154
 
89
- // export binding
90
- if (body[0]?.[0] === 'export') build.export([...body.shift(), ['func', ctx.func.length]], ctx)
155
+ // (func $name? ...params result ...body)
156
+ func(idx, [...node], ctx) {
157
+ const [typeidx, param, result] = typeuse(node, ctx)
91
158
 
92
- // register/consume type info
93
- let [typeIdx, params, result] = consumeType(body, ctx)
159
+ ctx.func[idx] = uleb(typeidx)
94
160
 
95
- // register new function
96
- ctx.func.push([typeIdx])
161
+ // build code section
162
+ let blocks = [] // control instructions / blocks stack
163
+ let locals = [] // list of local variables
97
164
 
98
165
  // collect locals
99
- while (body[0]?.[0] === 'local') {
100
- let [, ...types] = body.shift(), name
101
- if (types[0][0] === '$')
102
- params[name = types.shift()] ? err('Ambiguous name ' + name) :
103
- locals[name] = params.length + locals.length
166
+ while (node[0]?.[0] === 'local') {
167
+ let [, ...types] = node.shift(), name
168
+ if (types[0]?.[0] === '$')
169
+ param[name = types.shift()] ? err('Ambiguous name ' + name) : // FIXME: not supposed to happen
170
+ locals[name] = param.length + locals.length
104
171
  locals.push(...types.map(t => TYPE[t]))
105
172
  }
106
173
 
107
- // squash local types
108
- let locTypes = locals.reduce((a, type) => (type == a[a.length - 1] ? a[a.length - 2]++ : a.push(1, type), a), [])
109
-
110
174
  // convert sequence of instructions from input nodes to out bytes
175
+ // FIXME: make external func
111
176
  const consume = (nodes, out = []) => {
112
177
  if (!nodes?.length) return out
113
178
 
114
179
  let op = nodes.shift(), opCode, args = nodes, immed, id, group
115
180
 
116
- // groups are flattened, eg. (cmd z w) -> z w cmd
181
+ // flatten groups, eg. (cmd z w) -> z w cmd
117
182
  if (group = Array.isArray(op)) {
118
183
  args = [...op] // op is immutable
119
- opCode = OP.indexOf(op = args.shift())
184
+ opCode = INSTR[op = args.shift()]
120
185
  }
121
- else opCode = OP.indexOf(op)
122
-
123
- // NOTE: numeric comparison is faster than generic hash lookup
186
+ else opCode = INSTR[op]
124
187
 
125
188
  // v128s: (v128.load x) etc
126
189
  // https://github.com/WebAssembly/simd/blob/master/proposals/simd/BinarySIMD.md
127
- if (opCode >= 268) {
128
- opCode -= 268
190
+ if (opCode >= 0x10f) {
191
+ opCode -= 0x10f
129
192
  immed = [0xfd, ...uleb(opCode)]
130
193
  // (v128.load)
131
194
  if (opCode <= 0x0b) {
132
- const o = consumeParams(args)
195
+ const o = memarg(args)
133
196
  immed.push(Math.log2(o.align ?? ALIGN[op]), ...uleb(o.offset ?? 0))
134
197
  }
135
198
  // (v128.load_lane offset? align? idx)
136
199
  else if (opCode >= 0x54 && opCode <= 0x5d) {
137
- const o = consumeParams(args)
200
+ const o = memarg(args)
138
201
  immed.push(Math.log2(o.align ?? ALIGN[op]), ...uleb(o.offset ?? 0))
139
202
  // (v128.load_lane_zero)
140
203
  if (opCode <= 0x5b) immed.push(...uleb(args.shift()))
@@ -147,7 +210,7 @@ const build = {
147
210
  // (v128.const i32x4)
148
211
  else if (opCode === 0x0c) {
149
212
  args.unshift(op)
150
- immed = consumeConst(args, ctx)
213
+ immed = expr(args, ctx)
151
214
  }
152
215
  // (i8x16.extract_lane_s 0 ...)
153
216
  else if (opCode >= 0x15 && opCode <= 0x22) {
@@ -158,8 +221,8 @@ const build = {
158
221
 
159
222
  // bulk memory: (memory.init) (memory.copy) etc
160
223
  // https://github.com/WebAssembly/bulk-memory-operations/blob/master/proposals/bulk-memory-operations/Overview.md#instruction-encoding
161
- else if (opCode >= 252) {
162
- immed = [0xfc, ...uleb(opCode -= 252)]
224
+ else if (opCode >= 0xfc) {
225
+ immed = [0xfc, ...uleb(opCode -= 0xfc)]
163
226
  // memory.init idx, memory.drop idx, table.init idx, table.drop idx
164
227
  if (!(opCode & 0b10)) immed.push(...uleb(args.shift()))
165
228
  else immed.push(0)
@@ -168,13 +231,22 @@ const build = {
168
231
  opCode = null // ignore opcode
169
232
  }
170
233
 
234
+ // ref.func $id
235
+ else if (opCode == 0xd2) {
236
+ immed = uleb(args[0][0] === '$' ? ctx.func[args.shift()] : +args.shift())
237
+ }
238
+ // ref.null
239
+ else if (opCode == 0xd0) {
240
+ immed = [TYPE[args.shift() + 'ref']] // func->funcref, extern->externref
241
+ }
242
+
171
243
  // binary/unary (i32.add a b) - no immed
172
244
  else if (opCode >= 0x45) { }
173
245
 
174
246
  // (i32.store align=n offset=m at value) etc
175
- else if (opCode >= 40 && opCode <= 62) {
247
+ else if (opCode >= 0x28 && opCode <= 0x3e) {
176
248
  // FIXME: figure out point in Math.log2 aligns
177
- let o = consumeParams(args)
249
+ let o = memarg(args)
178
250
  immed = [Math.log2(o.align ?? ALIGN[op]), ...uleb(o.offset ?? 0)]
179
251
  }
180
252
 
@@ -184,32 +256,27 @@ const build = {
184
256
  }
185
257
 
186
258
  // (local.get $id), (local.tee $id x)
187
- else if (opCode >= 32 && opCode <= 34) {
188
- immed = uleb(args[0]?.[0] === '$' ? params[id = args.shift()] || locals[id] : args.shift())
259
+ else if (opCode >= 0x20 && opCode <= 0x22) {
260
+ immed = uleb(args[0]?.[0] === '$' ? param[id = args.shift()] ?? locals[id] ?? err('Unknown local ' + id) : +args.shift())
189
261
  }
190
262
 
191
- // (global.get id), (global.set id)
192
- else if (opCode == 0x23 || opCode == 36) {
193
- immed = uleb(args[0]?.[0] === '$' ? ctx.global[args.shift()] : args.shift())
263
+ // (global.get $id), (global.set $id)
264
+ else if (opCode == 0x23 || opCode == 0x24) {
265
+ immed = uleb(args[0]?.[0] === '$' ? ctx.global[args.shift()] : +args.shift())
194
266
  }
195
267
 
196
268
  // (call id ...nodes)
197
- else if (opCode == 16) {
269
+ else if (opCode == 0x10) {
198
270
  let fnName = args.shift()
199
- immed = uleb(id = fnName[0] === '$' ? ctx.func[fnName] ?? err('Unknown function `' + fnName + '`') : fnName);
271
+ immed = uleb(id = fnName[0] === '$' ? ctx.func[fnName] : +fnName);
200
272
  // FIXME: how to get signature of imported function
201
273
  }
202
274
 
203
- // (call_indirect (type $typeName) (idx) ...nodes)
204
- else if (opCode == 17) {
205
- let typeId = args.shift()[1];
206
- typeId = typeId[0] === '$' ? ctx.type[typeId] : typeId
207
- immed = uleb(typeId), immed.push(0) // extra immediate indicates table idx (reserved)
208
- }
209
-
210
- // FIXME multiple memory (memory.grow $idx?)
211
- else if (opCode == 63 || opCode == 64) {
212
- immed = [0]
275
+ // (call_indirect tableIdx? (type $typeName) (idx) ...nodes)
276
+ else if (opCode == 0x11) {
277
+ let tableidx = args[0]?.[0] === '$' ? ctx.table[args.shift()] : 0
278
+ let [typeidx] = typeuse(args, ctx)
279
+ immed = [...uleb(typeidx), ...uleb(tableidx)]
213
280
  }
214
281
 
215
282
  // (block ...), (loop ...), (if ...)
@@ -217,9 +284,9 @@ const build = {
217
284
  blocks.push(opCode)
218
285
 
219
286
  // (block $x) (loop $y)
220
- if (opCode < 4 && args[0]?.[0] === '$') (blocks[args.shift()] = blocks.length)
287
+ if (args[0]?.[0] === '$') (blocks[args.shift()] = blocks.length)
221
288
 
222
- // get type
289
+ // get type - can be either typeidx or valtype (numtype | reftype)
223
290
  // (result i32) - doesn't require registering type
224
291
  if (args[0]?.[0] === 'result' && args[0].length < 3) {
225
292
  let [, type] = args.shift()
@@ -227,8 +294,15 @@ const build = {
227
294
  }
228
295
  // (result i32 i32)
229
296
  else if (args[0]?.[0] === 'result' || args[0]?.[0] === 'param') {
230
- let [typeId] = consumeType(args, ctx)
231
- immed = [typeId]
297
+ let [typeidx] = typeuse(args, ctx)
298
+ immed = uleb(typeidx)
299
+ }
300
+ // FIXME: that def can be done nicer
301
+ else if (args[0]?.[0] === 'type') {
302
+ let [typeidx, params, result] = typeuse(args, ctx)
303
+ if (!params.length && !result.length) immed = [TYPE.void]
304
+ else if (!param.length && result.length === 1) immed = [TYPE[result[0]]]
305
+ else immed = uleb(typeidx)
232
306
  }
233
307
  else {
234
308
  immed = [TYPE.void]
@@ -239,7 +313,6 @@ const build = {
239
313
  nodes.unshift('end')
240
314
 
241
315
  if (opCode < 4) while (args.length) nodes.unshift(args.pop())
242
-
243
316
  // (if cond a) -> cond if a end
244
317
  else if (args.length < 3) nodes.unshift(args.pop())
245
318
  // (if cond (then a) (else b)) -> `cond if a else b end`
@@ -277,129 +350,187 @@ const build = {
277
350
  // (br_table 1 2 3 4 0 selector result?)
278
351
  else if (opCode == 0x0e) {
279
352
  immed = []
280
- while (!Array.isArray(args[0])) id = args.shift(), immed.push(...uleb(id[0][0] === '$' ? blocks.length - blocks[id] : id))
353
+ while (args[0] && !Array.isArray(args[0])) {
354
+ id = args.shift()
355
+ immed.push(...uleb(id[0][0] === '$' ? blocks.length - blocks[id] : id))
356
+ }
281
357
  immed.unshift(...uleb(immed.length - 1))
282
358
  }
283
- else if (opCode < 0) err(`Unknown instruction \`${op}\``)
359
+
360
+ // FIXME multiple memory (memory.grow $idx?)
361
+ else if (opCode == 0x3f || opCode == 0x40) {
362
+ immed = [0]
363
+ }
364
+
365
+ // (table.get $id)
366
+ else if (opCode == 0x25 || opCode == 0x26) {
367
+ immed = uleb(args[0]?.[0] === '$' ? ctx.table[args.shift()] : +args.shift())
368
+ }
369
+
370
+ // table.grow id, table.size id, table.fill id
371
+ else if (opCode >= 0x0f && opCode <= 0x11) {
372
+ immed = []
373
+ }
374
+
375
+ else if (opCode == null) err(`Unknown instruction \`${op}\``)
284
376
 
285
377
  // if group (cmd im1 im2 arg1 arg2) - insert any remaining args first: arg1 arg2
286
378
  // because inline case has them in stack already
287
- if (group) {
288
- while (args.length) consume(args, out)
289
- }
379
+ if (group) while (args.length) consume(args, out)
290
380
 
291
- if (opCode) out.push(opCode)
381
+ if (opCode != null) out.push(opCode)
292
382
  if (immed) out.push(...immed)
293
383
  }
294
384
 
295
- // evaluates after all definitions (need globals, elements, data etc.)
296
- // FIXME: get rid of this postcall
297
- return () => {
298
- const bytes = []
299
- while (body.length) consume(body, bytes)
300
- ctx.code.push([...uleb(bytes.length + 2 + locTypes.length), ...uleb(locTypes.length >> 1), ...locTypes, ...bytes, 0x0b])
301
- }
302
- },
385
+ const bytes = []
386
+ // FIXME: avoid passing bytes from outside, push result instead
387
+ while (node.length) consume(node, bytes)
388
+ bytes.push(0x0b)
389
+
390
+ // squash locals into (n:u32 t:valtype)*, n is number and t is type
391
+ let loctypes = locals.reduce((a, type) => (type == a[a.length - 1]?.[1] ? a[a.length - 1][0]++ : a.push([1, type]), a), [])
303
392
 
304
- // (memory min max shared)
305
- // (memory $name min max shared)
306
- // (memory (export "mem") 5)
307
- memory([, ...parts], ctx) {
308
- if (parts[0][0] === '$') ctx.memory[parts.shift()] = ctx.memory.length
309
- if (parts[0][0] === 'export') build.export([...parts.shift(), ['memory', ctx.memory.length]], ctx)
310
- ctx.memory.push(range(parts))
393
+ // https://webassembly.github.io/spec/core/binary/modules.html#code-section
394
+ ctx.code[idx] = vec([...uleb(loctypes.length), ...loctypes.flatMap(([n, t]) => [...uleb(n), t]), ...bytes])
311
395
  },
312
396
 
313
- // (global i32 (i32.const 42))
314
- // (global $id i32 (i32.const 42))
315
- // (global $id (mut i32) (i32.const 42))
316
- global([, ...args], ctx) {
317
- let name = args[0][0] === '$' && args.shift()
318
- if (name) ctx.global[name] = ctx.global.length
319
- let [type, [...init]] = args, mut = type[0] === 'mut' ? 1 : 0
320
- ctx.global.push([TYPE[mut ? type[1] : type], mut, ...consumeConst(init, ctx), 0x0b])
397
+ // (table id? 1 2? funcref)
398
+ table(idx, [...node], ctx) {
399
+ ctx.table[idx] = [TYPE[node.pop()], ...limits(node)]
321
400
  },
322
401
 
323
- // (table 1 2? funcref)
324
- // (table $name 1 2? funcref)
325
- table([, ...args], ctx) {
326
- let name = args[0][0] === '$' && args.shift()
327
- if (name) ctx.table[name] = ctx.table.length
328
- let lims = range(args)
329
- ctx.table.push([TYPE[args.pop()], ...lims])
402
+ // (memory id? export* min max shared)
403
+ memory(idx, [...node], ctx) {
404
+ ctx.memory[idx] = limits(node)
330
405
  },
331
406
 
332
- // (elem (i32.const 0) $f1 $f2), (elem (global.get 0) $f1 $f2)
333
- elem([, [...offset], ...elems], ctx) {
334
- const tableIdx = 0 // FIXME: table index can be defined
335
- ctx.elem.push([tableIdx, ...consumeConst(offset, ctx), 0x0b, ...uleb(elems.length), ...elems.flatMap(el => uleb(el[0] === '$' ? ctx.func[el] : el))])
407
+ // (global $id? (mut i32) (i32.const 42))
408
+ global(idx, [...node], ctx) {
409
+ let [type] = node, mut = type[0] === 'mut' ? 1 : 0
410
+
411
+ let [, [...init]] = node
412
+ ctx.global[idx] = [TYPE[mut ? type[1] : type], mut, ...expr(init, ctx), 0x0b]
336
413
  },
337
414
 
338
- // (export "name" (kind $name|idx))
339
- export([, name, [kind, idx]], ctx) {
340
- if (idx[0] === '$') idx = ctx[kind][idx]
341
- ctx.export.push([...str(name), KIND[kind], ...uleb(idx)])
415
+ // (export "name" (func|table|mem $name|idx))
416
+ export(_, [nm, [kind, id]], ctx) {
417
+ // put placeholder to future-init
418
+ let idx = id[0] === '$' ? ctx[kind][id] : +id
419
+ ctx.export.push([...str(nm), KIND[kind], ...uleb(idx)])
342
420
  },
343
421
 
344
- // (import "math" "add" (func $add (param i32 i32 externref) (result i32)))
345
- // (import "js" "mem" (memory 1))
346
- // (import "js" "mem" (memory $name 1))
347
- // (import "js" "v" (global $name (mut f64)))
348
- import([, mod, field, ref], ctx) {
349
- let details, [kind, ...parts] = ref,
350
- name = parts[0]?.[0] === '$' && parts.shift();
422
+ // (start $main)
423
+ start(_,[id], ctx) {
424
+ id = id[0] === '$' ? ctx.func[id] : +id
425
+ ctx.start[0] = uleb(id)
426
+ },
351
427
 
352
- if (kind === 'func') {
353
- // we track imported funcs in func section to share namespace, and skip them on final build
354
- if (name) ctx.func[name] = ctx.func.length
355
- let [typeIdx] = consumeType(parts, ctx)
356
- ctx.func.push(details = uleb(typeIdx))
357
- ctx.func.importc = (ctx.func.importc || 0) + 1
358
- }
359
- else if (kind === 'memory') {
360
- if (name) ctx.memory[name] = ctx.memory.length
361
- details = range(parts)
428
+ // ref: https://webassembly.github.io/spec/core/binary/modules.html#element-section
429
+ // passive: (elem elem*)
430
+ // declarative: (elem declare elem*)
431
+ // active: (elem (table idx)? (offset expr)|(expr) elem*)
432
+ // elems: funcref|externref (item expr)|expr (item expr)|expr
433
+ // idxs: func? $id0 $id1
434
+ elem(idx,[...parts], ctx) {
435
+ let tabidx, offset, mode = 0b000, reftype
436
+
437
+ // declare?
438
+ if (parts[0] === 'declare') parts.shift(), mode |= 0b010
439
+
440
+ // table?
441
+ if (parts[0][0] === 'table') {
442
+ [, tabidx] = parts.shift()
443
+ tabidx = tabidx[0] === '$' ? ctx.table[tabidx] : +tabidx
444
+ // ignore table=0
445
+ if (tabidx) mode |= 0b010
362
446
  }
363
- else if (kind === 'global') {
364
- // imported globals share namespace with internal globals - we skip them in final build
365
- if (name) ctx.global[name] = ctx.global.length
366
- let [type] = parts, mut = type[0] === 'mut' ? 1 : 0
367
- details = [TYPE[mut ? type[1] : type], mut]
368
- ctx.global.push(details)
369
- ctx.global.importc = (ctx.global.importc || 0) + 1
370
- }
371
- else throw Error('Unimplemented ' + kind)
372
447
 
373
- ctx.import.push([...str(mod), ...str(field), KIND[kind], ...details])
448
+ // (offset expr)|expr
449
+ if (parts[0]?.[0] === 'offset' || (Array.isArray(parts[0]) && parts[0][0] !== 'item' && !parts[0][0].startsWith('ref'))) {
450
+ [...offset] = parts.shift()
451
+ if (offset[0] === 'offset') [, [...offset]] = offset
452
+ }
453
+ else mode |= 0b001 // passive
454
+
455
+ // funcref|externref|func
456
+ if (parts[0]?.[0]!=='$') reftype = parts.shift()
457
+ // externref makes explicit table index
458
+ if (reftype === 'externref') offset ||= ['i32.const', 0], mode = 0b110
459
+
460
+ // reset to simplest mode if no actual elements
461
+ if (!parts.length) mode &= 0b011
462
+
463
+ // simplify els
464
+ parts = parts.map(el => {
465
+ if (el[0] === 'item') [, el] = el
466
+ if (el[0] === 'ref.func') [, el] = el
467
+ // (ref.null func) and other expressions
468
+ if (typeof el !== 'string') mode |= 0b100
469
+ return el
470
+ })
471
+
472
+ ctx.elem[idx] = ([
473
+ mode,
474
+ ...(
475
+ // 0b000 e:expr y*:vec(funcidx) | type=funcref, init ((ref.func y)end)*, active (table=0,offset=e)
476
+ mode === 0b000 ? [...expr(offset, ctx), 0x0b] :
477
+ // 0b001 et:elkind y*:vec(funcidx) | type=0x00, init ((ref.func y)end)*, passive
478
+ mode === 0b001 ? [0x00] :
479
+ // 0b010 x:tabidx e:expr et:elkind y*:vec(funcidx) | type=0x00, init ((ref.func y)end)*, active (table=x,offset=e)
480
+ mode === 0b010 ? [...uleb(tabidx || 0), ...expr(offset), 0x0b, 0x00] :
481
+ // 0b011 et:elkind y*:vec(funcidx) | type=0x00, init ((ref.func y)end)*, passive declare
482
+ mode === 0b011 ? [0x00] :
483
+ // 0b100 e:expr el*:vec(expr) | type=funcref, init el*, active (table=0, offset=e)
484
+ mode === 0b100 ? [...expr(offset, ctx), 0x0b] :
485
+ // 0b101 et:reftype el*:vec(expr) | type=et, init el*, passive
486
+ mode === 0b101 ? [TYPE[reftype]] :
487
+ // 0b110 x:tabidx e:expr et:reftype el*:vec(expr) | type=et, init el*, active (table=x, offset=e)
488
+ mode === 0b110 ? [...uleb(tabidx || 0), ...expr(offset), 0x0b, TYPE[reftype]] :
489
+ // 0b111 et:reftype el*:vec(expr) | type=et, init el*, passive declare
490
+ [TYPE[reftype]]
491
+ ),
492
+ ...uleb(parts.length),
493
+ ...parts.flatMap(mode & 0b100 ?
494
+ // ((ref.func y)end)*
495
+ el => [...expr(typeof el === 'string' ? ['ref.func', el] : [...el], ctx), 0x0b] :
496
+ // el*
497
+ el => uleb(el[0] === '$' ? ctx.func[el] : +el)
498
+ )
499
+ ])
374
500
  },
375
501
 
376
502
  // (data (i32.const 0) "\aa" "\bb"?)
377
- // (data (offset (i32.const 0)) (memory ref) "\aa" "\bb"?)
503
+ // (data (memory ref) (offset (i32.const 0)) "\aa" "\bb"?)
378
504
  // (data (global.get $x) "\aa" "\bb"?)
379
- data([, ...inits], ctx) {
380
- let offset, mem
381
-
382
- if (inits[0]?.[0] === 'offset') [, offset] = inits.shift()
383
- if (inits[0]?.[0] === 'memory') [, mem] = inits.shift()
384
- if (inits[0]?.[0] === 'offset') [, offset] = inits.shift()
385
- if (!offset && !mem) offset = inits.shift()
386
- if (!offset) offset = ['i32.const', 0]
387
-
388
- ctx.data.push([0, ...consumeConst([...offset], ctx), 0x0b, ...str(inits.map(i => i[0] === '"' ? i.slice(1, -1) : i).join(''))])
389
- },
505
+ data(idx, [...inits], ctx) {
506
+ let offset, mem = [0]
507
+
508
+ // (memory ref)?
509
+ if (inits[0]?.[0] === 'memory') {
510
+ [, mem] = inits.shift()
511
+ mem = mem[0] === '$' ? ctx.memory[mem] : +mem
512
+ mem = !mem ? [0] : [2, ...uleb(mem)]
513
+ }
390
514
 
391
- // (start $main)
392
- start([, name], ctx) {
393
- if (!ctx.start.length) ctx.start.push([name[0] === '$' ? ctx.func[name] : name])
515
+ // (offset (i32.const 0)) or (i32.const 0)
516
+ if (typeof inits[0] !== 'string') {
517
+ offset = inits.shift()
518
+ if (offset[0] === 'offset') [, offset] = offset
519
+ }
520
+ else offset = ['i32.const', 0]
521
+ ctx.data[idx] = [...mem, ...expr([...offset], ctx), 0x0b, ...str(inits.map(i => i.slice(1, -1)).join(''))]
394
522
  }
395
523
  }
396
524
 
397
- // instantiation time const initializer
398
- const consumeConst = (node, ctx) => {
525
+ // serialize binary array
526
+ const vec = a => [...uleb(a.length), ...a]
527
+
528
+ // instantiation time const initializer (consuming)
529
+ const expr = (node, ctx) => {
399
530
  let op = node.shift(), [type, cmd] = op.split('.')
400
531
 
401
532
  // (global.get idx)
402
- if (type === 'global') return [0x23, ...uleb(node[0][0] === '$' ? ctx.global[node[0]] : node[0])]
533
+ if (type === 'global') return [0x23, ...uleb(node[0][0] === '$' ? ctx.global[node[0]] : +node)]
403
534
 
404
535
  // (v128.const i32x4 1 2 3 4)
405
536
  if (type === 'v128') return [0xfd, 0x0c, ...v128(node)]
@@ -407,11 +538,19 @@ const consumeConst = (node, ctx) => {
407
538
  // (i32.const 1)
408
539
  if (cmd === 'const') return [0x41 + ['i32', 'i64', 'f32', 'f64'].indexOf(type), ...encode[type](node[0])]
409
540
 
541
+ // (ref.func $x) or (ref.null func|extern)
542
+ if (type === 'ref') {
543
+ return cmd === 'func' ?
544
+ [0xd2, ...uleb(node[0][0] === '$' ? ctx.func[node[0]] : +node)] :
545
+ // heaptype
546
+ [0xd0, TYPE[node[0] + 'ref']] // func->funcref, extern->externref
547
+ }
548
+
410
549
  // (i32.add a b), (i32.mult a b) etc
411
550
  return [
412
- ...consumeConst(node.shift(), ctx),
413
- ...consumeConst(node.shift(), ctx),
414
- OP.indexOf(op)
551
+ ...expr(node.shift(), ctx),
552
+ ...expr(node.shift(), ctx),
553
+ INSTR[op]
415
554
  ]
416
555
  }
417
556
 
@@ -440,6 +579,62 @@ const v128 = (args) => {
440
579
  return arr
441
580
  }
442
581
 
582
+ // https://webassembly.github.io/spec/core/text/modules.html#type-uses
583
+ // consume (type $id|id) (param t+)* (result t+)*
584
+ const typeuse = (nodes, ctx) => {
585
+ let idx, param, result, alias
586
+
587
+ // existing/new type (type 0|$name)
588
+ if (nodes[0]?.[0] === 'type') {
589
+ [, idx] = nodes.shift();
590
+
591
+ // (type 0), (type $n) - existing type
592
+ if (ctx.type[idx] != null) {
593
+ paramres(nodes);
594
+ if (idx[0] === '$') idx = ctx.type[idx];
595
+ ({ param, result } = ctx.type[idx] ?? err('Bad type ' + idx));
596
+ return [+idx, param, result]
597
+ }
598
+ }
599
+
600
+ // if new type - find existing match
601
+ ;[param, result] = paramres(nodes), alias = param + '>' + result
602
+ // or register new type
603
+ if (ctx.type[alias] == null) {
604
+ build.type(ctx.type.length, [[, ['param', ...param], ['result', ...result]]], ctx)
605
+ }
606
+
607
+ return [ctx.type[alias], param, result]
608
+ }
609
+
610
+ // consume (param t+)* (result t+)* sequence
611
+ const paramres = (nodes) => {
612
+ let param = [], result = []
613
+
614
+ // collect param (param i32 i64) (param $x? i32)
615
+ while (nodes[0]?.[0] === 'param') {
616
+ let [, ...args] = nodes.shift()
617
+ let name = args[0]?.[0] === '$' && args.shift()
618
+ if (name) param[name] = param.length // expose name refs
619
+ param.push(...args)
620
+ }
621
+
622
+ // collect result eg. (result f64 f32)(result i32)
623
+ while (nodes[0]?.[0] === 'result') {
624
+ let [, ...args] = nodes.shift()
625
+ result.push(...args)
626
+ }
627
+
628
+ return [param, result]
629
+ }
630
+
631
+ // consume align/offset/etc params
632
+ const memarg = (args) => {
633
+ let ao = {}, kv
634
+ while (args[0]?.includes('=')) kv = args.shift().split('='), ao[kv[0]] = Number(kv[1])
635
+ return ao
636
+ }
637
+
443
638
  // escape codes
444
639
  const escape = { n: 10, r: 13, t: 9, v: 1, '\\': 92 }
445
640
 
@@ -447,48 +642,16 @@ const escape = { n: 10, r: 13, t: 9, v: 1, '\\': 92 }
447
642
  const str = str => {
448
643
  str = str[0] === '"' ? str.slice(1, -1) : str
449
644
  let res = [], i = 0, c, BSLASH = 92
450
- // spec https://webassembly.github.io/spec/core/text/values.html#strings
645
+ // https://webassembly.github.io/spec/core/text/values.html#strings
451
646
  for (; i < str.length;) {
452
647
  c = str.charCodeAt(i++)
453
648
  res.push(c === BSLASH ? escape[str[i++]] || parseInt(str.slice(i - 1, ++i), 16) : c)
454
649
  }
455
650
 
456
- res.unshift(...uleb(res.length))
457
- return res
651
+ return vec(res)
458
652
  }
459
653
 
460
- // build range/limits sequence (non-consuming)
461
- const range = ([min, max, shared]) => isNaN(parseInt(max)) ? [0, ...uleb(min)] : [shared === 'shared' ? 3 : 1, ...uleb(min), ...uleb(max)]
462
-
463
- // get type info from (params) (result) nodes sequence (consumes nodes)
464
- // returns registered (reused) type idx, params bytes, result bytes
465
- // eg. (type $return_i32 (func (result i32)))
466
- const consumeType = (nodes, ctx) => {
467
- let params = [], result = [], idx, bytes
468
-
469
- // collect params
470
- while (nodes[0]?.[0] === 'param') {
471
- let [, ...types] = nodes.shift()
472
- if (types[0]?.[0] === '$') params[types.shift()] = params.length
473
- params.push(...types.map(t => TYPE[t]))
474
- }
475
-
476
- // collect result type
477
- if (nodes[0]?.[0] === 'result') result = nodes.shift().slice(1).map(t => TYPE[t])
654
+ // build limits sequence (non-consuming)
655
+ const limits = ([min, max, shared]) => isNaN(parseInt(max)) ? [0, ...uleb(min)] : [shared === 'shared' ? 3 : 1, ...uleb(min), ...uleb(max)]
478
656
 
479
- // reuse existing type or register new one
480
- bytes = [TYPE.func, ...uleb(params.length), ...params, ...uleb(result.length), ...result]
481
- idx = ctx.type.findIndex((t) => t.every((byte, i) => byte === bytes[i]))
482
-
483
- // register new type, if not found
484
- if (idx < 0) idx = ctx.type.push(bytes) - 1
485
-
486
- return [idx, params, result]
487
- }
488
-
489
- // consume align/offset/etc params
490
- const consumeParams = (args) => {
491
- let params = {}, param
492
- while (args[0]?.includes('=')) param = args.shift().split('='), params[param[0]] = Number(param[1])
493
- return params
494
- }
657
+ const err = text => { throw Error(text) }
package/src/const.js CHANGED
@@ -1,18 +1,16 @@
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
- // FIXME: object would allow faster lookup and number of immediates, which would allow reducing size of compile fn
4
- export const OP = [
5
- 'unreachable', 'nop', 'block', 'loop', 'if', 'else', 'then', , , , ,
6
- 'end', 'br', 'br_if', 'br_table', 'return', 'call', 'call_indirect', , , , , , , , ,
7
- 'drop', 'select', , , , ,
8
- 'local.get', 'local.set', 'local.tee', 'global.get', 'global.set', , , ,
9
- 'i32.load', 'i64.load', 'f32.load', 'f64.load',
10
- 'i32.load8_s', 'i32.load8_u', 'i32.load16_s', 'i32.load16_u',
11
- 'i64.load8_s', 'i64.load8_u', 'i64.load16_s', 'i64.load16_u', 'i64.load32_s', 'i64.load32_u',
12
- 'i32.store', 'i64.store', 'f32.store', 'f64.store',
13
- 'i32.store8', 'i32.store16', 'i64.store8', 'i64.store16', 'i64.store32',
1
+ // https://webassembly.github.io/spec/core/appendix/index-instructions.html
2
+ export const INSTR = [
3
+ 'unreachable', 'nop', 'block:b', 'loop:b', 'if:b', 'else', 'then', , , , ,
4
+ 'end', 'br:i', 'br_if:i', 'br_table:i*', 'return', 'call:i', 'call_indirect:i:i', , , , , , , , ,
5
+ 'drop', 'select', 'select2:t', , , ,
6
+ 'local.get:i', 'local.set:i', 'local.tee:i', 'global.get:i', 'global.set:i', 'table.get:i', 'table.set:i', ,
7
+ 'i32.load:m', 'i64.load:m', 'f32.load:m', 'f64.load:m',
8
+ 'i32.load8_s:m', 'i32.load8_u:m', 'i32.load16_s:m', 'i32.load16_u:m',
9
+ 'i64.load8_s:m', 'i64.load8_u:m', 'i64.load16_s:m', 'i64.load16_u:m', 'i64.load32_s:m', 'i64.load32_u:m',
10
+ 'i32.store:m', 'i64.store:m', 'f32.store:m', 'f64.store:m',
11
+ 'i32.store8:m', 'i32.store16:m', 'i64.store8:m', 'i64.store16:m', 'i64.store32:m',
14
12
  'memory.size', 'memory.grow',
15
- 'i32.const', 'i64.const', 'f32.const', 'f64.const',
13
+ 'i32.const:n', 'i64.const:n', 'f32.const:n', 'f64.const:n',
16
14
  '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',
17
15
  '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',
18
16
  'f32.eq', 'f32.ne', 'f32.lt', 'f32.gt', 'f32.le', 'f32.ge',
@@ -26,19 +24,29 @@ export const OP = [
26
24
  'i64.trunc_f32_s', 'i64.trunc_f32_u', 'i64.trunc_f64_s', 'i64.trunc_f64_u',
27
25
  'f32.convert_i32_s', 'f32.convert_i32_u', 'f32.convert_i64_s', 'f32.convert_i64_u', 'f32.demote_f64',
28
26
  'f64.convert_i32_s', 'f64.convert_i32_u', 'f64.convert_i64_s', 'f64.convert_i64_u', 'f64.promote_f32',
29
- 'i32.reinterpret_f32', 'i64.reinterpret_f64', 'f32.reinterpret_i32', 'f64.reinterpret_i64', , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,
30
- 'memory.init', 'data.drop', 'memory.copy', 'memory.fill', 'table.init', 'elem.drop', 'table.copy', ,
27
+ 'i32.reinterpret_f32', 'i64.reinterpret_f64', 'f32.reinterpret_i32', 'f64.reinterpret_i64',
28
+ 'i32.extend8_s', 'i32.extend16_s', 'i64.extend8_s', 'i64.extend16_s', 'i64.extend32_s', , , , , , , , , , , ,
29
+ 'ref.null:t', 'ref.is_null', 'ref.func:i', , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,
31
30
 
32
- // ref: https://github.com/WebAssembly/simd/blob/master/proposals/simd/BinarySIMD.md
33
- "v128.load", "v128.load8x8_s", "v128.load8x8_u", "v128.load16x4_s", "v128.load16x4_u", "v128.load32x2_s", "v128.load32x2_u", "v128.load8_splat", "v128.load16_splat", "v128.load32_splat", "v128.load64_splat", "v128.store", "v128.const", "i8x16.shuffle",
34
- "i8x16.swizzle", "i8x16.splat", "i16x8.splat", "i32x4.splat", "i64x2.splat", "f32x4.splat", "f64x2.splat", "i8x16.extract_lane_s", "i8x16.extract_lane_u", "i8x16.replace_lane", "i16x8.extract_lane_s", "i16x8.extract_lane_u", "i16x8.replace_lane", "i32x4.extract_lane", "i32x4.replace_lane", "i64x2.extract_lane", "i64x2.replace_lane", "f32x4.extract_lane", "f32x4.replace_lane", "f64x2.extract_lane", "f64x2.replace_lane",
35
- "i8x16.eq", "i8x16.ne", "i8x16.lt_s", "i8x16.lt_u", "i8x16.gt_s", "i8x16.gt_u", "i8x16.le_s", "i8x16.le_u", "i8x16.ge_s", "i8x16.ge_u", "i16x8.eq", "i16x8.ne", "i16x8.lt_s", "i16x8.lt_u", "i16x8.gt_s", "i16x8.gt_u", "i16x8.le_s", "i16x8.le_u", "i16x8.ge_s", "i16x8.ge_u", "i32x4.eq", "i32x4.ne", "i32x4.lt_s", "i32x4.lt_u", "i32x4.gt_s", "i32x4.gt_u", "i32x4.le_s", "i32x4.le_u", "i32x4.ge_s", "i32x4.ge_u", "f32x4.eq", "f32x4.ne", "f32x4.lt", "f32x4.gt", "f32x4.le", "f32x4.ge", "f64x2.eq", "f64x2.ne", "f64x2.lt", "f64x2.gt", "f64x2.le", "f64x2.ge", "v128.not", "v128.and", "v128.andnot", "v128.or", "v128.xor", "v128.bitselect", "v128.any_true",
36
- "v128.load8_lane", "v128.load16_lane", "v128.load32_lane", "v128.load64_lane", "v128.store8_lane", "v128.store16_lane", "v128.store32_lane", "v128.store64_lane", "v128.load32_zero", "v128.load64_zero", "f32x4.demote_f64x2_zero", "f64x2.promote_low_f32x4",
37
- "i8x16.abs", "i8x16.neg", "i8x16.popcnt", "i8x16.all_true", "i8x16.bitmask", "i8x16.narrow_i16x8_s", "i8x16.narrow_i16x8_u", "f32x4.ceil", "f32x4.floor", "f32x4.trunc", "f32x4.nearest", "i8x16.shl", "i8x16.shr_s", "i8x16.shr_u", "i8x16.add", "i8x16.add_sat_s", "i8x16.add_sat_u", "i8x16.sub", "i8x16.sub_sat_s", "i8x16.sub_sat_u", "f64x2.ceil", "f64x2.floor", "i8x16.min_s", "i8x16.min_u", "i8x16.max_s", "i8x16.max_u", "f64x2.trunc", "i8x16.avgr_u", "i16x8.extadd_pairwise_i8x16_s", "i16x8.extadd_pairwise_i8x16_u", "i32x4.extadd_pairwise_i16x8_s", "i32x4.extadd_pairwise_i16x8_u", "i16x8.abs", "i16x8.neg", "i16x8.q15mulr_sat_s", "i16x8.all_true", "i16x8.bitmask", "i16x8.narrow_i32x4_s", "i16x8.narrow_i32x4_u", "i16x8.extend_low_i8x16_s", "i16x8.extend_high_i8x16_s", "i16x8.extend_low_i8x16_u", "i16x8.extend_high_i8x16_u", "i16x8.shl", "i16x8.shr_s", "i16x8.shr_u", "i16x8.add", "i16x8.add_sat_s", "i16x8.add_sat_u", "i16x8.sub", "i16x8.sub_sat_s", "i16x8.sub_sat_u", "f64x2.nearest", "i16x8.mul", "i16x8.min_s", "i16x8.min_u", "i16x8.max_s", "i16x8.max_u", , "i16x8.avgr_u", "i16x8.extmul_low_i8x16_s", "i16x8.extmul_high_i8x16_s", "i16x8.extmul_low_i8x16_u", "i16x8.extmul_high_i8x16_u", "i32x4.abs", "i32x4.neg", , "i32x4.all_true", "i32x4.bitmask", , , "i32x4.extend_low_i16x8_s", "i32x4.extend_high_i16x8_s", "i32x4.extend_low_i16x8_u", "i32x4.extend_high_i16x8_u", "i32x4.shl", "i32x4.shr_s", "i32x4.shr_u", "i32x4.add", , , "i32x4.sub", , , , "i32x4.mul", "i32x4.min_s", "i32x4.min_u", "i32x4.max_s", "i32x4.max_u", "i32x4.dot_i16x8_s", , "i32x4.extmul_low_i16x8_s", "i32x4.extmul_high_i16x8_s", "i32x4.extmul_low_i16x8_u", "i32x4.extmul_high_i16x8_u", "i64x2.abs", "i64x2.neg", , "i64x2.all_true", "i64x2.bitmask", , , "i64x2.extend_low_i32x4_s", "i64x2.extend_high_i32x4_s", "i64x2.extend_low_i32x4_u", "i64x2.extend_high_i32x4_u", "i64x2.shl", "i64x2.shr_s", "i64x2.shr_u", "i64x2.add", , , "i64x2.sub", , , , "i64x2.mul", "i64x2.eq", "i64x2.ne", "i64x2.lt_s", "i64x2.gt_s", "i64x2.le_s", "i64x2.ge_s", "i64x2.extmul_low_i32x4_s", "i64x2.extmul_high_i32x4_s", "i64x2.extmul_low_i32x4_u", "i64x2.extmul_high_i32x4_u", "f32x4.abs", "f32x4.neg", , "f32x4.sqrt", "f32x4.add", "f32x4.sub", "f32x4.mul", "f32x4.div", "f32x4.min", "f32x4.max", "f32x4.pmin", "f32x4.pmax", "f64x2.abs", "f64x2.neg", , "f64x2.sqrt", "f64x2.add", "f64x2.sub", "f64x2.mul", "f64x2.div", "f64x2.min", "f64x2.max", "f64x2.pmin", "f64x2.pmax", "i32x4.trunc_sat_f32x4_s", "i32x4.trunc_sat_f32x4_u", "f32x4.convert_i32x4_s", "f32x4.convert_i32x4_u", "i32x4.trunc_sat_f64x2_s_zero", "i32x4.trunc_sat_f64x2_u_zero", "f64x2.convert_low_i32x4_s", "f64x2.convert_low_i32x4_u"
31
+ // 0xFC 0xNN (0xfc shift)
32
+ 'i32.trunc_sat_f32_u', 'i32.trunc_sat_f32_u', 'i32.trunc_sat_f64_s', 'i32.trunc_sat_f64_u', 'i64.trunc_sat_f32_s', 'i64.trunc_sat_f32_u', 'i64.trunc_sat_f64_s', 'i64.trunc_sat_f64_u',
33
+ 'memory.init:i', 'data.drop:i', 'memory.copy', 'memory.fill', 'table.init:i:i', 'elem.drop:i', 'table.copy:i:i', 'table.grow:i', 'table.size:i', 'table.fill:i', ,
34
+
35
+ // 0xFD 0xNN (0x10f shift)
36
+ 'v128.load:m', 'v128.load8x8_s:m', 'v128.load8x8_u:m', 'v128.load16x4_s:m', 'v128.load16x4_u:m', 'v128.load32x2_s:m', 'v128.load32x2_u:m', 'v128.load8_splat:m', 'v128.load16_splat:m', 'v128.load32_splat:m', 'v128.load64_splat:m', 'v128.store:m', 'v128.const:n', 'i8x16.shuffle:n:n:n:n:n:n:n:n:n:n:n:n:n:n:n:n',
37
+ 'i8x16.swizzle', 'i8x16.splat', 'i16x8.splat', 'i32x4.splat', 'i64x2.splat', 'f32x4.splat', 'f64x2.splat',
38
+ 'i8x16.extract_lane_s:n', 'i8x16.extract_lane_u:n', 'i8x16.replace_lane:n', 'i16x8.extract_lane_s:n', 'i16x8.extract_lane_u:n', 'i16x8.replace_lane:n', 'i32x4.extract_lane:n', 'i32x4.replace_lane:n', 'i64x2.extract_lane:n', 'i64x2.replace_lane:n', 'f32x4.extract_lane:n', 'f32x4.replace_lane:n', 'f64x2.extract_lane:n', 'f64x2.replace_lane:n',
39
+ 'i8x16.eq', 'i8x16.ne', 'i8x16.lt_s', 'i8x16.lt_u', 'i8x16.gt_s', 'i8x16.gt_u', 'i8x16.le_s', 'i8x16.le_u', 'i8x16.ge_s', 'i8x16.ge_u', 'i16x8.eq', 'i16x8.ne', 'i16x8.lt_s', 'i16x8.lt_u', 'i16x8.gt_s', 'i16x8.gt_u', 'i16x8.le_s', 'i16x8.le_u', 'i16x8.ge_s', 'i16x8.ge_u', 'i32x4.eq', 'i32x4.ne', 'i32x4.lt_s', 'i32x4.lt_u', 'i32x4.gt_s', 'i32x4.gt_u', 'i32x4.le_s', 'i32x4.le_u', 'i32x4.ge_s', 'i32x4.ge_u', 'f32x4.eq', 'f32x4.ne', 'f32x4.lt', 'f32x4.gt', 'f32x4.le', 'f32x4.ge', 'f64x2.eq', 'f64x2.ne', 'f64x2.lt', 'f64x2.gt', 'f64x2.le', 'f64x2.ge', 'v128.not', 'v128.and', 'v128.andnot', 'v128.or', 'v128.xor', 'v128.bitselect', 'v128.any_true',
40
+ 'v128.load8_lane:m:l', 'v128.load16_lane:m:l', 'v128.load32_lane:m:l', 'v128.load64_lane:m:l', 'v128.store8_lane', 'v128.store16_lane', 'v128.store32_lane', 'v128.store64_lane', 'v128.load32_zero:m', 'v128.load64_zero:m', 'f32x4.demote_f64x2_zero', 'f64x2.promote_low_f32x4',
41
+ 'i8x16.abs', 'i8x16.neg', 'i8x16.popcnt', 'i8x16.all_true', 'i8x16.bitmask', 'i8x16.narrow_i16x8_s', 'i8x16.narrow_i16x8_u', 'f32x4.ceil', 'f32x4.floor', 'f32x4.trunc', 'f32x4.nearest', 'i8x16.shl', 'i8x16.shr_s', 'i8x16.shr_u', 'i8x16.add', 'i8x16.add_sat_s', 'i8x16.add_sat_u', 'i8x16.sub', 'i8x16.sub_sat_s', 'i8x16.sub_sat_u', 'f64x2.ceil', 'f64x2.floor', 'i8x16.min_s', 'i8x16.min_u', 'i8x16.max_s', 'i8x16.max_u', 'f64x2.trunc', 'i8x16.avgr_u',
42
+ 'i16x8.extadd_pairwise_i8x16_s', 'i16x8.extadd_pairwise_i8x16_u', 'i32x4.extadd_pairwise_i16x8_s', 'i32x4.extadd_pairwise_i16x8_u', 'i16x8.abs', 'i16x8.neg', 'i16x8.q15mulr_sat_s', 'i16x8.all_true', 'i16x8.bitmask', 'i16x8.narrow_i32x4_s', 'i16x8.narrow_i32x4_u', 'i16x8.extend_low_i8x16_s', 'i16x8.extend_high_i8x16_s', 'i16x8.extend_low_i8x16_u', 'i16x8.extend_high_i8x16_u',
43
+ 'i16x8.shl', 'i16x8.shr_s', 'i16x8.shr_u', 'i16x8.add', 'i16x8.add_sat_s', 'i16x8.add_sat_u', 'i16x8.sub', 'i16x8.sub_sat_s', 'i16x8.sub_sat_u', 'f64x2.nearest', 'i16x8.mul', 'i16x8.min_s', 'i16x8.min_u', 'i16x8.max_s', 'i16x8.max_u', , 'i16x8.avgr_u',
44
+ 'i16x8.extmul_low_i8x16_s', 'i16x8.extmul_high_i8x16_s', 'i16x8.extmul_low_i8x16_u', 'i16x8.extmul_high_i8x16_u', 'i32x4.abs', 'i32x4.neg', , 'i32x4.all_true', 'i32x4.bitmask', , , 'i32x4.extend_low_i16x8_s', 'i32x4.extend_high_i16x8_s', 'i32x4.extend_low_i16x8_u', 'i32x4.extend_high_i16x8_u', 'i32x4.shl', 'i32x4.shr_s', 'i32x4.shr_u', 'i32x4.add', , , 'i32x4.sub', , , , 'i32x4.mul', 'i32x4.min_s', 'i32x4.min_u', 'i32x4.max_s', 'i32x4.max_u', 'i32x4.dot_i16x8_s', , 'i32x4.extmul_low_i16x8_s', 'i32x4.extmul_high_i16x8_s', 'i32x4.extmul_low_i16x8_u', 'i32x4.extmul_high_i16x8_u', 'i64x2.abs', 'i64x2.neg', , 'i64x2.all_true', 'i64x2.bitmask', , , 'i64x2.extend_low_i32x4_s', 'i64x2.extend_high_i32x4_s', 'i64x2.extend_low_i32x4_u', 'i64x2.extend_high_i32x4_u', 'i64x2.shl', 'i64x2.shr_s', 'i64x2.shr_u', 'i64x2.add', , , 'i64x2.sub', , , , 'i64x2.mul', 'i64x2.eq', 'i64x2.ne', 'i64x2.lt_s', 'i64x2.gt_s', 'i64x2.le_s', 'i64x2.ge_s', 'i64x2.extmul_low_i32x4_s', 'i64x2.extmul_high_i32x4_s', 'i64x2.extmul_low_i32x4_u', 'i64x2.extmul_high_i32x4_u', 'f32x4.abs', 'f32x4.neg', , 'f32x4.sqrt', 'f32x4.add', 'f32x4.sub', 'f32x4.mul', 'f32x4.div', 'f32x4.min', 'f32x4.max', 'f32x4.pmin', 'f32x4.pmax', 'f64x2.abs', 'f64x2.neg', , 'f64x2.sqrt', 'f64x2.add', 'f64x2.sub', 'f64x2.mul', 'f64x2.div', 'f64x2.min', 'f64x2.max', 'f64x2.pmin', 'f64x2.pmax', 'i32x4.trunc_sat_f32x4_s', 'i32x4.trunc_sat_f32x4_u', 'f32x4.convert_i32x4_s', 'f32x4.convert_i32x4_u', 'i32x4.trunc_sat_f64x2_s_zero', 'i32x4.trunc_sat_f64x2_u_zero', 'f64x2.convert_low_i32x4_s', 'f64x2.convert_low_i32x4_u'
38
45
  ],
39
- SECTION = { type: 1, import: 2, func: 3, table: 4, memory: 5, global: 6, export: 7, start: 8, elem: 9, code: 10, data: 11 },
40
- TYPE = { i32: 0x7f, i64: 0x7e, f32: 0x7d, f64: 0x7c, void: 0x40, func: 0x60, funcref: 0x70, v128: 0x7B },
46
+ SECTION = { custom: 0, type: 1, import: 2, func: 3, table: 4, memory: 5, global: 6, export: 7, start: 8, elem: 9, code: 10, data: 11, datacount: 12 },
47
+ TYPE = { i32: 0x7f, i64: 0x7e, f32: 0x7d, f64: 0x7c, void: 0x40, v128: 0x7B, func: 0x60, funcref: 0x70, externref: 0x6F, extern: 0x6f },
41
48
  KIND = { func: 0, table: 1, memory: 2, global: 3 },
49
+ // FIXME: replace with formula
42
50
  ALIGN = {
43
51
  'i32.load': 4, 'i64.load': 8, 'f32.load': 4, 'f64.load': 8,
44
52
  'i32.load8_s': 1, 'i32.load8_u': 1, 'i32.load16_s': 2, 'i32.load16_u': 2,
@@ -49,8 +57,5 @@ export const OP = [
49
57
  'v128.load': 16, 'v128.load8x8_s': 8, 'v128.load8x8_u': 8, 'v128.load16x4_s': 8, 'v128.load16x4_u': 8, 'v128.load32x2_s': 8, 'v128.load32x2_u': 8, 'v128.load8_splat': 1, 'v128.load16_splat': 2, 'v128.load32_splat': 4, 'v128.load64_splat': 8, 'v128.store': 16,
50
58
  'v128.load': 16,
51
59
 
52
- "v128.load8_lane": 1, "v128.load16_lane": 2, "v128.load32_lane": 4, "v128.load64_lane": 8, "v128.store8_lane": 1, "v128.store16_lane": 2, "v128.store32_lane": 4, "v128.store64_lane": 8, "v128.load32_zero": 4, "v128.load64_zero": 8
53
- },
54
- BLOCK = {
55
- loop: 1, block: 1, if: 1, end: -1, return: -1
60
+ 'v128.load8_lane': 1, 'v128.load16_lane': 2, 'v128.load32_lane': 4, 'v128.load64_lane': 8, 'v128.store8_lane': 1, 'v128.store16_lane': 2, 'v128.store32_lane': 4, 'v128.store64_lane': 8, 'v128.load32_zero': 4, 'v128.load64_zero': 8
56
61
  }
package/src/encode.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  // uleb
4
4
  export const uleb = (n, buffer = []) => {
5
+ if (n == null) return buffer
5
6
  if (typeof n === 'string') n = i32.parse(n)
6
7
 
7
8
  let byte = n & 0b01111111;
package/src/util.js DELETED
@@ -1 +0,0 @@
1
- export const err = text => { throw Error(text) }