diff --git a/src/parser/context-decls.cpp b/src/parser/context-decls.cpp index c124689d334..f02ec30dd66 100644 --- a/src/parser/context-decls.cpp +++ b/src/parser/context-decls.cpp @@ -67,6 +67,7 @@ Result<> ParseDeclsCtx::addFunc(Name name, const std::vector& exports, ImportNames* import, TypeUseT type, + Exactness exact, std::optional, std::vector&& annotations, Index pos) { diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 500c79ba315..d43967bffaa 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1078,6 +1078,7 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx { const std::vector& exports, ImportNames* import, TypeUseT type, + Exactness exact, std::optional, std::vector&&, Index pos); @@ -1411,6 +1412,7 @@ struct ParseModuleTypesCtx : TypeParserCtx, const std::vector&, ImportNames*, TypeUse type, + Exactness exact, std::optional locals, std::vector&&, Index pos) { @@ -1418,10 +1420,7 @@ struct ParseModuleTypesCtx : TypeParserCtx, if (!type.type.isSignature()) { return in.err(pos, "expected signature type"); } - f->type = f->type.with(type.type); - if (f->imported()) { - f->type = f->type.with(Inexact); - } + f->type = Type(type.type, NonNullable, exact); // If we are provided with too many names (more than the function has), we // will error on that later when we check the signature matches the type. // For now, avoid asserting in setLocalName. @@ -1803,6 +1802,7 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { const std::vector&, ImportNames*, TypeUseT, + Exactness, std::optional, std::vector&&, Index) { diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 65753942864..7188d8aad20 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -370,6 +370,8 @@ Result labelidx(Ctx&, bool inDelegate = false); template Result tagidx(Ctx&); template Result typeuse(Ctx&, bool allowNames = true); +template +Result> exacttypeuse(Ctx&); MaybeResult inlineImport(Lexer&); Result> inlineExports(Lexer&); template Result<> comptype(Ctx&); @@ -3007,6 +3009,24 @@ Result typeuse(Ctx& ctx, bool allowNames) { return ctx.makeTypeUse(pos, type, namedParams.getPtr(), resultTypes.getPtr()); } +// exacttypeuse ::= typeuse | +// '(' 'exact' typeuse ')' +template +Result> exacttypeuse(Ctx& ctx) { + auto exact = Inexact; + if (ctx.in.takeSExprStart("exact"sv)) { + exact = Exact; + } + auto type = typeuse(ctx, true); + CHECK_ERR(type); + if (exact == Exact) { + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of exact type use"); + } + } + return std::make_pair(*type, exact); +} + // ('(' 'import' mod:name nm:name ')')? inline MaybeResult inlineImport(Lexer& in) { if (!in.takeSExprStart("import"sv)) { @@ -3221,7 +3241,7 @@ template MaybeResult locals(Ctx& ctx) { } // import ::= '(' 'import' mod:name nm:name importdesc ')' -// importdesc ::= '(' 'func' id? typeuse ')' +// importdesc ::= '(' 'func' id? exacttypeuse ')' // | '(' 'table' id? tabletype ')' // | '(' 'memory' id? memtype ')' // | '(' 'global' id? globaltype ')' @@ -3246,11 +3266,12 @@ template MaybeResult<> import_(Ctx& ctx) { if (ctx.in.takeSExprStart("func"sv)) { auto name = ctx.in.takeID(); - auto type = typeuse(ctx); - CHECK_ERR(type); + auto use = exacttypeuse(ctx); + CHECK_ERR(use); + auto [type, exact] = *use; // TODO: function import annotations CHECK_ERR(ctx.addFunc( - name ? *name : Name{}, {}, &names, *type, std::nullopt, {}, pos)); + name ? *name : Name{}, {}, &names, type, exact, std::nullopt, {}, pos)); } else if (ctx.in.takeSExprStart("table"sv)) { auto name = ctx.in.takeID(); auto type = tabletype(ctx); @@ -3289,7 +3310,7 @@ template MaybeResult<> import_(Ctx& ctx) { // func ::= '(' 'func' id? ('(' 'export' name ')')* // x,I:typeuse t*:vec(local) (in:instr)* ')' // | '(' 'func' id? ('(' 'export' name ')')* -// '(' 'import' mod:name nm:name ')' typeuse ')' +// '(' 'import' mod:name nm:name ')' exacttypeuse ')' template MaybeResult<> func(Ctx& ctx) { auto pos = ctx.in.getPos(); auto annotations = ctx.in.getAnnotations(); @@ -3309,11 +3330,19 @@ template MaybeResult<> func(Ctx& ctx) { auto import = inlineImport(ctx.in); CHECK_ERR(import); - auto type = typeuse(ctx); - CHECK_ERR(type); - + typename Ctx::TypeUseT type; + Exactness exact = Exact; std::optional localVars; - if (!import) { + + if (import) { + auto use = exacttypeuse(ctx); + CHECK_ERR(use); + type = use->first; + exact = use->second; + } else { + auto use = typeuse(ctx); + CHECK_ERR(use); + type = *use; if (auto l = locals(ctx)) { CHECK_ERR(l); localVars = *l; @@ -3331,7 +3360,8 @@ template MaybeResult<> func(Ctx& ctx) { CHECK_ERR(ctx.addFunc(name, *exports, import.getPtr(), - *type, + type, + exact, localVars, std::move(annotations), pos)); diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 705722f498b..02c094c358d 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -3078,6 +3078,9 @@ void PrintSExpression::handleSignature(Function* curr, o << '('; printMajor(o, "func "); curr->name.print(o); + if (curr->imported() && curr->type.isExact()) { + o << " (exact"; + } if ((currModule && currModule->features.hasGC()) || requiresExplicitFuncType(curr->type.getHeapType())) { o << " (type "; @@ -3124,6 +3127,9 @@ void PrintSExpression::handleSignature(Function* curr, o << maybeSpace; printResultType(curr->getResults()); } + if (curr->imported() && curr->type.isExact()) { + o << ')'; + } } void PrintSExpression::visitExport(Export* curr) { diff --git a/src/passes/StackCheck.cpp b/src/passes/StackCheck.cpp index 31bf791fd80..eee088054a9 100644 --- a/src/passes/StackCheck.cpp +++ b/src/passes/StackCheck.cpp @@ -41,7 +41,8 @@ importStackOverflowHandler(Module& module, Name name, Signature sig) { ImportInfo info(module); if (!info.getImportedFunction(ENV, name)) { - auto import = Builder::makeFunction(name, sig, {}); + auto import = + Builder::makeFunction(name, Type(sig, NonNullable, Inexact), {}); import->module = ENV; import->base = name; module.addFunction(std::move(import)); diff --git a/src/wasm-binary.h b/src/wasm-binary.h index ee055dfbfbd..3117ca4c97f 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -356,6 +356,8 @@ enum BrOnCastFlag { OutputNullable = 1 << 1, }; +constexpr uint32_t ExactImport = 1 << 5; + enum EncodedType { // value types i32 = -0x1, // 0x7f diff --git a/src/wasm-builder.h b/src/wasm-builder.h index fef417fc1a5..d14e716f12b 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -47,7 +47,7 @@ class Builder { Type type, std::vector&& vars, Expression* body = nullptr) { - assert(type.isSignature()); + assert(type.isSignature() && type.isNonNullable()); auto func = std::make_unique(); func->name = name; func->type = type; diff --git a/src/wasm.h b/src/wasm.h index 757a0001501..605c7025395 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2342,15 +2342,20 @@ class Function : public Importable { void clearDebugInfo(); }; -// The kind of an import or export. -enum class ExternalKind { +// The kind of an import or export. Use a namespace to avoid polluting the wasm +// namespace while maintaining implicit conversion to int, which an enum class +// would not have. +namespace ExternalKindImpl { +enum Kind : uint32_t { Function = 0, Table = 1, Memory = 2, Global = 3, Tag = 4, - Invalid = -1 + Invalid = uint32_t(-1) }; +} // namespace ExternalKindImpl +using ExternalKind = ExternalKindImpl::Kind; // The kind of a top-level module item. (This overlaps with ExternalKind, but // C++ has no good way to extend an enum.) All such items are referred to by diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 57d034b3c62..21d9732d398 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -332,8 +332,11 @@ void WasmBinaryWriter::writeImports() { }; ModuleUtils::iterImportedFunctions(*wasm, [&](Function* func) { writeImportHeader(func); - o << U32LEB(int32_t(ExternalKind::Function)); - o << U32LEB(getTypeIndex(func->type.getHeapType())); + uint32_t kind = ExternalKind::Function; + if (func->type.isExact()) { + kind |= BinaryConsts::ExactImport; + } + o << U32LEB(kind) << U32LEB(getTypeIndex(func->type.getHeapType())); }); ModuleUtils::iterImportedGlobals(*wasm, [&](Global* global) { writeImportHeader(global); @@ -2875,12 +2878,13 @@ void WasmBinaryReader::readImports() { for (size_t i = 0; i < num; i++) { auto module = getInlineString(); auto base = getInlineString(); - auto kind = (ExternalKind)getU32LEB(); + auto kind = getU32LEB(); // We set a unique prefix for the name based on the kind. This ensures no // collisions between them, which can't occur here (due to the index i) but // could occur later due to the names section. switch (kind) { - case ExternalKind::Function: { + case ExternalKind::Function: + case ExternalKind::Function | BinaryConsts::ExactImport: { auto [name, isExplicit] = getOrMakeName(functionNames, wasm.functions.size(), @@ -2894,8 +2898,9 @@ void WasmBinaryReader::readImports() { '.' + base.toString() + "'s type must be a signature. Given: " + type.toString()); } + auto exact = (kind & BinaryConsts::ExactImport) ? Exact : Inexact; auto curr = - builder.makeFunction(name, Type(type, NonNullable, Inexact), {}); + builder.makeFunction(name, Type(type, NonNullable, exact), {}); curr->hasExplicitName = isExplicit; curr->module = module; curr->base = base; @@ -4691,8 +4696,8 @@ void WasmBinaryReader::readExports() { if (!names.emplace(name).second) { throwError("duplicate export name"); } - ExternalKind kind = (ExternalKind)getU32LEB(); - std::variant value; + auto kind = getU32LEB(); + std::optional> value; auto index = getU32LEB(); switch (kind) { case ExternalKind::Function: @@ -4711,9 +4716,12 @@ void WasmBinaryReader::readExports() { value = getTagName(index); break; case ExternalKind::Invalid: - throwError("invalid export kind"); + break; + } + if (!value) { + throwError("invalid export kind"); } - wasm.addExport(new Export(name, kind, value)); + wasm.addExport(new Export(name, ExternalKind(kind), *value)); } } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index bd64dc4cead..94d3b137699 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -4159,10 +4159,7 @@ void FunctionValidator::visitFunction(Function* curr) { curr->name, "all used types should be allowed"); - if (curr->imported()) { - shouldBeTrue( - !curr->type.isExact(), curr->name, "imported function should be inexact"); - } else { + if (!curr->imported()) { shouldBeTrue( curr->type.isExact(), curr->name, "defined function should be exact"); } diff --git a/test/lit/basic/exact-imports.wast b/test/lit/basic/exact-imports.wast new file mode 100644 index 00000000000..7ad6e87f0b3 --- /dev/null +++ b/test/lit/basic/exact-imports.wast @@ -0,0 +1,97 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt %s -all -o %t.text.wast -g -S +;; RUN: wasm-as %s -all -g -o %t.wasm +;; RUN: wasm-dis %t.wasm -all -o %t.bin.wast +;; RUN: wasm-as %s -all -o %t.nodebug.wasm +;; RUN: wasm-dis %t.nodebug.wasm -all -o %t.bin.nodebug.wast +;; RUN: cat %t.text.wast | filecheck %s --check-prefix=CHECK-TEXT +;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK-BIN +;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG + +(module + ;; CHECK-TEXT: (type $f (func (param i32) (result i64))) + ;; CHECK-BIN: (type $f (func (param i32) (result i64))) + (type $f (func (param i32) (result i64))) + ;; CHECK-TEXT: (import "" "" (func $1 (exact (type $f) (param i32) (result i64)))) + ;; CHECK-BIN: (import "" "" (func $1 (exact (type $f) (param i32) (result i64)))) + (import "" "" (func $1 (exact (type $f)))) + ;; CHECK-TEXT: (import "" "" (func $2 (exact (type $f) (param i32) (result i64)))) + ;; CHECK-BIN: (import "" "" (func $2 (exact (type $f) (param i32) (result i64)))) + (import "" "" (func $2 (exact (type $f) (param i32) (result i64)))) + ;; CHECK-TEXT: (import "" "" (func $3 (exact (type $f) (param i32) (result i64)))) + ;; CHECK-BIN: (import "" "" (func $3 (exact (type $f) (param i32) (result i64)))) + (import "" "" (func $3 (exact (param i32) (result i64)))) + + (func $4 (import "" "") (exact (type $f))) + (func $5 (import "" "") (exact (type $f) (param i32) (result i64))) + (func $6 (import "" "") (exact (param i32) (result i64))) + + (global (ref (exact $f)) (ref.func $1)) + (global (ref (exact $f)) (ref.func $2)) + (global (ref (exact $f)) (ref.func $3)) + (global (ref (exact $f)) (ref.func $4)) + (global (ref (exact $f)) (ref.func $5)) + (global (ref (exact $f)) (ref.func $6)) +) +;; CHECK-TEXT: (import "" "" (func $4 (exact (type $f) (param i32) (result i64)))) + +;; CHECK-TEXT: (import "" "" (func $5 (exact (type $f) (param i32) (result i64)))) + +;; CHECK-TEXT: (import "" "" (func $6 (exact (type $f) (param i32) (result i64)))) + +;; CHECK-TEXT: (global $global$0 (ref (exact $f)) (ref.func $1)) + +;; CHECK-TEXT: (global $global$1 (ref (exact $f)) (ref.func $2)) + +;; CHECK-TEXT: (global $global$2 (ref (exact $f)) (ref.func $3)) + +;; CHECK-TEXT: (global $global$3 (ref (exact $f)) (ref.func $4)) + +;; CHECK-TEXT: (global $global$4 (ref (exact $f)) (ref.func $5)) + +;; CHECK-TEXT: (global $global$5 (ref (exact $f)) (ref.func $6)) + +;; CHECK-BIN: (import "" "" (func $4 (exact (type $f) (param i32) (result i64)))) + +;; CHECK-BIN: (import "" "" (func $5 (exact (type $f) (param i32) (result i64)))) + +;; CHECK-BIN: (import "" "" (func $6 (exact (type $f) (param i32) (result i64)))) + +;; CHECK-BIN: (global $global$0 (ref (exact $f)) (ref.func $1)) + +;; CHECK-BIN: (global $global$1 (ref (exact $f)) (ref.func $2)) + +;; CHECK-BIN: (global $global$2 (ref (exact $f)) (ref.func $3)) + +;; CHECK-BIN: (global $global$3 (ref (exact $f)) (ref.func $4)) + +;; CHECK-BIN: (global $global$4 (ref (exact $f)) (ref.func $5)) + +;; CHECK-BIN: (global $global$5 (ref (exact $f)) (ref.func $6)) + +;; CHECK-BIN-NODEBUG: (type $0 (func (param i32) (result i64))) + +;; CHECK-BIN-NODEBUG: (import "" "" (func $fimport$0 (exact (type $0) (param i32) (result i64)))) + +;; CHECK-BIN-NODEBUG: (import "" "" (func $fimport$1 (exact (type $0) (param i32) (result i64)))) + +;; CHECK-BIN-NODEBUG: (import "" "" (func $fimport$2 (exact (type $0) (param i32) (result i64)))) + +;; CHECK-BIN-NODEBUG: (import "" "" (func $fimport$3 (exact (type $0) (param i32) (result i64)))) + +;; CHECK-BIN-NODEBUG: (import "" "" (func $fimport$4 (exact (type $0) (param i32) (result i64)))) + +;; CHECK-BIN-NODEBUG: (import "" "" (func $fimport$5 (exact (type $0) (param i32) (result i64)))) + +;; CHECK-BIN-NODEBUG: (global $global$0 (ref (exact $0)) (ref.func $fimport$0)) + +;; CHECK-BIN-NODEBUG: (global $global$1 (ref (exact $0)) (ref.func $fimport$1)) + +;; CHECK-BIN-NODEBUG: (global $global$2 (ref (exact $0)) (ref.func $fimport$2)) + +;; CHECK-BIN-NODEBUG: (global $global$3 (ref (exact $0)) (ref.func $fimport$3)) + +;; CHECK-BIN-NODEBUG: (global $global$4 (ref (exact $0)) (ref.func $fimport$4)) + +;; CHECK-BIN-NODEBUG: (global $global$5 (ref (exact $0)) (ref.func $fimport$5)) diff --git a/test/spec/exact-func-import.wast b/test/spec/exact-func-import.wast new file mode 100644 index 00000000000..49330fc7b84 --- /dev/null +++ b/test/spec/exact-func-import.wast @@ -0,0 +1,268 @@ +;; TODO: Use the upstream version from the custom descriptors proposal. + +(module + (type $f (func)) + (import "" "" (func $1 (exact (type 0)))) + (import "" "" (func $2 (exact (type $f) (param) (result)))) + (import "" "" (func $3 (exact (type $f)))) + (import "" "" (func $4 (exact (type 1)))) ;; Implicitly defined next + (import "" "" (func $5 (exact (param i32) (result i64)))) + + (func $6 (import "" "") (exact (type 0))) + (func $7 (import "" "") (exact (type $f) (param) (result))) + (func $8 (import "" "") (exact (type $f))) + (func $9 (import "" "") (exact (type 2))) ;; Implicitly defined next + (func $10 (import "" "") (exact (param i64) (result i32))) + + (global (ref (exact $f)) (ref.func $1)) + (global (ref (exact $f)) (ref.func $2)) + (global (ref (exact $f)) (ref.func $3)) + (global (ref (exact 1)) (ref.func $4)) + (global (ref (exact 1)) (ref.func $5)) + (global (ref (exact $f)) (ref.func $6)) + (global (ref (exact $f)) (ref.func $7)) + (global (ref (exact $f)) (ref.func $8)) + (global (ref (exact 2)) (ref.func $9)) + (global (ref (exact 2)) (ref.func $10)) +) + +;; References to inexact imports are not exact. + +(assert_invalid + (module + (type $f (func)) + (import "" "" (func $1 (type $f))) + (global (ref (exact $f)) (ref.func $1)) + ) + "type mismatch" +) + +(assert_invalid + (module + (type $f (func)) + (import "" "" (func $1 (type $f))) + (elem declare func $1) + (func (result (ref (exact $f))) + (ref.func $1) + ) + ) + "type mismatch" +) + +;; Inexact imports can still be referenced inexactly, though. + +(module + (type $f (func)) + (import "" "" (func $1 (type $f))) + (global (ref $f) (ref.func $1)) + (func (result (ref $f)) + (ref.func $1) + ) +) + +;; Define a function and export it exactly. +(module $A + (type $f (func)) + (func (export "f") (type $f)) +) +(register "A") + +;; Import and re-export inexactly. +(module $B + (type $f (func)) + (func (export "f") (import "A" "f") (type $f)) +) +(register "B") + +;; The export from A is exact. +(module + (type $f (func)) + (import "A" "f" (func (exact (type $f)))) +) + +;; The export from B is _statically_ inexact, but instantiation checks +;; the dynamic types of imports, so this link still succeeds. +(module + (type $f (func)) + (import "B" "f" (func (exact (type $f)))) +) + +;; Even when the function is imported inexactly, it can still be cast to its +;; exact type. +(module + (type $f (func)) + (import "A" "f" (func $1 (type $f))) + (elem declare func $1) + (func (export "exact-test") (result i32) + (ref.test (ref (exact $f)) (ref.func $1)) + ) + (func (export "exact-cast") (result (ref (exact $f))) + (ref.cast (ref (exact $f)) (ref.func $1)) + ) + (func (export "exact-br-on-cast") (result funcref) + (br_on_cast 0 funcref (ref (exact $f)) (ref.func $1)) + (unreachable) + ) + (func (export "exact-br-on-cast-fail") (result funcref) + (block (result funcref) + (br_on_cast_fail 0 funcref (ref (exact $f)) (ref.func $1)) + (return) + ) + (unreachable) + ) +) + +(assert_return (invoke "exact-test") (i32.const 1)) +(assert_return (invoke "exact-cast") (ref.func)) +(assert_return (invoke "exact-br-on-cast") (ref.func)) +(assert_return (invoke "exact-br-on-cast-fail") (ref.func)) + +;; Define a function with a type that has a supertype. +(module $C + (type $super (sub (func))) + (type $sub (sub $super (func))) + (func (export "f") (type $sub)) + (func (export "g") (type $super)) +) +(register "C") + +;; TODO: Fix function type checking on linking. + +;; ;; We should not be able to import the function with the exact supertype. +;; (assert_unlinkable +;; (module +;; (type $super (sub (func))) +;; (type $sub (sub $super (func))) +;; (import "C" "f" (func (exact (type $super)))) +;; ) +;; "incompatible import type" +;; ) + +;; But we can still import it and re-export it inexactly with the supertype. +(module $D + (type $super (sub (func))) + (type $sub (sub $super (func))) + (import "C" "f" (func (type $super))) + (export "f" (func 0)) +) +(register "D") + +;; As before, we can still import the function with its real dynamic type. +(module + (type $super (sub (func))) + (type $sub (sub $super (func))) + (import "D" "f" (func (exact (type $sub)))) +) + +;; As before, even when the function is imported inexactly (this time with its +;; supertype), it can still be cast to its exact type. +(module + (type $super (sub (func))) + (type $sub (sub $super (func))) + (import "C" "f" (func $1 (type $super))) + (elem declare func $1) + (func (export "exact-test") (result i32) + (ref.test (ref (exact $sub)) (ref.func $1)) + ) + (func (export "exact-cast") (result (ref (exact $sub))) + (ref.cast (ref (exact $sub)) (ref.func $1)) + ) + (func (export "exact-br-on-cast") (result funcref) + (br_on_cast 0 funcref (ref (exact $sub)) (ref.func $1)) + (unreachable) + ) + (func (export "exact-br-on-cast-fail") (result funcref) + (block (result funcref) + (br_on_cast_fail 0 funcref (ref (exact $sub)) (ref.func $1)) + (return) + ) + (unreachable) + ) +) + +(assert_return (invoke "exact-test") (i32.const 1)) +(assert_return (invoke "exact-cast") (ref.func)) +(assert_return (invoke "exact-br-on-cast") (ref.func)) +(assert_return (invoke "exact-br-on-cast-fail") (ref.func)) + +;; But if we import a function whose dynamic type is the supertype, the same +;; casts will fail. +(module + (type $super (sub (func))) + (type $sub (sub $super (func))) + (import "C" "g" (func $1 (type $super))) + (elem declare func $1) + (func (export "exact-test") (result i32) + (ref.test (ref (exact $sub)) (ref.func $1)) + ) + (func (export "exact-cast") (result (ref (exact $sub))) + (ref.cast (ref (exact $sub)) (ref.func $1)) + ) + (func (export "exact-br-on-cast") (result funcref) + (br_on_cast 0 funcref (ref (exact $sub)) (ref.func $1)) + (unreachable) + ) + (func (export "exact-br-on-cast-fail") (result funcref) + (block (result funcref) + (br_on_cast_fail 0 funcref (ref (exact $sub)) (ref.func $1)) + (return) + ) + (unreachable) + ) +) + +(assert_return (invoke "exact-test") (i32.const 0)) +(assert_trap (invoke "exact-cast") "cast failure") +(assert_trap (invoke "exact-br-on-cast") "unreachable") +(assert_trap (invoke "exact-br-on-cast-fail") "unreachable") + + +;; Test the binary format + +;; Exact function imports use 0x20. +(module binary + "\00asm" "\01\00\00\00" + "\01" ;; Type section id + "\04" ;; Type section length + "\01" ;; Types vector length + "\60" ;; Function + "\00" ;; Number of params + "\00" ;; Number of results + "\02" ;; Import section id + "\05" ;; Import section length + "\01" ;; Import vector length + "\00" ;; Module name length + "\00" ;; Base name length + "\20" ;; Exact function + "\00" ;; Type index +) + +;; 0x20 is malformed in exports. +(assert_malformed + (module binary + "\00asm" "\01\00\00\00" + "\01" ;; Type section id + "\04" ;; Type section length + "\01" ;; Types vector length + "\60" ;; Function + "\00" ;; Number of params + "\00" ;; Number of results + "\03" ;; Function section id + "\02" ;; Function section length + "\01" ;; Function vector length + "\00" ;; Type index + "\07" ;; Export section id + "\04" ;; Export section length + "\01" ;; Export vector length + "\00" ;; Name length + "\20" ;; Exact func (malformed) + "\00" ;; Function index + "\0a" ;; Code section + "\04" ;; Code section length + "\01" ;; Code vector length + "\02" ;; Function length + "\00" ;; Type index + "\0b" ;; End + ) + "malformed export kind" +)