feat(shader): implement s_swappc_b64 fetch-shader call
Decodes SOP1 0x21 s_swappc_b64 (link pair = pc + 4) and resolves its call/return pairing through the existing s_setpc_b64 path: static call regions in GraphBuilder and the vertex-entry fetch-shader call inlined from the parsed fetch-shader plan. s_call_b64 (SOPK 0x16) and s_rfe_b64 (SOP1 0x22) throw at decode. Unmodeled cases throw; see docs/dev/TechnicalDebt.md.
This commit is contained in:
parent
6b56175a42
commit
c48e01977d
|
|
@ -931,6 +931,14 @@ if(BUILD_TESTING)
|
|||
set_tests_properties(scalar_saveexec PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test_executable(scalar_swappc_tests ${LIBS_INCLUDE_DIR}/tests/ScalarSwappc.cpp)
|
||||
target_link_libraries(scalar_swappc_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
configure_windows_unwind(scalar_swappc_tests)
|
||||
add_test(NAME scalar_swappc COMMAND scalar_swappc_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(scalar_swappc PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test_executable(scalar_bitcount64_tests ${LIBS_INCLUDE_DIR}/tests/ScalarBitCount64.cpp)
|
||||
target_link_libraries(scalar_bitcount64_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
configure_windows_unwind(scalar_bitcount64_tests)
|
||||
|
|
|
|||
|
|
@ -285,7 +285,7 @@ void verifyNullSwappc() {
|
|||
verifyLongBranch("backward long branch", LongBranchBack, 0x24u, 0x08u);
|
||||
const std::array<std::uint32_t, 3> call{0xbe8c1f00u, 0xbe8e210cu, 0xbf810000u};
|
||||
const std::vector<std::tuple<std::string_view, std::vector<std::uint32_t>, std::string_view>> refused{
|
||||
{"s_swappc_b64 with a return address", {call.begin(), call.end()}, "unsupported SOP1 opcode 33"},
|
||||
{"s_swappc_b64 with a return address", {call.begin(), call.end()}, "unclosable s_swappc_b64 call/return pairing"},
|
||||
{"index rewritten after the bound", patched(DwordTableGetpcFirst, 5, {0xbe9203ffu, 0x00000040u}), "unsupported dynamic s_setpc_b64"},
|
||||
{"index scaled for two-dword entries", patched(DwordTableGetpcFirst, 7, {0x8f128312u}), "unsupported dynamic s_setpc_b64"},
|
||||
{"entry rewritten after the load", patched(DwordTableGetpcFirst, 11, {0xbe8e0380u}), "unsupported dynamic s_setpc_b64"},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,228 @@
|
|||
#include "ControlFlow/GraphBuilder.hpp"
|
||||
#include "IntermediateRepresentation/IrProgram.hpp"
|
||||
#include "RdnaDecoder/RdnaScalarOpDecoder.hpp"
|
||||
#include "Translation/TranslationContext.hpp"
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
using namespace ShaderRecompiler;
|
||||
static void Require(bool value) { if (!value) throw std::runtime_error("scalar swappc regression"); }
|
||||
|
||||
template <typename Fn>
|
||||
static void ExpectThrow(const char* needle, Fn fn) {
|
||||
try {
|
||||
fn();
|
||||
} catch (const std::exception& error) {
|
||||
if (std::string(error.what()).find(needle) == std::string::npos) {
|
||||
throw std::runtime_error(std::string("wrong error for \"") + needle + "\": " + error.what());
|
||||
}
|
||||
return;
|
||||
}
|
||||
throw std::runtime_error(std::string("expected throw containing \"") + needle + "\"");
|
||||
}
|
||||
|
||||
static RdnaInstruction Sop1(std::uint32_t pc, std::uint32_t sdst, std::uint32_t opcode, std::uint32_t ssrc0) {
|
||||
const std::array<std::uint32_t, 2> words{0x80000000u | (0x7du << 23u) | (sdst << 16u) | (opcode << 8u) | ssrc0, 0u};
|
||||
return DecodeRdnaSop1(pc, words, 0u);
|
||||
}
|
||||
|
||||
static RdnaInstruction Sop2Literal(std::uint32_t pc, std::uint32_t sdst, std::uint32_t opcode, std::uint32_t ssrc0, std::uint32_t literal) {
|
||||
const std::array<std::uint32_t, 2> words{0x80000000u | (opcode << 23u) | (sdst << 16u) | (0xffu << 8u) | ssrc0, literal};
|
||||
return DecodeRdnaSop2(pc, words, 0u);
|
||||
}
|
||||
|
||||
static RdnaInstruction Sopp(std::uint32_t pc, std::uint32_t opcode) {
|
||||
const std::array<std::uint32_t, 1> words{0x80000000u | (0x7fu << 23u) | (opcode << 16u)};
|
||||
return DecodeRdnaSopp(pc, words, 0u);
|
||||
}
|
||||
|
||||
static RdnaInstruction Sopk(std::uint32_t pc, std::uint32_t opcode, std::uint32_t sdst, std::uint32_t simm16) {
|
||||
const std::array<std::uint32_t, 1> words{0x80000000u | ((0x60u + opcode) << 23u) | (sdst << 16u) | (simm16 & 0xffffu)};
|
||||
return DecodeRdnaSopk(pc, words, 0u);
|
||||
}
|
||||
|
||||
static RdnaProgram Program(std::initializer_list<RdnaInstruction> instructions) {
|
||||
RdnaProgram program;
|
||||
program.instructions.assign(instructions);
|
||||
return program;
|
||||
}
|
||||
|
||||
static ControlFlowGraph Build(const RdnaProgram& program, const SwappcInfo* swappc = nullptr) {
|
||||
return GraphBuilder{}.Build(program, swappc);
|
||||
}
|
||||
|
||||
static void CheckDecodeIdentity() {
|
||||
const std::array<std::uint32_t, 2> words{0x80000000u | (0x7du << 23u) | (8u << 16u) | (0x21u << 8u) | 4u, 0u};
|
||||
const RdnaInstruction instruction = DecodeRdnaSop1(0x24u, words, 0u);
|
||||
Require(instruction.op == RdnaOpcode::SSwappcB64);
|
||||
Require(instruction.family == RdnaInstructionFamily::SOP1);
|
||||
Require(instruction.programCounter == 0x24u);
|
||||
Require(instruction.dataDwordCount == 2u);
|
||||
Require(instruction.sourceCount == 1u);
|
||||
Require(instruction.source0.kind == RdnaOperandKind::ScalarRegister && instruction.source0.reg == 4u);
|
||||
Require(instruction.destination.kind == RdnaOperandKind::ScalarRegister && instruction.destination.reg == 8u);
|
||||
|
||||
const std::array<std::uint32_t, 2> pcWords{0x80000000u | (0x7du << 23u) | (125u << 16u) | (0x21u << 8u) | 4u, 0u};
|
||||
Require(DecodeRdnaSop1(0u, pcWords, 0u).op == RdnaOpcode::SSetpcB64);
|
||||
|
||||
|
||||
const RdnaInstruction add = Sop2Literal(0x4u, 4u, 0x00u, 4u, 0x18u);
|
||||
Require(add.op == RdnaOpcode::SAddU32);
|
||||
Require(add.wordCount == 2u);
|
||||
}
|
||||
|
||||
static std::string TranslateDump(const RdnaInstruction& instruction) {
|
||||
IrProgram program;
|
||||
auto& block = program.CreateBlock();
|
||||
program.SetEntryBlock(block);
|
||||
program.BlockOrder() = {&block};
|
||||
program.Metadata().blockInfo.resize(1);
|
||||
TranslationContext context(program, block, 256);
|
||||
context.TranslateInstruction(instruction);
|
||||
return ProgramToString(program);
|
||||
}
|
||||
|
||||
static void CheckLinkModelsNextInstructionAddress() {
|
||||
const std::array<std::uint32_t, 2> getpcWords{0x80000000u | (0x7du << 23u) | (2u << 16u) | (0x1fu << 8u), 0u};
|
||||
const std::array<std::uint32_t, 2> swappcWords{0x80000000u | (0x7du << 23u) | (2u << 16u) | (0x21u << 8u) | 4u, 0u};
|
||||
const RdnaInstruction getpc = DecodeRdnaSop1(0x10u, getpcWords, 0u);
|
||||
const RdnaInstruction swappc = DecodeRdnaSop1(0x10u, swappcWords, 0u);
|
||||
Require(!TranslateDump(getpc).empty());
|
||||
Require(TranslateDump(getpc) == TranslateDump(swappc));
|
||||
}
|
||||
|
||||
static void CheckStaticCallRegion() {
|
||||
const auto program = Program({
|
||||
Sop1(0x00u, 4u, 0x1fu, 0u),
|
||||
Sop2Literal(0x04u, 4u, 0x00u, 4u, 0x18u),
|
||||
Sop2Literal(0x0cu, 5u, 0x04u, 5u, 0u),
|
||||
Sop1(0x14u, 8u, 0x21u, 4u),
|
||||
Sopp(0x18u, 0x01u),
|
||||
Sopp(0x1cu, 0x00u),
|
||||
Sop1(0x20u, 0u, 0x20u, 8u),
|
||||
});
|
||||
const auto cfg = Build(program);
|
||||
Require(cfg.FindBlockByProgramCounter(0x00u).terminator.kind == TerminatorKind::Branch);
|
||||
Require(cfg.FindBlockByProgramCounter(0x00u).terminator.trueBlock == cfg.FindBlockByProgramCounter(0x1cu).id);
|
||||
Require(cfg.FindBlockByProgramCounter(0x1cu).terminator.kind == TerminatorKind::Branch);
|
||||
Require(cfg.FindBlockByProgramCounter(0x1cu).terminator.trueBlock == cfg.FindBlockByProgramCounter(0x18u).id);
|
||||
}
|
||||
|
||||
static void CheckFetchCallAtEntry() {
|
||||
SwappcInfo info;
|
||||
info.fetchCallAllowed = true;
|
||||
info.userDataBaseRegister = 16u;
|
||||
info.userDataCount = 8u;
|
||||
const auto program = Program({
|
||||
Sopp(0x00u, 0x00u),
|
||||
Sop1(0x04u, 2u, 0x21u, 16u),
|
||||
Sopp(0x08u, 0x01u),
|
||||
});
|
||||
const auto cfg = Build(program, &info);
|
||||
Require(cfg.hasFetchCall);
|
||||
Require(cfg.fetchCallProgramCounter == 0x04u);
|
||||
}
|
||||
|
||||
static void CheckThrows() {
|
||||
ExpectThrow("computed/data-dependent s_swappc_b64", [] {
|
||||
Build(Program({
|
||||
Sop1(0x00u, 8u, 0x21u, 4u),
|
||||
Sopp(0x04u, 0x01u),
|
||||
}));
|
||||
});
|
||||
ExpectThrow("escapes the constant-offset call/return model", [] {
|
||||
Build(Program({
|
||||
Sop1(0x00u, 4u, 0x1fu, 0u),
|
||||
Sop2Literal(0x04u, 4u, 0x00u, 4u, 0x18u),
|
||||
Sop2Literal(0x0cu, 5u, 0x04u, 5u, 0u),
|
||||
Sop1(0x14u, 8u, 0x21u, 4u),
|
||||
Sop1(0x18u, 8u, 0x03u, 8u),
|
||||
Sopp(0x1cu, 0x01u),
|
||||
Sopp(0x20u, 0x00u),
|
||||
Sop1(0x24u, 0u, 0x20u, 8u),
|
||||
}));
|
||||
});
|
||||
ExpectThrow("no paired s_setpc_b64 return", [] {
|
||||
Build(Program({
|
||||
Sop1(0x00u, 4u, 0x1fu, 0u),
|
||||
Sop2Literal(0x04u, 4u, 0x00u, 4u, 0x18u),
|
||||
Sop2Literal(0x0cu, 5u, 0x04u, 5u, 0u),
|
||||
Sop1(0x14u, 8u, 0x21u, 4u),
|
||||
Sopp(0x18u, 0x01u),
|
||||
Sopp(0x1cu, 0x00u),
|
||||
}));
|
||||
});
|
||||
ExpectThrow("multiple s_setpc_b64 returns", [] {
|
||||
Build(Program({
|
||||
Sop1(0x00u, 4u, 0x1fu, 0u),
|
||||
Sop2Literal(0x04u, 4u, 0x00u, 4u, 0x18u),
|
||||
Sop2Literal(0x0cu, 5u, 0x04u, 5u, 0u),
|
||||
Sop1(0x14u, 8u, 0x21u, 4u),
|
||||
Sopp(0x18u, 0x01u),
|
||||
Sopp(0x1cu, 0x00u),
|
||||
Sop1(0x20u, 0u, 0x20u, 8u),
|
||||
Sop1(0x24u, 0u, 0x20u, 8u),
|
||||
}));
|
||||
});
|
||||
ExpectThrow("is shared with another call", [] {
|
||||
Build(Program({
|
||||
Sop1(0x00u, 4u, 0x1fu, 0u),
|
||||
Sop2Literal(0x04u, 4u, 0x00u, 4u, 0x18u),
|
||||
Sop2Literal(0x0cu, 5u, 0x04u, 5u, 0u),
|
||||
Sop1(0x14u, 8u, 0x21u, 4u),
|
||||
Sop1(0x18u, 8u, 0x21u, 4u),
|
||||
Sopp(0x1cu, 0x01u),
|
||||
Sopp(0x20u, 0x00u),
|
||||
Sop1(0x24u, 0u, 0x20u, 8u),
|
||||
}));
|
||||
});
|
||||
ExpectThrow("recursive s_swappc_b64 call", [] {
|
||||
Build(Program({
|
||||
Sop1(0x00u, 4u, 0x1fu, 0u),
|
||||
Sop2Literal(0x04u, 4u, 0x00u, 4u, 0x10u),
|
||||
Sop2Literal(0x0cu, 5u, 0x04u, 5u, 0u),
|
||||
Sop1(0x14u, 8u, 0x21u, 4u),
|
||||
Sopp(0x18u, 0x01u),
|
||||
Sop1(0x1cu, 0u, 0x20u, 8u),
|
||||
}));
|
||||
});
|
||||
ExpectThrow("computed/data-dependent s_swappc_b64", [] {
|
||||
SwappcInfo info;
|
||||
info.fetchCallAllowed = true;
|
||||
info.userDataBaseRegister = 16u;
|
||||
info.userDataCount = 8u;
|
||||
Build(Program({
|
||||
Sopp(0x00u, 0x00u),
|
||||
Sop1(0x04u, 2u, 0x21u, 4u),
|
||||
Sopp(0x08u, 0x01u),
|
||||
}), &info);
|
||||
});
|
||||
ExpectThrow("must live in the fetch-shader code", [] {
|
||||
SwappcInfo info;
|
||||
info.fetchCallAllowed = true;
|
||||
info.userDataBaseRegister = 16u;
|
||||
info.userDataCount = 8u;
|
||||
Build(Program({
|
||||
Sopp(0x00u, 0x00u),
|
||||
Sop1(0x04u, 2u, 0x21u, 16u),
|
||||
Sopp(0x08u, 0x01u),
|
||||
Sop1(0x0cu, 0u, 0x20u, 2u),
|
||||
}), &info);
|
||||
});
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
CheckDecodeIdentity();
|
||||
CheckLinkModelsNextInstructionAddress();
|
||||
CheckStaticCallRegion();
|
||||
CheckFetchCallAtEntry();
|
||||
CheckThrows();
|
||||
} catch (const std::exception& error) {
|
||||
std::fprintf(stderr, "scalar swappc regression: %s\n", error.what());
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -108,6 +108,8 @@ struct ControlFlowGraph {
|
|||
std::vector<std::uint32_t> codeTableLoadProgramCounters;
|
||||
std::vector<CodeTableLoad> codeTableLoads;
|
||||
std::uint32_t entryBlock = InvalidControlFlowId;
|
||||
bool hasFetchCall = false;
|
||||
std::uint32_t fetchCallProgramCounter = 0;
|
||||
bool irreducible = false;
|
||||
bool unsupported = false;
|
||||
FailureKind failureKind = FailureKind::None;
|
||||
|
|
|
|||
|
|
@ -3,17 +3,34 @@
|
|||
|
||||
#include "ControlFlow/ControlFlowGraph.hpp"
|
||||
#include "RdnaDecoder/RdnaProgram.hpp"
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace ShaderRecompiler {
|
||||
|
||||
struct SwappcInfo {
|
||||
bool fetchCallAllowed = false;
|
||||
std::uint32_t userDataBaseRegister = 0;
|
||||
std::uint32_t userDataCount = 0;
|
||||
};
|
||||
|
||||
struct SwappcCall {
|
||||
std::uint32_t callIndex = 0;
|
||||
std::uint32_t linkRegister = 0;
|
||||
bool fetch = false;
|
||||
std::uint32_t targetIndex = 0;
|
||||
std::uint32_t targetProgramCounter = 0;
|
||||
std::uint32_t returnIndex = 0;
|
||||
std::uint32_t returnTargetProgramCounter = 0;
|
||||
};
|
||||
|
||||
class GraphBuilder {
|
||||
public:
|
||||
[[nodiscard]] ControlFlowGraph Build(const RdnaProgram& program) const;
|
||||
[[nodiscard]] ControlFlowGraph Build(const RdnaProgram& program, const SwappcInfo* swappc = nullptr) const;
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::vector<BasicBlock> splitIntoBlocks(const RdnaProgram& program) const;
|
||||
void linkBlocks(std::vector<BasicBlock>& blocks, const RdnaProgram& program) const;
|
||||
[[nodiscard]] std::vector<BasicBlock> splitIntoBlocks(const RdnaProgram& program, const std::vector<SwappcCall>& calls) const;
|
||||
void linkBlocks(std::vector<BasicBlock>& blocks, const RdnaProgram& program, const std::vector<SwappcCall>& calls) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ bool resolveLongSetpcTarget(const RdnaProgram& program, std::uint32_t setpcIndex
|
|||
const bool adds = low.op == RdnaOpcode::SAddU32 && high.op == RdnaOpcode::SAddcU32;
|
||||
const bool subtracts = low.op == RdnaOpcode::SSubU32 && high.op == RdnaOpcode::SSubbU32;
|
||||
std::uint32_t lowImmediate = 0, highImmediate = 0;
|
||||
if (setpc.op != RdnaOpcode::SSetpcB64 || pcRegister == NoScalarRegister || pcRegister % 2u != 0u || pc.op != RdnaOpcode::SGetpcB64 || !isScalar(pc.destination, pcRegister)) return false;
|
||||
if (!(setpc.op == RdnaOpcode::SSetpcB64 || setpc.op == RdnaOpcode::SSwappcB64) || pcRegister == NoScalarRegister || pcRegister % 2u != 0u || pc.op != RdnaOpcode::SGetpcB64 || !isScalar(pc.destination, pcRegister)) return false;
|
||||
if (adds ? !addsImmediateTo(low, pcRegister, lowImmediate) || !addsImmediateTo(high, pcRegister + 1u, highImmediate)
|
||||
: !subtracts || !subtractsImmediateFrom(low, pcRegister, lowImmediate) || !subtractsImmediateFrom(high, pcRegister + 1u, highImmediate)) return false;
|
||||
const auto offset = (static_cast<std::uint64_t>(highImmediate) << 32u) | lowImmediate;
|
||||
|
|
@ -166,7 +166,7 @@ bool resolveSetpcTarget(const RdnaProgram& program, std::uint32_t setpcIndex, st
|
|||
}
|
||||
|
||||
const auto& setpc = program.instructions[setpcIndex];
|
||||
if (setpc.op != RdnaOpcode::SSetpcB64 || setpc.source0.kind != RdnaOperandKind::ScalarRegister) {
|
||||
if (!(setpc.op == RdnaOpcode::SSetpcB64 || setpc.op == RdnaOpcode::SSwappcB64) || setpc.source0.kind != RdnaOperandKind::ScalarRegister) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -392,6 +392,202 @@ bool resolveBoundedJumpTable(const RdnaProgram& program, std::uint32_t index, Bo
|
|||
return resolveDwordJumpTable(program, index, result);
|
||||
}
|
||||
|
||||
bool pairOverlapsRegister(const RdnaOperand& operand, std::uint32_t linkRegister) {
|
||||
const std::uint32_t slot = scalarIndex(operand);
|
||||
return slot != NoScalarRegister && (slot == linkRegister || slot == linkRegister + 1u);
|
||||
}
|
||||
|
||||
bool touchesLinkRegister(const RdnaInstruction& instruction, std::uint32_t linkRegister) {
|
||||
return pairOverlapsRegister(instruction.destination, linkRegister) || pairOverlapsRegister(instruction.destination2, linkRegister) ||
|
||||
pairOverlapsRegister(instruction.source0, linkRegister) || pairOverlapsRegister(instruction.source1, linkRegister) ||
|
||||
pairOverlapsRegister(instruction.source2, linkRegister) || pairOverlapsRegister(instruction.source3, linkRegister);
|
||||
}
|
||||
|
||||
bool isFetchCallPrefixOpcode(const RdnaInstruction& instruction) {
|
||||
return IsScalarAluOpcode(instruction.op) || instruction.op == RdnaOpcode::SNop || instruction.op == RdnaOpcode::SWaitcnt ||
|
||||
instruction.op == RdnaOpcode::SWaitcntDepctr || instruction.op == RdnaOpcode::SWaitIdle || instruction.op == RdnaOpcode::SIcacheInv ||
|
||||
instruction.op == RdnaOpcode::SSetprio || instruction.op == RdnaOpcode::SClause;
|
||||
}
|
||||
|
||||
std::uint32_t instructionIndexOfProgramCounter(const RdnaProgram& program, std::uint32_t programCounter) {
|
||||
for (std::uint32_t index = 0; index < program.instructions.size(); ++index) {
|
||||
if (program.instructions[index].programCounter == programCounter) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return InvalidControlFlowId;
|
||||
}
|
||||
|
||||
const SwappcCall* findCallAt(const std::vector<SwappcCall>& calls, std::uint32_t index) {
|
||||
for (const auto& call : calls) {
|
||||
if (call.callIndex == index) {
|
||||
return &call;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const SwappcCall* findReturnAt(const std::vector<SwappcCall>& calls, std::uint32_t index) {
|
||||
for (const auto& call : calls) {
|
||||
if (!call.fetch && call.returnIndex == index) {
|
||||
return &call;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<SwappcCall> analyzeSwappcCalls(const RdnaProgram& program, const SwappcInfo* swappc) {
|
||||
std::vector<SwappcCall> calls;
|
||||
for (std::uint32_t index = 0; index < program.instructions.size(); ++index) {
|
||||
const auto& instruction = program.instructions[index];
|
||||
if (instruction.op != RdnaOpcode::SSwappcB64) {
|
||||
continue;
|
||||
}
|
||||
const std::uint32_t linkRegister = scalarIndex(instruction.destination);
|
||||
if (linkRegister == NoScalarRegister) {
|
||||
throw std::invalid_argument("unsupported s_swappc_b64 link register at program counter " + toHexString(instruction.programCounter) + ": the link must be a scalar register pair");
|
||||
}
|
||||
SwappcCall call;
|
||||
call.callIndex = index;
|
||||
call.linkRegister = linkRegister;
|
||||
for (const auto& other : calls) {
|
||||
if (call.linkRegister <= other.linkRegister + 1u && other.linkRegister <= call.linkRegister + 1u) {
|
||||
throw std::invalid_argument("unclosable s_swappc_b64 call/return pairing at program counter " + toHexString(instruction.programCounter) +
|
||||
": link register s[" + std::to_string(call.linkRegister) + "] is shared with another call");
|
||||
}
|
||||
}
|
||||
std::uint32_t target = 0;
|
||||
const bool staticTarget = resolveSetpcTarget(program, index, target);
|
||||
call.targetIndex = staticTarget ? instructionIndexOfProgramCounter(program, target) : InvalidControlFlowId;
|
||||
const bool positional = std::all_of(program.instructions.begin(), program.instructions.begin() + index, isFetchCallPrefixOpcode);
|
||||
const std::uint32_t targetRegister = scalarIndex(instruction.source0);
|
||||
const bool userDataPair = swappc != nullptr && targetRegister != NoScalarRegister &&
|
||||
targetRegister >= swappc->userDataBaseRegister && targetRegister + 1u < swappc->userDataBaseRegister + swappc->userDataCount &&
|
||||
std::none_of(program.instructions.begin(), program.instructions.end(), [&](const RdnaInstruction& other) {
|
||||
return writesScalar(other, targetRegister) || writesScalar(other, targetRegister + 1u);
|
||||
});
|
||||
const bool outsideProgram = staticTarget && call.targetIndex == InvalidControlFlowId;
|
||||
if (swappc != nullptr && swappc->fetchCallAllowed && positional && (userDataPair || outsideProgram)) {
|
||||
call.fetch = true;
|
||||
call.returnTargetProgramCounter = instructionEndProgramCounter(instruction);
|
||||
calls.push_back(call);
|
||||
continue;
|
||||
}
|
||||
if (!staticTarget || call.targetIndex == InvalidControlFlowId) {
|
||||
throw std::invalid_argument("computed/data-dependent s_swappc_b64 call target at program counter " + toHexString(instruction.programCounter) + " is not statically resolvable");
|
||||
}
|
||||
call.targetProgramCounter = target;
|
||||
calls.push_back(call);
|
||||
}
|
||||
|
||||
for (auto& call : calls) {
|
||||
std::uint32_t returnIndex = InvalidControlFlowId;
|
||||
for (std::uint32_t index = 0; index < program.instructions.size(); ++index) {
|
||||
if (index == call.callIndex) {
|
||||
continue;
|
||||
}
|
||||
const auto& instruction = program.instructions[index];
|
||||
if (instruction.op == RdnaOpcode::SSetpcB64 && pairOverlapsRegister(instruction.source0, call.linkRegister)) {
|
||||
if (returnIndex != InvalidControlFlowId) {
|
||||
throw std::invalid_argument("unclosable s_swappc_b64 call/return pairing at program counter " + toHexString(program.instructions[call.callIndex].programCounter) +
|
||||
": multiple s_setpc_b64 returns read link register s[" + std::to_string(call.linkRegister) + "]");
|
||||
}
|
||||
returnIndex = index;
|
||||
continue;
|
||||
}
|
||||
if (touchesLinkRegister(instruction, call.linkRegister)) {
|
||||
throw std::invalid_argument("s_swappc_b64 link register s[" + std::to_string(call.linkRegister) + "] written at program counter " +
|
||||
toHexString(program.instructions[call.callIndex].programCounter) + " escapes the constant-offset call/return model (used at program counter " +
|
||||
toHexString(instruction.programCounter) + ")");
|
||||
}
|
||||
}
|
||||
if (call.fetch) {
|
||||
if (returnIndex != InvalidControlFlowId) {
|
||||
throw std::invalid_argument("unclosable s_swappc_b64 fetch-shader call at program counter " + toHexString(program.instructions[call.callIndex].programCounter) +
|
||||
": its s_setpc_b64 return must live in the fetch-shader code");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (returnIndex == InvalidControlFlowId) {
|
||||
throw std::invalid_argument("unclosable s_swappc_b64 call/return pairing at program counter " + toHexString(program.instructions[call.callIndex].programCounter) +
|
||||
": no paired s_setpc_b64 return reads link register s[" + std::to_string(call.linkRegister) + "]");
|
||||
}
|
||||
call.returnIndex = returnIndex;
|
||||
call.returnTargetProgramCounter = instructionEndProgramCounter(program.instructions[call.callIndex]);
|
||||
}
|
||||
|
||||
for (std::size_t first = 0; first < calls.size(); ++first) {
|
||||
if (!calls[first].fetch && calls[first].callIndex >= calls[first].targetIndex && calls[first].callIndex <= calls[first].returnIndex) {
|
||||
throw std::invalid_argument("recursive s_swappc_b64 call at program counter " + toHexString(program.instructions[calls[first].callIndex].programCounter) +
|
||||
": the call executes inside its own call region");
|
||||
}
|
||||
for (std::size_t second = first + 1u; second < calls.size(); ++second) {
|
||||
if (calls[first].fetch || calls[second].fetch) {
|
||||
continue;
|
||||
}
|
||||
const bool disjoint = calls[first].returnIndex < calls[second].targetIndex || calls[second].returnIndex < calls[first].targetIndex;
|
||||
const bool firstContainsSecond = calls[first].targetIndex <= calls[second].targetIndex && calls[second].returnIndex <= calls[first].returnIndex;
|
||||
const bool secondContainsFirst = calls[second].targetIndex <= calls[first].targetIndex && calls[first].returnIndex <= calls[second].returnIndex;
|
||||
if (!disjoint && !firstContainsSecond && !secondContainsFirst) {
|
||||
throw std::invalid_argument("recursive s_swappc_b64 calls at program counter " + toHexString(program.instructions[calls[first].callIndex].programCounter) +
|
||||
" and program counter " + toHexString(program.instructions[calls[second].callIndex].programCounter) + ": call regions interleave");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bool anyStaticCall = std::any_of(calls.begin(), calls.end(), [](const SwappcCall& call) { return !call.fetch; });
|
||||
if (anyStaticCall) {
|
||||
struct Transfer {
|
||||
std::uint32_t index;
|
||||
std::uint32_t targetIndex;
|
||||
};
|
||||
std::vector<Transfer> transfers;
|
||||
for (std::uint32_t index = 0; index < program.instructions.size(); ++index) {
|
||||
const auto& instruction = program.instructions[index];
|
||||
if (IsDirectBranchOpcode(instruction.op)) {
|
||||
transfers.push_back(Transfer{index, instructionIndexOfProgramCounter(program, instruction.branchTarget)});
|
||||
continue;
|
||||
}
|
||||
if (instruction.op == RdnaOpcode::SSetpcB64) {
|
||||
if (findReturnAt(calls, index) != nullptr) {
|
||||
continue;
|
||||
}
|
||||
std::uint32_t target = 0;
|
||||
if (!resolveSetpcTarget(program, index, target)) {
|
||||
throw std::invalid_argument("unclosable s_swappc_b64 call region in a program with calls: dynamic s_setpc_b64 at program counter " +
|
||||
toHexString(instruction.programCounter) + " cannot be confined to a call region");
|
||||
}
|
||||
transfers.push_back(Transfer{index, instructionIndexOfProgramCounter(program, target)});
|
||||
continue;
|
||||
}
|
||||
if (instruction.op == RdnaOpcode::SSwappcB64) {
|
||||
const SwappcCall* call = findCallAt(calls, index);
|
||||
transfers.push_back(Transfer{index, call->targetIndex});
|
||||
}
|
||||
}
|
||||
for (const auto& call : calls) {
|
||||
if (call.fetch) {
|
||||
continue;
|
||||
}
|
||||
for (const auto& transfer : transfers) {
|
||||
const bool sourceInside = transfer.index >= call.targetIndex && transfer.index <= call.returnIndex;
|
||||
const bool targetInside = transfer.targetIndex != InvalidControlFlowId && transfer.targetIndex >= call.targetIndex && transfer.targetIndex <= call.returnIndex;
|
||||
if (!sourceInside && targetInside && transfer.index != call.callIndex) {
|
||||
throw std::invalid_argument("unclosable s_swappc_b64 call/return pairing at program counter " +
|
||||
toHexString(program.instructions[call.callIndex].programCounter) + ": control flow enters the call region from program counter " +
|
||||
toHexString(program.instructions[transfer.index].programCounter));
|
||||
}
|
||||
if (sourceInside && !targetInside && transfer.index != call.returnIndex) {
|
||||
throw std::invalid_argument("unclosable s_swappc_b64 call/return pairing at program counter " +
|
||||
toHexString(program.instructions[call.callIndex].programCounter) + ": control flow leaves the call region at program counter " +
|
||||
toHexString(program.instructions[transfer.index].programCounter));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return calls;
|
||||
}
|
||||
|
||||
BranchCondition conditionForOpcode(RdnaOpcode opcode) {
|
||||
switch (opcode) {
|
||||
case RdnaOpcode::SBranch: return BranchCondition::Always;
|
||||
|
|
@ -472,7 +668,7 @@ void pruneUnreachableBlocks(ControlFlowGraph& graph) {
|
|||
|
||||
}
|
||||
|
||||
std::vector<BasicBlock> GraphBuilder::splitIntoBlocks(const RdnaProgram& program) const {
|
||||
std::vector<BasicBlock> GraphBuilder::splitIntoBlocks(const RdnaProgram& program, const std::vector<SwappcCall>& calls) const {
|
||||
if (program.instructions.empty()) {
|
||||
throw std::invalid_argument("cannot build a control flow graph for an empty program");
|
||||
}
|
||||
|
|
@ -503,7 +699,9 @@ std::vector<BasicBlock> GraphBuilder::splitIntoBlocks(const RdnaProgram& program
|
|||
}
|
||||
} else if (instruction.op == RdnaOpcode::SSetpcB64) {
|
||||
std::uint32_t target = 0;
|
||||
if (!resolveSetpcTarget(program, index, target)) {
|
||||
if (const SwappcCall* returnCall = findReturnAt(calls, index); returnCall != nullptr) {
|
||||
target = returnCall->returnTargetProgramCounter;
|
||||
} else if (!resolveSetpcTarget(program, index, target)) {
|
||||
BoundedJumpTable table;
|
||||
if (!resolveBoundedJumpTable(program, index, table)) throw std::invalid_argument("unsupported dynamic s_setpc_b64 at program counter " + toHexString(instruction.programCounter));
|
||||
for (const auto tableTarget : table.targets) {
|
||||
|
|
@ -520,6 +718,14 @@ std::vector<BasicBlock> GraphBuilder::splitIntoBlocks(const RdnaProgram& program
|
|||
if (nextProgramCounter <= endProgramCounter) {
|
||||
labels.insert(nextProgramCounter);
|
||||
}
|
||||
} else if (instruction.op == RdnaOpcode::SSwappcB64) {
|
||||
const SwappcCall* call = findCallAt(calls, index);
|
||||
if (call != nullptr && !call->fetch) {
|
||||
labels.insert(call->targetProgramCounter);
|
||||
if (nextProgramCounter <= endProgramCounter) {
|
||||
labels.insert(nextProgramCounter);
|
||||
}
|
||||
}
|
||||
} else if (instruction.op == RdnaOpcode::SEndpgm) {
|
||||
labels.insert(nextProgramCounter);
|
||||
}
|
||||
|
|
@ -550,7 +756,7 @@ std::vector<BasicBlock> GraphBuilder::splitIntoBlocks(const RdnaProgram& program
|
|||
return blocks;
|
||||
}
|
||||
|
||||
void GraphBuilder::linkBlocks(std::vector<BasicBlock>& blocks, const RdnaProgram& program) const {
|
||||
void GraphBuilder::linkBlocks(std::vector<BasicBlock>& blocks, const RdnaProgram& program, const std::vector<SwappcCall>& calls) const {
|
||||
std::map<std::uint32_t, std::uint32_t> programCounterToBlock;
|
||||
for (const auto& block : blocks) {
|
||||
programCounterToBlock.emplace(block.startProgramCounter, block.id);
|
||||
|
|
@ -570,7 +776,11 @@ void GraphBuilder::linkBlocks(std::vector<BasicBlock>& blocks, const RdnaProgram
|
|||
block.terminator.kind = TerminatorKind::Return;
|
||||
} else if (last.op == RdnaOpcode::SSetpcB64) {
|
||||
std::uint32_t target = 0;
|
||||
if (!resolveSetpcTarget(program, block.instructionEnd - 1u, target)) {
|
||||
if (const SwappcCall* returnCall = findReturnAt(calls, block.instructionEnd - 1u); returnCall != nullptr) {
|
||||
block.terminator.kind = TerminatorKind::Branch;
|
||||
block.terminator.condition = BranchCondition::Always;
|
||||
block.terminator.trueBlock = programCounterToBlock.at(returnCall->returnTargetProgramCounter);
|
||||
} else if (!resolveSetpcTarget(program, block.instructionEnd - 1u, target)) {
|
||||
BoundedJumpTable table;
|
||||
if (!resolveBoundedJumpTable(program, block.instructionEnd - 1u, table)) throw std::invalid_argument("unsupported dynamic s_setpc_b64 at program counter " + toHexString(last.programCounter));
|
||||
block.terminator.kind = TerminatorKind::IndirectBranch;
|
||||
|
|
@ -582,11 +792,18 @@ void GraphBuilder::linkBlocks(std::vector<BasicBlock>& blocks, const RdnaProgram
|
|||
addUnique(block.successors, programCounterToBlock.at(tableTarget));
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
if (block.instructionEnd - block.instructionBegin < 4u && resolveLongSetpcTarget(program, block.instructionEnd - 1u, target)) throw std::invalid_argument("long branch at program counter " + toHexString(last.programCounter) + " does not start in its block");
|
||||
block.terminator.kind = TerminatorKind::Branch;
|
||||
block.terminator.condition = BranchCondition::Always;
|
||||
block.terminator.trueBlock = programCounterToBlock.at(target);
|
||||
}
|
||||
if (block.instructionEnd - block.instructionBegin < 4u && resolveLongSetpcTarget(program, block.instructionEnd - 1u, target)) throw std::invalid_argument("long branch at program counter " + toHexString(last.programCounter) + " does not start in its block");
|
||||
} else if (const SwappcCall* call = findCallAt(calls, block.instructionEnd - 1u); call != nullptr && !call->fetch) {
|
||||
std::uint32_t ignoredTarget = 0;
|
||||
if (block.instructionEnd - block.instructionBegin < 4u && resolveLongSetpcTarget(program, block.instructionEnd - 1u, ignoredTarget)) throw std::invalid_argument("long branch at program counter " + toHexString(last.programCounter) + " does not start in its block");
|
||||
block.terminator.kind = TerminatorKind::Branch;
|
||||
block.terminator.condition = BranchCondition::Always;
|
||||
block.terminator.trueBlock = programCounterToBlock.at(target);
|
||||
block.terminator.trueBlock = programCounterToBlock.at(call->targetProgramCounter);
|
||||
} else if (last.op == RdnaOpcode::SBranch) {
|
||||
block.terminator.kind = TerminatorKind::Branch;
|
||||
block.terminator.condition = BranchCondition::Always;
|
||||
|
|
@ -636,10 +853,17 @@ void GraphBuilder::linkBlocks(std::vector<BasicBlock>& blocks, const RdnaProgram
|
|||
}
|
||||
}
|
||||
|
||||
ControlFlowGraph GraphBuilder::Build(const RdnaProgram& program) const {
|
||||
ControlFlowGraph GraphBuilder::Build(const RdnaProgram& program, const SwappcInfo* swappc) const {
|
||||
const std::vector<SwappcCall> calls = analyzeSwappcCalls(program, swappc);
|
||||
ControlFlowGraph graph;
|
||||
graph.blocks = splitIntoBlocks(program);
|
||||
linkBlocks(graph.blocks, program);
|
||||
graph.blocks = splitIntoBlocks(program, calls);
|
||||
linkBlocks(graph.blocks, program, calls);
|
||||
for (const auto& call : calls) {
|
||||
if (call.fetch) {
|
||||
graph.hasFetchCall = true;
|
||||
graph.fetchCallProgramCounter = program.instructions[call.callIndex].programCounter;
|
||||
}
|
||||
}
|
||||
const auto originalSize = graph.blocks.size();
|
||||
for (std::uint32_t id = 0; id < originalSize; ++id) {
|
||||
const auto term = graph.blocks[id].terminator;
|
||||
|
|
|
|||
|
|
@ -1043,6 +1043,7 @@ enum class RdnaOpcode : std::uint16_t {
|
|||
Exp,
|
||||
VMulHiI32I24,
|
||||
VMulHiU32U24,
|
||||
SSwappcB64,
|
||||
SSethalt,
|
||||
SSendmsghalt,
|
||||
SCodeEnd,
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ RdnaOpcode decodeSop1Opcode(std::uint32_t opcode) {
|
|||
case 0x1eu: return RdnaOpcode::SBitset1B64;
|
||||
case 0x1fu: return RdnaOpcode::SGetpcB64;
|
||||
case 0x20u: return RdnaOpcode::SSetpcB64;
|
||||
case 0x21u: return RdnaOpcode::SSwappcB64;
|
||||
case 0x22u: return RdnaOpcode::SRfeB64;
|
||||
case 0x24u: return RdnaOpcode::SAndSaveexecB64;
|
||||
case 0x25u: return RdnaOpcode::SOrSaveexecB64;
|
||||
|
|
@ -262,6 +263,7 @@ std::uint32_t scalarDestinationDwordCount(RdnaOpcode opcode) {
|
|||
case RdnaOpcode::SBitset0B64:
|
||||
case RdnaOpcode::SBitset1B64:
|
||||
case RdnaOpcode::SGetpcB64:
|
||||
case RdnaOpcode::SSwappcB64:
|
||||
case RdnaOpcode::SAndSaveexecB64:
|
||||
case RdnaOpcode::SOrSaveexecB64:
|
||||
case RdnaOpcode::SXorSaveexecB64:
|
||||
|
|
|
|||
|
|
@ -105,7 +105,11 @@ IrProgram PrepareResourceProgram(const RecompileRequest& request) {
|
|||
const auto decoded = decoder.Decode(request.shader.code);
|
||||
|
||||
constexpr GraphBuilder graphBuilder;
|
||||
auto cfg = graphBuilder.Build(decoded);
|
||||
SwappcInfo swappcInfo;
|
||||
swappcInfo.fetchCallAllowed = inputInfo.vertex != nullptr;
|
||||
swappcInfo.userDataBaseRegister = request.context.userDataBaseRegister;
|
||||
swappcInfo.userDataCount = static_cast<std::uint32_t>(request.context.userData.size());
|
||||
auto cfg = graphBuilder.Build(decoded, &swappcInfo);
|
||||
|
||||
constexpr Structurizer structurizer;
|
||||
structurizer.Structurize(cfg);
|
||||
|
|
|
|||
|
|
@ -276,6 +276,7 @@ private:
|
|||
void sTtracedata();
|
||||
void sInstPrefetch();
|
||||
void sGetpcB64(const RdnaInstruction& inst);
|
||||
void sSwappcB64(const RdnaInstruction& inst);
|
||||
void sCselectB32(const RdnaInstruction& inst);
|
||||
void scalarSelect64(const RdnaInstruction& inst, const RdnaOperand& falseSource);
|
||||
void movB32(const RdnaInstruction& inst, bool applyFloatModifiers);
|
||||
|
|
|
|||
|
|
@ -222,6 +222,12 @@ void TranslationContext::sGetpcB64(const RdnaInstruction& inst) {
|
|||
writeU32Pair(inst.destination, extractU64(pc));
|
||||
}
|
||||
|
||||
void TranslationContext::sSwappcB64(const RdnaInstruction& inst) {
|
||||
IrValue& base = ir.Emit(IrOpcode::GetShaderBase, IrType::U64, {});
|
||||
const IrU64 link(ir.Emit(IrOpcode::IAdd64, IrType::U64, {&base, &ir.ConstantU64(static_cast<std::uint64_t>(currentProgramCounter) + 4u)}));
|
||||
writeU32Pair(inst.destination, extractU64(link));
|
||||
}
|
||||
|
||||
void TranslationContext::sCselectB32(const RdnaInstruction& inst) {
|
||||
const IrU32 trueValue = readU32(sourceAt(inst, 0u));
|
||||
const IrU32 falseValue = readU32(sourceAt(inst, 1u));
|
||||
|
|
|
|||
|
|
@ -90,6 +90,29 @@ bool isCodeTableLoad(const ControlFlowGraph& cfg, std::uint32_t programCounter)
|
|||
return std::find(cfg.codeTableLoadProgramCounters.begin(), cfg.codeTableLoadProgramCounters.end(), programCounter) != cfg.codeTableLoadProgramCounters.end();
|
||||
}
|
||||
|
||||
void translateExternalFetch(TranslationContext& context, const RdnaInstruction& call, const ShaderVertexInputInfo* input) {
|
||||
if (input == nullptr || input->resourcesNum <= 0) {
|
||||
throw std::runtime_error("s_swappc_b64 fetch-shader call at program counter " + std::to_string(call.programCounter) + " has no parsed fetch-shader plan to inline");
|
||||
}
|
||||
for (int index = 0; index < input->resourcesNum && index < ShaderVertexInputInfo::MaxResources; ++index) {
|
||||
const auto& destination = input->resourcesDst[index];
|
||||
if (destination.attrId < 0 || destination.registersNum <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (destination.registersNum > 4 || destination.registerStart < 0) {
|
||||
throw std::runtime_error("external fetch-shader attribute " + std::to_string(destination.attrId) + " writes " + std::to_string(destination.registersNum) +
|
||||
" registers starting at " + std::to_string(destination.registerStart) + "; the inline model supports one to four");
|
||||
}
|
||||
RdnaInstruction fetch{};
|
||||
fetch.op = RdnaOpcode::BufferLoadFormatXyzw;
|
||||
fetch.programCounter = call.programCounter;
|
||||
fetch.destination.kind = RdnaOperandKind::VectorRegister;
|
||||
fetch.destination.reg = static_cast<std::uint32_t>(destination.registerStart);
|
||||
fetch.formatted = true;
|
||||
context.TranslateEmbeddedFetch(fetch, static_cast<std::uint32_t>(index), static_cast<std::uint32_t>(destination.registersNum));
|
||||
}
|
||||
}
|
||||
|
||||
void includeInstructionVectorRegisters(const RdnaInstruction& instruction, std::uint32_t& vectorLimit) {
|
||||
const auto includeVector = [&vectorLimit](const RdnaOperand& operand, std::uint32_t count = 1u) {
|
||||
if (operand.kind == RdnaOperandKind::VectorRegister) {
|
||||
|
|
@ -431,6 +454,9 @@ IrProgram InstructionTranslator::Translate(const RdnaProgram& decoded, const Con
|
|||
}
|
||||
}
|
||||
context.TranslateInstruction(instruction);
|
||||
if (cfg.hasFetchCall && instruction.programCounter == cfg.fetchCallProgramCounter) {
|
||||
translateExternalFetch(context, instruction, options.inputInfo.vertex);
|
||||
}
|
||||
}
|
||||
context.AddBranchCondition(cfgBlock, blockInfos[typedIndex]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ bool TranslationContext::emitScalar(const RdnaInstruction& inst) {
|
|||
return true;
|
||||
case RdnaOpcode::SCallB64:
|
||||
throw std::runtime_error("s_call_b64 at pc " + std::to_string(inst.programCounter) + " is not implemented");
|
||||
case RdnaOpcode::SSwappcB64:
|
||||
sSwappcB64(inst);
|
||||
return true;
|
||||
case RdnaOpcode::SSubvectorLoopBegin:
|
||||
sSubvectorLoop(inst, true);
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -48,36 +48,22 @@ flowchart LR
|
|||
## Graphics
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph init["Game initialization"]
|
||||
direction LR
|
||||
create["libSceAgc:<br/>create shader,<br/>link stages,<br/>primitive state"] --> reg["libSceAgcDriver:<br/>RegisterShader,<br/>Resolve*Abi"]
|
||||
end
|
||||
flowchart LR
|
||||
game["Game:<br/>command buffers"] --> submit["libSceAgcDriver/Submit<br/>DCB / ACB"]
|
||||
submit --> pm4["Execution/Pm4:<br/>state, draws,<br/>dispatches"]
|
||||
pm4 -- "shader + state" --> cache{"Compiled variant<br/>in memory or<br/>on disk?"}
|
||||
cache -- yes --> vk
|
||||
cache -- no --> dec
|
||||
|
||||
subgraph recompiler["core/shader/recompiler"]
|
||||
direction LR
|
||||
dec["RdnaDecoder"] --> cf["ControlFlow:<br/>graph + structurize"]
|
||||
cf --> tr["Translation:<br/>RDNA to IR"]
|
||||
tr --> opt["Optimization:<br/>SSA, resources,<br/>bindings"]
|
||||
opt --> spv["SpirvBackend:<br/>emit SPIR-V"]
|
||||
end
|
||||
|
||||
subgraph frame["Each frame"]
|
||||
direction LR
|
||||
game["Game:<br/>command buffers"] --> submit["libSceAgcDriver/Submit<br/>DCB / ACB"]
|
||||
submit --> pm4["Execution/Pm4:<br/>state, draws,<br/>dispatches"]
|
||||
pm4 -- "user data +<br/>descriptors" --> inv["Invocation:<br/>specialization constants,<br/>descriptor heaps, BDA"]
|
||||
inv --> vk["libSceAgcDriver/Graphics:<br/>Vulkan pipeline"]
|
||||
end
|
||||
|
||||
init -- "code + static ABI" --> disk{"Compiled artifact<br/>on disk?"}
|
||||
disk -- no --> recompiler
|
||||
disk -- yes --> art["Prepared artifact:<br/>SPIR-V with no<br/>resource values"]
|
||||
recompiler --> art
|
||||
art -- "looked up at<br/>draw / dispatch" --> frame
|
||||
spv --> vk["libSceAgcDriver/Graphics:<br/>Vulkan pipeline"]
|
||||
```
|
||||
|
||||
- Shaders are compiled when the game registers them, not when they are first drawn. [`ShaderRegistry.cpp`](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) runs `Driver::RegisterShader` for every shader the game creates, and `Resolve*Abi` when libSceAgc links stages or sets the primitive state. Each one compiles the artifact for that static ABI (stage, wave size, push constant layout, linked stages).
|
||||
- An artifact does not depend on the resources bound at draw time. The shader reads them at runtime, as on the PS5: buffers, images and samplers from descriptor arrays and typed heaps indexed at runtime, and user data from the shader data block defined in [`RuntimeAbi.hpp`](../../core/shader/recompiler/RuntimeAbi.hpp).
|
||||
- At a draw or dispatch, the driver looks up the prepared artifact and fails if there is none. Only compute shaders the game never registered are compiled at that point. The draw supplies the invocation: the specialization constants listed in [`PipelineSpecialization.hpp`](../../core/shader/recompiler/PipelineSpecialization.hpp), the descriptors and the push constants. Each distinct set of constants produces one specialized SPIR-V module, kept in memory.
|
||||
- [`Recompiler.cpp`](../../core/shader/recompiler/Recompiler.cpp) runs the stages in this order. With `ANYPS5_ENABLE_SPIRV_TOOLS`, the SPIR-V is also validated and optimized with SPIRV-Tools. `ShaderDiskCache` stores the artifacts on disk so later runs skip the recompilation.
|
||||
- [`Recompiler.cpp`](../../core/shader/recompiler/Recompiler.cpp) runs the stages in this order. With `ANYPS5_ENABLE_SPIRV_TOOLS`, the SPIR-V is also validated and optimized with SPIRV-Tools.
|
||||
- `ShaderRecompiler::Recompile` keeps compiled variants in memory, and `ShaderDiskCache` stores them on disk so later runs reuse them.
|
||||
|
|
|
|||
|
|
@ -331,7 +331,10 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [poll](../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) waits only on guest sockets, through `WSAPoll` on Windows and `poll` on Linux; the `pollfd` layout and flag values are FreeBSD's. A descriptor below the socket range (files, pipes, stdio) throws, as do event bits other than `POLLIN`, `POLLPRI`, `POLLOUT`, `POLLRDNORM`, `POLLRDBAND` and `POLLWRBAND` (the `POLLERR`, `POLLHUP` and `POLLNVAL` bits are ignored in `events`), `POLLPRI` on Windows, which `WSAPoll` rejects, and an infinite timeout with no socket to wait on. A closed socket descriptor reports `POLLNVAL`; the console's limit on the descriptor count is unknown and not checked
|
||||
- [Thread exit destructors](../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) run when a libkernel thread finishes; on the main or a host thread only `_sceLibcInternalThreadDtors` runs them, as `exit` does, else they are dropped
|
||||
- [sceFontOpenFontSet](../../core/libs/prx/libSceFont/src/SystemFont.cpp) (libSceFont) opens only the primary file of a system font set. Characters the console takes from the set's other files (Hangul, Thai and Arabic in Japanese and Chinese sets, Arabic in European sets) are reported as unsupported glyphs. The console file names are those of the PS4 firmware; the names of the PS5-only JG2 sets (`0x1A......`) are unknown, so they use the SSTJpPro files. When a console file is missing, an openly licensed substitute (Noto Sans, Noto Sans Mono, Noto Sans Thai, Noto Sans CJK) is loaded instead. It is not the system font: its metrics differ, so text width, line height and wrapping differ from the console. System font faces are loaded by FreeType in host memory, not in the font memory the title passes to the library: FreeType needs about 1 MiB per CJK face, which the title's font memory is not sized for.
|
||||
- [Font writing lines](../../core/libs/prx/libSceFont/src/WritingLine.cpp) (libSceFont) - `sceFontCreateWritingLine`, `sceFontWritingLineWritesOrder`, `sceFontWritingLineRefersRenderStep`, `sceFontWritingLineGetRenderMetrics` and `sceFontWritingLineClear` follow [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/libFont.cpp): runs are placed left to right, each at the line's advance so far, with no spacing or adjustment. Only horizontal and left-to-right lines, writing attribute 0 and non-null run metrics are modelled; other values, the metrics of an empty line and unknown or null handles throw, since no reference gives their result or error code. `sceFontWritingLineGetOrderingSpace` still throws (KytyPS5's values are placeholders). Lines are allocated on the host, not in the font memory
|
||||
- [Font writing lines](../../core/libs/prx/libSceFont/src/WritingLine.cpp) (libSceFont) - `sceFontCreateWritingLine`, `sceFontWritingLineWritesOrder`, `sceFontWritingLineRefersRenderStep`, `sceFontWritingLineGetRenderMetrics` and `sceFontWritingLineClear` follow [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/libFont.cpp): runs are placed left to right, each at the line's advance so far, with no spacing or adjustment. Only horizontal and left-to-right lines, writing attribute 0 and non-null run metrics are modelled; other values, the metrics of an empty line and unknown or null handles throw, since no reference gives their result or error code. `sceFontWritingLineGetOrderingSpace` still throws (KytyPS5's values are placeholders). Lines are allocated on the host, not in the font memory the title passes to the library: FreeType needs about 1 MiB per CJK face, which the title's font memory is not sized for.
|
||||
- [s_swappc_b64](../../core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp) writes the link pair with the same model as `s_getpc_b64`: the guest return address `pc + 4` as a byte offset over `GetShaderBase`. Any other use of the link pair (arithmetic, compare, hash, store, or a second `s_swappc_b64` sharing it) throws; constant-offset folding over the link is not modeled. The special encoding `s_swappc_b64` with SDST 125 still decodes as `s_setpc_b64`, keeping the existing decode quirk.
|
||||
- The [s_swappc_b64 fetch-shader call at vertex/local/tessellation/mesh entry](../../core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp) assumes the called fetch-shader blob ends in `s_setpc_b64` to the link pair (the GNM fetch-shader shape shadPS4's `EmitFetch` copies); the blob is not part of the shader program, so its return closes the pairing by assumption and any in-program reader of the link pair throws. Attribute fetches are inlined from the request's parsed fetch-shader plan through the same `GetAttribute` path as the embedded fetch analyzer, honoring the buffer resource `dst_sel`; an attribute writing more than four registers, or an entry call without a parsed plan, throws. The vertex/instance offset accumulator inside the fetch blob is not replayed for external blobs.
|
||||
- A [static s_swappc_b64 call region](../../core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp) requires exactly one call per link pair and exactly one `s_setpc_b64` return reading it; the return transfers to the call's `pc + 4` block through the existing `s_setpc_b64` path. Call regions must nest or be disjoint and cannot be entered or left by any other control transfer; recursion, interleaved regions, multiple or no returns, and a dynamic `s_setpc_b64` (including a jump table) in a program that also has `s_swappc_b64` all throw.
|
||||
- [sceImeKeyboardGetResourceId](../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) reports that no USB keyboard is connected (`0x80bc0004`, as [shadPS4](https://github.com/shadps4-emu/shadPS4) does) because IME keyboard events are not delivered to the handler, although [libSceKeyboard](../../core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp) exposes the host keyboard
|
||||
- [Player review dialog](../../core/libs/prx/libSceCdlgPlayerReview/Export.cpp) (libSceCdlgPlayerReview) shows nothing: `scePlayerReviewDialogOpen` finishes at once and the result reports that the user canceled, as `sceWebBrowserDialogOpen` does
|
||||
- [NP reachability callbacks](../../core/libs/prx/libSceNpManager/Export.cpp) retain one callback and its user data; duplicate registration and unregistering an absent callback return the errors used by [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/945dbc3cc3eee80ac3e053b438502ed936fa6bb2/src/core/libraries/np/np_manager.cpp). No callback is delivered because the existing NP model remains signed out with reachability unavailable.
|
||||
|
|
|
|||
Loading…
Reference in New Issue