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 +6 -3
- package/readme.md +36 -21
- package/src/compile.js +399 -236
- package/src/const.js +33 -28
- package/src/encode.js +1 -0
- package/src/util.js +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "watr",
|
|
3
|
-
"version": "
|
|
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 [](https://github.com/audio-lab/watr/actions/workflows/test.js.yml) [](https://bundlephobia.com/package/watr) [](https://npmjs.org/watr)
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
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
|
-
* [
|
|
80
|
-
* [
|
|
81
|
-
* [
|
|
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
|
| 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
|
-
* [
|
|
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 {
|
|
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 (
|
|
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
|
|
12
|
-
* @returns {Uint8Array} The compiled
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
89
|
+
else if (kind === 'start') {name && node.unshift(name);}
|
|
90
|
+
|
|
91
|
+
nodeGroups[kind].push(node)
|
|
52
92
|
}
|
|
53
93
|
|
|
54
|
-
//
|
|
55
|
-
for (let
|
|
56
|
-
|
|
57
|
-
//
|
|
58
|
-
for (let
|
|
59
|
-
let items = sections[
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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 $
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
// (
|
|
82
|
-
|
|
83
|
-
let
|
|
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
|
-
|
|
87
|
-
|
|
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
|
-
|
|
90
|
-
|
|
155
|
+
// (func $name? ...params result ...body)
|
|
156
|
+
func(idx, [...node], ctx) {
|
|
157
|
+
const [typeidx, param, result] = typeuse(node, ctx)
|
|
91
158
|
|
|
92
|
-
|
|
93
|
-
let [typeIdx, params, result] = consumeType(body, ctx)
|
|
159
|
+
ctx.func[idx] = uleb(typeidx)
|
|
94
160
|
|
|
95
|
-
//
|
|
96
|
-
|
|
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 (
|
|
100
|
-
let [, ...types] =
|
|
101
|
-
if (types[0][0] === '$')
|
|
102
|
-
|
|
103
|
-
locals[name] =
|
|
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
|
|
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 =
|
|
184
|
+
opCode = INSTR[op = args.shift()]
|
|
120
185
|
}
|
|
121
|
-
else opCode =
|
|
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 >=
|
|
128
|
-
opCode -=
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 >=
|
|
162
|
-
immed = [0xfc, ...uleb(opCode -=
|
|
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 >=
|
|
247
|
+
else if (opCode >= 0x28 && opCode <= 0x3e) {
|
|
176
248
|
// FIXME: figure out point in Math.log2 aligns
|
|
177
|
-
let o =
|
|
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 >=
|
|
188
|
-
immed = uleb(args[0]?.[0] === '$' ?
|
|
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 ==
|
|
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 ==
|
|
269
|
+
else if (opCode == 0x10) {
|
|
198
270
|
let fnName = args.shift()
|
|
199
|
-
immed = uleb(id = fnName[0] === '$' ? ctx.func[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 ==
|
|
205
|
-
let
|
|
206
|
-
|
|
207
|
-
immed = uleb(
|
|
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 (
|
|
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 [
|
|
231
|
-
immed =
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
296
|
-
// FIXME:
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
305
|
-
|
|
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
|
-
// (
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
// (
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
// (
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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" (
|
|
339
|
-
export(
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
// (
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
|
|
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))
|
|
503
|
+
// (data (memory ref) (offset (i32.const 0)) "\aa" "\bb"?)
|
|
378
504
|
// (data (global.get $x) "\aa" "\bb"?)
|
|
379
|
-
data(
|
|
380
|
-
let offset, mem
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
if (inits[0]?.[0] === 'memory')
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
-
//
|
|
398
|
-
const
|
|
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
|
|
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
|
-
...
|
|
413
|
-
...
|
|
414
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
457
|
-
return res
|
|
651
|
+
return vec(res)
|
|
458
652
|
}
|
|
459
653
|
|
|
460
|
-
// build
|
|
461
|
-
const
|
|
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
|
-
|
|
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
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
'
|
|
6
|
-
'
|
|
7
|
-
'
|
|
8
|
-
'
|
|
9
|
-
'
|
|
10
|
-
'i32.
|
|
11
|
-
'
|
|
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
|
-
'
|
|
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
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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,
|
|
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
|
-
|
|
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
package/src/util.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const err = text => { throw Error(text) }
|