blob: 57420a1733fa9dc1284187add9bcb4b39dfc6d55 [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.
*/
#include <optional>
#include "ir/constraint.h"
#include "ir/properties.h"
#include "wasm.h"
namespace wasm::constraint {
namespace {
// Evaluate whether a => b, where a and b are operations on constants.
Result provesConstantPair(Abstract::Op aOp,
const Literal& aConstant,
Abstract::Op bOp,
const Literal& bConstant) {
// x == X =?=> x == Y. True iff X == Y.
if (aOp == Abstract::Eq && bOp == Abstract::Eq) {
return aConstant == bConstant ? True : False;
}
// x == X =?=> x != Y. True iff X != Y.
if (aOp == Abstract::Eq && bOp == Abstract::Ne) {
return aConstant == bConstant ? False : True;
}
// x != X =?=> x == Y. False if X = Y, else unknown.
if (aOp == Abstract::Ne && bOp == Abstract::Eq) {
if (aConstant == bConstant) {
return False;
}
}
// x != X =?=> x != Y. True if X = Y, else unknown.
if (aOp == Abstract::Ne && bOp == Abstract::Ne) {
if (aConstant == bConstant) {
return True;
}
}
// TODO: handle >, >=, <, and <=
return Unknown;
}
// Core comparison of two constraints: whether a => b
Result provesPair(const Constraint& a, const Constraint& b) {
// A thing always implies itself.
if (a == b) {
return True;
}
// Comparisons of two constants.
auto* aConstant = std::get_if<Literal>(&a.term);
auto* bConstant = std::get_if<Literal>(&b.term);
if (aConstant && bConstant) {
return provesConstantPair(a.op, *aConstant, b.op, *bConstant);
}
return Unknown;
}
} // anonymous namespace
Result AndedConstraintSet::proves(const Constraint& condition) const {
// Sometimes a single constraint is enough to determine the condition.
for (auto& c : *this) {
auto result = provesPair(c, condition);
if (result != Unknown) {
return result;
}
}
// TODO smarts for multiple constraints
// Otherwise, who knows.
return Unknown;
}
Result AndedConstraintSet::proves(const AndedConstraintSet& other) const {
if (other.empty()) {
// The empty set of constraints is always true.
return True;
}
bool hasUnknown = false;
for (auto& c : other) {
auto result = proves(c);
if (result == False) {
// The entire conjunction is proven false.
return False;
}
if (result == Unknown) {
hasUnknown = true;
}
}
return hasUnknown ? Unknown : True;
}
void AndedConstraintSet::approximateAnd(const Constraint& c) {
if (size() < MaxConstraints) {
push_back(c);
return;
}
// Otherwise, just do not add this one.
// TODO: We could try to be clever and see if one of the existing ones makes
// more sense to drop.
}
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()) {
return;
}
// If this is already implied by current constraints, then it is redundant.
// E.g. if we are { x = 10 } and other is { x >= 0 } then all we need is
// { x >= 0 } as the result of the OR.
if (proves(other) == True) {
*this = other;
return;
}
if (other.proves(*this) == True) {
return;
}
// TODO smarts: handle <= > and so forth
// Otherwise, we don't know how to nicely OR these things, and expand to the
// trivial set of no constraints.
clear();
}
} // namespace wasm::constraint