-
Notifications
You must be signed in to change notification settings - Fork 882
ConstraintAnalysis: Parse AND operations #9075
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a6cb42c
06240fa
c0a41e4
2d23f5e
71997f9
1490b10
bf44254
e290c24
8e2c218
4117c1e
ea13cd0
ec0eea3
fedaa18
be901a8
d7c1182
5f3bce7
e84e822
f5616bf
ee03cf2
3b9d045
b47a8f1
012ee79
df6d01c
39d675c
d706843
576ca49
9730b97
e1d1172
376845d
a671a63
6005e4c
a0da7e0
d8553ec
4b6f293
4c3b1f5
89ea672
f038bbe
8d4c3ac
f8a2649
68bca39
6d2e4ad
b20b331
44cd327
657bfb7
e472334
1e89d01
70e0865
d00e0da
b6a4a76
56dd575
82bf613
5062fef
8a5e5d5
bc12f04
c5a6836
f140f7a
ab8bcc1
2e299a6
324e41c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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" | ||
|
|
@@ -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 | ||
| // | ||
|
|
@@ -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). | ||
|
Comment on lines
+287
to
+289
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not clear to me what the second sentence has to do with negation.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It just explains what happens when we fail. Negating |
||
| void negate(); | ||
| }; | ||
|
|
||
| // A map of locals and their constraints, representing the state at a basic | ||
| // block. We use the following representation: | ||
| // | ||
|
|
@@ -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 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -66,6 +66,8 @@ | |
| // function analysis). | ||
| // | ||
|
|
||
| #include <algorithm> | ||
|
|
||
| #include "cfg/cfg-traversal.h" | ||
| #include "ir/constraint.h" | ||
| #include "ir/drop.h" | ||
|
|
@@ -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; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -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()) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice use of the comma operator! |
||
| auto sentConstraints = constraints; | ||
| applyBranchConstraints(*branch, sentConstraints); | ||
| applyBranchConstraints(branch, sentConstraints); | ||
| #if CONSTRAINT_DEBUG | ||
| std::cout << block << " sending branch to " << out | ||
| << " with sent constraints: " << sentConstraints << '\n'; | ||
|
|
@@ -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; | ||
|
|
@@ -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 {}; | ||
|
|
@@ -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) { | ||
|
|
@@ -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; | ||
| } | ||
|
|
@@ -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 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why it doesn't compile without
Match::in front oflocal, I couldn't figure out... must be overlapping with some other global namespace name