summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/pass.h6
-rw-r--r--src/passes/CMakeLists.txt1
-rw-r--r--src/passes/Poppify.cpp500
-rw-r--r--src/passes/pass.cpp2
-rw-r--r--src/passes/passes.h1
-rw-r--r--src/wasm-type.h13
-rw-r--r--test/lit/passes/poppify-globals.wast55
-rw-r--r--test/lit/passes/poppify.wast434
8 files changed, 1010 insertions, 2 deletions
diff --git a/src/pass.h b/src/pass.h
index 7a830acb5..643d9a25a 100644
--- a/src/pass.h
+++ b/src/pass.h
@@ -171,6 +171,12 @@ struct PassRunner {
PassRunner(const PassRunner&) = delete;
PassRunner& operator=(const PassRunner&) = delete;
+ // But we can make it easy to create a nested runner
+ // TODO: Go through and use this in more places
+ explicit PassRunner(const PassRunner* runner)
+ : wasm(runner->wasm), allocator(runner->allocator),
+ options(runner->options), isNested(true) {}
+
void setDebug(bool debug) {
options.debug = debug;
// validate everything by default if debugging
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt
index a2cb9a50c..f6df9d1ea 100644
--- a/src/passes/CMakeLists.txt
+++ b/src/passes/CMakeLists.txt
@@ -49,6 +49,7 @@ set(passes_SOURCES
OptimizeAddedConstants.cpp
OptimizeInstructions.cpp
PickLoadSigns.cpp
+ Poppify.cpp
PostAssemblyScript.cpp
PostEmscripten.cpp
Precompute.cpp
diff --git a/src/passes/Poppify.cpp b/src/passes/Poppify.cpp
new file mode 100644
index 000000000..b0d07e60c
--- /dev/null
+++ b/src/passes/Poppify.cpp
@@ -0,0 +1,500 @@
+/*
+ * Copyright 2021 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Poppify.cpp - Transform Binaryen IR to Poppy IR.
+//
+// Poppy IR represents stack machine code using normal Binaryen IR types by
+// imposing the following constraints:
+//
+// 1. Function bodies and children of control flow (except If conditions) must
+// be blocks.
+//
+// 2. Blocks may have any Expressions as children. The sequence of instructions
+// in a block follows the same validation rules as in WebAssembly. That
+// means that any expression may have a concrete type, not just the final
+// expression in the block.
+//
+// 3. All other children must be Pops, which are used to determine the input
+// stack type of each instruction. Pops may not have `unreachable` type.
+// Pops must correspond to the results of previous expressions or block
+// inputs in a stack discipline.
+//
+// 4. Only control flow structures and instructions that have polymorphic
+// 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, this block could have unreachable type in Poppy IR but would
+// have to have type i32 in Binaryen IR:
+//
+// (block
+// (unreachable)
+// (i32.const 1)
+// )
+//
+// As an example of Poppification, the following Binaryen IR Function:
+//
+// (func $foo (result i32)
+// (i32.add
+// (i32.const 42)
+// (i32.const 5)
+// )
+// )
+//
+// would look like this in Poppy IR:
+//
+// (func $foo (result i32)
+// (block
+// (i32.const 42)
+// (i32.const 5)
+// (i32.add
+// (pop i32)
+// (pop i32)
+// )
+// )
+// )
+//
+// Notice that the sequence of instructions in the block is now identical to the
+// sequence of instructions in a WebAssembly binary. 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 unreachability rules.
+//
+
+#include "ir/names.h"
+#include "ir/properties.h"
+#include "ir/stack-utils.h"
+#include "ir/utils.h"
+#include "pass.h"
+#include "wasm-stack.h"
+
+namespace wasm {
+
+namespace {
+
+// Generate names for the elements of tuple globals
+Name getGlobalElem(Module* module, Name global, Index i) {
+ return Names::getValidGlobalName(
+ *module, std::string(global.c_str()) + '$' + std::to_string(i));
+}
+
+struct Poppifier : BinaryenIRWriter<Poppifier> {
+ // Collects instructions to be inserted into a block at a certain scope, as
+ // well as what kind of scope it is, which determines how the instructions are
+ // inserted.
+ struct Scope {
+ enum Kind { Func, Block, Loop, If, Else, Try, Catch } kind;
+ std::vector<Expression*> instrs;
+ Scope(Kind kind) : kind(kind) {}
+ };
+
+ Module* module;
+ Builder builder;
+ std::vector<Scope> scopeStack;
+
+ // Maps tuple locals to the new locals that will hold their elements
+ std::unordered_map<Index, std::vector<Index>> tupleVars;
+
+ // Records the scratch local to be used for tuple.extracts of each type
+ std::unordered_map<Type, Index> scratchLocals;
+
+ Poppifier(Function* func, Module* module);
+
+ Index getScratchLocal(Type type);
+
+ // Replace `expr`'s children with Pops of the correct type.
+ void poppify(Expression* expr);
+
+ // Pops the current scope off the scope stack and replaces `expr` with a block
+ // containing the instructions from that scope.
+ void patchScope(Expression*& expr);
+
+ // BinaryenIRWriter methods
+ void emit(Expression* curr);
+ void emitHeader() {}
+ void emitIfElse(If* curr);
+ void emitCatch(Try* curr, Index i);
+ void emitCatchAll(Try* curr);
+ void emitScopeEnd(Expression* curr);
+ void emitFunctionEnd();
+ void emitUnreachable();
+ void emitDebugLocation(Expression* curr) {}
+
+ // Tuple lowering methods
+ void emitTupleExtract(TupleExtract* curr);
+ void emitDrop(Drop* curr);
+ void emitLocalGet(LocalGet* curr);
+ void emitLocalSet(LocalSet* curr);
+ void emitGlobalGet(GlobalGet* curr);
+ void emitGlobalSet(GlobalSet* curr);
+};
+
+Poppifier::Poppifier(Function* func, Module* module)
+ : BinaryenIRWriter<Poppifier>(func), module(module), builder(*module) {
+ // Start with a scope to emit top-level instructions into
+ scopeStack.emplace_back(Scope::Func);
+
+ // Map each tuple local to a set of expanded locals
+ for (Index i = func->getNumParams(), end = func->getNumLocals(); i < end;
+ ++i) {
+ Type localType = func->getLocalType(i);
+ if (localType.isTuple()) {
+ auto& vars = tupleVars[i];
+ for (auto type : localType) {
+ vars.push_back(builder.addVar(func, type));
+ }
+ }
+ }
+}
+
+Index Poppifier::getScratchLocal(Type type) {
+ // If there is no scratch local for `type`, allocate a new one
+ auto insert = scratchLocals.insert({type, Index(-1)});
+ if (insert.second) {
+ insert.first->second = builder.addVar(func, type);
+ }
+ return insert.first->second;
+}
+
+void Poppifier::patchScope(Expression*& expr) {
+ auto scope = std::move(scopeStack.back());
+ auto& instrs = scope.instrs;
+ scopeStack.pop_back();
+ if (auto* block = expr->dynCast<Block>()) {
+ // Reuse blocks, but do not patch a block into itself, which would otherwise
+ // happen when emitting if/else or try/catch arms and function bodies.
+ if (instrs.size() == 0 || instrs[0] != block) {
+ block->list.set(instrs);
+ }
+ } else {
+ // Otherwise create a new block, even if we have just a single
+ // expression. We want blocks in every new scope rather than other
+ // instructions because Poppy IR optimizations only look at the children of
+ // blocks.
+ expr = builder.makeBlock(instrs, expr->type);
+ }
+}
+
+void Poppifier::emit(Expression* curr) {
+ // Control flow structures introduce new scopes. The instructions collected
+ // for the new scope will be patched back into the original Expression when
+ // the scope ends.
+ if (Properties::isControlFlowStructure(curr)) {
+ Scope::Kind kind;
+ switch (curr->_id) {
+ case Expression::BlockId: {
+ kind = Scope::Block;
+ break;
+ }
+ case Expression::LoopId: {
+ kind = Scope::Loop;
+ break;
+ }
+ case Expression::IfId:
+ // The condition has already been emitted
+ curr->cast<If>()->condition = builder.makePop(Type::i32);
+ kind = Scope::If;
+ break;
+ case Expression::TryId:
+ kind = Scope::Try;
+ break;
+ default:
+ WASM_UNREACHABLE("Unexpected control flow structure");
+ }
+ scopeStack.emplace_back(kind);
+ } else if (curr->is<Pop>()) {
+ // Turns into nothing when poppified
+ return;
+ } else if (curr->is<TupleMake>()) {
+ // Turns into nothing when poppified
+ return;
+ } else if (auto* extract = curr->dynCast<TupleExtract>()) {
+ emitTupleExtract(extract);
+ } else if (auto* drop = curr->dynCast<Drop>()) {
+ emitDrop(drop);
+ } else if (auto* get = curr->dynCast<LocalGet>()) {
+ emitLocalGet(get);
+ } else if (auto* set = curr->dynCast<LocalSet>()) {
+ emitLocalSet(set);
+ } else if (auto* get = curr->dynCast<GlobalGet>()) {
+ emitGlobalGet(get);
+ } else if (auto* set = curr->dynCast<GlobalSet>()) {
+ emitGlobalSet(set);
+ } else {
+ // Replace all children (which have already been emitted) with pops and emit
+ // the current instruction into the current scope.
+ poppify(curr);
+ scopeStack.back().instrs.push_back(curr);
+ }
+};
+
+void Poppifier::emitIfElse(If* curr) {
+ auto& scope = scopeStack.back();
+ assert(scope.kind == Scope::If);
+ patchScope(curr->ifTrue);
+ scopeStack.emplace_back(Scope::Else);
+}
+
+void Poppifier::emitCatch(Try* curr, Index i) {
+ auto& scope = scopeStack.back();
+ if (i == 0) {
+ assert(scope.kind == Scope::Try);
+ patchScope(curr->body);
+ } else {
+ assert(scope.kind == Scope::Catch);
+ patchScope(curr->catchBodies[i - 1]);
+ }
+ scopeStack.emplace_back(Scope::Catch);
+}
+
+void Poppifier::emitCatchAll(Try* curr) {
+ auto& scope = scopeStack.back();
+ if (curr->catchBodies.size() == 1) {
+ assert(scope.kind == Scope::Try);
+ patchScope(curr->body);
+ } else {
+ assert(scope.kind == Scope::Catch);
+ patchScope(curr->catchBodies[curr->catchBodies.size() - 2]);
+ }
+ scopeStack.emplace_back(Scope::Catch);
+}
+
+void Poppifier::emitScopeEnd(Expression* curr) {
+ switch (scopeStack.back().kind) {
+ case Scope::Block:
+ patchScope(curr);
+ break;
+ case Scope::Loop:
+ patchScope(curr->cast<Loop>()->body);
+ break;
+ case Scope::If:
+ patchScope(curr->cast<If>()->ifTrue);
+ break;
+ case Scope::Else:
+ patchScope(curr->cast<If>()->ifFalse);
+ break;
+ case Scope::Catch:
+ patchScope(curr->cast<Try>()->catchBodies.back());
+ break;
+ case Scope::Try:
+ WASM_UNREACHABLE("try without catch");
+ case Scope::Func:
+ WASM_UNREACHABLE("unexpected end of function");
+ }
+ scopeStack.back().instrs.push_back(curr);
+}
+
+void Poppifier::emitFunctionEnd() {
+ auto& scope = scopeStack.back();
+ assert(scope.kind == Scope::Func);
+ patchScope(func->body);
+}
+
+void Poppifier::emitUnreachable() {
+ // TODO: Try making this a nop
+ auto& instrs = scopeStack.back().instrs;
+ instrs.push_back(builder.makeUnreachable());
+}
+
+void Poppifier::emitTupleExtract(TupleExtract* curr) {
+ auto& instrs = scopeStack.back().instrs;
+ auto types = curr->tuple->type;
+ // Drop all the values after the one we want
+ for (size_t i = types.size() - 1; i > curr->index; --i) {
+ instrs.push_back(builder.makeDrop(builder.makePop(types[i])));
+ }
+ // If the extracted value is the only one left, we're done
+ if (curr->index == 0) {
+ return;
+ }
+ // Otherwise, save it to a scratch local and drop the other values
+ auto type = types[curr->index];
+ Index scratch = getScratchLocal(type);
+ instrs.push_back(builder.makeLocalSet(scratch, builder.makePop(type)));
+ for (size_t i = curr->index - 1; i != size_t(-1); --i) {
+ instrs.push_back(builder.makeDrop(builder.makePop(types[i])));
+ }
+ // Retrieve the saved value
+ instrs.push_back(builder.makeLocalGet(scratch, type));
+}
+
+void Poppifier::emitDrop(Drop* curr) {
+ auto& instrs = scopeStack.back().instrs;
+ if (curr->value->type.isTuple()) {
+ // Drop each element individually
+ auto types = curr->value->type;
+ for (auto it = types.rbegin(), end = types.rend(); it != end; ++it) {
+ instrs.push_back(builder.makeDrop(builder.makePop(*it)));
+ }
+ } else {
+ poppify(curr);
+ instrs.push_back(curr);
+ }
+}
+
+void Poppifier::emitLocalGet(LocalGet* curr) {
+ auto& instrs = scopeStack.back().instrs;
+ if (curr->type.isTuple()) {
+ auto types = func->getLocalType(curr->index);
+ const auto& elems = tupleVars[curr->index];
+ for (size_t i = 0; i < types.size(); ++i) {
+ instrs.push_back(builder.makeLocalGet(elems[i], types[i]));
+ }
+ } else {
+ instrs.push_back(curr);
+ }
+}
+
+void Poppifier::emitLocalSet(LocalSet* curr) {
+ auto& instrs = scopeStack.back().instrs;
+ if (curr->value->type.isTuple()) {
+ auto types = func->getLocalType(curr->index);
+ const auto& elems = tupleVars[curr->index];
+ // Add the unconditional sets
+ for (size_t i = types.size() - 1; i >= 1; --i) {
+ instrs.push_back(
+ builder.makeLocalSet(elems[i], builder.makePop(types[i])));
+ }
+ if (curr->isTee()) {
+ // Use a tee followed by gets to retrieve the tuple
+ instrs.push_back(
+ builder.makeLocalTee(elems[0], builder.makePop(types[0]), types[0]));
+ for (size_t i = 1; i < types.size(); ++i) {
+ instrs.push_back(builder.makeLocalGet(elems[i], types[i]));
+ }
+ } else {
+ // Otherwise just add the last set
+ instrs.push_back(
+ builder.makeLocalSet(elems[0], builder.makePop(types[0])));
+ }
+ } else {
+ poppify(curr);
+ instrs.push_back(curr);
+ }
+}
+
+void Poppifier::emitGlobalGet(GlobalGet* curr) {
+ auto& instrs = scopeStack.back().instrs;
+ if (curr->type.isTuple()) {
+ auto types = module->getGlobal(curr->name)->type;
+ for (Index i = 0; i < types.size(); ++i) {
+ instrs.push_back(
+ builder.makeGlobalGet(getGlobalElem(module, curr->name, i), types[i]));
+ }
+ } else {
+ instrs.push_back(curr);
+ }
+}
+
+void Poppifier::emitGlobalSet(GlobalSet* curr) {
+ auto& instrs = scopeStack.back().instrs;
+ if (curr->value->type.isTuple()) {
+ auto types = module->getGlobal(curr->name)->type;
+ for (Index i = types.size(); i > 0; --i) {
+ instrs.push_back(
+ builder.makeGlobalSet(getGlobalElem(module, curr->name, i - 1),
+ builder.makePop(types[i - 1])));
+ }
+ } else {
+ poppify(curr);
+ instrs.push_back(curr);
+ }
+}
+
+void Poppifier::poppify(Expression* expr) {
+ struct Poppifier : PostWalker<Poppifier> {
+ bool scanned = false;
+ Builder builder;
+ Poppifier(Builder& builder) : builder(builder) {}
+
+ static void scan(Poppifier* self, Expression** currp) {
+ if (!self->scanned) {
+ self->scanned = true;
+ PostWalker<Poppifier>::scan(self, currp);
+ } else {
+ *currp = self->builder.makePop((*currp)->type);
+ }
+ }
+ } poppifier(builder);
+ poppifier.walk(expr);
+}
+
+class PoppifyFunctionsPass : public Pass {
+ bool isFunctionParallel() override { return true; }
+ void
+ runOnFunction(PassRunner* runner, Module* module, Function* func) override {
+ if (func->profile != IRProfile::Poppy) {
+ Poppifier(func, module).write();
+ func->profile = IRProfile::Poppy;
+ }
+ }
+ Pass* create() override { return new PoppifyFunctionsPass; }
+};
+
+} // anonymous namespace
+
+class PoppifyPass : public Pass {
+ void run(PassRunner* runner, Module* module) {
+ PassRunner subRunner(runner);
+ subRunner.add(std::make_unique<PoppifyFunctionsPass>());
+ // TODO: Enable this once it handles Poppy blocks correctly
+ // subRunner.add(std::make_unique<ReFinalize>());
+ subRunner.run();
+ lowerTupleGlobals(module);
+ }
+
+ void lowerTupleGlobals(Module* module) {
+ Builder builder(*module);
+ std::vector<std::unique_ptr<Global>> newGlobals;
+ for (int g = module->globals.size() - 1; g >= 0; --g) {
+ const auto& global = *module->globals[g];
+ if (!global.type.isTuple()) {
+ continue;
+ }
+ assert(!global.imported());
+ for (Index i = 0; i < global.type.size(); ++i) {
+ Expression* init;
+ if (global.init == nullptr) {
+ init = nullptr;
+ } else if (auto* make = global.init->dynCast<TupleMake>()) {
+ init = make->operands[i];
+ } else if (auto* get = global.init->dynCast<GlobalGet>()) {
+ init = builder.makeGlobalGet(getGlobalElem(module, get->name, i),
+ global.type[i]);
+ } else {
+ WASM_UNREACHABLE("Unexpected tuple global initializer");
+ }
+ auto mutability =
+ global.mutable_ ? Builder::Mutable : Builder::Immutable;
+ newGlobals.emplace_back(
+ builder.makeGlobal(getGlobalElem(module, global.name, i),
+ global.type[i],
+ init,
+ mutability));
+ }
+ module->removeGlobal(global.name);
+ }
+ while (newGlobals.size()) {
+ module->addGlobal(std::move(newGlobals.back()));
+ newGlobals.pop_back();
+ }
+ module->updateMaps();
+ }
+};
+
+Pass* createPoppifyPass() { return new PoppifyPass; }
+
+} // namespace wasm
diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp
index 7d068a7dd..7734572f3 100644
--- a/src/passes/pass.cpp
+++ b/src/passes/pass.cpp
@@ -235,6 +235,8 @@ void PassRegistry::registerPasses() {
registerPass("pick-load-signs",
"pick load signs based on their uses",
createPickLoadSignsPass);
+ registerPass(
+ "poppify", "Tranform Binaryen IR into Poppy IR", createPoppifyPass);
registerPass("post-assemblyscript",
"eliminates redundant ARC patterns in AssemblyScript output",
createPostAssemblyScriptPass);
diff --git a/src/passes/passes.h b/src/passes/passes.h
index bc93cee05..c0d41ec2b 100644
--- a/src/passes/passes.h
+++ b/src/passes/passes.h
@@ -79,6 +79,7 @@ Pass* createOptimizeStackIRPass();
Pass* createPickLoadSignsPass();
Pass* createModAsyncifyAlwaysOnlyUnwindPass();
Pass* createModAsyncifyNeverUnwindPass();
+Pass* createPoppifyPass();
Pass* createPostAssemblyScriptPass();
Pass* createPostAssemblyScriptFinalizePass();
Pass* createPostEmscriptenPass();
diff --git a/src/wasm-type.h b/src/wasm-type.h
index f5c2fb273..fe1b832bc 100644
--- a/src/wasm-type.h
+++ b/src/wasm-type.h
@@ -215,8 +215,11 @@ public:
std::string toString() const;
- struct Iterator
- : std::iterator<std::random_access_iterator_tag, Type, long, Type*, Type&> {
+ struct Iterator : std::iterator<std::random_access_iterator_tag,
+ Type,
+ long,
+ Type*,
+ const Type&> {
const Type* parent;
size_t index;
Iterator(const Type* parent, size_t index) : parent(parent), index(index) {}
@@ -265,6 +268,12 @@ public:
Iterator begin() const { return Iterator(this, 0); }
Iterator end() const;
+ std::reverse_iterator<Iterator> rbegin() const {
+ return std::make_reverse_iterator(end());
+ }
+ std::reverse_iterator<Iterator> rend() const {
+ return std::make_reverse_iterator(begin());
+ }
size_t size() const { return end() - begin(); }
const Type& operator[](size_t i) const;
};
diff --git a/test/lit/passes/poppify-globals.wast b/test/lit/passes/poppify-globals.wast
new file mode 100644
index 000000000..ccdb025c0
--- /dev/null
+++ b/test/lit/passes/poppify-globals.wast
@@ -0,0 +1,55 @@
+;; TODO: enable validation
+;; RUN: wasm-opt %s --poppify --no-validation -all -S -o - | filecheck %s
+
+(module
+ ;; CHECK: (global $foo (mut i32) (i32.const 0))
+ (global $foo (mut i32) (i32.const 0))
+
+ ;; CHECK: (global $tuple$1 f64 (f64.const 0))
+ (global $tuple$1 f64 (f64.const 0)) ;; interfering name!
+
+ ;; CHECK: (global $tuple$2 (mut f32) (f32.const 2))
+ ;; CHECK: (global $tuple$1_0 (mut i64) (i64.const 1))
+ ;; CHECK: (global $tuple$0 (mut i32) (global.get $foo))
+ (global $tuple (mut (i32 i64 f32))
+ (tuple.make (global.get $foo) (i64.const 1) (f32.const 2))
+ )
+
+ ;; CHECK: (global $other-tuple$2 f32 (global.get $tuple$2))
+ ;; CHECK: (global $other-tuple$1 i64 (global.get $tuple$1_0))
+ ;; CHECK: (global $other-tuple$0 i32 (global.get $tuple$0))
+ (global $other-tuple (i32 i64 f32) (global.get $tuple))
+
+ ;; CHECK: (func $global-get-tuple
+ ;; CHECK-NEXT: (global.get $tuple$0)
+ ;; CHECK-NEXT: (global.get $tuple$1_0)
+ ;; CHECK-NEXT: (global.get $tuple$2)
+ ;; CHECK-NEXT: )
+ (func $global-get-tuple (result i32 i64 f32)
+ (global.get $tuple)
+ )
+
+ ;; CHECK: (func $global-set-tuple
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i64.const 1)
+ ;; CHECK-NEXT: (f32.const 2)
+ ;; CHECK-NEXT: (global.set $tuple$2
+ ;; CHECK-NEXT: (pop f32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $tuple$1_0
+ ;; CHECK-NEXT: (pop i64)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $tuple$0
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $global-set-tuple
+ (global.set $tuple
+ (tuple.make
+ (i32.const 0)
+ (i64.const 1)
+ (f32.const 2)
+ )
+ )
+ )
+)
diff --git a/test/lit/passes/poppify.wast b/test/lit/passes/poppify.wast
new file mode 100644
index 000000000..1b9ba1fb3
--- /dev/null
+++ b/test/lit/passes/poppify.wast
@@ -0,0 +1,434 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; TODO: enable validation
+;; RUN: wasm-opt %s --poppify --no-validation -all -S -o - | filecheck %s
+
+(module
+ (event $e (attr 0) (param i32))
+
+ ;; CHECK: (func $id (param $x i32) (result i32)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ (func $id (param $x i32) (result i32)
+ (local.get $x)
+ )
+
+ ;; CHECK: (func $add (param $x i32) (param $y i32) (result i32)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: (i32.add
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $add (param $x i32) (param $y i32) (result i32)
+ (i32.add
+ (local.get $x)
+ (local.get $y)
+ )
+ )
+
+ ;; CHECK: (func $expr-tree (result i32)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (i32.mul
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: (i32.const 3)
+ ;; CHECK-NEXT: (i32.mul
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.add
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $expr-tree (result i32)
+ (i32.add
+ (i32.mul
+ (i32.const 0)
+ (i32.const 1)
+ )
+ (i32.mul
+ (i32.const 2)
+ (i32.const 3)
+ )
+ )
+ )
+
+ ;; CHECK: (func $block (result i32)
+ ;; CHECK-NEXT: (block $block (result i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $block (result i32)
+ (block i32
+ (nop)
+ (i32.const 0)
+ )
+ )
+
+ ;; CHECK: (func $nested (result i32)
+ ;; CHECK-NEXT: (block $block (result i32)
+ ;; CHECK-NEXT: (block $block0 (result i32)
+ ;; CHECK-NEXT: (block $block1 (result i32)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $nested (result i32)
+ (block i32
+ (block i32
+ (block i32
+ (i32.const 0)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $child-blocks (result i32)
+ ;; CHECK-NEXT: (block $block (result i32)
+ ;; CHECK-NEXT: (block $block2 (result i32)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $block3 (result i32)
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.add
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $child-blocks (result i32)
+ (block (result i32)
+ (i32.add
+ (block (result i32)
+ (i32.const 0)
+ )
+ (block (result i32)
+ (i32.const 1)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $block-br (result i32)
+ ;; CHECK-NEXT: (block $l (result i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (br $l
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $block-br (result i32)
+ (block $l i32
+ (nop)
+ (br $l
+ (i32.const 0)
+ )
+ )
+ )
+
+ ;; CHECK: (func $loop
+ ;; CHECK-NEXT: (loop $l
+ ;; CHECK-NEXT: (br $l)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ (func $loop
+ (loop $l
+ (br $l)
+ )
+ )
+
+ ;; CHECK: (func $if
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $if
+ (if
+ (i32.const 0)
+ (nop)
+ )
+ )
+
+ ;; CHECK: (func $if-else (result i32)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (if (result i32)
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $if-else (result i32)
+ (if i32
+ (i32.const 0)
+ (i32.const 1)
+ (i32.const 2)
+ )
+ )
+
+ ;; CHECK: (func $try-catch (result i32)
+ ;; CHECK-NEXT: (try (result i32)
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (throw $e
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch $e
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-catch (result i32)
+ (try i32
+ (do
+ (throw $e
+ (i32.const 0)
+ )
+ )
+ (catch $e
+ (pop i32)
+ )
+ (catch_all
+ (i32.const 1)
+ )
+ )
+ )
+
+ ;; CHECK: (func $tuple (result i32 i64)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i64.const 1)
+ ;; CHECK-NEXT: )
+ (func $tuple (result i32 i64)
+ (tuple.make
+ (i32.const 0)
+ (i64.const 1)
+ )
+ )
+
+ ;; CHECK: (func $extract-first (result i32)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i64.const 1)
+ ;; CHECK-NEXT: (f32.const 2)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (pop f32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (pop i64)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $extract-first (result i32)
+ (tuple.extract 0
+ (tuple.make
+ (i32.const 0)
+ (i64.const 1)
+ (f32.const 2)
+ )
+ )
+ )
+
+ ;; CHECK: (func $extract-middle (result i64)
+ ;; CHECK-NEXT: (local $0 i64)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i64.const 1)
+ ;; CHECK-NEXT: (f32.const 2)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (pop f32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (pop i64)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ (func $extract-middle (result i64)
+ (tuple.extract 1
+ (tuple.make
+ (i32.const 0)
+ (i64.const 1)
+ (f32.const 2)
+ )
+ )
+ )
+
+ ;; CHECK: (func $extract-last (result f32)
+ ;; CHECK-NEXT: (local $0 f32)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i64.const 1)
+ ;; CHECK-NEXT: (f32.const 2)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (pop f32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (pop i64)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ (func $extract-last (result f32)
+ (tuple.extract 2
+ (tuple.make
+ (i32.const 0)
+ (i64.const 1)
+ (f32.const 2)
+ )
+ )
+ )
+
+ ;; CHECK: (func $drop
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $drop
+ (drop
+ (i32.const 0)
+ )
+ )
+
+ ;; CHECK: (func $drop-tuple
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i64.const 1)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (pop i64)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $drop-tuple
+ (drop
+ (tuple.make
+ (i32.const 0)
+ (i64.const 1)
+ )
+ )
+ )
+
+ ;; CHECK: (func $local-get-tuple (result i32 i64)
+ ;; CHECK-NEXT: (local $x (i32 i64))
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (local $2 i64)
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ (func $local-get-tuple (result i32 i64)
+ (local $x (i32 i64))
+ (local.get $x)
+ )
+
+ ;; CHECK: (func $local-set
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $local-set
+ (local $x i32)
+ (local.set $x
+ (i32.const 0)
+ )
+ )
+
+ ;; CHECK: (func $local-set-tuple
+ ;; CHECK-NEXT: (local $x (i32 i64))
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (local $2 i64)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i64.const 1)
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (pop i64)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $local-set-tuple
+ (local $x (i32 i64))
+ (local.set $x
+ (tuple.make
+ (i32.const 0)
+ (i64.const 1)
+ )
+ )
+ )
+
+ ;; CHECK: (func $local-tee-tuple (result i32 i64)
+ ;; CHECK-NEXT: (local $x (i32 i64))
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (local $2 i64)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i64.const 1)
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (pop i64)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.tee $1
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ (func $local-tee-tuple (result i32 i64)
+ (local $x (i32 i64))
+ (local.tee $x
+ (tuple.make
+ (i32.const 0)
+ (i64.const 1)
+ )
+ )
+ )
+
+ ;; CHECK: (func $break-tuple (result i32 i64)
+ ;; CHECK-NEXT: (block $l (result i32 i64)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i64.const 1)
+ ;; CHECK-NEXT: (br $l
+ ;; CHECK-NEXT: (pop i32 i64)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $break-tuple (result i32 i64)
+ (block $l (result i32 i64)
+ (br $l
+ (tuple.make
+ (i32.const 0)
+ (i64.const 1)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $return-tuple (result i32 i64)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i64.const 1)
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (pop i32 i64)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $return-tuple (result i32 i64)
+ (return
+ (tuple.make
+ (i32.const 0)
+ (i64.const 1)
+ )
+ )
+ )
+)