Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
a6cb42c
go
kripken Aug 27, 2026
06240fa
format
kripken Aug 27, 2026
c0a41e4
work
kripken Aug 27, 2026
2d23f5e
work
kripken Aug 27, 2026
71997f9
work
kripken Aug 27, 2026
1490b10
work
kripken Aug 27, 2026
bf44254
work
kripken Aug 27, 2026
e290c24
work
kripken Aug 27, 2026
8e2c218
work
kripken Aug 27, 2026
4117c1e
work
kripken Aug 27, 2026
ea13cd0
work
kripken Aug 27, 2026
ec0eea3
work
kripken Aug 27, 2026
fedaa18
work
kripken Aug 27, 2026
be901a8
work
kripken Aug 27, 2026
d7c1182
work
kripken Aug 27, 2026
5f3bce7
work
kripken Aug 27, 2026
e84e822
work
kripken Aug 27, 2026
f5616bf
work
kripken Aug 27, 2026
ee03cf2
Merge remote-tracking branch 'origin/main' into c.AND
kripken Aug 28, 2026
3b9d045
work
kripken Aug 28, 2026
b47a8f1
work
kripken Aug 28, 2026
012ee79
work
kripken Aug 28, 2026
df6d01c
Merge remote-tracking branch 'origin/main' into c.AND
kripken Sep 2, 2026
39d675c
show bug
kripken Sep 2, 2026
d706843
work
kripken Sep 2, 2026
576ca49
work
kripken Sep 2, 2026
9730b97
work
kripken Sep 2, 2026
e1d1172
fixes
kripken Sep 2, 2026
376845d
work
kripken Sep 2, 2026
a671a63
work
kripken Sep 2, 2026
6005e4c
work
kripken Sep 2, 2026
a0da7e0
work
kripken Sep 2, 2026
d8553ec
work
kripken Sep 2, 2026
4b6f293
work
kripken Sep 2, 2026
4c3b1f5
work
kripken Sep 2, 2026
89ea672
work
kripken Sep 2, 2026
f038bbe
work
kripken Sep 2, 2026
8d4c3ac
work
kripken Sep 2, 2026
f8a2649
work
kripken Sep 2, 2026
68bca39
Add missing cases
kripken Sep 2, 2026
6d2e4ad
test
kripken Sep 2, 2026
b20b331
format
kripken Sep 2, 2026
44cd327
work
kripken Sep 3, 2026
657bfb7
work
kripken Sep 3, 2026
e472334
work
kripken Sep 3, 2026
1e89d01
work
kripken Sep 3, 2026
70e0865
work
kripken Sep 3, 2026
d00e0da
work
kripken Sep 3, 2026
b6a4a76
work
kripken Sep 3, 2026
56dd575
work
kripken Sep 3, 2026
82bf613
work
kripken Sep 4, 2026
5062fef
fix apple compiler error
kripken Sep 4, 2026
8a5e5d5
filter relevant locals in branches
kripken Sep 4, 2026
bc12f04
format
kripken Sep 4, 2026
c5a6836
remove some iterators that seem to fix a compiler error
kripken Sep 4, 2026
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
116 changes: 112 additions & 4 deletions src/ir/constraint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -747,8 +747,21 @@ std::optional<LocalConstraint> LocalConstraint::parse(Expression* curr) {

if (auto* unary = curr->dynCast<Unary>()) {
if (Abstract::getUnary(unary->value->type, Abstract::EqZ) == unary->op) {
// EqZ of EqZ means a check that the value is *not* zero.
if (auto* nested = unary->value->dynCast<Unary>()) {
if (Abstract::getUnary(nested->value->type, Abstract::EqZ) ==
nested->op) {
if (auto* get = nested->value->dynCast<LocalGet>()) {
auto value = Literal::makeZero(get->type);
return LocalConstraint{get->index,
Constraint{Abstract::Ne, {value}}};
}
}
}

return parseEqZArgument(unary->value);
}

return {};
}

Expand Down Expand Up @@ -807,17 +820,107 @@ std::optional<LocalConstraint> LocalConstraint::parse(Expression* curr) {
return {};
}

std::optional<LocalConstraint>
LocalConstraint::parseCondition(Expression* curr) {
ParsedAndedConstraints ParsedAndedConstraints::parse(Expression* curr) {
// The final return value.
ParsedAndedConstraints ret;

// Starting from |curr|, parse and recurse into sub-trees: when we see an AND,
// we push both children as further work.
SmallVector<Expression*, 4> work;
work.push_back(curr);
while (!work.empty()) {
auto* curr = work.back();
work.pop_back();

auto parsed = LocalConstraint::parse(curr);
if (parsed) {
ret.push_back(*parsed);
continue;
}

if (auto* binary = curr->dynCast<Binary>()) {
// An AND can be recursively processed: both sides must be true.
if (Abstract::getBinary(binary->left->type, Abstract::And) ==
binary->op) {
work.push_back(binary->left);
work.push_back(binary->right);
continue;
}
// TODO: support OR
}

// We failed to parse this.
ret.hasUnknown = true;
}

return ret;
}

ParsedAndedConstraints
ParsedAndedConstraints::parseCondition(Expression* curr) {
// A get by itself is a check for not being null.
if (auto* get = curr->dynCast<LocalGet>()) {
auto value = Literal::makeZero(get->type);
return LocalConstraint{get->index, Constraint{Abstract::Ne, {value}}};
return {LocalConstraint{get->index, Constraint{Abstract::Ne, {value}}}};
}

// Otherwise, parse normally.
return parse(curr);
};
}

void ParsedAndedConstraints::negate() {
if (empty()) {
return;
}

if (hasUnknown) {
// This includes things we don't know about, and don't know how to negate.
clear();
return;
}

// The input is a list of constraints all applying at once, A & B & C. The
// negation is !A | !B | !C, but we cannot express a general OR like that,
// except in the simple case where they all talk about the same local: then
// we can at least approximateOr them all into one constraint.
auto& self = *this;
for (Index i = 1; i < size(); i++) {
if (self[i].local != self[0].local) {
// They refer to different locals. Give up.
clear();
return;
}
}

// Negate them before the OR.
for (auto& pair : self) {
pair.constraint = pair.constraint.negate();
}

if (size() == 1) {
// The simple case of 1 doesn't need any more work.
return;
}

// Do the OR.
AndedConstraintSet anded;
anded.set(self[0].constraint);
for (Index i = 1; i < size(); i++) {
anded.approximateOr({self[i].constraint});
if (anded.provesNothing()) {
// We have nothing useful here.
clear();
return;
}
}

// Return only the OR'ed result.
auto local = self[0].local;
clear();
for (auto& c : anded) {
emplace_back(local, c);
}
}

void LocalConstraint::flip() {
auto other = std::get<Index>(constraint.term);
Expand Down Expand Up @@ -1152,6 +1255,11 @@ std::ostream& operator<<(std::ostream& o, const Constraint& c) {
return o;
}

std::ostream& operator<<(std::ostream& o, const LocalConstraint& c) {
o << "LocalConstraint{$" << c.local << ", " << c.constraint << '}';
return o;
}

std::ostream& operator<<(std::ostream& o, const AndedConstraintSet& set) {
if (set.provesEverything()) {
o << "AndedConstraintSet(contradiction)";
Expand Down
44 changes: 40 additions & 4 deletions src/ir/constraint.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

#include "ir/abstract.h"
#include "support/inplace_vector.h"
#include "support/small_vector.h"
#include "support/span.h"
#include "support/utilities.h"
#include "wasm.h"
Expand Down Expand Up @@ -235,6 +236,12 @@ struct LocalConstraint {
Index local;
Constraint constraint;

LocalConstraint() = default;
LocalConstraint(Index local, Constraint constraint)
: local(local), constraint(std::move(constraint)) {}

bool operator==(const LocalConstraint&) const = default;

// Try to parse BinaryenIR into a local to which a constraint is applied. For
// example
//
Expand All @@ -246,15 +253,43 @@ struct LocalConstraint {
//
static std::optional<LocalConstraint> parse(Expression* curr);

// Parse in a condition context, i.e., where (local.get $x) is the same as
// $x != 0 (e.g., in an if condition, or a br_on ref).
static std::optional<LocalConstraint> parseCondition(Expression* curr);

// Reverse the constraint. The constraint's term must, of course, be another
// local.
void flip();
};

// A utility to parse BinaryenIR into locals and constraints on them. This is
// similar to LocalConstraint::parse, but that parses a single constraint, while
// this can handle a list of ANDed ones:
//
// (i32.and (..A..) (..B..))
//
// parses into [ A, B ].
//
// We also set a field |hasUnknown| if we saw things we could not parse. E.g.
//
// (i32.and (call $unknown) (i32.eqz (local.get $x)))
//
// This parses into [ $x == 0 ] and sets hasUnknown=true. Even if there are
// unknown things, we do know that definitely $x == 0 at least, which is useful
// in some cases.
struct ParsedAndedConstraints : public SmallVector<LocalConstraint, 1> {
using SmallVector<LocalConstraint, 1>::SmallVector;

bool hasUnknown = false;

static ParsedAndedConstraints parse(Expression* curr);

// Parse in a condition context, i.e., where (local.get $x) is the same as
// $x != 0 (e.g., in an if condition, or a br_on ref).
static ParsedAndedConstraints parseCondition(Expression* curr);

// Negate the entire list of constraints. If we fail to generate something
// that can be represented as a list of ANDed constraints, the list will be
// empty (i.e., we can prove nothing).
void negate();
};

// A map of locals and their constraints, representing the state at a basic
// block. We use the following representation:
//
Expand Down Expand Up @@ -366,6 +401,7 @@ struct BasicBlockConstraintMap {
};

std::ostream& operator<<(std::ostream& o, const Constraint& c);
std::ostream& operator<<(std::ostream& o, const LocalConstraint& c);
std::ostream& operator<<(std::ostream& o, const AndedConstraintSet& set);

} // namespace wasm::constraint
Expand Down
83 changes: 51 additions & 32 deletions src/passes/ConstraintAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@
// function analysis).
//

#include <algorithm>

#include "cfg/cfg-traversal.h"
#include "ir/constraint.h"
#include "ir/drop.h"
Expand Down Expand Up @@ -146,11 +148,12 @@ struct ConstraintAnalysis

void maybeMarkRelevant(Expression* curr) {
// If this parses into a constraint on a local, that local is relevant.
if (auto parsed = LocalConstraint::parseCondition(curr);
parsed && isRelevantType(getFunction()->getLocalType(parsed->local))) {
relevantLocals[parsed->local] = true;
if (auto* other = std::get_if<Index>(&parsed->constraint.term)) {
relevantLocals[*other] = true;
for (auto& pair : ParsedAndedConstraints::parseCondition(curr)) {
if (isRelevantType(getFunction()->getLocalType(pair.local))) {
relevantLocals[pair.local] = true;
if (auto* other = std::get_if<Index>(&pair.constraint.term)) {
relevantLocals[*other] = true;
}
}
}
}
Expand Down Expand Up @@ -371,9 +374,9 @@ struct ConstraintAnalysis
// Find the constraints sent to this specific successor, if there is a
// branch, and use them.
if (auto branch = getBranchConstraints(block, out);
branch && checkRelevancy(*branch)) {
filterRelevant(branch), !branch.empty()) {
auto sentConstraints = constraints;
applyBranchConstraints(*branch, sentConstraints);
applyBranchConstraints(branch, sentConstraints);
#if CONSTRAINT_DEBUG
std::cout << block << " sending branch to " << out
<< " with sent constraints: " << sentConstraints << '\n';
Expand Down Expand Up @@ -441,6 +444,9 @@ struct ConstraintAnalysis
void optimizeExpression(Expression** currp,
const BasicBlockConstraintMap& constraints) {
auto* curr = *currp;
// Note that we don't need to try to parse a series of constraints with
// ParsedAndedConstraints: if there is a tree of ANDed things, we will
// simply optimize it as we walk it, each time handling one.
auto parsed = LocalConstraint::parse(curr);
if (!parsed) {
return;
Expand Down Expand Up @@ -472,8 +478,8 @@ struct ConstraintAnalysis

// Given a predecessor and one of its successors, find new constraints that
// can be added due to the flow to that specific successor.
std::optional<LocalConstraint> getBranchConstraints(BasicBlock* pred,
BasicBlock* succ) {
ParsedAndedConstraints getBranchConstraints(BasicBlock* pred,
BasicBlock* succ) {
auto* brancher = pred->contents.brancher;
if (!brancher) {
return {};
Expand Down Expand Up @@ -502,32 +508,31 @@ struct ConstraintAnalysis
return {};
}

std::optional<LocalConstraint> getConstraintsFromIf(If* iff,
bool physicalSuccessor) {
auto parsed = LocalConstraint::parseCondition(iff->condition);
if (parsed && !physicalSuccessor) {
ParsedAndedConstraints getConstraintsFromIf(If* iff, bool physicalSuccessor) {
auto parsed = ParsedAndedConstraints::parseCondition(iff->condition);
if (!physicalSuccessor) {
// We are in the ifFalse, so negate the condition.
parsed->constraint = parsed->constraint.negate();
parsed.negate();
}
return parsed;
}

std::optional<LocalConstraint>
getConstraintsFromBreak(Break* br, bool physicalSuccessor) {
ParsedAndedConstraints getConstraintsFromBreak(Break* br,
bool physicalSuccessor) {
// We get here when there is more than one successor, so there must be a
// condition.
assert(br->condition);

auto parsed = LocalConstraint::parseCondition(br->condition);
if (parsed && physicalSuccessor) {
auto parsed = ParsedAndedConstraints::parseCondition(br->condition);
if (physicalSuccessor) {
// The branch was not taken, so negate the condition.
parsed->constraint = parsed->constraint.negate();
parsed.negate();
}
return parsed;
}

std::optional<LocalConstraint>
getConstraintsFromBrOn(BrOn* brOn, bool physicalSuccessor) {
ParsedAndedConstraints getConstraintsFromBrOn(BrOn* brOn,
bool physicalSuccessor) {
// The constraint on that local depends on the op.
// TODO: Handle BrOnCast* etc using subtyping operations.
if (brOn->op != BrOnNull && brOn->op != BrOnNonNull) {
Expand All @@ -537,10 +542,10 @@ struct ConstraintAnalysis
// parseCondition can parse more things than a local.get, which is all we
// handle here, but there is no other valid IR that can appear there, so we
// can reuse it.
auto parsed = LocalConstraint::parseCondition(brOn->ref);
auto parsed = ParsedAndedConstraints::parseCondition(brOn->ref);
// Negate depending on the op and (similar to Break) the successor.
if (parsed && ((brOn->op == BrOnNull) ^ physicalSuccessor)) {
parsed->constraint = parsed->constraint.negate();
if ((brOn->op == BrOnNull) ^ physicalSuccessor) {
parsed.negate();
}
return parsed;
}
Expand Down Expand Up @@ -664,17 +669,31 @@ struct ConstraintAnalysis
return true;
}

// Filters out constraints on irrelevant locals.
void filterRelevant(ParsedAndedConstraints& parsed) {
parsed.erase(std::remove_if(parsed.begin(),
parsed.end(),
[&](const LocalConstraint& pair) {
return !checkRelevancy(pair);
}),
parsed.end());
}

// Apply branch constraints to the current set of constraints.
void applyBranchConstraints(const LocalConstraint& branch,
void applyBranchConstraints(const ParsedAndedConstraints& branch,
BasicBlockConstraintMap& constraints) {
// Extend the range of values in the "jump ahead" manner described in the
// top-level comment.
if (applyBranchRangeExtensionToConstraints(branch, constraints)) {
return;
}
for (auto& pair : branch) {
// Extend the range of values in the "jump ahead" manner described in the
// top-level comment.
if (!applyBranchRangeExtensionToConstraints(pair, constraints)) {
// Otherwise, apply the constraint normally.
constraints.approximateAnd(pair.local, pair.constraint);
}

// Otherwise, apply the constraint normally.
constraints.approximateAnd(branch.local, branch.constraint);
if (constraints.unreachable) {
return;
}
}
}

bool
Expand Down
1 change: 0 additions & 1 deletion src/support/inplace_vector.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,6 @@ template<typename T, size_t N> class inplace_vector {
ConstIterator(const Iterator& other)
: wasm::ParentIndexIterator<const inplace_vector<T, N>*, ConstIterator>{
other.parent, other.index} {}
ConstIterator(const ConstIterator& other) = default;

const T& operator*() const { return (*this->parent)[this->index]; }
const T* operator->() const { return &(*this->parent)[this->index]; }
Expand Down
Loading
Loading