diff options
Diffstat (limited to 'src/wasm/wasm-validator.cpp')
-rw-r--r-- | src/wasm/wasm-validator.cpp | 1091 |
1 files changed, 792 insertions, 299 deletions
diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 1e94ead0a..ea8e92047 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -19,29 +19,30 @@ #include <sstream> #include <unordered_set> -#include "wasm.h" -#include "wasm-printing.h" -#include "wasm-validator.h" -#include "ir/utils.h" #include "ir/branch-utils.h" #include "ir/features.h" #include "ir/module-utils.h" +#include "ir/utils.h" #include "support/colors.h" +#include "wasm-printing.h" +#include "wasm-validator.h" +#include "wasm.h" namespace wasm { // Print anything that can be streamed to an ostream template<typename T, - typename std::enable_if< - !std::is_base_of<Expression, typename std::remove_pointer<T>::type>::value - >::type* = nullptr> + typename std::enable_if<!std::is_base_of< + Expression, + typename std::remove_pointer<T>::type>::value>::type* = nullptr> inline std::ostream& printModuleComponent(T curr, std::ostream& stream) { stream << curr << std::endl; return stream; } // Extra overload for Expressions, to print type info too -inline std::ostream& printModuleComponent(Expression* curr, std::ostream& stream) { +inline std::ostream& printModuleComponent(Expression* curr, + std::ostream& stream) { WasmPrinter::printExpression(curr, stream, false, true) << std::endl; return stream; } @@ -60,14 +61,13 @@ struct ValidationInfo { std::mutex mutex; std::unordered_map<Function*, std::unique_ptr<std::ostringstream>> outputs; - ValidationInfo() { - valid.store(true); - } + ValidationInfo() { valid.store(true); } std::ostringstream& getStream(Function* func) { std::unique_lock<std::mutex> lock(mutex); auto iter = outputs.find(func); - if (iter != outputs.end()) return *(iter->second.get()); + if (iter != outputs.end()) + return *(iter->second.get()); auto& ret = outputs[func] = make_unique<std::ostringstream>(); return *ret.get(); } @@ -78,7 +78,8 @@ struct ValidationInfo { std::ostream& fail(S text, T curr, Function* func) { valid.store(false); auto& stream = getStream(func); - if (quiet) return stream; + if (quiet) + return stream; auto& ret = printFailureHeader(func); ret << text << ", on \n"; return printModuleComponent(curr, ret); @@ -86,7 +87,8 @@ struct ValidationInfo { std::ostream& printFailureHeader(Function* func) { auto& stream = getStream(func); - if (quiet) return stream; + if (quiet) + return stream; Colors::red(stream); if (func) { stream << "[wasm-validator error in function "; @@ -104,7 +106,10 @@ struct ValidationInfo { // checking utilities template<typename T> - bool shouldBeTrue(bool result, T curr, const char* text, Function* func = nullptr) { + bool shouldBeTrue(bool result, + T curr, + const char* text, + Function* func = nullptr) { if (!result) { fail("unexpected false: " + std::string(text), curr, func); return false; @@ -112,7 +117,10 @@ struct ValidationInfo { return result; } template<typename T> - bool shouldBeFalse(bool result, T curr, const char* text, Function* func = nullptr) { + bool shouldBeFalse(bool result, + T curr, + const char* text, + Function* func = nullptr) { if (result) { fail("unexpected true: " + std::string(text), curr, func); return false; @@ -121,7 +129,8 @@ struct ValidationInfo { } template<typename T, typename S> - bool shouldBeEqual(S left, S right, T curr, const char* text, Function* func = nullptr) { + bool shouldBeEqual( + S left, S right, T curr, const char* text, Function* func = nullptr) { if (left != right) { std::ostringstream ss; ss << left << " != " << right << ": " << text; @@ -132,7 +141,8 @@ struct ValidationInfo { } template<typename T, typename S> - bool shouldBeEqualOrFirstIsUnreachable(S left, S right, T curr, const char* text, Function* func = nullptr) { + bool shouldBeEqualOrFirstIsUnreachable( + S left, S right, T curr, const char* text, Function* func = nullptr) { if (left != unreachable && left != right) { std::ostringstream ss; ss << left << " != " << right << ": " << text; @@ -143,7 +153,8 @@ struct ValidationInfo { } template<typename T, typename S> - bool shouldBeUnequal(S left, S right, T curr, const char* text, Function* func = nullptr) { + bool shouldBeUnequal( + S left, S right, T curr, const char* text, Function* func = nullptr) { if (left == right) { std::ostringstream ss; ss << left << " == " << right << ": " << text; @@ -153,17 +164,20 @@ struct ValidationInfo { return true; } - void shouldBeIntOrUnreachable(Type ty, Expression* curr, const char* text, Function* func = nullptr) { + void shouldBeIntOrUnreachable(Type ty, + Expression* curr, + const char* text, + Function* func = nullptr) { switch (ty) { case i32: case i64: case unreachable: { break; } - default: fail(text, curr, func); + default: + fail(text, curr, func); } } - }; struct FunctionValidator : public WalkerPass<PostWalker<FunctionValidator>> { @@ -178,10 +192,7 @@ struct FunctionValidator : public WalkerPass<PostWalker<FunctionValidator>> { FunctionValidator(ValidationInfo* info) : info(*info) {} struct BreakInfo { - enum { - UnsetArity = Index(-1), - PoisonArity = Index(-2) - }; + enum { UnsetArity = Index(-1), PoisonArity = Index(-2) }; Type type; Index arity; @@ -198,7 +209,9 @@ struct FunctionValidator : public WalkerPass<PostWalker<FunctionValidator>> { Type returnType = unreachable; // type used in returns - std::unordered_set<Name> labelNames; // Binaryen IR requires that label names must be unique - IR generators must ensure that + // Binaryen IR requires that label names must be unique - IR generators must + // ensure that + std::unordered_set<Name> labelNames; void noteLabelName(Name name); @@ -207,14 +220,16 @@ public: static void visitPreBlock(FunctionValidator* self, Expression** currp) { auto* curr = (*currp)->cast<Block>(); - if (curr->name.is()) self->breakInfos[curr->name]; + if (curr->name.is()) + self->breakInfos[curr->name]; } void visitBlock(Block* curr); static void visitPreLoop(FunctionValidator* self, Expression** currp) { auto* curr = (*currp)->cast<Loop>(); - if (curr->name.is()) self->breakInfos[curr->name]; + if (curr->name.is()) + self->breakInfos[curr->name]; } void visitLoop(Loop* curr); @@ -225,8 +240,10 @@ public: PostWalker<FunctionValidator>::scan(self, currp); auto* curr = *currp; - if (curr->is<Block>()) self->pushTask(visitPreBlock, currp); - if (curr->is<Loop>()) self->pushTask(visitPreLoop, currp); + if (curr->is<Block>()) + self->pushTask(visitPreBlock, currp); + if (curr->is<Loop>()) + self->pushTask(visitPreLoop, currp); } void noteBreak(Name name, Expression* value, Expression* curr); @@ -264,9 +281,7 @@ public: // helpers private: - std::ostream& getStream() { - return info.getStream(getFunction()); - } + std::ostream& getStream() { return info.getStream(getFunction()); } template<typename T> bool shouldBeTrue(bool result, T curr, const char* text) { @@ -283,8 +298,10 @@ private: } template<typename T, typename S> - bool shouldBeEqualOrFirstIsUnreachable(S left, S right, T curr, const char* text) { - return info.shouldBeEqualOrFirstIsUnreachable(left, right, curr, text, getFunction()); + bool + shouldBeEqualOrFirstIsUnreachable(S left, S right, T curr, const char* text) { + return info.shouldBeEqualOrFirstIsUnreachable( + left, right, curr, text, getFunction()); } template<typename T, typename S> @@ -296,16 +313,20 @@ private: return info.shouldBeIntOrUnreachable(ty, curr, text, getFunction()); } - void validateAlignment(size_t align, Type type, Index bytes, bool isAtomic, - Expression* curr); + void validateAlignment( + size_t align, Type type, Index bytes, bool isAtomic, Expression* curr); void validateMemBytes(uint8_t bytes, Type type, Expression* curr); }; void FunctionValidator::noteLabelName(Name name) { - if (!name.is()) return; + if (!name.is()) + return; bool inserted; std::tie(std::ignore, inserted) = labelNames.insert(name); - shouldBeTrue(inserted, name, "names in Binaryen IR must be unique - IR generators must ensure that"); + shouldBeTrue( + inserted, + name, + "names in Binaryen IR must be unique - IR generators must ensure that"); } void FunctionValidator::visitBlock(Block* curr) { @@ -317,25 +338,46 @@ void FunctionValidator::visitBlock(Block* curr) { auto& info = iter->second; if (info.hasBeenSet()) { if (isConcreteType(curr->type)) { - shouldBeTrue(info.arity != 0, curr, "break arities must be > 0 if block has a value"); + shouldBeTrue(info.arity != 0, + curr, + "break arities must be > 0 if block has a value"); } else { - shouldBeTrue(info.arity == 0, curr, "break arities must be 0 if block has no value"); + shouldBeTrue(info.arity == 0, + curr, + "break arities must be 0 if block has no value"); } - // none or unreachable means a poison value that we should ignore - if consumed, it will error + // none or unreachable means a poison value that we should ignore - if + // consumed, it will error if (isConcreteType(info.type) && isConcreteType(curr->type)) { - shouldBeEqual(curr->type, info.type, curr, "block+breaks must have right type if breaks return a value"); + shouldBeEqual( + curr->type, + info.type, + curr, + "block+breaks must have right type if breaks return a value"); } - if (isConcreteType(curr->type) && info.arity && info.type != unreachable) { - shouldBeEqual(curr->type, info.type, curr, "block+breaks must have right type if breaks have arity"); + if (isConcreteType(curr->type) && info.arity && + info.type != unreachable) { + shouldBeEqual(curr->type, + info.type, + curr, + "block+breaks must have right type if breaks have arity"); } - shouldBeTrue(info.arity != BreakInfo::PoisonArity, curr, "break arities must match"); + shouldBeTrue( + info.arity != BreakInfo::PoisonArity, curr, "break arities must match"); if (curr->list.size() > 0) { auto last = curr->list.back()->type; if (isConcreteType(last) && info.type != unreachable) { - shouldBeEqual(last, info.type, curr, "block+breaks must have right type if block ends with a reachable value"); + 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, "if block ends with a none, breaks cannot send a value of any type"); + shouldBeTrue(info.arity == Index(0), + curr, + "if block ends with a none, breaks cannot send a value " + "of any type"); } } } @@ -343,25 +385,44 @@ void FunctionValidator::visitBlock(Block* curr) { } if (curr->list.size() > 1) { for (Index i = 0; i < curr->list.size() - 1; i++) { - if (!shouldBeTrue(!isConcreteType(curr->list[i]->type), curr, "non-final block elements returning a value must be drop()ed (binaryen's autodrop option might help you)") && !info.quiet) { - getStream() << "(on index " << i << ":\n" << curr->list[i] << "\n), type: " << curr->list[i]->type << "\n"; + if (!shouldBeTrue( + !isConcreteType(curr->list[i]->type), + curr, + "non-final block elements returning a value must be drop()ed " + "(binaryen's autodrop option might help you)") && + !info.quiet) { + getStream() << "(on index " << i << ":\n" + << curr->list[i] << "\n), type: " << curr->list[i]->type + << "\n"; } } } if (curr->list.size() > 0) { auto backType = curr->list.back()->type; if (!isConcreteType(curr->type)) { - shouldBeFalse(isConcreteType(backType), curr, "if block is not returning a value, final element should not flow out a value"); + shouldBeFalse(isConcreteType(backType), + curr, + "if block is not returning a value, final element should " + "not flow out a value"); } else { if (isConcreteType(backType)) { - shouldBeEqual(curr->type, backType, curr, "block with value and last element with value must match types"); + shouldBeEqual( + curr->type, + backType, + curr, + "block with value and last element with value must match types"); } else { - shouldBeUnequal(backType, none, curr, "block with value must not have last element that is none"); + shouldBeUnequal( + backType, + none, + curr, + "block with value must not have last element that is none"); } } } if (isConcreteType(curr->type)) { - shouldBeTrue(curr->list.size() > 0, curr, "block with a value must not be empty"); + shouldBeTrue( + curr->list.size() > 0, curr, "block with a value must not be empty"); } } @@ -372,44 +433,84 @@ void FunctionValidator::visitLoop(Loop* curr) { assert(iter != breakInfos.end()); // we set it ourselves auto& info = iter->second; if (info.hasBeenSet()) { - shouldBeEqual(info.arity, Index(0), curr, "breaks to a loop cannot pass a value"); + shouldBeEqual( + info.arity, Index(0), curr, "breaks to a loop cannot pass a value"); } breakInfos.erase(iter); } if (curr->type == none) { - shouldBeFalse(isConcreteType(curr->body->type), curr, "bad body for a loop that has no value"); + shouldBeFalse(isConcreteType(curr->body->type), + curr, + "bad body for a loop that has no value"); } } void FunctionValidator::visitIf(If* curr) { - shouldBeTrue(curr->condition->type == unreachable || curr->condition->type == i32, curr, "if condition must be valid"); + shouldBeTrue(curr->condition->type == unreachable || + curr->condition->type == i32, + curr, + "if condition must be valid"); if (!curr->ifFalse) { - shouldBeFalse(isConcreteType(curr->ifTrue->type), curr, "if without else must not return a value in body"); + shouldBeFalse(isConcreteType(curr->ifTrue->type), + curr, + "if without else must not return a value in body"); if (curr->condition->type != unreachable) { - shouldBeEqual(curr->type, none, curr, "if without else and reachable condition must be none"); + shouldBeEqual(curr->type, + none, + curr, + "if without else and reachable condition must be none"); } } else { if (curr->type != unreachable) { - shouldBeEqualOrFirstIsUnreachable(curr->ifTrue->type, curr->type, curr, "returning if-else's true must have right type"); - shouldBeEqualOrFirstIsUnreachable(curr->ifFalse->type, curr->type, curr, "returning if-else's false must have right type"); + shouldBeEqualOrFirstIsUnreachable( + curr->ifTrue->type, + curr->type, + curr, + "returning if-else's true must have right type"); + shouldBeEqualOrFirstIsUnreachable( + curr->ifFalse->type, + curr->type, + curr, + "returning if-else's false must have right type"); } else { if (curr->condition->type != unreachable) { - shouldBeEqual(curr->ifTrue->type, unreachable, curr, "unreachable if-else must have unreachable true"); - shouldBeEqual(curr->ifFalse->type, unreachable, curr, "unreachable if-else must have unreachable false"); + shouldBeEqual(curr->ifTrue->type, + unreachable, + curr, + "unreachable if-else must have unreachable true"); + shouldBeEqual(curr->ifFalse->type, + unreachable, + curr, + "unreachable if-else must have unreachable false"); } } if (isConcreteType(curr->ifTrue->type)) { - 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"); + 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"); } if (isConcreteType(curr->ifFalse->type)) { - 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"); + 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"); } } } -void FunctionValidator::noteBreak(Name name, Expression* value, Expression* curr) { +void FunctionValidator::noteBreak(Name name, + Expression* value, + Expression* curr) { Type valueType = none; Index arity = 0; if (value) { @@ -418,7 +519,9 @@ void FunctionValidator::noteBreak(Name name, Expression* value, Expression* curr arity = 1; } auto iter = breakInfos.find(name); - if (!shouldBeTrue(iter != breakInfos.end(), curr, "all break targets must be valid")) return; + if (!shouldBeTrue( + iter != breakInfos.end(), curr, "all break targets must be valid")) + return; auto& info = iter->second; if (!info.hasBeenSet()) { info = BreakInfo(valueType, arity); @@ -438,7 +541,10 @@ void FunctionValidator::noteBreak(Name name, Expression* value, Expression* curr void FunctionValidator::visitBreak(Break* curr) { noteBreak(curr->name, curr->value, curr); if (curr->condition) { - shouldBeTrue(curr->condition->type == unreachable || curr->condition->type == i32, curr, "break condition must be i32"); + shouldBeTrue(curr->condition->type == unreachable || + curr->condition->type == i32, + curr, + "break condition must be i32"); } } @@ -447,260 +553,500 @@ void FunctionValidator::visitSwitch(Switch* curr) { noteBreak(target, curr->value, curr); } noteBreak(curr->default_, curr->value, curr); - shouldBeTrue(curr->condition->type == unreachable || curr->condition->type == i32, curr, "br_table condition must be i32"); + shouldBeTrue(curr->condition->type == unreachable || + curr->condition->type == i32, + curr, + "br_table condition must be i32"); } void FunctionValidator::visitCall(Call* curr) { - if (!info.validateGlobally) return; + if (!info.validateGlobally) + return; auto* target = getModule()->getFunctionOrNull(curr->target); - if (!shouldBeTrue(!!target, curr, "call target must exist")) return; - if (!shouldBeTrue(curr->operands.size() == target->params.size(), curr, "call param number must match")) return; + if (!shouldBeTrue(!!target, curr, "call target must exist")) + return; + if (!shouldBeTrue(curr->operands.size() == target->params.size(), + curr, + "call param number must match")) + return; for (size_t i = 0; i < curr->operands.size(); i++) { - if (!shouldBeEqualOrFirstIsUnreachable(curr->operands[i]->type, target->params[i], curr, "call param types must match") && !info.quiet) { + if (!shouldBeEqualOrFirstIsUnreachable(curr->operands[i]->type, + target->params[i], + curr, + "call param types must match") && + !info.quiet) { getStream() << "(on argument " << i << ")\n"; } } } void FunctionValidator::visitCallIndirect(CallIndirect* curr) { - if (!info.validateGlobally) return; + if (!info.validateGlobally) + return; auto* type = getModule()->getFunctionTypeOrNull(curr->fullType); - if (!shouldBeTrue(!!type, curr, "call_indirect type must exist")) return; - shouldBeEqualOrFirstIsUnreachable(curr->target->type, i32, curr, "indirect call target must be an i32"); - if (!shouldBeTrue(curr->operands.size() == type->params.size(), curr, "call param number must match")) return; + if (!shouldBeTrue(!!type, curr, "call_indirect type must exist")) + return; + shouldBeEqualOrFirstIsUnreachable( + curr->target->type, i32, curr, "indirect call target must be an i32"); + if (!shouldBeTrue(curr->operands.size() == type->params.size(), + curr, + "call param number must match")) + return; for (size_t i = 0; i < curr->operands.size(); i++) { - if (!shouldBeEqualOrFirstIsUnreachable(curr->operands[i]->type, type->params[i], curr, "call param types must match") && !info.quiet) { + if (!shouldBeEqualOrFirstIsUnreachable(curr->operands[i]->type, + type->params[i], + curr, + "call param types must match") && + !info.quiet) { getStream() << "(on argument " << i << ")\n"; } } } void FunctionValidator::visitConst(Const* curr) { - shouldBeTrue(getFeatures(curr->type) <= getModule()->features, curr, + shouldBeTrue(getFeatures(curr->type) <= getModule()->features, + curr, "all used features should be allowed"); } void FunctionValidator::visitGetLocal(GetLocal* curr) { - shouldBeTrue(curr->index < getFunction()->getNumLocals(), curr, "local.get index must be small enough"); - shouldBeTrue(isConcreteType(curr->type), curr, "local.get must have a valid type - check what you provided when you constructed the node"); - shouldBeTrue(curr->type == getFunction()->getLocalType(curr->index), curr, "local.get must have proper type"); + shouldBeTrue(curr->index < getFunction()->getNumLocals(), + curr, + "local.get index must be small enough"); + shouldBeTrue(isConcreteType(curr->type), + curr, + "local.get must have a valid type - check what you provided " + "when you constructed the node"); + shouldBeTrue(curr->type == getFunction()->getLocalType(curr->index), + curr, + "local.get must have proper type"); } void FunctionValidator::visitSetLocal(SetLocal* curr) { - shouldBeTrue(curr->index < getFunction()->getNumLocals(), curr, "local.set index must be small enough"); + shouldBeTrue(curr->index < getFunction()->getNumLocals(), + curr, + "local.set index must be small enough"); if (curr->value->type != unreachable) { if (curr->type != none) { // tee is ok anyhow - shouldBeEqualOrFirstIsUnreachable(curr->value->type, curr->type, curr, "local.set type must be correct"); + shouldBeEqualOrFirstIsUnreachable( + curr->value->type, curr->type, curr, "local.set type must be correct"); } - shouldBeEqual(getFunction()->getLocalType(curr->index), curr->value->type, curr, "local.set type must match function"); + shouldBeEqual(getFunction()->getLocalType(curr->index), + curr->value->type, + curr, + "local.set type must match function"); } } void FunctionValidator::visitGetGlobal(GetGlobal* curr) { - if (!info.validateGlobally) return; - shouldBeTrue(getModule()->getGlobalOrNull(curr->name), curr, "global.get name must be valid"); + if (!info.validateGlobally) + return; + shouldBeTrue(getModule()->getGlobalOrNull(curr->name), + curr, + "global.get name must be valid"); } void FunctionValidator::visitSetGlobal(SetGlobal* curr) { - if (!info.validateGlobally) return; + if (!info.validateGlobally) + return; auto* global = getModule()->getGlobalOrNull(curr->name); - if (shouldBeTrue(global, curr, "global.set name must be valid (and not an import; imports can't be modified)")) { + if (shouldBeTrue(global, + 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"); + shouldBeEqualOrFirstIsUnreachable(curr->value->type, + global->type, + curr, + "global.set value must have right type"); } } void FunctionValidator::visitLoad(Load* curr) { - shouldBeTrue(getModule()->memory.exists, curr, "Memory operations require a memory"); + shouldBeTrue( + getModule()->memory.exists, curr, "Memory operations require a memory"); if (curr->isAtomic) { - shouldBeTrue(getModule()->features.hasAtomics(), curr, "Atomic operation (atomics are disabled)"); - shouldBeTrue(curr->type == i32 || curr->type == i64 || curr->type == unreachable, curr, "Atomic load should be i32 or i64"); - } - if (curr->type == v128) shouldBeTrue(getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); - shouldBeFalse(curr->isAtomic && !getModule()->memory.shared, curr, "Atomic operation with non-shared memory"); + shouldBeTrue(getModule()->features.hasAtomics(), + curr, + "Atomic operation (atomics are disabled)"); + shouldBeTrue(curr->type == i32 || curr->type == i64 || + curr->type == unreachable, + curr, + "Atomic load should be i32 or i64"); + } + if (curr->type == v128) + shouldBeTrue(getModule()->features.hasSIMD(), + curr, + "SIMD operation (SIMD is disabled)"); + shouldBeFalse(curr->isAtomic && !getModule()->memory.shared, + curr, + "Atomic operation with non-shared memory"); validateMemBytes(curr->bytes, curr->type, curr); validateAlignment(curr->align, curr->type, curr->bytes, curr->isAtomic, curr); - shouldBeEqualOrFirstIsUnreachable(curr->ptr->type, i32, curr, "load pointer type must be i32"); + shouldBeEqualOrFirstIsUnreachable( + curr->ptr->type, i32, curr, "load pointer type must be i32"); if (curr->isAtomic) { shouldBeFalse(curr->signed_, curr, "atomic loads must be unsigned"); - shouldBeIntOrUnreachable(curr->type, curr, "atomic loads must be of integers"); + shouldBeIntOrUnreachable( + curr->type, curr, "atomic loads must be of integers"); } } void FunctionValidator::visitStore(Store* curr) { - shouldBeTrue(getModule()->memory.exists, curr, "Memory operations require a memory"); + shouldBeTrue( + getModule()->memory.exists, curr, "Memory operations require a memory"); if (curr->isAtomic) { - shouldBeTrue(getModule()->features.hasAtomics(), curr, "Atomic operation (atomics are disabled)"); - shouldBeTrue(curr->valueType == i32 || curr->valueType == i64 || curr->valueType == unreachable, curr, "Atomic store should be i32 or i64"); - } - if (curr->valueType == v128) shouldBeTrue(getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); - shouldBeFalse(curr->isAtomic && !getModule()->memory.shared, curr, "Atomic operation with non-shared memory"); + shouldBeTrue(getModule()->features.hasAtomics(), + curr, + "Atomic operation (atomics are disabled)"); + shouldBeTrue(curr->valueType == i32 || curr->valueType == i64 || + curr->valueType == unreachable, + curr, + "Atomic store should be i32 or i64"); + } + if (curr->valueType == v128) + shouldBeTrue(getModule()->features.hasSIMD(), + curr, + "SIMD operation (SIMD is disabled)"); + shouldBeFalse(curr->isAtomic && !getModule()->memory.shared, + curr, + "Atomic operation with non-shared memory"); validateMemBytes(curr->bytes, curr->valueType, curr); - validateAlignment(curr->align, curr->valueType, curr->bytes, curr->isAtomic, curr); - shouldBeEqualOrFirstIsUnreachable(curr->ptr->type, i32, curr, "store pointer type must be i32"); - shouldBeUnequal(curr->value->type, none, curr, "store value type must not be none"); - shouldBeEqualOrFirstIsUnreachable(curr->value->type, curr->valueType, curr, "store value type must match"); + validateAlignment( + curr->align, curr->valueType, curr->bytes, curr->isAtomic, curr); + shouldBeEqualOrFirstIsUnreachable( + curr->ptr->type, i32, curr, "store pointer type must be i32"); + shouldBeUnequal( + curr->value->type, none, curr, "store value type must not be none"); + shouldBeEqualOrFirstIsUnreachable( + curr->value->type, curr->valueType, curr, "store value type must match"); if (curr->isAtomic) { - shouldBeIntOrUnreachable(curr->valueType, curr, "atomic stores must be of integers"); + shouldBeIntOrUnreachable( + curr->valueType, curr, "atomic stores must be of integers"); } } void FunctionValidator::visitAtomicRMW(AtomicRMW* curr) { - shouldBeTrue(getModule()->memory.exists, curr, "Memory operations require a memory"); - shouldBeTrue(getModule()->features.hasAtomics(), curr, "Atomic operation (atomics are disabled)"); - shouldBeFalse(!getModule()->memory.shared, curr, "Atomic operation with non-shared memory"); + shouldBeTrue( + getModule()->memory.exists, curr, "Memory operations require a memory"); + shouldBeTrue(getModule()->features.hasAtomics(), + curr, + "Atomic operation (atomics are disabled)"); + shouldBeFalse(!getModule()->memory.shared, + curr, + "Atomic operation with non-shared memory"); validateMemBytes(curr->bytes, curr->type, curr); - shouldBeEqualOrFirstIsUnreachable(curr->ptr->type, i32, curr, "AtomicRMW pointer type must be i32"); - shouldBeEqualOrFirstIsUnreachable(curr->type, curr->value->type, curr, "AtomicRMW result type must match operand"); - shouldBeIntOrUnreachable(curr->type, curr, "Atomic operations are only valid on int types"); + shouldBeEqualOrFirstIsUnreachable( + curr->ptr->type, i32, curr, "AtomicRMW pointer type must be i32"); + shouldBeEqualOrFirstIsUnreachable(curr->type, + curr->value->type, + curr, + "AtomicRMW result type must match operand"); + shouldBeIntOrUnreachable( + curr->type, curr, "Atomic operations are only valid on int types"); } void FunctionValidator::visitAtomicCmpxchg(AtomicCmpxchg* curr) { - shouldBeTrue(getModule()->memory.exists, curr, "Memory operations require a memory"); - shouldBeTrue(getModule()->features.hasAtomics(), curr, "Atomic operation (atomics are disabled)"); - shouldBeFalse(!getModule()->memory.shared, curr, "Atomic operation with non-shared memory"); + shouldBeTrue( + getModule()->memory.exists, curr, "Memory operations require a memory"); + shouldBeTrue(getModule()->features.hasAtomics(), + curr, + "Atomic operation (atomics are disabled)"); + shouldBeFalse(!getModule()->memory.shared, + curr, + "Atomic operation with non-shared memory"); validateMemBytes(curr->bytes, curr->type, curr); - shouldBeEqualOrFirstIsUnreachable(curr->ptr->type, i32, curr, "cmpxchg pointer type must be i32"); - if (curr->expected->type != unreachable && curr->replacement->type != unreachable) { - shouldBeEqual(curr->expected->type, curr->replacement->type, curr, "cmpxchg operand types must match"); - } - shouldBeEqualOrFirstIsUnreachable(curr->type, curr->expected->type, curr, "Cmpxchg result type must match expected"); - shouldBeEqualOrFirstIsUnreachable(curr->type, curr->replacement->type, curr, "Cmpxchg result type must match replacement"); - shouldBeIntOrUnreachable(curr->expected->type, curr, "Atomic operations are only valid on int types"); + shouldBeEqualOrFirstIsUnreachable( + curr->ptr->type, i32, curr, "cmpxchg pointer type must be i32"); + if (curr->expected->type != unreachable && + curr->replacement->type != unreachable) { + shouldBeEqual(curr->expected->type, + curr->replacement->type, + curr, + "cmpxchg operand types must match"); + } + shouldBeEqualOrFirstIsUnreachable(curr->type, + curr->expected->type, + curr, + "Cmpxchg result type must match expected"); + shouldBeEqualOrFirstIsUnreachable( + curr->type, + curr->replacement->type, + curr, + "Cmpxchg result type must match replacement"); + shouldBeIntOrUnreachable(curr->expected->type, + curr, + "Atomic operations are only valid on int types"); } void FunctionValidator::visitAtomicWait(AtomicWait* curr) { - shouldBeTrue(getModule()->memory.exists, curr, "Memory operations require a memory"); - shouldBeTrue(getModule()->features.hasAtomics(), curr, "Atomic operation (atomics are disabled)"); - shouldBeFalse(!getModule()->memory.shared, curr, "Atomic operation with non-shared memory"); - shouldBeEqualOrFirstIsUnreachable(curr->type, i32, curr, "AtomicWait must have type i32"); - shouldBeEqualOrFirstIsUnreachable(curr->ptr->type, i32, curr, "AtomicWait pointer type must be i32"); - shouldBeIntOrUnreachable(curr->expected->type, curr, "AtomicWait expected type must be int"); - shouldBeEqualOrFirstIsUnreachable(curr->expected->type, curr->expectedType, curr, "AtomicWait expected type must match operand"); - shouldBeEqualOrFirstIsUnreachable(curr->timeout->type, i64, curr, "AtomicWait timeout type must be i64"); + shouldBeTrue( + getModule()->memory.exists, curr, "Memory operations require a memory"); + shouldBeTrue(getModule()->features.hasAtomics(), + curr, + "Atomic operation (atomics are disabled)"); + shouldBeFalse(!getModule()->memory.shared, + curr, + "Atomic operation with non-shared memory"); + shouldBeEqualOrFirstIsUnreachable( + curr->type, i32, curr, "AtomicWait must have type i32"); + shouldBeEqualOrFirstIsUnreachable( + curr->ptr->type, i32, curr, "AtomicWait pointer type must be i32"); + shouldBeIntOrUnreachable( + curr->expected->type, curr, "AtomicWait expected type must be int"); + shouldBeEqualOrFirstIsUnreachable( + curr->expected->type, + curr->expectedType, + curr, + "AtomicWait expected type must match operand"); + shouldBeEqualOrFirstIsUnreachable( + curr->timeout->type, i64, curr, "AtomicWait timeout type must be i64"); } void FunctionValidator::visitAtomicNotify(AtomicNotify* curr) { - shouldBeTrue(getModule()->memory.exists, curr, "Memory operations require a memory"); - shouldBeTrue(getModule()->features.hasAtomics(), curr, "Atomic operation (atomics are disabled)"); - shouldBeFalse(!getModule()->memory.shared, curr, "Atomic operation with non-shared memory"); - shouldBeEqualOrFirstIsUnreachable(curr->type, i32, curr, "AtomicNotify must have type i32"); - shouldBeEqualOrFirstIsUnreachable(curr->ptr->type, i32, curr, "AtomicNotify pointer type must be i32"); - shouldBeEqualOrFirstIsUnreachable(curr->notifyCount->type, i32, curr, "AtomicNotify notifyCount type must be i32"); + shouldBeTrue( + getModule()->memory.exists, curr, "Memory operations require a memory"); + shouldBeTrue(getModule()->features.hasAtomics(), + curr, + "Atomic operation (atomics are disabled)"); + shouldBeFalse(!getModule()->memory.shared, + curr, + "Atomic operation with non-shared memory"); + shouldBeEqualOrFirstIsUnreachable( + curr->type, i32, curr, "AtomicNotify must have type i32"); + shouldBeEqualOrFirstIsUnreachable( + curr->ptr->type, i32, curr, "AtomicNotify pointer type must be i32"); + shouldBeEqualOrFirstIsUnreachable( + curr->notifyCount->type, + i32, + curr, + "AtomicNotify notifyCount type must be i32"); } void FunctionValidator::visitSIMDExtract(SIMDExtract* curr) { - shouldBeTrue(getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); - shouldBeEqualOrFirstIsUnreachable(curr->vec->type, v128, curr, "extract_lane must operate on a v128"); + shouldBeTrue( + getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); + shouldBeEqualOrFirstIsUnreachable( + curr->vec->type, v128, curr, "extract_lane must operate on a v128"); Type lane_t = none; size_t lanes = 0; switch (curr->op) { case ExtractLaneSVecI8x16: - case ExtractLaneUVecI8x16: lane_t = i32; lanes = 16; break; + case ExtractLaneUVecI8x16: + lane_t = i32; + lanes = 16; + break; case ExtractLaneSVecI16x8: - case ExtractLaneUVecI16x8: lane_t = i32; lanes = 8; break; - case ExtractLaneVecI32x4: lane_t = i32; lanes = 4; break; - case ExtractLaneVecI64x2: lane_t = i64; lanes = 2; break; - case ExtractLaneVecF32x4: lane_t = f32; lanes = 4; break; - case ExtractLaneVecF64x2: lane_t = f64; lanes = 2; break; + case ExtractLaneUVecI16x8: + lane_t = i32; + lanes = 8; + break; + case ExtractLaneVecI32x4: + lane_t = i32; + lanes = 4; + break; + case ExtractLaneVecI64x2: + lane_t = i64; + lanes = 2; + break; + case ExtractLaneVecF32x4: + lane_t = f32; + lanes = 4; + break; + case ExtractLaneVecF64x2: + lane_t = f64; + lanes = 2; + break; } - shouldBeEqualOrFirstIsUnreachable(curr->type, lane_t, curr, "extract_lane must have same type as vector lane"); + shouldBeEqualOrFirstIsUnreachable( + curr->type, + lane_t, + curr, + "extract_lane must have same type as vector lane"); shouldBeTrue(curr->index < lanes, curr, "invalid lane index"); } void FunctionValidator::visitSIMDReplace(SIMDReplace* curr) { - shouldBeTrue(getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); - shouldBeEqualOrFirstIsUnreachable(curr->type, v128, curr, "replace_lane must have type v128"); - shouldBeEqualOrFirstIsUnreachable(curr->vec->type, v128, curr, "replace_lane must operate on a v128"); + shouldBeTrue( + getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); + shouldBeEqualOrFirstIsUnreachable( + curr->type, v128, curr, "replace_lane must have type v128"); + shouldBeEqualOrFirstIsUnreachable( + curr->vec->type, v128, curr, "replace_lane must operate on a v128"); Type lane_t = none; size_t lanes = 0; switch (curr->op) { - case ReplaceLaneVecI8x16: lane_t = i32; lanes = 16; break; - case ReplaceLaneVecI16x8: lane_t = i32; lanes = 8; break; - case ReplaceLaneVecI32x4: lane_t = i32; lanes = 4; break; - case ReplaceLaneVecI64x2: lane_t = i64; lanes = 2; break; - case ReplaceLaneVecF32x4: lane_t = f32; lanes = 4; break; - case ReplaceLaneVecF64x2: lane_t = f64; lanes = 2; break; - } - shouldBeEqualOrFirstIsUnreachable(curr->value->type, lane_t, curr, "unexpected value type"); + case ReplaceLaneVecI8x16: + lane_t = i32; + lanes = 16; + break; + case ReplaceLaneVecI16x8: + lane_t = i32; + lanes = 8; + break; + case ReplaceLaneVecI32x4: + lane_t = i32; + lanes = 4; + break; + case ReplaceLaneVecI64x2: + lane_t = i64; + lanes = 2; + break; + case ReplaceLaneVecF32x4: + lane_t = f32; + lanes = 4; + break; + case ReplaceLaneVecF64x2: + lane_t = f64; + lanes = 2; + break; + } + shouldBeEqualOrFirstIsUnreachable( + curr->value->type, lane_t, curr, "unexpected value type"); shouldBeTrue(curr->index < lanes, curr, "invalid lane index"); } void FunctionValidator::visitSIMDShuffle(SIMDShuffle* curr) { - shouldBeTrue(getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); - shouldBeEqualOrFirstIsUnreachable(curr->type, v128, curr, "v128.shuffle must have type v128"); - shouldBeEqualOrFirstIsUnreachable(curr->left->type, v128, curr, "expected operand of type v128"); - shouldBeEqualOrFirstIsUnreachable(curr->right->type, v128, curr, "expected operand of type v128"); + shouldBeTrue( + getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); + shouldBeEqualOrFirstIsUnreachable( + curr->type, v128, curr, "v128.shuffle must have type v128"); + shouldBeEqualOrFirstIsUnreachable( + curr->left->type, v128, curr, "expected operand of type v128"); + shouldBeEqualOrFirstIsUnreachable( + curr->right->type, v128, curr, "expected operand of type v128"); for (uint8_t index : curr->mask) { shouldBeTrue(index < 32, curr, "Invalid lane index in mask"); } } void FunctionValidator::visitSIMDBitselect(SIMDBitselect* curr) { - shouldBeTrue(getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); - shouldBeEqualOrFirstIsUnreachable(curr->type, v128, curr, "v128.bitselect must have type v128"); - shouldBeEqualOrFirstIsUnreachable(curr->left->type, v128, curr, "expected operand of type v128"); - shouldBeEqualOrFirstIsUnreachable(curr->right->type, v128, curr, "expected operand of type v128"); - shouldBeEqualOrFirstIsUnreachable(curr->cond->type, v128, curr, "expected operand of type v128"); + shouldBeTrue( + getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); + shouldBeEqualOrFirstIsUnreachable( + curr->type, v128, curr, "v128.bitselect must have type v128"); + shouldBeEqualOrFirstIsUnreachable( + curr->left->type, v128, curr, "expected operand of type v128"); + shouldBeEqualOrFirstIsUnreachable( + curr->right->type, v128, curr, "expected operand of type v128"); + shouldBeEqualOrFirstIsUnreachable( + curr->cond->type, v128, curr, "expected operand of type v128"); } void FunctionValidator::visitSIMDShift(SIMDShift* curr) { - shouldBeTrue(getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); - shouldBeEqualOrFirstIsUnreachable(curr->type, v128, curr, "vector shift must have type v128"); - shouldBeEqualOrFirstIsUnreachable(curr->vec->type, v128, curr, "expected operand of type v128"); - shouldBeEqualOrFirstIsUnreachable(curr->shift->type, i32, curr, "expected shift amount to have type i32"); + shouldBeTrue( + getModule()->features.hasSIMD(), curr, "SIMD operation (SIMD is disabled)"); + shouldBeEqualOrFirstIsUnreachable( + curr->type, v128, curr, "vector shift must have type v128"); + shouldBeEqualOrFirstIsUnreachable( + curr->vec->type, v128, curr, "expected operand of type v128"); + shouldBeEqualOrFirstIsUnreachable( + curr->shift->type, i32, curr, "expected shift amount to have type i32"); } void FunctionValidator::visitMemoryInit(MemoryInit* curr) { - shouldBeTrue(getModule()->memory.exists, curr, "Memory operations require a memory"); - shouldBeTrue(getModule()->features.hasBulkMemory(), curr, "Bulk memory operation (bulk memory is disabled)"); - shouldBeEqualOrFirstIsUnreachable(curr->type, none, curr, "memory.init must have type none"); - shouldBeEqualOrFirstIsUnreachable(curr->dest->type, i32, curr, "memory.init dest must be an i32"); - shouldBeEqualOrFirstIsUnreachable(curr->offset->type, i32, curr, "memory.init offset must be an i32"); - shouldBeEqualOrFirstIsUnreachable(curr->size->type, i32, curr, "memory.init size must be an i32"); - shouldBeTrue(curr->segment < getModule()->memory.segments.size(), curr, "memory.init segment index out of bounds"); + shouldBeTrue( + getModule()->memory.exists, curr, "Memory operations require a memory"); + shouldBeTrue(getModule()->features.hasBulkMemory(), + curr, + "Bulk memory operation (bulk memory is disabled)"); + shouldBeEqualOrFirstIsUnreachable( + curr->type, none, curr, "memory.init must have type none"); + shouldBeEqualOrFirstIsUnreachable( + curr->dest->type, i32, curr, "memory.init dest must be an i32"); + shouldBeEqualOrFirstIsUnreachable( + curr->offset->type, i32, curr, "memory.init offset must be an i32"); + shouldBeEqualOrFirstIsUnreachable( + curr->size->type, i32, curr, "memory.init size must be an i32"); + shouldBeTrue(curr->segment < getModule()->memory.segments.size(), + curr, + "memory.init segment index out of bounds"); } void FunctionValidator::visitDataDrop(DataDrop* curr) { - shouldBeTrue(getModule()->memory.exists, curr, "Memory operations require a memory"); - shouldBeTrue(getModule()->features.hasBulkMemory(), curr, "Bulk memory operation (bulk memory is disabled)"); - shouldBeEqualOrFirstIsUnreachable(curr->type, none, curr, "data.drop must have type none"); - shouldBeTrue(curr->segment < getModule()->memory.segments.size(), curr, "data.drop segment index out of bounds"); + shouldBeTrue( + getModule()->memory.exists, curr, "Memory operations require a memory"); + shouldBeTrue(getModule()->features.hasBulkMemory(), + curr, + "Bulk memory operation (bulk memory is disabled)"); + shouldBeEqualOrFirstIsUnreachable( + curr->type, none, curr, "data.drop must have type none"); + shouldBeTrue(curr->segment < getModule()->memory.segments.size(), + curr, + "data.drop segment index out of bounds"); } void FunctionValidator::visitMemoryCopy(MemoryCopy* curr) { - shouldBeTrue(getModule()->memory.exists, curr, "Memory operations require a memory"); - shouldBeTrue(getModule()->features.hasBulkMemory(), curr, "Bulk memory operation (bulk memory is disabled)"); - shouldBeEqualOrFirstIsUnreachable(curr->type, none, curr, "memory.copy must have type none"); - shouldBeEqualOrFirstIsUnreachable(curr->dest->type, i32, curr, "memory.copy dest must be an i32"); - shouldBeEqualOrFirstIsUnreachable(curr->source->type, i32, curr, "memory.copy source must be an i32"); - shouldBeEqualOrFirstIsUnreachable(curr->size->type, i32, curr, "memory.copy size must be an i32"); + shouldBeTrue( + getModule()->memory.exists, curr, "Memory operations require a memory"); + shouldBeTrue(getModule()->features.hasBulkMemory(), + curr, + "Bulk memory operation (bulk memory is disabled)"); + shouldBeEqualOrFirstIsUnreachable( + curr->type, none, curr, "memory.copy must have type none"); + shouldBeEqualOrFirstIsUnreachable( + curr->dest->type, i32, curr, "memory.copy dest must be an i32"); + shouldBeEqualOrFirstIsUnreachable( + curr->source->type, i32, curr, "memory.copy source must be an i32"); + shouldBeEqualOrFirstIsUnreachable( + curr->size->type, i32, curr, "memory.copy size must be an i32"); } void FunctionValidator::visitMemoryFill(MemoryFill* curr) { - shouldBeTrue(getModule()->memory.exists, curr, "Memory operations require a memory"); - shouldBeTrue(getModule()->features.hasBulkMemory(), curr, "Bulk memory operation (bulk memory is disabled)"); - shouldBeEqualOrFirstIsUnreachable(curr->type, none, curr, "memory.fill must have type none"); - shouldBeEqualOrFirstIsUnreachable(curr->dest->type, i32, curr, "memory.fill dest must be an i32"); - shouldBeEqualOrFirstIsUnreachable(curr->value->type, i32, curr, "memory.fill value must be an i32"); - shouldBeEqualOrFirstIsUnreachable(curr->size->type, i32, curr, "memory.fill size must be an i32"); + shouldBeTrue( + getModule()->memory.exists, curr, "Memory operations require a memory"); + shouldBeTrue(getModule()->features.hasBulkMemory(), + curr, + "Bulk memory operation (bulk memory is disabled)"); + shouldBeEqualOrFirstIsUnreachable( + curr->type, none, curr, "memory.fill must have type none"); + shouldBeEqualOrFirstIsUnreachable( + curr->dest->type, i32, curr, "memory.fill dest must be an i32"); + shouldBeEqualOrFirstIsUnreachable( + curr->value->type, i32, curr, "memory.fill value must be an i32"); + shouldBeEqualOrFirstIsUnreachable( + curr->size->type, i32, curr, "memory.fill size must be an i32"); } -void FunctionValidator::validateMemBytes(uint8_t bytes, Type type, Expression* curr) { +void FunctionValidator::validateMemBytes(uint8_t bytes, + Type type, + Expression* curr) { switch (type) { - case i32: shouldBeTrue(bytes == 1 || bytes == 2 || bytes == 4, curr, "expected i32 operation to touch 1, 2, or 4 bytes"); break; - case i64: shouldBeTrue(bytes == 1 || bytes == 2 || bytes == 4 || bytes == 8, curr, "expected i64 operation to touch 1, 2, 4, or 8 bytes"); break; - case f32: shouldBeEqual(bytes, uint8_t(4), curr, "expected f32 operation to touch 4 bytes"); break; - case f64: shouldBeEqual(bytes, uint8_t(8), curr, "expected f64 operation to touch 8 bytes"); break; - case v128: shouldBeEqual(bytes, uint8_t(16), curr, "expected v128 operation to touch 16 bytes"); break; - case none: WASM_UNREACHABLE(); - case unreachable: break; + case i32: + shouldBeTrue(bytes == 1 || bytes == 2 || bytes == 4, + curr, + "expected i32 operation to touch 1, 2, or 4 bytes"); + break; + case i64: + shouldBeTrue(bytes == 1 || bytes == 2 || bytes == 4 || bytes == 8, + curr, + "expected i64 operation to touch 1, 2, 4, or 8 bytes"); + break; + case f32: + shouldBeEqual( + bytes, uint8_t(4), curr, "expected f32 operation to touch 4 bytes"); + break; + case f64: + shouldBeEqual( + bytes, uint8_t(8), curr, "expected f64 operation to touch 8 bytes"); + break; + case v128: + shouldBeEqual( + bytes, uint8_t(16), curr, "expected v128 operation to touch 16 bytes"); + break; + case none: + WASM_UNREACHABLE(); + case unreachable: + break; } } void FunctionValidator::visitBinary(Binary* curr) { if (curr->left->type != unreachable && curr->right->type != unreachable) { - shouldBeEqual(curr->left->type, curr->right->type, curr, "binary child types must be equal"); + shouldBeEqual(curr->left->type, + curr->right->type, + curr, + "binary child types must be equal"); } switch (curr->op) { case AddInt32: @@ -866,30 +1212,41 @@ void FunctionValidator::visitBinary(Binary* curr) { case MulVecF64x2: case DivVecF64x2: case MinVecF64x2: - case MaxVecF64x2: { - shouldBeEqualOrFirstIsUnreachable(curr->left->type, v128, curr, "v128 op"); - shouldBeEqualOrFirstIsUnreachable(curr->right->type, v128, curr, "v128 op"); + case MaxVecF64x2: { + shouldBeEqualOrFirstIsUnreachable( + curr->left->type, v128, curr, "v128 op"); + shouldBeEqualOrFirstIsUnreachable( + curr->right->type, v128, curr, "v128 op"); break; } - case InvalidBinary: WASM_UNREACHABLE(); + case InvalidBinary: + WASM_UNREACHABLE(); } - shouldBeTrue(Features::get(curr->op) <= getModule()->features, curr, "all used features should be allowed"); + shouldBeTrue(Features::get(curr->op) <= getModule()->features, + curr, + "all used features should be allowed"); } void FunctionValidator::visitUnary(Unary* curr) { - shouldBeUnequal(curr->value->type, none, curr, "unaries must not receive a none as their input"); - if (curr->value->type == unreachable) return; // nothing to check + shouldBeUnequal(curr->value->type, + none, + curr, + "unaries must not receive a none as their input"); + if (curr->value->type == unreachable) + return; // nothing to check switch (curr->op) { case ClzInt32: case CtzInt32: case PopcntInt32: { - shouldBeEqual(curr->value->type, i32, curr, "i32 unary value type must be correct"); + shouldBeEqual( + curr->value->type, i32, curr, "i32 unary value type must be correct"); break; } case ClzInt64: case CtzInt64: case PopcntInt64: { - shouldBeEqual(curr->value->type, i64, curr, "i64 unary value type must be correct"); + shouldBeEqual( + curr->value->type, i64, curr, "i64 unary value type must be correct"); break; } case NegFloat32: @@ -899,7 +1256,8 @@ void FunctionValidator::visitUnary(Unary* curr) { case TruncFloat32: case NearestFloat32: case SqrtFloat32: { - shouldBeEqual(curr->value->type, f32, curr, "f32 unary value type must be correct"); + shouldBeEqual( + curr->value->type, f32, curr, "f32 unary value type must be correct"); break; } case NegFloat64: @@ -909,7 +1267,8 @@ void FunctionValidator::visitUnary(Unary* curr) { case TruncFloat64: case NearestFloat64: case SqrtFloat64: { - shouldBeEqual(curr->value->type, f64, curr, "f64 unary value type must be correct"); + shouldBeEqual( + curr->value->type, f64, curr, "f64 unary value type must be correct"); break; } case EqZInt32: { @@ -924,13 +1283,15 @@ void FunctionValidator::visitUnary(Unary* curr) { case ExtendUInt32: case ExtendS8Int32: case ExtendS16Int32: { - shouldBeEqual(curr->value->type, i32, curr, "extend type must be correct"); + shouldBeEqual( + curr->value->type, i32, curr, "extend type must be correct"); break; } case ExtendS8Int64: case ExtendS16Int64: case ExtendS32Int64: { - shouldBeEqual(curr->value->type, i64, curr, "extend type must be correct"); + shouldBeEqual( + curr->value->type, i64, curr, "extend type must be correct"); break; } case WrapInt64: { @@ -966,41 +1327,49 @@ void FunctionValidator::visitUnary(Unary* curr) { break; } case ReinterpretFloat32: { - shouldBeEqual(curr->value->type, f32, curr, "reinterpret/f32 type must be correct"); + shouldBeEqual( + curr->value->type, f32, curr, "reinterpret/f32 type must be correct"); break; } case ReinterpretFloat64: { - shouldBeEqual(curr->value->type, f64, curr, "reinterpret/f64 type must be correct"); + shouldBeEqual( + curr->value->type, f64, curr, "reinterpret/f64 type must be correct"); break; } case ConvertUInt32ToFloat32: case ConvertUInt32ToFloat64: case ConvertSInt32ToFloat32: case ConvertSInt32ToFloat64: { - shouldBeEqual(curr->value->type, i32, curr, "convert type must be correct"); + shouldBeEqual( + curr->value->type, i32, curr, "convert type must be correct"); break; } case ConvertUInt64ToFloat32: case ConvertUInt64ToFloat64: case ConvertSInt64ToFloat32: case ConvertSInt64ToFloat64: { - shouldBeEqual(curr->value->type, i64, curr, "convert type must be correct"); + shouldBeEqual( + curr->value->type, i64, curr, "convert type must be correct"); break; } case PromoteFloat32: { - shouldBeEqual(curr->value->type, f32, curr, "promote type must be correct"); + shouldBeEqual( + curr->value->type, f32, curr, "promote type must be correct"); break; } case DemoteFloat64: { - shouldBeEqual(curr->value->type, f64, curr, "demote type must be correct"); + shouldBeEqual( + curr->value->type, f64, curr, "demote type must be correct"); break; } case ReinterpretInt32: { - shouldBeEqual(curr->value->type, i32, curr, "reinterpret/i32 type must be correct"); + shouldBeEqual( + curr->value->type, i32, curr, "reinterpret/i32 type must be correct"); break; } case ReinterpretInt64: { - shouldBeEqual(curr->value->type, i64, curr, "reinterpret/i64 type must be correct"); + shouldBeEqual( + curr->value->type, i64, curr, "reinterpret/i64 type must be correct"); break; } case SplatVecI8x16: @@ -1051,25 +1420,39 @@ void FunctionValidator::visitUnary(Unary* curr) { case AllTrueVecI32x4: case AnyTrueVecI64x2: case AllTrueVecI64x2: - shouldBeEqual(curr->type, i32, curr, "expected boolean reduction to have i32 type"); + shouldBeEqual( + curr->type, i32, curr, "expected boolean reduction to have i32 type"); shouldBeEqual(curr->value->type, v128, curr, "expected v128 operand"); break; - case InvalidUnary: WASM_UNREACHABLE(); + case InvalidUnary: + WASM_UNREACHABLE(); } - shouldBeTrue(Features::get(curr->op) <= getModule()->features, curr, "all used features should be allowed"); + shouldBeTrue(Features::get(curr->op) <= getModule()->features, + curr, + "all used features should be allowed"); } 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"); - shouldBeTrue(curr->condition->type == unreachable || curr->condition->type == i32, curr, "select condition must be valid"); + shouldBeUnequal( + curr->ifFalse->type, none, curr, "select right 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"); + shouldBeEqual(curr->ifTrue->type, + curr->ifFalse->type, + curr, + "select sides must be equal"); } } void FunctionValidator::visitDrop(Drop* curr) { - shouldBeTrue(isConcreteType(curr->value->type) || curr->value->type == unreachable, curr, "can only drop a valid value"); + shouldBeTrue(isConcreteType(curr->value->type) || + curr->value->type == unreachable, + curr, + "can only drop a valid value"); } void FunctionValidator::visitReturn(Return* curr) { @@ -1077,7 +1460,8 @@ void FunctionValidator::visitReturn(Return* curr) { if (returnType == unreachable) { returnType = curr->value->type; } else if (curr->value->type != unreachable) { - shouldBeEqual(curr->value->type, returnType, curr, "function results must match"); + shouldBeEqual( + curr->value->type, returnType, curr, "function results must match"); } } else { returnType = none; @@ -1087,11 +1471,18 @@ void FunctionValidator::visitReturn(Return* curr) { void FunctionValidator::visitHost(Host* curr) { switch (curr->op) { case GrowMemory: { - shouldBeEqual(curr->operands.size(), size_t(1), curr, "grow_memory must have 1 operand"); - shouldBeEqualOrFirstIsUnreachable(curr->operands[0]->type, i32, curr, "grow_memory must have i32 operand"); + shouldBeEqual(curr->operands.size(), + size_t(1), + curr, + "grow_memory must have 1 operand"); + shouldBeEqualOrFirstIsUnreachable(curr->operands[0]->type, + i32, + curr, + "grow_memory must have i32 operand"); break; } - case CurrentMemory: break; + case CurrentMemory: + break; } } @@ -1105,27 +1496,42 @@ void FunctionValidator::visitFunction(Function* curr) { typeFeatures |= getFeatures(type); shouldBeTrue(isConcreteType(type), curr, "vars must be concretely typed"); } - shouldBeTrue(typeFeatures <= getModule()->features, curr, + shouldBeTrue(typeFeatures <= getModule()->features, + 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->result, curr->body->type, curr->body, "function body type must match, if function returns"); + shouldBeEqual(curr->result, + curr->body->type, + curr->body, + "function body type must match, if function returns"); } if (returnType != unreachable) { - shouldBeEqual(curr->result, returnType, curr->body, "function result must match, if function has returns"); + shouldBeEqual(curr->result, + returnType, + curr->body, + "function result must match, if function has returns"); } - shouldBeTrue(breakInfos.empty(), curr->body, "all named break targets must exist"); + shouldBeTrue( + breakInfos.empty(), curr->body, "all named break targets must exist"); returnType = unreachable; labelNames.clear(); - // if function has a named type, it must match up with the function's params and result + // if function has a named type, it must match up with the function's params + // and result if (info.validateGlobally && curr->type.is()) { auto* ft = getModule()->getFunctionType(curr->type); - shouldBeTrue(ft->params == curr->params, curr->name, "function params must match its declared type"); - shouldBeTrue(ft->result == curr->result, curr->name, "function result must match its declared type"); + shouldBeTrue(ft->params == curr->params, + curr->name, + "function params must match its declared type"); + shouldBeTrue(ft->result == curr->result, + curr->name, + "function result must match its declared type"); } if (curr->imported()) { - shouldBeTrue(curr->type.is(), curr->name, "imported functions must have a function type"); + shouldBeTrue(curr->type.is(), + curr->name, + "imported functions must have a function type"); } // validate optional local names std::set<Name> seen; @@ -1136,9 +1542,11 @@ void FunctionValidator::visitFunction(Function* curr) { } static bool checkOffset(Expression* curr, Address add, Address max) { - if (curr->is<GetGlobal>()) return true; + if (curr->is<GetGlobal>()) + return true; auto* c = curr->dynCast<Const>(); - if (!c) return false; + if (!c) + return false; uint64_t raw = c->value.getInteger(); if (raw > std::numeric_limits<Address::address_t>::max()) { return false; @@ -1150,10 +1558,13 @@ static bool checkOffset(Expression* curr, Address add, Address max) { return offset + add <= max; } -void FunctionValidator::validateAlignment(size_t align, Type type, Index bytes, - bool isAtomic, Expression* curr) { +void FunctionValidator::validateAlignment( + size_t align, Type type, Index bytes, bool isAtomic, Expression* curr) { if (isAtomic) { - shouldBeEqual(align, (size_t)bytes, curr, "atomic accesses must have natural alignment"); + shouldBeEqual(align, + (size_t)bytes, + curr, + "atomic accesses must have natural alignment"); return; } switch (align) { @@ -1161,8 +1572,9 @@ void FunctionValidator::validateAlignment(size_t align, Type type, Index bytes, case 2: case 4: case 8: - case 16: break; - default:{ + case 16: + break; + default: { info.fail("bad alignment: " + std::to_string(align), curr, getFunction()); break; } @@ -1180,13 +1592,17 @@ void FunctionValidator::validateAlignment(size_t align, Type type, Index bytes, break; } case v128: - case unreachable: break; - case none: WASM_UNREACHABLE(); + case unreachable: + break; + case none: + WASM_UNREACHABLE(); } } static void validateBinaryenIR(Module& wasm, ValidationInfo& info) { - struct BinaryenIRValidator : public PostWalker<BinaryenIRValidator, UnifiedExpressionVisitor<BinaryenIRValidator>> { + struct BinaryenIRValidator + : public PostWalker<BinaryenIRValidator, + UnifiedExpressionVisitor<BinaryenIRValidator>> { ValidationInfo& info; std::unordered_set<Expression*> seen; @@ -1195,7 +1611,8 @@ static void validateBinaryenIR(Module& wasm, ValidationInfo& info) { void visitExpression(Expression* curr) { auto scope = getFunction() ? getFunction()->name : Name("(global scope)"); - // check if a node type is 'stale', i.e., we forgot to finalize() the node. + // check if a node type is 'stale', i.e., we forgot to finalize() the + // node. auto oldType = curr->type; ReFinalizeNode().visit(curr); auto newType = curr->type; @@ -1205,21 +1622,25 @@ static void validateBinaryenIR(Module& wasm, ValidationInfo& info) { // // (drop (block (result i32) (unreachable))) // - // The block has an added type, not derived from the ast itself, so it is - // ok for it to be either i32 or unreachable. + // 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 (!(isConcreteType(oldType) && newType == unreachable)) { std::ostringstream ss; - ss << "stale type found in " << scope << " on " << curr << "\n(marked as " << printType(oldType) << ", should be " << printType(newType) << ")\n"; + ss << "stale type found in " << scope << " on " << curr + << "\n(marked as " << printType(oldType) << ", should be " + << printType(newType) << ")\n"; info.fail(ss.str(), curr, getFunction()); } curr->type = oldType; } - // check if a node is a duplicate - expressions must not be seen more than once + // check if a node is a duplicate - expressions must not be seen more than + // once bool inserted; std::tie(std::ignore, inserted) = seen.insert(curr); if (!inserted) { std::ostringstream ss; - ss << "expression seen more than once in the tree in " << scope << " on " << curr << '\n'; + ss << "expression seen more than once in the tree in " << scope + << " on " << curr << '\n'; info.fail(ss.str(), curr, getFunction()); } } @@ -1234,15 +1655,22 @@ static void validateImports(Module& module, ValidationInfo& info) { ModuleUtils::iterImportedFunctions(module, [&](Function* curr) { if (info.validateWeb) { auto* functionType = module.getFunctionType(curr->type); - info.shouldBeUnequal(functionType->result, i64, curr->name, "Imported function must not have i64 return type"); + info.shouldBeUnequal(functionType->result, + i64, + curr->name, + "Imported function must not have i64 return type"); for (Type param : functionType->params) { - info.shouldBeUnequal(param, i64, curr->name, "Imported function must not have i64 parameters"); + info.shouldBeUnequal(param, + i64, + curr->name, + "Imported function must not have i64 parameters"); } } }); if (!module.features.hasMutableGlobals()) { ModuleUtils::iterImportedGlobals(module, [&](Global* curr) { - info.shouldBeFalse(curr->mutable_, curr->name, "Imported global cannot be mutable"); + info.shouldBeFalse( + curr->mutable_, curr->name, "Imported global cannot be mutable"); }); } } @@ -1252,14 +1680,23 @@ static void validateExports(Module& module, ValidationInfo& info) { if (curr->kind == ExternalKind::Function) { if (info.validateWeb) { Function* f = module.getFunction(curr->value); - info.shouldBeUnequal(f->result, i64, f->name, "Exported function must not have i64 return type"); + info.shouldBeUnequal(f->result, + i64, + f->name, + "Exported function must not have i64 return type"); for (auto param : f->params) { - info.shouldBeUnequal(param, i64, f->name, "Exported function must not have i64 parameters"); + info.shouldBeUnequal( + param, + i64, + f->name, + "Exported function must not have i64 parameters"); } } - } else if (curr->kind == ExternalKind::Global && !module.features.hasMutableGlobals()) { + } else if (curr->kind == ExternalKind::Global && + !module.features.hasMutableGlobals()) { if (Global* g = module.getGlobalOrNull(curr->value)) { - info.shouldBeFalse(g->mutable_, g->name, "Exported global cannot be mutable"); + info.shouldBeFalse( + g->mutable_, g->name, "Exported global cannot be mutable"); } } } @@ -1267,29 +1704,47 @@ static void validateExports(Module& module, ValidationInfo& info) { for (auto& exp : module.exports) { Name name = exp->value; if (exp->kind == ExternalKind::Function) { - info.shouldBeTrue(module.getFunctionOrNull(name), name, "module function exports must be found"); + info.shouldBeTrue(module.getFunctionOrNull(name), + name, + "module function exports must be found"); } else if (exp->kind == ExternalKind::Global) { - info.shouldBeTrue(module.getGlobalOrNull(name), name, "module global exports must be found"); + info.shouldBeTrue(module.getGlobalOrNull(name), + name, + "module global exports must be found"); } else if (exp->kind == ExternalKind::Table) { - info.shouldBeTrue(name == Name("0") || name == module.table.name, name, "module table exports must be found"); + info.shouldBeTrue(name == Name("0") || name == module.table.name, + name, + "module table exports must be found"); } else if (exp->kind == ExternalKind::Memory) { - info.shouldBeTrue(name == Name("0") || name == module.memory.name, name, "module memory exports must be found"); + info.shouldBeTrue(name == Name("0") || name == module.memory.name, + name, + "module memory exports must be found"); } else { WASM_UNREACHABLE(); } Name exportName = exp->name; - info.shouldBeFalse(exportNames.count(exportName) > 0, exportName, "module exports must be unique"); + info.shouldBeFalse(exportNames.count(exportName) > 0, + exportName, + "module exports must be unique"); exportNames.insert(exportName); } } static void validateGlobals(Module& module, ValidationInfo& info) { ModuleUtils::iterDefinedGlobals(module, [&](Global* curr) { - info.shouldBeTrue(getFeatures(curr->type) <= module.features, curr->name, + info.shouldBeTrue(getFeatures(curr->type) <= module.features, + curr->name, "all used types should be allowed"); - info.shouldBeTrue(curr->init != nullptr, curr->name, "global init must be non-null"); - info.shouldBeTrue(curr->init->is<Const>() || curr->init->is<GetGlobal>(), curr->name, "global init must be valid"); - if (!info.shouldBeEqual(curr->type, curr->init->type, curr->init, "global init must have correct type") && !info.quiet) { + info.shouldBeTrue( + curr->init != nullptr, curr->name, "global init must be non-null"); + info.shouldBeTrue(curr->init->is<Const>() || curr->init->is<GetGlobal>(), + curr->name, + "global init must be valid"); + if (!info.shouldBeEqual(curr->type, + curr->init->type, + curr->init, + "global init must have correct type") && + !info.quiet) { info.getStream(nullptr) << "(on global " << curr->name << ")\n"; } }); @@ -1297,30 +1752,57 @@ static void validateGlobals(Module& module, ValidationInfo& info) { static void validateMemory(Module& module, ValidationInfo& info) { auto& curr = module.memory; - info.shouldBeFalse(curr.initial > curr.max, "memory", "memory max >= initial"); - info.shouldBeTrue(curr.initial <= Memory::kMaxSize, "memory", "initial memory must be <= 4GB"); - info.shouldBeTrue(!curr.hasMax() || curr.max <= Memory::kMaxSize, "memory", "max memory must be <= 4GB, or unlimited"); - info.shouldBeTrue(!curr.shared || curr.hasMax(), "memory", "shared memory must have max size"); - if (curr.shared) info.shouldBeTrue(module.features.hasAtomics(), "memory", "memory is shared, but atomics are disabled"); + info.shouldBeFalse( + curr.initial > curr.max, "memory", "memory max >= initial"); + info.shouldBeTrue(curr.initial <= Memory::kMaxSize, + "memory", + "initial memory must be <= 4GB"); + info.shouldBeTrue(!curr.hasMax() || curr.max <= Memory::kMaxSize, + "memory", + "max memory must be <= 4GB, or unlimited"); + info.shouldBeTrue(!curr.shared || curr.hasMax(), + "memory", + "shared memory must have max size"); + if (curr.shared) + info.shouldBeTrue(module.features.hasAtomics(), + "memory", + "memory is shared, but atomics are disabled"); for (auto& segment : curr.segments) { Index size = segment.data.size(); if (segment.isPassive) { - info.shouldBeTrue(module.features.hasBulkMemory(), segment.offset, "nonzero segment flags (bulk memory is disabled)"); - info.shouldBeEqual(segment.offset, (Expression*)nullptr, segment.offset, "passive segment should not have an offset"); + info.shouldBeTrue(module.features.hasBulkMemory(), + segment.offset, + "nonzero segment flags (bulk memory is disabled)"); + info.shouldBeEqual(segment.offset, + (Expression*)nullptr, + segment.offset, + "passive segment should not have an offset"); } else { - if (!info.shouldBeEqual(segment.offset->type, i32, segment.offset, "segment offset should be i32")) continue; - info.shouldBeTrue(checkOffset(segment.offset, segment.data.size(), curr.initial * Memory::kPageSize), segment.offset, "segment offset should be reasonable"); + if (!info.shouldBeEqual(segment.offset->type, + i32, + segment.offset, + "segment offset should be i32")) + continue; + info.shouldBeTrue(checkOffset(segment.offset, + segment.data.size(), + curr.initial * Memory::kPageSize), + segment.offset, + "segment offset should be reasonable"); if (segment.offset->is<Const>()) { Index start = segment.offset->cast<Const>()->value.geti32(); Index end = start + size; - info.shouldBeTrue(end <= curr.initial * Memory::kPageSize, segment.data.size(), "segment size should fit in memory (end)"); + info.shouldBeTrue(end <= curr.initial * Memory::kPageSize, + segment.data.size(), + "segment size should fit in memory (end)"); } } // If the memory is imported we don't actually know its initial size. // Specifically wasm dll's import a zero sized memory which is perfectly // valid. if (!curr.imported()) { - info.shouldBeTrue(size <= curr.initial * Memory::kPageSize, segment.data.size(), "segment size should fit in memory (initial)"); + info.shouldBeTrue(size <= curr.initial * Memory::kPageSize, + segment.data.size(), + "segment size should fit in memory (initial)"); } } } @@ -1328,10 +1810,18 @@ static void validateMemory(Module& module, ValidationInfo& info) { static void validateTable(Module& module, ValidationInfo& info) { auto& curr = module.table; for (auto& segment : curr.segments) { - info.shouldBeEqual(segment.offset->type, i32, segment.offset, "segment offset should be i32"); - info.shouldBeTrue(checkOffset(segment.offset, segment.data.size(), module.table.initial * Table::kPageSize), segment.offset, "segment offset should be reasonable"); + info.shouldBeEqual(segment.offset->type, + i32, + segment.offset, + "segment offset should be i32"); + info.shouldBeTrue(checkOffset(segment.offset, + segment.data.size(), + module.table.initial * Table::kPageSize), + segment.offset, + "segment offset should be reasonable"); for (auto name : segment.data) { - info.shouldBeTrue(module.getFunctionOrNull(name), name, "segment name should be valid"); + info.shouldBeTrue( + module.getFunctionOrNull(name), name, "segment name should be valid"); } } } @@ -1340,16 +1830,19 @@ static void validateModule(Module& module, ValidationInfo& info) { // start if (module.start.is()) { auto func = module.getFunctionOrNull(module.start); - if (info.shouldBeTrue(func != nullptr, module.start, "start must be found")) { - info.shouldBeTrue(func->params.size() == 0, module.start, "start must have 0 params"); - info.shouldBeTrue(func->result == none, module.start, "start must not return a value"); + if (info.shouldBeTrue( + func != nullptr, module.start, "start must be found")) { + info.shouldBeTrue( + func->params.size() == 0, module.start, "start must have 0 params"); + info.shouldBeTrue( + func->result == none, module.start, "start must not return a value"); } } } -// TODO: If we want the validator to be part of libwasm rather than libpasses, then -// Using PassRunner::getPassDebug causes a circular dependence. We should fix that, -// perhaps by moving some of the pass infrastructure into libsupport. +// TODO: If we want the validator to be part of libwasm rather than libpasses, +// then Using PassRunner::getPassDebug causes a circular dependence. We should +// fix that, perhaps by moving some of the pass infrastructure into libsupport. bool WasmValidator::validate(Module& module, Flags flags) { ValidationInfo info; info.validateWeb = (flags & Web) != 0; |