fix(agc): expire collected write proofs on allocation changes

This commit is contained in:
Zenodeon committed 2026-10-20 18:48:22 +04:00
1 parent b8f6d316a9
commit c19dcecfec
3 files changed
+59 -8

No files matched your search

@@ -1383,6 +1383,14 @@ if(BUILD_TESTING)
set_tests_properties(agc_collect_mapping_history_ring PROPERTIES ENVIRONMENT "APS5_NO_EPOCH_COLLECT_MAP=1")
set_tests_properties(agc_collect_mapping_history_shared PROPERTIES ENVIRONMENT "APS5_SHARED_COLLECT_MEMO=1")
set_tests_properties(agc_collect_mapping_history_uncached PROPERTIES ENVIRONMENT "APS5_NO_COLLECT_MEMO=1")
if(WIN32)
foreach(mode IN ITEMS cached uncached)
add_test(NAME agc_hint_mapping_history_${mode} COMMAND agc_driver_recorder_tests --hint-mapping-history-only)
set_tests_properties(agc_hint_mapping_history_${mode} PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30
ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endforeach()
set_tests_properties(agc_hint_mapping_history_uncached PROPERTIES ENVIRONMENT "APS5_NO_COLLECT_MEMO=1")
endif()
add_test(NAME agc_storage_kept_unit_settle COMMAND agc_driver_recorder_tests --kept-unit-settle-only)
set_tests_properties(agc_storage_kept_unit_settle PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30)
if(WIN32)
@@ -873,6 +873,7 @@ struct WriteTracker {
std::uintptr_t end = 0;
std::uint64_t epoch = 0;
std::uint64_t unwatched = 0;
std::uint64_t mapping = 0;
};
std::array<Memo, 256> memo{};
std::size_t nextMemo = 0;
@@ -1347,19 +1348,20 @@ std::uint64_t collectWrites(std::uint64_t address, std::size_t bytes, bool memoi
// must not differ between them.
const auto epoch = currentCollectEpoch();
const auto unwatched = unwatchSerial.load(std::memory_order_acquire);
const auto mapping = GuestAllocations::GuestAllocationsGeneration_nid_postfix();
const bool useMemo = memoized && collectMemoEnabled() && bytes != 0;
if (useMemo && !sharedCollectMemo()) {
// An entry exists only for a completed walk of an in-arena range, so the tracker is
// initialized and watched; nothing below the lock needs asking.
if (epochCollectMapEnabled()) {
const auto& map = epochCollectMap(epoch);
if (const auto found = map.ranges.find(first); found != map.ranges.end() && found->second.unwatched == unwatched && stop <= found->second.end) {
if (const auto found = map.ranges.find(first); found != map.ranges.end() && found->second.unwatched == unwatched && found->second.mapping == mapping && stop <= found->second.end) {
collectMemoHits.fetch_add(1, std::memory_order_relaxed);
return tracker.generation.load(std::memory_order_relaxed);
}
}
for (const auto& entry : threadCollectMemo.entries) {
if (entry.epoch == epoch && entry.unwatched == unwatched && entry.begin <= first && stop <= entry.end) {
if (entry.epoch == epoch && entry.unwatched == unwatched && entry.mapping == mapping && entry.begin <= first && stop <= entry.end) {
// The current generation, not the memoized one: blocks stamped since (MarkWritten, an
// overlapping collect) were written before this caller reads, and an older value would
// make every later UnchangedSince fail until the epoch ends.
@@ -1374,7 +1376,7 @@ std::uint64_t collectWrites(std::uint64_t address, std::size_t bytes, bool memoi
auto cursor = first;
if (useMemo && sharedCollectMemo()) {
for (const auto& entry : tracker.memo) {
if (entry.epoch == epoch && entry.unwatched == unwatchSerial.load(std::memory_order_relaxed) && entry.begin <= cursor && stop <= entry.end) {
if (entry.epoch == epoch && entry.unwatched == unwatchSerial.load(std::memory_order_relaxed) && entry.mapping == mapping && entry.begin <= cursor && stop <= entry.end) {
collectMemoHits.fetch_add(1, std::memory_order_relaxed);
return tracker.generation;
}
@@ -1395,12 +1397,12 @@ std::uint64_t collectWrites(std::uint64_t address, std::size_t bytes, bool memoi
// Only a completed walk is remembered; a failed one (uncommitted pages) returned 0 above.
if (collectMemoEnabled()) {
const auto serial = unwatchSerial.load(std::memory_order_relaxed);
if (sharedCollectMemo()) tracker.memo[tracker.nextMemo++ % tracker.memo.size()] = {first, stop, epoch, serial};
if (sharedCollectMemo()) tracker.memo[tracker.nextMemo++ % tracker.memo.size()] = {first, stop, epoch, serial, mapping};
else {
threadCollectMemo.entries[threadCollectMemo.next++ % threadCollectMemo.entries.size()] = {first, stop, epoch, serial};
threadCollectMemo.entries[threadCollectMemo.next++ % threadCollectMemo.entries.size()] = {first, stop, epoch, serial, mapping};
if (epochCollectMapEnabled()) {
auto& entry = epochCollectMap(epoch).ranges[first];
if (entry.unwatched != serial || entry.end < stop) entry = {first, stop, epoch, serial};
if (entry.unwatched != serial || entry.mapping != mapping || entry.end < stop) entry = {first, stop, epoch, serial, mapping};
}
}
}
@@ -1826,15 +1828,16 @@ std::uint64_t UntrackedHint(std::uint64_t address, std::size_t bytes) {
tracker.initialize();
if (!tracker.watched || !tracker.hinted(first, stop)) return 0;
const auto unwatched = unwatchSerial.load(std::memory_order_relaxed);
const auto mapping = GuestAllocations::GuestAllocationsGeneration_nid_postfix();
const bool memoized = collectMemoEnabled() && std::any_of(threadHintMemo.entries.begin(), threadHintMemo.entries.end(), [&](const WriteTracker::Memo& entry) {
return entry.epoch == epoch && entry.unwatched == unwatched && entry.begin <= first && stop <= entry.end;
return entry.epoch == epoch && entry.unwatched == unwatched && entry.mapping == mapping && entry.begin <= first && stop <= entry.end;
});
if (memoized) {
collectMemoHits.fetch_add(1, std::memory_order_relaxed);
} else {
const TimedAccess timed(CounterCollect, bytes);
if (!walkHints(tracker, first, stop)) return 0;
if (collectMemoEnabled()) threadHintMemo.entries[threadHintMemo.next++ % threadHintMemo.entries.size()] = {first, stop, epoch, unwatched};
if (collectMemoEnabled()) threadHintMemo.entries[threadHintMemo.next++ % threadHintMemo.entries.size()] = {first, stop, epoch, unwatched, mapping};
}
return tracker.newestHint(first, stop);
#else
@@ -169,6 +169,41 @@ std::optional<std::size_t> selectDevice(std::span<const VkPhysicalDeviceProperti
return selected;
}
bool hintMappingHistoryTests() {
#ifdef _WIN32
constexpr std::size_t bytes = 131072;
auto* memory = static_cast<std::uint8_t*>(AllocateWatched(bytes, 65536));
if (memory == nullptr) return false;
GuestAllocations::Mutation().Add(memory, bytes, true, true, false);
struct Cleanup {
std::uint8_t* memory;
~Cleanup() {
GuestAllocations::Mutation().Remove(memory);
ReleaseWatched(memory, bytes);
}
} cleanup{memory};
const auto address = reinterpret_cast<std::uint64_t>(memory);
std::memset(memory, 0x11, bytes);
AgcDriver::GuestMemory::BumpCollectEpoch();
AgcDriver::GuestMemory::Unwatch(address, bytes);
const auto before = AgcDriver::GuestMemory::UntrackedHint(address, bytes);
Require(before != 0, "the hint mapping fixture has no write hint");
Require(AgcDriver::GuestMemory::UntrackedHint(address, bytes) == before, "an unchanged hint range advanced its history");
memory[4096] = 0x22;
GuestAllocations::Mutation().Remove(memory);
GuestAllocations::Mutation().Add(memory, bytes, true, true, false);
const auto after = AgcDriver::GuestMemory::UntrackedHint(address, bytes);
Require(after > before, "a cached hint walk hid a CPU store across allocation replacement");
Require(memory[4096] == 0x22 && memory[bytes - 4096] == 0x11, "collecting replacement hints changed guest bytes");
GuestAllocations::Mutation().Remove(memory);
GuestAllocations::Mutation().Add(memory, bytes, true, true, false);
Require(AgcDriver::GuestMemory::UntrackedHint(address, bytes) == after, "allocation replacement invented a CPU write hint");
return true;
#else
return false;
#endif
}
void deviceSelectionTests() {
const auto properties = [](const char* name, VkPhysicalDeviceType type) {
VkPhysicalDeviceProperties result{};
@@ -9672,6 +9707,11 @@ int main(int argc, char** argv) {
std::cout << "collect mapping history tests passed\n";
return 0;
}
if (argc == 2 && std::string_view(argv[1]) == "--hint-mapping-history-only") {
if (!hintMappingHistoryTests()) return 77;
std::cout << "hint mapping history tests passed\n";
return 0;
}
if (argc == 2 && std::string_view(argv[1]) == "--device-selection-only") {
deviceSelectionTests();
std::cout << "recorder device selection tests passed\n";