Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 5 additions & 0 deletions core/libs/prx/libSceAgcDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,11 @@ if(BUILD_TESTING)
set_tests_properties(${preparedTest} PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endforeach()
add_test(NAME agc_driver_compute_user_data_calls_registered COMMAND agc_driver_compute_user_data_calls_tests --registered)
set_tests_properties(agc_driver_compute_user_data_calls_registered PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
set_tests_properties(agc_driver_compute_user_data_calls_registered PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
set_tests_properties(agc_driver_prepared_shaders
agc_driver_prepared_shaders_indirect agc_driver_prepared_shaders_uncached
agc_driver_prepared_shaders_debug agc_driver_prepared_shaders_debug_failure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,24 +357,80 @@ ShaderRecompiler::RectListShaders DrawRectangle(const ShaderSnapshot& front, con
return PreparedRectangle(front, vertexId, fragmentId);
}

namespace {

std::string hexText(std::uint64_t value) {
char text[24];
std::snprintf(text, sizeof(text), "0x%llx", static_cast<unsigned long long>(value));
return text;
}

std::vector<std::uint32_t> ReadUserDataCallee(std::uint64_t address, std::uint32_t linkRegister) {
constexpr std::uint64_t page = 4096u;
constexpr std::size_t limitWords = 16384u;
if (address % 4u != 0u) throw std::runtime_error("its callee address " + hexText(address) + " is not dword aligned");
std::vector<std::uint32_t> words;
while (words.size() < limitWords) {
const auto next = address + words.size() * 4u;
const auto bytes = static_cast<std::size_t>(page - next % page);
if (!GuestMemory::Accessible(reinterpret_cast<const void*>(static_cast<std::uintptr_t>(next)), bytes)) throw std::runtime_error("its callee at " + hexText(address) + " is not readable at " + hexText(next));
const auto old = words.size();
words.resize(old + bytes / 4u);
GuestMemory::Read(next, std::as_writable_bytes(std::span(words).subspan(old)));
if (const auto length = ShaderRecompiler::UserDataCalleeLength(words, linkRegister)) {
words.resize(*length);
return words;
}
}
throw std::runtime_error("its callee at " + hexText(address) + " does not return within " + std::to_string(limitWords * 4u) + " bytes");
}

std::vector<std::uint32_t> LinkUserDataCallees(const ShaderRecompiler::RecompileRequest& request) {
if (request.shader.stage != ShaderRecompiler::ShaderStage::Compute) return {};
const auto code = request.shader.code;
if (std::none_of(code.begin(), code.end(), [](std::uint32_t word) { return (word & 0xff80ff00u) == 0xbe802100u; })) return {};
const auto program = ShaderRecompiler::RdnaInstructionDecoder{}.Decode(code);
const ShaderRecompiler::SwappcInfo swappc{false, request.context.userDataBaseRegister, static_cast<std::uint32_t>(request.context.userData.size())};
const auto calls = ShaderRecompiler::FindUserDataCalls(program, swappc);
if (calls.empty()) return {};
std::vector<std::vector<std::uint32_t>> callees;
for (const auto& call : calls) {
const auto slot = call.userDataRegister - swappc.userDataBaseRegister;
const auto address = static_cast<std::uint64_t>(request.context.userData[slot]) | static_cast<std::uint64_t>(request.context.userData[slot + 1u]) << 32u;
try {
callees.push_back(ReadUserDataCallee(address, call.linkRegister));
} catch (const std::exception& error) {
throw std::runtime_error("AGC driver: compute shader " + hexText(request.shader.codeAddress) + " calls user data s[" + std::to_string(call.userDataRegister) + ":" +
std::to_string(call.userDataRegister + 1u) + "] at program counter " + hexText(call.programCounter) + ", but " + error.what());
}
}
return ShaderRecompiler::LinkUserDataCalls(program, calls, callees);
}

}

ShaderRecompiler::PreparedShaderInvocation InvocationFor(const ShaderSnapshot& snapshot, std::size_t codeOffset, const ShaderRecompiler::RecompileRequest& request) {
require(codeOffset < snapshot.code.size(), "prepared shader code offset is outside the snapshot");
const auto code = std::span(snapshot.code).subspan(codeOffset);
require(request.shader.code.data() == code.data() && request.shader.code.size() == code.size(), "prepared invocation does not refer to registered code");
auto invocationRequest = request;
const auto linked = LinkUserDataCallees(request);
auto source = request;
if (!linked.empty()) source.shader.code = linked;
auto invocationRequest = source;
struct PreparedKeyStorage {};
auto& key = HostThreadLocal<std::vector<std::uint64_t>, PreparedKeyStorage>();
ShaderRecompiler::BuildPreparedShaderKey(request, key);
ShaderRecompiler::BuildPreparedShaderKey(source, key);
std::unique_lock lock(snapshot.prepared->mutex);
AwaitRegisteredPreparation(*snapshot.prepared, lock);
for (const auto& entry : snapshot.prepared->entries) {
if (entry.codeOffset != codeOffset) continue;
invocationRequest.shader.code = ShaderRecompiler::GetPreparedCode(*entry.handle);
if (!linked.empty() && !std::ranges::equal(invocationRequest.shader.code, linked)) continue;
if (auto invocation = ShaderRecompiler::PreparedShaderInvocation::TryCreate(invocationRequest, entry.handle, key)) return std::move(*invocation);
}
ReportPreparedAtUse(snapshot, request);
auto handle = PrepareShaderWithDiagnostics(request);
invocationRequest = request;
ReportPreparedAtUse(snapshot, source);
auto handle = PrepareShaderWithDiagnostics(source);
invocationRequest = source;
invocationRequest.shader.code = ShaderRecompiler::GetPreparedCode(*handle);
auto invocation = ShaderRecompiler::PreparedShaderInvocation::TryCreate(invocationRequest, handle, key);
if (!invocation.has_value()) throw std::runtime_error("AGC driver: an artifact prepared at use does not match its invocation");
Expand Down
135 changes: 135 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/execution/ComputeUserDataCalls.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "SceShaders.hpp"
#include "VulkanTestDevice.hpp"
#include <array>
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <span>
#include <stdexcept>
#include <string>
#include <vector>

namespace {

using AgcDriver::Graphics::Require;

constexpr std::uint32_t Threads = 64;
constexpr std::uint32_t Untouched = 0xcafebabeu;

alignas(256) constexpr std::array<std::uint32_t, 64> Caller{
0x7e020280u, 0xbe940383u, 0xbe8e0300u, 0xbe8f0301u, 0xbe8e210eu, 0xbe8e0302u, 0xbe8f0303u, 0xbe8e210eu,
0x80948114u, 0xbf078014u, 0xbf85fff7u, 0x34040082u, 0xe0701000u, 0x80010102u, 0xbf810000u, 0xbf9f0000u,
0x30306c73u, 0x00000040u, 0x00000008u};
alignas(256) constexpr std::array<std::uint32_t, 2> AddFive{0x4a020285u, 0xbe80200eu};
alignas(256) constexpr std::array<std::uint32_t, 2> AddSixtyFour{0x4a0202c0u, 0xbe80200eu};
alignas(256) constexpr std::array<std::uint32_t, 2> AddOne{0x4a020281u, 0xbe80200eu};
alignas(256) std::array<std::uint32_t, Threads> Output{};

std::array<std::uint32_t, 8> UserData(const void* first, const void* second) {
const auto low = reinterpret_cast<std::uintptr_t>(first);
const auto high = reinterpret_cast<std::uintptr_t>(second);
const auto output = reinterpret_cast<std::uintptr_t>(Output.data());
return {static_cast<std::uint32_t>(low), static_cast<std::uint32_t>(low >> 32u), static_cast<std::uint32_t>(high), static_cast<std::uint32_t>(high >> 32u),
static_cast<std::uint32_t>(output), static_cast<std::uint32_t>((output >> 32u) & 0xffffu), static_cast<std::uint32_t>(sizeof(Output)), 0x31016facu};
}

std::vector<std::uint32_t> Commands(const std::array<std::uint32_t, 8>& data, bool wave32) {
std::vector<std::uint32_t> words;
const auto registers = [&](std::uint32_t first, std::span<const std::uint32_t> values) {
words.push_back(0xc0007600u | (static_cast<std::uint32_t>(values.size()) << 16u));
words.push_back(first);
words.insert(words.end(), values.begin(), values.end());
};
const std::array<std::uint32_t, 3> shape{Threads, 1, 1};
registers(0x207, shape);
const auto address = reinterpret_cast<std::uintptr_t>(Caller.data());
const std::array<std::uint32_t, 2> program{static_cast<std::uint32_t>(address >> 8u), static_cast<std::uint32_t>(address >> 40u)};
registers(0x20c, program);
const std::array<std::uint32_t, 1> resource{static_cast<std::uint32_t>(data.size()) << 1u};
registers(0x213, resource);
registers(0x240, data);
words.insert(words.end(), {0xc0031500u, 1, 1, 1, 0x41u | (wave32 ? 0x8000u : 0u)});
return words;
}

void Dispatch(const void* first, const void* second, bool wave32) {
Output.fill(Untouched);
const auto words = Commands(UserData(first, second), wave32);
Packet packet{const_cast<std::uint32_t*>(words.data()), static_cast<std::uint32_t>(words.size()), 0, {}};
AgcDriver::Submit(&packet, 0);
AgcDriverWaitIdle_nid_postfix();
AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast<std::uintptr_t>(Output.data()), sizeof(Output));
}

void Check(const void* first, const void* second, bool wave32, std::uint32_t expected, const std::string& what) {
Dispatch(first, second, wave32);
for (std::uint32_t lane = 0; lane < Threads; ++lane) {
Require(Output[lane] == expected, what + (wave32 ? " (wave32)" : " (wave64)") + ": lane " + std::to_string(lane) + " is " + std::to_string(Output[lane]) + ", expected " + std::to_string(expected));
}
}

void Register(bool wave32) {
struct Header {
Shader shader{};
std::array<ShaderRegister, 7> registers{};
ShaderSpecialRegs specials{};
};
alignas(8) static Header header{};
const auto address = reinterpret_cast<std::uintptr_t>(Caller.data());
header.shader.file_header = 0x34333231u;
header.shader.version = 0x18;
header.shader.header_size = sizeof(Header);
header.shader.shader_size = sizeof(Caller);
header.shader.code = Caller.data();
header.shader.sh_registers = header.registers.data();
header.shader.num_sh_registers = header.registers.size();
header.shader.specials = &header.specials;
header.specials.dispatch_modifier = wave32 ? 0x8000u : 0u;
header.registers = {{{0x20c, static_cast<std::uint32_t>(address >> 8u)}, {0x20d, static_cast<std::uint32_t>(address >> 40u)}, {0x207, Threads}, {0x208, 1}, {0x209, 1}, {0x212, 0}, {0x213, 8u << 1u}}};
AgcDriverRegisterShader_nid_postfix(&header.shader);
}

template <typename TAction>
void ExpectFailure(TAction action, const char* expected) {
try {
action();
} catch (const std::exception& error) {
Require(std::string(error.what()).find(expected) != std::string::npos, std::string("unexpected failure: ") + error.what());
return;
}
throw std::runtime_error(std::string("expected a failure containing ") + expected);
}

}

int main(int argc, char** argv) {
try {
Require(argc == 1 || (argc == 2 && std::string(argv[1]) == "--registered"), "usage: [--registered]");
const bool registered = argc == 2;
{
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
}
if (registered) Register(true);
for (const bool wave32 : registered ? std::vector<bool>{true} : std::vector<bool>{false, true}) {
Check(AddFive.data(), AddSixtyFour.data(), wave32, 3u * (5u + 64u), "two user-data callees called from a loop");
Check(AddOne.data(), AddSixtyFour.data(), wave32, 3u * (1u + 64u), "the first callee replaced");
Check(AddSixtyFour.data(), AddSixtyFour.data(), wave32, 3u * (64u + 64u), "one callee behind both pointers");
}
const auto* unmapped = reinterpret_cast<const void*>(std::uintptr_t{0x1000u});
ExpectFailure([&] { Dispatch(AddFive.data(), unmapped, registered); }, "is not readable");
ExpectFailure([] { AgcDriverShutdown_nid_postfix(); }, "is not readable");
std::puts("compute user-data call tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
try {
AgcDriverShutdown_nid_postfix();
} catch (const std::exception&) {
}
return 1;
}
}
86 changes: 86 additions & 0 deletions core/libs/tests/ScalarCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
#include "ControlFlow/GraphBuilder.hpp"
#include <array>
#include <cstdio>
#include <algorithm>
#include <initializer_list>
#include <optional>
#include <span>
#include <stdexcept>
#include <string>
#include <vector>

using namespace ShaderRecompiler;

Expand Down Expand Up @@ -96,6 +99,88 @@ static void CheckCallAfterEnd() {
static_cast<void>(InstructionTranslator{}.Translate(decoded, cfg, options));
}

static std::vector<std::uint32_t> UserDataCaller() {
return {0x7e020280u, 0xbe940383u, 0xbe8e0300u, 0xbe8f0301u, 0xbe8e210eu, 0xbe8e0302u, 0xbe8f0303u, 0xbe8e210eu,
0x80948114u, 0xbf078014u, 0xbf85fff7u, 0xbf810000u, 0x30306c73u};
}

static std::vector<UserDataCall> UserDataCalls(const std::vector<std::uint32_t>& code, std::uint32_t userDataCount = 8u) {
const SwappcInfo swappc{false, 0u, userDataCount};
return FindUserDataCalls(RdnaInstructionDecoder{}.Decode(code), swappc);
}

static void CheckUserDataCalls() {
const SwappcInfo swappc{false, 0u, 8u};
const auto caller = UserDataCaller();
const auto program = RdnaInstructionDecoder{}.Decode(caller);
const auto calls = FindUserDataCalls(program, swappc);
Require(calls.size() == 2u);
Require(calls[0].programCounter == 0x10u && calls[0].linkRegister == 14u && calls[0].userDataRegister == 0u);
Require(calls[1].programCounter == 0x1cu && calls[1].linkRegister == 14u && calls[1].userDataRegister == 2u);
ExpectThrow("not statically resolvable", [&] { static_cast<void>(GraphBuilder{}.Build(program, &swappc)); });

auto overwritten = caller;
overwritten[1] = 0xbe800380u;
const auto second = UserDataCalls(overwritten);
Require(second.size() == 1u && second[0].programCounter == 0x1cu);
auto entered = caller;
entered[10] = 0xbf85fff8u;
const auto afterLabel = UserDataCalls(entered);
Require(afterLabel.size() == 1u && afterLabel[0].programCounter == 0x1cu);
auto looped = caller;
looped[8] = 0xbe820380u;
const auto beforeWrite = UserDataCalls(looped);
Require(beforeWrite.size() == 1u && beforeWrite[0].programCounter == 0x10u);
Require(UserDataCalls({0xbe842104u, 0xbf810000u}, 6u).empty());
const auto direct = UserDataCalls({0xbe8e2104u, 0xbf810000u}, 6u);
Require(direct.size() == 1u && direct[0].linkRegister == 14u && direct[0].userDataRegister == 4u);

const std::vector<std::uint32_t> addFive{0x4a020285u, 0xbe80200eu};
const std::vector<std::uint32_t> addSixtyFour{0x4a0202c0u, 0xbe80200eu};
Require(UserDataCalleeLength(addFive, 14u) == 2u);
Require(!UserDataCalleeLength(std::vector<std::uint32_t>{0x4a020285u}, 14u).has_value());
ExpectThrow("reads its program counter", [] { static_cast<void>(UserDataCalleeLength(std::vector<std::uint32_t>{0xbe901f00u, 0xbe80200eu}, 14u)); });
ExpectThrow("makes a call", [] { static_cast<void>(UserDataCalleeLength(std::vector<std::uint32_t>{0xbe92210eu, 0xbe80200eu}, 14u)); });
ExpectThrow("ends the program", [] { static_cast<void>(UserDataCalleeLength(std::vector<std::uint32_t>{0xbf810000u}, 14u)); });
ExpectThrow("jumps through another register pair", [] { static_cast<void>(UserDataCalleeLength(std::vector<std::uint32_t>{0xbe802010u}, 14u)); });
ExpectThrow("before code it branches to", [] { static_cast<void>(UserDataCalleeLength(std::vector<std::uint32_t>{0xbf840001u, 0xbe80200eu, 0x4a020285u, 0xbe80200eu}, 14u)); });

const std::vector<std::vector<std::uint32_t>> callees{addFive, addSixtyFour};
const auto linked = LinkUserDataCalls(program, calls, callees);
Require(linked.size() == 17u && linked[4] == 0xbb0e0007u && linked[7] == 0xbb0e0006u && linked[16] == 0xbf9f0000u);
Require(std::equal(addFive.begin(), addFive.end(), linked.begin() + 12) && std::equal(addSixtyFour.begin(), addSixtyFour.end(), linked.begin() + 14));
auto readsPc = caller;
readsPc[1] = 0xbe941f00u;
ExpectThrow("cannot have user-data callees linked", [&] {
const auto readsPcProgram = RdnaInstructionDecoder{}.Decode(readsPc);
static_cast<void>(LinkUserDataCalls(readsPcProgram, FindUserDataCalls(readsPcProgram, swappc), callees));
});

const auto linkedProgram = RdnaInstructionDecoder{}.Decode(linked);
const auto cfg = GraphBuilder{}.Build(linkedProgram, &swappc);
const auto block = [&](std::uint32_t programCounter) { return cfg.FindBlockByProgramCounter(programCounter).id; };
Require(cfg.FindBlockByProgramCounter(0x08u).terminator.trueBlock == block(0x30u));
Require(cfg.FindBlockByProgramCounter(0x30u).terminator.trueBlock == block(0x14u));
Require(cfg.FindBlockByProgramCounter(0x14u).terminator.trueBlock == block(0x38u));
Require(cfg.FindBlockByProgramCounter(0x38u).terminator.trueBlock == block(0x20u));
ShaderComputeInputInfo computeInfo{};
TranslateOptions options{};
options.stage = ShaderStageKind::Compute;
options.inputInfo.compute = &computeInfo;
options.userDataCount = 8u;
static_cast<void>(InstructionTranslator{}.Translate(linkedProgram, cfg, options));

auto readsLink = linked;
readsLink[8] = 0xbe94030eu;
ExpectThrow("escapes the constant-offset call/return model", [&] { static_cast<void>(GraphBuilder{}.Build(RdnaInstructionDecoder{}.Decode(readsLink), &swappc)); });
auto writesInside = linked;
writesInside[12] = 0xbe8e0300u;
ExpectThrow("escapes the constant-offset call/return model", [&] { static_cast<void>(GraphBuilder{}.Build(RdnaInstructionDecoder{}.Decode(writesInside), &swappc)); });
auto sameCopy = linked;
sameCopy[7] = 0xbb0e0004u;
ExpectThrow("is shared with another call", [&] { static_cast<void>(GraphBuilder{}.Build(RdnaInstructionDecoder{}.Decode(sameCopy), &swappc)); });
}

static void CheckRejections() {
ExpectThrow("invalid instruction boundary", [] {
Build({Call(0u, 8u, 16u), Sopp(4u, 1u)});
Expand Down Expand Up @@ -194,6 +279,7 @@ int main(int argc, char** argv) {
CheckLink();
CheckCallAfterEnd();
CheckRejections();
CheckUserDataCalls();
CheckGuards("");
} else {
Require(argc == 2);
Expand Down
3 changes: 3 additions & 0 deletions core/shader/recompiler/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,9 @@ function(add_shader_recompiler target runtimeTarget)
SrgbUnormFormat
ThreadOwned
UnresolvableSwappcTarget
FindUserDataCalls
UserDataCalleeLength
LinkUserDataCalls
WriteFileAtomically
)
set(shaderCacheManifest)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "RdnaDecoder/RdnaProgram.hpp"
#include <cstdint>
#include <optional>
#include <span>
#include <vector>

namespace ShaderRecompiler {
Expand All @@ -25,7 +26,16 @@ struct SwappcCall {
std::uint32_t returnTargetProgramCounter = 0;
};

struct UserDataCall {
std::uint32_t programCounter = 0;
std::uint32_t linkRegister = 0;
std::uint32_t userDataRegister = 0;
};

[[nodiscard]] std::optional<std::uint32_t> UnresolvableSwappcTarget(const RdnaProgram& program, const SwappcInfo* swappc);
[[nodiscard]] std::vector<UserDataCall> FindUserDataCalls(const RdnaProgram& program, const SwappcInfo& swappc);
[[nodiscard]] std::optional<std::uint32_t> UserDataCalleeLength(std::span<const std::uint32_t> callee, std::uint32_t linkRegister);
[[nodiscard]] std::vector<std::uint32_t> LinkUserDataCalls(const RdnaProgram& program, std::span<const UserDataCall> calls, std::span<const std::vector<std::uint32_t>> callees);

class GraphBuilder {
public:
Expand Down
Loading
Loading