summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/binaryen-c.cpp2
-rw-r--r--src/ir/stack-utils.cpp23
-rw-r--r--src/ir/stack-utils.h14
-rw-r--r--src/passes/RemoveNonJSOps.cpp3
-rw-r--r--src/tools/tool-options.h9
-rw-r--r--src/tools/wasm-as.cpp2
-rw-r--r--src/tools/wasm-opt.cpp1
-rw-r--r--src/tools/wasm-shell.cpp4
-rw-r--r--src/tools/wasm2js.cpp5
-rw-r--r--src/wasm-io.h4
-rw-r--r--src/wasm-s-parser.h2
-rw-r--r--src/wasm.h3
-rw-r--r--src/wasm/wasm-io.cpp8
-rw-r--r--src/wasm/wasm-s-parser.cpp4
-rw-r--r--src/wasm/wasm-validator.cpp102
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(