diff --git a/readme.md b/readme.md index e7cd895..3e8c47d 100644 --- a/readme.md +++ b/readme.md @@ -84,9 +84,9 @@ print(src, { * [x] [simd](https://github.com/WebAssembly/simd/blob/master/proposals/simd/SIMD.md) * [x] [relaxed simd](https://github.com/WebAssembly/relaxed-simd) * [x] [fixed-width simd](https://github.com/WebAssembly/simd/blob/master/proposals/simd/SIMD.md) +* [x] [tail_call](https://github.com/WebAssembly/tail-call) * [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) -* [x] [tail_call](https://github.com/WebAssembly/tail-call) * [ ] [gc](https://github.com/WebAssembly/gc) * [ ] [exceptions](https://github.com/WebAssembly/exception-handling) * [ ] [memory64](https://github.com/WebAssembly/memory64) diff --git a/src/compile.js b/src/compile.js index 795b30e..8a3a504 100644 --- a/src/compile.js +++ b/src/compile.js @@ -102,7 +102,7 @@ export default function watr(nodes) { // convert nodes to bytes const bin = (kind, count = true) => { let items = sections[kind].filter(Boolean).map(item => build[kind](item, sections)) - return !items.length ? [] : [kind, ...vec([...(count ? uleb(items.length) : []), ...items.flat()])] + return !items.length ? [] : [kind, ...vec(count ? vec(items) : items)] } // build final binary @@ -289,11 +289,11 @@ const build = [, details = limits(dfn) } else if (kind === 'global') { - let [type] = dfn, mut = type[0] === 'mut' ? 1 : 0 - details = [TYPE[mut ? type[1] : type], mut] + let [t] = dfn, mut = t[0] === 'mut' ? 1 : 0 + details = [...type(mut ? t[1] : t, ctx), mut] } else if (kind === 'table') { - details = [TYPE[dfn.pop()], ...limits(dfn)] + details = [...type(dfn.pop(), ctx), ...limits(dfn)] } return ([...vec(str(mod.slice(1, -1))), ...vec(str(field.slice(1, -1))), KIND[kind], ...details]) @@ -303,24 +303,24 @@ const build = [, ([[, typeidx]], ctx) => (uleb(id(typeidx, ctx.type))), // (table id? 1 2? funcref) - (node, ctx) => ([TYPE[node.pop()], ...limits(node)]), + (node, ctx) => ([...type(node.pop(), ctx), ...limits(node)]), // (memory id? export* min max shared) (node, ctx) => limits(node), // (global $id? (mut i32) (i32.const 42)) (node, ctx) => { - let [type] = node, mut = type[0] === 'mut' ? 1 : 0 + let [t] = node, mut = t[0] === 'mut' ? 1 : 0 let [, init] = node - return ([TYPE[mut ? type[1] : type], mut, ...expr(init, ctx), 0x0b]) + return ([...type(mut ? t[1] : t, ctx), mut, ...expr(init, ctx), 0x0b]) }, // (export "name" (func|table|mem $name|idx)) ([nm, [kind, l]], ctx) => ([...vec(str(nm.slice(1, -1))), KIND[kind], ...uleb(id(l, ctx[kind]))]), // (start $main) - ([l], ctx) => (uleb(id(l, ctx.func))), + ([l], ctx) => uleb(id(l, ctx.func)), // (elem elem*) - passive // (elem declare elem*) - declarative @@ -386,13 +386,13 @@ const build = [, // 0b111 et:reftype el*:vec(expr) | type=et, init el*, passive declare [TYPE[reftype]] ), - ...uleb(parts.length), - ...parts.flatMap(mode & 0b100 ? - // ((ref.func y)end)* - el => [...expr(typeof el === 'string' ? ['ref.func', el] : el, ctx), 0x0b] : - // el* - el => uleb(id(el, ctx.func)) - ) + ...vec( + parts.map(mode & 0b100 ? + // ((ref.func y)end)* + el => [...expr(typeof el === 'string' ? ['ref.func', el] : el, ctx), 0x0b] : + // el* + el => uleb(id(el, ctx.func)) + )) ]) }, @@ -413,7 +413,7 @@ const build = [, while (body[0]?.[0] === 'local') { let [, ...types] = body.shift() if (types[0]?.[0] === '$') ctx.local[types.shift()] = ctx.local.length - ctx.local.push(...types.flatMap(t => type(t, ctx))) + ctx.local.push(...types) } const bytes = [] @@ -428,7 +428,7 @@ const build = [, ctx.local = ctx.block = null // https://webassembly.github.io/spec/core/binary/modules.html#code-section - return (vec([...uleb(loctypes.length), ...loctypes.flatMap(([n, t]) => [...uleb(n), t]), ...bytes])) + return vec([...vec(loctypes.map(([n, t]) => [...uleb(n), ...type(t, ctx)])), ...bytes]) }, // (data (i32.const 0) "\aa" "\bb"?) @@ -469,7 +469,7 @@ const build = [, // insert type, either direct or ref type const type = (t, ctx) => - t[0] === 'ref' ? ([t[1] == 'null' ? TYPE.refnull : TYPE.ref, ...uleb(TYPE[t[t.length - 1]] || id(t[t.length - 1], ctx.type))]) + t[0] === 'ref' ? ([t[1] == 'null' ? TYPE.refnull : TYPE.ref, ...uleb(TYPE[t[t.length - 1]] || id(t[t.length - 1], ctx.type))]) : [TYPE[t] ?? err(`Unknown type ${t}`)] // consume one instruction from nodes sequence @@ -653,7 +653,7 @@ const instr = (nodes, ctx) => { else if (code == 0x1b) { let result = nodes.shift() // 0x1b -> 0x1c - if (result.length) immed.push(immed.pop() + 1, ...uleb(result.length), ...result.flatMap(t => type(t, ctx))) + if (result.length) immed.push(immed.pop() + 1, ...vec(result.map(t => type(t, ctx)))) } // ref.func $id diff --git a/test/compile.js b/test/compile.js index 2760f4b..398df81 100644 --- a/test/compile.js +++ b/test/compile.js @@ -2164,14 +2164,13 @@ t('feature: function refs', () => { inline(src) src=` - (module - (type $t (func)) - (func (param $r (ref null $t)) (drop (block (result (ref $t)) (br_on_non_null 0 (local.get $r)) (unreachable)))) - (func (param $r (ref null func)) (drop (block (result (ref func)) (br_on_non_null 0 (local.get $r)) (unreachable)))) - (func (param $r (ref null extern)) (drop (block (result (ref extern)) (br_on_non_null 0 (local.get $r)) (unreachable)))) - ) + (type $t (func)) + (func (param $r (ref null $t)) (drop (block (result (ref $t)) (br_on_non_null 0 (local.get $r)) (unreachable)))) + (func (param $r (ref null func)) (drop (block (result (ref func)) (br_on_non_null 0 (local.get $r)) (unreachable)))) + (func (param $r (ref null extern)) (drop (block (result (ref extern)) (br_on_non_null 0 (local.get $r)) (unreachable)))) ` inline(src) + }) // examples @@ -2406,7 +2405,7 @@ export async function file(path, imports = {}) { } // save binary (asm buffer) to file -export const save = (buf) => { +export function save (buf) { // Create a Blob const blob = new Blob([buf], { type: "application/wasm" }); diff --git a/test/testsuite.js b/test/testsuite.js index 4cc8b78..342a30e 100644 --- a/test/testsuite.js +++ b/test/testsuite.js @@ -177,28 +177,28 @@ t('/test/official/proposals/extended-const/global.wast', async function () { awa t('/test/official/proposals/function-references/binary.wast', async function () { await file(this.name, { spectest }) }) t('/test/official/proposals/function-references/br_on_non_null.wast', async function () { await file(this.name, { spectest }) }) t.todo('/test/official/proposals/function-references/br_table.wast', async function () { await file(this.name, { spectest }) }) -t.todo('/test/official/proposals/function-references/call_ref.wast', async function () { await file(this.name, { spectest }) }) -t.todo('/test/official/proposals/function-references/data.wast', async function () { await file(this.name, { spectest }) }) +t('/test/official/proposals/function-references/call_ref.wast', async function () { await file(this.name, { spectest }) }) +t('/test/official/proposals/function-references/data.wast', async function () { await file(this.name, { spectest }) }) t.todo('/test/official/proposals/function-references/elem.wast', async function () { await file(this.name, { spectest }) }) -t.todo('/test/official/proposals/function-references/func.wast', async function () { await file(this.name, { spectest }) }) -t.todo('/test/official/proposals/function-references/global.wast', async function () { await file(this.name, { spectest }) }) -t.todo('/test/official/proposals/function-references/if.wast', async function () { await file(this.name, { spectest }) }) -t.todo('/test/official/proposals/function-references/linking.wast', async function () { await file(this.name, { spectest }) }) -t.todo('/test/official/proposals/function-references/local_get.wast', async function () { await file(this.name, { spectest }) }) -t.todo('/test/official/proposals/function-references/local_init.wast', async function () { await file(this.name, { spectest }) }) -t.todo('/test/official/proposals/function-references/ref_as_non_null.wast', async function () { await file(this.name, { spectest }) }) +t('/test/official/proposals/function-references/func.wast', async function () { await file(this.name, { spectest }) }) +t('/test/official/proposals/function-references/global.wast', async function () { await file(this.name, { spectest }) }) +t('/test/official/proposals/function-references/if.wast', async function () { await file(this.name, { spectest }) }) +t('/test/official/proposals/function-references/linking.wast', async function () { await file(this.name, { spectest }) }) +t('/test/official/proposals/function-references/local_get.wast', async function () { await file(this.name, { spectest }) }) +t('/test/official/proposals/function-references/local_init.wast', async function () { await file(this.name, { spectest }) }) +t('/test/official/proposals/function-references/ref_as_non_null.wast', async function () { await file(this.name, { spectest }) }) t.todo('/test/official/proposals/function-references/ref_is_null.wast', async function () { await file(this.name, { spectest }) }) -t.todo('/test/official/proposals/function-references/ref_null.wast', async function () { await file(this.name, { spectest }) }) -t.todo('/test/official/proposals/function-references/ref.wast', async function () { await file(this.name, { spectest }) }) -t.todo('/test/official/proposals/function-references/return_call_indirect.wast', async function () { await file(this.name, { spectest }) }) -t.todo('/test/official/proposals/function-references/return_call_ref.wast', async function () { await file(this.name, { spectest }) }) -t.todo('/test/official/proposals/function-references/return_call.wast', async function () { await file(this.name, { spectest }) }) +t('/test/official/proposals/function-references/ref_null.wast', async function () { await file(this.name, { spectest }) }) +t('/test/official/proposals/function-references/ref.wast', async function () { await file(this.name, { spectest }) }) +t('/test/official/proposals/function-references/return_call_indirect.wast', async function () { await file(this.name, { spectest }) }) +t('/test/official/proposals/function-references/return_call_ref.wast', async function () { await file(this.name, { spectest }) }) +t('/test/official/proposals/function-references/return_call.wast', async function () { await file(this.name, { spectest }) }) t.todo('/test/official/proposals/function-references/select.wast', async function () { await file(this.name, { spectest }) }) -t.todo('/test/official/proposals/function-references/table-sub.wast', async function () { await file(this.name, { spectest }) }) +t('/test/official/proposals/function-references/table-sub.wast', async function () { await file(this.name, { spectest }) }) t.todo('/test/official/proposals/function-references/table.wast', async function () { await file(this.name, { spectest }) }) t.todo('/test/official/proposals/function-references/type-equivalence.wast', async function () { await file(this.name, { spectest }) }) -t.todo('/test/official/proposals/function-references/unreached-invalid.wast', async function () { await file(this.name, { spectest }) }) -t.todo('/test/official/proposals/function-references/unreached-valid.wast', async function () { await file(this.name, { spectest }) }) +t('/test/official/proposals/function-references/unreached-invalid.wast', async function () { await file(this.name, { spectest }) }) +t('/test/official/proposals/function-references/unreached-valid.wast', async function () { await file(this.name, { spectest }) }) t.todo('/test/official/proposals/gc/try_table.wast', async function () { await file(this.name, { spectest }) })