include/PatriciaTreeMapAbstractEnvironment.h (235 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <cstddef>
#include <functional>
#include <initializer_list>
#include <ostream>
#include <sstream>
#include <unordered_map>
#include <utility>
#include "AbstractDomain.h"
#include "PatriciaTreeMap.h"
namespace sparta {
namespace ptmae_impl {
template <typename Variable, typename Domain>
class MapValue;
class value_is_bottom {};
} // namespace ptmae_impl
/*
* An abstract environment based on Patricia trees that is cheap to copy.
*
* In order to minimize the size of the underlying tree, we do not explicitly
* represent bindings of a variable to the Top element.
*
* See HashedAbstractEnvironment.h for more details about abstract
* environments.
*/
template <typename Variable, typename Domain>
class PatriciaTreeMapAbstractEnvironment final
: public AbstractDomainScaffolding<
ptmae_impl::MapValue<Variable, Domain>,
PatriciaTreeMapAbstractEnvironment<Variable, Domain>> {
public:
using Value = ptmae_impl::MapValue<Variable, Domain>;
using MapType =
PatriciaTreeMap<Variable, Domain, typename Value::ValueInterface>;
/*
* The default constructor produces the Top value.
*/
PatriciaTreeMapAbstractEnvironment()
: AbstractDomainScaffolding<Value, PatriciaTreeMapAbstractEnvironment>() {
}
PatriciaTreeMapAbstractEnvironment(AbstractValueKind kind)
: AbstractDomainScaffolding<Value, PatriciaTreeMapAbstractEnvironment>(
kind) {}
PatriciaTreeMapAbstractEnvironment(
std::initializer_list<std::pair<Variable, Domain>> l) {
for (const auto& p : l) {
if (p.second.is_bottom()) {
this->set_to_bottom();
return;
}
this->get_value()->insert_binding(p.first, p.second);
}
this->normalize();
}
size_t size() const {
RUNTIME_CHECK(this->kind() == AbstractValueKind::Value,
invalid_abstract_value()
<< expected_kind(AbstractValueKind::Value)
<< actual_kind(this->kind()));
return this->get_value()->m_map.size();
}
const MapType& bindings() const {
RUNTIME_CHECK(this->kind() == AbstractValueKind::Value,
invalid_abstract_value()
<< expected_kind(AbstractValueKind::Value)
<< actual_kind(this->kind()));
return this->get_value()->m_map;
}
const Domain& get(const Variable& variable) const {
if (this->is_bottom()) {
static const Domain bottom = Domain::bottom();
return bottom;
}
return this->get_value()->m_map.at(variable);
}
PatriciaTreeMapAbstractEnvironment& set(const Variable& variable,
const Domain& value) {
if (this->is_bottom()) {
return *this;
}
if (value.is_bottom()) {
this->set_to_bottom();
return *this;
}
this->get_value()->insert_binding(variable, value);
this->normalize();
return *this;
}
bool map(std::function<Domain(const Domain&)> f) {
if (this->is_bottom()) {
return false;
}
bool res = this->get_value()->map(f);
this->normalize();
return res;
}
bool erase_all_matching(const Variable& variable_mask) {
if (this->is_bottom()) {
return false;
}
bool res = this->get_value()->erase_all_matching(variable_mask);
this->normalize();
return res;
}
PatriciaTreeMapAbstractEnvironment& clear() {
if (this->is_bottom()) {
return *this;
}
this->get_value()->clear();
this->normalize();
return *this;
}
PatriciaTreeMapAbstractEnvironment& update(
const Variable& variable,
std::function<Domain(const Domain&)> operation) {
if (this->is_bottom()) {
return *this;
}
try {
this->get_value()->m_map.update(
[&operation](const Domain& x) {
Domain result = operation(x);
if (result.is_bottom()) {
throw ptmae_impl::value_is_bottom();
}
return result;
},
variable);
} catch (const ptmae_impl::value_is_bottom&) {
this->set_to_bottom();
}
this->normalize();
return *this;
}
static PatriciaTreeMapAbstractEnvironment bottom() {
return PatriciaTreeMapAbstractEnvironment(AbstractValueKind::Bottom);
}
static PatriciaTreeMapAbstractEnvironment top() {
return PatriciaTreeMapAbstractEnvironment(AbstractValueKind::Top);
}
};
} // namespace sparta
template <typename Variable, typename Domain>
inline std::ostream& operator<<(
std::ostream& o,
const typename sparta::PatriciaTreeMapAbstractEnvironment<Variable, Domain>&
e) {
using namespace sparta;
switch (e.kind()) {
case AbstractValueKind::Bottom: {
o << "_|_";
break;
}
case AbstractValueKind::Top: {
o << "T";
break;
}
case AbstractValueKind::Value: {
o << "[#" << e.size() << "]";
o << e.bindings();
break;
}
}
return o;
}
namespace sparta {
namespace ptmae_impl {
/*
* The definition of an element of an abstract environment, i.e., a map from a
* (possibly infinite) set of variables to an abstract domain implemented as a
* hashtable. Variable bindings with the Top value are not stored in the
* hashtable. The hashtable can never contain bindings with Bottom, as those are
* filtered out in PatriciaTreeMapAbstractEnvironment (the whole environment is
* set to Bottom in that case). The Meet and Narrowing operations abort and
* return AbstractValueKind::Bottom whenever a binding with Bottom is about to
* be created.
*/
template <typename Variable, typename Domain>
class MapValue final : public AbstractValue<MapValue<Variable, Domain>> {
public:
struct ValueInterface {
using type = Domain;
static type default_value() { return type::top(); }
static bool is_default_value(const type& x) { return x.is_top(); }
static bool equals(const type& x, const type& y) { return x.equals(y); }
static bool leq(const type& x, const type& y) { return x.leq(y); }
};
MapValue() = default;
MapValue(const Variable& variable, const Domain& value) {
insert_binding(variable, value);
}
void clear() override { m_map.clear(); }
AbstractValueKind kind() const override {
// If the map is empty, then all variables are implicitly bound to Top,
// i.e., the abstract environment itself is Top.
return m_map.empty() ? AbstractValueKind::Top : AbstractValueKind::Value;
}
bool leq(const MapValue& other) const override {
return m_map.leq(other.m_map);
}
bool equals(const MapValue& other) const override {
return m_map.equals(other.m_map);
}
AbstractValueKind join_with(const MapValue& other) override {
return join_like_operation(
other, [](const Domain& x, const Domain& y) { return x.join(y); });
}
AbstractValueKind widen_with(const MapValue& other) override {
return join_like_operation(
other, [](const Domain& x, const Domain& y) { return x.widening(y); });
}
AbstractValueKind meet_with(const MapValue& other) override {
return meet_like_operation(
other, [](const Domain& x, const Domain& y) { return x.meet(y); });
}
AbstractValueKind narrow_with(const MapValue& other) override {
return meet_like_operation(
other, [](const Domain& x, const Domain& y) { return x.narrowing(y); });
}
private:
void insert_binding(const Variable& variable, const Domain& value) {
// The Bottom value is handled by the caller and should never occur here.
RUNTIME_CHECK(!value.is_bottom(), internal_error());
m_map.insert_or_assign(variable, value);
}
bool map(std::function<Domain(const Domain&)>& f) { return m_map.map(f); }
bool erase_all_matching(const Variable& variable_mask) {
return m_map.erase_all_matching(variable_mask);
}
AbstractValueKind join_like_operation(
const MapValue& other,
std::function<Domain(const Domain&, const Domain&)> operation) {
m_map.intersection_with(operation, other.m_map);
return kind();
}
AbstractValueKind meet_like_operation(
const MapValue& other,
std::function<Domain(const Domain&, const Domain&)> operation) {
try {
m_map.union_with(
[&operation](const Domain& x, const Domain& y) {
Domain result = operation(x, y);
if (result.is_bottom()) {
throw value_is_bottom();
}
return result;
},
other.m_map);
return kind();
} catch (const value_is_bottom&) {
clear();
return AbstractValueKind::Bottom;
}
}
PatriciaTreeMap<Variable, Domain, ValueInterface> m_map;
template <typename T1, typename T2>
friend class sparta::PatriciaTreeMapAbstractEnvironment;
};
} // namespace ptmae_impl
} // namespace sparta