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
101 changes: 101 additions & 0 deletions src/ir/constraint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,107 @@ void BasicBlockConstraintMap::set(Index index, const Constraint& c) {
approximateAnd(index, c);
}

void BasicBlockConstraintMap::set(Index index,
const AndedConstraintSet& constraints) {
// As above, but with a loop after.
assert(!unreachable);
eraseStaleRefs(index);
map.erase(index);

// Apply the constraints, if there are any.
if (constraints.provesNothing()) {
setProvesNothing(index);
} else {
for (auto& c : constraints) {
approximateAnd(index, c);
}
}
}

void BasicBlockConstraintMap::set(Index index, Expression* value) {
using namespace Match;
using namespace Abstract;

// Apply a constraint to a value, x = C.
if (Properties::isSingleConstantExpression(value)) {
auto c = Properties::getLiteral(value);
set(index, Constraint{Abstract::Eq, {c}});
return;
}

// Apply a constraint to a local, x = y.
if (auto* get = value->dynCast<LocalGet>()) {
set(index, Constraint{Abstract::Eq, {get->index}});
return;
}

// Apply an increment of a local, x = y + 1.
Index y;
if (matches(value, binary(Abstract::Add, local(&y), ival(1)))) {
// The local y must have old constraints that we know how to increment.
auto old = get(y);

// Iterate over the old constraints and increment each one.
auto success = true;
for (auto& c : old) {
auto* N = std::get_if<Literal>(&c.term);
if (!N) {
// A non-constant term, which we don't know how to increment.
success = false;
break;
}

switch (c.op) {
// x == N, x++ => x == N+1.
case Eq:
*N = N->add(Literal::makeFromInt32(1, N->type));
continue;
// x >= N, x++ => x > N
case GeS:
c.op = GtS;
continue;
case GeU:
c.op = GtU;
continue;
// x < N, x++ => x <= N
case LtS:
c.op = LeS;
continue;
case LtU:
c.op = LeU;
continue;
// x <= N, x++ => x <= N+1 if no overflow
case LeS:
if (N->isSignedMax()) {
success = false;
break;
}
*N = N->add(Literal::makeFromInt32(1, N->type));
continue;
case LeU:
if (N->isUnsignedMax()) {
success = false;
break;
}
*N = N->add(Literal::makeFromInt32(1, N->type));
continue;
default:
// Something we don't recognize.
success = false;
break;
}
}

if (success) {
set(index, old);
return;
}
}

// We know and can prove nothing.
setProvesNothing(index);
}

void BasicBlockConstraintMap::setProvesNothing(Index index) {
assert(!unreachable);
eraseStaleRefs(index);
Expand Down
8 changes: 7 additions & 1 deletion src/ir/constraint.h
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,15 @@ struct BasicBlockConstraintMap {
assert(map.empty());
}

// Apply a constraint to a local.
// Apply a constraint to a local, replacing anything before.
void set(Index index, const Constraint& c);

// Apply a set of constraints to a local, replacing anything before.
void set(Index index, const AndedConstraintSet& constraints);

// Set the value in an expression to a local, replacing anything before.
void set(Index index, Expression* value);

// Mark a local as unknown and able to prove nothing.
void setProvesNothing(Index index);

Expand Down
19 changes: 19 additions & 0 deletions src/ir/match.h
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,18 @@ SelectMatcher(Select** binder, S1&& s1, S2&& s2, S3&& s3) {
return Matcher<Select*, S1, S2, S3>(binder, {}, s1, s2, s3);
}

// LocalGet
template<> struct NumComponents<LocalGet*> {
static constexpr size_t value = 1;
};
template<> struct GetComponent<LocalGet*, 0> {
Index operator()(LocalGet* curr) { return curr->index; }
};
template<class S>
inline decltype(auto) LocalGetMatcher(LocalGet** binder, S&& s) {
return Matcher<LocalGet*, S>(binder, {}, s);
}

} // namespace Internal

// Public matching API
Expand Down Expand Up @@ -878,6 +890,13 @@ inline decltype(auto) select(Select** binder, S1&& s1, S2&& s2, S3&& s3) {
return Internal::SelectMatcher(binder, s1, s2, s3);
}

inline decltype(auto) local() {
return Internal::LocalGetMatcher(nullptr, Internal::Any<Index>(nullptr));
}
inline decltype(auto) local(Index* binder) {
return Internal::LocalGetMatcher(nullptr, Internal::Any(binder));
}

} // namespace wasm::Match

#endif // wasm_ir_match_h
89 changes: 80 additions & 9 deletions src/passes/ConstraintAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@
#include "wasm-builder.h"
#include "wasm.h"

#define CONSTRAINT_DEBUG 0

#ifndef CONSTRAINT_DEBUG
#define CONSTRAINT_DEBUG 0
#endif

namespace wasm {

using namespace wasm::constraint;
Expand Down Expand Up @@ -187,6 +193,7 @@ struct ConstraintAnalysis
}

computeRelevantLocals();
prepareToFlow();
flow();
optimize();
}
Expand Down Expand Up @@ -217,9 +224,42 @@ struct ConstraintAnalysis
}
}

// Maintain a maximum amount of operations. The one non-linear thing that can
// happen is when we increment a local in a loop: it may go from 0 to 1, then
// branch back to the top and merge, making it in the range [0, 1], then get
// incremented and loop again, leading to [0, 2] and so forth, only stopping
// when it reaches the loop bound, which may be very high. We don't want to
// spend significant time on such constant operations, as other passes will
// propagate them anyhow, so we keep our time bounded. When this reaches 0,
// we will not do loop operations that might lead to such incrementing.
Index maxWorkLeft = 0;

void prepareToFlow() {
// Compute a bound for maxOperations. flow() will spend time on each block,
// operation in a block, and branch, so add all those up.
for (auto& block : basicBlocks) {
maxWorkLeft += 1 + block->contents.actions.size() + block->out.size();
}

// We also allow a multiple of all the above: loop optimization generally
// requires us to process it twice (so that we see the merge at the top).
// Use a constant of 3 to make sure to work enough.
maxWorkLeft *= 3;
}

void decMaxWork() {
if (maxWorkLeft > 0) {
maxWorkLeft--;
}
}

// Flow infos around until we have inferred all we can about the constraints
// in each location.
void flow() {
#if CONSTRAINT_DEBUG
dumpCFG("flow");
#endif

// Start from the entry as the only reachable block. That block has incoming
// values - defaults - for each var.
entry->contents.startConstraints.setReachable();
Expand Down Expand Up @@ -247,18 +287,34 @@ struct ConstraintAnalysis
// Starting from the entry, keep going while we find something new.
UniqueDeferredQueue<BasicBlock*> work;
work.push(entry);

while (!work.empty()) {
auto* block = work.pop();

decMaxWork();

// Start at the top of the block, then go through, applying things.
BasicBlockConstraintMap constraints = block->contents.startConstraints;

#if CONSTRAINT_DEBUG
std::cout << block << " start constraints: " << constraints << '\n';
#endif

for (auto** currp : block->contents.actions) {
applyToConstraints(*currp, constraints);

decMaxWork();
}

#if CONSTRAINT_DEBUG
std::cout << block << " end constraints: " << constraints << '\n';
#endif

// We now know the values at the end of the block. Flow it onward, and
// where it causes changes, queue more work.
for (auto* out : block->out) {
decMaxWork();

auto& outStartConstraints = out->contents.startConstraints;

// Find the constraints sent to this specific successor, if there is a
Expand All @@ -267,14 +323,27 @@ struct ConstraintAnalysis
branch && checkRelevancy(*branch)) {
auto sentConstraints = constraints;
sentConstraints.approximateAnd(branch->local, branch->constraint);
#if CONSTRAINT_DEBUG
std::cout << block << " sending branch to " << out
<< " with sent constraints: " << sentConstraints << '\n';
#endif
// If anything changed at the start of the target block, flow onwards.
if (outStartConstraints.approximateOr(sentConstraints)) {
#if CONSTRAINT_DEBUG
std::cout << "out's start after " << outStartConstraints << '\n';
std::cout << block << " branch-modified " << out
<< " to start with: " << outStartConstraints << '\n';
#endif
work.push(out);
}
} else {
// There are no specific branch constraints, so send the unmodified
// |constraints|, avoiding a copy.
if (outStartConstraints.approximateOr(constraints)) {
#if CONSTRAINT_DEBUG
std::cout << block << " modified " << out
<< " to start with: " << outStartConstraints << '\n';
#endif
work.push(out);
}
}
Expand All @@ -293,6 +362,9 @@ struct ConstraintAnalysis
// of course not needed at this stage.)
auto& constraints = block->contents.startConstraints;
for (auto** currp : block->contents.actions) {
#if CONSTRAINT_DEBUG
std::cout << block << " trying to optimize " << **currp << '\n';
#endif
if (!constraints.unreachable) {
applyToConstraints(*currp, constraints);
optimizeExpression(currp, constraints);
Expand Down Expand Up @@ -432,17 +504,16 @@ struct ConstraintAnalysis
// No point to apply a constraint to an irrelevant local.
return;
}
if (Properties::isSingleConstantExpression(set->value)) {
// Apply a constraint to this value.
auto value = Properties::getLiteral(set->value);
constraints.set(set->index, Constraint{Abstract::Eq, {value}});
} else if (auto* get = set->value->dynCast<LocalGet>()) {
// Apply a constraint to this local.
constraints.set(set->index, Constraint{Abstract::Eq, {get->index}});
} else {
// We know and can prove nothing.

// The only binary operation we match is an increment (x + 1), and we do
// not always want to apply it: only when we are allowed to keep working
// (see above).
if (set->value->is<Binary>() && !maxWorkLeft) {
constraints.setProvesNothing(set->index);
return;
}

constraints.set(set->index, set->value);
}
}

Expand Down
Loading
Loading