blob: 202614ccd21df3491f11d02fd49a7f140b52f6f5 [file] [edit]
/*
* 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.
*/
#ifndef wasm_analysis_lattices_bounded_conjunction_h
#define wasm_analysis_lattices_bounded_conjunction_h
#include <algorithm>
#include <cassert>
#include <compare>
#include <variant>
#include <vector>
#include "analysis/lattice.h"
#include "support/inplace_vector.h"
namespace wasm::analysis {
// BoundedConjunction<Subclass, L, N> represents a conjunction of up to N
// pairwise unrelated elements of the underlying lattice L.
//
// This is a semilattice (implements Lattice but not FullLattice).
//
// The elements are represented as a std::variant of wasm::inplace_vector and
// Bot. The top element (no constraints) is represented as an empty vector.
//
// Subclasses must implement:
// std::strong_ordering orderElements(const typename L::Element&, const
// typename L::Element&) const
//
// To maximize monotonicity of boundedMeet, the total order defined by
// orderElements must be a linear extension of the lattice partial order (i.e. x
// <_L y => x < y in total order).
//
// To enforce this and simplify subclass implementation, boundedMeet will ONLY
// call subclass.orderElements for elements that are unrelated in L. For related
// elements, the lattice order is used.
template<typename Subclass, Lattice L, std::size_t N>
struct BoundedConjunction {
struct Bot {
bool operator==(const Bot&) const { return true; }
bool operator!=(const Bot&) const { return false; }
};
using Element = std::variant<inplace_vector<typename L::Element, N>, Bot>;
L lattice;
BoundedConjunction(L&& lattice) : lattice(std::move(lattice)) {
static_assert(
requires(const Subclass& sub,
const typename L::Element& a,
const typename L::Element& b) {
{ sub.orderElements(a, b) } -> std::same_as<std::strong_ordering>;
},
"Subclass must implement orderElements(const L::Element&, const "
"L::Element&) -> std::strong_ordering");
}
Element getBottom() const noexcept {
return Element{std::in_place_type<Bot>};
}
LatticeComparison compare(const Element& a, const Element& b) const noexcept {
if (std::holds_alternative<Bot>(a) && std::holds_alternative<Bot>(b))
return EQUAL;
if (std::holds_alternative<Bot>(a))
return LESS;
if (std::holds_alternative<Bot>(b))
return GREATER;
const auto& va = std::get<inplace_vector<typename L::Element, N>>(a);
const auto& vb = std::get<inplace_vector<typename L::Element, N>>(b);
if (va.empty() && vb.empty())
return EQUAL;
if (va.empty())
return GREATER;
if (vb.empty())
return LESS;
// a <= b iff for all eb in b, there exists ea in a s.t. ea <= eb
bool a_le_b = std::all_of(vb.begin(), vb.end(), [&](const auto& eb) {
return std::any_of(va.begin(), va.end(), [&](const auto& ea) {
auto comp = lattice.compare(ea, eb);
return comp == LESS || comp == EQUAL;
});
});
// b <= a iff for all ea in a, there exists eb in b s.t. eb <= ea
bool b_le_a = std::all_of(va.begin(), va.end(), [&](const auto& ea) {
return std::any_of(vb.begin(), vb.end(), [&](const auto& eb) {
auto comp = lattice.compare(eb, ea);
return comp == LESS || comp == EQUAL;
});
});
if (a_le_b && b_le_a)
return EQUAL;
if (a_le_b)
return LESS;
if (b_le_a)
return GREATER;
return NO_RELATION;
}
bool join(Element& joinee, const Element& joiner) const noexcept {
if (std::holds_alternative<Bot>(joiner))
return false;
if (std::holds_alternative<Bot>(joinee)) {
joinee = joiner;
return true;
}
auto& v_joinee = std::get<inplace_vector<typename L::Element, N>>(joinee);
const auto& v_joiner =
std::get<inplace_vector<typename L::Element, N>>(joiner);
if (v_joinee.empty())
return false;
if (v_joiner.empty()) {
v_joinee.clear();
return true;
}
std::vector<typename L::Element> temp_result;
for (const auto& ea : v_joinee) {
for (const auto& eb : v_joiner) {
auto joined = ea;
lattice.join(joined, eb);
addConstraint(temp_result, joined);
}
}
if (temp_result.size() > N) {
const auto& subclass = *static_cast<const Subclass*>(this);
std::sort(temp_result.begin(),
temp_result.end(),
[&](const auto& a, const auto& b) {
auto comp = lattice.compare(a, b);
if (comp == LESS)
return true;
if (comp == GREATER)
return false;
if (comp == EQUAL)
return false;
return subclass.orderElements(a, b) ==
std::strong_ordering::less;
});
temp_result.erase(temp_result.begin() + N, temp_result.end());
}
inplace_vector<typename L::Element, N> result;
for (auto& e : temp_result) {
result.push_back(std::move(e));
}
if (v_joinee == result)
return false;
v_joinee = std::move(result);
return true;
}
bool boundedMeet(Element& meetee, const Element& meeter) const noexcept {
if (std::holds_alternative<Bot>(meetee))
return false;
if (std::holds_alternative<Bot>(meeter)) {
meetee = getBottom();
return true;
}
auto& v_meetee = std::get<inplace_vector<typename L::Element, N>>(meetee);
const auto& v_meeter =
std::get<inplace_vector<typename L::Element, N>>(meeter);
if (v_meeter.empty())
return false;
if (v_meetee.empty()) {
v_meetee = v_meeter;
return true;
}
std::vector<typename L::Element> temp_result(v_meetee.begin(),
v_meetee.end());
for (const auto& em : v_meeter) {
if (!addConstraint(temp_result, em)) {
meetee = getBottom();
return true;
}
}
if (temp_result.size() > N) {
const auto& subclass = *static_cast<const Subclass*>(this);
std::sort(temp_result.begin(),
temp_result.end(),
[&](const auto& a, const auto& b) {
auto comp = lattice.compare(a, b);
if (comp == LESS)
return true;
if (comp == GREATER)
return false;
if (comp == EQUAL)
return false;
return subclass.orderElements(a, b) ==
std::strong_ordering::less;
});
temp_result.erase(temp_result.begin() + N, temp_result.end());
}
inplace_vector<typename L::Element, N> result;
for (auto& e : temp_result) {
result.push_back(std::move(e));
}
if (v_meetee == result)
return false;
v_meetee = std::move(result);
return true;
}
private:
// Helper to add constraint and simplify.
// Returns false if meet results in Bottom.
bool addConstraint(std::vector<typename L::Element>& vec,
const typename L::Element& e) const noexcept {
if (lattice.compare(e, lattice.getBottom()) == EQUAL) {
return false;
}
if constexpr (requires {
{ lattice.getTop() } -> std::same_as<typename L::Element>;
}) {
if (lattice.compare(e, lattice.getTop()) == EQUAL) {
return true;
}
}
bool add = true;
for (auto it = vec.begin(); it != vec.end();) {
auto comp = lattice.compare(e, *it);
if (comp == EQUAL || comp == LESS) {
it = vec.erase(it);
} else if (comp == GREATER) {
add = false;
++it;
} else {
++it;
}
}
if (add) {
vec.push_back(e);
}
return true;
}
};
} // namespace wasm::analysis
#endif // wasm_analysis_lattices_bounded_conjunction_h