watr 3.2.0 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/compile.js +132 -135
package/package.json
CHANGED
package/src/compile.js
CHANGED
|
@@ -39,32 +39,46 @@ export default function watr(nodes) {
|
|
|
39
39
|
// scopes are aliased by key as well, eg. section.func.$name = section[SECTION.func] = idx
|
|
40
40
|
const ctx = []
|
|
41
41
|
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
42
|
|
|
43
|
+
// initialize types
|
|
44
|
+
nodes.filter(([kind, ...node]) => {
|
|
52
45
|
// (rec (type $a (sub final? $sup* (func ...))...) (type $b ...)) -> save subtypes
|
|
53
46
|
if (kind === 'rec') {
|
|
54
47
|
// node contains a list of subtypes, (type ...) or (type (sub final? ...))
|
|
55
48
|
// convert rec type into regular type (first subtype) with stashed subtypes length
|
|
56
49
|
// add rest of subtypes as regular type nodes with subtype flag
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
for (let i = 0; i < node.length; i++) {
|
|
51
|
+
let [,...subnode] = node[i]
|
|
52
|
+
alias(subnode, ctx.type);
|
|
53
|
+
(subnode = typedef(subnode, ctx)).push(i ? true : [ctx.type.length, node.length])
|
|
54
|
+
ctx.type.push(subnode)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// (type (func param* result*))
|
|
58
|
+
// (type (array (mut i8)))
|
|
59
|
+
// (type (struct (field a)*)
|
|
60
|
+
// (type (sub final? $nm* (struct|array|func ...)))
|
|
61
|
+
else if (kind === 'type') {
|
|
62
|
+
alias(node, ctx.type);
|
|
63
|
+
ctx.type.push(typedef(node, ctx));
|
|
59
64
|
}
|
|
65
|
+
// other sections may have id
|
|
66
|
+
else if (kind === 'start' || kind === 'export') ctx[kind].push(node)
|
|
67
|
+
|
|
68
|
+
else return true
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
// prepare/normalize nodes
|
|
72
|
+
.forEach(([kind, ...node]) => {
|
|
73
|
+
let imported // if node needs to be imported
|
|
60
74
|
|
|
61
75
|
// import abbr
|
|
62
76
|
// (import m n (table|memory|global|func id? type)) -> (table|memory|global|func id? (import m n) type)
|
|
63
|
-
|
|
77
|
+
if (kind === 'import') [kind, ...node] = (imported = node).pop()
|
|
64
78
|
|
|
65
79
|
// index, alias
|
|
66
80
|
let items = ctx[kind];
|
|
67
|
-
let name = alias(node, items)
|
|
81
|
+
let name = alias(node, items);
|
|
68
82
|
|
|
69
83
|
// export abbr
|
|
70
84
|
// (table|memory|global|func id? (export n)* ...) -> (table|memory|global|func id ...) (export n (table|memory|global|func id))
|
|
@@ -91,37 +105,13 @@ export default function watr(nodes) {
|
|
|
91
105
|
node = [m, m]
|
|
92
106
|
}
|
|
93
107
|
|
|
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
|
-
// (type (func param* result*))
|
|
99
|
-
// (type (array (mut i8)))
|
|
100
|
-
// (type (struct (field a)*)
|
|
101
|
-
// (type (sub final? $nm* (struct|array|func ...)))
|
|
102
|
-
else if (kind === 'type') {
|
|
103
|
-
let [dfn] = node
|
|
104
|
-
let issub = subc-- > 0
|
|
105
|
-
let subkind = issub && 'subfinal', supertypes = []
|
|
106
|
-
if (dfn[0] === 'sub') {
|
|
107
|
-
subkind = dfn.shift(), dfn[0] === 'final' && (subkind += dfn.shift())
|
|
108
|
-
dfn = (supertypes = dfn).pop() // last item is definition
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
let ckind = dfn.shift() // composite type kind
|
|
112
|
-
if (ckind === 'func') dfn = paramres(dfn), ctx.type['$' + dfn.join('>')] ??= ctx.type.length
|
|
113
|
-
else if (ckind === 'struct') dfn = fieldseq(dfn, 'field', true)
|
|
114
|
-
else if (ckind === 'array') dfn = dfn.shift()
|
|
115
|
-
|
|
116
|
-
node = [ckind, dfn, subkind, supertypes, rec ? [ctx.type.length, rec] : issub]
|
|
117
|
-
}
|
|
118
|
-
|
|
119
108
|
// dupe to code section, save implicit type
|
|
120
109
|
else if (kind === 'func') {
|
|
121
110
|
let [idx, param, result] = typeuse(node, ctx);
|
|
122
|
-
idx
|
|
111
|
+
idx ??= regtype(param, result, ctx)
|
|
112
|
+
|
|
123
113
|
// we save idx because type can be defined after
|
|
124
|
-
!imported &&
|
|
114
|
+
!imported && ctx.code.push([[idx, param, result], ...plain(node, ctx)]) // pass param since they may have names
|
|
125
115
|
node.unshift(['type', idx])
|
|
126
116
|
}
|
|
127
117
|
|
|
@@ -129,14 +119,7 @@ export default function watr(nodes) {
|
|
|
129
119
|
if (imported) ctx.import.push([...imported, [kind, ...node]]), node = null
|
|
130
120
|
|
|
131
121
|
items.push(node)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// add implicit types - main types receive aliases, implicit types are added if no explicit types exist
|
|
135
|
-
for (let n in ctx._) ctx.type[n] ??= (ctx.type.push(['func', ctx._[n]]) - 1)
|
|
136
|
-
|
|
137
|
-
// patch datacount if data === 0
|
|
138
|
-
// FIXME: let's try to return empty in datacount builder, since we filter after builder as well
|
|
139
|
-
// if (!ctx.data.length) ctx.datacount.length = 0
|
|
122
|
+
})
|
|
140
123
|
|
|
141
124
|
// convert nodes to bytes
|
|
142
125
|
const bin = (kind, count = true) => {
|
|
@@ -175,6 +158,103 @@ const alias = (node, list) => {
|
|
|
175
158
|
return name
|
|
176
159
|
}
|
|
177
160
|
|
|
161
|
+
// (type $id? (func param* result*))
|
|
162
|
+
// (type $id? (array (mut i8)))
|
|
163
|
+
// (type $id? (struct (field a)*)
|
|
164
|
+
// (type $id? (sub final? $nm* (struct|array|func ...)))
|
|
165
|
+
const typedef = ([dfn], ctx) => {
|
|
166
|
+
let subkind = 'subfinal', supertypes = [], compkind
|
|
167
|
+
if (dfn[0] === 'sub') {
|
|
168
|
+
subkind = dfn.shift(), dfn[0] === 'final' && (subkind += dfn.shift())
|
|
169
|
+
dfn = (supertypes = dfn).pop() // last item is definition
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
[compkind, ...dfn] = dfn // composite type kind
|
|
173
|
+
|
|
174
|
+
if (compkind === 'func') dfn = paramres(dfn), ctx.type['$' + dfn.join('>')] ??= ctx.type.length
|
|
175
|
+
else if (compkind === 'struct') dfn = fieldseq(dfn, 'field', true)
|
|
176
|
+
else if (compkind === 'array') [dfn] = dfn
|
|
177
|
+
|
|
178
|
+
return [compkind, dfn, subkind, supertypes]
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// register (implicit) type
|
|
182
|
+
const regtype = (param, result, ctx, idx='$' + param + '>' + result) => (
|
|
183
|
+
(ctx.type[idx] ??= ctx.type.push(['func', [param, result]]) - 1),
|
|
184
|
+
idx
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
// consume typeuse nodes, return type index/params, or null idx if no type
|
|
188
|
+
// https://webassembly.github.io/spec/core/text/modules.html#type-uses
|
|
189
|
+
const typeuse = (nodes, ctx, names) => {
|
|
190
|
+
let idx, param, result
|
|
191
|
+
|
|
192
|
+
// explicit type (type 0|$name)
|
|
193
|
+
if (nodes[0]?.[0] === 'type') {
|
|
194
|
+
[, idx] = nodes.shift();
|
|
195
|
+
[param, result] = paramres(nodes, names);
|
|
196
|
+
|
|
197
|
+
const [,srcParamRes] = ctx.type[id(idx, ctx.type)] ?? err(`Unknown type ${idx}`)
|
|
198
|
+
|
|
199
|
+
// check type consistency (excludes forward refs)
|
|
200
|
+
if ((param.length || result.length) && srcParamRes.join('>') !== param + '>' + result) err(`Type ${idx} mismatch`)
|
|
201
|
+
|
|
202
|
+
return [idx, ...srcParamRes]
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// implicit type (param i32 i32)(result i32)
|
|
206
|
+
return [idx, ...paramres(nodes, names)]
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// consume (param t+)* (result t+)* sequence
|
|
210
|
+
const paramres = (nodes, names = true) => {
|
|
211
|
+
// let param = [], result = []
|
|
212
|
+
|
|
213
|
+
// collect param (param i32 i64) (param $x? i32)
|
|
214
|
+
let param = fieldseq(nodes, 'param', names)
|
|
215
|
+
|
|
216
|
+
// collect result eg. (result f64 f32)(result i32)
|
|
217
|
+
let result = fieldseq(nodes, 'result')
|
|
218
|
+
|
|
219
|
+
if (nodes[0]?.[0] === 'param') err(`Unexpected param`)
|
|
220
|
+
|
|
221
|
+
return [param, result]
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// collect sequence of field, eg. (param a) (param b c), (field a) (field b c) or (result a b) (result c)
|
|
225
|
+
// optionally allow or not names
|
|
226
|
+
const fieldseq = (nodes, field, names = false) => {
|
|
227
|
+
let seq = []
|
|
228
|
+
// collect field eg. (field f64 f32)(field i32)
|
|
229
|
+
while (nodes[0]?.[0] === field) {
|
|
230
|
+
let [, ...args] = nodes.shift()
|
|
231
|
+
let name = args[0]?.[0] === '$' && args.shift()
|
|
232
|
+
// expose name refs, if allowed
|
|
233
|
+
if (name) {
|
|
234
|
+
if (names) name in seq ? err(`Duplicate ${field} ${name}`) : seq[name] = seq.length
|
|
235
|
+
else err(`Unexpected ${field} name ${name}`)
|
|
236
|
+
}
|
|
237
|
+
seq.push(...args)
|
|
238
|
+
}
|
|
239
|
+
return seq
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// consume blocktype - makes sure either type or single result is returned
|
|
243
|
+
const blocktype = (nodes, ctx) => {
|
|
244
|
+
let [idx, param, result] = typeuse(nodes, ctx, 0)
|
|
245
|
+
|
|
246
|
+
// get type - can be either idx or valtype (numtype | reftype)
|
|
247
|
+
if (!param.length && !result.length) return
|
|
248
|
+
|
|
249
|
+
// (result i32) - doesn't require registering type
|
|
250
|
+
if (!param.length && result.length === 1) return ['result', ...result]
|
|
251
|
+
|
|
252
|
+
// register implicit type
|
|
253
|
+
idx ??= regtype(param, result, ctx)
|
|
254
|
+
|
|
255
|
+
return ['type', idx]
|
|
256
|
+
}
|
|
257
|
+
|
|
178
258
|
// abbr blocks, loops, ifs; collect implicit types via typeuses; resolve optional immediates
|
|
179
259
|
// https://webassembly.github.io/spec/core/text/instructions.html#folded-instructions
|
|
180
260
|
const plain = (nodes, ctx) => {
|
|
@@ -211,7 +291,7 @@ const plain = (nodes, ctx) => {
|
|
|
211
291
|
else if (node.endsWith('call_indirect')) {
|
|
212
292
|
let tableidx = nodes[0]?.[0] === '$' || !isNaN(nodes[0]) ? nodes.shift() : 0
|
|
213
293
|
let [idx, param, result] = typeuse(nodes, ctx, 0)
|
|
214
|
-
out.push(tableidx, ['type', idx ?? (
|
|
294
|
+
out.push(tableidx, ['type', idx ?? regtype(param, result, ctx)])
|
|
215
295
|
}
|
|
216
296
|
|
|
217
297
|
// mark datacount section as required
|
|
@@ -267,80 +347,6 @@ const plain = (nodes, ctx) => {
|
|
|
267
347
|
return out
|
|
268
348
|
}
|
|
269
349
|
|
|
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
|
-
|
|
305
|
-
return [param, result]
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// collect sequence of field, eg. (param a) (param b c), (field a) (field b c) or (result a b) (result c)
|
|
309
|
-
// optionally allow or not names
|
|
310
|
-
const fieldseq = (nodes, field, names = false) => {
|
|
311
|
-
let seq = []
|
|
312
|
-
// collect field eg. (field f64 f32)(field i32)
|
|
313
|
-
while (nodes[0]?.[0] === field) {
|
|
314
|
-
let [, ...args] = nodes.shift()
|
|
315
|
-
let name = args[0]?.[0] === '$' && args.shift()
|
|
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}`)
|
|
320
|
-
}
|
|
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
|
-
|
|
333
|
-
// get type - can be either idx or valtype (numtype | reftype)
|
|
334
|
-
if (!param.length && !result.length) return
|
|
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
350
|
|
|
345
351
|
// build section binary [by section codes] (non consuming)
|
|
346
352
|
const build = [,
|
|
@@ -354,7 +360,6 @@ const build = [,
|
|
|
354
360
|
let details
|
|
355
361
|
// (rec (sub ...)*)
|
|
356
362
|
if (rec) {
|
|
357
|
-
// FIXME: rec of one type
|
|
358
363
|
kind = 'rec'
|
|
359
364
|
let [from, length] = rec, subtypes = Array.from({ length }, (_, i) => build[SECTION.type](ctx.type[from + i].slice(0, 4), ctx))
|
|
360
365
|
details = vec(subtypes)
|
|
@@ -378,7 +383,7 @@ const build = [,
|
|
|
378
383
|
return [DEFTYPE[kind], ...details]
|
|
379
384
|
},
|
|
380
385
|
|
|
381
|
-
// (import "math" "add" (func|table|global|memory
|
|
386
|
+
// (import "math" "add" (func|table|global|memory dfn?))
|
|
382
387
|
([mod, field, [kind, ...dfn]], ctx) => {
|
|
383
388
|
let details
|
|
384
389
|
|
|
@@ -635,14 +640,12 @@ const instr = (nodes, ctx) => {
|
|
|
635
640
|
}
|
|
636
641
|
// ref.test|cast (ref null? $t|heaptype)
|
|
637
642
|
else if (code >= 20 && code <= 23) {
|
|
638
|
-
// FIXME: normalizer is supposed to resolve this
|
|
639
643
|
let ht = reftype(nodes.shift(), ctx)
|
|
640
644
|
if (ht[0] !== REFTYPE.ref) immed.push(code = immed.pop()+1) // ref.test|cast (ref null $t) is next op
|
|
641
645
|
if (ht.length > 1) ht.shift() // pop ref
|
|
642
646
|
immed.push(...ht)
|
|
643
647
|
}
|
|
644
648
|
// br_on_cast[_fail] $l? (ref null? ht1) (ref null? ht2)
|
|
645
|
-
// FIXME: normalizer should resolve anyref|etc to (ref null any|etc)
|
|
646
649
|
else if (code === 24 || code === 25) {
|
|
647
650
|
let i = blockid(nodes.shift(), ctx.block),
|
|
648
651
|
ht1 = reftype(nodes.shift(), ctx),
|
|
@@ -744,7 +747,6 @@ const instr = (nodes, ctx) => {
|
|
|
744
747
|
ctx.block.push(code)
|
|
745
748
|
|
|
746
749
|
// (block $x) (loop $y) - save label pointer
|
|
747
|
-
// FIXME: do in normalizer
|
|
748
750
|
if (nodes[0]?.[0] === '$') ctx.block[nodes.shift()] = ctx.block.length
|
|
749
751
|
|
|
750
752
|
let t = nodes.shift();
|
|
@@ -754,13 +756,8 @@ const instr = (nodes, ctx) => {
|
|
|
754
756
|
// (result i32) - doesn't require registering type
|
|
755
757
|
// FIXME: Make sure it is signed positive integer (leb, not uleb) https://webassembly.github.io/gc/core/binary/instructions.html#control-instructions
|
|
756
758
|
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
|
-
}
|
|
759
|
+
// (type idx)
|
|
760
|
+
else immed.push(...uleb(id(t[1], ctx.type)))
|
|
764
761
|
}
|
|
765
762
|
// else
|
|
766
763
|
else if (code === 5) { }
|