watr 1.4.1 → 2.1.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 +14 -3
- package/readme.md +34 -78
- package/src/compile.js +156 -171
- package/src/const.js +19 -18
package/package.json
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "watr",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Ligth & fast WAT compiler",
|
|
5
5
|
"main": "watr.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./watr.js",
|
|
8
|
+
"./parse": "./src/parse.js",
|
|
9
|
+
"./print": "./src/print.js",
|
|
10
|
+
"./compile": "./src/compile.js"
|
|
11
|
+
},
|
|
6
12
|
"type": "module",
|
|
7
13
|
"scripts": {
|
|
8
14
|
"test": "node test"
|
|
@@ -16,11 +22,16 @@
|
|
|
16
22
|
],
|
|
17
23
|
"keywords": [
|
|
18
24
|
"wat",
|
|
25
|
+
"wasm",
|
|
26
|
+
"wat2wasm",
|
|
19
27
|
"compiler",
|
|
20
|
-
"wabt"
|
|
28
|
+
"wabt",
|
|
29
|
+
"pretty-print",
|
|
30
|
+
"webassembly",
|
|
31
|
+
"wasm-text"
|
|
21
32
|
],
|
|
22
33
|
"author": "Dmitry Iv",
|
|
23
|
-
"license": "
|
|
34
|
+
"license": "MIT",
|
|
24
35
|
"bugs": {
|
|
25
36
|
"url": "https://github.com/audio-lab/watr/issues"
|
|
26
37
|
},
|
package/readme.md
CHANGED
|
@@ -1,25 +1,16 @@
|
|
|
1
|
-
# watr [](https://github.com/audio-lab/watr/actions/workflows/test.js.yml) [](https://bundlephobia.com/package/watr)
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Bare minimum wasm text compiler & formatter.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
Useful as WASM API layer, eg. for hi-level languages or for dynamic (in-browser?) compilation.
|
|
7
|
-
<!--, eg. [sonl](https://github.com/audio-lab/sonl). -->
|
|
5
|
+
Light & fast alternative for [wat2wasm](https://github.com/AssemblyScript/wabt.js), useful for hi-level languages or dynamic (in-browser) compilation.<br>
|
|
8
6
|
|
|
9
7
|
<!-- See [REPL](https://audio-lab.github.io/watr/repl.html).-->
|
|
10
8
|
|
|
11
|
-
<!--
|
|
12
|
-
| watr | wat-compiler | wabt
|
|
13
|
-
---|---|---|---
|
|
14
|
-
Size (gzipped) | 2.8kb | 6kb | 300kb
|
|
15
|
-
Performance (op/s) | 45000 | 2500 | 3100
|
|
16
|
-
-->
|
|
17
|
-
|
|
18
9
|
| Size (gzipped) | Performance (op/s)
|
|
19
10
|
---|---|---
|
|
20
|
-
watr | 3.8 kb |
|
|
21
|
-
[wat-compiler](https://github.com/stagas/wat-compiler) | 6 kb |
|
|
22
|
-
[wabt](https://github.com/AssemblyScript/wabt.js) | 300 kb |
|
|
11
|
+
watr | 3.8 kb | 5950
|
|
12
|
+
[wat-compiler](https://github.com/stagas/wat-compiler) | 6 kb | 348
|
|
13
|
+
[wabt](https://github.com/AssemblyScript/wabt.js) | 300 kb | 574
|
|
23
14
|
|
|
24
15
|
## Usage
|
|
25
16
|
|
|
@@ -43,32 +34,17 @@ double(108) // 216
|
|
|
43
34
|
|
|
44
35
|
## API
|
|
45
36
|
|
|
46
|
-
### Parse
|
|
47
|
-
|
|
48
|
-
Parser converts input Wasm text string to syntax tree.
|
|
49
|
-
|
|
50
|
-
```js
|
|
51
|
-
import { parse } from 'watr
|
|
52
|
-
|
|
53
|
-
parse(`(func (export "double") (param f64) (result f64) (f64.mul (local.get 0) (f64.const 2)))`)
|
|
54
|
-
|
|
55
|
-
// [
|
|
56
|
-
// 'func', ['export', '"double"'], ['param', 'f64'], ['result', 'f64'],
|
|
57
|
-
// ['f64.mul', ['local.get', 0], ['f64.const', 2]]
|
|
58
|
-
// ]
|
|
59
|
-
```
|
|
60
|
-
|
|
61
37
|
### Compile
|
|
62
38
|
|
|
63
|
-
Compiles
|
|
39
|
+
Compiles wasm text or syntax tree into wasm binary.
|
|
64
40
|
|
|
65
41
|
```js
|
|
66
42
|
import { compile } from 'watr'
|
|
67
43
|
|
|
68
|
-
const buffer = compile(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
44
|
+
const buffer = compile(`(func (export "double")
|
|
45
|
+
(param f64) (result f64)
|
|
46
|
+
(f64.mul (local.get 0) (f64.const 2))
|
|
47
|
+
)`)
|
|
72
48
|
const module = new WebAssembly.Module(buffer)
|
|
73
49
|
const instance = new WebAssembly.Instance(module)
|
|
74
50
|
const {double} = instance.exports
|
|
@@ -78,20 +54,20 @@ double(108) // 216
|
|
|
78
54
|
|
|
79
55
|
### Print
|
|
80
56
|
|
|
81
|
-
Format input
|
|
57
|
+
Format input wasm text or syntax tree into minified or pretty form.
|
|
82
58
|
|
|
83
59
|
```js
|
|
84
60
|
import { print } from 'watr'
|
|
85
61
|
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
62
|
+
const src = `(func (export "double")
|
|
63
|
+
(param f64) (result f64)
|
|
64
|
+
(f64.mul (local.get 0) (f64.const 2))
|
|
65
|
+
)`
|
|
90
66
|
|
|
91
67
|
// pretty-print (default)
|
|
92
|
-
|
|
93
|
-
indent: ' ',
|
|
94
|
-
newline: '\n',
|
|
68
|
+
print(src, {
|
|
69
|
+
indent: ' ',
|
|
70
|
+
newline: '\n',
|
|
95
71
|
})
|
|
96
72
|
// (func (export "double")
|
|
97
73
|
// (param f64) (result f64)
|
|
@@ -100,53 +76,34 @@ const str = print(tree, {
|
|
|
100
76
|
// (f64.const 2)))
|
|
101
77
|
|
|
102
78
|
// minify
|
|
103
|
-
|
|
79
|
+
print(src, {
|
|
104
80
|
indent: false,
|
|
105
81
|
newline: false
|
|
106
82
|
})
|
|
107
83
|
// (func (export "double")(param f64)(result f64)(f64.mul (local.get 0)(f64.const 2)))
|
|
108
84
|
```
|
|
109
85
|
|
|
110
|
-
|
|
111
|
-
## Limitations
|
|
112
|
-
|
|
113
|
-
It may miss some edge cases and nice error messages.
|
|
114
|
-
For better REPL/dev experience use [wabt](https://github.com/AssemblyScript/wabt.js).
|
|
115
|
-
|
|
86
|
+
### Parse
|
|
116
87
|
|
|
117
|
-
|
|
88
|
+
Parse input wasm text into syntax tree.
|
|
118
89
|
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
i32.const 1 ;; ✘ stacked arguments
|
|
122
|
-
drop
|
|
123
|
-
i32.const 0
|
|
124
|
-
i32.load offset=0 align=4 ;; ✘ ungrouped immediates
|
|
125
|
-
)
|
|
90
|
+
```js
|
|
91
|
+
import { parse } from 'watr'
|
|
126
92
|
|
|
127
|
-
(func (result
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
93
|
+
parse(`(func (export "double") (param f64) (result f64) (f64.mul (local.get 0) (f64.const 2)))`)
|
|
94
|
+
// [
|
|
95
|
+
// 'func', ['export', '"double"'], ['param', 'f64'], ['result', 'f64'],
|
|
96
|
+
// ['f64.mul', ['local.get', 0], ['f64.const', 2]]
|
|
97
|
+
// ]
|
|
131
98
|
```
|
|
132
99
|
|
|
133
|
-
|
|
134
|
-
(local.get 0) ;; ✘ stacked argument
|
|
135
|
-
if (result i32) ;; ✘ inline instruction
|
|
136
|
-
(i32.const 1)
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
(if (result i32) (local.get 0) ;; ✔ explicit signature
|
|
140
|
-
(i32.const 1)
|
|
141
|
-
)
|
|
142
|
-
```
|
|
100
|
+
## Limitations
|
|
143
101
|
|
|
144
|
-
|
|
145
|
-
(f32.const 0x1.fffffep+127) ;; ✘ floating HEX - not supported
|
|
146
|
-
```
|
|
147
|
-
-->
|
|
102
|
+
No floating HEX support, eg. `(f32.const 0x1.fffffep+127)`.
|
|
148
103
|
|
|
104
|
+
## Projects using watr
|
|
149
105
|
|
|
106
|
+
* [auro](https://github.com/audio-lab/auro) – audio processing language
|
|
150
107
|
|
|
151
108
|
## Useful links
|
|
152
109
|
|
|
@@ -162,20 +119,19 @@ end
|
|
|
162
119
|
* [wabt source search](https://github.com/WebAssembly/wabt/search?p=5&q=then)
|
|
163
120
|
* [wat control flow](https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Control_flow)
|
|
164
121
|
* [ontouchstart wasm book](https://ontouchstart.pages.dev/chapter_wasm_binary)
|
|
165
|
-
* [wat-compiler](https://github.com/stagas/wat-compiler/)
|
|
166
122
|
* [hackernoon](https://web.archive.org/web/20210215171830/https://hackernoon.com/webassembly-binary-format-explained-part-2-hj1t33yp?source=rss)
|
|
167
123
|
* [webassemblyjs](https://github.com/xtuc/webassemblyjs)
|
|
168
124
|
* [chasm](https://github.com/ColinEberhardt/chasm/blob/master/src/encoding.ts)
|
|
169
125
|
* [WebBS](https://github.com/j-s-n/WebBS)
|
|
170
126
|
* [leb128a](https://github.com/minhducsun2002/leb128/blob/master/src/index.ts)
|
|
171
127
|
* [leb128b](https://github.com/shmishtopher/wasm-LEB128/tree/master/esm)
|
|
172
|
-
|
|
173
128
|
-->
|
|
174
129
|
|
|
175
130
|
## Alternatives
|
|
176
131
|
|
|
177
132
|
* [wabt](https://www.npmjs.com/package/wabt) − port of WABT for the web, de-facto standard.
|
|
178
133
|
* [wat-compiler](https://www.npmjs.com/package/wat-compiler) − compact alternative for WABT, older brother of _watr_.
|
|
134
|
+
* [wassemble](https://github.com/wingo/wassemble)
|
|
179
135
|
* [web49](https://github.com/FastVM/Web49)
|
|
180
136
|
|
|
181
137
|
<p align=center><a href="https://github.com/krsnzd/license/">🕉</a></p>
|
package/src/compile.js
CHANGED
|
@@ -2,8 +2,7 @@ import { uleb, leb, bigleb, f64, f32 } from './util.js'
|
|
|
2
2
|
import { OP, SECTION, ALIGN, TYPE, KIND } from './const.js'
|
|
3
3
|
import parse from './parse.js'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
const INLINE = { loop: 1, block: 1, if: 1, end: -1, return: -1 }
|
|
5
|
+
const OP_END = 0xb, OP_I32_CONST = 0x41, OP_I64_CONST = 0x42, OP_F32_CONST = 0x43, OP_F64_CONST = 0x44
|
|
7
6
|
|
|
8
7
|
// convert wat tree to wasm binary
|
|
9
8
|
export default (nodes) => {
|
|
@@ -43,13 +42,11 @@ export default (nodes) => {
|
|
|
43
42
|
for (let node of nodes) {
|
|
44
43
|
node[0] === name ? postcall.push(build[name](node, sections)) : remaining.push(node)
|
|
45
44
|
}
|
|
46
|
-
|
|
47
45
|
nodes = remaining
|
|
48
46
|
}
|
|
49
47
|
|
|
50
48
|
// code must be compiled after all definitions
|
|
51
|
-
for (let cb of postcall) cb
|
|
52
|
-
|
|
49
|
+
for (let cb of postcall) cb?.()
|
|
53
50
|
|
|
54
51
|
// 3. build binary
|
|
55
52
|
for (let name in sections) {
|
|
@@ -69,37 +66,16 @@ const build = {
|
|
|
69
66
|
// (type $name? (func (param $x i32) (param i64 i32) (result i32 i64)))
|
|
70
67
|
// signature part is identical to function
|
|
71
68
|
// FIXME: handle non-function types
|
|
72
|
-
type([, typeName,
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (kind === 'func') {
|
|
77
|
-
// collect params
|
|
78
|
-
while (sig[0]?.[0] === 'param') {
|
|
79
|
-
let [, ...types] = sig.shift()
|
|
80
|
-
if (types[0]?.[0] === '$') params[types.shift()] = params.length
|
|
81
|
-
params.push(...types.map(t => TYPE[t]))
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// collect result type
|
|
85
|
-
if (sig[0]?.[0] === 'result') result = sig.shift().slice(1).map(t => TYPE[t])
|
|
86
|
-
|
|
87
|
-
// reuse existing type or register new one
|
|
88
|
-
bytes = [TYPE.func, ...uleb(params.length), ...params, ...uleb(result.length), ...result]
|
|
89
|
-
|
|
90
|
-
idx = ctx.type.findIndex((prevType) => prevType.every((byte, i) => byte === bytes[i]))
|
|
91
|
-
if (idx < 0) idx = ctx.type.push(bytes) - 1
|
|
92
|
-
}
|
|
93
|
-
|
|
69
|
+
type([, typeName, [kind, ...sig]], ctx) {
|
|
70
|
+
if (kind !== 'func') err(`Unknown type kind '${kind}'`)
|
|
71
|
+
const [idx] = consumeType(sig, ctx)
|
|
94
72
|
if (typeName) ctx.type[typeName] = idx
|
|
95
|
-
|
|
96
|
-
return [idx, params, result]
|
|
97
73
|
},
|
|
98
74
|
|
|
99
75
|
// (func $name? ...params result ...body)
|
|
100
76
|
func([, ...body], ctx) {
|
|
101
77
|
let locals = [], // list of local variables
|
|
102
|
-
|
|
78
|
+
blocks = [] // control instructions / blocks stack
|
|
103
79
|
|
|
104
80
|
// fn name
|
|
105
81
|
if (body[0]?.[0] === '$') ctx.func[body.shift()] = ctx.func.length
|
|
@@ -107,10 +83,10 @@ const build = {
|
|
|
107
83
|
// export binding
|
|
108
84
|
if (body[0]?.[0] === 'export') build.export([...body.shift(), ['func', ctx.func.length]], ctx)
|
|
109
85
|
|
|
110
|
-
// register type
|
|
111
|
-
let [typeIdx, params, result] =
|
|
112
|
-
|
|
113
|
-
|
|
86
|
+
// register/consume type info
|
|
87
|
+
let [typeIdx, params, result] = consumeType(body, ctx)
|
|
88
|
+
|
|
89
|
+
// register new function
|
|
114
90
|
ctx.func.push([typeIdx])
|
|
115
91
|
|
|
116
92
|
// collect locals
|
|
@@ -125,164 +101,148 @@ const build = {
|
|
|
125
101
|
// squash local types
|
|
126
102
|
let locTypes = locals.reduce((a, type) => (type == a[a.length - 1] ? a[a.length - 2]++ : a.push(1, type), a), [])
|
|
127
103
|
|
|
128
|
-
//
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
let opCode = OP[op], argc = 0, before = [], after = [], id
|
|
132
|
-
|
|
133
|
-
// NOTE: we could reorganize ops by groups and detect signature as `op in STORE`
|
|
134
|
-
// but numeric comparison is faster than generic hash lookup
|
|
135
|
-
// FIXME: we often use OP.end or alike: what if we had list of global constants?
|
|
136
|
-
|
|
137
|
-
// binary/unary
|
|
138
|
-
if (opCode >= 69) {
|
|
139
|
-
argc = opCode >= 167 ||
|
|
140
|
-
(opCode <= 159 && opCode >= 153) ||
|
|
141
|
-
(opCode <= 145 && opCode >= 139) ||
|
|
142
|
-
(opCode <= 123 && opCode >= 121) ||
|
|
143
|
-
(opCode <= 105 && opCode >= 103) ||
|
|
144
|
-
opCode == 80 || opCode == 69 ? 1 : 2
|
|
145
|
-
}
|
|
146
|
-
// instruction
|
|
147
|
-
else {
|
|
148
|
-
// (i32.store align=n offset=m at value)
|
|
149
|
-
if (opCode >= 40 && opCode <= 62) {
|
|
150
|
-
// FIXME: figure out point in Math.log2 aligns
|
|
151
|
-
let o = { align: ALIGN[op], offset: 0 }, p
|
|
152
|
-
while (nodes[0]?.includes('=')) p = nodes.shift().split('='), o[p[0]] = Number(p[1])
|
|
153
|
-
after = [Math.log2(o.align), ...uleb(o.offset)]
|
|
154
|
-
argc = opCode >= 54 ? 2 : 1
|
|
155
|
-
}
|
|
104
|
+
// convert sequence of instructions from input nodes to out bytes
|
|
105
|
+
const consume = (nodes, out = []) => {
|
|
106
|
+
if (!nodes?.length) return out
|
|
156
107
|
|
|
157
|
-
|
|
158
|
-
else if (opCode >= 65 && opCode <= 68) {
|
|
159
|
-
after = (opCode == 65 ? leb : opCode == 66 ? bigleb : opCode == 67 ? f32 : f64)(nodes.shift())
|
|
160
|
-
}
|
|
108
|
+
let op = nodes.shift(), opCode, args = nodes, immed, id, group
|
|
161
109
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
110
|
+
// groups are flattened, eg. (cmd z w) -> z w cmd
|
|
111
|
+
if (group = Array.isArray(op)) {
|
|
112
|
+
args = [...op] // op is immutable
|
|
113
|
+
opCode = OP.indexOf(op = args.shift())
|
|
114
|
+
}
|
|
115
|
+
else opCode = OP.indexOf(op)
|
|
167
116
|
|
|
168
|
-
|
|
169
|
-
else if (opCode == 35 || opCode == 36) {
|
|
170
|
-
after = uleb(nodes[0]?.[0] === '$' ? ctx.global[nodes.shift()] : nodes.shift())
|
|
171
|
-
if (opCode > 35) argc = 1
|
|
172
|
-
}
|
|
117
|
+
// NOTE: numeric comparison is faster than generic hash lookup
|
|
173
118
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
let fnName = nodes.shift()
|
|
177
|
-
after = uleb(id = fnName[0] === '$' ? ctx.func[fnName] ?? err('Unknown function `' + fnName + '`') : fnName);
|
|
178
|
-
// FIXME: how to get signature of imported function
|
|
179
|
-
[, argc] = ctx.type[ctx.func[id][0]]
|
|
180
|
-
}
|
|
119
|
+
// binary/unary - just consume immed
|
|
120
|
+
if (opCode >= 69) { }
|
|
181
121
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
122
|
+
// (i32.store align=n offset=m at value)
|
|
123
|
+
else if (opCode >= 40 && opCode <= 62) {
|
|
124
|
+
// FIXME: figure out point in Math.log2 aligns
|
|
125
|
+
let o = { align: ALIGN[op], offset: 0 }, param
|
|
126
|
+
while (args[0]?.includes('=')) param = args.shift().split('='), o[param[0]] = Number(param[1])
|
|
127
|
+
immed = [Math.log2(o.align), ...uleb(o.offset)]
|
|
128
|
+
}
|
|
189
129
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
130
|
+
// (i32.const 123)
|
|
131
|
+
else if (opCode >= 65 && opCode <= 68) {
|
|
132
|
+
immed = (opCode == 65 ? leb : opCode == 66 ? bigleb : opCode == 67 ? f32 : f64)(args.shift())
|
|
133
|
+
}
|
|
195
134
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
after = [TYPE[type]]
|
|
201
|
-
|
|
202
|
-
argc = 0, before.push(...instr(nodes.shift()))
|
|
203
|
-
let body
|
|
204
|
-
if (nodes[0]?.[0] === 'then') [, ...body] = nodes.shift(); else body = nodes
|
|
205
|
-
after.push(...consume(body))
|
|
206
|
-
|
|
207
|
-
callstack.pop(), callstack.push(OP.else)
|
|
208
|
-
if (nodes[0]?.[0] === 'else') {
|
|
209
|
-
[, ...body] = nodes.shift()
|
|
210
|
-
if (body.length) after.push(OP.else, ...consume(body))
|
|
211
|
-
}
|
|
212
|
-
callstack.pop()
|
|
213
|
-
after.push(OP.end)
|
|
214
|
-
}
|
|
135
|
+
// (local.get $id), (local.tee $id x)
|
|
136
|
+
else if (opCode >= 32 && opCode <= 34) {
|
|
137
|
+
immed = uleb(args[0]?.[0] === '$' ? params[id = args.shift()] || locals[id] : args.shift())
|
|
138
|
+
}
|
|
215
139
|
|
|
216
|
-
|
|
217
|
-
|
|
140
|
+
// (global.get id), (global.set id)
|
|
141
|
+
else if (opCode == 35 || opCode == 36) {
|
|
142
|
+
immed = uleb(args[0]?.[0] === '$' ? ctx.global[args.shift()] : args.shift())
|
|
143
|
+
}
|
|
218
144
|
|
|
219
|
-
|
|
220
|
-
|
|
145
|
+
// (call id ...nodes)
|
|
146
|
+
else if (opCode == 16) {
|
|
147
|
+
let fnName = args.shift()
|
|
148
|
+
immed = uleb(id = fnName[0] === '$' ? ctx.func[fnName] ?? err('Unknown function `' + fnName + '`') : fnName);
|
|
149
|
+
// FIXME: how to get signature of imported function
|
|
150
|
+
}
|
|
221
151
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
152
|
+
// (call_indirect (type $typeName) (idx) ...nodes)
|
|
153
|
+
else if (opCode == 17) {
|
|
154
|
+
let typeId = args.shift()[1];
|
|
155
|
+
typeId = typeId[0] === '$' ? ctx.type[typeId] : typeId
|
|
156
|
+
immed = uleb(typeId), immed.push(0) // extra immediate indicates table idx (reserved)
|
|
157
|
+
}
|
|
228
158
|
|
|
229
|
-
|
|
230
|
-
|
|
159
|
+
// FIXME (memory.grow $idx?)
|
|
160
|
+
else if (opCode == 63 || opCode == 64) {
|
|
161
|
+
immed = [0]
|
|
162
|
+
}
|
|
231
163
|
|
|
232
|
-
|
|
233
|
-
|
|
164
|
+
// (if ...), (block ...), (loop ...)
|
|
165
|
+
else if (opCode > 1 && opCode < 5) {
|
|
166
|
+
blocks.push(opCode)
|
|
234
167
|
|
|
235
|
-
// (
|
|
236
|
-
|
|
237
|
-
else if (opCode == 0x0c || opCode == 0x0d) {
|
|
238
|
-
// br index indicates how many callstack items to pop
|
|
239
|
-
after = uleb(nodes[0]?.[0] === '$' ? callstack.length - callstack[nodes.shift()] : nodes.shift())
|
|
240
|
-
argc = (opCode == 0x0d ? 1 + (nodes.length > 1) : !!nodes.length)
|
|
241
|
-
}
|
|
168
|
+
// (block $x) (loop $y)
|
|
169
|
+
if (opCode < 4 && args[0]?.[0] === '$') (blocks[args.shift()] = blocks.length)
|
|
242
170
|
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
171
|
+
// get type
|
|
172
|
+
// (result i32)
|
|
173
|
+
if (args[0]?.[0] === 'result') {
|
|
174
|
+
if (args[0].length < 3) {
|
|
175
|
+
let [, type] = args.shift()
|
|
176
|
+
immed = [TYPE[type]]
|
|
177
|
+
}
|
|
178
|
+
// (result i32 i32)
|
|
179
|
+
else {
|
|
180
|
+
let [typeId] = consumeType(args, ctx)
|
|
181
|
+
immed = [typeId]
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else immed = [TYPE.void]
|
|
185
|
+
|
|
186
|
+
if (group) {
|
|
187
|
+
nodes.unshift('end')
|
|
188
|
+
|
|
189
|
+
// (block xxx) -> block xxx end
|
|
190
|
+
if (opCode < 4) while (args.length) nodes.unshift(args.pop())
|
|
191
|
+
|
|
192
|
+
// (if cond a) -> cond if a end
|
|
193
|
+
else if (args.length < 3) nodes.unshift(args.pop())
|
|
194
|
+
// (if cond (then a) (else b)) -> `cond if a else b end`
|
|
195
|
+
else {
|
|
196
|
+
nodes.unshift(args.pop())
|
|
197
|
+
// (if cond a b) -> (if cond a else b)
|
|
198
|
+
if (nodes[0][0] !== 'else') nodes.unshift('else')
|
|
199
|
+
// (if a b (else)) -> (if a b)
|
|
200
|
+
else if (nodes[0].length < 2) nodes.shift()
|
|
201
|
+
nodes.unshift(args.pop())
|
|
202
|
+
}
|
|
249
203
|
}
|
|
204
|
+
}
|
|
250
205
|
|
|
251
|
-
|
|
206
|
+
// (else)
|
|
207
|
+
else if (opCode === 5) {
|
|
208
|
+
// (else xxx) -> else xxx
|
|
209
|
+
if (group) while (args.length) nodes.unshift(args.pop())
|
|
252
210
|
}
|
|
253
211
|
|
|
254
|
-
//
|
|
255
|
-
if (
|
|
256
|
-
while (argc--) before.push(...instr(nodes.shift()))
|
|
257
|
-
if (nodes.length) err(`Too many arguments for \`${op}\`.`)
|
|
212
|
+
// (end)
|
|
213
|
+
else if (opCode == 0x0b) blocks.pop()
|
|
258
214
|
|
|
259
|
-
|
|
260
|
-
|
|
215
|
+
// (br $label result?)
|
|
216
|
+
// (br_if $label cond result?)
|
|
217
|
+
else if (opCode == 0x0c || opCode == 0x0d) {
|
|
218
|
+
// br index indicates how many block items to pop
|
|
219
|
+
immed = uleb(args[0]?.[0] === '$' ? blocks.length - blocks[args.shift()] : args.shift())
|
|
220
|
+
}
|
|
261
221
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (typeof node === 'string') {
|
|
269
|
-
// permit some inline instructions: loop $label ... end, br $label, arg return
|
|
270
|
-
if (c = INLINE[node]) {
|
|
271
|
-
node = [node], node.inline = true
|
|
272
|
-
if (c > 0) nodes[0]?.[0] === '$' && node.push(nodes.shift())
|
|
273
|
-
}
|
|
274
|
-
else err(`Inline instruction \`${node}\` is not supported`)
|
|
275
|
-
}
|
|
222
|
+
// (br_table 1 2 3 4 0 selector result?)
|
|
223
|
+
else if (opCode == 0x0e) {
|
|
224
|
+
immed = []
|
|
225
|
+
while (!Array.isArray(args[0])) id = args.shift(), immed.push(...uleb(id[0][0] === '$' ? blocks.length - blocks[id] : id))
|
|
226
|
+
immed.unshift(...uleb(immed.length - 1))
|
|
227
|
+
}
|
|
276
228
|
|
|
277
|
-
|
|
229
|
+
// if group (cmd im1 im2 arg1 arg2) - insert any remaining args first: arg1 arg2
|
|
230
|
+
// because inline case has them in stack already
|
|
231
|
+
if (group) {
|
|
232
|
+
while (args.length) consume(args, out)
|
|
278
233
|
}
|
|
279
|
-
|
|
234
|
+
|
|
235
|
+
// ignore (then) and other unknown instructions
|
|
236
|
+
if (opCode >= 0) out.push(opCode)
|
|
237
|
+
if (immed) out.push(...immed)
|
|
280
238
|
}
|
|
281
239
|
|
|
282
|
-
// evaluates after all definitions
|
|
240
|
+
// evaluates after all definitions (need globals, elements, data etc.)
|
|
241
|
+
// FIXME: get rid of this postcall
|
|
283
242
|
return () => {
|
|
284
|
-
|
|
285
|
-
|
|
243
|
+
const bytes = []
|
|
244
|
+
while (body.length) consume(body, bytes)
|
|
245
|
+
ctx.code.push([...uleb(bytes.length + 2 + locTypes.length), ...uleb(locTypes.length >> 1), ...locTypes, ...bytes, OP_END])
|
|
286
246
|
}
|
|
287
247
|
},
|
|
288
248
|
|
|
@@ -337,7 +297,7 @@ const build = {
|
|
|
337
297
|
if (kind === 'func') {
|
|
338
298
|
// we track imported funcs in func section to share namespace, and skip them on final build
|
|
339
299
|
if (name) ctx.func[name] = ctx.func.length
|
|
340
|
-
let [typeIdx] =
|
|
300
|
+
let [typeIdx] = consumeType(parts, ctx)
|
|
341
301
|
ctx.func.push(details = uleb(typeIdx))
|
|
342
302
|
ctx.func.importc = (ctx.func.importc || 0) + 1
|
|
343
303
|
}
|
|
@@ -372,8 +332,8 @@ const build = {
|
|
|
372
332
|
|
|
373
333
|
// (i32.const 0) - instantiation time initializer
|
|
374
334
|
const iinit = ([op, literal], ctx) => op[0] === 'f' ?
|
|
375
|
-
[
|
|
376
|
-
[
|
|
335
|
+
[op[1] === '3' ? OP_F32_CONST : OP_F64_CONST, ...(op[1] === '3' ? f32 : f64)(literal), OP_END] :
|
|
336
|
+
[op[1] === '3' ? OP_I32_CONST : OP_I64_CONST, ...(op[1] === '3' ? leb : bigleb)(literal[0] === '$' ? ctx.global[literal] : literal), OP_END]
|
|
377
337
|
|
|
378
338
|
const escape = { n: 10, r: 13, t: 9, v: 1 }
|
|
379
339
|
|
|
@@ -391,8 +351,33 @@ const str = str => {
|
|
|
391
351
|
return res
|
|
392
352
|
}
|
|
393
353
|
|
|
394
|
-
|
|
395
354
|
// build range/limits sequence (non-consuming)
|
|
396
355
|
const range = ([min, max, shared]) => isNaN(parseInt(max)) ? [0, ...uleb(min)] : [shared === 'shared' ? 3 : 1, ...uleb(min), ...uleb(max)]
|
|
397
356
|
|
|
357
|
+
// get type info from (params) (result) nodes sequence (consumes nodes)
|
|
358
|
+
// returns registered (reused) type idx, params bytes, result bytes
|
|
359
|
+
// eg. (type $return_i32 (func (result i32)))
|
|
360
|
+
const consumeType = (nodes, ctx) => {
|
|
361
|
+
let params = [], result = [], idx, bytes
|
|
362
|
+
|
|
363
|
+
// collect params
|
|
364
|
+
while (nodes[0]?.[0] === 'param') {
|
|
365
|
+
let [, ...types] = nodes.shift()
|
|
366
|
+
if (types[0]?.[0] === '$') params[types.shift()] = params.length
|
|
367
|
+
params.push(...types.map(t => TYPE[t]))
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// collect result type
|
|
371
|
+
if (nodes[0]?.[0] === 'result') result = nodes.shift().slice(1).map(t => TYPE[t])
|
|
372
|
+
|
|
373
|
+
// reuse existing type or register new one
|
|
374
|
+
bytes = [TYPE.func, ...uleb(params.length), ...params, ...uleb(result.length), ...result]
|
|
375
|
+
idx = ctx.type.findIndex((t) => t.every((byte, i) => byte === bytes[i]))
|
|
376
|
+
|
|
377
|
+
// register new type, if not found
|
|
378
|
+
if (idx < 0) idx = ctx.type.push(bytes) - 1
|
|
379
|
+
|
|
380
|
+
return [idx, params, result]
|
|
381
|
+
}
|
|
382
|
+
|
|
398
383
|
const err = text => { throw Error(text) }
|
package/src/const.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// ref: https://github.com/stagas/wat-compiler/blob/main/lib/const.js
|
|
2
2
|
// NOTE: squashing into a string doesn't save up gzipped size
|
|
3
3
|
export const OP = [
|
|
4
|
-
'unreachable', 'nop', 'block', 'loop', 'if', 'else',
|
|
5
|
-
'end', 'br', 'br_if', 'br_table', 'return', 'call', 'call_indirect',
|
|
6
|
-
'drop', 'select',
|
|
7
|
-
'local.get', 'local.set', 'local.tee', 'global.get', 'global.set',
|
|
4
|
+
'unreachable', 'nop', 'block', 'loop', 'if', 'else', , , , , ,
|
|
5
|
+
'end', 'br', 'br_if', 'br_table', 'return', 'call', 'call_indirect', , , , , , , , ,
|
|
6
|
+
'drop', 'select', , , , ,
|
|
7
|
+
'local.get', 'local.set', 'local.tee', 'global.get', 'global.set', , , ,
|
|
8
8
|
'i32.load', 'i64.load', 'f32.load', 'f64.load',
|
|
9
9
|
'i32.load8_s', 'i32.load8_u', 'i32.load16_s', 'i32.load16_u',
|
|
10
10
|
'i64.load8_s', 'i64.load8_u', 'i64.load16_s', 'i64.load16_u', 'i64.load32_s', 'i64.load32_u',
|
|
@@ -14,8 +14,8 @@ export const OP = [
|
|
|
14
14
|
'i32.const', 'i64.const', 'f32.const', 'f64.const',
|
|
15
15
|
'i32.eqz', 'i32.eq', 'i32.ne', 'i32.lt_s', 'i32.lt_u', 'i32.gt_s', 'i32.gt_u', 'i32.le_s', 'i32.le_u', 'i32.ge_s', 'i32.ge_u',
|
|
16
16
|
'i64.eqz', 'i64.eq', 'i64.ne', 'i64.lt_s', 'i64.lt_u', 'i64.gt_s', 'i64.gt_u', 'i64.le_s', 'i64.le_u', 'i64.ge_s', 'i64.ge_u',
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
'f32.eq', 'f32.ne', 'f32.lt', 'f32.gt', 'f32.le', 'f32.ge',
|
|
18
|
+
'f64.eq', 'f64.ne', 'f64.lt', 'f64.gt', 'f64.le', 'f64.ge',
|
|
19
19
|
'i32.clz', 'i32.ctz', 'i32.popcnt', 'i32.add', 'i32.sub', 'i32.mul', 'i32.div_s', 'i32.div_u', 'i32.rem_s', 'i32.rem_u', 'i32.and', 'i32.or', 'i32.xor', 'i32.shl', 'i32.shr_s', 'i32.shr_u', 'i32.rotl', 'i32.rotr',
|
|
20
20
|
'i64.clz', 'i64.ctz', 'i64.popcnt', 'i64.add', 'i64.sub', 'i64.mul', 'i64.div_s', 'i64.div_u', 'i64.rem_s', 'i64.rem_u', 'i64.and', 'i64.or', 'i64.xor', 'i64.shl', 'i64.shr_s', 'i64.shr_u', 'i64.rotl', 'i64.rotr',
|
|
21
21
|
'f32.abs', 'f32.neg', 'f32.ceil', 'f32.floor', 'f32.trunc', 'f32.nearest', 'f32.sqrt', 'f32.add', 'f32.sub', 'f32.mul', 'f32.div', 'f32.min', 'f32.max', 'f32.copysign',
|
|
@@ -27,15 +27,16 @@ export const OP = [
|
|
|
27
27
|
'f64.convert_i32_s', 'f64.convert_i32_u', 'f64.convert_i64_s', 'f64.convert_i64_u', 'f64.promote_f32',
|
|
28
28
|
'i32.reinterpret_f32', 'i64.reinterpret_f64', 'f32.reinterpret_i32', 'f64.reinterpret_i64',
|
|
29
29
|
],
|
|
30
|
-
SECTION = { type:1, import:2, func:3, table:4, memory:5, global:6, export:7, start:8, elem:9, code:10, data:11 },
|
|
31
|
-
TYPE = { i32:0x7f, i64:0x7e, f32:0x7d, f64:0x7c, void:0x40, func:0x60, funcref:0x70 },
|
|
32
|
-
KIND = { func: 0, table: 1, memory: 2, global: 3 },
|
|
33
|
-
ALIGN = {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
30
|
+
SECTION = { type: 1, import: 2, func: 3, table: 4, memory: 5, global: 6, export: 7, start: 8, elem: 9, code: 10, data: 11 },
|
|
31
|
+
TYPE = { i32: 0x7f, i64: 0x7e, f32: 0x7d, f64: 0x7c, void: 0x40, func: 0x60, funcref: 0x70 },
|
|
32
|
+
KIND = { func: 0, table: 1, memory: 2, global: 3 },
|
|
33
|
+
ALIGN = {
|
|
34
|
+
'i32.load': 4, 'i64.load': 8, 'f32.load': 4, 'f64.load': 8,
|
|
35
|
+
'i32.load8_s': 1, 'i32.load8_u': 1, 'i32.load16_s': 2, 'i32.load16_u': 2,
|
|
36
|
+
'i64.load8_s': 1, 'i64.load8_u': 1, 'i64.load16_s': 2, 'i64.load16_u': 2, 'i64.load32_s': 4, 'i64.load32_u': 4, 'i32.store': 4,
|
|
37
|
+
'i64.store': 8, 'f32.store': 4, 'f64.store': 8,
|
|
38
|
+
'i32.store8': 1, 'i32.store16': 2, 'i64.store8': 1, 'i64.store16': 2, 'i64.store32': 4,
|
|
39
|
+
},
|
|
40
|
+
BLOCK = {
|
|
41
|
+
loop: 1, block: 1, if: 1, end: -1, return: -1
|
|
42
|
+
}
|