Merge pull request #4518 from AlxcBTW/fix/bda-unaligned-dword-write

fix(shader): store unaligned 32-bit BDA writes byte by byte
This commit is contained in:
Dean 2026-10-13 07:34:34 +13:00 committed by GitHub
commit c90404f97a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 24 additions and 5 deletions

View File

@ -9,6 +9,7 @@
#include <limits>
#include <set>
#include <string>
#include <utility>
#include <vector>
namespace {
@ -345,15 +346,16 @@ void RunBdaStoreExecutionTests(const Context& context) {
constexpr std::uint32_t writable = Abi::Read | Abi::Write;
const Abi::Header header{Abi::Version, 2, sizeof(Abi::Range), 0};
std::array<Abi::Range, 2> ranges{};
bool runtime = false;
const auto run = [&](const char* name, std::uint64_t address, std::uint32_t bits, bool coherent, const std::array<std::uint8_t, 5>& expected, std::initializer_list<std::uint64_t> writtenAddresses, Abi::FaultReason reason = {}, std::uint64_t faultAddress = 0, std::uint32_t faultBytes = 0) {
const std::string label = std::string("BDA store ") + name + (coherent ? " coherent: " : ": ");
const std::string label = std::string(runtime ? "BDA runtime write " : "BDA store ") + name + (coherent ? " coherent: " : ": ");
std::memset(first.Bytes().data(), 0xcd, first.Bytes().size());
std::memset(second.Bytes().data(), 0xcd, second.Bytes().size());
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());
std::memset(output.Bytes().data(), 0, output.Bytes().size());
Pipeline pipeline(context, MakeBdaStoreTestShader(address, 0xa1b2c3d4u, bits, coherent), {&table, &fault, &output});
Pipeline pipeline(context, runtime ? MakeBdaWriteTestShader(address, 0xa1b2c3d4u, bits, coherent) : MakeBdaStoreTestShader(address, 0xa1b2c3d4u, bits, coherent), {&table, &fault, &output});
pipeline.Run(1);
std::array<std::uint8_t, 5> actual{};
std::memcpy(actual.data(), first.Bytes().data(), 2);
@ -387,7 +389,8 @@ void RunBdaStoreExecutionTests(const Context& context) {
std::memcpy(&completed, output.Bytes().data(), sizeof(completed));
Require(completed == 1, label + "store did not finish its per-byte accesses");
};
for (const bool coherent : {false, true}) {
for (const auto& [coherent, writes] : std::initializer_list<std::pair<bool, bool>>{{false, false}, {false, true}, {true, false}, {true, true}}) {
runtime = writes;
ranges = {{{guest, guest + 2, first.DeviceAddress(), writable, 0}, {guest + 2, guest + 5, second.DeviceAddress(), writable, 0}}};
run("byte", guest + 1, 8, coherent, {0xcd, 0xd4, 0xcd, 0xcd, 0xcd}, {guest + 1});
run("split short", guest + 1, 16, coherent, {0xcd, 0xd4, 0xc3, 0xcd, 0xcd}, {guest + 1, guest + 2});

View File

@ -121,3 +121,15 @@ std::vector<std::uint32_t> MakeBdaStoreTestShader(std::uint64_t address, std::ui
#endif
return shader;
}
std::vector<std::uint32_t> MakeBdaWriteTestShader(std::uint64_t address, std::uint32_t value, std::uint32_t bits, bool coherent) {
using namespace ShaderRecompiler;
auto shader = MakeShader(address, coherent, true, [&](SpirvValueEmitContext& ctx, const IrValue& instruction, std::uint32_t base) {
EmitBdaWrite(ctx, instruction, base, ConstantU32(ctx.state, value), bits);
return std::vector<std::uint32_t>{ConstantU32(ctx.state, 1u)};
}, 0u, true);
#if ANYPS5_ENABLE_SPIRV_TOOLS
shader = ValidateAndOptimizeSpirv(shader, VK_API_VERSION_1_1, shader[1], false, false);
#endif
return shader;
}

View File

@ -10,6 +10,7 @@ std::vector<std::uint32_t> MakeBdaDwordReadTestShader(std::uint64_t address, std
std::vector<std::uint32_t> MakeBdaSpanReadTestShader(std::uint64_t address, std::uint32_t offset, std::uint32_t extracted, bool coherent, bool stops);
std::vector<std::uint32_t> MakeBdaDwordWriteTestShader(std::uint64_t address, std::uint32_t dwords, const std::uint32_t* values);
std::vector<std::uint32_t> MakeBdaStoreTestShader(std::uint64_t address, std::uint32_t value, std::uint32_t bits, bool coherent);
std::vector<std::uint32_t> MakeBdaWriteTestShader(std::uint64_t address, std::uint32_t value, std::uint32_t bits, bool coherent);
void RunBdaStoreExecutionTests(const AgcDriver::Graphics::Context& context);
void RunBdaExecutionTests(const AgcDriver::Graphics::Context& context);
void RunBdaContractTests();

View File

@ -358,7 +358,7 @@ void EmitBdaWrite(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t
if (state.bdaWritePointerFunction == 0 || state.bdaNoteWriteFunction == 0) ctx.Fail(inst, "BDA write functions are missing");
const auto instruction = BdaInstructionPc(state, inst);
if (bits != 8u && bits != 16u && bits != 32u) ctx.Fail(inst, "unsupported BDA write width");
if (bits != 32u) {
const auto storeBytes = [&] {
EmitBdaOverflowCheck(state, address, bits / 8u, instruction);
for (std::uint32_t byte = 0u; byte < bits / 8u; ++byte) {
const auto guest = Binary(state, spv::OpIAdd, TypeScalarU64(state), address, BdaConstant(state, byte));
@ -373,10 +373,13 @@ void EmitBdaWrite(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t
state.module.AddFunction(spv::OpFunctionCall, state.module.Type(spv::OpTypeVoid), state.module.AllocateId(), state.bdaNoteWriteFunction, guest);
});
}
};
if (bits != 32u) {
storeBytes();
return;
}
const auto unaligned = BdaAddressUnaligned(state, address);
EmitIfCondition(state, unaligned, [&] { RecordBdaFault(state, address, ConstantU32(state, 4u), instruction, BdaAbi::FaultReason::Unaligned); });
EmitIfCondition(state, unaligned, storeBytes);
EmitIfCondition(state, Unary(state, spv::OpLogicalNot, TypeBool(state), unaligned), [&] {
EmitBdaStoreAt(state, address, 4u, TypePhysicalU32Pointer(state), value, instruction, BdaAccessMask(state, inst));
});