blob: 7b1a04d445379ff2fcad18e65d2fa33a00005ae5 [file]
/*
* 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.
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.
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;
auto* func = getFunction();
auto numLocals = func->getNumLocals();
for (Index i = 0; i < numLocals; i++) {
auto type = func->getLocalType(i);
// TODO: support tuples
if (func->isParam(i) || type.size() != 1) {
// We can't do anything in this case.
entryConstraints[i].setProvesNothing();
} else if (LiteralUtils::canMakeZero(type)) {
// We have a default value.
auto value = Literal::makeZero(type);
entryConstraints[i].set(Constraint{Abstract::Eq, {value}});
} else {
// Otherwise, this is non-nullable, and it is unreachable until set.
assert(type.isNonNullable());
entryConstraints[i].setProvesEverything();
}
}
// 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& outStartConstraints = out->contents.startConstraints;
auto old = outStartConstraints;
outStartConstraints.approximateOr(constraints);
if (outStartConstraints != old) {
work.push(out);
}
}
}
}
// 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;
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
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];
if (Properties::isSingleConstantExpression(set->value)) {
// We know this one constraint.
auto value = Properties::getLiteral(set->value);
localConstraints.set(Constraint{Abstract::Eq, {value}});
} else {
// We know and can prove nothing.
localConstraints.setProvesNothing();
}
}
}
};
} // anonymous namespace
Pass* createConstraintAnalysisPass() { return new ConstraintAnalysis(); }
} // namespace wasm