watr 2.0.0 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/compile.js +71 -53
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "watr",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Ligth & fast WAT compiler",
5
5
  "main": "watr.js",
6
6
  "exports": {
package/src/compile.js CHANGED
@@ -46,7 +46,7 @@ export default (nodes) => {
46
46
  }
47
47
 
48
48
  // code must be compiled after all definitions
49
- for (let cb of postcall) cb && cb.call && cb()
49
+ for (let cb of postcall) cb?.()
50
50
 
51
51
  // 3. build binary
52
52
  for (let name in sections) {
@@ -66,31 +66,10 @@ const build = {
66
66
  // (type $name? (func (param $x i32) (param i64 i32) (result i32 i64)))
67
67
  // signature part is identical to function
68
68
  // FIXME: handle non-function types
69
- type([, typeName, decl], ctx) {
70
- if (typeName[0] !== '$') decl = typeName, typeName = null
71
- let params = [], result = [], [kind, ...sig] = decl, idx, bytes
72
-
73
- if (kind === 'func') {
74
- // collect params
75
- while (sig[0]?.[0] === 'param') {
76
- let [, ...types] = sig.shift()
77
- if (types[0]?.[0] === '$') params[types.shift()] = params.length
78
- params.push(...types.map(t => TYPE[t]))
79
- }
80
-
81
- // collect result type
82
- if (sig[0]?.[0] === 'result') result = sig.shift().slice(1).map(t => TYPE[t])
83
-
84
- // reuse existing type or register new one
85
- bytes = [TYPE.func, ...uleb(params.length), ...params, ...uleb(result.length), ...result]
86
-
87
- idx = ctx.type.findIndex((prevType) => prevType.every((byte, i) => byte === bytes[i]))
88
- if (idx < 0) idx = ctx.type.push(bytes) - 1
89
- }
90
-
69
+ type([, typeName, [kind, ...sig]], ctx) {
70
+ if (kind !== 'func') err(`Unknown type kind '${kind}'`)
71
+ const [idx] = consumeType(sig, ctx)
91
72
  if (typeName) ctx.type[typeName] = idx
92
-
93
- return [idx, params, result]
94
73
  },
95
74
 
96
75
  // (func $name? ...params result ...body)
@@ -104,10 +83,10 @@ const build = {
104
83
  // export binding
105
84
  if (body[0]?.[0] === 'export') build.export([...body.shift(), ['func', ctx.func.length]], ctx)
106
85
 
107
- // register type
108
- let [typeIdx, params, result] = build.type([, ['func', ...body]], ctx)
109
- // FIXME: try merging with build.type: it should be able to consume body
110
- while (body[0]?.[0] === 'param' || body[0]?.[0] === 'result') body.shift()
86
+ // register/consume type info
87
+ let [typeIdx, params, result] = consumeType(body, ctx)
88
+
89
+ // register new function
111
90
  ctx.func.push([typeIdx])
112
91
 
113
92
  // collect locals
@@ -182,40 +161,54 @@ const build = {
182
161
  immed = [0]
183
162
  }
184
163
 
185
- // (if (result i32)? (local.get 0) (then a b) (argse a b)?)
186
- else if (opCode == 4) {
164
+ // (if ...), (block ...), (loop ...)
165
+ else if (opCode > 1 && opCode < 5) {
187
166
  blocks.push(opCode)
188
- let [, type] = args[0][0] === 'result' ? args.shift() : [, 'void']
189
- immed = [TYPE[type]] // FIXME: what if that's multiple values return?
190
167
 
191
- // (if ... (then) (else)) -> `... if ... else ... end`
168
+ // (block $x) (loop $y)
169
+ if (opCode < 4 && args[0]?.[0] === '$') (blocks[args.shift()] = blocks.length)
170
+
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
+
192
186
  if (group) {
193
187
  nodes.unshift('end')
194
- if (args[args.length - 1][0] === 'else') nodes.unshift(args.pop())
195
- if (args[args.length - 1][0] === 'then') nodes.unshift(args.pop())
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
+ }
196
203
  }
197
204
  }
205
+
198
206
  // (else)
199
207
  else if (opCode === 5) {
200
- // ignore empty else
201
- if (!args.length && nodes[0] === 'end') return
202
208
  // (else xxx) -> else xxx
203
209
  if (group) while (args.length) nodes.unshift(args.pop())
204
210
  }
205
211
 
206
- // (block ...), (loop ...)
207
- else if (opCode == 2 || opCode == 3) {
208
- blocks.push(opCode)
209
- if (args[0]?.[0] === '$') (blocks[args.shift()] = blocks.length)
210
- let [, type] = args[0]?.[0] === 'result' ? args.shift() : [, 'void']
211
- immed = [TYPE[type]]
212
- // (block xxx) -> block xxx end
213
- if (group) {
214
- nodes.unshift('end')
215
- while (args.length) nodes.unshift(args.pop())
216
- }
217
- }
218
-
219
212
  // (end)
220
213
  else if (opCode == 0x0b) blocks.pop()
221
214
 
@@ -304,7 +297,7 @@ const build = {
304
297
  if (kind === 'func') {
305
298
  // we track imported funcs in func section to share namespace, and skip them on final build
306
299
  if (name) ctx.func[name] = ctx.func.length
307
- let [typeIdx] = build.type([, ['func', ...parts]], ctx)
300
+ let [typeIdx] = consumeType(parts, ctx)
308
301
  ctx.func.push(details = uleb(typeIdx))
309
302
  ctx.func.importc = (ctx.func.importc || 0) + 1
310
303
  }
@@ -358,8 +351,33 @@ const str = str => {
358
351
  return res
359
352
  }
360
353
 
361
-
362
354
  // build range/limits sequence (non-consuming)
363
355
  const range = ([min, max, shared]) => isNaN(parseInt(max)) ? [0, ...uleb(min)] : [shared === 'shared' ? 3 : 1, ...uleb(min), ...uleb(max)]
364
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
+
365
383
  const err = text => { throw Error(text) }