From fe2b3afddc87090a9ab89d1532964487734ea861 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 3 Feb 2021 15:53:29 -0800 Subject: [PATCH 1/5] Poppify pass Adds a Poppify ("--poppify") pass for converting normal Binaryen IR to Poppy IR. Like the existing construction of Stacky IR, Poppify depends on the BinaryenIRWriter to drive the emitting of instructions in correct stack machine order. As instructions are "emitted," Poppify replaces their children with pops and collects them in a list. At the end of each scope, Poppify creates a block containing all the collected instructions for that scope and injects that block into the enclosing scope. All tuple globals and instructions dealing with tuples are also expanded to remove all tuples from the program. The validator currently fails to validate many valid Poppy IR patterns produced in the tests, but fixing that is left as follow-on work to keep this PR focused on the Poppify pass itself. For now the tests simply skip validation. --- src/pass.h | 6 + src/passes/CMakeLists.txt | 1 + src/passes/Poppify.cpp | 504 +++++++++++++++++++++++++++ src/passes/pass.cpp | 2 + src/passes/passes.h | 1 + src/wasm-type.h | 13 +- test/lit/passes/poppify-globals.wast | 52 +++ test/lit/passes/poppify.wast | 411 ++++++++++++++++++++++ 8 files changed, 988 insertions(+), 2 deletions(-) create mode 100644 src/passes/Poppify.cpp create mode 100644 test/lit/passes/poppify-globals.wast create mode 100644 test/lit/passes/poppify.wast diff --git a/src/pass.h b/src/pass.h index 7a830acb584..643d9a25a23 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 a2cb9a50c99..f6df9d1ea1c 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 00000000000..066d557a765 --- /dev/null +++ b/src/passes/Poppify.cpp @@ -0,0 +1,504 @@ +/* + * 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 except for Pops 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. +// +// 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 +// (i32.pop) +// (i32.pop) +// ) +// ) +// ) +// +// 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/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(Name global, Index i) { + return Name(std::string(global.c_str()) + '$' + std::to_string(i)); +} + +struct Poppifier : BinaryenIRWriter { + // 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 instrs; + Scope(Kind kind) : kind(kind) {} + }; + + Module* module; + Builder builder; + std::vector scopeStack; + + // Maps tuple locals to the new locals that will hold their elements + std::unordered_map> tupleVars; + + // Records the scratch local to be used for tuple.extracts of each type + std::unordered_map scratchLocals; + + // Maps labels to the types they receive. Used for typing breaks. + // TODO: is this necessary? + std::unordered_map labelTypes; + + Poppifier(Function* func, Module* module); + + Index getScratchLocal(Type type); + + // Replace `expr`'s children with Pops of the correct type. + void poppify(Expression* expr); + + // Replace `expr` with a block containing the instructions for the current + // scope and clear the current scope. + void patchInstrs(Expression*& expr); + + // BinaryIRWriter 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(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::patchInstrs(Expression*& expr) { + auto& instrs = scopeStack.back().instrs; + if (auto* block = expr->dynCast()) { + // 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); + } + instrs.clear(); +} + +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; + auto* block = curr->cast(); + if (block->name.is()) { + labelTypes[block->name] = block->type; + } + break; + } + case Expression::LoopId: { + kind = Scope::Loop; + auto* loop = curr->cast(); + if (loop->name.is()) { + // TODO: support loop parameters + labelTypes[loop->name] = Type::none; + } + break; + } + case Expression::IfId: + // The condition has already been emitted + curr->cast()->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()) { + // Turns into nothing when poppified + return; + } else if (curr->is()) { + // Turns into nothing when poppified + return; + } else if (auto* extract = curr->dynCast()) { + emitTupleExtract(extract); + } else if (auto* drop = curr->dynCast()) { + emitDrop(drop); + } else if (auto* get = curr->dynCast()) { + emitLocalGet(get); + } else if (auto* set = curr->dynCast()) { + emitLocalSet(set); + } else if (auto* get = curr->dynCast()) { + emitGlobalGet(get); + } else if (auto* set = curr->dynCast()) { + 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); + patchInstrs(curr->ifTrue); + scope.kind = Scope::Else; +} + +void Poppifier::emitCatch(Try* curr, Index i) { + auto& scope = scopeStack.back(); + if (i == 0) { + assert(scope.kind == Scope::Try); + patchInstrs(curr->body); + } else { + assert(scope.kind == Scope::Catch); + patchInstrs(curr->catchBodies[i - 1]); + } + scope.kind = Scope::Catch; +} + +void Poppifier::emitCatchAll(Try* curr) { + auto& scope = scopeStack.back(); + if (curr->catchBodies.size() == 1) { + assert(scope.kind == Scope::Try); + patchInstrs(curr->body); + } else { + assert(scope.kind == Scope::Catch); + patchInstrs(curr->catchBodies[curr->catchBodies.size() - 2]); + } + scope.kind = Scope::Catch; +} + +void Poppifier::emitScopeEnd(Expression* curr) { + auto& scope = scopeStack.back(); + switch (scope.kind) { + case Scope::Block: + patchInstrs(curr); + break; + case Scope::Loop: + patchInstrs(curr->cast()->body); + break; + case Scope::If: + patchInstrs(curr->cast()->ifTrue); + break; + case Scope::Else: + patchInstrs(curr->cast()->ifFalse); + break; + case Scope::Catch: + patchInstrs(curr->cast()->catchBodies.back()); + break; + case Scope::Try: + WASM_UNREACHABLE("try without catch"); + case Scope::Func: + WASM_UNREACHABLE("unexpected end of function"); + } + scopeStack.pop_back(); + scopeStack.back().instrs.push_back(curr); +} + +void Poppifier::emitFunctionEnd() { + auto& scope = scopeStack.back(); + assert(scope.kind == Scope::Func); + patchInstrs(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(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(curr->name, i - 1), + builder.makePop(types[i - 1]))); + } + } else { + poppify(curr); + instrs.push_back(curr); + } +} + +void Poppifier::poppify(Expression* expr) { + struct Poppifier : PostWalker { + bool scanned = false; + Builder builder; + Poppifier(Builder& builder) : builder(builder) {} + + static void scan(Poppifier* self, Expression** currp) { + if (!self->scanned) { + self->scanned = true; + PostWalker::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()); + subRunner.add(std::make_unique()); + subRunner.run(); + lowerTupleGlobals(module); + } + + void lowerTupleGlobals(Module* module) { + Builder builder(*module); + std::vector> newGlobals; + for (int g = module->globals.size() - 1; g >= 0; --g) { + const auto& global = *module->globals[g]; + if (global.type.isTuple()) { + 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()) { + init = make->operands[i]; + } else if (auto* get = global.init->dynCast()) { + init = builder.makeGlobalGet(getGlobalElem(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(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 7d068a7dd56..7734572f3f3 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 bc93cee0501..c0d41ec2b3c 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 f5c2fb2738a..fe1b832bc22 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -215,8 +215,11 @@ class Type { std::string toString() const; - struct Iterator - : std::iterator { + struct Iterator : std::iterator { const Type* parent; size_t index; Iterator(const Type* parent, size_t index) : parent(parent), index(index) {} @@ -265,6 +268,12 @@ class Type { Iterator begin() const { return Iterator(this, 0); } Iterator end() const; + std::reverse_iterator rbegin() const { + return std::make_reverse_iterator(end()); + } + std::reverse_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 00000000000..d5d5f522c3a --- /dev/null +++ b/test/lit/passes/poppify-globals.wast @@ -0,0 +1,52 @@ +;; 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$2 (mut f32) (f32.const 2)) + ;; CHECK: (global $tuple$1 (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)) + ;; 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) + ;; 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 + ;; 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 00000000000..d41e15d16ba --- /dev/null +++ b/test/lit/passes/poppify.wast @@ -0,0 +1,411 @@ +;; 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 $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: (return + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; 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 + (return + (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) + ) + ) + ) +) From 01af669a4faa0bf9caf7e3e1214b342a3d7e281c Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 8 Feb 2021 17:12:37 -0800 Subject: [PATCH 2/5] Fix pop formatting in comment --- src/passes/Poppify.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/passes/Poppify.cpp b/src/passes/Poppify.cpp index 066d557a765..5c48285d4fd 100644 --- a/src/passes/Poppify.cpp +++ b/src/passes/Poppify.cpp @@ -59,8 +59,8 @@ // (i32.const 42) // (i32.const 5) // (i32.add -// (i32.pop) -// (i32.pop) +// (pop i32) +// (pop i32) // ) // ) // ) From b19e01b02339d30a6128101e9cb1b99ce4c63ebf Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 8 Feb 2021 19:33:58 -0800 Subject: [PATCH 3/5] Address feedback --- src/passes/Poppify.cpp | 73 +++++++++++++--------------- test/lit/passes/poppify-globals.wast | 11 +++-- test/lit/passes/poppify.wast | 28 +++++++++++ 3 files changed, 69 insertions(+), 43 deletions(-) diff --git a/src/passes/Poppify.cpp b/src/passes/Poppify.cpp index 5c48285d4fd..4f1a0b92093 100644 --- a/src/passes/Poppify.cpp +++ b/src/passes/Poppify.cpp @@ -72,6 +72,7 @@ // block unreachability rules. // +#include "ir/names.h" #include "ir/properties.h" #include "ir/stack-utils.h" #include "ir/utils.h" @@ -83,8 +84,9 @@ namespace wasm { namespace { // Generate names for the elements of tuple globals -Name getGlobalElem(Name global, Index i) { - return Name(std::string(global.c_str()) + '$' + std::to_string(i)); +Name getGlobalElem(Module* module, Name global, Index i) { + return Names::getValidGlobalName( + *module, std::string(global.c_str()) + '$' + std::to_string(i)); } struct Poppifier : BinaryenIRWriter { @@ -107,10 +109,6 @@ struct Poppifier : BinaryenIRWriter { // Records the scratch local to be used for tuple.extracts of each type std::unordered_map scratchLocals; - // Maps labels to the types they receive. Used for typing breaks. - // TODO: is this necessary? - std::unordered_map labelTypes; - Poppifier(Function* func, Module* module); Index getScratchLocal(Type type); @@ -122,7 +120,7 @@ struct Poppifier : BinaryenIRWriter { // scope and clear the current scope. void patchInstrs(Expression*& expr); - // BinaryIRWriter methods + // BinaryenIRWriter methods void emit(Expression* curr); void emitHeader() {} void emitIfElse(If* curr); @@ -196,19 +194,10 @@ void Poppifier::emit(Expression* curr) { switch (curr->_id) { case Expression::BlockId: { kind = Scope::Block; - auto* block = curr->cast(); - if (block->name.is()) { - labelTypes[block->name] = block->type; - } break; } case Expression::LoopId: { kind = Scope::Loop; - auto* loop = curr->cast(); - if (loop->name.is()) { - // TODO: support loop parameters - labelTypes[loop->name] = Type::none; - } break; } case Expression::IfId: @@ -402,7 +391,7 @@ void Poppifier::emitGlobalGet(GlobalGet* curr) { auto types = module->getGlobal(curr->name)->type; for (Index i = 0; i < types.size(); ++i) { instrs.push_back( - builder.makeGlobalGet(getGlobalElem(curr->name, i), types[i])); + builder.makeGlobalGet(getGlobalElem(module, curr->name, i), types[i])); } } else { instrs.push_back(curr); @@ -414,8 +403,9 @@ void Poppifier::emitGlobalSet(GlobalSet* curr) { 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(curr->name, i - 1), - builder.makePop(types[i - 1]))); + instrs.push_back( + builder.makeGlobalSet(getGlobalElem(module, curr->name, i - 1), + builder.makePop(types[i - 1]))); } } else { poppify(curr); @@ -459,7 +449,8 @@ class PoppifyPass : public Pass { void run(PassRunner* runner, Module* module) { PassRunner subRunner(runner); subRunner.add(std::make_unique()); - subRunner.add(std::make_unique()); + // TODO: Enable this once it handles Poppy blocks correctly + // subRunner.add(std::make_unique()); subRunner.run(); lowerTupleGlobals(module); } @@ -469,27 +460,31 @@ class PoppifyPass : public Pass { std::vector> newGlobals; for (int g = module->globals.size() - 1; g >= 0; --g) { const auto& global = *module->globals[g]; - if (global.type.isTuple()) { - 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()) { - init = make->operands[i]; - } else if (auto* get = global.init->dynCast()) { - init = builder.makeGlobalGet(getGlobalElem(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(global.name, i), global.type[i], init, mutability)); + 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()) { + init = make->operands[i]; + } else if (auto* get = global.init->dynCast()) { + init = builder.makeGlobalGet(getGlobalElem(module, get->name, i), + global.type[i]); + } else { + WASM_UNREACHABLE("Unexpected tuple global initializer"); } - module->removeGlobal(global.name); + 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())); diff --git a/test/lit/passes/poppify-globals.wast b/test/lit/passes/poppify-globals.wast index d5d5f522c3a..ccdb025c076 100644 --- a/test/lit/passes/poppify-globals.wast +++ b/test/lit/passes/poppify-globals.wast @@ -5,21 +5,24 @@ ;; 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 (mut i64) (i64.const 1)) + ;; 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)) + ;; 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) + ;; CHECK-NEXT: (global.get $tuple$1_0) ;; CHECK-NEXT: (global.get $tuple$2) ;; CHECK-NEXT: ) (func $global-get-tuple (result i32 i64 f32) @@ -33,7 +36,7 @@ ;; CHECK-NEXT: (global.set $tuple$2 ;; CHECK-NEXT: (pop f32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (global.set $tuple$1 + ;; CHECK-NEXT: (global.set $tuple$1_0 ;; CHECK-NEXT: (pop i64) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $tuple$0 diff --git a/test/lit/passes/poppify.wast b/test/lit/passes/poppify.wast index d41e15d16ba..ac11d78fefe 100644 --- a/test/lit/passes/poppify.wast +++ b/test/lit/passes/poppify.wast @@ -89,6 +89,34 @@ ) ) ) + + ;; 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) From 708ae89ce66f4d14e6221e7d6d7f84504f578a9f Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 9 Feb 2021 12:13:12 -0800 Subject: [PATCH 4/5] Update comments and catch test --- src/passes/Poppify.cpp | 10 ++++++---- test/lit/passes/poppify.wast | 7 +------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/passes/Poppify.cpp b/src/passes/Poppify.cpp index 4f1a0b92093..5eaa53088c3 100644 --- a/src/passes/Poppify.cpp +++ b/src/passes/Poppify.cpp @@ -22,13 +22,15 @@ // 1. Function bodies and children of control flow (except If conditions) must // be blocks. // -// 2. Blocks may have any Expressions except for Pops 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. +// 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 diff --git a/test/lit/passes/poppify.wast b/test/lit/passes/poppify.wast index ac11d78fefe..1b9ba1fb371 100644 --- a/test/lit/passes/poppify.wast +++ b/test/lit/passes/poppify.wast @@ -186,9 +186,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $e - ;; CHECK-NEXT: (return - ;; CHECK-NEXT: (pop i32) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all ;; CHECK-NEXT: (i32.const 1) @@ -203,9 +200,7 @@ ) ) (catch $e - (return - (pop i32) - ) + (pop i32) ) (catch_all (i32.const 1) From a2627d18667cbbb9ac97f4219e44e72ae5964b37 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 9 Feb 2021 12:49:05 -0800 Subject: [PATCH 5/5] patchInstrs => patchScope and pop scope --- src/passes/Poppify.cpp | 45 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/src/passes/Poppify.cpp b/src/passes/Poppify.cpp index 5eaa53088c3..b0d07e60cb0 100644 --- a/src/passes/Poppify.cpp +++ b/src/passes/Poppify.cpp @@ -118,9 +118,9 @@ struct Poppifier : BinaryenIRWriter { // Replace `expr`'s children with Pops of the correct type. void poppify(Expression* expr); - // Replace `expr` with a block containing the instructions for the current - // scope and clear the current scope. - void patchInstrs(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); @@ -169,8 +169,10 @@ Index Poppifier::getScratchLocal(Type type) { return insert.first->second; } -void Poppifier::patchInstrs(Expression*& expr) { - auto& instrs = scopeStack.back().instrs; +void Poppifier::patchScope(Expression*& expr) { + auto scope = std::move(scopeStack.back()); + auto& instrs = scope.instrs; + scopeStack.pop_back(); if (auto* block = expr->dynCast()) { // 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. @@ -184,7 +186,6 @@ void Poppifier::patchInstrs(Expression*& expr) { // blocks. expr = builder.makeBlock(instrs, expr->type); } - instrs.clear(); } void Poppifier::emit(Expression* curr) { @@ -243,65 +244,63 @@ void Poppifier::emit(Expression* curr) { void Poppifier::emitIfElse(If* curr) { auto& scope = scopeStack.back(); assert(scope.kind == Scope::If); - patchInstrs(curr->ifTrue); - scope.kind = Scope::Else; + 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); - patchInstrs(curr->body); + patchScope(curr->body); } else { assert(scope.kind == Scope::Catch); - patchInstrs(curr->catchBodies[i - 1]); + patchScope(curr->catchBodies[i - 1]); } - scope.kind = Scope::Catch; + scopeStack.emplace_back(Scope::Catch); } void Poppifier::emitCatchAll(Try* curr) { auto& scope = scopeStack.back(); if (curr->catchBodies.size() == 1) { assert(scope.kind == Scope::Try); - patchInstrs(curr->body); + patchScope(curr->body); } else { assert(scope.kind == Scope::Catch); - patchInstrs(curr->catchBodies[curr->catchBodies.size() - 2]); + patchScope(curr->catchBodies[curr->catchBodies.size() - 2]); } - scope.kind = Scope::Catch; + scopeStack.emplace_back(Scope::Catch); } void Poppifier::emitScopeEnd(Expression* curr) { - auto& scope = scopeStack.back(); - switch (scope.kind) { + switch (scopeStack.back().kind) { case Scope::Block: - patchInstrs(curr); + patchScope(curr); break; case Scope::Loop: - patchInstrs(curr->cast()->body); + patchScope(curr->cast()->body); break; case Scope::If: - patchInstrs(curr->cast()->ifTrue); + patchScope(curr->cast()->ifTrue); break; case Scope::Else: - patchInstrs(curr->cast()->ifFalse); + patchScope(curr->cast()->ifFalse); break; case Scope::Catch: - patchInstrs(curr->cast()->catchBodies.back()); + patchScope(curr->cast()->catchBodies.back()); break; case Scope::Try: WASM_UNREACHABLE("try without catch"); case Scope::Func: WASM_UNREACHABLE("unexpected end of function"); } - scopeStack.pop_back(); scopeStack.back().instrs.push_back(curr); } void Poppifier::emitFunctionEnd() { auto& scope = scopeStack.back(); assert(scope.kind == Scope::Func); - patchInstrs(func->body); + patchScope(func->body); } void Poppifier::emitUnreachable() {