shader: walk every resource plan with the x86 SRT walker

This commit is contained in:
nmzik
2026-10-24 02:57:24 +02:00
parent bdf48dcf3b
commit d7856753e0
12 changed files with 1782 additions and 1701 deletions
+49 -38
View File
@@ -4,7 +4,7 @@
#include "graphics/shader/recompiler/ir/passes/ReadLaneElimination.h"
#include "graphics/shader/recompiler/ir/passes/ResourceTracking.h"
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
#include "SrtWalkerTestMemory.h"
#include <cstdlib>
#include <iostream>
@@ -95,6 +95,16 @@ bool ReadMemory(void *userdata, uint64_t address, std::span<uint32_t> values) {
return true;
}
// Evaluates the DWORDs of a descriptor source that no resource consumes.
bool EvaluateSource(ResourcePlan &plan, const SrtRuntime &runtime, uint32_t source,
DescriptorValue &result) {
const auto &words = plan.descriptor_sources[source];
result = {.dword_count = words.dword_count};
for (uint32_t word = 0; word < words.dword_count; ++word)
if (!EvaluateSrtValue(plan, runtime, words.dwords[word], result.dwords[word])) return false;
return true;
}
Value Address(Fixture &fixture, Value low, Value high, uint32_t block = 0) {
return fixture.Emit(ValueOpcode::GetAddressResource, {low, high}, 0, block);
}
@@ -132,9 +142,9 @@ void TestImmediateFlatteningAndGvn() {
"flattened raw read was not kept as a planning-only root");
TestMemory memory_image{{{0x1020u, 0xfeedbeefu}}};
SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image};
FakeGuestMemory fake(ReadMemory, &memory_image);
std::vector<uint32_t> flat;
Check(SrtWalker(ExtractResourcePlan(fixture.program), runtime).RefreshFlatBuffer(flat), "flattened SRT walk failed");
Check(WalkSrt(ExtractResourcePlan(fixture.program), {}, flat, nullptr), "flattened SRT walk failed");
Check(flat == std::vector<uint32_t>{0xfeedbeefu} && memory_image.reads == 1,
"flattened SRT did not evaluate its canonical read once");
}
@@ -148,9 +158,9 @@ void TestRawScalarComponentAlignment() {
fixture.Plan();
TestMemory memory_image{{{0x1000u, 0x12345678u}}};
SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image};
FakeGuestMemory fake(ReadMemory, &memory_image);
std::vector<uint32_t> flat;
Check(SrtWalker(ExtractResourcePlan(fixture.program), runtime).RefreshFlatBuffer(flat), "raw scalar SRT walk failed");
Check(WalkSrt(ExtractResourcePlan(fixture.program), {}, flat, nullptr), "raw scalar SRT walk failed");
Check(
flat == std::vector<uint32_t>{0x12345678u} && memory_image.reads == 1,
"raw scalar base, immediate, and offset were not aligned independently");
@@ -203,9 +213,9 @@ void TestNestedSrtWalk() {
fixture.Plan();
TestMemory memory_image{{{0x1000u, 0x2000u}, {0x2000u, 0xabcdef01u}}};
SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image};
FakeGuestMemory fake(ReadMemory, &memory_image);
std::vector<uint32_t> flat;
Check(SrtWalker(ExtractResourcePlan(fixture.program), runtime).RefreshFlatBuffer(flat), "nested SRT walk failed");
Check(WalkSrt(ExtractResourcePlan(fixture.program), {}, flat, nullptr), "nested SRT walk failed");
Check(flat == std::vector<uint32_t>({0x2000u, 0xabcdef01u}),
"nested typed SRT reads were not evaluated in dependency order");
}
@@ -227,8 +237,9 @@ void TestShaderBaseAndUserData() {
const std::array user_data{0x20u};
SrtRuntime runtime{.user_data = user_data,
.shader_base = 0x12345678abcdef00ull};
auto plan = ExtractResourcePlan(fixture.program);
DescriptorValue result;
Check(SrtWalker(ExtractResourcePlan(fixture.program), runtime).EvaluateDescriptor(0, result),
Check(EvaluateSource(plan, runtime, 0, result),
"shader-relative descriptor evaluation failed");
Check(result.dword_count == 3 && result.dwords[0] == 0xabcdef00u &&
result.dwords[1] == 0x12345678u && result.dwords[2] == 0x24u,
@@ -249,8 +260,9 @@ void TestCarryAndBitFields() {
fixture.program.descriptor_sources.push_back(
{.dwords = {low, high, inserted, sign}, .dword_count = 4});
auto plan = ExtractResourcePlan(fixture.program);
DescriptorValue result;
Check(SrtWalker(ExtractResourcePlan(fixture.program), {}).EvaluateDescriptor(0, result),
Check(EvaluateSource(plan, {}, 0, result),
"carry and bit-field descriptor evaluation failed");
Check(result.dwords[0] == 1u && result.dwords[1] == 1u &&
result.dwords[2] == 0x89abcdefu && result.dwords[3] == 0xffffffffu,
@@ -273,12 +285,12 @@ void TestInvariantAndDivergentPhi() {
fixture.program.descriptor_sources.push_back(
{.dwords = {Value(&divergent)}, .dword_count = 1});
const auto plan = ExtractResourcePlan(fixture.program);
auto plan = ExtractResourcePlan(fixture.program);
DescriptorValue result;
Check(SrtWalker(plan, {}).EvaluateDescriptor(0, result) &&
Check(EvaluateSource(plan, {}, 0, result) &&
result.dwords[0] == 7u,
"loop-invariant typed phi was rejected");
Check(!SrtWalker(plan, {}).EvaluateDescriptor(1, result),
Check(!EvaluateSource(plan, {}, 1, result),
"divergent phi was accepted");
}
@@ -294,7 +306,7 @@ void TestOperandMutationLifecycle() {
source->ReplaceUsesWith(Value(0xaau));
uint32_t result = 0;
Check(source->UseCount() == 0 &&
SrtWalker(fixture.program, {}).Evaluate(inserted, result) && result == 0x3au,
EvaluateSrtValue(fixture.program, {}, inserted, result) && result == 0x3au,
"inline operand replacement changed the evaluated value");
for (const auto opcode : {ValueOpcode::GetImageResource, ValueOpcode::MakeImageAddress}) {
@@ -333,7 +345,7 @@ void TestOperandMutationLifecycle() {
const auto sum = fixture.Emit(ValueOpcode::IAdd32, {Value(&phi), Value(5u)}, 0, 5);
phi.ReplaceUsesWith(Value(6u));
Check(source->UseCount() == 0 && phi.NumPhiBlocks() == 0 && phi.NumArgs() == 1 &&
SrtWalker(fixture.program, {}).Evaluate(sum, result) && result == 11,
EvaluateSrtValue(fixture.program, {}, sum, result) && result == 11,
"Phi replacement retained incoming uses or changed the consumer value");
}
@@ -374,8 +386,9 @@ void TestRuntime64BitDescriptorOps() {
fixture.program.descriptor_sources.push_back(
{.dwords = {low, high}, .dword_count = 2});
auto plan = ExtractResourcePlan(fixture.program);
DescriptorValue result;
Check(SrtWalker(ExtractResourcePlan(fixture.program), {}).EvaluateDescriptor(0, result) &&
Check(EvaluateSource(plan, {}, 0, result) &&
result.dwords[0] == 0xabcdu && result.dwords[1] == 0x1235u,
"64-bit typed descriptor arithmetic evaluation is incorrect");
}
@@ -448,13 +461,13 @@ void TestUniformFirstLaneSamplerLod() {
std::array<uint32_t, 7> user_data{};
user_data[6] = 3u;
SrtRuntime runtime{.user_data = user_data};
const auto plan = ExtractResourcePlan(fixture.program);
auto plan = ExtractResourcePlan(fixture.program);
DescriptorValue result;
Check(SrtWalker(plan, runtime).EvaluateDescriptor(0, result) &&
Check(EvaluateSource(plan, runtime, 0, result) &&
result.dwords[0] == 0x00300300u,
"uniform sampler LOD evaluated incorrectly");
user_data[6] = 20u;
Check(SrtWalker(plan, runtime).EvaluateDescriptor(0, result) &&
Check(EvaluateSource(plan, runtime, 0, result) &&
result.dwords[0] == 0x00ffffffu,
"uniform sampler LOD clamp evaluated incorrectly");
}
@@ -489,13 +502,12 @@ void TestFloatComparisonDescriptorInputs() {
fixture.program.descriptor_sources.push_back(
{.dwords = {low, high}, .dword_count = 2});
const auto plan = ExtractResourcePlan(fixture.program);
auto plan = ExtractResourcePlan(fixture.program);
for (size_t index = 0; index < inputs.size(); index++) {
const auto &input = inputs[index];
const std::array user_data{input.bits};
DescriptorValue result;
Check(SrtWalker(plan, {.user_data = user_data})
.EvaluateDescriptor(0, result) &&
Check(EvaluateSource(plan, {.user_data = user_data}, 0, result) &&
result.dwords[0] == (flush && index < 4 ? 1 : input.less_equal) &&
result.dwords[1] == (flush && index < 4 ? 1 : input.greater_equal),
"descriptor comparison disagrees with native FP32 input mode");
@@ -551,7 +563,6 @@ void TestConstantBufferBounds() {
uint32_t immediate;
uint32_t stride;
uint32_t records;
bool valid;
uint32_t expected;
uint32_t reads;
};
@@ -565,17 +576,17 @@ void TestConstantBufferBounds() {
++probe.attempts;
return ReadMemory(&probe.memory, address, values);
};
// PS5 scalar-buffer loads return zero per out-of-range DWORD; an unreadable
// in-range address remains a failed memory read.
for (const auto &test : {Case{12u, 0u, 0u, 16u, true, 0xa5a5a5a5u, 1u},
Case{3u, 1u, 0u, 16u, true, 0x12345678u, 1u},
Case{0xfffffffcu, 4u, 0u, 16u, true, 0u, 0u},
Case{16u, 0u, 0u, 16u, true, 0u, 0u},
Case{12u, 0u, 0u, 14u, true, 0u, 0u},
Case{0u, 0u, 0u, 0u, true, 0u, 0u},
Case{8u, 0u, 0u, 16u, false, 0u, 1u},
Case{12u, 0u, 48u, 32768u, true, 0xa5a5a5a5u, 1u},
Case{0x6339c0u, 16u, 48u, 32768u, true, 0u, 0u}}) {
// PS5 scalar-buffer loads return zero per out-of-range DWORD without an access;
// an unreadable in-range address is accessed and reads as zero too.
for (const auto &test : {Case{12u, 0u, 0u, 16u, 0xa5a5a5a5u, 1u},
Case{3u, 1u, 0u, 16u, 0x12345678u, 1u},
Case{0xfffffffcu, 4u, 0u, 16u, 0u, 0u},
Case{16u, 0u, 0u, 16u, 0u, 0u},
Case{12u, 0u, 0u, 14u, 0u, 0u},
Case{0u, 0u, 0u, 0u, 0u, 0u},
Case{8u, 0u, 0u, 16u, 0u, 1u},
Case{12u, 0u, 48u, 32768u, 0xa5a5a5a5u, 1u},
Case{0x6339c0u, 16u, 48u, 32768u, 0u, 0u}}) {
Fixture fixture;
const auto memory = fixture.AddMemory(ResourceKind::ScalarBuffer, test.immediate);
const auto buffer =
@@ -588,12 +599,11 @@ void TestConstantBufferBounds() {
fixture.Plan();
MemoryProbe memory_image;
SrtRuntime runtime{.read_memory = read_memory, .userdata = &memory_image};
FakeGuestMemory fake(read_memory, &memory_image);
std::vector<uint32_t> flat;
Check(SrtWalker(ExtractResourcePlan(fixture.program), runtime).RefreshFlatBuffer(flat) == test.valid,
Check(WalkSrt(ExtractResourcePlan(fixture.program), {}, flat, nullptr),
"constant-buffer walk misaligned or wrapped its offset components");
Check((!test.valid || flat == std::vector<uint32_t>{test.expected}) &&
memory_image.attempts == test.reads,
Check(flat == std::vector<uint32_t>{test.expected} && memory_image.attempts == test.reads,
"constant-buffer walk read the wrong word or accessed an out-of-bounds address");
}
}
@@ -831,8 +841,9 @@ void TestUndefinedRuntimeValueFails() {
fixture.program.descriptor_sources.push_back(
{.dwords = {undef}, .dword_count = 1});
auto plan = ExtractResourcePlan(fixture.program);
DescriptorValue result;
Check(!SrtWalker(ExtractResourcePlan(fixture.program), {}).EvaluateDescriptor(0, result),
Check(!EvaluateSource(plan, {}, 0, result),
"undefined typed descriptor source was accepted");
}