diff options
-rw-r--r-- | src/passes/Outlining.cpp | 5 | ||||
-rw-r--r-- | src/wasm-ir-builder.h | 11 | ||||
-rw-r--r-- | src/wasm/wasm-ir-builder.cpp | 74 | ||||
-rw-r--r-- | test/lit/passes/outlining.wast | 51 |
4 files changed, 136 insertions, 5 deletions
diff --git a/src/passes/Outlining.cpp b/src/passes/Outlining.cpp index c7644c339..f799d2c7a 100644 --- a/src/passes/Outlining.cpp +++ b/src/passes/Outlining.cpp @@ -93,6 +93,11 @@ struct ReconstructStringifyWalker ASSERT_OK(existingBuilder.visitBlockStart(curr->block)); DBG(desc = "Block Start for "); } else if (auto curr = reason.getIfStart()) { + // IR builder needs the condition of the If pushed onto the builder before + // visitIfStart(), which will expect to be able to pop the condition. + // This is always okay to do because the correct condition was installed + // onto the If when the outer scope was visited. + existingBuilder.push(curr->iff->condition); ASSERT_OK(existingBuilder.visitIfStart(curr->iff)); DBG(desc = "If Start for "); } else if (reason.getEnd()) { diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index e02d623f8..0089f547f 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -52,6 +52,10 @@ public: // initialized to initialize the child fields and refinalize it. [[nodiscard]] Result<> visit(Expression*); + // Like visit, but pushes the expression onto the stack as-is without popping + // any children or refinalization. + void push(Expression*); + // Handle the boundaries of control flow structures. Users may choose to use // the corresponding `makeXYZ` function below instead of `visitXYZStart`, but // either way must call `visitEnd` and friends at the appropriate times. @@ -187,7 +191,7 @@ public: // Private functions that must be public for technical reasons. [[nodiscard]] Result<> visitExpression(Expression*); - [[nodiscard]] Result<> visitBlock(Block*); + [[nodiscard]] Result<> visitIf(If*); [[nodiscard]] Result<> visitReturn(Return*); [[nodiscard]] Result<> visitStructNew(StructNew*); [[nodiscard]] Result<> visitArrayNew(ArrayNew*); @@ -308,7 +312,7 @@ private: WASM_UNREACHABLE("unexpected scope kind"); } Name getOriginalLabel() { - if (getFunction()) { + if (std::get_if<NoScope>(&scope) || getFunction()) { return Name{}; } if (auto* block = getBlock()) { @@ -381,7 +385,6 @@ private: [[nodiscard]] Result<Name> getLabelName(Index label); [[nodiscard]] Result<Index> addScratchLocal(Type); [[nodiscard]] Result<Expression*> pop(); - void push(Expression*); struct HoistedVal { // The index in the stack of the original value-producing expression. @@ -401,6 +404,8 @@ private: [[nodiscard]] Result<Expression*> getBranchValue(Name labelName, std::optional<Index> label); + + void dump(); }; } // namespace wasm diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 433145d13..a4a4ec5d0 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -17,9 +17,18 @@ #include <cassert> #include "ir/names.h" +#include "ir/properties.h" #include "ir/utils.h" #include "wasm-ir-builder.h" +#define IR_BUILDER_DEBUG 0 + +#if IR_BUILDER_DEBUG +#define DBG(statement) statement +#else +#define DBG(statement) +#endif + using namespace std::string_literals; namespace wasm { @@ -144,6 +153,9 @@ void IRBuilder::push(Expression* expr) { scope.unreachable = true; } scope.exprStack.push_back(expr); + + DBG(std::cerr << "After pushing " << ShallowExpression(expr) << ":\n"); + DBG(dump()); } Result<Expression*> IRBuilder::pop() { @@ -185,7 +197,55 @@ Result<Expression*> IRBuilder::build() { return expr; } +void IRBuilder::dump() { +#if IR_BUILDER_DEBUG + std::cerr << "Scope stack"; + if (func) { + std::cerr << " in function $" << func->name; + } + std::cerr << ":\n"; + + for (auto& scope : scopeStack) { + std::cerr << " scope "; + if (std::get_if<ScopeCtx::NoScope>(&scope.scope)) { + std::cerr << "none"; + } else if (auto* f = std::get_if<ScopeCtx::FuncScope>(&scope.scope)) { + std::cerr << "func " << f->func->name; + } else if (std::get_if<ScopeCtx::BlockScope>(&scope.scope)) { + std::cerr << "block"; + } else if (std::get_if<ScopeCtx::IfScope>(&scope.scope)) { + std::cerr << "if"; + } else if (std::get_if<ScopeCtx::ElseScope>(&scope.scope)) { + std::cerr << "else"; + } else if (std::get_if<ScopeCtx::LoopScope>(&scope.scope)) { + std::cerr << "loop"; + } else { + WASM_UNREACHABLE("unexpected scope"); + } + + if (auto name = scope.getOriginalLabel()) { + std::cerr << " (original label: " << name << ")"; + } + + if (scope.label) { + std::cerr << " (label: " << scope.label << ")"; + } + + if (scope.unreachable) { + std::cerr << " (unreachable)"; + } + + std::cerr << ":\n"; + + for (auto* expr : scope.exprStack) { + std::cerr << " " << ShallowExpression(expr) << "\n"; + } + } +#endif // IR_BUILDER_DEBUG +} + Result<> IRBuilder::visit(Expression* curr) { + // Call either `visitExpression` or an expression-specific override. auto val = UnifiedExpressionVisitor<IRBuilder, Result<>>::visit(curr); CHECK_ERR(val); if (auto* block = curr->dynCast<Block>()) { @@ -202,6 +262,12 @@ Result<> IRBuilder::visit(Expression* curr) { // Handle the common case of instructions with a constant number of children // uniformly. Result<> IRBuilder::visitExpression(Expression* curr) { + if (Properties::isControlFlowStructure(curr)) { + // Control flow structures (besides `if`, handled separately) do not consume + // stack values. + return Ok{}; + } + #define DELEGATE_ID curr->_id #define DELEGATE_START(id) [[maybe_unused]] auto* expr = curr->cast<id>(); #define DELEGATE_FIELD_CHILD(id, field) \ @@ -238,8 +304,12 @@ Result<> IRBuilder::visitExpression(Expression* curr) { return Ok{}; } -Result<> IRBuilder::visitBlock(Block* curr) { - // No children; pushing and finalizing will be handled by `visit`. +Result<> IRBuilder::visitIf(If* curr) { + // Only the condition is popped from the stack. The ifTrue and ifFalse are + // self-contained so we do not modify them. + auto cond = pop(); + CHECK_ERR(cond); + curr->condition = *cond; return Ok{}; } diff --git a/test/lit/passes/outlining.wast b/test/lit/passes/outlining.wast index ffd5eb081..a080162ca 100644 --- a/test/lit/passes/outlining.wast +++ b/test/lit/passes/outlining.wast @@ -243,3 +243,54 @@ ) ) ) + + +;; Tests that outlining works correctly with If control flow +(module + ;; CHECK: (type $0 (func (param i32))) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (func $outline$ + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + + ;; CHECK: (func $a (param $0 i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $outline$) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $a (param i32) + (if + (i32.eqz + (local.get 0) + ) + (drop + (i32.const 10) + ) + ) + ) + ;; CHECK: (func $b (param $0 i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $outline$) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $b (param i32) + (if + (i32.eqz + (local.get 0) + ) + (drop + (i32.const 10) + ) + ) + ) +) |