diff options
Diffstat (limited to 'src/wasm/wasm-validator.cpp')
-rw-r--r-- | src/wasm/wasm-validator.cpp | 306 |
1 files changed, 175 insertions, 131 deletions
diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 55e115d95..7bf51c5f7 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -21,6 +21,7 @@ #include "ir/branch-utils.h" #include "ir/features.h" +#include "ir/global-utils.h" #include "ir/module-utils.h" #include "ir/utils.h" #include "support/colors.h" @@ -181,6 +182,31 @@ struct ValidationInfo { fail(text, curr, func); } } + + // Type 'left' should be a subtype of 'right'. + bool shouldBeSubType(Type left, + Type right, + Expression* curr, + const char* text, + Function* func = nullptr) { + if (Type::isSubType(left, right)) { + return true; + } + fail(text, curr, func); + return false; + } + + // Type 'left' should be a subtype of 'right', or unreachable. + bool shouldBeSubTypeOrUnreachable(Type left, + Type right, + Expression* curr, + const char* text, + Function* func = nullptr) { + if (left == Type::unreachable) { + return true; + } + return shouldBeSubType(left, right, curr, text, func); + } }; struct FunctionValidator : public WalkerPass<PostWalker<FunctionValidator>> { @@ -210,7 +236,7 @@ struct FunctionValidator : public WalkerPass<PostWalker<FunctionValidator>> { std::unordered_map<Name, BreakInfo> breakInfos; - Type returnType = unreachable; // type used in returns + std::set<Type> returnTypes; // types used in returns // Binaryen IR requires that label names must be unique - IR generators must // ensure that @@ -287,6 +313,8 @@ public: void visitDrop(Drop* curr); void visitReturn(Return* curr); void visitHost(Host* curr); + void visitRefIsNull(RefIsNull* curr); + void visitRefFunc(RefFunc* curr); void visitTry(Try* curr); void visitThrow(Throw* curr); void visitRethrow(Rethrow* curr); @@ -327,6 +355,19 @@ private: return info.shouldBeIntOrUnreachable(ty, curr, text, getFunction()); } + bool + shouldBeSubType(Type left, Type right, Expression* curr, const char* text) { + return info.shouldBeSubType(left, right, curr, text, getFunction()); + } + + bool shouldBeSubTypeOrUnreachable(Type left, + Type right, + Expression* curr, + const char* text) { + return info.shouldBeSubTypeOrUnreachable( + left, right, curr, text, getFunction()); + } + void validateAlignment( size_t align, Type type, Index bytes, bool isAtomic, Expression* curr); void validateMemBytes(uint8_t bytes, Type type, Expression* curr); @@ -364,29 +405,23 @@ void FunctionValidator::visitBlock(Block* curr) { // none or unreachable means a poison value that we should ignore - if // consumed, it will error if (info.type.isConcrete() && curr->type.isConcrete()) { - shouldBeEqual( - curr->type, + shouldBeSubType( info.type, + curr->type, curr, "block+breaks must have right type if breaks return a value"); } if (curr->type.isConcrete() && info.arity && info.type != unreachable) { - shouldBeEqual(curr->type, - info.type, - curr, - "block+breaks must have right type if breaks have arity"); + shouldBeSubType( + info.type, + curr->type, + curr, + "block+breaks must have right type if breaks have arity"); } shouldBeTrue( info.arity != BreakInfo::PoisonArity, curr, "break arities must match"); if (curr->list.size() > 0) { auto last = curr->list.back()->type; - if (last.isConcrete() && info.type != unreachable) { - shouldBeEqual(last, - info.type, - curr, - "block+breaks must have right type if block ends with " - "a reachable value"); - } if (last == none) { shouldBeTrue(info.arity == Index(0), curr, @@ -420,9 +455,9 @@ void FunctionValidator::visitBlock(Block* curr) { "not flow out a value"); } else { if (backType.isConcrete()) { - shouldBeEqual( - curr->type, + shouldBeSubType( backType, + curr->type, curr, "block with value and last element with value must match types"); } else { @@ -457,6 +492,23 @@ void FunctionValidator::visitLoop(Loop* curr) { curr, "bad body for a loop that has no value"); } + + // When there are multiple instructions within a loop, they are wrapped in a + // Block internally, so visitBlock can take care of verification. Here we + // check cases when there is only one instruction in a Loop. + if (!curr->body->is<Block>()) { + if (!curr->type.isConcrete()) { + shouldBeFalse(curr->body->type.isConcrete(), + curr, + "if loop is not returning a value, final element should " + "not flow out a value"); + } else { + shouldBeSubTypeOrUnreachable(curr->body->type, + curr->type, + curr, + "loop with value and body must match types"); + } + } } void FunctionValidator::visitIf(If* curr) { @@ -476,12 +528,12 @@ void FunctionValidator::visitIf(If* curr) { } } else { if (curr->type != unreachable) { - shouldBeEqualOrFirstIsUnreachable( + shouldBeSubTypeOrUnreachable( curr->ifTrue->type, curr->type, curr, "returning if-else's true must have right type"); - shouldBeEqualOrFirstIsUnreachable( + shouldBeSubTypeOrUnreachable( curr->ifFalse->type, curr->type, curr, @@ -499,25 +551,16 @@ void FunctionValidator::visitIf(If* curr) { } } if (curr->ifTrue->type.isConcrete()) { - shouldBeEqual(curr->type, - curr->ifTrue->type, - curr, - "if type must match concrete ifTrue"); - shouldBeEqualOrFirstIsUnreachable(curr->ifFalse->type, - curr->ifTrue->type, - curr, - "other arm must match concrete ifTrue"); + shouldBeSubType(curr->ifTrue->type, + curr->type, + curr, + "if type must match concrete ifTrue"); } if (curr->ifFalse->type.isConcrete()) { - shouldBeEqual(curr->type, - curr->ifFalse->type, - curr, - "if type must match concrete ifFalse"); - shouldBeEqualOrFirstIsUnreachable( - curr->ifTrue->type, - curr->ifFalse->type, - curr, - "other arm must match concrete ifFalse"); + shouldBeSubType(curr->ifFalse->type, + curr->type, + curr, + "if type must match concrete ifFalse"); } } } @@ -545,13 +588,7 @@ void FunctionValidator::noteBreak(Name name, Type valueType, Expression* curr) { if (!info.hasBeenSet()) { info = BreakInfo(valueType, arity); } else { - if (info.type == unreachable) { - info.type = valueType; - } else if (valueType != unreachable) { - if (valueType != info.type) { - info.type = none; // a poison value that must not be consumed - } - } + info.type = Type::getLeastUpperBound(info.type, valueType); if (arity != info.arity) { info.arity = BreakInfo::PoisonArity; } @@ -600,10 +637,10 @@ void FunctionValidator::visitCall(Call* curr) { return; } for (size_t i = 0; i < curr->operands.size(); i++) { - if (!shouldBeEqualOrFirstIsUnreachable(curr->operands[i]->type, - params[i], - curr, - "call param types must match") && + if (!shouldBeSubTypeOrUnreachable(curr->operands[i]->type, + params[i], + curr, + "call param types must match") && !info.quiet) { getStream() << "(on argument " << i << ")\n"; } @@ -653,10 +690,10 @@ void FunctionValidator::visitCallIndirect(CallIndirect* curr) { return; } for (size_t i = 0; i < curr->operands.size(); i++) { - if (!shouldBeEqualOrFirstIsUnreachable(curr->operands[i]->type, - params[i], - curr, - "call param types must match") && + if (!shouldBeSubTypeOrUnreachable(curr->operands[i]->type, + params[i], + curr, + "call param types must match") && !info.quiet) { getStream() << "(on argument " << i << ")\n"; } @@ -723,10 +760,10 @@ void FunctionValidator::visitLocalSet(LocalSet* curr) { curr, "local.set type must be correct"); } - shouldBeEqual(curr->value->type, - getFunction()->getLocalType(curr->index), - curr, - "local.set's value type must be correct"); + shouldBeSubType(curr->value->type, + getFunction()->getLocalType(curr->index), + curr, + "local.set's value type must be correct"); } } } @@ -750,10 +787,10 @@ void FunctionValidator::visitGlobalSet(GlobalSet* curr) { "global.set name must be valid (and not an import; imports " "can't be modified)")) { shouldBeTrue(global->mutable_, curr, "global.set global must be mutable"); - shouldBeEqualOrFirstIsUnreachable(curr->value->type, - global->type, - curr, - "global.set value must have right type"); + shouldBeSubTypeOrUnreachable(curr->value->type, + global->type, + curr, + "global.set value must have right type"); } } @@ -1182,12 +1219,14 @@ void FunctionValidator::validateMemBytes(uint8_t bytes, shouldBeEqual( bytes, uint8_t(16), curr, "expected v128 operation to touch 16 bytes"); break; - case anyref: // anyref cannot be stored in memory - case exnref: // exnref cannot be stored in memory - case none: - WASM_UNREACHABLE("unexpected type"); case unreachable: break; + case funcref: + case anyref: + case nullref: + case exnref: + case none: + WASM_UNREACHABLE("unexpected type"); } } @@ -1616,15 +1655,18 @@ void FunctionValidator::visitSelect(Select* curr) { shouldBeUnequal(curr->ifTrue->type, none, curr, "select left must be valid"); shouldBeUnequal( curr->ifFalse->type, none, curr, "select right must be valid"); + shouldBeUnequal(curr->type, none, curr, "select type must be valid"); shouldBeTrue(curr->condition->type == unreachable || curr->condition->type == i32, curr, "select condition must be valid"); - if (curr->ifTrue->type != unreachable && curr->ifFalse->type != unreachable) { - shouldBeEqual(curr->ifTrue->type, - curr->ifFalse->type, - curr, - "select sides must be equal"); + if (curr->type != unreachable) { + shouldBeTrue(Type::isSubType(curr->ifTrue->type, curr->type), + curr, + "select's left expression must be subtype of select's type"); + shouldBeTrue(Type::isSubType(curr->ifFalse->type, curr->type), + curr, + "select's right expression must be subtype of select's type"); } } @@ -1636,16 +1678,7 @@ void FunctionValidator::visitDrop(Drop* curr) { } void FunctionValidator::visitReturn(Return* curr) { - if (curr->value) { - if (returnType == unreachable) { - returnType = curr->value->type; - } else if (curr->value->type != unreachable) { - shouldBeEqual( - curr->value->type, returnType, curr, "function results must match"); - } - } else { - returnType = none; - } + returnTypes.insert(curr->value ? curr->value->type : Type::none); } void FunctionValidator::visitHost(Host* curr) { @@ -1668,32 +1701,37 @@ void FunctionValidator::visitHost(Host* curr) { } } +void FunctionValidator::visitRefIsNull(RefIsNull* curr) { + shouldBeTrue(curr->value->type == Type::unreachable || + curr->value->type.isRef(), + curr->value, + "ref.is_null's argument should be a reference type"); +} + +void FunctionValidator::visitRefFunc(RefFunc* curr) { + auto* func = getModule()->getFunctionOrNull(curr->func); + shouldBeTrue(!!func, curr, "function argument of ref.func must exist"); +} + void FunctionValidator::visitTry(Try* curr) { if (curr->type != unreachable) { - shouldBeEqualOrFirstIsUnreachable( - curr->body->type, - curr->type, - curr->body, - "try's type does not match try body's type"); - shouldBeEqualOrFirstIsUnreachable( - curr->catchBody->type, - curr->type, - curr->catchBody, - "try's type does not match catch's body type"); - } - if (curr->body->type.isConcrete()) { - shouldBeEqualOrFirstIsUnreachable( - curr->catchBody->type, - curr->body->type, - curr->catchBody, - "try's body type must match catch's body type"); - } - if (curr->catchBody->type.isConcrete()) { - shouldBeEqualOrFirstIsUnreachable( - curr->body->type, - curr->catchBody->type, - curr->body, - "try's body type must match catch's body type"); + shouldBeSubTypeOrUnreachable(curr->body->type, + curr->type, + curr->body, + "try's type does not match try body's type"); + shouldBeSubTypeOrUnreachable(curr->catchBody->type, + curr->type, + curr->catchBody, + "try's type does not match catch's body type"); + } else { + shouldBeEqual(curr->body->type, + unreachable, + curr, + "unreachable try-catch must have unreachable try body"); + shouldBeEqual(curr->catchBody->type, + unreachable, + curr, + "unreachable try-catch must have unreachable catch body"); } } @@ -1727,10 +1765,10 @@ void FunctionValidator::visitThrow(Throw* curr) { void FunctionValidator::visitRethrow(Rethrow* curr) { shouldBeEqual( curr->type, unreachable, curr, "rethrow's type must be unreachable"); - shouldBeEqual(curr->exnref->type, - exnref, - curr->exnref, - "rethrow's argument must be exnref type"); + shouldBeSubType(curr->exnref->type, + Type::exnref, + curr->exnref, + "rethrow's argument must be exnref type or its subtype"); } void FunctionValidator::visitBrOnExn(BrOnExn* curr) { @@ -1740,10 +1778,11 @@ void FunctionValidator::visitBrOnExn(BrOnExn* curr) { curr, "br_on_exn's event params and event's params are different"); noteBreak(curr->name, curr->sent, curr); - shouldBeTrue(curr->exnref->type == unreachable || - curr->exnref->type == exnref, - curr, - "br_on_exn's argument must be unreachable or exnref type"); + shouldBeSubTypeOrUnreachable( + curr->exnref->type, + Type::exnref, + curr, + "br_on_exn's argument must be unreachable or exnref type or its subtype"); if (curr->exnref->type == unreachable) { shouldBeTrue(curr->type == unreachable, curr, @@ -1779,21 +1818,22 @@ void FunctionValidator::visitFunction(Function* curr) { "all used types should be allowed"); // if function has no result, it is ignored // if body is unreachable, it might be e.g. a return - if (curr->body->type != unreachable) { - shouldBeEqual(curr->sig.results, - curr->body->type, - curr->body, - "function body type must match, if function returns"); - } - if (returnType != unreachable) { - shouldBeEqual(curr->sig.results, - returnType, - curr->body, - "function result must match, if function has returns"); + shouldBeSubTypeOrUnreachable( + curr->body->type, + curr->sig.results, + curr->body, + "function body type must match, if function returns"); + for (Type returnType : returnTypes) { + shouldBeSubTypeOrUnreachable( + returnType, + curr->sig.results, + curr->body, + "function result must match, if function has returns"); } + shouldBeTrue( breakInfos.empty(), curr->body, "all named break targets must exist"); - returnType = unreachable; + returnTypes.clear(); labelNames.clear(); // validate optional local names std::set<Name> seen; @@ -1858,8 +1898,10 @@ void FunctionValidator::validateAlignment( case v128: case unreachable: break; - case anyref: // anyref cannot be stored in memory - case exnref: // exnref cannot be stored in memory + case funcref: + case anyref: + case nullref: + case exnref: case none: WASM_UNREACHABLE("invalid type"); } @@ -1890,7 +1932,8 @@ static void validateBinaryenIR(Module& wasm, ValidationInfo& info) { // // The block has an added type, not derived from the ast itself, so it // is ok for it to be either i32 or unreachable. - if (!(oldType.isConcrete() && newType == unreachable)) { + if (!Type::isSubType(newType, oldType) && + !(oldType.isConcrete() && newType == Type::unreachable)) { std::ostringstream ss; ss << "stale type found in " << scope << " on " << curr << "\n(marked as " << oldType << ", should be " << newType @@ -2011,13 +2054,14 @@ static void validateGlobals(Module& module, ValidationInfo& info) { info.shouldBeTrue( curr->init != nullptr, curr->name, "global init must be non-null"); assert(curr->init); - info.shouldBeTrue(curr->init->is<Const>() || curr->init->is<GlobalGet>(), + info.shouldBeTrue(GlobalUtils::canInitializeGlobal(curr->init), curr->name, "global init must be valid"); - if (!info.shouldBeEqual(curr->type, - curr->init->type, - curr->init, - "global init must have correct type") && + + if (!info.shouldBeSubType(curr->init->type, + curr->type, + curr->init, + "global init must have correct type") && !info.quiet) { info.getStream(nullptr) << "(on global " << curr->name << ")\n"; } @@ -2118,9 +2162,9 @@ static void validateEvents(Module& module, ValidationInfo& info) { curr->name, "Event type's result type should be none"); for (auto type : curr->sig.params.expand()) { - info.shouldBeTrue(type.isInteger() || type.isFloat(), + info.shouldBeTrue(type.isConcrete(), curr->name, - "Values in an event should have integer or float type"); + "Values in an event should have concrete types"); } } } |