fix(agc): note BDA-written pages at full address width and read back only the claimed ones

A shader store through the BDA table records its 4 KiB page in the fault
buffer, and the driver marks those pages written after the use. The page
number was truncated to 32 bits, so a store above 16 TiB marked a page at
another address. And the driver scanned all 4096 slots after every use,
even when nothing was stored.

Slots now hold a 128 KiB chunk (its number plus one, 32 bits for any
address below 512 TiB) and a mask of its 4 KiB pages. A shader that
claims a slot appends it to a list, with a count. The driver reads the
count, decodes only the claimed slots, marks each run of pages once and
clears only those slots. A writable BDA range past 512 TiB is refused
when the table is built.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
raghav314 2026-10-11 05:07:36 +05:30
parent 6968564656
commit c0348a02bf
5 changed files with 206 additions and 35 deletions

View File

@ -124,7 +124,7 @@ BdaResources::BdaResources(const Context& context) {
static_assert(std::endian::native == std::endian::little);
Require(ShaderRecompiler::BdaAbi::FaultBufferBytes <= context.limits.maxStorageBufferRange, "BDA fault buffer exceeds storage buffer range limit");
fault = std::make_unique<Buffer>(context, ShaderRecompiler::BdaAbi::FaultBufferBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
std::memset(fault->Bytes().data(), 0, fault->Bytes().size());
std::memset(fault->Bytes().data(), 0, ShaderRecompiler::BdaAbi::WrittenListWord * sizeof(std::uint32_t));
}
BdaResources::BdaResources(const Context& context, const GuestBufferMemory& memory) : BdaResources(context) {
@ -192,6 +192,7 @@ BdaResources::BdaResources(const Context& context, const GuestBufferMemory& memo
++it;
}
}
for (const auto& range : ranges) Require((range.permissions & ShaderRecompiler::BdaAbi::Write) == 0 || range.end <= ShaderRecompiler::BdaAbi::WrittenAddressLimit, "a writable BDA range ends past the addresses the written-page record can hold");
table = std::make_shared<Buffer>(context, tableBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
const ShaderRecompiler::BdaAbi::Header header{ShaderRecompiler::BdaAbi::Version, static_cast<std::uint32_t>(ranges.size()), sizeof(ShaderRecompiler::BdaAbi::Range), 0};
std::memcpy(table->Bytes().data(), &header, sizeof(header));
@ -276,15 +277,16 @@ namespace AgcDriver::Graphics {
void BdaResources::markWrittenPages() const {
namespace Abi = ShaderRecompiler::BdaAbi;
auto* words = reinterpret_cast<std::uint32_t*>(fault->Bytes().data());
Require(words[Abi::WrittenOverflowWord] == 0, "more than " + std::to_string(Abi::WrittenPageSlots) + " pages stored to through the BDA table in one use are not implemented");
bool any = false;
for (std::uint32_t slot = 0; slot < Abi::WrittenPageSlots; ++slot) {
const auto page = words[Abi::WrittenSlotsWord + slot];
if (page == 0) continue;
GuestMemory::MarkWritten(static_cast<std::uint64_t>(page - 1u) << Abi::WrittenPageShift, std::size_t{1} << Abi::WrittenPageShift);
any = true;
Require(words[Abi::WrittenOverflowWord] == 0, "more than " + std::to_string(Abi::WrittenPageSlots) + " chunks of " + std::to_string(Abi::WrittenChunkPages) + " pages stored to through the BDA table in one use are not implemented");
const auto count = words[Abi::WrittenCountWord];
if (count == 0) return;
Require(Abi::ForEachWrittenRange(words, [](std::uint64_t address, std::size_t bytes) { GuestMemory::MarkWritten(address, bytes); }), "invalid BDA written-page record");
for (std::uint32_t claim = 0; claim < count; ++claim) {
const auto slot = words[Abi::WrittenListWord + claim];
words[Abi::WrittenSlotsWord + 2 * slot] = 0;
words[Abi::WrittenSlotsWord + 2 * slot + 1] = 0;
}
if (any) std::memset(words + Abi::WrittenSlotsWord, 0, Abi::WrittenPageSlots * sizeof(std::uint32_t));
words[Abi::WrittenCountWord] = 0;
}
}

View File

@ -90,7 +90,46 @@ private:
}
static void RunWrittenRangeDecodeTests() {
namespace Abi = ShaderRecompiler::BdaAbi;
std::vector<std::uint32_t> words(Abi::FaultBufferBytes / sizeof(std::uint32_t));
std::vector<std::pair<std::uint64_t, std::size_t>> marked;
const auto decode = [&] {
marked.clear();
return Abi::ForEachWrittenRange(words.data(), [&](std::uint64_t address, std::size_t bytes) { marked.emplace_back(address, bytes); });
};
const auto claim = [&](std::uint32_t index, std::uint32_t slot, std::uint64_t address, std::uint32_t mask) {
words[Abi::WrittenListWord + index] = slot;
words[Abi::WrittenSlotsWord + 2 * slot] = Abi::WrittenChunkKey(address);
words[Abi::WrittenSlotsWord + 2 * slot + 1] = mask;
words[Abi::WrittenCountWord] = std::max(words[Abi::WrittenCountWord], index + 1);
};
constexpr std::uint64_t page = std::uint64_t{1} << Abi::WrittenPageShift;
Require(decode() && marked.empty(), "an empty written-page record marked pages");
constexpr std::uint64_t top = 0x7ffffffe0000ULL;
claim(0, 5, top, 0b1011u);
Require(decode() && marked == std::vector<std::pair<std::uint64_t, std::size_t>>{{top, 2 * page}, {top + 3 * page, page}}, "written pages near the top of the address space were not decoded as written");
std::fill(words.begin(), words.end(), 0u);
constexpr std::uint64_t high = 0x200000001000ULL;
claim(0, Abi::WrittenPageSlots - 1, high, 1u << ((high >> Abi::WrittenPageShift) & (Abi::WrittenChunkPages - 1u)));
Require(decode() && marked == std::vector<std::pair<std::uint64_t, std::size_t>>{{high, page}}, "a written page above 16 TiB was decoded at another address");
std::fill(words.begin(), words.end(), 0u);
constexpr std::uint64_t chunk = 0x3e0000000ULL;
claim(0, 7, chunk, 0xffffffffu);
claim(1, 8, chunk + (std::uint64_t{1} << Abi::WrittenChunkShift), 1u);
claim(2, 9, chunk - (std::uint64_t{1} << Abi::WrittenChunkShift), 0x80000000u);
Require(decode() && marked == std::vector<std::pair<std::uint64_t, std::size_t>>{{chunk, 32 * page}, {chunk + 32 * page, page}, {chunk - page, page}}, "whole, first-page and last-page chunks were not decoded exactly");
words[Abi::WrittenListWord + 1] = Abi::WrittenPageSlots;
Require(!decode(), "a claim outside the slots was accepted");
std::fill(words.begin(), words.end(), 0u);
words[Abi::WrittenCountWord] = 1;
Require(!decode(), "a claim of an empty slot was accepted");
words[Abi::WrittenCountWord] = Abi::WrittenPageSlots + 1;
Require(!decode(), "more claims than slots were accepted");
}
static void RunBdaDwordWriteTests(const Context& context) {
RunWrittenRangeDecodeTests();
namespace Abi = ShaderRecompiler::BdaAbi;
constexpr std::uint64_t guest = 0x7fff12340f80ULL;
constexpr std::size_t firstBytes = 160;
@ -114,20 +153,23 @@ static void RunBdaDwordWriteTests(const Context& context) {
}
return nullptr;
};
const auto noted = [&](std::uint64_t address) {
const auto page = static_cast<std::uint32_t>(address >> Abi::WrittenPageShift) + 1u;
std::vector<std::uint32_t> slots(Abi::WrittenPageSlots);
std::memcpy(slots.data(), fault.Bytes().data() + Abi::WrittenSlotsWord * sizeof(std::uint32_t), slots.size() * sizeof(std::uint32_t));
return std::find(slots.begin(), slots.end(), page) != slots.end();
const auto notedBytes = [&](std::uint64_t first, std::uint64_t last) {
std::uint64_t covered = 0, total = 0;
const auto* faultWords = reinterpret_cast<const std::uint32_t*>(fault.Bytes().data());
Require(Abi::ForEachWrittenRange(faultWords, [&](std::uint64_t begin, std::size_t bytes) {
total += bytes;
for (const auto address : {first, last}) covered += address >= begin && address < begin + bytes ? 1u : 0u;
}), "a BDA dword write left an invalid written-page record");
return std::pair{covered, total};
};
const auto write = [&](std::uint64_t address, std::uint32_t dwords, std::uint32_t storedBytes, Abi::FaultReason reason) {
const auto write = [&](std::uint64_t address, std::uint32_t dwords, std::uint32_t storedBytes, Abi::FaultReason reason, std::uint32_t groups = 1) {
const Abi::Header header{Abi::Version, static_cast<std::uint32_t>(ranges.size()), sizeof(Abi::Range), 0};
std::memcpy(table.Bytes().data(), &header, sizeof(header));
std::memcpy(table.Bytes().data() + sizeof(header), ranges.data(), sizeof(ranges));
std::memset(fault.Bytes().data(), 0, fault.Bytes().size());
for (auto* buffer : {&first, &second, &readOnly}) std::memset(buffer->Bytes().data(), 0xa5, buffer->Bytes().size());
Pipeline pipeline(context, MakeBdaDwordWriteTestShader(address, dwords, values.data()), {&table, &fault, &output});
pipeline.Run(1);
pipeline.Run(groups);
Abi::Fault report{};
std::memcpy(&report, fault.Bytes().data(), sizeof(report));
for (std::uint32_t byte = 0; byte < dwords * 4u; ++byte) {
@ -142,7 +184,17 @@ static void RunBdaDwordWriteTests(const Context& context) {
} else {
Require(report.state == Abi::FaultState::Ready && report.reason == reason && report.instruction == 0x1234, "a BDA dword write did not publish its fault");
}
if (storedBytes != 0) Require(noted(address) && noted(address + storedBytes - 1u), "a BDA dword write did not note its written pages");
const auto [covered, total] = notedBytes(address, address + (storedBytes != 0 ? storedBytes - 1u : 0u));
if (storedBytes == 0) {
Require(total == 0, "a BDA dword write that stored nothing noted pages");
} else {
const auto pages = ((address + storedBytes - 1u) >> Abi::WrittenPageShift) - (address >> Abi::WrittenPageShift) + 1u;
Require(covered == 2 && total == pages << Abi::WrittenPageShift, "a BDA dword write did not note exactly its written pages");
const auto chunks = ((address + storedBytes - 1u) >> Abi::WrittenChunkShift) - (address >> Abi::WrittenChunkShift) + 1u;
std::uint32_t claims = 0;
std::memcpy(&claims, fault.Bytes().data() + Abi::WrittenCountWord * sizeof(std::uint32_t), sizeof(claims));
Require(claims == chunks, "a BDA dword write claimed a written-page slot more than once per chunk");
}
};
write(guest + 16, 4, 16, static_cast<Abi::FaultReason>(0));
write(guest + 20, 4, 16, static_cast<Abi::FaultReason>(0));
@ -154,6 +206,7 @@ static void RunBdaDwordWriteTests(const Context& context) {
write(guest + 41, 4, 16, static_cast<Abi::FaultReason>(0));
write(guest + firstBytes - 8, 4, 16, static_cast<Abi::FaultReason>(0));
write(guest + firstBytes + 12, 4, 4, Abi::FaultReason::Permission);
write(guest + 0x7c, 2, 8, static_cast<Abi::FaultReason>(0), 64);
}
void RunBdaExecutionTests(const Context& context) {

View File

@ -1,6 +1,8 @@
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "prx/libc/include/GuestWriteWatch.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#ifdef _WIN32
@ -8,6 +10,8 @@
#define NOMINMAX
#endif
#include <windows.h>
#else
#include <sys/mman.h>
#endif
#include <array>
#include <cstdio>
@ -27,6 +31,9 @@ constexpr std::uint32_t MaskedThreads = 16;
constexpr std::size_t BlockBytes = 65536;
constexpr std::uint32_t CheckedBytes = 0x2000;
constexpr std::uint8_t Fill = 0xcd;
#ifdef __linux__
constexpr std::array<std::uintptr_t, 2> GuestWindowAddresses{0x80000000000, 0x200000000000};
#endif
alignas(256) constexpr std::array<std::uint32_t, 45> FlatStoreCode{
0x34020082, 0x34040084, 0x340c0081, 0x340e0083, 0x4a060200, 0x7e080201, 0x4a1806ff, 0x00000204,
@ -39,28 +46,54 @@ alignas(256) constexpr std::array<std::uint32_t, 45> FlatStoreCode{
class GuestBlock {
public:
explicit GuestBlock(bool writable) {
explicit GuestBlock(bool writable, bool watched = false, std::uintptr_t at = 0) : bytes(watched ? 2 * BlockBytes : BlockBytes), watched(watched) {
#ifdef _WIN32
block = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
Require(!watched, "flat store: watched guest blocks are mapped only on Linux");
block = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
#else
block = static_cast<std::uint8_t*>(std::aligned_alloc(BlockBytes, BlockBytes));
if (watched) {
#ifdef __linux__
void* raw = mmap(reinterpret_cast<void*>(at), bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0);
Require(raw == reinterpret_cast<void*>(at), "flat store: cannot map the watched guest block at its address");
block = static_cast<std::uint8_t*>(raw);
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(block, bytes);
#else
Require(false, "flat store: watched guest blocks are mapped only on Linux");
#endif
} else {
block = static_cast<std::uint8_t*>(std::aligned_alloc(BlockBytes, bytes));
}
#endif
Require(block != nullptr, "flat store: cannot allocate the guest block");
Require(!watched || AgcDriver::GuestMemory::Watched(Address(), bytes), "flat store: the guest block is not write-watched");
Clear();
GuestAllocations::Mutation().Add(block, BlockBytes, true, writable, true);
GuestAllocations::Mutation().Add(block, bytes, true, writable, true);
}
~GuestBlock() {
GuestAllocations::Mutation().Remove(block);
#ifdef _WIN32
VirtualFree(block, 0, MEM_RELEASE);
#else
if (watched) {
GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(block, bytes);
munmap(block, bytes);
} else {
std::free(block);
}
#endif
}
GuestBlock(const GuestBlock&) = delete;
GuestBlock& operator=(const GuestBlock&) = delete;
void Clear() { std::memset(block, Fill, BlockBytes); }
void Clear() { std::memset(block, Fill, bytes); }
const std::uint8_t* Data() const { return block; }
std::uint64_t Address() const { return reinterpret_cast<std::uintptr_t>(block); }
private:
std::size_t bytes;
bool watched;
std::uint8_t* block = nullptr;
};
@ -116,6 +149,23 @@ void RunStores(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t
}
}
#ifdef __linux__
void RunStampedBlocks(AgcDriver::VulkanDevice& device, GuestBlock& guest) {
namespace GuestMemory = AgcDriver::GuestMemory;
const auto base = guest.Address();
Dispatch(device, 32, guest.Data());
for (int use = 0; use < 2; ++use) {
guest.Clear();
const auto collected = GuestMemory::CollectWrites(base, 2 * BlockBytes);
Require(collected != 0, "flat store: the watched guest blocks are not collected");
Require(GuestMemory::UnchangedSince(base, 2 * BlockBytes, collected), "flat store: the guest blocks changed before the store");
Dispatch(device, 32, guest.Data());
Require(!GuestMemory::UnchangedSince(base, BlockBytes, collected), "flat store: the stored block is not stamped as written");
Require(GuestMemory::UnchangedSince(base + BlockBytes, BlockBytes, collected), "flat store: a block no store reached is stamped as written");
}
}
#endif
void RunReadOnly(AgcDriver::VulkanDevice& device, const GuestBlock& guest) {
Dispatch(device, 32, guest.Data());
for (std::uint32_t offset = 0; offset < CheckedBytes; ++offset) {
@ -134,6 +184,14 @@ int main() {
RunStores(*device, writable, 32);
RunStores(*device, writable, 64);
RunReadOnly(*device, readOnly);
#ifdef __linux__
if (AgcDriver::GuestMemory::WriteWatched()) {
for (const auto at : GuestWindowAddresses) {
GuestBlock watched(true, true, at);
RunStampedBlocks(*device, watched);
}
}
#endif
std::puts("flat store tests passed");
return 0;
} catch (const std::exception& error) {

View File

@ -1,6 +1,7 @@
#ifndef CORE_SHADER_RECOMPILER_BDAABI_HPP
#define CORE_SHADER_RECOMPILER_BDAABI_HPP
#include <bit>
#include <cstddef>
#include <cstdint>
#include <type_traits>
@ -46,12 +47,45 @@ static_assert(std::is_standard_layout_v<Fault> && std::is_trivially_copyable_v<F
static_assert(offsetof(Fault, address) == 8 && offsetof(Fault, instruction) == 24);
inline constexpr std::uint32_t WrittenPageShift = 12;
inline constexpr std::uint32_t WrittenPageSlots = 4096;
inline constexpr std::uint32_t WrittenChunkShift = 17;
inline constexpr std::uint32_t WrittenChunkPages = 1u << (WrittenChunkShift - WrittenPageShift);
inline constexpr std::uint32_t WrittenPageSlots = 2048;
inline constexpr std::uint32_t WrittenPageProbes = 8;
inline constexpr std::uint64_t WrittenAddressLimit = std::uint64_t{0xffffffffu} << WrittenChunkShift;
inline constexpr std::uint32_t WrittenOverflowWord = sizeof(Fault) / sizeof(std::uint32_t);
inline constexpr std::uint32_t WrittenSlotsWord = WrittenOverflowWord + 1;
inline constexpr std::size_t FaultBufferBytes = (WrittenSlotsWord + WrittenPageSlots) * sizeof(std::uint32_t);
inline constexpr std::uint32_t WrittenCountWord = WrittenOverflowWord + 1;
inline constexpr std::uint32_t WrittenSlotsWord = WrittenCountWord + 1;
inline constexpr std::uint32_t WrittenListWord = WrittenSlotsWord + 2 * WrittenPageSlots;
inline constexpr std::size_t FaultBufferBytes = (WrittenListWord + WrittenPageSlots) * sizeof(std::uint32_t);
static_assert((WrittenPageSlots & (WrittenPageSlots - 1)) == 0);
static_assert(WrittenChunkPages == 32);
inline constexpr std::uint32_t WrittenChunkKey(std::uint64_t address) {
return static_cast<std::uint32_t>(address >> WrittenChunkShift) + 1u;
}
template<typename TMark>
bool ForEachWrittenRange(const std::uint32_t* words, TMark&& mark) {
const auto count = words[WrittenCountWord];
if (count > WrittenPageSlots) return false;
for (std::uint32_t claim = 0; claim < count; ++claim) {
const auto slot = words[WrittenListWord + claim];
if (slot >= WrittenPageSlots || words[WrittenSlotsWord + 2 * slot] == 0) return false;
}
for (std::uint32_t claim = 0; claim < count; ++claim) {
const auto slot = words[WrittenListWord + claim];
const auto key = words[WrittenSlotsWord + 2 * slot];
auto mask = words[WrittenSlotsWord + 2 * slot + 1];
const auto base = static_cast<std::uint64_t>(key - 1u) << WrittenChunkShift;
while (mask != 0) {
const auto first = static_cast<std::uint32_t>(std::countr_zero(mask));
const auto pages = static_cast<std::uint32_t>(std::countr_one(mask >> first));
mark(base + (static_cast<std::uint64_t>(first) << WrittenPageShift), static_cast<std::size_t>(pages) << WrittenPageShift);
mask = pages + first >= 32u ? 0u : mask & ~((1u << (first + pages)) - 1u);
}
}
return true;
}
}

View File

@ -132,10 +132,17 @@ static std::uint32_t DefineBdaNoteWrite(SpirvEmitterState& state) {
state.module.AddFunction(spv::OpVariable, TypePointer(state, spv::StorageClassFunction, boolean), done, spv::StorageClassFunction);
const auto counter = state.module.AllocateId();
state.module.AddFunction(spv::OpVariable, TypePointer(state, spv::StorageClassFunction, u32), counter, spv::StorageClassFunction);
const auto held = state.module.AllocateId();
state.module.AddFunction(spv::OpVariable, TypePointer(state, spv::StorageClassFunction, u32), held, spv::StorageClassFunction);
state.module.AddFunction(spv::OpStore, done, ConstantBool(state, false));
state.module.AddFunction(spv::OpStore, counter, constant(0u));
const auto page = binary(spv::OpIAdd, u32, Unary(state, spv::OpUConvert, u32, binary(spv::OpShiftRightLogical, TypeScalarU64(state), address, BdaConstant(state, BdaAbi::WrittenPageShift))), constant(1));
const auto hash = binary(spv::OpShiftRightLogical, u32, binary(spv::OpIMul, u32, page, constant(0x9e3779b1u)), constant(32u - static_cast<std::uint32_t>(std::countr_zero(BdaAbi::WrittenPageSlots))));
state.module.AddFunction(spv::OpStore, held, constant(0u));
const auto u64 = TypeScalarU64(state);
const auto key = binary(spv::OpIAdd, u32, Unary(state, spv::OpUConvert, u32, binary(spv::OpShiftRightLogical, u64, address, BdaConstant(state, BdaAbi::WrittenChunkShift))), constant(1));
const auto page = binary(spv::OpBitwiseAnd, u32, Unary(state, spv::OpUConvert, u32, binary(spv::OpShiftRightLogical, u64, address, BdaConstant(state, BdaAbi::WrittenPageShift))), constant(BdaAbi::WrittenChunkPages - 1u));
const auto bit = binary(spv::OpShiftLeftLogical, u32, constant(1u), page);
const auto hash = binary(spv::OpShiftRightLogical, u32, binary(spv::OpIMul, u32, key, constant(0x9e3779b1u)), constant(32u - static_cast<std::uint32_t>(std::countr_zero(BdaAbi::WrittenPageSlots))));
const auto keyWordOf = [&](std::uint32_t slot) { return binary(spv::OpIAdd, u32, constant(BdaAbi::WrittenSlotsWord), binary(spv::OpShiftLeftLogical, u32, slot, constant(1u))); };
const auto scope = constant(spv::ScopeDevice);
const auto relaxed = constant(spv::MemorySemanticsMaskNone);
const auto header = state.module.AllocateId();
@ -152,16 +159,23 @@ static std::uint32_t DefineBdaNoteWrite(SpirvEmitterState& state) {
state.module.AddFunction(spv::OpLoopMerge, merge, continuation, spv::LoopControlMaskNone);
state.module.AddFunction(spv::OpBranchConditional, searching, body, merge);
EmitLabel(state, body);
const auto slot = binary(spv::OpIAdd, u32, constant(BdaAbi::WrittenSlotsWord), binary(spv::OpBitwiseAnd, u32, binary(spv::OpIAdd, u32, hash, probe), constant(BdaAbi::WrittenPageSlots - 1u)));
const auto slot = binary(spv::OpBitwiseAnd, u32, binary(spv::OpIAdd, u32, hash, probe), constant(BdaAbi::WrittenPageSlots - 1u));
const auto keyWord = keyWordOf(slot);
state.module.AddFunction(spv::OpStore, held, slot);
const auto current = state.module.AllocateId();
state.module.AddFunction(spv::OpAtomicLoad, u32, current, BdaWord(state, state.faultBufferVariable, slot), scope, relaxed);
const auto held = binary(spv::OpIEqual, boolean, current, page);
state.module.AddFunction(spv::OpStore, done, held);
EmitIfCondition(state, Unary(state, spv::OpLogicalNot, boolean, held), [&] {
state.module.AddFunction(spv::OpAtomicLoad, u32, current, BdaWord(state, state.faultBufferVariable, keyWord), scope, relaxed);
const auto same = binary(spv::OpIEqual, boolean, current, key);
state.module.AddFunction(spv::OpStore, done, same);
EmitIfCondition(state, Unary(state, spv::OpLogicalNot, boolean, same), [&] {
const auto previous = state.module.AllocateId();
state.module.AddFunction(spv::OpAtomicCompareExchange, u32, previous, BdaWord(state, state.faultBufferVariable, slot), scope, relaxed, relaxed, page, constant(0u));
const auto taken = binary(spv::OpLogicalOr, boolean, binary(spv::OpIEqual, boolean, previous, constant(0u)), binary(spv::OpIEqual, boolean, previous, page));
state.module.AddFunction(spv::OpStore, done, taken);
state.module.AddFunction(spv::OpAtomicCompareExchange, u32, previous, BdaWord(state, state.faultBufferVariable, keyWord), scope, relaxed, relaxed, key, constant(0u));
const auto claimed = binary(spv::OpIEqual, boolean, previous, constant(0u));
state.module.AddFunction(spv::OpStore, done, binary(spv::OpLogicalOr, boolean, claimed, binary(spv::OpIEqual, boolean, previous, key)));
EmitIfCondition(state, claimed, [&] {
const auto index = state.module.AllocateId();
state.module.AddFunction(spv::OpAtomicIAdd, u32, index, BdaWord(state, state.faultBufferVariable, constant(BdaAbi::WrittenCountWord)), scope, relaxed, constant(1u));
state.module.AddFunction(spv::OpAtomicStore, BdaWord(state, state.faultBufferVariable, binary(spv::OpIAdd, u32, constant(BdaAbi::WrittenListWord), index)), scope, relaxed, slot);
});
});
state.module.AddFunction(spv::OpBranch, continuation);
EmitLabel(state, continuation);
@ -170,6 +184,16 @@ static std::uint32_t DefineBdaNoteWrite(SpirvEmitterState& state) {
EmitLabel(state, merge);
const auto noted = state.module.AllocateId();
state.module.AddFunction(spv::OpLoad, boolean, noted, done);
EmitIfCondition(state, noted, [&] {
const auto heldSlot = state.module.AllocateId();
state.module.AddFunction(spv::OpLoad, u32, heldSlot, held);
const auto maskWord = binary(spv::OpIAdd, u32, keyWordOf(heldSlot), constant(1u));
const auto mask = state.module.AllocateId();
state.module.AddFunction(spv::OpAtomicLoad, u32, mask, BdaWord(state, state.faultBufferVariable, maskWord), scope, relaxed);
EmitIfCondition(state, binary(spv::OpIEqual, boolean, binary(spv::OpBitwiseAnd, u32, mask, bit), constant(0u)), [&] {
state.module.AddFunction(spv::OpAtomicOr, u32, state.module.AllocateId(), BdaWord(state, state.faultBufferVariable, maskWord), scope, relaxed, bit);
});
});
EmitIfCondition(state, Unary(state, spv::OpLogicalNot, boolean, noted), [&] {
state.module.AddFunction(spv::OpAtomicStore, BdaWord(state, state.faultBufferVariable, constant(BdaAbi::WrittenOverflowWord)), scope, relaxed, constant(1u));
});