Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
188 changes: 1 addition & 187 deletions src/ir/stack-utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Nop>()) {
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<Break>()->condition == nullptr;
case Expression::Id::CallId:
return expr->cast<Call>()->isReturn;
case Expression::Id::CallIndirectId:
return expr->cast<CallIndirect>()->isReturn;
case Expression::Id::ReturnId:
case Expression::Id::SwitchId:
case Expression::Id::UnreachableId:
case Expression::Id::ThrowId:
case Expression::Id::RethrowId:
return true;
default:
return false;
}
}

} // namespace StackUtils

StackSignature::StackSignature(Expression* expr) {
std::vector<Type> 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);
Expand Down Expand Up @@ -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<Expression*, size_t> 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<Location> 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<Location>(consumed);
dests[expr] = std::vector<Location>(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<Type> 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
57 changes: 2 additions & 55 deletions src/ir/stack-utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<Expression*, std::vector<Location>>;

// 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
1 change: 0 additions & 1 deletion src/passes/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ set(passes_SOURCES
OptimizeForJS.cpp
OptimizeNoReturn.cpp
PickLoadSigns.cpp
Poppify.cpp
PostEmscripten.cpp
Precompute.cpp
Print.cpp
Expand Down
Loading
Loading