Merge pull request #1312 from TibixDev/pr/incremental-draw-fingerprints
perf(agc): maintain draw fingerprints on register writes
This commit is contained in:
commit
21bdab007a
|
|
@ -325,6 +325,29 @@ foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static)
|
|||
string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}")
|
||||
endforeach()
|
||||
|
||||
add_test_executable(agc_driver_register_key_tests EXCLUDE_FROM_ALL tests/RegisterKey.cpp)
|
||||
target_include_directories(agc_driver_register_key_tests PRIVATE
|
||||
${LIBS_INCLUDE_DIR}
|
||||
${CMAKE_SOURCE_DIR}/core/shader/recompiler
|
||||
${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}
|
||||
)
|
||||
target_link_libraries(agc_driver_register_key_tests PRIVATE
|
||||
libSceAgcDriver
|
||||
libc
|
||||
libkernel
|
||||
)
|
||||
set_target_properties(agc_driver_register_key_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_driver_register_key_tests)
|
||||
if(BUILD_TESTING)
|
||||
set_target_properties(agc_driver_register_key_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
|
||||
add_test(NAME agc_driver_register_key COMMAND agc_driver_register_key_tests)
|
||||
set_tests_properties(agc_driver_register_key PROPERTIES TIMEOUT 30 ENVIRONMENT "APS5_VERIFY_REGISTER_KEYS=1")
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_driver_register_key PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_test_executable(agc_driver_tests EXCLUDE_FROM_ALL
|
||||
tests/Submit.cpp
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_DRIVER_DRAW_DRAWREGISTERKEY_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_DRIVER_DRAW_DRAWREGISTERKEY_HPP
|
||||
|
||||
#include "prx/libSceAgcDriver/Execution/include/QueueState.hpp"
|
||||
|
||||
namespace AgcDriver::DriverDetail {
|
||||
|
||||
std::uint64_t RegisterStateKey(const QueueState& queue, bool allUserWords = false, bool incremental = true);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
|
|
@ -23,6 +24,29 @@ public:
|
|||
using mapped_type = std::uint32_t;
|
||||
using value_type = std::pair<const std::uint32_t, std::uint32_t>;
|
||||
|
||||
class Mask {
|
||||
public:
|
||||
explicit Mask(std::vector<std::uint64_t> bits) : bits(std::move(bits)) {}
|
||||
bool Contains(std::uint32_t offset) const { return offset / 64 < bits.size() && ((bits[offset / 64] >> (offset % 64)) & 1u) != 0; }
|
||||
private:
|
||||
std::vector<std::uint64_t> bits;
|
||||
};
|
||||
|
||||
class Reference {
|
||||
public:
|
||||
Reference(Registers& owner, std::uint32_t offset) : owner(owner), offset(offset) {}
|
||||
operator std::uint32_t() const { return owner.values[offset]; }
|
||||
Reference& operator=(std::uint32_t value) { owner.insert_or_assign(offset, value); return *this; }
|
||||
Reference& operator=(const Reference& other) { return *this = static_cast<std::uint32_t>(other); }
|
||||
Reference& operator|=(std::uint32_t value) { return *this = static_cast<std::uint32_t>(*this) | value; }
|
||||
Reference& operator&=(std::uint32_t value) { return *this = static_cast<std::uint32_t>(*this) & value; }
|
||||
Reference& operator^=(std::uint32_t value) { return *this = static_cast<std::uint32_t>(*this) ^ value; }
|
||||
Reference& operator+=(std::uint32_t value) { return *this = static_cast<std::uint32_t>(*this) + value; }
|
||||
private:
|
||||
Registers& owner;
|
||||
std::uint32_t offset;
|
||||
};
|
||||
|
||||
class const_iterator {
|
||||
public:
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
|
|
@ -57,6 +81,18 @@ public:
|
|||
using iterator = const_iterator;
|
||||
|
||||
Registers() = default;
|
||||
Registers(const Registers&) = default;
|
||||
Registers& operator=(const Registers&) = default;
|
||||
Registers(Registers&& other) noexcept : values(std::move(other.values)), present(std::move(other.present)), entries(std::exchange(other.entries, 0)), mask(std::move(other.mask)), fingerprint(std::exchange(other.fingerprint, 0)) {}
|
||||
Registers& operator=(Registers&& other) noexcept {
|
||||
if (this == &other) return *this;
|
||||
values = std::move(other.values);
|
||||
present = std::move(other.present);
|
||||
entries = std::exchange(other.entries, 0);
|
||||
mask = std::move(other.mask);
|
||||
fingerprint = std::exchange(other.fingerprint, 0);
|
||||
return *this;
|
||||
}
|
||||
Registers(std::initializer_list<std::pair<std::uint32_t, std::uint32_t>> entries) {
|
||||
for (const auto& [offset, value] : entries) emplace(offset, value);
|
||||
}
|
||||
|
|
@ -83,27 +119,33 @@ public:
|
|||
values.clear();
|
||||
present.clear();
|
||||
entries = 0;
|
||||
fingerprint = 0;
|
||||
}
|
||||
std::pair<const_iterator, bool> emplace(std::uint32_t offset, std::uint32_t value) {
|
||||
if (contains(offset)) return {const_iterator{this, offset}, false};
|
||||
mark(offset) = value;
|
||||
changeFingerprint(offset, value);
|
||||
return {const_iterator{this, offset}, true};
|
||||
}
|
||||
std::pair<const_iterator, bool> insert_or_assign(std::uint32_t offset, std::uint32_t value) {
|
||||
if (contains(offset)) {
|
||||
if (values[offset] == value) return {const_iterator{this, offset}, false};
|
||||
changeFingerprint(offset, values[offset]);
|
||||
values[offset] = value;
|
||||
changeFingerprint(offset, value);
|
||||
return {const_iterator{this, offset}, false};
|
||||
}
|
||||
mark(offset) = value;
|
||||
changeFingerprint(offset, value);
|
||||
return {const_iterator{this, offset}, true};
|
||||
}
|
||||
std::uint32_t& operator[](std::uint32_t offset) {
|
||||
if (contains(offset)) return values[offset];
|
||||
return mark(offset) = 0;
|
||||
Reference operator[](std::uint32_t offset) {
|
||||
if (!contains(offset)) emplace(offset, 0);
|
||||
return {*this, offset};
|
||||
}
|
||||
std::uint32_t& at(std::uint32_t offset) {
|
||||
Reference at(std::uint32_t offset) {
|
||||
if (!contains(offset)) throw std::out_of_range("register is not set");
|
||||
return values[offset];
|
||||
return {*this, offset};
|
||||
}
|
||||
const std::uint32_t& at(std::uint32_t offset) const {
|
||||
if (!contains(offset)) throw std::out_of_range("register is not set");
|
||||
|
|
@ -111,6 +153,7 @@ public:
|
|||
}
|
||||
std::size_t erase(std::uint32_t offset) {
|
||||
if (!contains(offset)) return 0;
|
||||
changeFingerprint(offset, values[offset]);
|
||||
present[offset / 64] &= ~(std::uint64_t{1} << (offset % 64));
|
||||
--entries;
|
||||
return 1;
|
||||
|
|
@ -123,8 +166,31 @@ public:
|
|||
}
|
||||
return a == end() && b == other.end();
|
||||
}
|
||||
std::uint64_t Fingerprint(const std::shared_ptr<const Mask>& selected) const {
|
||||
if (mask != selected) {
|
||||
fingerprint = RecomputeFingerprint(*selected);
|
||||
mask = selected;
|
||||
}
|
||||
return fingerprint;
|
||||
}
|
||||
std::uint64_t RecomputeFingerprint(const Mask& selected) const {
|
||||
std::uint64_t result = 0;
|
||||
for (const auto [offset, value] : *this) {
|
||||
if (selected.Contains(offset)) result ^= contribution(offset, value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
static std::uint64_t contribution(std::uint32_t offset, std::uint32_t value) {
|
||||
auto hash = ((static_cast<std::uint64_t>(offset) << 32u) | value) + 0x9e3779b97f4a7c15ull;
|
||||
hash = (hash ^ (hash >> 30u)) * 0xbf58476d1ce4e5b9ull;
|
||||
hash = (hash ^ (hash >> 27u)) * 0x94d049bb133111ebull;
|
||||
return hash ^ (hash >> 31u);
|
||||
}
|
||||
void changeFingerprint(std::uint32_t offset, std::uint32_t value) {
|
||||
if (mask != nullptr && mask->Contains(offset)) fingerprint ^= contribution(offset, value);
|
||||
}
|
||||
static constexpr std::size_t End = ~std::size_t{0};
|
||||
std::uint32_t& mark(std::uint32_t offset) {
|
||||
if (offset >= values.size()) {
|
||||
|
|
@ -147,6 +213,8 @@ private:
|
|||
std::vector<std::uint32_t> values;
|
||||
std::vector<std::uint64_t> present;
|
||||
std::size_t entries = 0;
|
||||
mutable std::shared_ptr<const Mask> mask;
|
||||
mutable std::uint64_t fingerprint = 0;
|
||||
};
|
||||
|
||||
inline Registers BuildInitialContextRegisters() {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawRegisterKey.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
|
|
@ -6,36 +7,69 @@
|
|||
|
||||
namespace AgcDriver::DriverDetail {
|
||||
|
||||
namespace {
|
||||
|
||||
bool runtimeRegister(std::uint32_t offset) {
|
||||
return offset - 0x00cu < 32u || offset - 0x08cu < 32u || offset - 0x10cu < 32u || offset == 0x082u || offset == 0x083u || offset == 0x102u || offset == 0x103u;
|
||||
}
|
||||
|
||||
const auto& registerMasks() {
|
||||
static const auto masks = [] {
|
||||
std::array<std::vector<std::uint64_t>, 3> bits;
|
||||
for (const auto& range : Graphics::DrawKeyRegisters) {
|
||||
auto& bank = bits[static_cast<std::size_t>(range.bank)];
|
||||
bank.resize(std::max(bank.size(), (static_cast<std::size_t>(range.first) + range.count + 63) / 64));
|
||||
for (auto offset = range.first; offset < range.first + range.count; ++offset) {
|
||||
if (range.bank == Graphics::RegisterBank::Shader && runtimeRegister(offset)) continue;
|
||||
bank[offset / 64] |= std::uint64_t{1} << (offset % 64);
|
||||
}
|
||||
}
|
||||
std::array<std::shared_ptr<const Registers::Mask>, 3> result;
|
||||
for (std::size_t i = 0; i < bits.size(); ++i) result[i] = std::make_shared<const Registers::Mask>(std::move(bits[i]));
|
||||
return result;
|
||||
}();
|
||||
return masks;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::uint64_t RegisterStateKey(const QueueState& queue, bool allUserWords, bool incremental) {
|
||||
const auto& masks = registerMasks();
|
||||
const auto fingerprint = [&](const Registers& registers, Graphics::RegisterBank bank) {
|
||||
const auto& mask = masks[static_cast<std::size_t>(bank)];
|
||||
return incremental ? registers.Fingerprint(mask) : registers.RecomputeFingerprint(*mask);
|
||||
};
|
||||
auto shape = (0xcbf29ce484222325ull ^ fingerprint(queue.context, Graphics::RegisterBank::Context)) * 0x100000001b3ull;
|
||||
shape = (shape ^ fingerprint(queue.shader, Graphics::RegisterBank::Shader)) * 0x100000001b3ull;
|
||||
shape = (shape ^ fingerprint(queue.userConfig, Graphics::RegisterBank::UserConfig)) * 0x100000001b3ull;
|
||||
auto exact = shape;
|
||||
const auto words = [&](std::uint32_t base, std::uint32_t count) {
|
||||
for (auto it = queue.shader.lower_bound(base); it != queue.shader.end() && it->first < base + count; ++it) {
|
||||
exact = (exact ^ it->first) * 0x100000001b3ull;
|
||||
exact = (exact ^ it->second) * 0x100000001b3ull;
|
||||
}
|
||||
};
|
||||
for (const auto base : {0x00cu, 0x08cu, 0x10cu}) {
|
||||
const auto resources = queue.shader.find(base - 1);
|
||||
const auto count = resources == queue.shader.end() ? 0u : ((resources->second >> 1u) & 0x1fu) | (((resources->second >> 27u) & 1u) << 5u);
|
||||
words(base, allUserWords ? 32u : std::min(count, 32u));
|
||||
}
|
||||
words(0x082u, 2);
|
||||
words(0x102u, 2);
|
||||
return exact;
|
||||
}
|
||||
|
||||
|
||||
std::uint64_t Driver::drawRegisterKey(const QueueState& queue, const ShaderRegistry& registry, std::uint64_t deviceSerial) {
|
||||
static const bool allUserWords = std::getenv("APS5_DRAW_KEY_ALL_USER_WORDS") != nullptr;
|
||||
std::uint64_t key = 0xcbf29ce484222325ull;
|
||||
static const bool verify = std::getenv("APS5_VERIFY_REGISTER_KEYS") != nullptr;
|
||||
auto key = RegisterStateKey(queue, allUserWords);
|
||||
if (verify && key != RegisterStateKey(queue, allUserWords, false)) throw std::runtime_error("incremental register fingerprint disagrees with full state");
|
||||
const auto mix = [&](std::uint64_t value) {
|
||||
key ^= value;
|
||||
key *= 0x100000001b3ull;
|
||||
};
|
||||
const auto userEnd = [&](std::uint32_t base) {
|
||||
if (allUserWords) return base + 32u;
|
||||
const auto resources = queue.shader.find(base - 1);
|
||||
if (resources == queue.shader.end()) return base;
|
||||
const auto count = ((resources->second >> 1u) & 0x1fu) | (((resources->second >> 27u) & 1u) << 5u);
|
||||
return base + std::min(count, 32u);
|
||||
};
|
||||
const std::array<std::pair<std::uint32_t, std::uint32_t>, 3> users{{{0x00cu, userEnd(0x00cu)}, {0x08cu, userEnd(0x08cu)}, {0x10cu, userEnd(0x10cu)}}};
|
||||
const auto unread = [&](std::uint32_t offset) {
|
||||
return std::any_of(users.begin(), users.end(), [&](const auto& user) { return offset >= user.second && offset < user.first + 32u; });
|
||||
};
|
||||
mix(deviceSerial);
|
||||
for (const auto& range : Graphics::DrawKeyRegisters) {
|
||||
const auto& bank = range.bank == Graphics::RegisterBank::Context ? queue.context : range.bank == Graphics::RegisterBank::Shader ? queue.shader : queue.userConfig;
|
||||
mix((static_cast<std::uint64_t>(range.bank) << 32u) | range.first);
|
||||
const auto end = range.first + range.count;
|
||||
const bool shader = range.bank == Graphics::RegisterBank::Shader;
|
||||
for (auto it = bank.lower_bound(range.first); it != bank.end() && it->first < end; ++it) {
|
||||
if (shader && unread(it->first)) continue;
|
||||
mix(it->first);
|
||||
mix(it->second);
|
||||
}
|
||||
}
|
||||
for (const auto base : {0x008u, 0x088u, 0x0c8u, 0x108u, 0x148u}) {
|
||||
const auto low = queue.shader.find(base);
|
||||
const auto high = queue.shader.find(base + 1);
|
||||
|
|
|
|||
|
|
@ -2378,7 +2378,7 @@ void AlphaToCoverageTests() {
|
|||
Require(AgcDriver::Graphics::DrawRejection(queue, false).find("alpha-to-coverage") != std::string::npos, "the precheck accepted unsupported alpha-to-coverage");
|
||||
}
|
||||
queue.context[0x2dc] = 0xaa01;
|
||||
const auto exportFormat = queue.context.at(0x1c5);
|
||||
const std::uint32_t exportFormat = queue.context.at(0x1c5);
|
||||
for (const auto format : {4u, 5u, 6u, 9u}) {
|
||||
queue.context[0x1c5] = (exportFormat & ~0xfu) | format;
|
||||
Require(AgcDriver::Graphics::DecodeState(queue).alphaToCoverage, "alpha-to-coverage rejected an MRT0 export format with a floating-point alpha");
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawRegisterKey.hpp"
|
||||
#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp"
|
||||
#include "prx/libSceAgcDriver/Submit/include/Acb.hpp"
|
||||
#include "prx/libc/include/Shutdown.hpp"
|
||||
|
|
@ -50,8 +51,10 @@ std::uint32_t low(const void* pointer) { return static_cast<std::uint32_t>(reint
|
|||
std::uint32_t high(const void* pointer) { return static_cast<std::uint32_t>(reinterpret_cast<std::uintptr_t>(pointer) >> 32u); }
|
||||
|
||||
void execute(AgcDriver::QueueState& state, const std::vector<std::uint32_t>& packet) {
|
||||
AgcDriver::DriverDetail::RegisterStateKey(state);
|
||||
AgcDriver::Pm4::Validate(packet, 0);
|
||||
AgcDriver::Pm4::Execute(packet, state);
|
||||
check(AgcDriver::DriverDetail::RegisterStateKey(state) == AgcDriver::DriverDetail::RegisterStateKey(state, false, false), "PM4 changed registers without updating their fingerprint");
|
||||
}
|
||||
|
||||
void testCatalog() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,140 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawRegisterKey.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/State.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <random>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
using namespace AgcDriver;
|
||||
using namespace AgcDriver::DriverDetail;
|
||||
|
||||
namespace {
|
||||
|
||||
void check(bool condition, const char* message) {
|
||||
if (!condition) throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
std::uint64_t baselineRegisterStateKey(const QueueState& queue, bool allUserWords = false) {
|
||||
std::uint64_t key = 0xcbf29ce484222325ull;
|
||||
const auto mix = [&](std::uint64_t value) {
|
||||
key ^= value;
|
||||
key *= 0x100000001b3ull;
|
||||
};
|
||||
const auto userEnd = [&](std::uint32_t base) {
|
||||
if (allUserWords) return base + 32u;
|
||||
const auto resources = queue.shader.find(base - 1);
|
||||
if (resources == queue.shader.end()) return base;
|
||||
const auto count = ((resources->second >> 1u) & 0x1fu) | (((resources->second >> 27u) & 1u) << 5u);
|
||||
return base + std::min(count, 32u);
|
||||
};
|
||||
const std::array<std::pair<std::uint32_t, std::uint32_t>, 3> users{{{0x00cu, userEnd(0x00cu)}, {0x08cu, userEnd(0x08cu)}, {0x10cu, userEnd(0x10cu)}}};
|
||||
const auto unread = [&](std::uint32_t offset) {
|
||||
return std::any_of(users.begin(), users.end(), [&](const auto& user) { return offset >= user.second && offset < user.first + 32u; });
|
||||
};
|
||||
for (const auto& range : Graphics::DrawKeyRegisters) {
|
||||
const auto& bank = range.bank == Graphics::RegisterBank::Context ? queue.context : range.bank == Graphics::RegisterBank::Shader ? queue.shader : queue.userConfig;
|
||||
mix((static_cast<std::uint64_t>(range.bank) << 32u) | range.first);
|
||||
const auto end = range.first + range.count;
|
||||
const bool shader = range.bank == Graphics::RegisterBank::Shader;
|
||||
for (auto it = bank.lower_bound(range.first); it != bank.end() && it->first < end; ++it) {
|
||||
if (shader && unread(it->first)) continue;
|
||||
mix(it->first);
|
||||
mix(it->second);
|
||||
}
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
void testFingerprints() {
|
||||
static_assert(!std::is_convertible_v<Registers::Reference, std::uint32_t&>);
|
||||
std::mt19937 random(42);
|
||||
QueueState queue;
|
||||
auto reference = baselineRegisterStateKey(queue);
|
||||
auto tracked = RegisterStateKey(queue);
|
||||
const auto compare = [&] {
|
||||
const auto expected = baselineRegisterStateKey(queue);
|
||||
const auto actual = RegisterStateKey(queue);
|
||||
check(actual == RegisterStateKey(queue, false, false), "incremental fingerprint missed a mutation");
|
||||
check(RegisterStateKey(queue, true) == RegisterStateKey(queue, true, false), "all-user fingerprint missed a mutation");
|
||||
check((expected == reference) == (actual == tracked), "exact key changed its dependency set");
|
||||
reference = expected;
|
||||
tracked = actual;
|
||||
};
|
||||
for (const auto& range : Graphics::DrawKeyRegisters) {
|
||||
auto& bank = range.bank == Graphics::RegisterBank::Context ? queue.context : range.bank == Graphics::RegisterBank::Shader ? queue.shader : queue.userConfig;
|
||||
for (auto offset = range.first; offset < range.first + range.count; ++offset) {
|
||||
bank.erase(offset); compare();
|
||||
bank.emplace(offset, 0); compare();
|
||||
bank.insert_or_assign(offset, 0); compare();
|
||||
bank[offset] = random(); compare();
|
||||
bank.at(offset) ^= 1; compare();
|
||||
}
|
||||
}
|
||||
for (std::uint32_t i = 0; i < 30000; ++i) {
|
||||
auto& bank = i % 3 == 0 ? queue.context : i % 3 == 1 ? queue.shader : queue.userConfig;
|
||||
const auto offset = random() % 0x500;
|
||||
switch (i % 11) {
|
||||
case 0: bank.erase(offset); break;
|
||||
case 1: bank.emplace(offset, random()); break;
|
||||
case 2: bank[offset] |= random(); break;
|
||||
case 3: bank[offset] &= random(); break;
|
||||
case 6: bank[offset] += random(); break;
|
||||
case 4: {
|
||||
auto retained = bank[offset];
|
||||
bank.insert_or_assign(0x1000, random());
|
||||
compare();
|
||||
retained = random();
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
auto copy = bank;
|
||||
copy.insert_or_assign(offset, random());
|
||||
compare();
|
||||
bank = copy;
|
||||
break;
|
||||
}
|
||||
default: bank.insert_or_assign(offset, random()); break;
|
||||
}
|
||||
compare();
|
||||
}
|
||||
queue.shader[0x8b] = 0; compare();
|
||||
queue.shader[0x8c] = random(); compare();
|
||||
queue.shader[0x8b] = 2; compare();
|
||||
queue.shader[0x8b] = 1u << 27u; compare();
|
||||
queue.shader.erase(0x8b); compare();
|
||||
queue.savedContext = queue.context;
|
||||
queue.ClearContext(); compare();
|
||||
queue.context = *queue.savedContext; compare();
|
||||
auto moved = std::move(queue.context);
|
||||
check(queue.context.empty(), "moved register bank retained its population");
|
||||
queue.context.emplace(0x200, 1); compare();
|
||||
queue.context = std::move(moved); compare();
|
||||
queue.context = queue.context; compare();
|
||||
queue.context.clear(); compare();
|
||||
queue.context.emplace(0x200, 0); compare();
|
||||
queue = QueueState{}; compare();
|
||||
Registers registers{{0, 7}, {64, 9}};
|
||||
auto low = std::make_shared<const Registers::Mask>(std::vector<std::uint64_t>{1});
|
||||
auto high = std::make_shared<const Registers::Mask>(std::vector<std::uint64_t>{0, 1});
|
||||
const auto lowHash = registers.Fingerprint(low);
|
||||
check(registers.Fingerprint(high) != lowHash && registers.Fingerprint(low) == lowHash, "projection switch reused another mask's hash");
|
||||
registers[64] = 8;
|
||||
check(registers.Fingerprint(low) == lowHash && registers.Fingerprint(high) == registers.RecomputeFingerprint(*high), "projection switch lost a masked write");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
testFingerprints();
|
||||
std::puts("Register fingerprint dependencies and mutations passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::fprintf(stderr, "%s\n", error.what());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue