blob: 9e71ebc17f8947df19ab9b98af6210718bba25e2 [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_bound_h
#define wasm_analysis_lattices_bound_h
#include <iostream>
#include <optional>
#include <utility>
#include <variant>
#include "analysis/lattice.h"
namespace wasm::analysis {
enum class BoundRelation { LT, LE, GE, GT };
inline std::ostream& operator<<(std::ostream& os, BoundRelation rel) {
switch (rel) {
case BoundRelation::LT:
os << "<";
break;
case BoundRelation::LE:
os << "<=";
break;
case BoundRelation::GE:
os << ">=";
break;
case BoundRelation::GT:
os << ">";
break;
}
return os;
}
template<FullLattice L> struct Bound {
struct Bot {
bool operator==(const Bot&) const { return true; }
bool operator!=(const Bot&) const { return false; }
};
struct Top {
bool operator==(const Top&) const { return true; }
bool operator!=(const Top&) const { return false; }
};
struct Constraint {
BoundRelation rel;
typename L::Element val;
bool operator==(const Constraint& other) const {
return rel == other.rel && val == other.val;
}
bool operator!=(const Constraint& other) const { return !(*this == other); }
};
using Element = std::variant<Constraint, Bot, Top>;
L lattice;
Bound(L&& lattice) : lattice(std::move(lattice)) {}
Element getBottom() const noexcept { return Bot{}; }
Element getTop() const noexcept { return Top{}; }
Element makeBound(BoundRelation rel, typename L::Element val) const {
auto bot_L = lattice.getBottom();
std::optional<typename L::Element> top_L = std::nullopt;
if constexpr (requires {
{ lattice.getTop() } -> std::same_as<typename L::Element>;
}) {
top_L = lattice.getTop();
}
if (rel == BoundRelation::LT && lattice.compare(val, bot_L) == EQUAL) {
return Bot{};
}
if (top_L && rel == BoundRelation::GT &&
lattice.compare(val, *top_L) == EQUAL) {
return Bot{};
}
if (top_L && rel == BoundRelation::LE &&
lattice.compare(val, *top_L) == EQUAL) {
return Top{};
}
if (rel == BoundRelation::GE && lattice.compare(val, bot_L) == EQUAL) {
return Top{};
}
return Constraint{rel, val};
}
bool implies(const Constraint& c1, const Constraint& c2) const noexcept {
auto comp = lattice.compare(c1.val, c2.val);
switch (c1.rel) {
case BoundRelation::LT:
switch (c2.rel) {
case BoundRelation::LT:
return comp == LESS || comp == EQUAL;
case BoundRelation::LE:
return comp == LESS || comp == EQUAL;
default:
return false;
}
break;
case BoundRelation::LE:
switch (c2.rel) {
case BoundRelation::LE:
return comp == LESS || comp == EQUAL;
case BoundRelation::LT:
return comp == LESS;
default:
return false;
}
break;
case BoundRelation::GE:
switch (c2.rel) {
case BoundRelation::GE:
return comp == GREATER || comp == EQUAL;
case BoundRelation::GT:
return comp == GREATER;
default:
return false;
}
break;
case BoundRelation::GT:
switch (c2.rel) {
case BoundRelation::GT:
return comp == GREATER || comp == EQUAL;
case BoundRelation::GE:
return comp == GREATER || comp == EQUAL;
default:
return false;
}
break;
}
return false;
}
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;
if (std::holds_alternative<Top>(a) && std::holds_alternative<Top>(b))
return EQUAL;
if (std::holds_alternative<Top>(a))
return GREATER;
if (std::holds_alternative<Top>(b))
return LESS;
const auto& c1 = std::get<Constraint>(a);
const auto& c2 = std::get<Constraint>(b);
bool a_implies_b = implies(c1, c2);
bool b_implies_a = implies(c2, c1);
if (a_implies_b && b_implies_a)
return EQUAL;
if (a_implies_b)
return LESS;
if (b_implies_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;
}
if (std::holds_alternative<Top>(joinee))
return false;
if (std::holds_alternative<Top>(joiner)) {
joinee = Top{};
return true;
}
const auto& c1 = std::get<Constraint>(joinee);
const auto& c2 = std::get<Constraint>(joiner);
auto result = joinConstraints(c1, c2);
if (joinee == result)
return false;
joinee = std::move(result);
return true;
}
bool meet(Element& meetee, const Element& meeter) const noexcept {
if (std::holds_alternative<Top>(meeter))
return false;
if (std::holds_alternative<Top>(meetee)) {
meetee = meeter;
return true;
}
if (std::holds_alternative<Bot>(meetee))
return false;
if (std::holds_alternative<Bot>(meeter)) {
meetee = Bot{};
return true;
}
const auto& c1 = std::get<Constraint>(meetee);
const auto& c2 = std::get<Constraint>(meeter);
auto result = meetConstraints(c1, c2);
if (meetee == result)
return false;
meetee = std::move(result);
return true;
}
private:
typename L::Element meet_values(const typename L::Element& a,
const typename L::Element& b) const {
auto copy = a;
lattice.meet(copy, b);
return copy;
}
Element joinConstraints(const Constraint& c1, const Constraint& c2) const {
if (implies(c1, c2))
return c2;
if (implies(c2, c1))
return c1;
auto is_upper = [](BoundRelation r) {
return r == BoundRelation::LT || r == BoundRelation::LE;
};
auto is_lower = [](BoundRelation r) {
return r == BoundRelation::GT || r == BoundRelation::GE;
};
if (is_upper(c1.rel) && is_upper(c2.rel)) {
auto joined_val = c1.val;
lattice.join(joined_val, c2.val);
return makeBound(BoundRelation::LT, joined_val);
}
if (is_lower(c1.rel) && is_lower(c2.rel)) {
auto met = meet_values(c1.val, c2.val);
return makeBound(BoundRelation::GT, met);
}
return Top{};
}
Element meetConstraints(const Constraint& c1, const Constraint& c2) const {
if (implies(c1, c2))
return c1;
if (implies(c2, c1))
return c2;
if (c1.rel == c2.rel) {
if (c1.rel == BoundRelation::LE) {
auto met_val = meet_values(c1.val, c2.val);
return makeBound(c1.rel, met_val);
}
if (c1.rel == BoundRelation::GE) {
auto joined_val = c1.val;
lattice.join(joined_val, c2.val);
return makeBound(c1.rel, joined_val);
}
}
return Bot{};
}
};
template<FullLattice L>
std::ostream& operator<<(std::ostream& os,
const typename Bound<L>::Element& elem) {
if (std::holds_alternative<typename Bound<L>::Bot>(elem)) {
os << "bound bot";
} else if (std::holds_alternative<typename Bound<L>::Top>(elem)) {
os << "bound top";
} else {
const auto& c = std::get<typename Bound<L>::Constraint>(elem);
os << "Bound(" << c.rel << " " << c.val << ")";
}
return os;
}
} // namespace wasm::analysis
#endif // wasm_analysis_lattices_bound_h