watr 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/readme.md +9 -12
- package/src/compile.js +317 -214
- package/src/const.js +7 -3
- package/src/parse.js +10 -6
- package/src/util.js +46 -0
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -77,25 +77,22 @@ print(src, {
|
|
|
77
77
|
## Status
|
|
78
78
|
|
|
79
79
|
* [x] core
|
|
80
|
-
* [x] [mutable globals](https://github.com/WebAssembly/mutable-global), [extended const](https://github.com/WebAssembly/extended-const/blob/main/proposals/extended-const/Overview.md), [
|
|
81
|
-
* [x] [multi-value](https://github.com/WebAssembly/spec/blob/master/proposals/multi-value/Overview.md), [bulk memory ops](https://github.com/WebAssembly/bulk-memory-operations/blob/master/proposals/bulk-memory-operations/Overview.md), [multiple memories](https://github.com/WebAssembly/multi-memory/blob/
|
|
80
|
+
* [x] [mutable globals](https://github.com/WebAssembly/mutable-global), [extended const](https://github.com/WebAssembly/extended-const/blob/main/proposals/extended-const/Overview.md), [sign extension](https://github.com/WebAssembly/sign-extension-ops), [nontrapping float to int](https://github.com/WebAssembly/nontrapping-float-to-int-conversions)
|
|
81
|
+
* [x] [multi-value](https://github.com/WebAssembly/spec/blob/master/proposals/multi-value/Overview.md), [bulk memory ops](https://github.com/WebAssembly/bulk-memory-operations/blob/master/proposals/bulk-memory-operations/Overview.md), [multiple memories](https://github.com/WebAssembly/multi-memory/blob/main/proposals/multi-memory/Overview.md)
|
|
82
82
|
* [x] [simd](https://github.com/WebAssembly/simd/blob/master/proposals/simd/SIMD.md), [relaxed simd](https://github.com/WebAssembly/relaxed-simd), [fixed-width simd](https://github.com/WebAssembly/simd/blob/master/proposals/simd/SIMD.md)
|
|
83
83
|
* [x] [tail_call](https://github.com/WebAssembly/tail-call)
|
|
84
|
-
* [x] [ref types](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md), [func refs](https://github.com/WebAssembly/function-references/blob/main/proposals/function-references/Overview.md)
|
|
85
|
-
* [x] [
|
|
86
|
-
* [ ] [exceptions](https://github.com/WebAssembly/exception-handling)
|
|
87
|
-
* [ ] [memory64](https://github.com/WebAssembly/memory64)
|
|
88
|
-
* [ ] [annotations](https://github.com/WebAssembly/annotations), [code_metadata](https://github.com/WebAssembly/tool-conventions/blob/main/CodeMetadata.md)
|
|
89
|
-
* [ ] [js strings](https://github.com/WebAssembly/js-string-builtins/blob/main/proposals/js-string-builtins/Overview.md)
|
|
84
|
+
* [x] [ref types](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md), [func refs](https://github.com/WebAssembly/function-references/blob/main/proposals/function-references/Overview.md), [gc](https://github.com/WebAssembly/gc)
|
|
85
|
+
* [x] [annotations](https://github.com/WebAssembly/annotations), [code_metadata](https://github.com/WebAssembly/tool-conventions/blob/main/CodeMetadata.md)
|
|
86
|
+
* [ ] [exceptions](https://github.com/WebAssembly/exception-handling), [memory64](https://github.com/WebAssembly/memory64), [js strings](https://github.com/WebAssembly/js-string-builtins/blob/main/proposals/js-string-builtins/Overview.md), wide arithmetic, threads, custom page size, wasm 3
|
|
90
87
|
|
|
91
88
|
## Alternatives
|
|
92
89
|
|
|
93
90
|
| Size (gzipped) | Performance
|
|
94
91
|
---|---|---
|
|
95
|
-
watr |
|
|
96
|
-
[spec/wast.js](https://github.com/WebAssembly/spec/tree/main/interpreter#javascript-library) | 216 kb |
|
|
97
|
-
[wabt](https://github.com/WebAssembly/wabt) | 282 kb | 2
|
|
98
|
-
[wat-compiler](https://github.com/stagas/wat-compiler) | 7.7 kb |
|
|
92
|
+
watr | 7.5 kb | 6.0 op/s
|
|
93
|
+
[spec/wast.js](https://github.com/WebAssembly/spec/tree/main/interpreter#javascript-library) | 216 kb | 2.2 op/s
|
|
94
|
+
[wabt](https://github.com/WebAssembly/wabt) | 282 kb | 1.2 op/s
|
|
95
|
+
[wat-compiler](https://github.com/stagas/wat-compiler) | 7.7 kb | 0.7 op/s
|
|
99
96
|
|
|
100
97
|
<!--
|
|
101
98
|
## Projects using watr
|
package/src/compile.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import * as encode from './encode.js'
|
|
2
2
|
import { uleb, i32, i64 } from './encode.js'
|
|
3
|
-
import { SECTION, TYPE, KIND, INSTR, HEAPTYPE, DEFTYPE, RECTYPE, REFTYPE } from './const.js'
|
|
3
|
+
import { SECTION, TYPE, KIND, INSTR, HEAPTYPE, DEFTYPE, RECTYPE, REFTYPE, ESCAPE } from './const.js'
|
|
4
4
|
import parse from './parse.js'
|
|
5
|
-
import { clone, err } from './util.js'
|
|
5
|
+
import { clone, err, str } from './util.js'
|
|
6
6
|
|
|
7
7
|
// build instructions index
|
|
8
8
|
INSTR.forEach((op, i) => INSTR[op] = i >= 0x133 ? [0xfd, i - 0x133] : i >= 0x11b ? [0xfc, i - 0x11b] : i >= 0xfb ? [0xfb, i - 0xfb] : [i]);
|
|
9
9
|
|
|
10
|
+
// recursively strip all annotation nodes from AST, except @custom and @metadata.code.*
|
|
11
|
+
const unannot = (node) => Array.isArray(node) ? (node[0]?.[0] === '@' && node[0] !== '@custom' && !node[0]?.startsWith?.('@metadata.code.') ? null : node.map(unannot).filter(n => n != null)) : node
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Converts a WebAssembly Text Format (WAT) tree to a WebAssembly binary format (WASM).
|
|
@@ -20,6 +22,9 @@ export default function watr(nodes) {
|
|
|
20
22
|
if (typeof nodes === 'string') nodes = parse(nodes);
|
|
21
23
|
else nodes = clone(nodes)
|
|
22
24
|
|
|
25
|
+
// strip annotations (text-format only), except @custom which becomes binary custom sections
|
|
26
|
+
nodes = unannot(nodes) || []
|
|
27
|
+
|
|
23
28
|
// module abbr https://webassembly.github.io/spec/core/text/modules.html#id10
|
|
24
29
|
if (nodes[0] === 'module') nodes.shift(), nodes[0]?.[0] === '$' && nodes.shift()
|
|
25
30
|
// single node, not module
|
|
@@ -28,115 +33,109 @@ export default function watr(nodes) {
|
|
|
28
33
|
// binary abbr "\00" "\0x61" ...
|
|
29
34
|
if (nodes[0] === 'binary') {
|
|
30
35
|
nodes.shift()
|
|
31
|
-
return Uint8Array.from(str(nodes
|
|
36
|
+
return Uint8Array.from(str(...nodes))
|
|
32
37
|
}
|
|
33
38
|
// quote "a" "b"
|
|
34
39
|
else if (nodes[0] === 'quote') {
|
|
35
40
|
nodes.shift()
|
|
36
|
-
return watr(nodes.map(
|
|
41
|
+
return watr(nodes.map(s => s.slice(1, -1)).join(''))
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
// scopes are aliased by key as well, eg. section.func.$name = section[SECTION.func] = idx
|
|
40
45
|
const ctx = []
|
|
41
46
|
for (let kind in SECTION) (ctx[SECTION[kind]] = ctx[kind] = []).name = kind
|
|
42
|
-
ctx._ = {} // implicit types
|
|
43
|
-
|
|
44
|
-
let subc // current subtype count
|
|
45
|
-
|
|
46
|
-
// prepare/normalize nodes
|
|
47
|
-
while (nodes.length) {
|
|
48
|
-
let [kind, ...node] = nodes.shift()
|
|
49
|
-
let imported // if node needs to be imported
|
|
50
|
-
let rec // number of subtypes under rec type
|
|
51
47
|
|
|
48
|
+
// initialize types
|
|
49
|
+
nodes.filter(([kind, ...node]) => {
|
|
52
50
|
// (rec (type $a (sub final? $sup* (func ...))...) (type $b ...)) -> save subtypes
|
|
53
51
|
if (kind === 'rec') {
|
|
54
52
|
// node contains a list of subtypes, (type ...) or (type (sub final? ...))
|
|
55
53
|
// convert rec type into regular type (first subtype) with stashed subtypes length
|
|
56
54
|
// add rest of subtypes as regular type nodes with subtype flag
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// (import m n (table|memory|global|func id? type)) -> (table|memory|global|func id? (import m n) type)
|
|
63
|
-
else if (kind === 'import') [kind, ...node] = (imported = node).pop()
|
|
64
|
-
|
|
65
|
-
// index, alias
|
|
66
|
-
let items = ctx[kind];
|
|
67
|
-
let name = alias(node, items)
|
|
68
|
-
|
|
69
|
-
// export abbr
|
|
70
|
-
// (table|memory|global|func id? (export n)* ...) -> (table|memory|global|func id ...) (export n (table|memory|global|func id))
|
|
71
|
-
while (node[0]?.[0] === 'export') ctx.export.push([node.shift()[1], [kind, items.length]])
|
|
72
|
-
|
|
73
|
-
// for import nodes - redirect output to import
|
|
74
|
-
if (node[0]?.[0] === 'import') [, ...imported] = node.shift()
|
|
75
|
-
|
|
76
|
-
// table abbr
|
|
77
|
-
if (kind === 'table') {
|
|
78
|
-
// (table id? reftype (elem ...{n})) -> (table id? n n reftype) (elem (table id) (i32.const 0) reftype ...)
|
|
79
|
-
if (node[1]?.[0] === 'elem') {
|
|
80
|
-
let [reftype, [, ...els]] = node
|
|
81
|
-
node = [els.length, els.length, reftype]
|
|
82
|
-
ctx.elem.push([['table', name || items.length], ['i32.const', '0'], reftype, ...els])
|
|
55
|
+
for (let i = 0; i < node.length; i++) {
|
|
56
|
+
let [, ...subnode] = node[i]
|
|
57
|
+
alias(subnode, ctx.type);
|
|
58
|
+
(subnode = typedef(subnode, ctx)).push(i ? true : [ctx.type.length, node.length])
|
|
59
|
+
ctx.type.push(subnode)
|
|
83
60
|
}
|
|
84
61
|
}
|
|
85
|
-
|
|
86
|
-
// data abbr
|
|
87
|
-
// (memory id? (data str)) -> (memory id? n n) (data (memory id) (i32.const 0) str)
|
|
88
|
-
else if (kind === 'memory' && node[0]?.[0] === 'data') {
|
|
89
|
-
let [, ...data] = node.shift(), m = '' + Math.ceil(data.map(s => s.slice(1, -1)).join('').length / 65536) // FIXME: figure out actual data size
|
|
90
|
-
ctx.data.push([['memory', items.length], ['i32.const', 0], ...data])
|
|
91
|
-
node = [m, m]
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// keep start name
|
|
95
|
-
else if (kind === 'start') name && node.push(name)
|
|
96
|
-
|
|
97
|
-
// normalize type definition to (func|array|struct dfn) form
|
|
98
62
|
// (type (func param* result*))
|
|
99
63
|
// (type (array (mut i8)))
|
|
100
64
|
// (type (struct (field a)*)
|
|
101
65
|
// (type (sub final? $nm* (struct|array|func ...)))
|
|
102
66
|
else if (kind === 'type') {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
67
|
+
alias(node, ctx.type);
|
|
68
|
+
ctx.type.push(typedef(node, ctx));
|
|
69
|
+
}
|
|
70
|
+
// (@custom "name" placement? data)
|
|
71
|
+
else if (kind === '@custom') {
|
|
72
|
+
ctx.custom.push(node) // node is just the arguments, not including @custom
|
|
73
|
+
}
|
|
74
|
+
// other sections may have id
|
|
75
|
+
else if (kind === 'start' || kind === 'export') ctx[kind].push(node)
|
|
76
|
+
|
|
77
|
+
else return true
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// prepare/normalize nodes
|
|
81
|
+
.forEach(([kind, ...node]) => {
|
|
82
|
+
let imported // if node needs to be imported
|
|
83
|
+
|
|
84
|
+
// import abbr
|
|
85
|
+
// (import m n (table|memory|global|func id? type)) -> (table|memory|global|func id? (import m n) type)
|
|
86
|
+
if (kind === 'import') [kind, ...node] = (imported = node).pop()
|
|
87
|
+
|
|
88
|
+
// index, alias
|
|
89
|
+
let items = ctx[kind];
|
|
90
|
+
let name = alias(node, items);
|
|
91
|
+
|
|
92
|
+
// export abbr
|
|
93
|
+
// (table|memory|global|func id? (export n)* ...) -> (table|memory|global|func id ...) (export n (table|memory|global|func id))
|
|
94
|
+
while (node[0]?.[0] === 'export') ctx.export.push([node.shift()[1], [kind, items?.length]])
|
|
95
|
+
|
|
96
|
+
// for import nodes - redirect output to import
|
|
97
|
+
if (node[0]?.[0] === 'import') [, ...imported] = node.shift()
|
|
98
|
+
|
|
99
|
+
// table abbr
|
|
100
|
+
if (kind === 'table') {
|
|
101
|
+
// (table id? reftype (elem ...{n})) -> (table id? n n reftype) (elem (table id) (i32.const 0) reftype ...)
|
|
102
|
+
if (node[1]?.[0] === 'elem') {
|
|
103
|
+
let [reftype, [, ...els]] = node
|
|
104
|
+
node = [els.length, els.length, reftype]
|
|
105
|
+
ctx.elem.push([['table', name || items.length], ['i32.const', '0'], reftype, ...els])
|
|
106
|
+
}
|
|
109
107
|
}
|
|
110
108
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
else if (
|
|
114
|
-
|
|
109
|
+
// data abbr
|
|
110
|
+
// (memory id? (data str)) -> (memory id? n n) (data (memory id) (i32.const 0) str)
|
|
111
|
+
else if (kind === 'memory' && node[0]?.[0] === 'data') {
|
|
112
|
+
let [, ...data] = node.shift(), m = '' + Math.ceil(data.map(s => s.slice(1, -1)).join('').length / 65536) // FIXME: figure out actual data size
|
|
113
|
+
ctx.data.push([['memory', items.length], ['i32.const', 0], ...data])
|
|
114
|
+
node = [m, m]
|
|
115
|
+
}
|
|
115
116
|
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
// dupe to code section, save implicit type
|
|
118
|
+
else if (kind === 'func') {
|
|
119
|
+
let [idx, param, result] = typeuse(node, ctx);
|
|
120
|
+
idx ??= regtype(param, result, ctx)
|
|
118
121
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
// we save idx because type can be defined after
|
|
124
|
-
!imported && nodes.push(['code', [idx, param, result], ...plain(node, ctx)]) // pass param since they may have names
|
|
125
|
-
node.unshift(['type', idx])
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// import writes to import section amd adds placeholder for (kind) section
|
|
129
|
-
if (imported) ctx.import.push([...imported, [kind, ...node]]), node = null
|
|
122
|
+
// we save idx because type can be defined after
|
|
123
|
+
!imported && ctx.code.push([[idx, param, result], ...plain(node, ctx)]) // pass param since they may have names
|
|
124
|
+
node.unshift(['type', idx])
|
|
125
|
+
}
|
|
130
126
|
|
|
131
|
-
|
|
132
|
-
|
|
127
|
+
// tag has a type similar to func
|
|
128
|
+
else if (kind === 'tag') {
|
|
129
|
+
let [idx, param] = typeuse(node, ctx);
|
|
130
|
+
idx ??= regtype(param, [], ctx)
|
|
131
|
+
node.unshift(['type', idx])
|
|
132
|
+
}
|
|
133
133
|
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
// import writes to import section amd adds placeholder for (kind) section
|
|
135
|
+
if (imported) ctx.import.push([...imported, [kind, ...node]]), node = null
|
|
136
136
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
// if (!ctx.data.length) ctx.datacount.length = 0
|
|
137
|
+
items?.push(node)
|
|
138
|
+
})
|
|
140
139
|
|
|
141
140
|
// convert nodes to bytes
|
|
142
141
|
const bin = (kind, count = true) => {
|
|
@@ -145,11 +144,14 @@ export default function watr(nodes) {
|
|
|
145
144
|
.map(item => build[kind](item, ctx))
|
|
146
145
|
.filter(Boolean) // filter out unrenderable things (subtype or data.length)
|
|
147
146
|
|
|
147
|
+
// Custom sections - each is output as separate section with own header
|
|
148
|
+
if (kind === SECTION.custom) return items.flatMap(content => [kind, ...vec(content)])
|
|
149
|
+
|
|
148
150
|
return !items.length ? [] : [kind, ...vec(count ? vec(items) : items)]
|
|
149
151
|
}
|
|
150
152
|
|
|
151
153
|
// build final binary
|
|
152
|
-
|
|
154
|
+
const out = [
|
|
153
155
|
0x00, 0x61, 0x73, 0x6d, // magic
|
|
154
156
|
0x01, 0x00, 0x00, 0x00, // version
|
|
155
157
|
...bin(SECTION.custom),
|
|
@@ -158,31 +160,153 @@ export default function watr(nodes) {
|
|
|
158
160
|
...bin(SECTION.func),
|
|
159
161
|
...bin(SECTION.table),
|
|
160
162
|
...bin(SECTION.memory),
|
|
163
|
+
...bin(SECTION.tag),
|
|
161
164
|
...bin(SECTION.global),
|
|
162
165
|
...bin(SECTION.export),
|
|
163
166
|
...bin(SECTION.start, false),
|
|
164
167
|
...bin(SECTION.elem),
|
|
165
168
|
...bin(SECTION.datacount, false),
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
// Build code section first (populates ctx.meta)
|
|
172
|
+
const codeSection = bin(SECTION.code)
|
|
173
|
+
|
|
174
|
+
// Build code metadata custom sections: metadata.code.<type>
|
|
175
|
+
for (const type in ctx.meta) {
|
|
176
|
+
const name = vec(str(`"metadata.code.${type}"`))
|
|
177
|
+
const content = vec(ctx.meta[type].map(([funcIdx, instances]) =>
|
|
178
|
+
[...uleb(funcIdx), ...vec(instances.map(([pos, data]) => [...uleb(pos), ...vec(str(data))]))]
|
|
179
|
+
))
|
|
180
|
+
out.push(0, ...vec([...name, ...content]))
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
out.push(...codeSection, ...bin(SECTION.data))
|
|
184
|
+
|
|
185
|
+
return Uint8Array.from(out)
|
|
169
186
|
}
|
|
170
187
|
|
|
171
188
|
// consume name eg. $t ...
|
|
172
189
|
const alias = (node, list) => {
|
|
173
190
|
let name = (node[0]?.[0] === '$' || node[0]?.[0] == null) && node.shift();
|
|
174
|
-
if (name) name in list ? err(`Duplicate ${list.name} ${name}`) : list[name] = list.length; // save alias
|
|
191
|
+
if (name && list) name in list ? err(`Duplicate ${list.name} ${name}`) : list[name] = list.length; // save alias
|
|
175
192
|
return name
|
|
176
193
|
}
|
|
177
194
|
|
|
195
|
+
// (type $id? (func param* result*))
|
|
196
|
+
// (type $id? (array (mut i8)))
|
|
197
|
+
// (type $id? (struct (field a)*)
|
|
198
|
+
// (type $id? (sub final? $nm* (struct|array|func ...)))
|
|
199
|
+
const typedef = ([dfn], ctx) => {
|
|
200
|
+
let subkind = 'subfinal', supertypes = [], compkind
|
|
201
|
+
if (dfn[0] === 'sub') {
|
|
202
|
+
subkind = dfn.shift(), dfn[0] === 'final' && (subkind += dfn.shift())
|
|
203
|
+
dfn = (supertypes = dfn).pop() // last item is definition
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
[compkind, ...dfn] = dfn // composite type kind
|
|
207
|
+
|
|
208
|
+
if (compkind === 'func') dfn = paramres(dfn), ctx.type['$' + dfn.join('>')] ??= ctx.type.length
|
|
209
|
+
else if (compkind === 'struct') dfn = fieldseq(dfn, 'field', true)
|
|
210
|
+
else if (compkind === 'array') [dfn] = dfn
|
|
211
|
+
|
|
212
|
+
return [compkind, dfn, subkind, supertypes]
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// register (implicit) type
|
|
216
|
+
const regtype = (param, result, ctx, idx = '$' + param + '>' + result) => (
|
|
217
|
+
(ctx.type[idx] ??= ctx.type.push(['func', [param, result]]) - 1),
|
|
218
|
+
idx
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
// consume typeuse nodes, return type index/params, or null idx if no type
|
|
222
|
+
// https://webassembly.github.io/spec/core/text/modules.html#type-uses
|
|
223
|
+
const typeuse = (nodes, ctx, names) => {
|
|
224
|
+
let idx, param, result
|
|
225
|
+
|
|
226
|
+
// explicit type (type 0|$name)
|
|
227
|
+
if (nodes[0]?.[0] === 'type') {
|
|
228
|
+
[, idx] = nodes.shift();
|
|
229
|
+
[param, result] = paramres(nodes, names);
|
|
230
|
+
|
|
231
|
+
const [, srcParamRes] = ctx.type[id(idx, ctx.type)] ?? err(`Unknown type ${idx}`)
|
|
232
|
+
|
|
233
|
+
// check type consistency (excludes forward refs)
|
|
234
|
+
if ((param.length || result.length) && srcParamRes.join('>') !== param + '>' + result) err(`Type ${idx} mismatch`)
|
|
235
|
+
|
|
236
|
+
return [idx, ...srcParamRes]
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// implicit type (param i32 i32)(result i32)
|
|
240
|
+
return [idx, ...paramres(nodes, names)]
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// consume (param t+)* (result t+)* sequence
|
|
244
|
+
const paramres = (nodes, names = true) => {
|
|
245
|
+
// let param = [], result = []
|
|
246
|
+
|
|
247
|
+
// collect param (param i32 i64) (param $x? i32)
|
|
248
|
+
let param = fieldseq(nodes, 'param', names)
|
|
249
|
+
|
|
250
|
+
// collect result eg. (result f64 f32)(result i32)
|
|
251
|
+
let result = fieldseq(nodes, 'result')
|
|
252
|
+
|
|
253
|
+
if (nodes[0]?.[0] === 'param') err(`Unexpected param`)
|
|
254
|
+
|
|
255
|
+
return [param, result]
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// collect sequence of field, eg. (param a) (param b c), (field a) (field b c) or (result a b) (result c)
|
|
259
|
+
// optionally allow or not names
|
|
260
|
+
const fieldseq = (nodes, field, names = false) => {
|
|
261
|
+
let seq = []
|
|
262
|
+
// collect field eg. (field f64 f32)(field i32)
|
|
263
|
+
while (nodes[0]?.[0] === field) {
|
|
264
|
+
let [, ...args] = nodes.shift()
|
|
265
|
+
let name = args[0]?.[0] === '$' && args.shift()
|
|
266
|
+
// expose name refs, if allowed
|
|
267
|
+
if (name) {
|
|
268
|
+
if (names) name in seq ? err(`Duplicate ${field} ${name}`) : seq[name] = seq.length
|
|
269
|
+
else err(`Unexpected ${field} name ${name}`)
|
|
270
|
+
}
|
|
271
|
+
seq.push(...args)
|
|
272
|
+
}
|
|
273
|
+
return seq
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// consume blocktype - makes sure either type or single result is returned
|
|
277
|
+
const blocktype = (nodes, ctx) => {
|
|
278
|
+
let [idx, param, result] = typeuse(nodes, ctx, 0)
|
|
279
|
+
|
|
280
|
+
// get type - can be either idx or valtype (numtype | reftype)
|
|
281
|
+
if (!param.length && !result.length) return
|
|
282
|
+
|
|
283
|
+
// (result i32) - doesn't require registering type
|
|
284
|
+
if (!param.length && result.length === 1) return ['result', ...result]
|
|
285
|
+
|
|
286
|
+
// register implicit type
|
|
287
|
+
idx ??= regtype(param, result, ctx)
|
|
288
|
+
|
|
289
|
+
return ['type', idx]
|
|
290
|
+
}
|
|
291
|
+
|
|
178
292
|
// abbr blocks, loops, ifs; collect implicit types via typeuses; resolve optional immediates
|
|
179
293
|
// https://webassembly.github.io/spec/core/text/instructions.html#folded-instructions
|
|
180
294
|
const plain = (nodes, ctx) => {
|
|
181
295
|
let out = [], stack = [], label
|
|
296
|
+
// helper: check if node is immediate (not array operand)
|
|
297
|
+
const isImm = n => typeof n === 'string' || typeof n === 'number'
|
|
182
298
|
|
|
183
299
|
while (nodes.length) {
|
|
184
300
|
let node = nodes.shift()
|
|
185
301
|
|
|
302
|
+
// code metadata annotations - pass through as marker with metadata type and data
|
|
303
|
+
// (@metadata.code.<type> data:str)
|
|
304
|
+
if (Array.isArray(node) && node[0]?.startsWith?.('@metadata.code.')) {
|
|
305
|
+
let type = node[0].slice(15) // remove '@metadata.code.' prefix
|
|
306
|
+
out.push(['@metadata', type, node[1]])
|
|
307
|
+
continue
|
|
308
|
+
}
|
|
309
|
+
|
|
186
310
|
// lookup is slower than sequence of known ifs
|
|
187
311
|
if (typeof node === 'string') {
|
|
188
312
|
out.push(node)
|
|
@@ -198,7 +322,7 @@ const plain = (nodes, ctx) => {
|
|
|
198
322
|
// else $label
|
|
199
323
|
// end $label - make sure it matches block label
|
|
200
324
|
else if (node === 'else' || node === 'end') {
|
|
201
|
-
if (nodes[0]?.[0] === '$') (node === 'end' ? stack.pop() : label) !== (label = nodes.shift()) && err(`Mismatched label ${label}`)
|
|
325
|
+
if (nodes[0]?.[0] === '$') (node === 'end' ? stack.pop() : label) !== (label = nodes.shift()) && err(`Mismatched ${node} label ${label}`)
|
|
202
326
|
}
|
|
203
327
|
|
|
204
328
|
// select (result i32 i32 i32)?
|
|
@@ -211,12 +335,24 @@ const plain = (nodes, ctx) => {
|
|
|
211
335
|
else if (node.endsWith('call_indirect')) {
|
|
212
336
|
let tableidx = nodes[0]?.[0] === '$' || !isNaN(nodes[0]) ? nodes.shift() : 0
|
|
213
337
|
let [idx, param, result] = typeuse(nodes, ctx, 0)
|
|
214
|
-
out.push(tableidx, ['type', idx ?? (
|
|
338
|
+
out.push(tableidx, ['type', idx ?? regtype(param, result, ctx)])
|
|
215
339
|
}
|
|
216
340
|
|
|
217
341
|
// mark datacount section as required
|
|
218
342
|
else if (node === 'memory.init' || node === 'data.drop' || node === 'array.new_data' || node === 'array.init_data') {
|
|
219
343
|
ctx.datacount[0] = true
|
|
344
|
+
// memory.init memidx? dataidx
|
|
345
|
+
if (node === 'memory.init') out.push(isImm(nodes[1]) ? nodes.shift() : 0, isImm(nodes[0]) ? nodes.shift() : 0)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// memory.* memidx? - multi-memory proposal
|
|
349
|
+
else if (node === 'memory.size' || node === 'memory.grow' || node === 'memory.fill') {
|
|
350
|
+
out.push(isImm(nodes[0]) ? nodes.shift() : 0)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// memory.copy dstmem? srcmem?
|
|
354
|
+
else if (node === 'memory.copy') {
|
|
355
|
+
out.push(isImm(nodes[0]) ? nodes.shift() : 0, isImm(nodes[0]) ? nodes.shift() : 0)
|
|
220
356
|
}
|
|
221
357
|
|
|
222
358
|
// table.init tableidx? elemidx -> table.init tableidx elemidx
|
|
@@ -239,6 +375,9 @@ const plain = (nodes, ctx) => {
|
|
|
239
375
|
|
|
240
376
|
// (if ...) -> if ... end
|
|
241
377
|
else if (node[0] === 'if') {
|
|
378
|
+
// Pop pending metadata (branch_hint) if present
|
|
379
|
+
let meta = out[out.length - 1]?.[0] === '@metadata' && out.pop()
|
|
380
|
+
|
|
242
381
|
let then = [], els = [], immed = [node.shift()]
|
|
243
382
|
// (if label? blocktype? cond*? (then instr*) (else instr*)?) -> cond*? if label? blocktype? instr* else instr*? end
|
|
244
383
|
// https://webassembly.github.io/spec/core/text/instructions.html#control-instructions
|
|
@@ -258,7 +397,8 @@ const plain = (nodes, ctx) => {
|
|
|
258
397
|
|
|
259
398
|
if (typeof node[0] === 'string') err('Unfolded condition')
|
|
260
399
|
|
|
261
|
-
|
|
400
|
+
// conditions, metadata (if any), if, then, else, end
|
|
401
|
+
out.push(...plain(node, ctx), ...(meta ? [meta] : []), ...immed, ...then, ...els, 'end')
|
|
262
402
|
}
|
|
263
403
|
else out.push(plain(node, ctx))
|
|
264
404
|
}
|
|
@@ -267,83 +407,23 @@ const plain = (nodes, ctx) => {
|
|
|
267
407
|
return out
|
|
268
408
|
}
|
|
269
409
|
|
|
270
|
-
// consume typeuse nodes, return type index/params, or null idx if no type
|
|
271
|
-
// https://webassembly.github.io/spec/core/text/modules.html#type-uses
|
|
272
|
-
const typeuse = (nodes, ctx, names) => {
|
|
273
|
-
let idx, param, result
|
|
274
|
-
|
|
275
|
-
// explicit type (type 0|$name)
|
|
276
|
-
if (nodes[0]?.[0] === 'type') {
|
|
277
|
-
[, idx] = nodes.shift();
|
|
278
|
-
[param, result] = paramres(nodes, names);
|
|
279
|
-
|
|
280
|
-
// check type consistency (excludes forward refs)
|
|
281
|
-
if ((param.length || result.length) && idx in ctx.type)
|
|
282
|
-
if (ctx.type[id(idx, ctx.type)][1].join('>') !== param + '>' + result) err(`Type ${idx} mismatch`)
|
|
283
|
-
|
|
284
|
-
return [idx]
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// implicit type (param i32 i32)(result i32)
|
|
288
|
-
[param, result] = paramres(nodes, names)
|
|
289
|
-
|
|
290
|
-
return [, param, result]
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// consume (param t+)* (result t+)* sequence
|
|
294
|
-
const paramres = (nodes, names = true) => {
|
|
295
|
-
// let param = [], result = []
|
|
296
|
-
|
|
297
|
-
// collect param (param i32 i64) (param $x? i32)
|
|
298
|
-
let param = fieldseq(nodes, 'param', names)
|
|
299
|
-
|
|
300
|
-
// collect result eg. (result f64 f32)(result i32)
|
|
301
|
-
let result = fieldseq(nodes, 'result')
|
|
302
|
-
|
|
303
|
-
if (nodes[0]?.[0] === 'param') err(`Unexpected param`)
|
|
304
410
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
//
|
|
309
|
-
//
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
// expose name refs, if allowed
|
|
317
|
-
if (name) {
|
|
318
|
-
if (names) name in seq ? err(`Duplicate ${field} ${name}`) : seq[name] = seq.length
|
|
319
|
-
else err(`Unexpected ${field} name ${name}`)
|
|
411
|
+
// build section binary [by section codes] (non consuming)
|
|
412
|
+
const build = [
|
|
413
|
+
// (@custom "name" placement? data)
|
|
414
|
+
// placement is optional: (before|after section) or (before first)|(after last)
|
|
415
|
+
// For now we ignore placement and just output the custom section
|
|
416
|
+
([name, ...rest], ctx) => {
|
|
417
|
+
// Check if second arg is placement directive
|
|
418
|
+
let data = rest
|
|
419
|
+
if (rest[0]?.[0] === 'before' || rest[0]?.[0] === 'after') {
|
|
420
|
+
// Skip placement for now - would need more complex section ordering
|
|
421
|
+
data = rest.slice(1)
|
|
320
422
|
}
|
|
321
|
-
seq.push(...args)
|
|
322
|
-
}
|
|
323
|
-
return seq
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// consume blocktype - makes sure either type or single result is returned
|
|
327
|
-
const blocktype = (nodes, ctx) => {
|
|
328
|
-
let [idx, param, result] = typeuse(nodes, ctx, 0)
|
|
329
|
-
|
|
330
|
-
// direct idx (no params/result needed)
|
|
331
|
-
if (idx != null) return ['type', idx]
|
|
332
423
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
// (result i32) - doesn't require registering type
|
|
337
|
-
if (!param.length && result.length === 1) return ['result', ...result]
|
|
338
|
-
|
|
339
|
-
// (param i32 i32)? (result i32 i32) - implicit type
|
|
340
|
-
ctx._[idx = '$' + param + '>' + result] = [param, result]
|
|
341
|
-
return ['type', idx]
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
// build section binary [by section codes] (non consuming)
|
|
346
|
-
const build = [,
|
|
424
|
+
// Custom section format: name (vec string) + raw content bytes
|
|
425
|
+
return [...vec(str(name)), ...str(...data)]
|
|
426
|
+
},
|
|
347
427
|
// type kinds
|
|
348
428
|
// (func params result)
|
|
349
429
|
// (array i8)
|
|
@@ -354,7 +434,6 @@ const build = [,
|
|
|
354
434
|
let details
|
|
355
435
|
// (rec (sub ...)*)
|
|
356
436
|
if (rec) {
|
|
357
|
-
// FIXME: rec of one type
|
|
358
437
|
kind = 'rec'
|
|
359
438
|
let [from, length] = rec, subtypes = Array.from({ length }, (_, i) => build[SECTION.type](ctx.type[from + i].slice(0, 4), ctx))
|
|
360
439
|
details = vec(subtypes)
|
|
@@ -378,7 +457,7 @@ const build = [,
|
|
|
378
457
|
return [DEFTYPE[kind], ...details]
|
|
379
458
|
},
|
|
380
459
|
|
|
381
|
-
// (import "math" "add" (func|table|global|memory
|
|
460
|
+
// (import "math" "add" (func|table|global|memory dfn?))
|
|
382
461
|
([mod, field, [kind, ...dfn]], ctx) => {
|
|
383
462
|
let details
|
|
384
463
|
|
|
@@ -387,6 +466,10 @@ const build = [,
|
|
|
387
466
|
let [[, typeidx]] = dfn
|
|
388
467
|
details = uleb(id(typeidx, ctx.type))
|
|
389
468
|
}
|
|
469
|
+
else if (kind === 'tag') {
|
|
470
|
+
let [[, typeidx]] = dfn
|
|
471
|
+
details = [0x00, ...uleb(id(typeidx, ctx.type))]
|
|
472
|
+
}
|
|
390
473
|
else if (kind === 'memory') {
|
|
391
474
|
details = limits(dfn)
|
|
392
475
|
}
|
|
@@ -398,7 +481,7 @@ const build = [,
|
|
|
398
481
|
}
|
|
399
482
|
else err(`Unknown kind ${kind}`)
|
|
400
483
|
|
|
401
|
-
return ([...vec(str(mod
|
|
484
|
+
return ([...vec(str(mod)), ...vec(str(field)), KIND[kind], ...details])
|
|
402
485
|
},
|
|
403
486
|
|
|
404
487
|
// (func $name? ...params result ...body)
|
|
@@ -417,7 +500,7 @@ const build = [,
|
|
|
417
500
|
([t, init], ctx) => [...fieldtype(t, ctx), ...expr(init, ctx)],
|
|
418
501
|
|
|
419
502
|
// (export "name" (func|table|mem $name|idx))
|
|
420
|
-
([nm, [kind, l]], ctx) => ([...vec(str(nm
|
|
503
|
+
([nm, [kind, l]], ctx) => ([...vec(str(nm)), KIND[kind], ...uleb(id(l, ctx[kind]))]),
|
|
421
504
|
|
|
422
505
|
// (start $main)
|
|
423
506
|
([l], ctx) => uleb(id(l, ctx.func)),
|
|
@@ -518,6 +601,10 @@ const build = [,
|
|
|
518
601
|
ctx.local.name = 'local'
|
|
519
602
|
ctx.block.name = 'block'
|
|
520
603
|
|
|
604
|
+
// Track current code index for code metadata
|
|
605
|
+
if (ctx._codeIdx === undefined) ctx._codeIdx = 0
|
|
606
|
+
let codeIdx = ctx._codeIdx++
|
|
607
|
+
|
|
521
608
|
// collect locals
|
|
522
609
|
while (body[0]?.[0] === 'local') {
|
|
523
610
|
let [, ...types] = body.shift()
|
|
@@ -529,10 +616,21 @@ const build = [,
|
|
|
529
616
|
ctx.local.push(...types)
|
|
530
617
|
}
|
|
531
618
|
|
|
619
|
+
ctx._meta = null
|
|
532
620
|
const bytes = []
|
|
533
621
|
while (body.length) bytes.push(...instr(body, ctx))
|
|
534
622
|
bytes.push(0x0b)
|
|
535
623
|
|
|
624
|
+
// Extract metadata placeholders (arrays), group by type
|
|
625
|
+
const metaByType = {}, cleanBytes = []
|
|
626
|
+
for (const b of bytes)
|
|
627
|
+
if (Array.isArray(b)) for (const [type, data] of b) (metaByType[type] ??= []).push([cleanBytes.length, data])
|
|
628
|
+
else cleanBytes.push(b)
|
|
629
|
+
|
|
630
|
+
// Store metadata for this function, grouped by type
|
|
631
|
+
const funcIdx = ctx.import.filter(imp => imp[2][0] === 'func').length + codeIdx
|
|
632
|
+
for (const type in metaByType) ((ctx.meta ??= {})[type] ??= []).push([funcIdx, metaByType[type]])
|
|
633
|
+
|
|
536
634
|
// squash locals into (n:u32 t:valtype)*, n is number and t is type
|
|
537
635
|
// we skip locals provided by params
|
|
538
636
|
let loctypes = ctx.local.slice(param.length).reduce((a, type) => (type == a[a.length - 1]?.[1] ? a[a.length - 1][0]++ : a.push([1, type]), a), [])
|
|
@@ -541,7 +639,7 @@ const build = [,
|
|
|
541
639
|
ctx.local = ctx.block = null
|
|
542
640
|
|
|
543
641
|
// https://webassembly.github.io/spec/core/binary/modules.html#code-section
|
|
544
|
-
return vec([...vec(loctypes.map(([n, t]) => [...uleb(n), ...reftype(t, ctx)])), ...
|
|
642
|
+
return vec([...vec(loctypes.map(([n, t]) => [...uleb(n), ...reftype(t, ctx)])), ...cleanBytes])
|
|
545
643
|
},
|
|
546
644
|
|
|
547
645
|
// (data (i32.const 0) "\aa" "\bb"?)
|
|
@@ -557,10 +655,10 @@ const build = [,
|
|
|
557
655
|
}
|
|
558
656
|
|
|
559
657
|
// (offset (i32.const 0)) or (i32.const 0)
|
|
560
|
-
if (typeof inits[0] !== 'string') {
|
|
658
|
+
if (typeof inits[0] !== 'string' && inits[0]) {
|
|
561
659
|
offset = inits.shift()
|
|
562
|
-
if (offset[0] === 'offset') [, offset] = offset
|
|
563
|
-
offset ?? err('Bad offset', offset)
|
|
660
|
+
if (offset?.[0] === 'offset') [, offset] = offset
|
|
661
|
+
else offset ?? err('Bad offset', offset)
|
|
564
662
|
}
|
|
565
663
|
|
|
566
664
|
return ([
|
|
@@ -572,7 +670,7 @@ const build = [,
|
|
|
572
670
|
// passive: 1
|
|
573
671
|
[1]
|
|
574
672
|
),
|
|
575
|
-
...vec(str(inits
|
|
673
|
+
...vec(str(...inits))
|
|
576
674
|
])
|
|
577
675
|
},
|
|
578
676
|
|
|
@@ -580,6 +678,9 @@ const build = [,
|
|
|
580
678
|
(nodes, ctx) => uleb(ctx.data.length)
|
|
581
679
|
]
|
|
582
680
|
|
|
681
|
+
// (tag $id? (param i32)*) - tags for exception handling
|
|
682
|
+
build[SECTION.tag] = ([[, typeidx]], ctx) => [0x00, ...uleb(id(typeidx, ctx.type))]
|
|
683
|
+
|
|
583
684
|
// build reftype, either direct absheaptype or wrapped heaptype https://webassembly.github.io/gc/core/binary/types.html#reference-types
|
|
584
685
|
const reftype = (t, ctx) => (
|
|
585
686
|
t[0] === 'ref' ?
|
|
@@ -594,17 +695,26 @@ const reftype = (t, ctx) => (
|
|
|
594
695
|
const fieldtype = (t, ctx, mut = t[0] === 'mut' ? 1 : 0) => [...reftype(mut ? t[1] : t, ctx), mut];
|
|
595
696
|
|
|
596
697
|
|
|
597
|
-
|
|
598
698
|
// consume one instruction from nodes sequence
|
|
599
699
|
const instr = (nodes, ctx) => {
|
|
600
700
|
if (!nodes?.length) return []
|
|
601
701
|
|
|
602
702
|
let out = [], op = nodes.shift(), immed, code
|
|
703
|
+
const isImm = n => typeof n === 'string' || typeof n === 'number'
|
|
704
|
+
|
|
705
|
+
// Handle code metadata marker - store for next instruction
|
|
706
|
+
// ['@metadata', type, data]
|
|
707
|
+
if (op?.[0] === '@metadata') {
|
|
708
|
+
;(ctx._meta ??= []).push(op.slice(1))
|
|
709
|
+
return nodes.length ? instr(nodes, ctx) : []
|
|
710
|
+
}
|
|
603
711
|
|
|
604
712
|
// consume group
|
|
605
713
|
if (Array.isArray(op)) {
|
|
606
714
|
immed = instr(op, ctx)
|
|
607
715
|
while (op.length) out.push(...instr(op, ctx))
|
|
716
|
+
// Insert metadata placeholder before instruction
|
|
717
|
+
if (ctx._meta) out.push(ctx._meta), ctx._meta = null
|
|
608
718
|
out.push(...immed)
|
|
609
719
|
return out
|
|
610
720
|
}
|
|
@@ -627,7 +737,9 @@ const instr = (nodes, ctx) => {
|
|
|
627
737
|
// array.new_fixed $t n
|
|
628
738
|
else if (code === 8) immed.push(...uleb(nodes.shift()))
|
|
629
739
|
// array.new_data|init_data $t $d
|
|
630
|
-
else if (code === 9 || code === 18)
|
|
740
|
+
else if (code === 9 || code === 18) {
|
|
741
|
+
immed.push(...uleb(id(isImm(nodes[0]) ? nodes.shift() : 0, ctx.data)))
|
|
742
|
+
}
|
|
631
743
|
// array.new_elem|init_elem $t $e
|
|
632
744
|
else if (code === 10 || code === 19) immed.push(...uleb(id(nodes.shift(), ctx.elem)))
|
|
633
745
|
// array.copy $t $t
|
|
@@ -635,20 +747,18 @@ const instr = (nodes, ctx) => {
|
|
|
635
747
|
}
|
|
636
748
|
// ref.test|cast (ref null? $t|heaptype)
|
|
637
749
|
else if (code >= 20 && code <= 23) {
|
|
638
|
-
// FIXME: normalizer is supposed to resolve this
|
|
639
750
|
let ht = reftype(nodes.shift(), ctx)
|
|
640
|
-
if (ht[0] !== REFTYPE.ref) immed.push(code = immed.pop()+1) // ref.test|cast (ref null $t) is next op
|
|
751
|
+
if (ht[0] !== REFTYPE.ref) immed.push(code = immed.pop() + 1) // ref.test|cast (ref null $t) is next op
|
|
641
752
|
if (ht.length > 1) ht.shift() // pop ref
|
|
642
753
|
immed.push(...ht)
|
|
643
754
|
}
|
|
644
755
|
// br_on_cast[_fail] $l? (ref null? ht1) (ref null? ht2)
|
|
645
|
-
// FIXME: normalizer should resolve anyref|etc to (ref null any|etc)
|
|
646
756
|
else if (code === 24 || code === 25) {
|
|
647
757
|
let i = blockid(nodes.shift(), ctx.block),
|
|
648
758
|
ht1 = reftype(nodes.shift(), ctx),
|
|
649
759
|
ht2 = reftype(nodes.shift(), ctx),
|
|
650
760
|
castflags = ((ht2[0] !== REFTYPE.ref) << 1) | (ht1[0] !== REFTYPE.ref)
|
|
651
|
-
|
|
761
|
+
immed.push(castflags, ...uleb(i), ht1.pop(), ht2.pop()) // we take only abstype or
|
|
652
762
|
}
|
|
653
763
|
}
|
|
654
764
|
|
|
@@ -658,14 +768,23 @@ const instr = (nodes, ctx) => {
|
|
|
658
768
|
else if (code == 0xfc) {
|
|
659
769
|
[, code] = immed
|
|
660
770
|
|
|
661
|
-
// memory.init
|
|
662
|
-
if (code === 0x08
|
|
771
|
+
// memory.init memidx dataidx (binary: dataidx memidx)
|
|
772
|
+
if (code === 0x08) {
|
|
773
|
+
let m = isImm(nodes[0]) ? nodes.shift() : 0, d = isImm(nodes[0]) ? nodes.shift() : 0
|
|
774
|
+
immed.push(...uleb(id(d, ctx.data)), ...uleb(id(m, ctx.memory)))
|
|
775
|
+
}
|
|
776
|
+
// data.drop idx
|
|
777
|
+
else if (code === 0x09) {
|
|
663
778
|
immed.push(...uleb(id(nodes.shift(), ctx.data)))
|
|
664
779
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
780
|
+
// memory.copy dstmem srcmem
|
|
781
|
+
else if (code === 0x0a) {
|
|
782
|
+
immed.push(...uleb(id(isImm(nodes[0]) ? nodes.shift() : 0, ctx.memory)), ...uleb(id(isImm(nodes[0]) ? nodes.shift() : 0, ctx.memory)))
|
|
783
|
+
}
|
|
784
|
+
// memory.fill memidx
|
|
785
|
+
else if (code === 0x0b) {
|
|
786
|
+
immed.push(...uleb(id(isImm(nodes[0]) ? nodes.shift() : 0, ctx.memory)))
|
|
787
|
+
}
|
|
669
788
|
|
|
670
789
|
// elem.drop elemidx
|
|
671
790
|
if (code === 0x0d) {
|
|
@@ -744,7 +863,6 @@ const instr = (nodes, ctx) => {
|
|
|
744
863
|
ctx.block.push(code)
|
|
745
864
|
|
|
746
865
|
// (block $x) (loop $y) - save label pointer
|
|
747
|
-
// FIXME: do in normalizer
|
|
748
866
|
if (nodes[0]?.[0] === '$') ctx.block[nodes.shift()] = ctx.block.length
|
|
749
867
|
|
|
750
868
|
let t = nodes.shift();
|
|
@@ -754,13 +872,8 @@ const instr = (nodes, ctx) => {
|
|
|
754
872
|
// (result i32) - doesn't require registering type
|
|
755
873
|
// FIXME: Make sure it is signed positive integer (leb, not uleb) https://webassembly.github.io/gc/core/binary/instructions.html#control-instructions
|
|
756
874
|
else if (t[0] === 'result') immed.push(...reftype(t[1], ctx))
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
// (type $idx (func (result i32)))
|
|
760
|
-
if (!param?.length && result.length === 1) immed.push(...reftype(result[0], ctx))
|
|
761
|
-
// (type idx)
|
|
762
|
-
else immed.push(...uleb(typeidx))
|
|
763
|
-
}
|
|
875
|
+
// (type idx)
|
|
876
|
+
else immed.push(...uleb(id(t[1], ctx.type)))
|
|
764
877
|
}
|
|
765
878
|
// else
|
|
766
879
|
else if (code === 5) { }
|
|
@@ -851,10 +964,10 @@ const instr = (nodes, ctx) => {
|
|
|
851
964
|
immed.push(...encode[op.split('.')[0]](nodes.shift()))
|
|
852
965
|
}
|
|
853
966
|
|
|
854
|
-
// memory.grow|size
|
|
967
|
+
// memory.grow|size memidx
|
|
855
968
|
// https://webassembly.github.io/spec/core/binary/instructions.html#memory-instructions
|
|
856
969
|
else if (code == 0x3f || code == 0x40) {
|
|
857
|
-
immed.push(0)
|
|
970
|
+
immed.push(...uleb(id(isImm(nodes[0]) ? nodes.shift() : 0, ctx.memory)))
|
|
858
971
|
}
|
|
859
972
|
|
|
860
973
|
// table.get|set $id
|
|
@@ -862,6 +975,9 @@ const instr = (nodes, ctx) => {
|
|
|
862
975
|
immed.push(...uleb(id(nodes.shift(), ctx.table)))
|
|
863
976
|
}
|
|
864
977
|
|
|
978
|
+
// Insert metadata placeholder before instruction in flat form
|
|
979
|
+
if (ctx._meta) out.push(ctx._meta), ctx._meta = null
|
|
980
|
+
|
|
865
981
|
out.push(...immed)
|
|
866
982
|
|
|
867
983
|
return out
|
|
@@ -883,10 +999,12 @@ const blockid = (nm, block, i) => (
|
|
|
883
999
|
// consume align/offset params
|
|
884
1000
|
const memarg = (args) => {
|
|
885
1001
|
let align, offset, k, v
|
|
886
|
-
while (args[0]?.includes('='))
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
1002
|
+
while (args[0]?.includes('=')) {
|
|
1003
|
+
[k, v] = args.shift().split('='), v = v.replaceAll('_', '')
|
|
1004
|
+
k === 'offset' ? offset = +v : k === 'align' ? align = +v : err(`Unknown param ${k}=${v}`)
|
|
1005
|
+
}
|
|
1006
|
+
if ((offset < 0 || offset > 0xffffffff)) err(`Bad offset ${offset}`)
|
|
1007
|
+
if ((align <= 0 || align > 0xffffffff)) err(`Bad align ${align}`)
|
|
890
1008
|
if (align) ((align = Math.log2(align)) % 1) && err(`Bad align ${align}`)
|
|
891
1009
|
return [align, offset]
|
|
892
1010
|
}
|
|
@@ -915,20 +1033,5 @@ const limits = (node) => (
|
|
|
915
1033
|
// we put extra condition for index ints for tests complacency
|
|
916
1034
|
const parseUint = (v, max = 0xFFFFFFFF) => (typeof v === 'string' && v[0] !== '+' ? (typeof max === 'bigint' ? i64 : i32).parse(v) : typeof v === 'number' ? v : err(`Bad int ${v}`)) > max ? err(`Value out of range ${v}`) : v
|
|
917
1035
|
|
|
918
|
-
|
|
919
|
-
// escape codes
|
|
920
|
-
const escape = { n: 10, r: 13, t: 9, v: 1, '"': 34, "'": 39, '\\': 92 }
|
|
921
|
-
|
|
922
|
-
// build string binary
|
|
923
|
-
const str = str => {
|
|
924
|
-
let res = [], i = 0, c, BSLASH = 92
|
|
925
|
-
// https://webassembly.github.io/spec/core/text/values.html#strings
|
|
926
|
-
for (; i < str.length;) {
|
|
927
|
-
c = str.charCodeAt(i++)
|
|
928
|
-
res.push(c === BSLASH ? escape[str[i++]] || parseInt(str.slice(i - 1, ++i), 16) : c)
|
|
929
|
-
}
|
|
930
|
-
return res
|
|
931
|
-
}
|
|
932
|
-
|
|
933
1036
|
// serialize binary array
|
|
934
1037
|
const vec = a => [...uleb(a.length), ...a.flat()]
|
package/src/const.js
CHANGED
|
@@ -49,17 +49,19 @@ export const INSTR = [
|
|
|
49
49
|
// relaxed SIMD instructions
|
|
50
50
|
'i8x16.relaxed_swizzle', 'i32x4.relaxed_trunc_f32x4_s', 'i32x4.relaxed_trunc_f32x4_u', 'i32x4.relaxed_trunc_f64x2_s_zero', 'i32x4.relaxed_trunc_f64x2_u_zero', 'f32x4.relaxed_madd', 'f32x4.relaxed_nmadd', 'f64x2.relaxed_madd', 'f64x2.relaxed_nmadd', 'i8x16.relaxed_laneselect', 'i16x8.relaxed_laneselect', 'i32x4.relaxed_laneselect', 'i64x2.relaxed_laneselect', 'f32x4.relaxed_min', 'f32x4.relaxed_max', 'f64x2.relaxed_min', 'f64x2.relaxed_max', 'i16x8.relaxed_q15mulr_s', 'i16x8.relaxed_dot_i8x16_i7x16_s', 'i32x4.relaxed_dot_i8x16_i7x16_add_s'
|
|
51
51
|
],
|
|
52
|
-
SECTION = { custom: 0, type: 1, import: 2, func: 3, table: 4, memory: 5, global: 6, export: 7, start: 8, elem: 9, datacount: 12, code: 10, data: 11 },
|
|
52
|
+
SECTION = { custom: 0, type: 1, import: 2, func: 3, table: 4, memory: 5, global: 6, tag: 13, export: 7, start: 8, elem: 9, datacount: 12, code: 10, data: 11 },
|
|
53
53
|
RECTYPE = { sub: 0x50, subfinal: 0x4F, rec: 0x4E },
|
|
54
54
|
DEFTYPE = { func: 0x60, struct: 0x5F, array: 0x5E, ...RECTYPE },
|
|
55
|
-
HEAPTYPE = { nofunc: 0x73, noextern: 0x72, none: 0x71, func: 0x70, extern: 0x6F, any: 0x6E, eq: 0x6D, i31: 0x6C, struct: 0x6B, array: 0x6A },
|
|
55
|
+
HEAPTYPE = { nofunc: 0x73, noextern: 0x72, noexn: 0x74, none: 0x71, func: 0x70, extern: 0x6F, exn: 0x75, any: 0x6E, eq: 0x6D, i31: 0x6C, struct: 0x6B, array: 0x6A },
|
|
56
56
|
REFTYPE = {
|
|
57
57
|
// absheaptype abbrs
|
|
58
58
|
nullfuncref: HEAPTYPE.nofunc,
|
|
59
59
|
nullexternref: HEAPTYPE.noextern,
|
|
60
|
+
nullexnref: HEAPTYPE.noexn,
|
|
60
61
|
nullref: HEAPTYPE.none,
|
|
61
62
|
funcref: HEAPTYPE.func,
|
|
62
63
|
externref: HEAPTYPE.extern,
|
|
64
|
+
exnref: HEAPTYPE.exn,
|
|
63
65
|
anyref: HEAPTYPE.any,
|
|
64
66
|
eqref: HEAPTYPE.eq,
|
|
65
67
|
i31ref: HEAPTYPE.i31,
|
|
@@ -70,4 +72,6 @@ export const INSTR = [
|
|
|
70
72
|
ref: 0x64 /* -0x1c */, refnull: 0x63 /* -0x1d */
|
|
71
73
|
},
|
|
72
74
|
TYPE = { i8: 0x78, i16: 0x77, i32: 0x7f, i64: 0x7e, f32: 0x7d, f64: 0x7c, void: 0x40, v128: 0x7B, ...HEAPTYPE, ...REFTYPE },
|
|
73
|
-
KIND = { func: 0, table: 1, memory: 2, global: 3 }
|
|
75
|
+
KIND = { func: 0, table: 1, memory: 2, global: 3, tag: 4 },
|
|
76
|
+
// WAT escape codes: https://webassembly.github.io/spec/core/text/values.html#strings
|
|
77
|
+
ESCAPE = { n: 10, r: 13, t: 9, v: 11, '"': 34, "'": 39, '\\': 92 }
|
package/src/parse.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { unescape } from "./util.js"
|
|
2
|
+
|
|
1
3
|
const OPAREN = 40, CPAREN = 41, OBRACK = 91, CBRACK = 93, SPACE = 32, DQUOTE = 34, PERIOD = 46,
|
|
2
|
-
_0 = 48, _9 = 57, SEMIC = 59, NEWLINE = 32, PLUS = 43, MINUS = 45, COLON = 58,
|
|
4
|
+
_0 = 48, _9 = 57, SEMIC = 59, NEWLINE = 32, PLUS = 43, MINUS = 45, COLON = 58, BACKSLASH = 92, AT = 64
|
|
5
|
+
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* Parses a wasm text string and constructs a nested array structure (AST).
|
|
@@ -17,22 +20,23 @@ export default (str, o={ comments: false }) => {
|
|
|
17
20
|
)
|
|
18
21
|
|
|
19
22
|
const parseLevel = () => {
|
|
20
|
-
for (let c, root, q; i < str.length;) {
|
|
23
|
+
for (let c, root, q, id; i < str.length;) {
|
|
21
24
|
|
|
22
25
|
c = str.charCodeAt(i)
|
|
23
26
|
if (q) {
|
|
24
27
|
buf += str[i++]
|
|
25
|
-
if (
|
|
26
|
-
else if (c === DQUOTE) commit(), q = 0
|
|
28
|
+
if (c === BACKSLASH) buf += str[i++]
|
|
29
|
+
else if (c === DQUOTE) id && (buf = '$' + unescape(buf)), commit(), q = id = 0
|
|
27
30
|
}
|
|
28
31
|
else if (c === DQUOTE) {
|
|
29
|
-
commit(),
|
|
32
|
+
q = c, id = buf == '$', !id && commit(), buf = '"', i++
|
|
30
33
|
}
|
|
31
34
|
else if (c === OPAREN) {
|
|
32
35
|
if (str.charCodeAt(i + 1) === SEMIC) comment = str.slice(i, i = str.indexOf(';)', i) + 2), o.comments && level.push(comment) // (; ... ;)
|
|
36
|
+
else if (str.charCodeAt(i + 1) === AT) commit(), i += 2, buf = '@', (root = level).push(level = []), parseLevel(), level = root // (@annotid ...)
|
|
33
37
|
else commit(), i++, (root = level).push(level = []), parseLevel(), level = root
|
|
34
38
|
}
|
|
35
|
-
else if (c === SEMIC) comment = str.slice(i, i = str.indexOf('\n', i) + 1 || str.length), o.comments && level.push(comment) //
|
|
39
|
+
else if (c === SEMIC && str.charCodeAt(i + 1) === SEMIC) comment = str.slice(i, i = str.indexOf('\n', i) + 1 || str.length), o.comments && level.push(comment) // ;; ...
|
|
36
40
|
else if (c <= SPACE) commit(), i++
|
|
37
41
|
else if (c === CPAREN) return commit(), i++
|
|
38
42
|
else buf += str[i++]
|
package/src/util.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ESCAPE } from './const.js'
|
|
1
2
|
|
|
2
3
|
export const err = text => { throw Error(text) }
|
|
3
4
|
|
|
@@ -6,3 +7,48 @@ export const clone = items => items.map(item => Array.isArray(item) ? clone(item
|
|
|
6
7
|
export const sepRE = /^_|_$|[^\da-f]_|_[^\da-f]/i
|
|
7
8
|
|
|
8
9
|
export const intRE = /^[+-]?(?:0x[\da-f]+|\d+)$/i
|
|
10
|
+
|
|
11
|
+
// build string binary - convert WAT string to byte array
|
|
12
|
+
const enc = new TextEncoder()
|
|
13
|
+
export const str = (...parts) => {
|
|
14
|
+
let s = parts.map(s => s[0] === '"' ? s.slice(1, -1) : s).join(''), res = []
|
|
15
|
+
|
|
16
|
+
for (let i = 0; i < s.length; i++) {
|
|
17
|
+
let c = s.charCodeAt(i)
|
|
18
|
+
if (c === 92) { // backslash
|
|
19
|
+
let n = s[i + 1]
|
|
20
|
+
// \u{...} unicode - decode and UTF-8 encode
|
|
21
|
+
if (n === 'u' && s[i + 2] === '{') {
|
|
22
|
+
let hex = s.slice(i + 3, i = s.indexOf('}', i + 3))
|
|
23
|
+
res.push(...enc.encode(String.fromCodePoint(parseInt(hex, 16))))
|
|
24
|
+
// i now points to '}', loop i++ will move past it
|
|
25
|
+
}
|
|
26
|
+
// Named escape
|
|
27
|
+
else if (ESCAPE[n]) {
|
|
28
|
+
res.push(ESCAPE[n])
|
|
29
|
+
i++ // skip the named char, loop i++ will move past backslash
|
|
30
|
+
}
|
|
31
|
+
// \xx hex byte (raw byte, not UTF-8 decoded)
|
|
32
|
+
else {
|
|
33
|
+
res.push(parseInt(s.slice(i + 1, i + 3), 16))
|
|
34
|
+
i += 2 // skip two hex digits, loop i++ will complete the skip
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Multi-byte char - UTF-8 encode
|
|
38
|
+
else if (c > 255) {
|
|
39
|
+
res.push(...enc.encode(s[i]))
|
|
40
|
+
}
|
|
41
|
+
// Raw byte
|
|
42
|
+
else res.push(c)
|
|
43
|
+
}
|
|
44
|
+
return res
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Unescapes a WAT string literal by parsing escapes to bytes, then UTF-8 decoding.
|
|
49
|
+
* Reuses str() for escape parsing to eliminate duplication.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} s - String with quotes and escapes, e.g. '"hello\\nworld"'
|
|
52
|
+
* @returns {string} Unescaped string without quotes, e.g. 'hello\nworld'
|
|
53
|
+
*/
|
|
54
|
+
export const unescape = s => new TextDecoder().decode(new Uint8Array(str(s)))
|