rpcs3/Utilities/address_range.h
kd-11 2d9a24d1d6
Some checks failed
Generate Translation Template / Generate Translation Template (push) Has been cancelled
Build RPCS3 / RPCS3 Linux ubuntu-24.04 gcc (push) Has been cancelled
Build RPCS3 / RPCS3 Linux ubuntu-24.04-arm clang (push) Has been cancelled
Build RPCS3 / RPCS3 Linux ubuntu-24.04 clang (push) Has been cancelled
Build RPCS3 / RPCS3 Windows (push) Has been cancelled
rsx/util/test: Cover edge cases in address_range test suite
2025-05-27 15:06:32 +03:00

597 lines
13 KiB
C++

#pragma once
#include "util/types.hpp"
#include "util/vm.hpp"
#include "StrFmt.h"
#include <vector>
#include <algorithm>
namespace utils
{
template <typename T>
class address_range_vector;
/**
* Helpers
*/
template <typename T>
T page_start(T addr)
{
return addr & ~static_cast<T>(get_page_size() - 1);
}
template <typename T>
static inline T next_page(T addr)
{
return page_start(addr) + static_cast<T>(get_page_size());
}
template <typename T>
static inline T page_end(T addr)
{
return next_page(addr) - 1;
}
template <typename T>
static inline T is_page_aligned(T val)
{
return (val & static_cast<T>(get_page_size() - 1)) == 0;
}
/**
* Address Range utility class
*/
template <typename T>
class address_range
{
public:
T start = umax; // First address in range
T end = 0; // Last address
using signed_type_t = std::make_signed<T>::type;
private:
// Helper constexprs
static constexpr inline bool range_overlaps(T start1, T end1, T start2, T end2)
{
return (start1 <= end2 && start2 <= end1);
}
static constexpr inline bool address_overlaps(T address, T start, T end)
{
return (start <= address && address <= end);
}
static constexpr inline bool range_inside_range(T start1, T end1, T start2, T end2)
{
return (start1 >= start2 && end1 <= end2);
}
constexpr address_range(T _start, T _end) : start(_start), end(_end) {}
public:
// Constructors
constexpr address_range() = default;
static constexpr address_range start_length(T _start, T _length)
{
if (!_length)
{
return {};
}
const T _end = static_cast<T>(_start + _length - 1);
return {_start, _end};
}
static constexpr address_range start_end(T _start, T _end)
{
return {_start, _end};
}
// Length
T length() const
{
AUDIT(valid());
return end - start + 1;
}
void set_length(const T new_length)
{
end = start + new_length - 1;
ensure(valid());
}
T next_address() const
{
return end + 1;
}
T prev_address() const
{
return start - 1;
}
// Overlapping checks
bool overlaps(const address_range<T>& other) const
{
AUDIT(valid() && other.valid());
return range_overlaps(start, end, other.start, other.end);
}
bool overlaps(const T addr) const
{
AUDIT(valid());
return address_overlaps(addr, start, end);
}
bool inside(const address_range<T>& other) const
{
AUDIT(valid() && other.valid());
return range_inside_range(start, end, other.start, other.end);
}
inline bool inside(const address_range_vector<T>& vec) const;
inline bool overlaps(const address_range_vector<T>& vec) const;
bool touches(const address_range<T>& other) const
{
AUDIT(valid() && other.valid());
// returns true if there is overlap, or if sections are side-by-side
return overlaps(other) || other.start == next_address() || other.end == prev_address();
}
// Utilities
signed_type_t signed_distance(const address_range<T>& other) const
{
if (touches(other))
{
return 0;
}
// other after this
if (other.start > end)
{
return static_cast<signed_type_t>(other.start - end - 1);
}
// this after other
AUDIT(start > other.end);
return -static_cast<signed_type_t>(start - other.end - 1);
}
T distance(const address_range<T>& other) const
{
if (touches(other))
{
return 0;
}
// other after this
if (other.start > end)
{
return (other.start - end - 1);
}
// this after other
AUDIT(start > other.end);
return (start - other.end - 1);
}
address_range<T> get_min_max(const address_range<T>& other) const
{
return {
std::min(valid() ? start : umax, other.valid() ? other.start : umax),
std::max(valid() ? end : 0, other.valid() ? other.end : 0)
};
}
void set_min_max(const address_range<T>& other)
{
*this = get_min_max(other);
}
bool is_page_range() const
{
return (valid() && is_page_aligned(start) && is_page_aligned(length()));
}
address_range<T> to_page_range() const
{
AUDIT(valid());
return { page_start(start), page_end(end) };
}
void page_align()
{
AUDIT(valid());
start = page_start(start);
end = page_end(end);
AUDIT(is_page_range());
}
address_range<T> get_intersect(const address_range<T>& clamp) const
{
if (!valid() || !clamp.valid())
{
return {};
}
return { std::max(start, clamp.start), std::min(end, clamp.end) };
}
void intersect(const address_range<T>& clamp)
{
if (!clamp.valid())
{
invalidate();
}
else
{
start = std::max(start, clamp.start);
end = std::min(end, clamp.end);
}
}
// Validity
bool valid() const
{
return (start <= end);
}
void invalidate()
{
start = umax;
end = 0;
}
// Comparison Operators
bool operator ==(const address_range<T>& other) const
{
return (start == other.start && end == other.end);
}
/**
* Debug
*/
std::string str() const
{
return fmt::format("{0x%x->0x%x}", start, end);
}
};
using address_range16 = address_range<u16>;
using address_range32 = address_range<u32>;
using address_range64 = address_range<u64>;
template <typename T>
static inline address_range<T> page_for(T addr)
{
return address_range<T>::start_end(page_start(addr), page_end(addr));
}
/**
* Address Range Vector utility class
*
* Collection of address_range<T> objects. Allows for merging and removing ranges from the set.
*/
template <typename T>
class address_range_vector
{
public:
using vector_type = std::vector<address_range<T>>;
using iterator = vector_type::iterator;
using const_iterator = vector_type::const_iterator;
using size_type = vector_type::size_type;
private:
vector_type data;
public:
// Wrapped functions
inline void reserve(usz nr) { data.reserve(nr); }
inline void clear() { data.clear(); }
inline size_type size() const { return data.size(); }
inline bool empty() const { return data.empty(); }
inline address_range<T>& operator[](size_type n) { return data[n]; }
inline const address_range<T>& operator[](size_type n) const { return data[n]; }
inline iterator begin() { return data.begin(); }
inline const_iterator begin() const { return data.begin(); }
inline iterator end() { return data.end(); }
inline const_iterator end() const { return data.end(); }
// Search for ranges that touch new_range. If found, merge instead of adding new_range.
// When adding a new range, re-use invalid ranges whenever possible
void merge(const address_range<T>& new_range)
{
// Note the case where we have
// AAAA BBBB
// CCCC
// If we have data={A,B}, and new_range=C, we have to merge A with C, then B with A and invalidate B
if (!new_range.valid())
{
return;
}
address_range<T> *found = nullptr;
address_range<T> *invalid = nullptr;
for (auto &existing : data)
{
if (!existing.valid())
{
invalid = &existing;
continue;
}
// range1 overlaps, is immediately before, or is immediately after range2
if (existing.touches(new_range))
{
if (found != nullptr)
{
// Already found a match, merge and invalidate "existing"
found->set_min_max(existing);
existing.invalidate();
}
else
{
// First match, merge "new_range"
existing.set_min_max(new_range);
found = &existing;
}
}
}
if (found != nullptr)
{
return;
}
if (invalid != nullptr)
{
*invalid = new_range;
}
else
{
data.push_back(new_range);
}
AUDIT(check_consistency());
}
void merge(const address_range_vector<T>& other)
{
for (const address_range<T>& new_range : other)
{
merge(new_range);
}
}
// Exclude a given range from data
void exclude(const address_range<T>& exclusion)
{
// Note the case where we have
// AAAAAAA
// EEE
// where data={A} and exclusion=E.
// In this case, we need to reduce A to the head (before E starts), and then create a new address_range<T> B for the tail (after E ends), i.e.
// AA BB
// EEE
if (!exclusion.valid())
{
return;
}
address_range<T> *invalid = nullptr; // try to re-use an invalid range instead of calling push_back
// We use index access because we might have to push_back within the loop, which could invalidate the iterators
size_type _size = data.size();
for (size_type n = 0; n < _size; ++n)
{
address_range<T>& existing = data[n];
if (!existing.valid())
{
// Null
invalid = &existing;
continue;
}
if (!existing.overlaps(exclusion))
{
// No overlap, skip
continue;
}
const bool head_excluded = exclusion.overlaps(existing.start); // This section has its start inside excluded range
const bool tail_excluded = exclusion.overlaps(existing.end); // This section has its end inside excluded range
if (head_excluded && tail_excluded)
{
// Cannot be salvaged, fully excluded
existing.invalidate();
invalid = &existing;
}
else if (head_excluded)
{
// Head overlaps, truncate head
existing.start = exclusion.next_address();
}
else if (tail_excluded)
{
// Tail overlaps, truncate tail
existing.end = exclusion.prev_address();
}
else
{
// Section sits in the middle (see comment above function header)
AUDIT(exclusion.inside(existing));
const auto tail_end = existing.end;
// Head
existing.end = exclusion.prev_address();
// Tail
if (invalid != nullptr)
{
invalid->start = exclusion.next_address();
invalid->end = tail_end;
invalid = nullptr;
}
else
{
// IMPORTANT: adding to data invalidates "existing". This must be done last!
data.push_back(address_range<T>::start_end(exclusion.next_address(), tail_end));
}
}
}
AUDIT(check_consistency());
AUDIT(!overlaps(exclusion));
}
void exclude(const address_range_vector<T>& other)
{
for (const address_range<T>& exclusion : other)
{
exclude(exclusion);
}
}
// Checks the consistency of this vector.
// Will fail if ranges within the vector overlap our touch each-other
bool check_consistency() const
{
const usz _size = data.size();
for (usz i = 0; i < _size; ++i)
{
const auto &r1 = data[i];
if (!r1.valid())
{
continue;
}
for (usz j = i + 1; j < _size; ++j)
{
const auto &r2 = data[j];
if (!r2.valid())
{
continue;
}
if (r1.touches(r2))
{
return false;
}
}
}
return true;
}
// Test for overlap with a given range
bool overlaps(const address_range<T>& range) const
{
return std::any_of(data.cbegin(), data.cend(), [&range](const address_range<T>& cur)
{
return cur.valid() && cur.overlaps(range);
});
}
// Test for overlap with a given address_range<T> vector
bool overlaps(const address_range_vector<T>& other) const
{
for (const address_range<T>& rng1 : data)
{
if (!rng1.valid())
{
continue;
}
for (const address_range<T>& rng2 : other.data)
{
if (!rng2.valid())
{
continue;
}
if (rng1.overlaps(rng2))
{
return true;
}
}
}
return false;
}
// Test if a given range is fully contained inside this vector
bool contains(const address_range<T>& range) const
{
return std::any_of(this->begin(), this->end(), [&range](const address_range<T>& cur)
{
return cur.valid() && cur.inside(range);
});
}
// Test if all ranges in this vector are full contained inside a specific range
bool inside(const address_range<T>& range) const
{
return std::all_of(this->begin(), this->end(), [&range](const address_range<T>& cur)
{
return !cur.valid() || cur.inside(range);
});
}
// Count valid entries
usz valid_count() const
{
usz count = 0;
for (const auto& e : data)
{
if (e.valid())
{
count++;
}
}
return count;
}
};
// These declarations must be done after address_range_vector has been defined
template <typename T>
bool address_range<T>::inside(const address_range_vector<T>& vec) const
{
return vec.contains(*this);
}
template <typename T>
bool address_range<T>::overlaps(const address_range_vector<T>& vec) const
{
return vec.overlaps(*this);
}
using address_range_vector16 = address_range_vector<u16>;
using address_range_vector32 = address_range_vector<u32>;
using address_range_vector64 = address_range_vector<u64>;
} // namespace utils
namespace std
{
static_assert(sizeof(usz) >= 2 * sizeof(u32), "usz must be at least twice the size of u32");
template <>
struct hash<utils::address_range32>
{
usz operator()(const utils::address_range32& k) const
{
// we can guarantee a unique hash since our type is 64 bits and usz as well
return (usz{ k.start } << 32) | usz{ k.end };
}
};
}