Merge pull request #1253 from popcoder565/feat/shader-system-opcodes

feat(shader): decode halt, trap return, code end, GWS and ordered count instructions
This commit is contained in:
Adrià 2026-10-08 12:19:38 +02:00 committed by GitHub
commit e28fa01ca6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 326 additions and 4 deletions

View File

@ -288,6 +288,20 @@ target_link_libraries(agc_masked_select_tests PRIVATE shader_recompiler_agc_driv
set_target_properties(agc_masked_select_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_masked_select_tests)
add_test_executable(agc_wave_control_decode_tests EXCLUDE_FROM_ALL tests/WaveControlDecode.cpp)
target_include_directories(agc_wave_control_decode_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_wave_control_decode_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_wave_control_decode_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_wave_control_decode_tests)
if(BUILD_TESTING)
set_target_properties(agc_wave_control_decode_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_wave_control_decode COMMAND agc_wave_control_decode_tests)
if(WIN32)
set_tests_properties(agc_wave_control_decode PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
add_test_executable(agc_vop1_control_decode_tests EXCLUDE_FROM_ALL tests/Vop1ControlDecode.cpp)
target_link_libraries(agc_vop1_control_decode_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_vop1_control_decode_tests PROPERTIES CXX_EXTENSIONS OFF)

View File

@ -0,0 +1,137 @@
#include "RdnaDecoder/RdnaInstructionDecoder.hpp"
#include "Recompiler.hpp"
#include <array>
#include <cstdio>
#include <exception>
#include <stdexcept>
#include <string>
#include <string_view>
#include <tuple>
#include <vector>
namespace {
using namespace ShaderRecompiler;
void require(bool condition, const std::string& message) {
if (!condition) throw std::runtime_error(message);
}
bool isVector(const RdnaOperand& operand, std::uint32_t reg) {
return operand.kind == RdnaOperandKind::VectorRegister && operand.reg == reg;
}
void verifyDecode() {
const std::vector<std::uint32_t> code{
0xbf8d0000u, 0xbf8d0001u, 0xbf911234u, 0xbe802204u,
0xd8661234u, 0x00000001u, 0xd86a0000u, 0x00000000u, 0xd86e0000u, 0x00000002u,
0xd8720000u, 0x00000000u, 0xd8620000u, 0x00000000u, 0xd8760000u, 0x00000003u,
0xd8fe0004u, 0x05000001u,
0xbf9f0000u, 0xbf9f0000u, 0xffffffffu,
};
const auto program = RdnaInstructionDecoder{}.Decode(code);
const auto& decoded = program.instructions;
require(decoded.size() == 12u, "decoding did not stop at the first s_code_end: " + std::to_string(decoded.size()) + " instructions");
require(decoded[0].op == RdnaOpcode::SSethalt && decoded[0].source0.value == 0u && decoded[0].sourceCount == 1u, "s_sethalt 0");
require(decoded[1].op == RdnaOpcode::SSethalt && decoded[1].source0.value == 1u, "s_sethalt 1");
require(decoded[2].op == RdnaOpcode::SSendmsghalt && decoded[2].source0.value == 0x1234u && decoded[2].sourceCount == 1u, "s_sendmsghalt 0x1234");
require(decoded[3].op == RdnaOpcode::SRfeB64 && decoded[3].destination.kind == RdnaOperandKind::Null && decoded[3].sourceCount == 1u &&
decoded[3].source0.kind == RdnaOperandKind::ScalarRegister && decoded[3].source0.reg == 4u, "s_rfe_b64 s[4:5]");
const std::array<std::tuple<RdnaOpcode, std::uint32_t, std::uint32_t, const char*>, 6> gws{{
{RdnaOpcode::DsGwsInit, 1u, 0x1234u, "ds_gws_init v1 offset:4660 gds"},
{RdnaOpcode::DsGwsSemaV, 0u, 0u, "ds_gws_sema_v gds"},
{RdnaOpcode::DsGwsSemaBr, 1u, 0u, "ds_gws_sema_br v2 gds"},
{RdnaOpcode::DsGwsSemaP, 0u, 0u, "ds_gws_sema_p gds"},
{RdnaOpcode::DsGwsSemaReleaseAll, 0u, 0u, "ds_gws_sema_release_all gds"},
{RdnaOpcode::DsGwsBarrier, 1u, 0u, "ds_gws_barrier v3 gds"},
}};
const std::array<std::uint32_t, 6> dataRegisters{1u, 0u, 2u, 0u, 0u, 3u};
for (std::size_t index = 0; index < gws.size(); ++index) {
const auto& [opcode, sources, offset, name] = gws[index];
const auto& instruction = decoded[4u + index];
require(instruction.op == opcode && instruction.gds && instruction.sourceCount == sources && instruction.memoryOffset == offset &&
instruction.destination.kind == RdnaOperandKind::None && instruction.wordCount == 2u, name);
if (sources != 0u) require(isVector(instruction.source0, dataRegisters[index]), std::string(name) + " reads the wrong data register");
}
const auto& ordered = decoded[10];
require(ordered.op == RdnaOpcode::DsOrderedCount && ordered.gds && ordered.memoryOffset == 4u && isVector(ordered.destination, 5u) &&
isVector(ordered.source0, 1u) && ordered.sourceCount == 1u, "ds_ordered_count v5, v1 offset:4 gds");
require(decoded[11].op == RdnaOpcode::SCodeEnd, "s_code_end");
}
std::string recompile(std::span<const std::uint32_t> code) {
const std::array<std::uint32_t, 4> userData{0x10000000u, 0x00100000u, 0x40u, 0x00027facu};
const std::array<std::uint32_t, 2> capabilities{1u, 61u};
const std::array<std::string_view, 1> extensions{"SPV_KHR_storage_buffer_storage_class"};
RecompileRequest request{};
request.shader = {ShaderStage::Compute, 0x20000u, code, 0, {}};
request.context.waveSize = 64;
request.context.userDataBaseRegister = 0;
request.context.userData = userData;
request.context.compute = ShaderComputeStageInfo{{64u, 1u, 1u}, 0u, {false, false, false}, false, 1u};
request.target.vulkanVersion = 0x00403000u;
request.target.spirvVersion = 0x00010600u;
request.target.subgroupSize = 64u;
request.target.bdaAbiVersion = 1;
request.target.supportedCapabilities = capabilities;
request.target.supportedExtensions = extensions;
request.target.maxWorkgroupSize = {1024u, 1024u, 64u};
request.target.maxWorkgroupInvocations = 1024;
request.target.maxWorkgroupSharedMemoryBytes = 49152;
request.layout = {0, 0, 0, 128};
request.useCache = false;
try {
const auto result = Recompile(request);
require(!result.spirv.empty(), "no SPIR-V");
} catch (const std::exception& error) {
return error.what();
}
return {};
}
std::vector<std::uint32_t> withStore(std::initializer_list<std::uint32_t> words) {
std::vector<std::uint32_t> code(words);
code.insert(code.end(), {0x7e020281u, 0xe0700000u, 0x80000100u, 0xbf810000u});
return code;
}
void verifyTranslation() {
require(recompile(withStore({0xbf8d0000u})).empty(), "s_sethalt 0 is refused: " + recompile(withStore({0xbf8d0000u})));
const std::vector<std::uint32_t> trailingLoop{0x7e020281u, 0xbf820003u, 0xe0700000u, 0x80000100u, 0xbf810000u, 0xbf82fffcu, 0xbf9f0000u, 0xbf9f0000u, 0xffffffffu};
require(recompile(trailingLoop).empty(), "a program padded with s_code_end after its last branch is refused: " + recompile(trailingLoop));
const std::vector<std::tuple<std::string_view, std::vector<std::uint32_t>, std::string_view>> refused{
{"s_sethalt 1", withStore({0xbf8d0001u}), "s_sethalt 1 at pc 0 halts the wave"},
{"s_sendmsghalt", withStore({0xbf910001u}), "s_sendmsghalt 1 at pc 0 halts the wave"},
{"s_code_end", {0x7e020281u, 0xbf9f0000u}, "s_code_end at pc 4 is reached"},
{"s_rfe_b64", withStore({0xbe802204u}), "s_rfe_b64 at pc 0 returns from a trap handler"},
{"ds_gws_init", withStore({0xd8660000u, 0x00000001u}), "ds_gws_init at pc 0: global wave sync"},
{"ds_gws_sema_v", withStore({0xd86a0000u, 0x00000000u}), "ds_gws_sema_v at pc 0: global wave sync"},
{"ds_gws_sema_br", withStore({0xd86e0000u, 0x00000002u}), "ds_gws_sema_br at pc 0: global wave sync"},
{"ds_gws_sema_p", withStore({0xd8720000u, 0x00000000u}), "ds_gws_sema_p at pc 0: global wave sync"},
{"ds_gws_sema_release_all", withStore({0xd8620000u, 0x00000000u}), "ds_gws_sema_release_all at pc 0: global wave sync"},
{"ds_gws_barrier", withStore({0xd8760000u, 0x00000003u}), "ds_gws_barrier at pc 0: global wave sync"},
{"ds_ordered_count", withStore({0xd8fe0000u, 0x05000001u}), "ds_ordered_count at pc 0: GDS counters"},
};
for (const auto& [name, code, message] : refused) {
const auto error = recompile(code);
require(error.find(message) != std::string::npos, std::string(name) + ": expected a refusal with '" + std::string(message) + "', got '" + error + "'");
}
}
}
int main() {
try {
verifyDecode();
verifyTranslation();
std::puts("wave control decode tests passed");
return 0;
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
}
return 1;
}

View File

@ -0,0 +1,92 @@
#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 <string>
#include <vector>
namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t Threads = 32;
constexpr std::uint32_t Inputs = 4;
constexpr std::uint32_t Results = 16;
alignas(256) std::array<std::uint32_t, Threads * Inputs> Input{};
alignas(256) std::array<std::uint32_t, Threads * Results> Output{};
alignas(256) constexpr std::array<std::uint32_t, 16> Code{
0x34020084, 0x34060086, 0xe0301000, 0x80000401, 0xbf8c3f70, 0xbf8d0000, 0x4a0a0881, 0xbf820003,
0xe0701000, 0x80010503, 0xbf810000, 0xbf82fffc, 0xbf9f0000, 0xbf9f0000, 0xbf9f0000, 0xbf9f0000,
};
void Fill(std::uint32_t tid, std::uint32_t* words) { words[0] = tid * 0x01010101u + 7u; }
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, 0x01016facu};
}
std::string Hex(std::uint32_t value) {
char text[16];
std::snprintf(text, sizeof(text), "0x%08x", value);
return text;
}
void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) {
Require(actual == expected, std::string("scalar sethalt: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected));
}
void Run(AgcDriver::VulkanDevice& device) {
for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]);
Output.fill(0xdeadbeefu);
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::span<const std::uint32_t> code(Code);
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1};
ShaderRecompiler::RecompileRequest request{
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
{32, 0, userData, compute, std::nullopt, std::nullopt, memory},
device.Target(),
{0, 0, 0, 128}
};
request.useCache = false;
const auto result = ShaderRecompiler::Recompile(request);
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
device.WaitIdle();
}
void Check() {
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
const std::uint32_t* in = &Input[tid * Inputs];
const std::uint32_t* out = &Output[tid * Results];
Expect(tid, out[0], in[0] + 1u, "store after s_sethalt 0");
Expect(tid, out[1], 0xdeadbeefu, "word after the store");
}
}
}
int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
Run(*device);
Check();
std::puts("scalar sethalt tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}

View File

@ -1042,6 +1042,17 @@ enum class RdnaOpcode : std::uint16_t {
Exp,
VMulHiI32I24,
VMulHiU32U24,
SSethalt,
SSendmsghalt,
SCodeEnd,
SRfeB64,
DsGwsInit,
DsGwsSemaV,
DsGwsSemaBr,
DsGwsSemaP,
DsGwsSemaReleaseAll,
DsGwsBarrier,
DsOrderedCount,
Count
};

View File

@ -168,7 +168,7 @@ void DecodeRdnaProgram(std::span<const std::uint32_t> code, RdnaProgram& program
furthestBranchTarget = std::max(furthestBranchTarget, targetIndex);
}
if (instruction.op == RdnaOpcode::SEndpgm && furthestBranchTarget < wordIndex) {
if ((instruction.op == RdnaOpcode::SEndpgm || instruction.op == RdnaOpcode::SCodeEnd) && furthestBranchTarget < wordIndex) {
return;
}
}

View File

@ -234,6 +234,12 @@ constexpr MemoryOpcodeInfo dsOpcodes[] = {
{0x11u, RdnaOpcode::DsCmpstF32, 1, 32, false, false, false},
{0x14u, RdnaOpcode::DsNop, 1, 32, false, false, false},
{0x15u, RdnaOpcode::DsAddF32, 1, 32, false, false, false},
{0x18u, RdnaOpcode::DsGwsSemaReleaseAll, 1, 32, false, false, false},
{0x19u, RdnaOpcode::DsGwsInit, 1, 32, false, false, false},
{0x1au, RdnaOpcode::DsGwsSemaV, 1, 32, false, false, false},
{0x1bu, RdnaOpcode::DsGwsSemaBr, 1, 32, false, false, false},
{0x1cu, RdnaOpcode::DsGwsSemaP, 1, 32, false, false, false},
{0x1du, RdnaOpcode::DsGwsBarrier, 1, 32, false, false, false},
{0x22u, RdnaOpcode::DsRsubRtnU32, 1, 32, false, false, false},
{0x2cu, RdnaOpcode::DsMskorRtnB32, 1, 32, false, false, false},
{0x30u, RdnaOpcode::DsCmpstRtnB32, 1, 32, false, false, false},
@ -316,6 +322,7 @@ constexpr MemoryOpcodeInfo dsOpcodes[] = {
{0x3cu, RdnaOpcode::DsReadU16, 1, 16, false, false, false},
{0x3du, RdnaOpcode::DsConsume, 1, 32, false, false, false},
{0x3eu, RdnaOpcode::DsAppend, 1, 32, false, false, false},
{0x3fu, RdnaOpcode::DsOrderedCount, 1, 32, false, false, false},
{0x4du, RdnaOpcode::DsWriteB64, 2, 32, false, false, false},
{0x4eu, RdnaOpcode::DsWrite2B64, 4, 32, false, false, false},
{0x4fu, RdnaOpcode::DsWrite2st64B64, 4, 32, false, false, false},
@ -469,6 +476,18 @@ bool isDsWriteOpcode(RdnaOpcode opcode) {
}
}
bool isDsGwsOpcode(RdnaOpcode opcode) {
switch (opcode) {
case RdnaOpcode::DsGwsInit:
case RdnaOpcode::DsGwsSemaV:
case RdnaOpcode::DsGwsSemaBr:
case RdnaOpcode::DsGwsSemaP:
case RdnaOpcode::DsGwsSemaReleaseAll:
case RdnaOpcode::DsGwsBarrier: return true;
default: return false;
}
}
bool isDsAtomicOpcode(RdnaOpcode opcode) {
switch (opcode) {
case RdnaOpcode::DsAddU32:
@ -565,7 +584,10 @@ std::uint32_t dsSourceCount(RdnaOpcode opcode) {
case RdnaOpcode::DsBpermuteB32: return 2u;
case RdnaOpcode::DsReadAddtidB32:
case RdnaOpcode::DsConsume:
case RdnaOpcode::DsAppend: return 0u;
case RdnaOpcode::DsAppend:
case RdnaOpcode::DsGwsSemaV:
case RdnaOpcode::DsGwsSemaP:
case RdnaOpcode::DsGwsSemaReleaseAll: return 0u;
default: return isDsWriteOpcode(opcode) || isDsAtomicOpcode(opcode) ? 2u : 1u;
}
}
@ -960,6 +982,9 @@ RdnaInstruction DecodeRdnaDs(std::uint32_t programCounter, std::span<const std::
instruction.source1 = isDsWriteOpcode(instruction.op) ? d16Half(vectorRegister(data0), instruction.op) : vectorRegister(data0);
instruction.source2 = vectorRegister(data1);
instruction.sourceCount = dsSourceCount(instruction.op);
if (isDsGwsOpcode(instruction.op)) {
instruction.destination.kind = RdnaOperandKind::None;
}
return instruction;
}

View File

@ -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 0x22u: return RdnaOpcode::SRfeB64;
case 0x24u: return RdnaOpcode::SAndSaveexecB64;
case 0x25u: return RdnaOpcode::SOrSaveexecB64;
case 0x26u: return RdnaOpcode::SXorSaveexecB64;
@ -204,9 +205,11 @@ RdnaOpcode decodeSoppOpcode(std::uint32_t opcode) {
case 0x0au: return RdnaOpcode::SBarrier;
case 0x0bu: return RdnaOpcode::SEndpgm;
case 0x0cu: return RdnaOpcode::SWaitcnt;
case 0x0du: return RdnaOpcode::SSethalt;
case 0x0eu: return RdnaOpcode::SSleep;
case 0x0fu: return RdnaOpcode::SSetprio;
case 0x10u: return RdnaOpcode::SSendmsg;
case 0x11u: return RdnaOpcode::SSendmsghalt;
case 0x12u: return RdnaOpcode::STrap;
case 0x13u: return RdnaOpcode::SIcacheInv;
case 0x14u: return RdnaOpcode::SIncperflevel;
@ -218,6 +221,7 @@ RdnaOpcode decodeSoppOpcode(std::uint32_t opcode) {
case 0x1au: return RdnaOpcode::SCbranchCdbg;
case 0x1bu:
case 0x1eu: return RdnaOpcode::SEndpgm;
case 0x1fu: return RdnaOpcode::SCodeEnd;
case 0x20u: return RdnaOpcode::SInstPrefetch;
case 0x21u: return RdnaOpcode::SClause;
case 0x22u: return RdnaOpcode::SWaitIdle;
@ -242,7 +246,8 @@ bool isSoppWaitOpcode(RdnaOpcode opcode) {
opcode == RdnaOpcode::STrap || opcode == RdnaOpcode::STtracedata || opcode == RdnaOpcode::SInstPrefetch ||
opcode == RdnaOpcode::SClause || opcode == RdnaOpcode::SCbranchCdbg || opcode == RdnaOpcode::SIcacheInv ||
opcode == RdnaOpcode::SIncperflevel || opcode == RdnaOpcode::SDecperflevel || opcode == RdnaOpcode::SWaitIdle ||
opcode == RdnaOpcode::SRoundMode || opcode == RdnaOpcode::SDenormMode;
opcode == RdnaOpcode::SRoundMode || opcode == RdnaOpcode::SDenormMode || opcode == RdnaOpcode::SSethalt ||
opcode == RdnaOpcode::SSendmsghalt;
}
std::uint32_t scalarDestinationDwordCount(RdnaOpcode opcode) {
@ -340,7 +345,7 @@ RdnaInstruction DecodeRdnaSop1(std::uint32_t programCounter, std::span<const std
instruction.destination = DecodeRdnaScalarDestination(scalarDestination, programCounter);
return instruction;
}
if (instruction.op == RdnaOpcode::SSetpcB64) {
if (instruction.op == RdnaOpcode::SSetpcB64 || instruction.op == RdnaOpcode::SRfeB64) {
instruction.sourceCount = 1;
instruction.destination.kind = RdnaOperandKind::Null;
instruction.source0 = DecodeRdnaScalarSource(scalarSource0, programCounter);

View File

@ -5,6 +5,22 @@
namespace ShaderRecompiler {
namespace {
const char* globalWaveSyncName(RdnaOpcode opcode) {
switch (opcode) {
case RdnaOpcode::DsGwsInit: return "ds_gws_init";
case RdnaOpcode::DsGwsSemaV: return "ds_gws_sema_v";
case RdnaOpcode::DsGwsSemaBr: return "ds_gws_sema_br";
case RdnaOpcode::DsGwsSemaP: return "ds_gws_sema_p";
case RdnaOpcode::DsGwsSemaReleaseAll: return "ds_gws_sema_release_all";
case RdnaOpcode::DsGwsBarrier: return "ds_gws_barrier";
default: throw std::logic_error("opcode is not a global wave sync instruction");
}
}
}
void TranslateMemoryInstruction(IrBuilder& builder, const RdnaInstruction& instruction) {
throw std::runtime_error("TranslateMemoryInstruction not implemented");
}
@ -445,6 +461,15 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) {
return dsAppendConsume(inst, IrOpcode::DataConsume);
case RdnaOpcode::DsAppend:
return dsAppendConsume(inst, IrOpcode::DataAppend);
case RdnaOpcode::DsGwsInit:
case RdnaOpcode::DsGwsSemaV:
case RdnaOpcode::DsGwsSemaBr:
case RdnaOpcode::DsGwsSemaP:
case RdnaOpcode::DsGwsSemaReleaseAll:
case RdnaOpcode::DsGwsBarrier:
throw std::runtime_error(std::string(globalWaveSyncName(inst.op)) + " at pc " + std::to_string(inst.programCounter) + ": global wave sync barriers and semaphores shared by waves of different workgroups are not modeled");
case RdnaOpcode::DsOrderedCount:
throw std::runtime_error("ds_ordered_count at pc " + std::to_string(inst.programCounter) + ": GDS counters updated in wave launch order are not modeled");
case RdnaOpcode::DsWriteAddtidB32:
return dsAddtid(inst, true);
case RdnaOpcode::DsReadAddtidB32:

View File

@ -417,6 +417,18 @@ bool TranslationContext::emitScalar(const RdnaInstruction& inst) {
return true;
case RdnaOpcode::SDenormMode:
throw std::runtime_error("s_denorm_mode " + std::to_string(inst.source0.value) + " at pc " + std::to_string(inst.programCounter) + ": the recompiler does not model denormal modes");
case RdnaOpcode::SSethalt:
if ((inst.source0.value & 1u) != 0u) {
throw std::runtime_error("s_sethalt " + std::to_string(inst.source0.value) + " at pc " + std::to_string(inst.programCounter) + " halts the wave until a debugger resumes it");
}
emitControlNop();
return true;
case RdnaOpcode::SSendmsghalt:
throw std::runtime_error("s_sendmsghalt " + std::to_string(inst.source0.value) + " at pc " + std::to_string(inst.programCounter) + " halts the wave until a debugger resumes it");
case RdnaOpcode::SCodeEnd:
throw std::runtime_error("s_code_end at pc " + std::to_string(inst.programCounter) + " is reached: it marks the end of the code and raises an illegal instruction exception");
case RdnaOpcode::SRfeB64:
throw std::runtime_error("s_rfe_b64 at pc " + std::to_string(inst.programCounter) + " returns from a trap handler, and recompiled shaders run without one");
case RdnaOpcode::SWaitcntDepctr:
case RdnaOpcode::SWaitIdle:
emitWaitcnt();

View File

@ -64,6 +64,7 @@ Throughout the project, every function at every stage either **does exactly what
- [sceRtcParseDateTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - accepted format assumed to be the ISO 8601 subset `YYYY-MM-DDTHH:MM:SS[.ffffff]` (T/t/space separator, optional fractional seconds) with the sibling RFC 3339 zone suffix (`Z` or `±HH:MM`), plus the RFC 2822 shape `Ddd, DD Mon YYYY HH:MM:SS[ GMT| ±HHMM]` and the asctime shape `Ddd Mon DD HH:MM:SS YYYY` (day zero- or space-padded) that shadPS4 parses at fixed positions; the weekday name is not checked against the date, a `±HHMM` offset is subtracted to get UTC like the RFC 3339 path (shadPS4 adds it), a string without a zone is taken as UTC, and anything outside these shapes throws instead of returning a parse error, unverified against hardware
- [sceAudio3dPortOpen](../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - buffer modes 0 and 1 (also selected by the 0x10 and 0x18 parameter sizes) and 3 beds depend on an SDK version check in the module ([prosper](https://github.com/mattias800/prosper)) and throw. A repeated `sceAudio3dInitialize` (`NOT_READY` in shadPS4, 0 in prosper) and `sceAudio3dPortAdvance` on a full queue (`NOT_READY` in shadPS4, unhandled in KytyPS5) throw too
- [sceAudio3dObjectReserve](../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - object ids, the `max_objects` limit and the checks of `sceAudio3dObjectReserve` and `sceAudio3dObjectUnreserve` follow [shadPS4](https://github.com/shadps4-emu/shadPS4) (PS4), the only reference with a body: ids count up from 1 per port, skipping 0, 0xFFFFFFFF and ids in use, and a failed reserve writes 0xFFFFFFFF
- [s_sethalt](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp) (core/shader/recompiler) - with SIMM16[0] clear it is a no-op: the RDNA 2 ISA guide describes that value as resuming a halted wave, and a wave that executes it is not halted; measured on a gfx1035 (RDNA 2) wave: `s_sethalt 0` followed by a VALU op completes and gives the normal result. Only SIMM16[0] is read; with it set the instruction throws
- [AMPR WriteAddressFromCounterPairOnCompletion](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - the pair of an even counter n is assumed to give counter n in the low 32 bits and n + 1 in the high 32 bits. Wait compare values, wait flush and counter index ranges follow the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation; the flush is accepted and has no effect, as commands run in order
- [AMPR command sizes](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - commands use the emulator's own encoding with an 8-byte header, so `Nop(n)` takes 8 + 4n bytes where the console record is n dwords including its header (`NopWithData(n)`: (n + 1) dwords). The measure functions agree with the appended sizes, but a title that pads to an exact offset or the end of the buffer with `Nop(remaining / 4)` gets `SCE_AMPR_ERROR_BUFFER_FULL` or a shifted offset. The at-start flag of the `_04_00` writes is ignored; commands run in order, each after the previous one completes
- [AMPR counter access](../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libSceAmpr) - signatures and checks of `WaitOnCounter_04_00`, `WriteCounter_04_00`, `ConstructNop` and `ConstructMarker` taken from the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation, which hands counter commands to the console and does not show their effect. The counters are taken as 32 bits each: the 8-byte access of counter n spans n (low) and n + 1 (high), the 2- and 1-byte accesses select a half or a byte of the counter by its offset, waits compare at the access width (signed and wrapped compares included), the AND mask applies to both sides and the atomic writes wrap within the access. The `ConstructNop` type is not recorded