Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
214 commits
Select commit Hold shift + click to select a range
fdd5024
go
kripken May 7, 2026
aabd860
go
kripken May 7, 2026
3332b8a
Merge remote-tracking branch 'origin/main' into range.analysis
kripken Jun 2, 2026
8b18012
work
kripken Jun 2, 2026
002d237
work
kripken Jun 2, 2026
229ae81
work
kripken Jun 2, 2026
5598aec
work
kripken Jun 2, 2026
ed255d4
work
kripken Jun 2, 2026
e5d8299
work
kripken Jun 2, 2026
fa4805f
work
kripken Jun 3, 2026
ae146cf
work
kripken Jun 3, 2026
4748cc5
work
kripken Jun 3, 2026
f7e33cc
work
kripken Jun 3, 2026
80a843e
work
kripken Jun 3, 2026
2b10d87
work
kripken Jun 3, 2026
c9f17bc
work
kripken Jun 3, 2026
18a691b
form
kripken Jun 3, 2026
5e8c7c3
work
kripken Jun 3, 2026
3b22e48
work
kripken Jun 3, 2026
08e419d
work
kripken Jun 3, 2026
70add1c
work
kripken Jun 3, 2026
15c806a
work
kripken Jun 3, 2026
c30dcda
work
kripken Jun 3, 2026
c74abed
work
kripken Jun 3, 2026
da40546
work
kripken Jun 3, 2026
ddc615e
work
kripken Jun 3, 2026
2707bb1
work
kripken Jun 3, 2026
6390870
format
kripken Jun 3, 2026
3cedbcf
work
kripken Jun 3, 2026
f54afaa
work
kripken Jun 3, 2026
8071200
work
kripken Jun 3, 2026
b550c4d
work
kripken Jun 3, 2026
b1aedcf
work
kripken Jun 3, 2026
0bc8d2e
work
kripken Jun 3, 2026
61fc656
work
kripken Jun 3, 2026
3a3970b
work
kripken Jun 3, 2026
8a6d355
work
kripken Jun 3, 2026
562872d
work
kripken Jun 3, 2026
d296092
go
kripken Jun 3, 2026
a46436f
go
kripken Jun 3, 2026
413f4e8
go
kripken Jun 3, 2026
817a9ca
go
kripken Jun 3, 2026
c14b3b9
go
kripken Jun 3, 2026
35329c8
work
kripken Jun 3, 2026
1dfe44c
go
kripken Jun 3, 2026
6e6d06e
f
kripken Jun 3, 2026
c9f67d1
go
kripken Jun 3, 2026
5f1fdc8
go
kripken Jun 3, 2026
c339b79
go
kripken Jun 3, 2026
d673317
go
kripken Jun 3, 2026
c4321a9
go
kripken Jun 4, 2026
260fb83
go
kripken Jun 4, 2026
4fe70e3
go
kripken Jun 4, 2026
c0d19d9
go
kripken Jun 4, 2026
e6f2d34
go
kripken Jun 4, 2026
200566c
go
kripken Jun 4, 2026
83e845e
go
kripken Jun 4, 2026
e16096b
go
kripken Jun 4, 2026
494f67a
go
kripken Jun 4, 2026
6e76ac0
go
kripken Jun 4, 2026
cf2b0e4
go
kripken Jun 4, 2026
2a02004
form
kripken Jun 4, 2026
279dc39
work
kripken Jun 4, 2026
6e8f576
work
kripken Jun 4, 2026
fcd7e41
work
kripken Jun 4, 2026
6537ace
work
kripken Jun 4, 2026
61dd31e
work
kripken Jun 4, 2026
783e6ff
work
kripken Jun 4, 2026
3e0055e
go
kripken Jun 4, 2026
c1e3dc8
go
kripken Jun 4, 2026
b13c894
go
kripken Jun 4, 2026
d7f5732
go
kripken Jun 4, 2026
362fb95
go
kripken Jun 4, 2026
05d9e29
go
kripken Jun 4, 2026
bc1f4c9
go
kripken Jun 4, 2026
70cd80e
go
kripken Jun 4, 2026
bf12966
go
kripken Jun 4, 2026
67b6707
go
kripken Jun 5, 2026
b9fe8c0
go
kripken Jun 5, 2026
6bae184
go
kripken Jun 5, 2026
6706a99
go
kripken Jun 5, 2026
62cba65
go
kripken Jun 5, 2026
c3089ea
go
kripken Jun 5, 2026
7c5b0b7
go
kripken Jun 5, 2026
958a6d4
go
kripken Jun 5, 2026
e4079e9
go
kripken Jun 5, 2026
31a0ea5
go
kripken Jun 5, 2026
d78f0ef
go
kripken Jun 5, 2026
e78d295
go
kripken Jun 5, 2026
c9eaeed
go
kripken Jun 5, 2026
8884744
go
kripken Jun 5, 2026
eda589a
go
kripken Jun 5, 2026
72e40e4
go
kripken Jun 5, 2026
54cd47e
go
kripken Jun 5, 2026
9e1af11
go
kripken Jun 5, 2026
561c80f
go
kripken Jun 5, 2026
f29d198
go
kripken Jun 5, 2026
3d4a7bb
go
kripken Jun 5, 2026
3cabc2e
go
kripken Jun 5, 2026
690a5ab
go
kripken Jun 5, 2026
840f863
go
kripken Jun 5, 2026
e8a4a80
go
kripken Jun 5, 2026
bd77752
go
kripken Jun 8, 2026
94a3c35
go
kripken Jun 8, 2026
4c5b867
go
kripken Jun 8, 2026
f828d9d
format
kripken Jun 8, 2026
6036296
go
kripken Jun 8, 2026
889658d
go
kripken Jun 8, 2026
724ecb9
go
kripken Jun 8, 2026
0fc9e55
go
kripken Jun 8, 2026
5ad1b75
go
kripken Jun 8, 2026
8181363
go
kripken Jun 8, 2026
d0ad2f4
go
kripken Jun 8, 2026
c5b7d1d
go
kripken Jun 8, 2026
0e35b2b
go
kripken Jun 8, 2026
60d50b4
go
kripken Jun 8, 2026
626b5d7
feedback
kripken Jun 8, 2026
5896406
feedback
kripken Jun 8, 2026
cf29b58
fix
kripken Jun 8, 2026
cdaff6b
clean
kripken Jun 8, 2026
c02ba2e
Merge remote-tracking branch 'myself/inplace' into constraint.by.itself
kripken Jun 8, 2026
94d2161
clean
kripken Jun 8, 2026
735d7ea
clean
kripken Jun 8, 2026
6e80fde
const
kripken Jun 8, 2026
cf7fcc6
undo CFP change
kripken Jun 8, 2026
ac02454
Merge branch 'inplace' into constraint.by.itself
kripken Jun 8, 2026
0930461
tidy
kripken Jun 8, 2026
7b7d2ac
add.assert
kripken Jun 8, 2026
679bd24
fix.comment
kripken Jun 8, 2026
920e7a9
Merge remote-tracking branch 'origin/main' into constraint.by.itself
kripken Jun 8, 2026
602a8c3
merg
kripken Jun 8, 2026
44ad794
value => term
kripken Jun 9, 2026
2f0bdd7
check => eval
kripken Jun 10, 2026
7f77016
fix new clang-18 warning
kripken Jun 10, 2026
daf2208
Merge remote-tracking branch 'myself/constraint.by.itself' into const…
kripken Jun 10, 2026
59f5a09
update
kripken Jun 10, 2026
ed455bd
update
kripken Jun 10, 2026
03e91b6
undo
kripken Jun 10, 2026
6177a16
update
kripken Jun 10, 2026
af791b1
update
kripken Jun 10, 2026
cb35706
update
kripken Jun 10, 2026
c0f54ad
update
kripken Jun 10, 2026
745116c
update
kripken Jun 10, 2026
4c8dcca
update
kripken Jun 10, 2026
725c08d
update
kripken Jun 10, 2026
3e96dcc
update
kripken Jun 10, 2026
03ec477
update
kripken Jun 10, 2026
945e042
update
kripken Jun 10, 2026
41ffb72
update
kripken Jun 10, 2026
ba50172
update
kripken Jun 10, 2026
613ef95
update
kripken Jun 10, 2026
923a42a
update
kripken Jun 10, 2026
5e432a4
2.bad
kripken Jun 10, 2026
2da66f7
update
kripken Jun 10, 2026
3ef82bb
update
kripken Jun 10, 2026
b05dba6
update
kripken Jun 10, 2026
564b3cb
update
kripken Jun 10, 2026
4c3eef2
update
kripken Jun 10, 2026
f40a39d
form
kripken Jun 10, 2026
8a0286a
Merge remote-tracking branch 'origin/main' into constraint
kripken Jun 10, 2026
0d2f0ed
rename
kripken Jun 10, 2026
517dac0
update
kripken Jun 10, 2026
9522de6
update
kripken Jun 10, 2026
0e358e3
update
kripken Jun 10, 2026
a178135
update
kripken Jun 10, 2026
e09cbda
update
kripken Jun 10, 2026
0cb581a
update
kripken Jun 10, 2026
91cc337
update
kripken Jun 10, 2026
ea3db17
refactor to use more helpers and less nesting
kripken Jun 12, 2026
060d8aa
format
kripken Jun 12, 2026
088a425
simpl
kripken Jun 12, 2026
9267485
form
kripken Jun 12, 2026
3965c27
simpl
kripken Jun 12, 2026
280e7b3
remove
kripken Jun 12, 2026
26dfc30
move code as requested
kripken Jun 12, 2026
c1dacec
eval => proves
kripken Jun 12, 2026
3952dfc
form
kripken Jun 12, 2026
224425c
Update src/ir/constraint.cpp
kripken Jun 12, 2026
203d455
work
kripken Jun 12, 2026
e4a2a49
fix set op
kripken Jun 12, 2026
22d2d3b
fuzzyAnd, and make it drop new ones automatically, as suggested
kripken Jun 12, 2026
bdc4911
avoid fuzzy, use approximate
kripken Jun 16, 2026
9b4a261
Merge remote-tracking branch 'origin/main' into constraint.by.itself
kripken Jun 16, 2026
c924041
remove Invalid
kripken Jun 16, 2026
8e5e075
Merge remote-tracking branch 'myself/constraint.by.itself' into const…
kripken Jun 16, 2026
90aabfa
merg
kripken Jun 16, 2026
81714b2
merg
kripken Jun 16, 2026
edf8059
work
kripken Jun 16, 2026
b1522c2
go
kripken Jun 16, 2026
97ec6b5
fix
kripken Jun 16, 2026
966676d
go
kripken Jun 16, 2026
9645e71
Update src/ir/constraint.h
kripken Jun 16, 2026
64e0a86
Update src/ir/constraint.h
kripken Jun 16, 2026
0e0bfb7
Update src/ir/constraint.h
kripken Jun 16, 2026
6947fc3
Update src/ir/constraint.h
kripken Jun 16, 2026
2b30208
format
kripken Jun 16, 2026
464aba5
Merge remote-tracking branch 'myself/constraint.by.itself' into const…
kripken Jun 16, 2026
82aad7d
Merge remote-tracking branch 'origin/main' into constraint
kripken Jun 17, 2026
62e5ee9
merg
kripken Jun 17, 2026
835d863
clean
kripken Jun 17, 2026
6aec092
fix
kripken Jun 17, 2026
c014633
nice
kripken Jun 17, 2026
81f2421
form
kripken Jun 17, 2026
4f9fb9d
help
kripken Jun 17, 2026
dab23fb
avoid warning
kripken Jun 17, 2026
26b1b62
avoid warning
kripken Jun 17, 2026
bda6665
avoid warning
kripken Jun 17, 2026
9dbf1d9
add todo
kripken Jun 17, 2026
1981731
rename
kripken Jun 18, 2026
ec2c0d5
format
kripken Jun 18, 2026
53974d7
test loops, showing the problem
kripken Jun 18, 2026
e575105
reverse
kripken Jun 18, 2026
aec9542
format
kripken Jun 18, 2026
92e5efd
fix.gtest
kripken Jun 18, 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: 110 additions & 6 deletions src/ir/constraint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,9 @@ void AndedConstraintSet::approximateAnd(const Constraint& c) {

void AndedConstraintSet::approximateOr(const AndedConstraintSet& other) {
// If one is empty (no constraints, everything is true, and we can prove
// nothing useful) then it does not add anything to the other.
if (empty()) {
*this = other;
return;
}
if (other.empty()) {
// nothing useful) then we can prove nothing after the OR.
if (empty() || other.empty()) {
clear();
return;
}

Expand All @@ -153,4 +150,111 @@ void AndedConstraintSet::approximateOr(const AndedConstraintSet& other) {
clear();
}

std::optional<LocalConstraint> LocalConstraint::parse(Expression* curr) {
auto parseEqZArgument =
[&](Expression* value) -> std::optional<LocalConstraint> {
if (auto* get = value->dynCast<LocalGet>()) {
// Canonicalize EqZ to Eq of 0.
auto value = Literal::makeZero(get->type);
return LocalConstraint{get->index, Constraint{Abstract::Eq, {value}}};
}
// TODO: Recursively parse and reverse a constraint
return {};

This comment was marked as resolved.

};

if (auto* unary = curr->dynCast<Unary>()) {
if (Abstract::getUnary(unary->type, Abstract::EqZ) == unary->op) {
return parseEqZArgument(unary->value);
}
return {};
}

if (auto* refIsNull = curr->dynCast<RefIsNull>()) {
return parseEqZArgument(refIsNull->value);
}

// Parse a get or a constant.
auto parseTerm = [&](Expression* expr) -> std::optional<Term> {
if (auto* get = expr->dynCast<LocalGet>()) {
return Term{get->index};
}
if (Properties::isSingleConstantExpression(expr)) {
return Term{Properties::getLiteral(expr)};
}
return {};
};

auto parseBinaryArguments =
[&](Abstract::Op op,
Expression* left,
Expression* right) -> std::optional<LocalConstraint> {
// The left must be a get.
if (auto* get = left->dynCast<LocalGet>()) {
// The right can be any term.
if (auto value = parseTerm(right)) {
return LocalConstraint{get->index, Constraint{op, *value}};
}
}
return {};
};

if (auto* binary = curr->dynCast<Binary>()) {
// The operation must be one we recognize.
for (auto op : {Abstract::Eq, Abstract::Ne}) {
if (Abstract::getBinary(binary->type, op) == binary->op) {
return parseBinaryArguments(op, binary->left, binary->right);
}
}
return {};
}

if (auto* refEq = curr->dynCast<RefEq>()) {
return parseBinaryArguments(Abstract::Eq, refEq->left, refEq->right);
}

return {};
}

void LocalConstraintMap::approximateOr(const LocalConstraintMap& other) {
// Find things in both, and OR them.
for (auto& [local, constraints] : other) {
if (auto iter = find(local); iter != end()) {
iter->second.approximateOr(constraints);
}
}

// Remove things only in us.
std::erase_if(*this, [&](const auto& item) {
const auto& [local, constraints] = item;
return !other.contains(local);
});
}

std::ostream& operator<<(std::ostream& o, const Constraint& constraint) {
o << "Constraint{" << /*constraint.op <<*/ ", ";
if (auto* c = std::get_if<Literal>(&constraint.term)) {
o << *c;
} else if (auto* i = std::get_if<Index>(&constraint.term)) {
o << "Index(" << *i << ')';
}
o << '}';
return o;
}

std::ostream& operator<<(std::ostream& o,
const AndedConstraintSet& constraints) {
o << "AndedConstraintSet{";
bool first = true;
for (auto& constraint : constraints) {
if (first) {
first = false;
} else {
o << ", ";
}
o << constraint;
}
o << '}';
return o;
}

} // namespace wasm::constraint
29 changes: 29 additions & 0 deletions src/ir/constraint.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,35 @@ struct AndedConstraintSet : inplace_vector<Constraint, MaxConstraints> {
void approximateOr(const AndedConstraintSet& other);
};

// A local plus a constraint on it.
struct LocalConstraint {
Index local;
Constraint constraint;

// Try to parse BinaryenIR into a local to which a constraint is applied. For
// example
//
// (i32.eq (local.get $r) (i32.const 10))
//
// parses into
//
// LocalConstraint($r, { x == 10 })
//
static std::optional<LocalConstraint> parse(Expression* curr);
};

// A map of locals and their constraints.
struct LocalConstraintMap
: public std::unordered_map<Index, AndedConstraintSet> {
// Perform an OR as above on each local that appears in both maps. If a local
// appears only in one, we can infer nothing, and drop it.
void approximateOr(const LocalConstraintMap& other);
};

std::ostream& operator<<(std::ostream& o, const Constraint& constraint);
std::ostream& operator<<(std::ostream& o,
const AndedConstraintSet& constraints);

} // namespace wasm::constraint

#endif // wasm_ir_constraint_h
1 change: 1 addition & 0 deletions src/passes/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ set(passes_SOURCES
CodeFolding.cpp
ConstantFieldPropagation.cpp
ConstHoisting.cpp
ConstraintAnalysis.cpp
DataFlowOpts.cpp
DeadArgumentElimination.cpp
DeadArgumentElimination2.cpp
Expand Down
217 changes: 217 additions & 0 deletions src/passes/ConstraintAnalysis.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
/*
* Copyright 2026 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.
*/

//
// Use mathematical constraint solving to optimize. For example:
//
// if (x == 10) {
// assert(x != 0); // redundant and can be removed.
// }
//

#include "cfg/cfg-traversal.h"
#include "ir/constraint.h"
#include "ir/drop.h"
#include "ir/literal-utils.h"
#include "ir/local-graph.h"
#include "ir/properties.h"
#include "pass.h"
#include "support/unique_deferring_queue.h"
#include "support/utilities.h"
#include "wasm-builder.h"
#include "wasm.h"

namespace wasm {

using namespace wasm::constraint;

namespace {

// In each basic block we will store the relevant operations, which are all
// local gets and sets, branches, and uses of them.
struct Info {
std::vector<Expression**> actions;

// For each local index, we track the constraints we know about it. We only do
// so at the start of each block, which is enough for the analysis below.
//
// We use an optional here to represent the "null" state before any
// information arrives. (From the perspective of set theory, nullopt can be
// taken to mean the empty set is the set of values possible for each local.)
std::optional<LocalConstraintMap> startConstraints;
};

struct ConstraintAnalysis
: public WalkerPass<
CFGWalker<ConstraintAnalysis, Visitor<ConstraintAnalysis>, Info>> {
bool isFunctionParallel() override { return true; }

// Locals are not modified here.
bool requiresNonNullableLocalFixups() override { return false; }

std::unique_ptr<Pass> create() override {
return std::make_unique<ConstraintAnalysis>();
}

// Branches outside of the function can be ignored, as we only look at local
// state in the function.
bool ignoreBranchesOutsideOfFunc = true;

// Store the actions we care about.
void addAction() {
if (currBasicBlock) {
currBasicBlock->contents.actions.push_back(getCurrentPointer());
}
}

void visitLocalSet(LocalSet* curr) { addAction(); }
void visitUnary(Unary* curr) { addAction(); }
void visitBinary(Binary* curr) { addAction(); }
void visitRefEq(RefEq* curr) { addAction(); }
void visitRefIsNull(RefIsNull* curr) { addAction(); }

void visitFunction(Function* curr) {
if (!entry) {
// Body is unreachable, no entry block.
Comment on lines +87 to +88

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this only happen when the body of the function is literally a single (unreachable)?

return;
}
// TODO: optimize for speed, find relevant locals etc.
flow();
optimize();
}

// Flow infos around until we have inferred all we can about the constraints
// in each location.
void flow() {
// Start from the entry. That block has incoming values - defaults - for
// each var.
auto& entryConstraints = entry->contents.startConstraints;
entryConstraints.emplace();
auto* func = getFunction();
auto numLocals = func->getNumLocals();
for (Index i = 0; i < numLocals; i++) {
if (!func->isVar(i)) {
continue;
}
Comment on lines +106 to +108

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be able to start i at the number of params and avoid this check.

auto type = func->getLocalType(i);
// TODO: support tuples
if (type.size() == 1 && LiteralUtils::canMakeZero(type)) {
auto value = Literal::makeZero(type);
(*entryConstraints)[i].approximateAnd(
Constraint{Abstract::Eq, {value}});
Comment on lines +113 to +114

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should logically be an OR because we've discovered a new possibility for the value of the local. An AND would be for when we've discovered a new constraint that constrains the possible values of the local.

The reason the code is currently correct even though it is doing the logically incorrect operation is that the default value created by (*entryConstraints)[i] is the top element (i.e. the empty set of constraints allowing any value) when it should be the bottom element (i.e. the set of all possible constraints allowing no values).

The possible fixes would be add a representation of this bottom element and make it the default value (thereby making it harder to accidentally use the top element when we should be using the bottom element, my preferred solution) or to assign the new constraint set to (*entryConstraints)[i] instead of merging the constraint into its default value.

}
}

// Starting from the entry, keep going while we find something new.
UniqueDeferredQueue<BasicBlock*> work;
work.push(entry);
while (!work.empty()) {
auto* block = work.pop();

// Start at the top of the block, then go through, applying things.
LocalConstraintMap constraints = *block->contents.startConstraints;
for (auto** currp : block->contents.actions) {
applyToConstraints(*currp, constraints);
}

// 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) {
auto& outConstraints = out->contents.startConstraints;
if (!outConstraints) {
// This is the first data arriving.
outConstraints.emplace(constraints);
work.push(out);
continue;
}
Comment on lines +134 to +139

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would not have to treat this as a separate case if we moved the std::optional inside the constraints to let us represent the bottom element.


// This is later data, which may or may not cause changes.
auto old = outConstraints;
outConstraints->approximateOr(constraints);
if (*outConstraints != old) {
work.push(out);
}
Comment on lines +143 to +146

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be slightly more efficient (avoiding a copy and a second look at the values) if approximateOr returned a bool saying whether the value was changed or not.

This is also missing the part where we use approximateAnd to add the information we gain from the branch condition. (Or is that meant to be added in a follow-on PR?)

}
}
}

// After inferring all we can, apply it to optimize the code.
void optimize() {
for (auto& block : basicBlocks) {
// Follow the general shape of flow(): we need to see what the state is
// at each intermediate point inside the block. (Flowing between blocks is
// of course not needed at this stage.)
auto& constraints = block->contents.startConstraints;
if (!constraints) {
// Unreachable.
continue;
}
Comment on lines +158 to +161

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should insert an (unreachable) in this case so DCE can know to remove the remaining code.


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

// Given an expression and the constraints on it, optimize it.
void optimizeExpression(Expression** currp,
const LocalConstraintMap& constraints) {
auto* curr = *currp;
auto parsed = LocalConstraint::parse(curr);
if (!parsed) {
return;
}

auto iter = constraints.find(parsed->local);
if (iter == constraints.end()) {
return;
}
auto& localConstraints = iter->second;
Result result = localConstraints.proves(parsed->constraint);
if (result == Unknown) {
// If we parsed something using two locals, like x != y, we can also look
// for the flipped condition among y's constraints TODO
Comment thread
tlively marked this conversation as resolved.
return;
}

// We know the result!
auto& wasm = *getModule();
auto value =
LiteralUtils::makeFromInt32(result == True ? 1 : 0, curr->type, wasm);
*currp = getDroppedChildrenAndAppend(
curr, wasm, getPassOptions(), value, DropMode::IgnoreParentEffects);
}

// Given an expression, apply it to the constraints. For example, a local.set
// sets the value for that local.
void applyToConstraints(Expression* curr, LocalConstraintMap& constraints) {
if (auto* set = curr->dynCast<LocalSet>()) {
auto& localConstraints = constraints[set->index];
localConstraints.clear();
if (Properties::isSingleConstantExpression(set->value)) {
auto value = Properties::getLiteral(set->value);
localConstraints.approximateAnd(Constraint{Abstract::Eq, {value}});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment. This should logically be an OR (or an assignment), and the current code is only correct because the default value is top rather than bottom.

}
}
}
};

} // anonymous namespace

Pass* createConstraintAnalysisPass() { return new ConstraintAnalysis(); }

} // namespace wasm
3 changes: 3 additions & 0 deletions src/passes/pass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ void PassRegistry::registerPasses() {
registerPass("cfp-reftest",
"propagate constant struct field values, using ref.test",
createConstantFieldPropagationRefTestPass);
registerPass("constraint-analysis",
"finds and uses mathematical constraints on locals",
createConstraintAnalysisPass);
registerPass(
"dce", "removes unreachable code", createDeadCodeEliminationPass);
registerPass("dealign",
Expand Down
Loading
Loading