Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,20 @@ void verifyDescriptorPhis() {

auto dynamic = makeRequest(dynamicCode);
expectFailure([&] { static_cast<void>(GetResourcePlan(dynamic)); }, "GetSamplerResource dword 0 is not a valid runtime value", "descriptor Phi: an edge without an SRT slot was accepted");

const std::vector<std::uint32_t> loopCode{0xf4080500u, 0xfa000020u, 0xf4080600u, 0xfa000040u, 0xf40c0200u, 0xfa000000u, 0xbe910380u, 0xbf8cc07fu, 0xf09c0f08u, 0x00a20000u, 0xbf8c3f70u, 0xe0700000u, 0x80060000u, 0xbf068011u, 0xbf850003u, 0xf40c0200u, 0xfa000000u, 0xbf820002u, 0xf40c0200u, 0xfa000060u, 0xbf8cc07fu, 0x80118111u, 0xbf0a8211u, 0xbf85fff0u, 0xbf810000u};
auto programCounterCode = loopCode;
programCounterCode.insert(programCounterCode.begin() + 8, 0xbe8e1f00u);
for (const auto* code : std::array<const std::vector<std::uint32_t>*, 2>{&loopCode, &programCounterCode}) {
const auto loopCapture = compile(*code, 2u, 1u);
const auto& loopImages = loopCapture->snapshot.images;
const auto holdsLoopImage = [&](const std::array<std::uint32_t, 8>& words) {
return std::ranges::any_of(loopImages, [&](const DescriptorValue& value) {
return std::equal(words.begin(), words.end(), value.dwords.begin());
});
};
require(loopImages.size() == 2u && holdsLoopImage(first) && holdsLoopImage(second), "descriptor Phi: the loop's chained T# Phis were not split into the two SRT T#s");
}
}

void verifyProgramCounterRelativeData() {
Expand Down
192 changes: 177 additions & 15 deletions core/shader/recompiler/Optimization/src/ResourceTracker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <cstdlib>
#include <cstring>
#include <limits>
#include <map>
#include <span>
#include <string>
#include <utility>
Expand Down Expand Up @@ -598,7 +599,7 @@ class Tracker {
void SplitDescriptorPhi(IrValue& inst) {
const auto info = ImageOpcodeInfoOf(inst.Opcode());
const std::uint32_t handleCount = info.needsSampler ? 2u : 1u;
if (inst.ArgumentCount() < handleCount) {
if (inst.ArgumentCount() < handleCount || inst.Parent() == nullptr) {
return;
}
std::array<IrValue*, 2> handles {};
Expand All @@ -625,21 +626,27 @@ class Tracker {
if (phis.empty()) {
return;
}
if (!SplitDescriptorEdges(inst, handles, split, handleCount, phis)) {
SplitDescriptorWeb(inst, handles, split, handleCount);
}
}

bool SplitDescriptorEdges(IrValue& inst, const std::array<IrValue*, 2>& handles, const std::array<bool, 2>& split, std::uint32_t handleCount, const std::vector<IrValue*>& phis) {
IrBlock* block = phis.front()->Parent();
if (block == nullptr || inst.Parent() == nullptr) {
return;
if (block == nullptr) {
return false;
}
const auto& predecessors = block->Predecessors();
if (predecessors.size() < 2u) {
return;
return false;
}
for (const IrValue* phi : phis) {
if (phi->Parent() != block || phi->PhiBlockCount() != predecessors.size()) {
return;
return false;
}
for (const IrBlock* predecessor : predecessors) {
if (PhiIncoming(*phi, predecessor) == nullptr) {
return;
return false;
}
}
}
Expand All @@ -665,24 +672,40 @@ class Tracker {
for (std::uint32_t slot = 0; slot < handleCount; slot++) {
for (std::size_t dword = 0; split[slot] && dword < handles[slot]->ArgumentCount(); dword++) {
if (SubstituteEdge(handles[slot]->Argument(dword), arm, inst, false, phiSearchDepth) == nullptr) {
return;
return false;
}
}
}
}

std::vector<IrValue*> conditions;
for (std::uint32_t arm = 0; arm + 1u < arms.size(); arm++) {
std::vector<bool> edges(edgeArm.size());
for (std::size_t edge = 0; edge < edgeArm.size(); edge++) {
edges[edge] = edgeArm[edge] == arm;
}
conditions.push_back(&EdgeSelector(*block, edges));
}
ReplaceWithArmCopies(inst, handles, split, handleCount, conditions, [&](std::uint32_t arm, std::uint32_t slot, std::size_t dword) {
return SubstituteEdge(handles[slot]->Argument(dword), arms[arm], inst, true, phiSearchDepth);
});
return true;
}

template <typename Dword>
void ReplaceWithArmCopies(IrValue& inst, const std::array<IrValue*, 2>& handles, const std::array<bool, 2>& split, std::uint32_t handleCount, std::span<IrValue* const> conditions, Dword&& dword) {
std::vector<IrValue*> copies;
const auto flags = inst.Flags<MemoryFlags>();
const auto memory = m_program.Resources().memoryInfo.at(flags.index);
for (std::uint32_t arm = 0; arm < arms.size(); arm++) {
for (std::uint32_t arm = 0; arm <= conditions.size(); arm++) {
std::vector<IrValue*> arguments(inst.Arguments().begin(), inst.Arguments().end());
for (std::uint32_t slot = 0; slot < handleCount; slot++) {
if (!split[slot]) {
continue;
}
std::vector<IrValue*> dwords;
for (std::size_t dword = 0; dword < handles[slot]->ArgumentCount(); dword++) {
dwords.push_back(SubstituteEdge(handles[slot]->Argument(dword), arms[arm], inst, true, phiSearchDepth));
for (std::size_t index = 0; index < handles[slot]->ArgumentCount(); index++) {
dwords.push_back(dword(arm, slot, index));
}
arguments[slot] = EmitCopy(*handles[slot], dwords, inst);
}
Expand All @@ -706,11 +729,7 @@ class Tracker {
};
IrValue* selected = extract(copies.back());
for (std::uint32_t arm = static_cast<std::uint32_t>(copies.size()) - 1u; arm-- > 0u;) {
std::vector<bool> edges(edgeArm.size());
for (std::size_t edge = 0; edge < edgeArm.size(); edge++) {
edges[edge] = edgeArm[edge] == arm;
}
const std::array<IrValue*, 3> arguments {&EdgeSelector(*block, edges), extract(copies[arm]), selected};
const std::array<IrValue*, 3> arguments {conditions[arm], extract(copies[arm]), selected};
selected = EmitBefore(inst, IrOpcode::SelectU32, IrType::U32, arguments);
}
components[component] = selected;
Expand All @@ -722,6 +741,149 @@ class Tracker {
inst.Parent()->RemoveInstruction(&inst);
}

static bool CarriesControlValue(IrValue* value, std::uint32_t depth) {
value = value->Resolve();
if (value->HasImmediate() || value->IsPhi()) {
return false;
}
switch (value->Opcode()) {
case IrOpcode::GetShaderBase:
case IrOpcode::GetExec:
case IrOpcode::GetExecLo:
case IrOpcode::GetExecHi:
case IrOpcode::Ballot:
case IrOpcode::UndefU32: return true;
default: break;
}
if (depth == 0u || (!Detail::IsRuntimeUniformOp(value->Opcode()) && value->Opcode() != IrOpcode::CompositeExtractU32x4)) {
return false;
}
for (std::size_t index = 0; index < value->ArgumentCount(); index++) {
if (CarriesControlValue(value->Argument(index), depth - 1u)) {
return true;
}
}
return false;
}

struct DescriptorWeb {
static constexpr std::size_t maxStates = 4096u;
static constexpr std::size_t maxArms = 64u;
std::vector<std::vector<IrValue*>> arms;
std::map<std::vector<IrValue*>, IrValue*> selectors;
std::vector<std::pair<IrBlock*, IrValue*>> phis;
};

IrValue* WebSelector(std::vector<IrValue*> tuple, DescriptorWeb& web, IrValue& position) {
for (IrValue*& value : tuple) {
value = value->Resolve();
while (value->IsPhi()) {
IrValue* invariant = ResolveInvariantPhi(m_program.Resources(), value);
if (invariant == nullptr || invariant == value) {
break;
}
value = invariant->Resolve();
}
}
if (const auto known = web.selectors.find(tuple); known != web.selectors.end()) {
return known->second;
}
const auto phi = std::ranges::find_if(tuple, [](const IrValue* value) { return value->IsPhi(); });
if (phi == tuple.end()) {
if (std::ranges::any_of(tuple, [](IrValue* value) { return CarriesControlValue(value, phiSearchDepth); })) {
return &m_builder.Constant(0u);
}
for (IrValue* value : tuple) {
if (Rematerialize(value, position, false, phiSearchDepth) == nullptr) {
return nullptr;
}
}
const auto same = std::ranges::find_if(web.arms, [&](const std::vector<IrValue*>& arm) {
return std::ranges::equal(tuple, arm, [&](IrValue* left, IrValue* right) { return EquivalentValue(m_program.Resources(), left, right); });
});
if (same != web.arms.end()) {
return &m_builder.Constant(static_cast<std::uint32_t>(same - web.arms.begin()));
}
if (web.arms.size() == DescriptorWeb::maxArms) {
return nullptr;
}
web.arms.push_back(std::move(tuple));
return &m_builder.Constant(static_cast<std::uint32_t>(web.arms.size() - 1u));
}
IrBlock* block = (*phi)->Parent();
if (block == nullptr || web.selectors.size() == DescriptorWeb::maxStates) {
return nullptr;
}
const auto& predecessors = block->Predecessors();
if (predecessors.size() < 2u) {
return nullptr;
}
for (const IrValue* value : tuple) {
if (value->IsPhi() && (value->Parent() != block || value->PhiBlockCount() != predecessors.size())) {
return nullptr;
}
}
IrValue& selector = m_program.CreateValue(IrOpcode::Phi, IrType::U32);
web.selectors.emplace(tuple, &selector);
web.phis.emplace_back(block, &selector);
for (IrBlock* predecessor : predecessors) {
std::vector<IrValue*> incoming;
for (IrValue* value : tuple) {
IrValue* edge = value->IsPhi() ? PhiIncoming(*value, predecessor) : value;
if (edge == nullptr) {
return nullptr;
}
incoming.push_back(edge);
}
IrValue* edgeSelector = WebSelector(std::move(incoming), web, position);
if (edgeSelector == nullptr) {
return nullptr;
}
selector.AddPhiOperand(predecessor, edgeSelector);
}
return &selector;
}

void SplitDescriptorWeb(IrValue& inst, const std::array<IrValue*, 2>& handles, const std::array<bool, 2>& split, std::uint32_t handleCount) {
std::vector<IrValue*> tuple;
std::array<std::size_t, 2> offsets {};
for (std::uint32_t slot = 0; slot < handleCount; slot++) {
offsets[slot] = tuple.size();
for (std::size_t dword = 0; split[slot] && dword < handles[slot]->ArgumentCount(); dword++) {
tuple.push_back(handles[slot]->Argument(dword));
}
}
DescriptorWeb web;
IrValue* selector = WebSelector(tuple, web, inst);
const bool select = selector != nullptr && web.arms.size() > 1u;
if (select) {
for (const auto& [block, phi] : web.phis) {
block->InsertInstructionBefore(nullptr, phi);
}
} else {
for (const auto& [block, phi] : web.phis) {
for (std::size_t index = 0; index < phi->ArgumentCount(); index++) {
phi->ReplaceArgument(index, nullptr);
}
}
for (const auto& [block, phi] : web.phis) {
phi->Invalidate();
}
}
if (selector == nullptr || web.arms.empty()) {
return;
}

std::vector<IrValue*> conditions;
for (std::uint32_t arm = 0; arm + 1u < web.arms.size(); arm++) {
const std::array<IrValue*, 2> compare {selector, &m_builder.Constant(arm)};
conditions.push_back(EmitBefore(inst, IrOpcode::IEqual32, IrType::Bool, compare));
}
ReplaceWithArmCopies(inst, handles, split, handleCount, conditions, [&](std::uint32_t arm, std::uint32_t slot, std::size_t dword) {
return Rematerialize(web.arms[arm][offsets[slot] + dword], inst, true, phiSearchDepth);
});
}

void SplitDescriptorPhis() {
std::vector<IrValue*> candidates;
for (auto& block : m_program.Blocks()) {
Expand Down
1 change: 1 addition & 0 deletions docs/dev/TechnicalDebt.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ Throughout the project, every function at every stage either **does exactly what
- The shader recompiler [ignores `s_setreg_b32`](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp): writes to MODE (float rounding and denormal controls) and the other hardware registers have no effect, and `s_getreg_b32` reads of the MODE round fields report round to nearest even even after `s_setreg_b32` wrote another mode. The initial round mode from the shader's program registers is not read either.
- On a host subgroup narrower than 32 lanes, [`threadBit`](../../core/shader/recompiler/Translation/src/TranslationContext/ExecMask.cpp) reads an EXEC written as a value (`s_mov_b32`/`s_mov_b64`) at bit (subgroup invocation id) of EXEC_LO, so lanes at or above the subgroup size read a lower lane's bit.
- [`v_readlane_b32`](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp) and fetch-inactive `v_permlane16_b32`/`v_permlanex16_b32` read undefined data from a lane that the host subgroup does not hold or whose host invocation is inactive; the console reads the lane's VGPR.
- [Image reads through a web of descriptor Phis](../../core/shader/recompiler/Optimization/src/ResourceTracker.cpp) are split into one read per distinct SRT descriptor. A path whose T# or S# dwords hold a program-counter, EXEC or ballot value, or an undefined register, is folded onto the first descriptor: no real descriptor holds such a value, so the path is assumed not to reach the read. If a title does read through it, the hardware would fetch from a garbage descriptor, and the read here returns the first descriptor's result instead.
- Without fetch-inactive, [`v_permlane16_b32` and `v_permlanex16_b32`](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp) take a source lane that the host subgroup does not hold as inactive and keep the old value (0 with bound_ctrl); the console reads that lane's VGPR.
- [Shader recompilation](../../core/shader/recompiler/Recompiler.cpp) currently occurs right before it was transferred to Vulkan with caching, but should be moved to the [relinker](../../core/relinker/main.cpp) stage. For this purpose, [shader/recompiler](../../core/shader/recompiler) was written completely independently from [libs/prx](../../core/libs/prx).
- [v_fma_f64](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp) is fused exactly only for normal inputs: with a subnormal input it is a separate multiply and add, which rounds twice.
Expand Down
Loading