Skip to content
Merged
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
16 changes: 10 additions & 6 deletions src/ir/properties.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@
namespace wasm::Properties {

bool isGenerative(Expression* curr, FeatureSet features) {
// Practically no wasm instructions are generative. Exceptions occur only in
// GC atm.
if (!features.hasGC()) {
return false;
}

struct Scanner : public PostWalker<Scanner> {
bool generative = false;

void visitCall(Call* curr) {
// TODO: We could in principle look at the called function to see if it is
// generative. To do that we'd need to compute generativity like we
// compute global effects (we can't just peek from here, as the
// other function might be modified in parallel).
generative = true;
}
void visitCallIndirect(CallIndirect* curr) { generative = true; }
void visitCallRef(CallRef* curr) { generative = true; }
void visitStructNew(StructNew* curr) { generative = true; }
void visitArrayNew(ArrayNew* curr) { generative = true; }
void visitArrayNewFixed(ArrayNewFixed* curr) { generative = true; }
Expand Down
80 changes: 52 additions & 28 deletions src/passes/OptimizeInstructions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2294,14 +2294,57 @@ struct OptimizeInstructions
// Information about our locals
std::vector<LocalInfo> localInfo;

// Checks if the first is a local.tee and the second a local.get of that same
// index. This is useful in the methods right below us, as it is a common
// pattern where two consecutive inputs are equal despite being syntactically
// different.
bool areMatchingTeeAndGet(Expression* left, Expression* right) {
if (auto* set = left->dynCast<LocalSet>()) {
if (auto* get = right->dynCast<LocalGet>()) {
if (set->isTee() && get->index == set->index) {
return true;
}
}
}
return false;
}

// Check if two consecutive inputs to an instruction are equal. As they are
// consecutive, no code can execeute in between them, which simplies the
// problem here (and which is the case we care about in this pass, which does
// simple peephole optimizations - all we care about is a single instruction
// at a time, and its inputs).
//
// This also checks that the inputs are removable (but we do not assume the
// caller will always remove them).
bool areConsecutiveInputsEqual(Expression* left, Expression* right) {
// When we look for a tee/get pair, we can consider the fallthrough values
// for the first, as the fallthrough happens last (however, we must use
// NoTeeBrIf as we do not want to look through the tee). We cannot do this
// on the second, however, as there could be effects in the middle.
// TODO: Use effects here perhaps.
auto& passOptions = getPassOptions();
left =
Properties::getFallthrough(left,
passOptions,
*getModule(),
Properties::FallthroughBehavior::NoTeeBrIf);
if (areMatchingTeeAndGet(left, right)) {
return true;
}

// Ignore extraneous things and compare them syntactically. We can also
// look at the full fallthrough for both sides now.
left = Properties::getFallthrough(left, passOptions, *getModule());
right = Properties::getFallthrough(right, passOptions, *getModule());
if (!ExpressionAnalyzer::equal(left, right)) {
return false;
}

// To be equal, they must also be known to return the same result
// deterministically.
return !Properties::isGenerative(left, getModule()->features);
}

// Similar to areConsecutiveInputsEqual() but also checks if we can remove
// them (but we do not assume the caller will always remove them).
bool areConsecutiveInputsEqualAndRemovable(Expression* left,
Expression* right) {
// First, check for side effects. If there are any, then we can't even
Expand All @@ -2316,18 +2359,7 @@ struct OptimizeInstructions
return false;
}

// Ignore extraneous things and compare them structurally.
left = Properties::getFallthrough(left, passOptions, *getModule());
right = Properties::getFallthrough(right, passOptions, *getModule());
if (!ExpressionAnalyzer::equal(left, right)) {
return false;
}
// To be equal, they must also be known to return the same result
// deterministically.
if (Properties::isGenerative(left, getModule()->features)) {
return false;
}
return true;
return areConsecutiveInputsEqual(left, right);
}

// Check if two consecutive inputs to an instruction are equal and can also be
Expand All @@ -2341,25 +2373,17 @@ struct OptimizeInstructions
// side effects at all in the middle. For example, a Const in between is ok.
bool areConsecutiveInputsEqualAndFoldable(Expression* left,
Expression* right) {
if (auto* set = left->dynCast<LocalSet>()) {
if (auto* get = right->dynCast<LocalGet>()) {
if (set->isTee() && get->index == set->index) {
return true;
}
}
// TODO: We could probably consider fallthrough values for left, at least
// (since we fold into it).
if (areMatchingTeeAndGet(left, right)) {
return true;
}

// stronger property than we need - we can not only fold
// them but remove them entirely.
return areConsecutiveInputsEqualAndRemovable(left, right);
}

// Similar to areConsecutiveInputsEqualAndFoldable, but only checks that they
// are equal (and not that they are foldable).
bool areConsecutiveInputsEqual(Expression* left, Expression* right) {
// TODO: optimize cases that must be equal but are *not* foldable.
return areConsecutiveInputsEqualAndFoldable(left, right);
}

// Canonicalizing the order of a symmetric binary helps us
// write more concise pattern matching code elsewhere.
void canonicalize(Binary* binary) {
Expand Down
Loading