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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/compile.js +132 -135
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "watr",
3
- "version": "3.2.0",
3
+ "version": "3.2.1",
4
4
  "description": "Ligth & fast WAT compiler",
5
5
  "main": "watr.js",
6
6
  "exports": {
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
- if (node.length > 1) rec = subc = node.length, nodes.unshift(...node), node = nodes.shift(), kind = node.shift()
58
- else kind = (node = node[0]).shift()
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
- else if (kind === 'import') [kind, ...node] = (imported = node).pop()
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 ?? (ctx._[idx = '$' + param + '>' + result] = [param, result]);
111
+ idx ??= regtype(param, result, ctx)
112
+
123
113
  // 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
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 ?? (ctx._[idx = '$' + param + '>' + result] = [param, result], 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 typedef?))
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
- else {
758
- let typeidx = id(t[1], ctx.type), [param, result] = ctx.type[typeidx][1]
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) { }