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
2 changes: 1 addition & 1 deletion core/libs/prx/libSceAgcDriver/tests/ControlFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()}, "unclosable s_swappc_b64 call/return pairing"},
{"s_swappc_b64 with a return address", {call.begin(), call.end()}, "unclosable scalar 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"},
Expand Down
134 changes: 134 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/execution/ScalarStaticCall.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <span>
#include <string>
#include <vector>

namespace {

using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;

constexpr std::uint32_t MaxThreads = 64;
constexpr std::uint32_t Inputs = 4;
constexpr std::uint32_t Results = 4;
alignas(256) std::array<std::uint32_t, MaxThreads * Inputs> Input{};
alignas(256) std::array<std::uint32_t, MaxThreads * Results> Output{};

alignas(256) constexpr std::array<std::uint32_t, 21> SwappcForward{0x34020084u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0xbe8a1f00u, 0x800aff0au, 0x00000014u, 0x820b800bu, 0xbe88210au, 0xbf820008u, 0x4a0808ffu, 0x13579bdfu, 0x3a0a0affu, 0xa5a5a5a5u, 0x4a0c0f06u, 0x3a0e0effu, 0x5a5a5a5au, 0xbe802008u, 0xe0781000u, 0x80010401u, 0xbf810000u};
alignas(256) constexpr std::array<std::uint32_t, 17> CallForward{0x34020084u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0xbb080001u, 0xbf820008u, 0x4a0808ffu, 0x13579bdfu, 0x3a0a0affu, 0xa5a5a5a5u, 0x4a0c0f06u, 0x3a0e0effu, 0x5a5a5a5au, 0xbe802008u, 0xe0781000u, 0x80010401u, 0xbf810000u};
alignas(256) constexpr std::array<std::uint32_t, 18> CallBackward{0x34020084u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0xbf820008u, 0x4a0808ffu, 0x13579bdfu, 0x3a0a0affu, 0xa5a5a5a5u, 0x4a0c0f06u, 0x3a0e0effu, 0x5a5a5a5au, 0xbe802008u, 0xbb08fff7u, 0xbf820000u, 0xe0781000u, 0x80010401u, 0xbf810000u};
alignas(256) constexpr std::array<std::uint32_t, 17> CallAfterEnd{0x34020084u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0xbb080003u, 0xe0781000u, 0x80010401u, 0xbf810000u, 0x4a0808ffu, 0x13579bdfu, 0x3a0a0affu, 0xa5a5a5a5u, 0x4a0c0f06u, 0x3a0e0effu, 0x5a5a5a5au, 0xbe802008u, 0xbf810000u};
alignas(256) constexpr std::array<std::uint32_t, 24> MixedNested{0x34020084u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0xbe8a1f00u, 0x800aff0au, 0x00000014u, 0x820b800bu, 0xbe88210au, 0xbf82000bu, 0x4a0808ffu, 0x13579bdfu, 0xbb0c0001u, 0xbf820006u, 0x3a0a0affu, 0xa5a5a5a5u, 0x4a0c0f06u, 0x3a0e0effu, 0x5a5a5a5au, 0xbe80200cu, 0xbe802008u, 0xe0781000u, 0x80010401u, 0xbf810000u};

constexpr std::array<std::uint32_t, 8> Selectors{0u, 1u, 2u, 3u, 4u, 7u, 0x80000000u, 0xffffffffu};
constexpr const char* Names[Results] = {"add", "xor", "sum", "xor_second"};

std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
const auto address = reinterpret_cast<std::uintptr_t>(data);
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), bytes, 0x31016facu};
}

std::string Hex(std::uint32_t value) {
char text[16];
std::snprintf(text, sizeof(text), "0x%08x", value);
return text;
}

std::uint32_t Expected(std::uint32_t lane, std::uint32_t result) {
const auto selector = Input[lane * Inputs];
const auto value = Input[lane * Inputs + 1u];
switch (result) {
case 0: return selector + 0x13579bdfu;
case 1: return value ^ 0xa5a5a5a5u;
case 2: return Input[lane * Inputs + 2u] + Input[lane * Inputs + 3u];
default: return Input[lane * Inputs + 3u] ^ 0x5a5a5a5au;
}
}

ShaderRecompiler::RecompileResult Compile(std::span<const std::uint32_t> code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) {
std::vector<std::uint32_t> userData(8, 0u);
const auto input = BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size() * 4u));
const auto output = BufferDescriptor(Output.data(), static_cast<std::uint32_t>(Output.size() * 4u));
std::copy(input.begin(), input.end(), userData.begin());
std::copy(output.begin(), output.end(), userData.begin() + 4);
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
const ShaderRecompiler::ShaderComputeStageInfo compute{{waveSize, 1, 1}, 0u, {false, false, false}, false, 1};
ShaderRecompiler::RecompileRequest request{
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
{waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory},
target,
{0, 0, 0, 128}
};
request.useCache = false;
return ShaderRecompiler::Recompile(request);
}

void Run(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) {
for (std::uint32_t tid = 0; tid < MaxThreads; ++tid) {
Input[tid * Inputs] = Selectors[tid % Selectors.size()];
Input[tid * Inputs + 1u] = 0x9e3779b9u * (tid + 1u);
Input[tid * Inputs + 2u] = 0xffffffffu - tid;
Input[tid * Inputs + 3u] = 17u * tid + 1u;
}
Output.fill(0xdeadbeefu);
const auto result = Compile(code, waveSize, target);
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
device.WaitIdle();
}

void Check(const char* variant, std::uint32_t lanes) {
for (std::uint32_t tid = 0; tid < lanes; ++tid) {
for (std::uint32_t index = 0; index < Results; ++index) {
const auto actual = Output[tid * Results + index];
const auto expected = Expected(tid, index);
Require(actual == expected, std::string(variant) + " wave" + std::to_string(lanes) + ": lane " + std::to_string(tid) + " " + Names[index] + " is " + Hex(actual) + ", expected " + Hex(expected));
}
}
for (std::uint32_t index = lanes * Results; index < Output.size(); ++index) {
Require(Output[index] == 0xdeadbeefu, std::string(variant) + ": inactive lane wrote output");
}
}

}

int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
const auto defaultTarget = device->Target();
const auto wave32Target = device->ComputeTarget(32);
const bool distinctTarget = defaultTarget.subgroupSize != wave32Target.subgroupSize;
std::printf("static call targets: default subgroup %u, ComputeTarget(32) subgroup %u\n", defaultTarget.subgroupSize, wave32Target.subgroupSize);
std::uint32_t lanes = 0u, dispatches = 0u;
const std::array<std::span<const std::uint32_t>, 5> variants{SwappcForward, CallForward, CallBackward, CallAfterEnd, MixedNested};
const std::array<const char*, 5> names{"SWAPPC forward", "CALL forward", "CALL backward", "CALL after endpgm", "mixed nested calls"};
for (std::size_t index = 0; index < variants.size(); ++index) {
const auto code = variants[index];
Run(*device, code, 32, wave32Target);
Check(names[index], 32);
Run(*device, code, 64, defaultTarget);
Check(names[index], 64);
if (distinctTarget) {
Run(*device, code, 64, wave32Target);
Check(names[index], 64);
}
const std::uint32_t variantLanes = distinctTarget ? 160u : 96u;
lanes += variantLanes;
dispatches += distinctTarget ? 3u : 2u;
std::printf("passed %s: %u lanes, %u output comparisons\n", names[index], variantLanes, variantLanes * Results);
}
std::printf("static call execution passed: 5 variants, %u dispatches, %u lanes, %u output comparisons\n", dispatches, lanes, lanes * Results);
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
214 changes: 174 additions & 40 deletions core/libs/tests/ScalarCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,72 +2,206 @@
#include "Translation/InstructionTranslator.hpp"
#include "RdnaDecoder/RdnaScalarOpDecoder.hpp"
#include "RdnaDecoder/RdnaInstructionDecoder.hpp"
#include "RdnaDecoder/RdnaMemoryOpDecoder.hpp"
#include "ControlFlow/GraphBuilder.hpp"
#include <array>
#include <cstdio>
#include <initializer_list>
#include <span>
#include <stdexcept>
#include <string>
#include <vector>

using namespace ShaderRecompiler;
static void Require(bool value) { if (!value) throw std::runtime_error("scalar call regression"); }

static void CheckDecodeAndBranch() {
const std::array<std::uint32_t, 1> code{0xbb040008u};
const RdnaInstruction instruction = DecodeRdnaSopk(0u, code, 0u);
Require(instruction.op == RdnaOpcode::SCallB64);
Require(instruction.family == RdnaInstructionFamily::SOPK);
Require(instruction.destination.kind == RdnaOperandKind::ScalarRegister);
Require(instruction.destination.reg == 4u);
Require(instruction.dataDwordCount == 2u);
Require(instruction.branchTarget == 36u);
static void Require(bool value) {
if (!value) throw std::runtime_error("scalar call regression");
}

template <typename TFunction>
static void ExpectThrow(const char* needle, TFunction fn) {
try {
fn();
} catch (const std::exception& error) {
if (std::string(error.what()).find(needle) == std::string::npos) {
throw std::runtime_error(std::string("expected ") + needle + ", got " + error.what());
}
return;
}
throw std::runtime_error(std::string("expected throw containing ") + needle);
}

static RdnaInstruction Call(std::uint32_t pc, std::uint32_t link, std::uint32_t immediate) {
const std::array<std::uint32_t, 1> code{0xbb000000u | (link << 16u) | (immediate & 0xffffu)};
return DecodeRdnaSopk(pc, code, 0u);
}

static RdnaInstruction Sop1(std::uint32_t pc, std::uint32_t destination, std::uint32_t op, std::uint32_t source) {
const std::array<std::uint32_t, 1> code{0xbe800000u | (destination << 16u) | (op << 8u) | source};
return DecodeRdnaSop1(pc, code, 0u);
}

static RdnaInstruction Sopp(std::uint32_t pc, std::uint32_t op, std::uint32_t immediate = 0u) {
const std::array<std::uint32_t, 1> code{0xbf800000u | (op << 16u) | immediate};
return DecodeRdnaSopp(pc, code, 0u);
}

static ControlFlowGraph Build(std::initializer_list<RdnaInstruction> instructions) {
RdnaProgram program;
program.instructions.assign(instructions);
return GraphBuilder{}.Build(program);
}

static void CheckNegativeOffset() {
const std::uint32_t word = 0xb8000000u | (0x16u << 23u) | (2u << 16u) | static_cast<std::uint16_t>(-4);
const std::array<std::uint32_t, 1> code{word};
const RdnaInstruction instruction = DecodeRdnaSopk(16u, code, 0u);
static void CheckDecode() {
const auto instruction = Call(0x40u, 8u, 7u);
Require(instruction.op == RdnaOpcode::SCallB64);
Require(instruction.destination.reg == 2u);
Require(instruction.branchTarget == 4u);
Require(instruction.family == RdnaInstructionFamily::SOPK);
Require(instruction.destination.kind == RdnaOperandKind::ScalarRegister && instruction.destination.reg == 8u);
Require(instruction.dataDwordCount == 2u && instruction.wordCount == 1u);
Require(instruction.branchTarget == 0x60u);
Require(Call(0x40u, 8u, 0xfff9u).branchTarget == 0x28u);
Require(Call(0x20000u, 8u, 0x8000u).branchTarget == 4u);
Require(Call(0u, 8u, 0x7fffu).branchTarget == 0x20000u);
}

static void CheckTranslationThrows() {
const std::array<std::uint32_t, 1> code{0xbb040008u};
const RdnaInstruction instruction = DecodeRdnaSopk(0u, code, 0u);
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);
bool threw = false;
try {
context.TranslateInstruction(instruction);
} catch (const std::runtime_error&) {
threw = true;
}
Require(threw);
context.TranslateInstruction(instruction);
return ProgramToString(program);
}

static void CheckProgramGraphBuilderAndTranslationThrows() {
const std::vector<std::uint32_t> code{0xbb040001u, 0xbf810000u, 0xbf810000u};
static void CheckLink() {
const auto dump = TranslateDump(Call(0x50u, 8u, 2u));
Require(dump.find("0x0000000000000054") != std::string::npos);
Require(dump == TranslateDump(Sop1(0x50u, 8u, 0x1fu, 0u)));
}

static void CheckCallAfterEnd() {
const std::array<std::uint32_t, 5> code{0xbb080001u, 0xbf810000u, 0xbf800000u, 0xbe802008u, 0xbf810000u};
const auto decoded = RdnaInstructionDecoder{}.Decode(code);
Require(decoded.instructions.size() == code.size());
const auto cfg = GraphBuilder{}.Build(decoded);
const auto& caller = cfg.FindBlockByProgramCounter(0u);
const auto& callee = cfg.FindBlockByProgramCounter(8u);
Require(caller.terminator.kind == TerminatorKind::Branch && caller.terminator.trueBlock == callee.id);
Require(callee.terminator.kind == TerminatorKind::Branch && callee.terminator.trueBlock == cfg.FindBlockByProgramCounter(4u).id);
ShaderComputeInputInfo computeInfo{};
TranslateOptions options{};
options.stage = ShaderStageKind::Compute;
options.inputInfo.compute = &computeInfo;
bool threw = false;
try {
static_cast<void>(InstructionTranslator{}.Translate(decoded, cfg, options));
} catch (const std::runtime_error& err) {
threw = std::string(err.what()).find("s_call_b64 at pc 0 is not implemented") != std::string::npos;
static_cast<void>(InstructionTranslator{}.Translate(decoded, cfg, options));
}

static void CheckRejections() {
ExpectThrow("invalid instruction boundary", [] {
Build({Call(0u, 8u, 16u), Sopp(4u, 1u)});
});
ExpectThrow("invalid instruction boundary", [] {
const std::array<std::uint32_t, 7> code{0xbb080002u, 0xbf810000u, 0xbe8003ffu, 0u, 0xbe802008u, 0xbf810000u, 0xbf810000u};
static_cast<void>(GraphBuilder{}.Build(RdnaInstructionDecoder{}.Decode(code)));
});
ExpectThrow("code span bounds", [] {
const std::array<std::uint32_t, 2> outside{0xbb080010u, 0xbf810000u};
static_cast<void>(RdnaInstructionDecoder{}.Decode(outside));
});
ExpectThrow("code span bounds", [] {
const std::array<std::uint32_t, 2> outside{0xbb08fffeu, 0xbf810000u};
static_cast<void>(RdnaInstructionDecoder{}.Decode(outside));
});
ExpectThrow("no paired s_setpc_b64 return", [] {
Build({Call(0u, 8u, 0u), Sopp(4u, 1u)});
});
for (const auto link : {9u, 105u, 106u, 125u}) {
ExpectThrow("ordinary aligned scalar register pair", [=] {
Build({Call(0u, link, 0u), Sopp(4u, 1u)});
});
}
ExpectThrow("escapes the constant-offset call/return model", [] {
Build({Call(0u, 8u, 1u), Sopp(4u, 1u), Sop1(8u, 8u, 3u, 128u), Sop1(12u, 0u, 0x20u, 8u)});
});
ExpectThrow("recursive scalar call", [] {
Build({Call(0u, 8u, 0xffffu), Sop1(4u, 0u, 0x20u, 8u), Sopp(8u, 1u)});
});
ExpectThrow("is shared with another call", [] {
Build({Call(0u, 8u, 2u), Call(4u, 8u, 1u), Sopp(8u, 1u), Sop1(12u, 0u, 0x20u, 8u)});
});
ExpectThrow("multiple s_setpc_b64 returns", [] {
Build({Call(0u, 8u, 1u), Sopp(4u, 1u), Sop1(8u, 0u, 0x20u, 8u), Sop1(12u, 0u, 0x20u, 8u)});
});
ExpectThrow("control flow enters the call region", [] {
Build({Call(0u, 8u, 2u), Sopp(4u, 2u, 1u), Sopp(8u, 1u), Sopp(12u, 0u), Sop1(16u, 0u, 0x20u, 8u)});
});
ExpectThrow("control flow leaves the call region", [] {
Build({Call(0u, 8u, 1u), Sopp(4u, 1u), Sopp(8u, 2u, 2u), Sop1(12u, 0u, 0x20u, 8u), Sopp(16u, 1u), Sopp(20u, 1u)});
});
}

static void CheckGuards(const std::string& only) {
bool checked = false;
const auto check = [&](const char* name, const char* needle, auto fn) {
if (only.empty() || only == name) {
checked = true;
ExpectThrow(needle, fn);
}
};
check("tuple", "escapes the constant-offset call/return model", [] {
auto load = Sopp(8u, 0u);
load.family = RdnaInstructionFamily::SMEM;
load.op = RdnaOpcode::SLoadDwordx8;
load.dataDwordCount = 8u;
load.destination.kind = RdnaOperandKind::ScalarRegister;
load.destination.reg = 4u;
Build({Call(0u, 8u, 1u), Sopp(4u, 1u), load, Sop1(12u, 0u, 0x20u, 8u)});
});
check("wrong_return", "escapes the constant-offset call/return model", [] {
Build({Call(0u, 8u, 1u), Sopp(4u, 1u), Sop1(8u, 0u, 0x20u, 9u)});
});
check("fallthrough", "control flow enters the call region", [] {
Build({Call(0u, 8u, 2u), Sopp(4u, 0u), Sopp(8u, 0u), Sopp(12u, 0u), Sop1(16u, 0u, 0x20u, 8u)});
});
check("conditional_fallthrough", "control flow enters the call region", [] {
Build({Call(0u, 8u, 2u), Sopp(4u, 2u), Sopp(8u, 4u, 2u), Sopp(12u, 0u), Sop1(16u, 0u, 0x20u, 8u), Sopp(20u, 1u)});
});
check("return_before_target", "return precedes the call target", [] {
Build({Call(0u, 8u, 2u), Sopp(4u, 1u), Sop1(8u, 0u, 0x20u, 8u), Sopp(12u, 0u), Sopp(16u, 1u)});
});
check("descriptor", "escapes the constant-offset call/return model", [] {
const std::array<std::uint32_t, 2> code{0xe0381000u, 0x80020401u};
const auto load = DecodeRdnaMubuf(8u, code, 0u);
Require(load.source1.reg == 8u);
Build({Call(0u, 10u, 1u), Sopp(4u, 1u), load, Sop1(16u, 0u, 0x20u, 10u)});
});
check("relative_read", "escapes the constant-offset call/return model", [] {
Build({Call(0u, 8u, 1u), Sopp(4u, 1u), Sop1(8u, 0u, 0x2eu, 0u), Sop1(12u, 0u, 0x20u, 8u)});
});
if (only.empty()) {
for (const auto link : {0u, 104u}) {
const auto cfg = Build({Call(0u, link, 1u), Sopp(4u, 1u), Sop1(8u, 0u, 0x20u, link)});
Require(cfg.FindBlockByProgramCounter(0u).terminator.kind == TerminatorKind::Branch);
}
}
Require(threw);
Require(checked);
}

int main() {
CheckDecodeAndBranch();
CheckNegativeOffset();
CheckTranslationThrows();
CheckProgramGraphBuilderAndTranslationThrows();
int main(int argc, char** argv) {
try {
if (argc == 1) {
CheckDecode();
CheckLink();
CheckCallAfterEnd();
CheckRejections();
CheckGuards("");
} else {
Require(argc == 2);
CheckGuards(argv[1]);
}
} catch (const std::exception& error) {
std::fprintf(stderr, "scalar call regression: %s\n", error.what());
return 1;
}
return 0;
}
2 changes: 1 addition & 1 deletion core/libs/tests/ScalarSwappc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ static void CheckThrows() {
Sop1(0x24u, 0u, 0x20u, 8u),
}));
});
ExpectThrow("recursive s_swappc_b64 call", [] {
ExpectThrow("recursive scalar call", [] {
Build(Program({
Sop1(0x00u, 4u, 0x1fu, 0u),
Sop2Literal(0x04u, 4u, 0x00u, 4u, 0x10u),
Expand Down
Loading
Loading