Merge pull request #7880 from klosixgaming-arch/fix/shader-indirect-column-phi-pointer

fix(shader): resolve table column pointers through Phi nodes
This commit is contained in:
Dean authored and GitHub committed 2026-10-24 13:22:58 +13:00
commit e2b9700a98
2 files changed
+120 -1

No files matched your search

@@ -3455,6 +3455,18 @@ void verifyPointerImageTable() {
require(!TableTouched(narrowTable.regions, root + (Palette + 32u) / 4u, 32u) && !TableTouched(narrowTable.regions, root + Committed / 4u, Reserved - Committed), "pointer image table: a record outside the key domain was read");
const std::vector<std::pair<std::uint64_t, std::uint64_t>> narrowReads{{base + Palette, 32u}, {base + Palette + 2u * 32u, 32u}, {base + Palette + 255u * 32u, 32u}, {base + Palette + 0x102u * 32u, 32u}};
require(narrowSnapshot.readRanges == narrowReads && narrowTable.result->imageTableReadRanges.size() == 1u && narrowTable.result->imageTableReadRanges[0] == narrowReads, "pointer image table: consumed ranges include skipped keys or the key-domain buffer");
auto phiCode = maskedCode;
phiCode.erase(phiCode.begin() + 8, phiCode.begin() + 10);
phiCode.insert(phiCode.begin() + 8, {
0xbf068010u, 0xbf850003u, 0xf4200444u, 0x20000000u, 0xbf820002u, 0xf4200444u, 0x20000000u
});
const auto phiRequest = request(phiCode, 0x36000u);
const auto phiPlan = GetResourcePlan(phiRequest);
require(phiPlan->info.images.size() == 1u && phiPlan->info.images[0].table == 0u, "pointer image table: a pointer read through a phi was not planned as a table");
const auto phiTable = CompileTable(phiRequest);
require(phiTable.capture->snapshot.tables.tables.at(0).codes == snapshot.codes, "pointer image table: a pointer read through a phi captured different records");
keys = {};
#ifdef _WIN32
VirtualFree(block, 0, MEM_RELEASE);
@@ -3858,6 +3870,45 @@ void verifyPointerChainImageTable() {
static_cast<void>(ValidateAndOptimizeSpirv(compiled.result->spirv, request.target.vulkanVersion, request.target.spirvVersion));
#endif
store(links[1].data() + 4u, address(descriptors[1].data()));
auto phiCode = code;
phiCode.erase(phiCode.begin() + 15, phiCode.begin() + 17);
phiCode.insert(phiCode.begin() + 15, {
0xbf068010u, 0xbf850003u, 0xf4240b02u, 0x24000008u, 0xbf820002u, 0xf4240b02u, 0x24000008u
});
auto phiRequest = TableRequest(phiCode, userData);
phiRequest.shader.codeAddress = 0x38000u;
const auto phiPlan = GetResourcePlan(phiRequest);
require(phiPlan->info.images.size() == 1u && phiPlan->info.images[0].table == 0u, "pointer chain image table: a pointer read through a phi was not planned as a table");
const auto phiTable = CompileTable(phiRequest);
require(phiTable.capture->snapshot.tables.tables.at(0).codes == snapshot.codes, "pointer chain image table: a pointer read through a phi captured different records");
auto divergentCode = code;
divergentCode.erase(divergentCode.begin() + 15, divergentCode.begin() + 17);
divergentCode.insert(divergentCode.begin() + 15, {
0xbf068010u, 0xbf850003u, 0xf4240b02u, 0x24000008u, 0xbf820002u, 0xf4240b02u, 0x24000018u
});
auto divergentRequest = TableRequest(divergentCode, userData);
divergentRequest.shader.codeAddress = 0x39000u;
expectFailure([&] { static_cast<void>(GetResourcePlan(divergentRequest)); }, "GetImageResource dword 0 is not a valid runtime value", "pointer chain image table: a phi reading different table offsets was planned as a table");
auto nonTableCode = code;
nonTableCode.erase(nonTableCode.begin() + 15, nonTableCode.begin() + 17);
nonTableCode.insert(nonTableCode.begin() + 15, {
0xbf068010u, 0xbf850003u, 0xf4240b02u, 0x24000008u, 0xbf820002u, 0xbe960380u, 0xbe970380u
});
auto nonTableRequest = TableRequest(nonTableCode, userData);
nonTableRequest.shader.codeAddress = 0x3a000u;
expectFailure([&] { static_cast<void>(GetResourcePlan(nonTableRequest)); }, "GetImageResource dword 0 is not a valid runtime value", "pointer chain image table: a phi with a non-table branch was planned as a table");
auto armOffsetCode = code;
armOffsetCode.erase(armOffsetCode.begin() + 14, armOffsetCode.begin() + 17);
armOffsetCode.insert(armOffsetCode.begin() + 14, {
0xbf068010u, 0xbf850004u, 0x8f128411u, 0xf4240b02u, 0x24000008u, 0xbf820003u, 0x8f128411u, 0xf4240b02u, 0x24000008u
});
auto armOffsetRequest = TableRequest(armOffsetCode, userData);
armOffsetRequest.shader.codeAddress = 0x3b000u;
expectFailure([&] { static_cast<void>(GetResourcePlan(armOffsetRequest)); }, "GetImageResource dword 0 is not a valid runtime value", "pointer chain image table: a phi whose arms compute their own table offset was planned as a table");
}
void verifyWaterfallImageTable() {
@@ -751,6 +751,68 @@ private:
return true;
}
IrValue* UnwrapTableRead(IrValue* value) const {
if (value == nullptr) return nullptr;
value = value->Resolve();
if (!value->IsPhi()) return value;
IrValue* candidate = nullptr;
std::vector<IrValue*> pending{value};
std::unordered_set<const IrValue*> visited;
while (!pending.empty()) {
auto* current = pending.back()->Resolve();
pending.pop_back();
if (!visited.insert(current).second) continue;
if (current->IsPhi()) {
for (std::size_t i = 0; i < current->ArgumentCount(); i++) {
pending.push_back(current->Argument(i));
}
continue;
}
std::uint32_t memoryIndex = 0;
IrValue* key = nullptr;
std::uint32_t stride = 0;
std::uint32_t addend = 0;
if (ScalarReadMemory(*current, memoryIndex) == nullptr || !MemoryIndexBelongsTo(memoryIndex, *current) ||
!MatchAffineOffset(current->Argument(1), key, stride, addend)) {
return nullptr;
}
if (candidate == nullptr) {
candidate = current;
} else if (!EquivalentValue(m_program.Resources(), candidate, current)) {
std::uint32_t candidateIndex = 0;
const auto* candMem = ScalarReadMemory(*candidate, candidateIndex);
const auto* curMem = ScalarReadMemory(*current, memoryIndex);
if (candMem == nullptr || curMem == nullptr || candMem->offset != curMem->offset ||
!EquivalentValue(m_program.Resources(), candidate->Argument(0), current->Argument(0)) ||
!EquivalentValue(m_program.Resources(), candidate->Argument(1), current->Argument(1))) {
return nullptr;
}
}
}
return candidate;
}
bool BlockDominates(const IrBlock* dominator, const IrBlock* block) const {
if (dominator == block) return true;
std::vector<const IrBlock*> pending{block};
std::unordered_set<const IrBlock*> visited{block};
while (!pending.empty()) {
const IrBlock* current = pending.back();
pending.pop_back();
if (current->Predecessors().empty()) return false;
for (const IrBlock* predecessor : current->Predecessors()) {
if (predecessor != dominator && visited.insert(predecessor).second) pending.push_back(predecessor);
}
}
return true;
}
bool AvailableAt(IrValue* value, const IrBlock* block) const {
value = value->Resolve();
if (value->HasImmediate() || value->Parent() == nullptr) return true;
return BlockDominates(value->Parent(), block);
}
bool TryMakeIndirectColumn(IrValue& handle, IrValue& descriptorPointer, bool sampler, std::uint32_t words, std::uint32_t descriptorOffset, std::uint32_t descriptorImmediate, TableColumnPlan& plan) {
std::vector<PointerHop> hops;
IrValue* pointer = &descriptorPointer;
@@ -788,7 +850,10 @@ private:
IrValue* offset = nullptr;
std::uint32_t first = 0;
for (std::uint32_t dword = 0; dword < 2u; dword++) {
IrValue* read = pointer->Argument(dword)->Resolve();
IrValue* read = UnwrapTableRead(pointer->Argument(dword));
if (read == nullptr) {
return false;
}
std::uint32_t memoryIndex = 0;
const MemoryInfo* memory = ScalarReadMemory(*read, memoryIndex);
if (memory == nullptr || !MemoryIndexBelongsTo(memoryIndex, *read)) {
@@ -802,6 +867,9 @@ private:
return false;
}
}
if (!AvailableAt(offset, handle.Parent())) {
return false;
}
IrValue* key = nullptr;
std::uint32_t stride = 0;
std::uint32_t addend = 0;