diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/binaryen-c.cpp | 2 | ||||
-rw-r--r-- | src/ir/stack-utils.cpp | 23 | ||||
-rw-r--r-- | src/ir/stack-utils.h | 14 | ||||
-rw-r--r-- | src/passes/RemoveNonJSOps.cpp | 3 | ||||
-rw-r--r-- | src/tools/tool-options.h | 9 | ||||
-rw-r--r-- | src/tools/wasm-as.cpp | 2 | ||||
-rw-r--r-- | src/tools/wasm-opt.cpp | 1 | ||||
-rw-r--r-- | src/tools/wasm-shell.cpp | 4 | ||||
-rw-r--r-- | src/tools/wasm2js.cpp | 5 | ||||
-rw-r--r-- | src/wasm-io.h | 4 | ||||
-rw-r--r-- | src/wasm-s-parser.h | 2 | ||||
-rw-r--r-- | src/wasm.h | 3 | ||||
-rw-r--r-- | src/wasm/wasm-io.cpp | 8 | ||||
-rw-r--r-- | src/wasm/wasm-s-parser.cpp | 4 | ||||
-rw-r--r-- | src/wasm/wasm-validator.cpp | 102 |
15 files changed, 171 insertions, 15 deletions
diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index a3aa0cd94..9b2da350d 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -3422,7 +3422,7 @@ BinaryenModuleRef BinaryenModuleParse(const char* text) { try { SExpressionParser parser(const_cast<char*>(text)); Element& root = *parser.root; - SExpressionWasmBuilder builder(*wasm, *root[0]); + SExpressionWasmBuilder builder(*wasm, *root[0], IRProfile::Normal); } catch (ParseException& p) { p.dump(std::cerr); Fatal() << "error in parsing wasm text"; diff --git a/src/ir/stack-utils.cpp b/src/ir/stack-utils.cpp index ef871040b..bc0fd2eb4 100644 --- a/src/ir/stack-utils.cpp +++ b/src/ir/stack-utils.cpp @@ -15,6 +15,7 @@ */ #include "stack-utils.h" +#include "ir/properties.h" namespace wasm { @@ -30,6 +31,28 @@ void removeNops(Block* block) { block->list.resize(newIndex); } +bool mayBeUnreachable(Expression* expr) { + if (Properties::isControlFlowStructure(expr)) { + return true; + } + switch (expr->_id) { + case Expression::Id::BreakId: + return expr->cast<Break>()->condition == nullptr; + case Expression::Id::CallId: + return expr->cast<Call>()->isReturn; + case Expression::Id::CallIndirectId: + return expr->cast<CallIndirect>()->isReturn; + case Expression::Id::ReturnId: + case Expression::Id::SwitchId: + case Expression::Id::UnreachableId: + case Expression::Id::ThrowId: + case Expression::Id::RethrowId: + return true; + default: + return false; + } +} + } // namespace StackUtils StackSignature::StackSignature(Expression* expr) { diff --git a/src/ir/stack-utils.h b/src/ir/stack-utils.h index 89ec7d47e..fc23b6080 100644 --- a/src/ir/stack-utils.h +++ b/src/ir/stack-utils.h @@ -33,7 +33,10 @@ // stack type of each instruction. Pops may not have `unreachable` type. // // 4. Only control flow structures and instructions that have polymorphic -// unreachable behavior in WebAssembly may have unreachable type. +// unreachable behavior in WebAssembly may have unreachable type. Blocks may +// be unreachable when they are not branch targets and when they have an +// unreachable child. Note that this means a block may be unreachable even +// if it would otherwise have a concrete type, unlike in Binaryen IR. // // For example, the following Binaryen IR Function: // @@ -58,7 +61,10 @@ // ) // // Notice that the sequence of instructions in the block is now identical to the -// sequence of instructions in raw WebAssembly. +// sequence of instructions in raw WebAssembly. Also note that Poppy IR's +// validation rules are largely additional on top of the normal Binaryen IR +// validation rules, with the only exceptions being block body validation and +// block unreahchability rules. // #ifndef wasm_ir_stack_h @@ -75,6 +81,10 @@ namespace StackUtils { // Iterate through `block` and remove nops. void removeNops(Block* block); +// Whether `expr` may be unreachable in Poppy IR. True for control flow +// structures and polymorphic unreachable instructions. +bool mayBeUnreachable(Expression* expr); + } // namespace StackUtils // Stack signatures are like regular function signatures, but they are used to diff --git a/src/passes/RemoveNonJSOps.cpp b/src/passes/RemoveNonJSOps.cpp index e2054a5d2..abd3526c4 100644 --- a/src/passes/RemoveNonJSOps.cpp +++ b/src/passes/RemoveNonJSOps.cpp @@ -79,7 +79,8 @@ struct RemoveNonJSOpsPass : public WalkerPass<PostWalker<RemoveNonJSOpsPass>> { std::string input(IntrinsicsModuleWast); SExpressionParser parser(const_cast<char*>(input.c_str())); Element& root = *parser.root; - SExpressionWasmBuilder builder(intrinsicsModule, *root[0]); + SExpressionWasmBuilder builder( + intrinsicsModule, *root[0], IRProfile::Normal); std::set<Name> neededFunctions; diff --git a/src/tools/tool-options.h b/src/tools/tool-options.h index 6f0592a45..f04b95304 100644 --- a/src/tools/tool-options.h +++ b/src/tools/tool-options.h @@ -31,6 +31,7 @@ struct ToolOptions : public Options { PassOptions passOptions; bool quiet = false; + IRProfile profile = IRProfile::Normal; ToolOptions(const std::string& command, const std::string& description) : Options(command, description) { @@ -67,7 +68,13 @@ struct ToolOptions : public Options { "-q", "Emit less verbose output and hide trivial warnings.", Arguments::Zero, - [this](Options*, const std::string&) { quiet = true; }); + [this](Options*, const std::string&) { quiet = true; }) + .add( + "--experimental-poppy", + "", + "Parse wast files as Poppy IR for testing purposes.", + Arguments::Zero, + [this](Options*, const std::string&) { profile = IRProfile::Poppy; }); (*this) .addFeature(FeatureSet::SignExt, "sign extension operations") .addFeature(FeatureSet::Atomics, "atomic operations") diff --git a/src/tools/wasm-as.cpp b/src/tools/wasm-as.cpp index b4e1e18fa..3a907c4c3 100644 --- a/src/tools/wasm-as.cpp +++ b/src/tools/wasm-as.cpp @@ -110,7 +110,7 @@ int main(int argc, const char* argv[]) { if (options.debug) { std::cerr << "w-parsing..." << std::endl; } - SExpressionWasmBuilder builder(wasm, *root[0]); + SExpressionWasmBuilder builder(wasm, *root[0], options.profile); } catch (ParseException& p) { p.dump(std::cerr); Fatal() << "error in parsing input"; diff --git a/src/tools/wasm-opt.cpp b/src/tools/wasm-opt.cpp index 41302eac8..24ea9c31c 100644 --- a/src/tools/wasm-opt.cpp +++ b/src/tools/wasm-opt.cpp @@ -230,6 +230,7 @@ int main(int argc, const char* argv[]) { // asked to remove it. reader.setDWARF(options.passOptions.debugInfo && !willRemoveDebugInfo(options.passes)); + reader.setProfile(options.profile); try { reader.read(options.extra["infile"], wasm, inputSourceMapFilename); } catch (ParseException& p) { diff --git a/src/tools/wasm-shell.cpp b/src/tools/wasm-shell.cpp index 688815598..80229554b 100644 --- a/src/tools/wasm-shell.cpp +++ b/src/tools/wasm-shell.cpp @@ -146,7 +146,7 @@ static void run_asserts(Name moduleName, std::unique_ptr<SExpressionWasmBuilder> builder; try { builder = std::unique_ptr<SExpressionWasmBuilder>( - new SExpressionWasmBuilder(wasm, *curr[1])); + new SExpressionWasmBuilder(wasm, *curr[1], IRProfile::Normal)); } catch (const ParseException&) { invalid = true; } @@ -306,7 +306,7 @@ int main(int argc, const char* argv[]) { auto module = wasm::make_unique<Module>(); Name moduleName; auto builder = wasm::make_unique<SExpressionWasmBuilder>( - *module, *root[i], &moduleName); + *module, *root[i], IRProfile::Normal, &moduleName); builders[moduleName].swap(builder); modules[moduleName].swap(module); i++; diff --git a/src/tools/wasm2js.cpp b/src/tools/wasm2js.cpp index fb78336d0..524be4c5c 100644 --- a/src/tools/wasm2js.cpp +++ b/src/tools/wasm2js.cpp @@ -825,7 +825,7 @@ void AssertionEmitter::emit() { asmModule = Name(moduleNameS.str().c_str()); Module wasm; options.applyFeatures(wasm); - SExpressionWasmBuilder builder(wasm, e); + SExpressionWasmBuilder builder(wasm, e, options.profile); emitWasm(wasm, out, flags, options.passOptions, funcName); continue; } @@ -972,7 +972,8 @@ int main(int argc, const char* argv[]) { if (options.debug) { std::cerr << "w-parsing..." << std::endl; } - sexprBuilder = make_unique<SExpressionWasmBuilder>(wasm, *(*root)[0]); + sexprBuilder = + make_unique<SExpressionWasmBuilder>(wasm, *(*root)[0], options.profile); } } catch (ParseException& p) { p.dump(std::cerr); diff --git a/src/wasm-io.h b/src/wasm-io.h index 77d2506c4..fbc8947c6 100644 --- a/src/wasm-io.h +++ b/src/wasm-io.h @@ -33,6 +33,8 @@ public: // the binary, so that we can update DWARF sections later when writing. void setDWARF(bool DWARF_) { DWARF = DWARF_; } + void setProfile(IRProfile profile_) { profile = profile_; } + // read text void readText(std::string filename, Module& wasm); // read binary @@ -49,6 +51,8 @@ public: private: bool DWARF = false; + IRProfile profile = IRProfile::Normal; + void readStdin(Module& wasm, std::string sourceMapFilename); void readBinaryData(std::vector<char>& input, diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h index 76a60d7d9..d77062e33 100644 --- a/src/wasm-s-parser.h +++ b/src/wasm-s-parser.h @@ -111,6 +111,7 @@ private: class SExpressionWasmBuilder { Module& wasm; MixedArena& allocator; + IRProfile profile; std::vector<Signature> signatures; std::unordered_map<std::string, size_t> signatureIndices; std::vector<Name> functionNames; @@ -127,6 +128,7 @@ public: // Assumes control of and modifies the input. SExpressionWasmBuilder(Module& wasm, Element& module, + IRProfile profile, Name* moduleName = nullptr); private: diff --git a/src/wasm.h b/src/wasm.h index edcda4219..63a6fe79c 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -62,6 +62,8 @@ struct Address { } }; +enum class IRProfile { Normal, Poppy }; + // Operators enum UnaryOp { @@ -1262,6 +1264,7 @@ class Function : public Importable { public: Name name; Signature sig; // parameters and return value + IRProfile profile = IRProfile::Normal; std::vector<Type> vars; // non-param locals // The body of the function diff --git a/src/wasm/wasm-io.cpp b/src/wasm/wasm-io.cpp index 8fa714d9c..79687d469 100644 --- a/src/wasm/wasm-io.cpp +++ b/src/wasm/wasm-io.cpp @@ -33,16 +33,16 @@ namespace wasm { #define DEBUG_TYPE "writer" -static void readTextData(std::string& input, Module& wasm) { +static void readTextData(std::string& input, Module& wasm, IRProfile profile) { SExpressionParser parser(const_cast<char*>(input.c_str())); Element& root = *parser.root; - SExpressionWasmBuilder builder(wasm, *root[0]); + SExpressionWasmBuilder builder(wasm, *root[0], profile); } void ModuleReader::readText(std::string filename, Module& wasm) { BYN_TRACE("reading text from " << filename << "\n"); auto input(read_file<std::string>(filename, Flags::Text)); - readTextData(input, wasm); + readTextData(input, wasm, profile); } void ModuleReader::readBinaryData(std::vector<char>& input, @@ -113,7 +113,7 @@ void ModuleReader::readStdin(Module& wasm, std::string sourceMapFilename) { s.write(input.data(), input.size()); s << '\0'; std::string input_str = s.str(); - readTextData(input_str, wasm); + readTextData(input_str, wasm, profile); } } diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp index 0e4202a24..bac5f958c 100644 --- a/src/wasm/wasm-s-parser.cpp +++ b/src/wasm/wasm-s-parser.cpp @@ -314,8 +314,9 @@ Element* SExpressionParser::parseString() { SExpressionWasmBuilder::SExpressionWasmBuilder(Module& wasm, Element& module, + IRProfile profile, Name* moduleName) - : wasm(wasm), allocator(wasm.allocator) { + : wasm(wasm), allocator(wasm.allocator), profile(profile) { if (module.size() == 0) { throw ParseException("empty toplevel, expected module"); } @@ -795,6 +796,7 @@ void SExpressionWasmBuilder::parseFunction(Element& s, bool preParseImport) { // make a new function currFunction = std::unique_ptr<Function>(Builder(wasm).makeFunction( name, std::move(params), sig.results, std::move(vars))); + currFunction->profile = profile; // parse body Block* autoBlock = nullptr; // may need to add a block for the very top level diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index b236f9120..c6d679a24 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -22,6 +22,7 @@ #include "ir/features.h" #include "ir/global-utils.h" #include "ir/module-utils.h" +#include "ir/stack-utils.h" #include "ir/utils.h" #include "support/colors.h" #include "wasm-printing.h" @@ -246,6 +247,13 @@ struct FunctionValidator : public WalkerPass<PostWalker<FunctionValidator>> { public: // visitors + void validatePoppyExpression(Expression* curr); + + static void visitPoppyExpression(FunctionValidator* self, + Expression** currp) { + self->validatePoppyExpression(*currp); + } + static void visitPreBlock(FunctionValidator* self, Expression** currp) { auto* curr = (*currp)->cast<Block>(); if (curr->name.is()) { @@ -254,6 +262,8 @@ public: } void visitBlock(Block* curr); + void validateNormalBlockElements(Block* curr); + void validatePoppyBlockElements(Block* curr); static void visitPreLoop(FunctionValidator* self, Expression** currp) { auto* curr = (*currp)->cast<Loop>(); @@ -276,6 +286,11 @@ public: if (curr->is<Loop>()) { self->pushTask(visitPreLoop, currp); } + if (auto* func = self->getFunction()) { + if (func->profile == IRProfile::Poppy) { + self->pushTask(visitPoppyExpression, currp); + } + } } void noteBreak(Name name, Expression* value, Expression* curr); @@ -386,6 +401,39 @@ void FunctionValidator::noteLabelName(Name name) { "names in Binaryen IR must be unique - IR generators must ensure that"); } +void FunctionValidator::validatePoppyExpression(Expression* curr) { + if (curr->type == Type::unreachable) { + shouldBeTrue(StackUtils::mayBeUnreachable(curr), + curr, + "Only control flow structures and unreachable polymorphic" + " instructions may be unreachable in Poppy IR"); + } + if (Properties::isControlFlowStructure(curr)) { + // Check that control flow children (except If conditions) are blocks + if (auto* if_ = curr->dynCast<If>()) { + shouldBeTrue( + if_->condition->is<Pop>(), curr, "Expected condition to be a Pop"); + shouldBeTrue(if_->ifTrue->is<Block>(), + curr, + "Expected control flow child to be a block"); + shouldBeTrue(!if_->ifFalse || if_->ifFalse->is<Block>(), + curr, + "Expected control flow child to be a block"); + } else if (!curr->is<Block>()) { + for (auto* child : ChildIterator(curr)) { + shouldBeTrue(child->is<Block>(), + curr, + "Expected control flow child to be a block"); + } + } + } else { + // Check that all children are Pops + for (auto* child : ChildIterator(curr)) { + shouldBeTrue(child->is<Pop>(), curr, "Unexpected non-Pop child"); + } + } +} + void FunctionValidator::visitBlock(Block* curr) { if (!getModule()->features.hasMultivalue()) { shouldBeTrue(!curr->type.isTuple(), @@ -439,6 +487,17 @@ void FunctionValidator::visitBlock(Block* curr) { } breakInfos.erase(iter); } + switch (getFunction()->profile) { + case IRProfile::Normal: + validateNormalBlockElements(curr); + break; + case IRProfile::Poppy: + validatePoppyBlockElements(curr); + break; + } +} + +void FunctionValidator::validateNormalBlockElements(Block* curr) { if (curr->list.size() > 1) { for (Index i = 0; i < curr->list.size() - 1; i++) { if (!shouldBeTrue( @@ -482,6 +541,45 @@ void FunctionValidator::visitBlock(Block* curr) { } } +void FunctionValidator::validatePoppyBlockElements(Block* curr) { + StackSignature blockSig; + for (size_t i = 0; i < curr->list.size(); ++i) { + Expression* expr = curr->list[i]; + if (!shouldBeTrue( + !expr->is<Pop>(), expr, "Unexpected top-level pop in block")) { + return; + } + StackSignature sig(expr); + if (!shouldBeTrue(blockSig.composes(sig), + curr, + "block element has incompatible type") && + !info.quiet) { + getStream() << "(on index " << i << ":\n" + << expr << "\n), required: " << sig.params << ", available: "; + if (blockSig.unreachable) { + getStream() << "unreachable, "; + } + getStream() << blockSig.results << "\n"; + return; + } + blockSig += sig; + } + if (curr->type == Type::unreachable) { + shouldBeTrue(blockSig.unreachable, + curr, + "unreachable block should have unreachable element"); + } else { + if (!shouldBeTrue(blockSig.satisfies(Signature(Type::none, curr->type)), + curr, + "block contents should satisfy block type") && + !info.quiet) { + getStream() << "contents: " << blockSig.results + << (blockSig.unreachable ? " [unreachable]" : "") << "\n" + << "expected: " << curr->type << "\n"; + } + } +} + void FunctionValidator::visitLoop(Loop* curr) { if (curr->name.is()) { noteLabelName(curr->name); @@ -1988,6 +2086,10 @@ void FunctionValidator::visitFunction(Function* curr) { shouldBeTrue(features <= getModule()->features, curr, "all used types should be allowed"); + if (curr->profile == IRProfile::Poppy) { + shouldBeTrue( + curr->body->is<Block>(), curr->body, "Function body must be a block"); + } // if function has no result, it is ignored // if body is unreachable, it might be e.g. a return shouldBeSubTypeOrFirstIsUnreachable( |