-
Notifications
You must be signed in to change notification settings - Fork 866
Add a ConstraintAnalysis pass #8853
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
base: main
Are you sure you want to change the base?
Changes from all commits
fdd5024
aabd860
3332b8a
8b18012
002d237
229ae81
5598aec
ed255d4
e5d8299
fa4805f
ae146cf
4748cc5
f7e33cc
80a843e
2b10d87
c9f17bc
18a691b
5e8c7c3
3b22e48
08e419d
70add1c
15c806a
c30dcda
c74abed
da40546
ddc615e
2707bb1
6390870
3cedbcf
f54afaa
8071200
b550c4d
b1aedcf
0bc8d2e
61fc656
3a3970b
8a6d355
562872d
d296092
a46436f
413f4e8
817a9ca
c14b3b9
35329c8
1dfe44c
6e6d06e
c9f67d1
5f1fdc8
c339b79
d673317
c4321a9
260fb83
4fe70e3
c0d19d9
e6f2d34
200566c
83e845e
e16096b
494f67a
6e76ac0
cf2b0e4
2a02004
279dc39
6e8f576
fcd7e41
6537ace
61dd31e
783e6ff
3e0055e
c1e3dc8
b13c894
d7f5732
362fb95
05d9e29
bc1f4c9
70cd80e
bf12966
67b6707
b9fe8c0
6bae184
6706a99
62cba65
c3089ea
7c5b0b7
958a6d4
e4079e9
31a0ea5
d78f0ef
e78d295
c9eaeed
8884744
eda589a
72e40e4
54cd47e
9e1af11
561c80f
f29d198
3d4a7bb
3cabc2e
690a5ab
840f863
e8a4a80
bd77752
94a3c35
4c5b867
f828d9d
6036296
889658d
724ecb9
0fc9e55
5ad1b75
8181363
d0ad2f4
c5b7d1d
0e35b2b
60d50b4
626b5d7
5896406
cf29b58
cdaff6b
c02ba2e
94d2161
735d7ea
6e80fde
cf7fcc6
ac02454
0930461
7b7d2ac
679bd24
920e7a9
602a8c3
44ad794
2f0bdd7
7f77016
daf2208
59f5a09
ed455bd
03e91b6
6177a16
af791b1
cb35706
c0f54ad
745116c
4c8dcca
725c08d
3e96dcc
03ec477
945e042
41ffb72
ba50172
613ef95
923a42a
5e432a4
2da66f7
3ef82bb
b05dba6
564b3cb
4c3eef2
f40a39d
8a0286a
0d2f0ed
517dac0
9522de6
0e358e3
a178135
e09cbda
0cb581a
91cc337
ea3db17
060d8aa
088a425
9267485
3965c27
280e7b3
26dfc30
c1dacec
3952dfc
224425c
203d455
e4a2a49
22d2d3b
bdc4911
9b4a261
c924041
8e5e075
90aabfa
81714b2
edf8059
b1522c2
97ec6b5
966676d
9645e71
64e0a86
0e0bfb7
6947fc3
2b30208
464aba5
82aad7d
62e5ee9
835d863
6aec092
c014633
81f2421
4f9fb9d
dab23fb
26b1b62
bda6665
9dbf1d9
1981731
ec2c0d5
53974d7
e575105
aec9542
92e5efd
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 |
|---|---|---|
| @@ -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
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. Does this only happen when the body of the function is literally a single |
||
| 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
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. We should be able to start |
||
| 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
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. 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 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 |
||
| } | ||
| } | ||
|
|
||
| // 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
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. We would not have to treat this as a separate case if we moved the |
||
|
|
||
| // 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
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 would be slightly more efficient (avoiding a copy and a second look at the values) if This is also missing the part where we use |
||
| } | ||
| } | ||
| } | ||
|
|
||
| // 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
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. We should insert an |
||
|
|
||
| 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 | ||
|
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}}); | ||
|
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. 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 | ||
This comment was marked as resolved.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.