diff --git a/README.md b/README.md index 6eda71e1c5b..3ff76b48d7b 100644 --- a/README.md +++ b/README.md @@ -388,7 +388,7 @@ it usually is run on the final linked wasm. Advanced optimization techniques in the Binaryen optimizer include [SSAification](https://github.com/WebAssembly/binaryen/blob/main/src/passes/SSAify.cpp), [Flat IR](https://github.com/WebAssembly/binaryen/blob/main/src/ir/flat.h), and -[Stack/Poppy IR](https://github.com/WebAssembly/binaryen/blob/main/src/ir/stack-utils.h). +[Stack IR](https://github.com/WebAssembly/binaryen/blob/main/src/wasm-stack.h). See the [Optimizer Cookbook wiki page](https://github.com/WebAssembly/binaryen/wiki/Optimizer-Cookbook) diff --git a/src/ir/stack-utils.cpp b/src/ir/stack-utils.cpp index bc20dfe9191..6aeb10685e7 100644 --- a/src/ir/stack-utils.cpp +++ b/src/ir/stack-utils.cpp @@ -16,51 +16,14 @@ #include "stack-utils.h" #include "ir/iteration.h" -#include "ir/properties.h" namespace wasm { -namespace StackUtils { - -void removeNops(Block* block) { - size_t newIndex = 0; - for (size_t i = 0, size = block->list.size(); i < size; ++i) { - if (!block->list[i]->is()) { - block->list[newIndex++] = block->list[i]; - } - } - block->list.resize(newIndex); -} - -bool mayBeUnreachable(Expression* expr) { - if (Properties::isControlFlowStructure(expr)) { - return true; - } - switch (expr->_id) { - case Expression::Id::BreakId: - return expr->cast()->condition == nullptr; - case Expression::Id::CallId: - return expr->cast()->isReturn; - case Expression::Id::CallIndirectId: - return expr->cast()->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) { std::vector inputs; for (auto* child : ValueChildIterator(expr)) { assert(child->type.isConcrete()); - // Children might be tuple pops, so expand their types + // Children might be tuples, so expand their types. inputs.insert(inputs.end(), child->type.begin(), child->type.end()); } params = Type(inputs); @@ -253,153 +216,4 @@ StackSignature StackSignature::getLeastUpperBound(StackSignature a, return StackSignature{Type(params), Type(results), kind}; } -StackFlow::StackFlow(Block* block) { - // Encapsulates the logic for treating the block and its children - // uniformly. The end of the block is treated as if it consumed values - // corresponding to the its result type and produced no values, which is why - // the block's result type is used as the params of its processed stack - // signature. - auto processBlock = [&block](auto process) { - // TODO: Once we support block parameters, set up the stack by calling - // `process` before iterating through the block. - for (auto* expr : block->list) { - process(expr, StackSignature(expr)); - } - auto kind = block->type == Type::unreachable ? StackSignature::Polymorphic - : StackSignature::Fixed; - Type params = block->type == Type::unreachable ? Type::none : block->type; - process(block, StackSignature(params, Type::none, kind)); - }; - - // We need to make an initial pass through the block to figure out how many - // values each unreachable instruction produces. - std::unordered_map producedByUnreachable; - { - size_t stackSize = 0; - size_t produced = 0; - Expression* lastUnreachable = nullptr; - processBlock([&](Expression* expr, const StackSignature sig) { - // Consume params - if (sig.params.size() > stackSize) { - // We need more values than are available, so they must come from the - // last unreachable. - assert(lastUnreachable); - produced += sig.params.size() - stackSize; - stackSize = 0; - } else { - stackSize -= sig.params.size(); - } - - // Handle unreachable or produce results - if (sig.kind == StackSignature::Polymorphic) { - if (lastUnreachable) { - producedByUnreachable[lastUnreachable] = produced; - produced = 0; - } - assert(produced == 0); - lastUnreachable = expr; - stackSize = 0; - } else { - stackSize += sig.results.size(); - } - }); - - // Finish record for final unreachable - if (lastUnreachable) { - producedByUnreachable[lastUnreachable] = produced; - } - } - - // Take another pass through the block, recording srcs and dests. - std::vector values; - Expression* lastUnreachable = nullptr; - processBlock([&](Expression* expr, const StackSignature sig) { - assert((sig.params.size() <= values.size() || lastUnreachable) && - "Block inputs not yet supported"); - - // Unreachable instructions consume all available values - size_t consumed = sig.kind == StackSignature::Polymorphic - ? std::max(values.size(), sig.params.size()) - : sig.params.size(); - - // We previously calculated how many values unreachable instructions produce - size_t produced = sig.kind == StackSignature::Polymorphic - ? producedByUnreachable[expr] - : sig.results.size(); - - srcs[expr] = std::vector(consumed); - dests[expr] = std::vector(produced); - - // Figure out what kind of unreachable values we have - assert(sig.params.size() <= consumed); - size_t unreachableBeyondStack = 0; - size_t unreachableFromStack = 0; - if (consumed > values.size()) { - assert(consumed == sig.params.size()); - unreachableBeyondStack = consumed - values.size(); - } else if (consumed > sig.params.size()) { - assert(consumed == values.size()); - unreachableFromStack = consumed - sig.params.size(); - } - - // Consume values - for (Index i = 0; i < consumed; ++i) { - if (i < unreachableBeyondStack) { - // This value comes from the polymorphic stack of the last unreachable - // because the stack did not have enough values to satisfy this - // instruction. - assert(lastUnreachable); - assert(producedByUnreachable[lastUnreachable] >= - unreachableBeyondStack); - Index destIndex = - producedByUnreachable[lastUnreachable] - unreachableBeyondStack + i; - Type type = sig.params[i]; - srcs[expr][i] = {lastUnreachable, destIndex, type, true}; - dests[lastUnreachable][destIndex] = {expr, i, type, false}; - } else { - // A normal value from the value stack - bool unreachable = i < unreachableFromStack; - auto& src = values[values.size() + i - consumed]; - srcs[expr][i] = src; - dests[src.expr][src.index] = {expr, i, src.type, unreachable}; - } - } - - // Update available values - if (unreachableBeyondStack) { - producedByUnreachable[lastUnreachable] -= unreachableBeyondStack; - values.resize(0); - } else { - values.resize(values.size() - consumed); - } - - // Produce values - for (Index i = 0; i < sig.results.size(); ++i) { - values.push_back({expr, i, sig.results[i], false}); - } - - // Update the last unreachable instruction - if (sig.kind == StackSignature::Polymorphic) { - assert(producedByUnreachable[lastUnreachable] == 0); - lastUnreachable = expr; - } - }); -} - -StackSignature StackFlow::getSignature(Expression* expr) { - auto exprSrcs = srcs.find(expr); - auto exprDests = dests.find(expr); - assert(exprSrcs != srcs.end() && exprDests != dests.end()); - std::vector params, results; - for (auto& src : exprSrcs->second) { - params.push_back(src.type); - } - for (auto& dest : exprDests->second) { - results.push_back(dest.type); - } - auto kind = expr->type == Type::unreachable ? StackSignature::Polymorphic - : StackSignature::Fixed; - return StackSignature(Type(params), Type(results), kind); -} - } // namespace wasm diff --git a/src/ir/stack-utils.h b/src/ir/stack-utils.h index 82d1f5a3d1f..b9524d48daf 100644 --- a/src/ir/stack-utils.h +++ b/src/ir/stack-utils.h @@ -15,30 +15,17 @@ */ // -// stack-utils.h: Utilities for manipulating and analyzing stack machine code in -// the form of Poppy IR. See src/passes/Poppify.cpp for Poppy IR documentation. +// Utilities for manipulating and analyzing stack machine code. // #ifndef wasm_ir_stack_h #define wasm_ir_stack_h -#include "ir/properties.h" #include "wasm-type.h" #include "wasm.h" namespace wasm { -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 // represent the stack parameters and results of arbitrary sequences of stack // machine instructions. Stack signatures that represent instruction sequences @@ -66,6 +53,7 @@ bool mayBeUnreachable(Expression* expr); // (block (param i32 i32) (result f64 f32) ... ) // (block (param f64 i32 i32) (result f64 f32) ... ) // +// TODO: Replace StackSignature with PrincipalType. struct StackSignature { Type params; Type results; @@ -175,47 +163,6 @@ struct StackSignature { static StackSignature getLeastUpperBound(StackSignature a, StackSignature b); }; -// Calculates stack machine data flow, associating the sources and destinations -// of all values in a block. -struct StackFlow { - // The destination (source) location at which a value of type `type` is - // consumed (produced), corresponding to the `index`th value consumed by - // (produced by) instruction `expr`. For destination locations, `unreachable` - // is true iff the corresponding value is consumed by the polymorphic behavior - // of an unreachable instruction rather than being used directly. For source - // locations, `unreachable` is true iff the corresponding value is produced by - // an unreachable instruction. For produced values that are not consumed - // within the block (TODO: also for consumed values that are not produced - // within the block), `expr` will be the enclosing block. - struct Location { - Expression* expr; - Index index; - Type type; - bool unreachable; - - bool operator==(const Location& other) const { - return expr == other.expr && index == other.index && type == other.type && - unreachable == other.unreachable; - } - }; - - using LocationMap = std::unordered_map>; - - // Maps each instruction to the set of source locations producing its inputs. - LocationMap srcs; - - // Maps each instruction to the set of output locations consuming its results. - LocationMap dests; - - // Gets the effective stack signature of `expr`, which must be a child of the - // block. If `expr` is unreachable, the returned signature will reflect the - // values consumed and produced by its polymorphic unreachable behavior. - StackSignature getSignature(Expression* expr); - - // Calculates `srcs` and `dests`. - StackFlow(Block* curr); -}; - } // namespace wasm #endif // wasm_ir_stack_h diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index 369825560cd..98c75fc24c3 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -92,7 +92,6 @@ set(passes_SOURCES OptimizeForJS.cpp OptimizeNoReturn.cpp PickLoadSigns.cpp - Poppify.cpp PostEmscripten.cpp Precompute.cpp Print.cpp diff --git a/src/passes/Poppify.cpp b/src/passes/Poppify.cpp deleted file mode 100644 index b6452b51cde..00000000000 --- a/src/passes/Poppify.cpp +++ /dev/null @@ -1,516 +0,0 @@ -/* - * 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, - global.toString() + '$' + 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; - - 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 emitDelegate(Try* curr); - void emitScopeEnd(Expression* curr); - void emitFunctionEnd(); - void emitUnreachable(); - void emitUnreachableLocalSet(Index i); - 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::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. - 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()->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) { - [[maybe_unused]] auto& scope = scopeStack.back(); - assert(scope.kind == Scope::If); - patchScope(curr->ifTrue); - scopeStack.emplace_back(Scope::Else); -} - -void Poppifier::emitCatch(Try* curr, Index i) { - [[maybe_unused]] 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) { - [[maybe_unused]] 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::emitDelegate(Try* curr) { - [[maybe_unused]] auto& scope = scopeStack.back(); - assert(scope.kind == Scope::Try); - patchScope(curr->body); - scopeStack.back().instrs.push_back(curr); -} - -void Poppifier::emitScopeEnd(Expression* curr) { - switch (scopeStack.back().kind) { - case Scope::Block: - patchScope(curr); - break; - case Scope::Loop: - patchScope(curr->cast()->body); - break; - case Scope::If: - patchScope(curr->cast()->ifTrue); - break; - case Scope::Else: - patchScope(curr->cast()->ifFalse); - break; - case Scope::Catch: - patchScope(curr->cast()->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() { - [[maybe_unused]] 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::emitUnreachableLocalSet(Index i) { - auto& instrs = scopeStack.back().instrs; - instrs.push_back(builder.makeUnreachable()); - instrs.push_back(builder.makeLocalSet(i, builder.makePop(Type::unreachable))); -} - -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 { - 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(Module* module, Function* func) override { - if (func->profile != IRProfile::Poppy) { - Poppifier(func, module).write(); - func->profile = IRProfile::Poppy; - } - } - std::unique_ptr create() override { - return std::make_unique(); - } -}; - -} // anonymous namespace - -class PoppifyPass : public Pass { - void run(Module* module) override { - PassRunner subRunner(getPassRunner()); - subRunner.add(std::make_unique()); - // TODO: Enable this once it handles Poppy blocks correctly - // 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()) { - 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"); - } - 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 e4d41ceb901..46b9493934d 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -388,8 +388,6 @@ void PassRegistry::registerPasses() { registerPass("pick-load-signs", "pick load signs based on their uses", createPickLoadSignsPass); - registerPass( - "poppify", "Transform Binaryen IR into Poppy IR", createPoppifyPass); registerPass("post-emscripten", "miscellaneous optimizations for Emscripten-generated code", createPostEmscriptenPass); diff --git a/src/passes/passes.h b/src/passes/passes.h index 627a18209e6..6dcf5aad838 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -130,7 +130,6 @@ Pass* createOutliningPass(); #endif Pass* createPickLoadSignsPass(); Pass* createLLVMNonTrappingFPToIntLoweringPass(); -Pass* createPoppifyPass(); Pass* createPostEmscriptenPass(); Pass* createPrecomputePass(); Pass* createPrecomputePropagatePass(); diff --git a/src/tools/tool-options.h b/src/tools/tool-options.h index 7f2f57bac4b..6287d53c38a 100644 --- a/src/tools/tool-options.h +++ b/src/tools/tool-options.h @@ -34,7 +34,6 @@ struct ToolOptions : public Options { bool quiet = false; bool preserveTypeOrder = false; bool emitModuleNames = false; - IRProfile profile = IRProfile::Normal; constexpr static const char* ToolOptionsCategory = "Tool options"; @@ -71,12 +70,6 @@ struct ToolOptions : public Options { ToolOptionsCategory, Arguments::Zero, [this](Options*, const std::string&) { quiet = true; }) - .add("--experimental-poppy", - "", - "Parse wast files as Poppy IR for testing purposes.", - ToolOptionsCategory, - Arguments::Zero, - [this](Options*, const std::string&) { profile = IRProfile::Poppy; }) .add("--emit-module-names", "", "Emit module names, even if not emitting the rest of the names " diff --git a/src/tools/wasm-opt.cpp b/src/tools/wasm-opt.cpp index 9903ba7f1e7..bd89fc72b09 100644 --- a/src/tools/wasm-opt.cpp +++ b/src/tools/wasm-opt.cpp @@ -341,7 +341,6 @@ For more on how to optimize effectively, see // asked to remove it. reader.setDWARF(options.passOptions.debugInfo && !willRemoveDebugInfo(options.passes)); - reader.setProfile(options.profile); try { reader.read(inputFile, wasm, inputSourceMapFilename); } catch (ParseException& p) { diff --git a/src/tools/wasm-split/wasm-split.cpp b/src/tools/wasm-split/wasm-split.cpp index 112a001dd01..74044f10003 100644 --- a/src/tools/wasm-split/wasm-split.cpp +++ b/src/tools/wasm-split/wasm-split.cpp @@ -39,7 +39,6 @@ namespace { void parseInput(Module& wasm, const WasmSplitOptions& options) { options.applyOptionsBeforeParse(wasm); ModuleReader reader; - reader.setProfile(options.profile); try { reader.read(options.inputFiles[0], wasm); } catch (ParseException& p) { diff --git a/src/wasm-io.h b/src/wasm-io.h index 30cda0f2da0..31b6bd4b4f2 100644 --- a/src/wasm-io.h +++ b/src/wasm-io.h @@ -48,8 +48,6 @@ class ModuleReader : public ModuleIOBase { // the binary, so that we can update DWARF sections later when writing. void setDWARF(bool DWARF_) { DWARF = DWARF_; } - void setProfile(IRProfile profile_) { profile = profile_; } - // TODO: add support for this in the text format as well void setSkipFunctionBodies(bool skipFunctionBodies_) { skipFunctionBodies = skipFunctionBodies_; @@ -77,8 +75,6 @@ class ModuleReader : public ModuleIOBase { private: bool DWARF = false; - IRProfile profile = IRProfile::Normal; - bool skipFunctionBodies = false; FeatureSet featuresSectionFeatures = FeatureSet::MVP; diff --git a/src/wasm.h b/src/wasm.h index 657433874fd..7459bc61d53 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -76,8 +76,6 @@ enum class MemoryOrder : uint8_t { SeqCst, }; -enum class IRProfile { Normal, Poppy }; - // Operators enum UnaryOp { @@ -2444,7 +2442,6 @@ class Function : public Importable { public: // A non-nullable reference to a function type. Exact for defined functions. Type type = Type(Signature(), NonNullable, Exact); - IRProfile profile = IRProfile::Normal; std::vector vars; // non-param locals // The body of the function diff --git a/src/wasm/wasm-io.cpp b/src/wasm/wasm-io.cpp index 15237f08c45..09341621c8c 100644 --- a/src/wasm/wasm-io.cpp +++ b/src/wasm/wasm-io.cpp @@ -36,8 +36,7 @@ namespace wasm { static void readTextData(std::optional filename, std::string_view input, - Module& wasm, - IRProfile profile) { + Module& wasm) { if (auto parsed = WATParser::parseModule(wasm, input, filename); auto err = parsed.getErr()) { Fatal() << err->msg; @@ -47,7 +46,7 @@ static void readTextData(std::optional filename, void ModuleReader::readText(std::string filename, Module& wasm) { BYN_TRACE("reading text from " << filename << "\n"); auto input(read_file(filename, Flags::Text)); - readTextData(filename, input, wasm, profile); + readTextData(filename, input, wasm); } void ModuleReader::readBinaryData(const std::vector& input, @@ -116,10 +115,8 @@ void ModuleReader::readData(const std::vector& input, input[2] == 's' && input[3] == 'm') { readBinaryData(input, wasm, sourceMapFilename); } else { - readTextData(std::nullopt, - std::string_view(input.data(), input.size()), - wasm, - profile); + readTextData( + std::nullopt, std::string_view(input.data(), input.size()), wasm); } } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index a8255e931e8..a98c205d113 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -27,7 +27,6 @@ #include "ir/intrinsics.h" #include "ir/local-structural-dominance.h" #include "ir/module-utils.h" -#include "ir/stack-utils.h" #include "ir/utils.h" #include "support/colors.h" #include "support/mixed_arena.h" @@ -335,13 +334,6 @@ struct FunctionValidator : public WalkerPass> { 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(); if (curr->name.is()) { @@ -350,8 +342,6 @@ struct FunctionValidator : public WalkerPass> { } void visitBlock(Block* curr); - void validateNormalBlockElements(Block* curr); - void validatePoppyBlockElements(Block* curr); static void visitPreLoop(FunctionValidator* self, Expression** currp) { auto* curr = (*currp)->cast(); @@ -416,11 +406,6 @@ struct FunctionValidator : public WalkerPass> { if (curr->is()) { self->pushTask(visitPreLoop, currp); } - if (auto* func = self->getFunction()) { - if (func->profile == IRProfile::Poppy) { - self->pushTask(visitPoppyExpression, currp); - } - } // Also verify that only allowed expressions end up in the situation where // the expression has type unreachable but there is no unreachable child. @@ -727,39 +712,6 @@ 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()) { - shouldBeTrue( - if_->condition->is(), curr, "Expected condition to be a Pop"); - shouldBeTrue(if_->ifTrue->is(), - curr, - "Expected control flow child to be a block"); - shouldBeTrue(!if_->ifFalse || if_->ifFalse->is(), - curr, - "Expected control flow child to be a block"); - } else if (!curr->is()) { - for (auto* child : ChildIterator(curr)) { - shouldBeTrue(child->is(), - curr, - "Expected control flow child to be a block"); - } - } - } else { - // Check that all children are Pops - for (auto* child : ChildIterator(curr)) { - shouldBeTrue(child->is(), curr, "Unexpected non-Pop child"); - } - } -} - void FunctionValidator::visitBlock(Block* curr) { auto feats = curr->type.getFeatures(); if (!shouldBeTrue(feats.isSubsetOf(getModule()->features), @@ -844,21 +796,9 @@ void FunctionValidator::visitBlock(Block* curr) { } } - auto* func = getFunction(); - if (!shouldBeTrue(!!func, curr, "function not defined")) { + if (!shouldBeTrue(!!getFunction(), curr, "function not defined")) { return; } - switch (func->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( @@ -901,51 +841,6 @@ void FunctionValidator::validateNormalBlockElements(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(), 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.kind == StackSignature::Polymorphic) { - getStream() << "polymorphic, "; - } - getStream() << blockSig.results << "\n"; - return; - } - blockSig += sig; - } - if (curr->type == Type::unreachable) { - shouldBeTrue(blockSig.kind == StackSignature::Polymorphic, - curr, - "unreachable block should have unreachable element"); - } else { - if (!shouldBeTrue( - StackSignature::isSubType( - blockSig, - StackSignature(Type::none, curr->type, StackSignature::Fixed)), - curr, - "block contents should satisfy block type") && - !info.quiet) { - getStream() << "contents: " << blockSig.results - << (blockSig.kind == StackSignature::Polymorphic - ? " [polymorphic]" - : "") - << "\n" - << "expected: " << curr->type << "\n"; - } - } -} - void FunctionValidator::visitLoop(Loop* curr) { if (curr->name.is()) { noteLabelName(curr->name); @@ -4981,10 +4876,6 @@ void FunctionValidator::visitFunction(Function* curr) { curr->body, "Multivalue function results (multivalue is not enabled)"); } - if (curr->profile == IRProfile::Poppy) { - shouldBeTrue( - curr->body->is(), 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 shouldBeSubType(curr->body->type, diff --git a/test/example/stack-utils.cpp b/test/example/stack-utils.cpp index 9fb13374b1a..2bce0d96769 100644 --- a/test/example/stack-utils.cpp +++ b/test/example/stack-utils.cpp @@ -2,8 +2,6 @@ #include #include "ir/stack-utils.h" -#include "literal.h" -#include "support/mixed_arena.h" #include "wasm-builder.h" #include "wasm-type.h" #include "wasm.h" @@ -13,23 +11,6 @@ using namespace wasm; Module module; Builder builder(module); -void test_remove_nops() { - std::cout << ";; Test removeNops\n"; - auto* block = builder.makeBlock( - { - builder.makeNop(), - builder.makeConst(Literal(int32_t(0))), - builder.makeNop(), - builder.makeConst(Literal(int64_t(0))), - builder.makeNop(), - builder.makeNop(), - }, - Type{Type::i32, Type::i64}); - std::cout << *block << '\n'; - StackUtils::removeNops(block); - std::cout << *block << '\n'; -} - void test_stack_signatures() { std::cout << ";; Test stack signatures\n"; // Typed block @@ -57,48 +38,49 @@ void test_stack_signatures() { { // If (no else) auto* if_ = builder.makeIf( - builder.makePop(Type::i32), unreachable, nullptr, Type::none); + builder.makeLocalGet(0, Type::i32), unreachable, nullptr, Type::none); assert(StackSignature(if_) == (StackSignature{Type::i32, Type::none, StackSignature::Fixed})); } { // If (with else) - auto* if_ = - builder.makeIf(builder.makePop(Type::i32), block, block, Type::f32); + auto* if_ = builder.makeIf( + builder.makeLocalGet(0, Type::i32), block, block, Type::f32); assert(StackSignature(if_) == (StackSignature{Type::i32, Type::f32, StackSignature::Fixed})); } { // Call - auto* call = - builder.makeCall("foo", - {builder.makePop(Type::i32), builder.makePop(Type::f32)}, - {Type::i64, Type::f64}); + auto* call = builder.makeCall( + "foo", + {builder.makeLocalGet(0, Type::i32), builder.makeLocalGet(1, Type::f32)}, + {Type::i64, Type::f64}); assert(StackSignature(call) == (StackSignature{{Type::i32, Type::f32}, {Type::i64, Type::f64}, StackSignature::Fixed})); } { // Return Call - auto* call = - builder.makeCall("bar", - {builder.makePop(Type::i32), builder.makePop(Type::f32)}, - Type::unreachable, - StackSignature::Polymorphic); + auto* call = builder.makeCall( + "bar", + {builder.makeLocalGet(0, Type::i32), builder.makeLocalGet(1, Type::f32)}, + Type::unreachable, + /*isReturn=*/true); assert(StackSignature(call) == (StackSignature{ {Type::i32, Type::f32}, Type::none, StackSignature::Polymorphic})); } { // Return - auto* ret = builder.makeReturn(builder.makePop(Type::i32)); + auto* ret = builder.makeReturn(builder.makeLocalGet(0, Type::i32)); assert( StackSignature(ret) == (StackSignature{Type::i32, Type::none, StackSignature::Polymorphic})); } { // Multivalue return - auto* ret = builder.makeReturn(builder.makePop({Type::i32, Type::f32})); + auto* ret = + builder.makeReturn(builder.makeLocalGet(2, {Type::i32, Type::f32})); assert(StackSignature(ret) == (StackSignature{ {Type::i32, Type::f32}, Type::none, StackSignature::Polymorphic})); @@ -469,145 +451,9 @@ void test_signature_lub() { } } -void test_stack_flow() { - std::cout << ";; Test stack flow\n"; - { - // Simple case: - // foo - // │i32 - // bar - // │f32 - // end - auto* foo = builder.makeCall("foo", {}, Type::i32); - auto* bar = - builder.makeCall("bar", {builder.makePop(Type::i32)}, Type::f32); - auto* block = builder.makeBlock({foo, bar}, Type::f32); - StackFlow flow(block); - - auto fooSrcs = flow.srcs.find(foo); - assert(fooSrcs != flow.srcs.end()); - assert(fooSrcs->second.size() == 0); - - auto fooDests = flow.dests.find(foo); - assert(fooDests != flow.dests.end()); - assert(fooDests->second.size() == 1); - assert(fooDests->second[0] == - (StackFlow::Location{bar, 0, Type::i32, false})); - - auto barSrcs = flow.srcs.find(bar); - assert(barSrcs != flow.dests.end()); - assert(barSrcs->second.size() == 1); - assert(barSrcs->second[0] == - (StackFlow::Location{foo, 0, Type::i32, false})); - - auto barDests = flow.dests.find(bar); - assert(barDests != flow.dests.end()); - assert(barDests->second.size() == 1); - assert(barDests->second[0] == - (StackFlow::Location{block, 0, Type::f32, false})); - - auto blockSrcs = flow.srcs.find(block); - assert(blockSrcs != flow.srcs.end()); - assert(blockSrcs->second.size() == 1); - assert(blockSrcs->second[0] == - (StackFlow::Location{bar, 0, Type::f32, false})); - } - { - // Interesting case: - // foo - // ├─────┬─────┐ - // │i32 │f32 │i64 - // │ │ bar - // │ │ │f64 - // │ baz╶───┘ - // │ ├─────┐ - // ╵ ╵i64 │f32 - // ret╶─────────┘ - // ╷ ╷ ╷ - // │i64 │f64 │i32 - // │ quux╶──┘ - // end - auto* foo = builder.makeCall("foo", {}, {Type::i32, Type::f32, Type::i64}); - auto* bar = - builder.makeCall("bar", {builder.makePop(Type::i64)}, Type::f64); - auto* baz = - builder.makeCall("baz", - {builder.makePop(Type::f32), builder.makePop(Type::f64)}, - {Type::i64, Type::f32}); - auto* ret = builder.makeReturn(builder.makePop(Type::f32)); - auto* quux = - builder.makeCall("quux", - {builder.makePop(Type::f64), builder.makePop(Type::i32)}, - Type::none); - auto* block = builder.makeBlock({foo, bar, baz, ret, quux}, Type::i64); - StackFlow flow(block); - - assert(flow.srcs.find(foo)->second.size() == 0); - - const auto& fooDests = flow.dests[foo]; - assert(fooDests.size() == 3); - assert(fooDests[0] == (StackFlow::Location{ret, 0, Type::i32, true})); - assert(fooDests[1] == (StackFlow::Location{baz, 0, Type::f32, false})); - assert(fooDests[2] == (StackFlow::Location{bar, 0, Type::i64, false})); - - const auto& barSrcs = flow.srcs[bar]; - assert(barSrcs.size() == 1); - assert(barSrcs[0] == (StackFlow::Location{foo, 2, Type::i64, false})); - - const auto& barDests = flow.dests[bar]; - assert(barDests.size() == 1); - assert(barDests[0] == (StackFlow::Location{baz, 1, Type::f64, false})); - - const auto& bazSrcs = flow.srcs[baz]; - assert(bazSrcs.size() == 2); - assert(bazSrcs[0] == (StackFlow::Location{foo, 1, Type::f32, false})); - assert(bazSrcs[1] == (StackFlow::Location{bar, 0, Type::f64, false})); - - const auto& bazDests = flow.dests[baz]; - assert(bazDests.size() == 2); - assert(bazDests[0] == (StackFlow::Location{ret, 1, Type::i64, true})); - assert(bazDests[1] == (StackFlow::Location{ret, 2, Type::f32, false})); - - const auto& retSrcs = flow.srcs[ret]; - assert(retSrcs.size() == 3); - assert(retSrcs[0] == (StackFlow::Location{foo, 0, Type::i32, false})); - assert(retSrcs[1] == (StackFlow::Location{baz, 0, Type::i64, false})); - assert(retSrcs[2] == (StackFlow::Location{baz, 1, Type::f32, false})); - - const auto& retDests = flow.dests[ret]; - assert(retDests.size() == 3); - assert(retDests[0] == (StackFlow::Location{block, 0, Type::i64, false})); - assert(retDests[1] == (StackFlow::Location{quux, 0, Type::f64, false})); - assert(retDests[2] == (StackFlow::Location{quux, 1, Type::i32, false})); - - const auto& quuxSrcs = flow.srcs[quux]; - assert(quuxSrcs.size() == 2); - assert(quuxSrcs[0] == (StackFlow::Location{ret, 1, Type::f64, true})); - assert(quuxSrcs[1] == (StackFlow::Location{ret, 2, Type::i32, true})); - - const auto& quuxDests = flow.dests[quux]; - assert(quuxDests.size() == 0); - - const auto& blockSrcs = flow.srcs[block]; - assert(blockSrcs.size() == 1); - assert(blockSrcs[0] == (StackFlow::Location{ret, 0, Type::i64, true})); - - assert(flow.getSignature(foo) == StackSignature(foo)); - assert(flow.getSignature(bar) == StackSignature(bar)); - assert(flow.getSignature(baz) == StackSignature(baz)); - assert(flow.getSignature(ret) == - (StackSignature{{Type::i32, Type::i64, Type::f32}, - {Type::i64, Type::f64, Type::i32}, - StackSignature::Polymorphic})); - assert(flow.getSignature(quux) == StackSignature(quux)); - } -} - int main() { - test_remove_nops(); test_stack_signatures(); test_signature_composition(); test_signature_subtype(); test_signature_lub(); - test_stack_flow(); } diff --git a/test/example/stack-utils.txt b/test/example/stack-utils.txt index d24119aab26..edc8330813e 100644 --- a/test/example/stack-utils.txt +++ b/test/example/stack-utils.txt @@ -1,18 +1,4 @@ -;; Test removeNops -(block (result i32 i64) - (nop) - (i32.const 0) - (nop) - (i64.const 0) - (nop) - (nop) -) -(block (result i32 i64) - (i32.const 0) - (i64.const 0) -) ;; Test stack signatures ;; Test stack signature composition ;; Test stack signature subtyping ;; Test stack signature lub -;; Test stack flow diff --git a/test/lit/help/wasm-as.test b/test/lit/help/wasm-as.test index e0948f13a4c..1205956dbd3 100644 --- a/test/lit/help/wasm-as.test +++ b/test/lit/help/wasm-as.test @@ -35,9 +35,6 @@ ;; CHECK-NEXT: --quiet,-q Emit less verbose output and hide trivial ;; CHECK-NEXT: warnings. ;; CHECK-EMPTY: -;; CHECK-NEXT: --experimental-poppy Parse wast files as Poppy IR for testing -;; CHECK-NEXT: purposes. -;; CHECK-EMPTY: ;; CHECK-NEXT: --emit-module-names Emit module names, even if not emitting ;; CHECK-NEXT: the rest of the names section. ;; CHECK-EMPTY: diff --git a/test/lit/help/wasm-ctor-eval.test b/test/lit/help/wasm-ctor-eval.test index 14cc82d65e1..3e44288f886 100644 --- a/test/lit/help/wasm-ctor-eval.test +++ b/test/lit/help/wasm-ctor-eval.test @@ -42,9 +42,6 @@ ;; CHECK-NEXT: --quiet,-q Emit less verbose output and hide trivial ;; CHECK-NEXT: warnings. ;; CHECK-EMPTY: -;; CHECK-NEXT: --experimental-poppy Parse wast files as Poppy IR for testing -;; CHECK-NEXT: purposes. -;; CHECK-EMPTY: ;; CHECK-NEXT: --emit-module-names Emit module names, even if not emitting ;; CHECK-NEXT: the rest of the names section. ;; CHECK-EMPTY: diff --git a/test/lit/help/wasm-dis.test b/test/lit/help/wasm-dis.test index 552286b0128..84c506e19b4 100644 --- a/test/lit/help/wasm-dis.test +++ b/test/lit/help/wasm-dis.test @@ -28,9 +28,6 @@ ;; CHECK-NEXT: --quiet,-q Emit less verbose output and hide trivial ;; CHECK-NEXT: warnings. ;; CHECK-EMPTY: -;; CHECK-NEXT: --experimental-poppy Parse wast files as Poppy IR for testing -;; CHECK-NEXT: purposes. -;; CHECK-EMPTY: ;; CHECK-NEXT: --emit-module-names Emit module names, even if not emitting ;; CHECK-NEXT: the rest of the names section. ;; CHECK-EMPTY: diff --git a/test/lit/help/wasm-emscripten-finalize.test b/test/lit/help/wasm-emscripten-finalize.test index b3a852124e2..719059bb3d3 100644 --- a/test/lit/help/wasm-emscripten-finalize.test +++ b/test/lit/help/wasm-emscripten-finalize.test @@ -66,9 +66,6 @@ ;; CHECK-NEXT: --quiet,-q Emit less verbose output and hide trivial ;; CHECK-NEXT: warnings. ;; CHECK-EMPTY: -;; CHECK-NEXT: --experimental-poppy Parse wast files as Poppy IR for testing -;; CHECK-NEXT: purposes. -;; CHECK-EMPTY: ;; CHECK-NEXT: --emit-module-names Emit module names, even if not emitting ;; CHECK-NEXT: the rest of the names section. ;; CHECK-EMPTY: diff --git a/test/lit/help/wasm-merge.test b/test/lit/help/wasm-merge.test index f25743a8062..2c7d3623bd3 100644 --- a/test/lit/help/wasm-merge.test +++ b/test/lit/help/wasm-merge.test @@ -65,9 +65,6 @@ ;; CHECK-NEXT: --quiet,-q Emit less verbose output and hide trivial ;; CHECK-NEXT: warnings. ;; CHECK-EMPTY: -;; CHECK-NEXT: --experimental-poppy Parse wast files as Poppy IR for testing -;; CHECK-NEXT: purposes. -;; CHECK-EMPTY: ;; CHECK-NEXT: --emit-module-names Emit module names, even if not emitting ;; CHECK-NEXT: the rest of the names section. ;; CHECK-EMPTY: diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 4a836ce396e..944f5019987 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -369,9 +369,6 @@ ;; CHECK-NEXT: --pick-load-signs pick load signs based on their ;; CHECK-NEXT: uses ;; CHECK-EMPTY: -;; CHECK-NEXT: --poppify Transform Binaryen IR into Poppy -;; CHECK-NEXT: IR -;; CHECK-EMPTY: ;; CHECK-NEXT: --post-emscripten miscellaneous optimizations for ;; CHECK-NEXT: Emscripten-generated code ;; CHECK-EMPTY: @@ -729,9 +726,6 @@ ;; CHECK-NEXT: --quiet,-q Emit less verbose output and ;; CHECK-NEXT: hide trivial warnings. ;; CHECK-EMPTY: -;; CHECK-NEXT: --experimental-poppy Parse wast files as Poppy IR for -;; CHECK-NEXT: testing purposes. -;; CHECK-EMPTY: ;; CHECK-NEXT: --emit-module-names Emit module names, even if not ;; CHECK-NEXT: emitting the rest of the names ;; CHECK-NEXT: section. diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index ea565c58a7c..0b7b086ba44 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -414,9 +414,6 @@ ;; CHECK-NEXT: --pick-load-signs pick load signs based on their ;; CHECK-NEXT: uses ;; CHECK-EMPTY: -;; CHECK-NEXT: --poppify Transform Binaryen IR into Poppy -;; CHECK-NEXT: IR -;; CHECK-EMPTY: ;; CHECK-NEXT: --post-emscripten miscellaneous optimizations for ;; CHECK-NEXT: Emscripten-generated code ;; CHECK-EMPTY: @@ -774,9 +771,6 @@ ;; CHECK-NEXT: --quiet,-q Emit less verbose output and ;; CHECK-NEXT: hide trivial warnings. ;; CHECK-EMPTY: -;; CHECK-NEXT: --experimental-poppy Parse wast files as Poppy IR for -;; CHECK-NEXT: testing purposes. -;; CHECK-EMPTY: ;; CHECK-NEXT: --emit-module-names Emit module names, even if not ;; CHECK-NEXT: emitting the rest of the names ;; CHECK-NEXT: section. diff --git a/test/lit/help/wasm-reduce.test b/test/lit/help/wasm-reduce.test index 92c12b7810f..d6c9924466f 100644 --- a/test/lit/help/wasm-reduce.test +++ b/test/lit/help/wasm-reduce.test @@ -121,9 +121,6 @@ ;; CHECK-NEXT: --quiet,-q Emit less verbose output and hide trivial ;; CHECK-NEXT: warnings. ;; CHECK-EMPTY: -;; CHECK-NEXT: --experimental-poppy Parse wast files as Poppy IR for testing -;; CHECK-NEXT: purposes. -;; CHECK-EMPTY: ;; CHECK-NEXT: --emit-module-names Emit module names, even if not emitting ;; CHECK-NEXT: the rest of the names section. ;; CHECK-EMPTY: diff --git a/test/lit/help/wasm-split.test b/test/lit/help/wasm-split.test index 542a8b3c382..bb30e285006 100644 --- a/test/lit/help/wasm-split.test +++ b/test/lit/help/wasm-split.test @@ -167,9 +167,6 @@ ;; CHECK-NEXT: --quiet,-q Emit less verbose output and hide trivial ;; CHECK-NEXT: warnings. ;; CHECK-EMPTY: -;; CHECK-NEXT: --experimental-poppy Parse wast files as Poppy IR for testing -;; CHECK-NEXT: purposes. -;; CHECK-EMPTY: ;; CHECK-NEXT: --emit-module-names Emit module names, even if not emitting ;; CHECK-NEXT: the rest of the names section. ;; CHECK-EMPTY: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 36cb0258a4a..9d0e77db95c 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -333,9 +333,6 @@ ;; CHECK-NEXT: --pick-load-signs pick load signs based on their ;; CHECK-NEXT: uses ;; CHECK-EMPTY: -;; CHECK-NEXT: --poppify Transform Binaryen IR into Poppy -;; CHECK-NEXT: IR -;; CHECK-EMPTY: ;; CHECK-NEXT: --post-emscripten miscellaneous optimizations for ;; CHECK-NEXT: Emscripten-generated code ;; CHECK-EMPTY: @@ -693,9 +690,6 @@ ;; CHECK-NEXT: --quiet,-q Emit less verbose output and ;; CHECK-NEXT: hide trivial warnings. ;; CHECK-EMPTY: -;; CHECK-NEXT: --experimental-poppy Parse wast files as Poppy IR for -;; CHECK-NEXT: testing purposes. -;; CHECK-EMPTY: ;; CHECK-NEXT: --emit-module-names Emit module names, even if not ;; CHECK-NEXT: emitting the rest of the names ;; CHECK-NEXT: section. diff --git a/test/lit/passes/poppify-globals.wast b/test/lit/passes/poppify-globals.wast deleted file mode 100644 index 11df5b2fcde..00000000000 --- a/test/lit/passes/poppify-globals.wast +++ /dev/null @@ -1,66 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; TODO: enable validation -;; RUN: wasm-opt %s --poppify --no-validation -all -S -o - | filecheck %s - -(module - ;; CHECK: (type $0 (func (result i32 i64 f32))) - - ;; CHECK: (type $1 (func)) - - ;; 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! - - (global $tuple (mut (tuple i32 i64 f32)) - (tuple.make 3 (global.get $foo) (i64.const 1) (f32.const 2)) - ) - - (global $other-tuple (tuple i32 i64 f32) (global.get $tuple)) - - ;; CHECK: (global $tuple$2 (mut f32) (f32.const 2)) - - ;; CHECK: (global $tuple$1_3 (mut i64) (i64.const 1)) - - ;; CHECK: (global $tuple$0 (mut i32) (global.get $foo)) - - ;; CHECK: (global $other-tuple$2 f32 (global.get $tuple$2)) - - ;; CHECK: (global $other-tuple$1 i64 (global.get $tuple$1_4)) - - ;; CHECK: (global $other-tuple$0 i32 (global.get $tuple$0)) - - ;; CHECK: (func $global-get-tuple (type $0) (result i32 i64 f32) - ;; CHECK-NEXT: (global.get $tuple$0) - ;; CHECK-NEXT: (global.get $tuple$1_4) - ;; CHECK-NEXT: (global.get $tuple$2) - ;; CHECK-NEXT: ) - (func $global-get-tuple (result i32 i64 f32) - (global.get $tuple) - ) - - ;; CHECK: (func $global-set-tuple (type $1) - ;; 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_4 - ;; 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 3 - (i32.const 0) - (i64.const 1) - (f32.const 2) - ) - ) - ) -) diff --git a/test/lit/passes/poppify.wast b/test/lit/passes/poppify.wast deleted file mode 100644 index d22bb874c7b..00000000000 --- a/test/lit/passes/poppify.wast +++ /dev/null @@ -1,515 +0,0 @@ -;; 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 - ;; CHECK: (tag $e (type $3) (param i32)) - (tag $e (param i32)) - - ;; CHECK: (func $id (type $4) (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 (type $5) (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 (type $0) (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 (type $0) (result i32) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - (func $block (result i32) - (block (result i32) - (nop) - (i32.const 0) - ) - ) - - ;; CHECK: (func $nested (type $0) (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 $block (result i32) - (block $block0 (result i32) - (block $block1 (result i32) - (i32.const 0) - ) - ) - ) - ) - - ;; CHECK: (func $nested-nonames (type $0) (result i32) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - (func $nested-nonames (result i32) - (block (result i32) - (block (result i32) - (block (result i32) - (i32.const 0) - ) - ) - ) - ) - - ;; CHECK: (func $child-blocks (type $0) (result i32) - ;; CHECK-NEXT: (block $block (result i32) - ;; CHECK-NEXT: (block $block0 (result i32) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block $block1 (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 $block (result i32) - (i32.add - (block $block0 (result i32) - (i32.const 0) - ) - (block $block1 (result i32) - (i32.const 1) - ) - ) - ) - ) - - ;; CHECK: (func $child-blocks-nonames (type $0) (result i32) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: (i32.add - ;; CHECK-NEXT: (pop i32) - ;; CHECK-NEXT: (pop i32) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $child-blocks-nonames (result i32) - (block (result i32) - (i32.add - (block (result i32) - (i32.const 0) - ) - (block (result i32) - (i32.const 1) - ) - ) - ) - ) - - ;; CHECK: (func $block-br (type $0) (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 (result i32) - (nop) - (br $l - (i32.const 0) - ) - ) - ) - - ;; CHECK: (func $loop (type $2) - ;; CHECK-NEXT: (loop $l - ;; CHECK-NEXT: (br $l) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - (func $loop - (loop $l - (br $l) - ) - ) - - ;; CHECK: (func $if (type $2) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (pop i32) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $if - (if - (i32.const 0) - (then - (nop) - ) - ) - ) - - ;; CHECK: (func $if-else (type $0) (result i32) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: (if (result i32) - ;; CHECK-NEXT: (pop i32) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (else - ;; CHECK-NEXT: (i32.const 2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $if-else (result i32) - (if (result i32) - (i32.const 0) - (then - (i32.const 1) - ) - (else - (i32.const 2) - ) - ) - ) - - ;; CHECK: (func $try-catch (type $0) (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 (result i32) - (do - (throw $e - (i32.const 0) - ) - ) - (catch $e - (pop i32) - ) - (catch_all - (i32.const 1) - ) - ) - ) - - ;; CHECK: (func $try-delegate (type $0) (result i32) - ;; CHECK-NEXT: (try $l0 (result i32) - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (try - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: (throw $e - ;; CHECK-NEXT: (pop i32) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (delegate $l0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (catch $e - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $try-delegate (result i32) - (try $l0 (result i32) - (do - (try - (do - (throw $e - (i32.const 0) - ) - ) - (delegate $l0) - ) - ) - (catch $e - (pop i32) - ) - ) - ) - - ;; CHECK: (func $tuple (type $1) (result i32 i64) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: (i64.const 1) - ;; CHECK-NEXT: ) - (func $tuple (result i32 i64) - (tuple.make 2 - (i32.const 0) - (i64.const 1) - ) - ) - - ;; CHECK: (func $extract-first (type $0) (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 3 0 - (tuple.make 3 - (i32.const 0) - (i64.const 1) - (f32.const 2) - ) - ) - ) - - ;; CHECK: (func $extract-middle (type $6) (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 3 1 - (tuple.make 3 - (i32.const 0) - (i64.const 1) - (f32.const 2) - ) - ) - ) - - ;; CHECK: (func $extract-last (type $7) (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 3 2 - (tuple.make 3 - (i32.const 0) - (i64.const 1) - (f32.const 2) - ) - ) - ) - - ;; CHECK: (func $drop (type $2) - ;; 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 (type $2) - ;; 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 - (tuple.drop 2 - (tuple.make 2 - (i32.const 0) - (i64.const 1) - ) - ) - ) - - ;; CHECK: (func $local-get-tuple (type $1) (result i32 i64) - ;; CHECK-NEXT: (local $x (tuple 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 (tuple i32 i64)) - (local.get $x) - ) - - ;; CHECK: (func $local-set (type $2) - ;; 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 (type $2) - ;; CHECK-NEXT: (local $x (tuple 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 (tuple i32 i64)) - (local.set $x - (tuple.make 2 - (i32.const 0) - (i64.const 1) - ) - ) - ) - - ;; CHECK: (func $local-tee-tuple (type $1) (result i32 i64) - ;; CHECK-NEXT: (local $x (tuple 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 (tuple i32 i64)) - (local.tee $x - (tuple.make 2 - (i32.const 0) - (i64.const 1) - ) - ) - ) - - ;; CHECK: (func $break-tuple (type $1) (result i32 i64) - ;; CHECK-NEXT: (block $l (type $1) (result i32 i64) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: (i64.const 1) - ;; CHECK-NEXT: (br $l - ;; CHECK-NEXT: (pop (tuple i32 i64)) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $break-tuple (result i32 i64) - (block $l (result i32 i64) - (br $l - (tuple.make 2 - (i32.const 0) - (i64.const 1) - ) - ) - ) - ) - - ;; CHECK: (func $return-tuple (type $1) (result i32 i64) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: (i64.const 1) - ;; CHECK-NEXT: (return - ;; CHECK-NEXT: (pop (tuple i32 i64)) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $return-tuple (result i32 i64) - (return - (tuple.make 2 - (i32.const 0) - (i64.const 1) - ) - ) - ) -)