fix(recompiler): give out-of-range relative vector accesses the measured semantics
M0 is unsigned, so an index at or past the window reads v0 and drops its write, v_swaprel_b32 writes neither side when either is out of range, and the window array starts zeroed like unwritten SSA registers; the window load counts as a lane source so a wave64 program's second half keeps its own registers. agc_driver_relative_indexing runs in wave32 and wave64 and pins the rows measured on gfx1035 in the review of #3856, and the TechnicalDebt entry states the measured behaviour, the window's end at the last named register and the sizes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
f8ed012e3e
commit
f9fd0f125c
|
|
@ -17,14 +17,15 @@ namespace {
|
|||
using AgcDriver::Graphics::Require;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Threads = 32;
|
||||
constexpr std::uint32_t MaxThreads = 64;
|
||||
constexpr std::uint32_t Inputs = 4;
|
||||
constexpr std::uint32_t Results = 16;
|
||||
alignas(256) std::array<std::uint32_t, Threads * Inputs> Input{};
|
||||
alignas(256) std::array<std::uint32_t, Threads * Results> Output{};
|
||||
constexpr std::uint32_t Results = 32;
|
||||
constexpr std::uint32_t Checked = 17;
|
||||
alignas(256) std::array<std::uint32_t, MaxThreads * Inputs> Input{};
|
||||
alignas(256) std::array<std::uint32_t, MaxThreads * Results> Output{};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 81> Code{
|
||||
0x34020084, 0x34060086, 0xe0301000, 0x80000401, 0x7e140280, 0x7e160280, 0x7e180280, 0x7e1a0280,
|
||||
alignas(256) constexpr std::array<std::uint32_t, 101> Code{
|
||||
0x34020084, 0x34060087, 0xe0301000, 0x80000401, 0x7e140280, 0x7e160280, 0x7e180280, 0x7e1a0280,
|
||||
0x7e1c0280, 0x7e1e0280, 0x7e200280, 0x7e220280, 0x7e240280, 0x7e260280, 0x7e280280, 0xbf8c3f70,
|
||||
0x7e3c0304, 0xd525001f, 0x00010304, 0xd5250020, 0x00010504, 0xd5250021, 0x00010704, 0xd5250022,
|
||||
0x00010904, 0xd5250023, 0x00010b04, 0xd5250024, 0x00010d04, 0xd5250025, 0x00010f04, 0xbefc0382,
|
||||
|
|
@ -34,44 +35,19 @@ alignas(256) constexpr std::array<std::uint32_t, 81> Code{
|
|||
0x0000011f, 0x7e280323, 0xe0701000, 0x80010a03, 0xe0701004, 0x80010b03, 0xe0701008, 0x80010c03,
|
||||
0xe070100c, 0x80010d03, 0xe0701010, 0x80010e03, 0xe0701014, 0x80010f03, 0xe0701018, 0x80011003,
|
||||
0xe070101c, 0x80011103, 0xe0701020, 0x80011203, 0xe0701024, 0x80011303, 0xe0701028, 0x80011403,
|
||||
0xbf810000,
|
||||
0xbefc03c1, 0x7e2a8720, 0xe070102c, 0x80011503, 0x7e428504, 0xe0701030, 0x80012003, 0x7e468922,
|
||||
0xe0701034, 0x80012203, 0xe0701038, 0x80012103, 0xbefc03ff, 0x000103ff, 0x7e3ed120, 0xe070103c,
|
||||
0x80012003, 0x7e48911e, 0xe0701040, 0x80012503, 0xbf810000,
|
||||
};
|
||||
|
||||
constexpr std::uint32_t Expected[32][11] = {
|
||||
{0x00001002u, 0x00001002u, 0x00001001u, 0x00001030u, 0x00001020u, 0x00001000u, 0x00001007u, 0x00001006u, 0x00001007u, 0x00001005u, 0x00001005u},
|
||||
{0x00002002u, 0x00002002u, 0x00002001u, 0x00002030u, 0x00002020u, 0x00002000u, 0x00002007u, 0x00002006u, 0x00002007u, 0x00002005u, 0x00002005u},
|
||||
{0x00003002u, 0x00003002u, 0x00003001u, 0x00003030u, 0x00003020u, 0x00003000u, 0x00003007u, 0x00003006u, 0x00003007u, 0x00003005u, 0x00003005u},
|
||||
{0x00004002u, 0x00004002u, 0x00004001u, 0x00004030u, 0x00004020u, 0x00004000u, 0x00004007u, 0x00004006u, 0x00004007u, 0x00004005u, 0x00004005u},
|
||||
{0x00005002u, 0x00005002u, 0x00005001u, 0x00005030u, 0x00005020u, 0x00005000u, 0x00005007u, 0x00005006u, 0x00005007u, 0x00005005u, 0x00005005u},
|
||||
{0x00006002u, 0x00006002u, 0x00006001u, 0x00006030u, 0x00006020u, 0x00006000u, 0x00006007u, 0x00006006u, 0x00006007u, 0x00006005u, 0x00006005u},
|
||||
{0x00007002u, 0x00007002u, 0x00007001u, 0x00007030u, 0x00007020u, 0x00007000u, 0x00007007u, 0x00007006u, 0x00007007u, 0x00007005u, 0x00007005u},
|
||||
{0x00008002u, 0x00008002u, 0x00008001u, 0x00008030u, 0x00008020u, 0x00008000u, 0x00008007u, 0x00008006u, 0x00008007u, 0x00008005u, 0x00008005u},
|
||||
{0x00009002u, 0x00009002u, 0x00009001u, 0x00009030u, 0x00009020u, 0x00009000u, 0x00009007u, 0x00009006u, 0x00009007u, 0x00009005u, 0x00009005u},
|
||||
{0x0000a002u, 0x0000a002u, 0x0000a001u, 0x0000a030u, 0x0000a020u, 0x0000a000u, 0x0000a007u, 0x0000a006u, 0x0000a007u, 0x0000a005u, 0x0000a005u},
|
||||
{0x0000b002u, 0x0000b002u, 0x0000b001u, 0x0000b030u, 0x0000b020u, 0x0000b000u, 0x0000b007u, 0x0000b006u, 0x0000b007u, 0x0000b005u, 0x0000b005u},
|
||||
{0x0000c002u, 0x0000c002u, 0x0000c001u, 0x0000c030u, 0x0000c020u, 0x0000c000u, 0x0000c007u, 0x0000c006u, 0x0000c007u, 0x0000c005u, 0x0000c005u},
|
||||
{0x0000d002u, 0x0000d002u, 0x0000d001u, 0x0000d030u, 0x0000d020u, 0x0000d000u, 0x0000d007u, 0x0000d006u, 0x0000d007u, 0x0000d005u, 0x0000d005u},
|
||||
{0x0000e002u, 0x0000e002u, 0x0000e001u, 0x0000e030u, 0x0000e020u, 0x0000e000u, 0x0000e007u, 0x0000e006u, 0x0000e007u, 0x0000e005u, 0x0000e005u},
|
||||
{0x0000f002u, 0x0000f002u, 0x0000f001u, 0x0000f030u, 0x0000f020u, 0x0000f000u, 0x0000f007u, 0x0000f006u, 0x0000f007u, 0x0000f005u, 0x0000f005u},
|
||||
{0x00010002u, 0x00010002u, 0x00010001u, 0x00010030u, 0x00010020u, 0x00010000u, 0x00010007u, 0x00010006u, 0x00010007u, 0x00010005u, 0x00010005u},
|
||||
{0x00011002u, 0x00011002u, 0x00011001u, 0x00011030u, 0x00011020u, 0x00011000u, 0x00011007u, 0x00011006u, 0x00011007u, 0x00011005u, 0x00011005u},
|
||||
{0x00012002u, 0x00012002u, 0x00012001u, 0x00012030u, 0x00012020u, 0x00012000u, 0x00012007u, 0x00012006u, 0x00012007u, 0x00012005u, 0x00012005u},
|
||||
{0x00013002u, 0x00013002u, 0x00013001u, 0x00013030u, 0x00013020u, 0x00013000u, 0x00013007u, 0x00013006u, 0x00013007u, 0x00013005u, 0x00013005u},
|
||||
{0x00014002u, 0x00014002u, 0x00014001u, 0x00014030u, 0x00014020u, 0x00014000u, 0x00014007u, 0x00014006u, 0x00014007u, 0x00014005u, 0x00014005u},
|
||||
{0x00015002u, 0x00015002u, 0x00015001u, 0x00015030u, 0x00015020u, 0x00015000u, 0x00015007u, 0x00015006u, 0x00015007u, 0x00015005u, 0x00015005u},
|
||||
{0x00016002u, 0x00016002u, 0x00016001u, 0x00016030u, 0x00016020u, 0x00016000u, 0x00016007u, 0x00016006u, 0x00016007u, 0x00016005u, 0x00016005u},
|
||||
{0x00017002u, 0x00017002u, 0x00017001u, 0x00017030u, 0x00017020u, 0x00017000u, 0x00017007u, 0x00017006u, 0x00017007u, 0x00017005u, 0x00017005u},
|
||||
{0x00018002u, 0x00018002u, 0x00018001u, 0x00018030u, 0x00018020u, 0x00018000u, 0x00018007u, 0x00018006u, 0x00018007u, 0x00018005u, 0x00018005u},
|
||||
{0x00019002u, 0x00019002u, 0x00019001u, 0x00019030u, 0x00019020u, 0x00019000u, 0x00019007u, 0x00019006u, 0x00019007u, 0x00019005u, 0x00019005u},
|
||||
{0x0001a002u, 0x0001a002u, 0x0001a001u, 0x0001a030u, 0x0001a020u, 0x0001a000u, 0x0001a007u, 0x0001a006u, 0x0001a007u, 0x0001a005u, 0x0001a005u},
|
||||
{0x0001b002u, 0x0001b002u, 0x0001b001u, 0x0001b030u, 0x0001b020u, 0x0001b000u, 0x0001b007u, 0x0001b006u, 0x0001b007u, 0x0001b005u, 0x0001b005u},
|
||||
{0x0001c002u, 0x0001c002u, 0x0001c001u, 0x0001c030u, 0x0001c020u, 0x0001c000u, 0x0001c007u, 0x0001c006u, 0x0001c007u, 0x0001c005u, 0x0001c005u},
|
||||
{0x0001d002u, 0x0001d002u, 0x0001d001u, 0x0001d030u, 0x0001d020u, 0x0001d000u, 0x0001d007u, 0x0001d006u, 0x0001d007u, 0x0001d005u, 0x0001d005u},
|
||||
{0x0001e002u, 0x0001e002u, 0x0001e001u, 0x0001e030u, 0x0001e020u, 0x0001e000u, 0x0001e007u, 0x0001e006u, 0x0001e007u, 0x0001e005u, 0x0001e005u},
|
||||
{0x0001f002u, 0x0001f002u, 0x0001f001u, 0x0001f030u, 0x0001f020u, 0x0001f000u, 0x0001f007u, 0x0001f006u, 0x0001f007u, 0x0001f005u, 0x0001f005u},
|
||||
{0x00020002u, 0x00020002u, 0x00020001u, 0x00020030u, 0x00020020u, 0x00020000u, 0x00020007u, 0x00020006u, 0x00020007u, 0x00020005u, 0x00020005u},
|
||||
};
|
||||
constexpr const char* Names[11] = {"movrelsd", "swapped_2", "sd2_untouched", "swaprel_dst", "swaprel_src", "swaprel_other", "swap_dst", "swap_src", "swaprel_same", "swaprel_same_other", "movrelsd_e64_out_of_range"};
|
||||
constexpr const char* Names[Checked] = {"movrelsd", "swapped_2", "sd2_untouched", "swaprel_dst", "swaprel_src", "swaprel_other", "swap_dst", "swap_src", "swaprel_same", "swaprel_same_other", "movrelsd_e64_dst",
|
||||
"movrels_out_of_range", "movreld_out_of_range_neighbour", "movrelsd_out_of_range_source", "movrelsd_out_of_range_other", "swaprel_one_side_out_of_range", "movrelsd_2_source_out_of_range"};
|
||||
|
||||
std::array<std::uint32_t, Checked> ExpectedFor(std::uint32_t tid) {
|
||||
const std::uint32_t x = 0x1000u * (tid + 1u);
|
||||
return {x + 2u, x + 2u, x + 1u, x + 0x30u, x + 0x20u, x, x + 7u, x + 6u, x + 7u, x + 5u, x + 5u,
|
||||
tid, x + 0x30u, x + 2u, x + 0x20u, x + 0x30u, tid};
|
||||
}
|
||||
|
||||
void Fill(std::uint32_t tid, std::uint32_t* words) {
|
||||
words[0] = 0x1000u * (tid + 1u);
|
||||
|
|
@ -88,12 +64,12 @@ std::string Hex(std::uint32_t value) {
|
|||
return text;
|
||||
}
|
||||
|
||||
void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) {
|
||||
Require(actual == expected, std::string("relative indexing: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected));
|
||||
void Expect(std::uint32_t waveSize, std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) {
|
||||
Require(actual == expected, std::string("relative indexing: wave") + std::to_string(waveSize) + " lane " + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected));
|
||||
}
|
||||
|
||||
void Run(AgcDriver::VulkanDevice& device) {
|
||||
for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]);
|
||||
void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, std::uint32_t threads) {
|
||||
for (std::uint32_t tid = 0; tid < threads; ++tid) Fill(tid, &Input[tid * Inputs]);
|
||||
Output.fill(0xdeadbeefu);
|
||||
std::vector<std::uint32_t> userData(8, 0u);
|
||||
const auto input = BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size() * 4u));
|
||||
|
|
@ -102,10 +78,10 @@ void Run(AgcDriver::VulkanDevice& device) {
|
|||
std::copy(output.begin(), output.end(), userData.begin() + 4);
|
||||
const std::span<const std::uint32_t> code(Code);
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
|
||||
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1};
|
||||
const ShaderRecompiler::ShaderComputeStageInfo compute{{threads, 1, 1}, 0u, {false, false, false}, false, 1};
|
||||
ShaderRecompiler::RecompileRequest request{
|
||||
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
|
||||
{32, 0, userData, compute, std::nullopt, std::nullopt, memory},
|
||||
{waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory},
|
||||
device.Target(),
|
||||
{0, 0, 0, 128}
|
||||
};
|
||||
|
|
@ -115,11 +91,11 @@ void Run(AgcDriver::VulkanDevice& device) {
|
|||
device.WaitIdle();
|
||||
}
|
||||
|
||||
void Check() {
|
||||
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
|
||||
const std::uint32_t* in = &Input[tid * Inputs];
|
||||
void Check(std::uint32_t waveSize, std::uint32_t threads) {
|
||||
for (std::uint32_t tid = 0; tid < threads; ++tid) {
|
||||
const std::uint32_t* out = &Output[tid * Results];
|
||||
for (std::uint32_t index = 0; index < 11; ++index) Expect(tid, out[index], Expected[tid][index], Names[index]);
|
||||
const auto expected = ExpectedFor(tid);
|
||||
for (std::uint32_t index = 0; index < Checked; ++index) Expect(waveSize, tid, out[index], expected[index], Names[index]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -129,8 +105,10 @@ int main() {
|
|||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
Run(*device);
|
||||
Check();
|
||||
Run(*device, 32, 32);
|
||||
Check(32, 32);
|
||||
Run(*device, 64, 64);
|
||||
Check(64, 64);
|
||||
std::puts("relative indexing tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ bool LaneSource(const IrProgram& program, const IrValue& value) {
|
|||
case IrOpcode::WriteLane:
|
||||
case IrOpcode::DataAppend:
|
||||
case IrOpcode::DataConsume:
|
||||
case IrOpcode::LoadVectorWindow:
|
||||
return true;
|
||||
case IrOpcode::GetBuiltin: {
|
||||
const auto* kind = value.Argument(0)->Resolve();
|
||||
|
|
|
|||
|
|
@ -1000,8 +1000,10 @@ void EmitProgram(SpirvEmitterState& state) {
|
|||
}
|
||||
}
|
||||
if (const auto window = state.program.Metadata().vectorWindowSize; window != 0u) {
|
||||
const auto array = state.module.Type(spv::OpTypeArray, TypeU32(state), ConstantU32(state, window));
|
||||
const auto zero = state.module.Constant(spv::OpConstantNull, array);
|
||||
for (std::uint32_t half = 0; half < state.laneCount; half++) {
|
||||
state.module.AddFunction(spv::OpVariable, TypeU32ArrayPointer(state, spv::StorageClassFunction, window), state.vectorWindowVariable.at(half), spv::StorageClassFunction);
|
||||
state.module.AddFunction(spv::OpVariable, TypePointer(state, spv::StorageClassFunction, array), state.vectorWindowVariable.at(half), spv::StorageClassFunction, zero);
|
||||
}
|
||||
}
|
||||
if (state.pixelValidMaskVariable != 0u) {
|
||||
|
|
|
|||
|
|
@ -319,7 +319,7 @@ private:
|
|||
void vMovrelsdB32(const RdnaInstruction& inst, bool split, bool swap);
|
||||
void vSwapB32(const RdnaInstruction& inst);
|
||||
IrU32 readRelativeVector(const RdnaOperand& base, IrValue& offset, const char* name);
|
||||
void writeRelativeVector(const RdnaOperand& base, IrValue& offset, IrU32 value, const char* name);
|
||||
void writeRelativeVector(const RdnaOperand& base, IrValue& offset, IrU32 value, const char* name, IrValue* enable = nullptr);
|
||||
void vReadfirstlaneB32(const RdnaInstruction& inst);
|
||||
void vReadlaneB32(const RdnaInstruction& inst);
|
||||
void vWritelaneB32(const RdnaInstruction& inst);
|
||||
|
|
|
|||
|
|
@ -293,62 +293,45 @@ void TranslationContext::sWqm(const RdnaInstruction& inst, bool wide) {
|
|||
ir.SetScc(ir.INotEqual(low.Value(), ir.Constant(0u)));
|
||||
}
|
||||
|
||||
// M0 holds a uniform register offset. Vector registers are SSA values with fixed indices, so an indexed
|
||||
// access becomes a select over every register the program references at or past the base.
|
||||
struct RelativeWindowAccess {
|
||||
IrValue* index = nullptr;
|
||||
IrValue* inRange = nullptr;
|
||||
};
|
||||
|
||||
RelativeWindowAccess relativeWindowAccess(IrBuilder& ir, const IrProgram& program, std::uint32_t reg, IrValue& offset) {
|
||||
RelativeWindowAccess relativeWindowAccess(IrBuilder& ir, const IrProgram& program, std::uint32_t reg, IrValue& offset, const char* name) {
|
||||
const auto& metadata = program.Metadata();
|
||||
if (metadata.vectorWindowSize == 0u) return {};
|
||||
if (reg < metadata.vectorWindowBase) throw std::runtime_error("relative vector access below the relative-addressing window");
|
||||
IrValue& raw = ir.IAdd(ir.Constant(reg - metadata.vectorWindowBase), offset);
|
||||
IrValue& index = ir.Emit(IrOpcode::UMin32, IrType::U32, {&raw, &ir.Constant(metadata.vectorWindowSize - 1u)});
|
||||
return {&index, &ir.ULessThan(raw, ir.Constant(metadata.vectorWindowSize))};
|
||||
if (metadata.vectorWindowSize == 0u || reg < metadata.vectorWindowBase) throw std::runtime_error(std::string(name) + " addresses a register outside the relative-addressing window");
|
||||
const auto first = reg - metadata.vectorWindowBase;
|
||||
IrValue& index = ir.Emit(IrOpcode::UMin32, IrType::U32, {&ir.IAdd(ir.Constant(first), offset), &ir.Constant(metadata.vectorWindowSize - 1u)});
|
||||
return {&index, &ir.ULessThan(offset, ir.Constant(metadata.vectorWindowSize - first))};
|
||||
}
|
||||
|
||||
IrU32 TranslationContext::readRelativeVector(const RdnaOperand& base, IrValue& offset, const char* name) {
|
||||
if (base.kind != RdnaOperandKind::VectorRegister) {
|
||||
throw std::runtime_error(std::string(name) + " source is not a vector register");
|
||||
}
|
||||
if (const auto access = relativeWindowAccess(ir, program, base.reg, offset); access.index != nullptr) {
|
||||
IrValue& loaded = ir.Emit(IrOpcode::LoadVectorWindow, IrType::U32, {access.index});
|
||||
return applyBitSourceModifiers(base, IrU32(ir.Select(*access.inRange, loaded, ir.Constant(0u))));
|
||||
}
|
||||
IrU32 result(ir.GetVectorReg(static_cast<VectorReg>(base.reg)));
|
||||
for (std::uint32_t reg = base.reg + 1u; reg < currentVectorLimit; ++reg) {
|
||||
IrValue& hit = ir.IEqual(offset, ir.Constant(reg - base.reg));
|
||||
result = IrU32(ir.Select(hit, ir.GetVectorReg(static_cast<VectorReg>(reg)), result.Value()));
|
||||
}
|
||||
return applyBitSourceModifiers(base, result);
|
||||
const auto access = relativeWindowAccess(ir, program, base.reg, offset, name);
|
||||
IrValue& loaded = ir.Emit(IrOpcode::LoadVectorWindow, IrType::U32, {access.index});
|
||||
return applyBitSourceModifiers(base, IrU32(ir.Select(*access.inRange, loaded, ir.GetVectorReg(static_cast<VectorReg>(0)))));
|
||||
}
|
||||
|
||||
void TranslationContext::writeRelativeVector(const RdnaOperand& base, IrValue& offset, IrU32 value, const char* name) {
|
||||
void TranslationContext::writeRelativeVector(const RdnaOperand& base, IrValue& offset, IrU32 value, const char* name, IrValue* enable) {
|
||||
const RdnaOperand& destination = base;
|
||||
if (destination.kind != RdnaOperandKind::VectorRegister) {
|
||||
throw std::runtime_error(std::string(name) + " destination is not a vector register");
|
||||
}
|
||||
if (const auto access = relativeWindowAccess(ir, program, destination.reg, offset); access.index != nullptr) {
|
||||
IrValue* index = access.index;
|
||||
IrValue& previous = ir.Emit(IrOpcode::LoadVectorWindow, IrType::U32, {index});
|
||||
IrValue* merged = nullptr;
|
||||
if (destination.dpp) {
|
||||
const DppMoveFlags flags{destination.dpp8 ? DppMoveFlags::Lanes8 | destination.dppCtrl : destination.dppCtrl, static_cast<std::uint8_t>(destination.dppRowMask), static_cast<std::uint8_t>(destination.dppBankMask), destination.dppFetchInactive, destination.dppBoundCtrl};
|
||||
merged = &ir.Emit(IrOpcode::DppUpdateU32, IrType::U32, {&value.Value(), &previous, &ir.GetExec()}, flags);
|
||||
} else {
|
||||
merged = &ir.Select(ir.GetExec(), value.Value(), previous);
|
||||
}
|
||||
(void)ir.Emit(IrOpcode::StoreVectorWindow, IrType::Void, {index, &ir.Select(*access.inRange, *merged, previous)});
|
||||
return;
|
||||
}
|
||||
for (std::uint32_t reg = destination.reg; reg < currentVectorLimit; ++reg) {
|
||||
RdnaOperand target = destination;
|
||||
target.reg = reg;
|
||||
IrValue& hit = ir.IEqual(offset, ir.Constant(reg - destination.reg));
|
||||
writeRawU32(target, IrU32(ir.Select(hit, value.Value(), ir.GetVectorReg(static_cast<VectorReg>(reg)))));
|
||||
const auto access = relativeWindowAccess(ir, program, destination.reg, offset, name);
|
||||
IrValue& previous = ir.Emit(IrOpcode::LoadVectorWindow, IrType::U32, {access.index});
|
||||
IrValue* merged = nullptr;
|
||||
if (destination.dpp) {
|
||||
const DppMoveFlags flags{destination.dpp8 ? DppMoveFlags::Lanes8 | destination.dppCtrl : destination.dppCtrl, static_cast<std::uint8_t>(destination.dppRowMask), static_cast<std::uint8_t>(destination.dppBankMask), destination.dppFetchInactive, destination.dppBoundCtrl};
|
||||
merged = &ir.Emit(IrOpcode::DppUpdateU32, IrType::U32, {&value.Value(), &previous, &ir.GetExec()}, flags);
|
||||
} else {
|
||||
merged = &ir.Select(ir.GetExec(), value.Value(), previous);
|
||||
}
|
||||
IrValue* written = access.inRange;
|
||||
if (enable != nullptr) written = &ir.LogicalAnd(*written, *enable);
|
||||
(void)ir.Emit(IrOpcode::StoreVectorWindow, IrType::Void, {access.index, &ir.Select(*written, *merged, previous)});
|
||||
}
|
||||
|
||||
void TranslationContext::vMovrelsB32(const RdnaInstruction& inst) {
|
||||
|
|
@ -371,8 +354,9 @@ void TranslationContext::vMovrelsdB32(const RdnaInstruction& inst, bool split, b
|
|||
return;
|
||||
}
|
||||
const IrU32 previous = readRelativeVector(plainOperand(inst.destination), destinationOffset, name);
|
||||
writeRelativeVector(inst.destination, destinationOffset, value, name);
|
||||
writeRelativeVector(sourceAt(inst, 0u), sourceOffset, previous, name);
|
||||
IrValue& bothInRange = ir.LogicalAnd(*relativeWindowAccess(ir, program, sourceAt(inst, 0u).reg, sourceOffset, name).inRange, *relativeWindowAccess(ir, program, inst.destination.reg, destinationOffset, name).inRange);
|
||||
writeRelativeVector(inst.destination, destinationOffset, value, name, &bothInRange);
|
||||
writeRelativeVector(sourceAt(inst, 0u), sourceOffset, previous, name, &bothInRange);
|
||||
}
|
||||
|
||||
void TranslationContext::vSwapB32(const RdnaInstruction& inst) {
|
||||
|
|
|
|||
|
|
@ -577,7 +577,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [LINE_STIPPLE_TEX](../../core/shader/recompiler/Translation/src/InstructionTranslator.cpp) (recompiler) reads as 0: line stipple is not emulated. RADV enables the input only as the required pixel input of a shader that reads no interpolants, because it initializes the fewest VGPRs ([Mesa](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/amd/vulkan/radv_shader.c) `radv_compute_spi_ps_input`), and never reads it
|
||||
- [Draw snapshot buffer descriptors](../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) rewrite each affected array from element zero, preserving unchanged descriptors. MoltenVK 1.4.2 writes auxiliary buffer lengths at index `i` instead of `start + i` for partial array updates ([MVKDescriptorSet.mm, BufferAuxSize](https://github.com/KhronosGroup/MoltenVK/blob/v1.4.2/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm#L1051)). Per-element writes therefore let one buffer length replace another even when Vulkan buffer handles and ranges are correct.
|
||||
- [Static scalar calls](../../core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp) (`s_call_b64` and `s_swappc_b64`) require an ordinary even-aligned SGPR link pair (s0:s1 through s104:s105) and a later `s_setpc_b64` return reading that exact pair. A pair serves one call, or several calls whose regions are disjoint, each closed by the first return reading the pair after its target. Outside every region of its calls the pair may only be overwritten whole, by `s_mov_b32`, `s_mov_b64`, an SOP2 result or a scalar load, as the caller of a user-data callee does before each call. Regions must nest or be disjoint; recursion, other shared links, escaping link values (including overlapping tuples and resource descriptors), invalid targets, interleaved regions, cross-region branches or fall-through, relative SGPR reads and dynamic `s_setpc_b64` in a program with calls throw. The analysis checks all decoded instructions, including unreachable code. Arbitrary indirect calls and general call frames are not implemented.
|
||||
- [Relative vector addressing](../../core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp) (`v_movrels_b32`, `v_movreld_b32`, `v_movrelsd_b32`, `v_movrelsd_2_b32`, `v_swaprel_b32`) keeps every vector register from the lowest base register the program addresses relatively up to its last allocated register in a function-scope array per invocation (`LoadVectorWindow`/`StoreVectorWindow`, one element per register), so an indexed access is one load or store instead of a compare-and-select chain over the window; the host compiler decides whether that array stays in registers or spills to scratch, which is not measured. An index past the window reads 0 and its write is dropped, as the RDNA ISA describes for out-of-range VGPR indexing, where the earlier select chain read the base register and dropped the write; neither is measured on the console. Killzone: Liberation's PSP vertex program (PPSA11142) went from 649,515 to the recompiled size noted in its pull request with this.
|
||||
- [Relative vector addressing](../../core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp) (`v_movrels_b32`, `v_movreld_b32`, `v_movrelsd_b32`, `v_movrelsd_2_b32`, `v_swaprel_b32`) keeps every vector register from the lowest base register the program addresses relatively up to the last register the program names in a zero-initialised function-scope array per invocation (`LoadVectorWindow`/`StoreVectorWindow`, one element per register), so an indexed access is one load or store instead of a compare-and-select chain over the window; the host compiler decides whether that array stays in registers or spills to scratch, which is not measured. M0 is unsigned: an index at or past the window reads `v0` and drops its write, and `v_swaprel_b32` writes neither side when either is out of range, as measured on RDNA2 (gfx1035, `agc_driver_relative_indexing`). The window ends at the last register the program names rather than at the shader's VGPR allocation, which the recompiler does not receive, so a relative write past the named registers but inside the allocation is dropped where the hardware keeps it for a later relative read. Killzone: Liberation's PSP vertex program (PPSA11142, 3176 ISA words, 104 relative accesses over 154 registers) went from 649,515 to 392,333 SPIR-V words with this.
|
||||
- [User-data callees](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (libSceAgcDriver) - a compute shader's `s_swappc_b64` whose source pair is a user-data pair, or a copy of one made with `s_mov_b32` or `s_mov_b64` in the same block, calls the code at the address that user data holds. At dispatch the callee is read from guest memory up to its `s_setpc_b64` on the link pair, one copy of it is appended per call after the caller's last instruction, and the call is rewritten to `s_call_b64` to that copy. The words after the caller's last instruction are dropped; in the shaders seen they are metadata, and a caller that reads its program counter, which could address them, throws. A copy runs at another program counter than the callee, so a callee that reads its program counter, makes a call, ends the program, jumps through another pair, returns before code it branches to, spans more than 64 KiB or is not readable throws. Callees are read again whenever a dispatch misses the dispatch cache; a dispatch that hits it reuses the linked program, so a title that rewrote a callee in place at the same address would keep running the old code.
|
||||
- [DB_RENDER_CONTROL resummarize draws](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - HTILE is not modeled, so a RESUMMARIZE_ENABLE draw without depth or stencil tests and without color writes changes no modeled state and draws nothing; any other resummarize, copy or decompress draw throws
|
||||
- [s_call_b64](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp) saves `PC + 4` over `GetShaderBase` and branches to `PC + 4 + signext(SIMM16) * 4`, following [AMD RDNA2 ISA](https://gpuopen.com/download/rdna2-shader-instruction-set-architecture.pdf) sections 3.6.3, 12.2 and 12.3. Forward callees after `s_endpgm` are decoded. Measured on RDNA2 (gfx1036) in wave32 and wave64 with SIMM16 = 3: the link pair receives `PC + 4`, the branch goes to `PC + 4 + signext(SIMM16) * 4`, and `s_setpc_b64` of the link returns to the instruction after the call. Execution on the PS5 (gfx1013) and title compatibility for these bounded scalar calls remain unverified; `s_rfe_b64` still throws in translation.
|
||||
|
|
|
|||
Loading…
Reference in New Issue