Files
AnyPS5/core/libs/tests/IrDumpMemory.cpp
T
Yunus Shaikh 2cbaa96390 fix(shader): print memory instructions' pc and offset in the IR dump
The APS5_DUMP_IR dump showed every scalar load's immediate offset as 0.
An SMEM load keeps its immediate in the instruction's memory metadata:
MemoryFlags indexes Resources().memoryInfo, whose offset holds the
immediate plus 4 per component, while the soffset argument is the
constant 0 when the instruction has none. Buffer, address and shared
memory instructions keep their offsets there too. ProgramToString
printed the arguments, the type and the use count, but not the flags or
the metadata, so a dump could not tell 0x54 from 0x6c.

ProgramToString now appends " [pc=0x... off=0x...]" to every
instruction that carries memory metadata, and " [pc=0x... mem=?]" when
its index is past memoryInfo, since dumps run before validation. The
opcode set is one predicate, carriesMemoryInfo, which ValidateProgram
now also uses for its memory-info index check; the validator accepts and
rejects the same programs with the same messages.

https://github.com/boykopovar/AnyPS5/pull/9633#issuecomment-6349729587
Related: #9749.
2026-10-22 19:55:54 +00:00

97 lines
4.0 KiB
C++

#include "Translation/TranslationContext.hpp"
#include "RdnaDecoder/RdnaMemoryOpDecoder.hpp"
#include <array>
#include <cstdio>
#include <initializer_list>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
using namespace ShaderRecompiler;
static void Require(bool value, const std::string& message) {
if (!value) throw std::runtime_error(message);
}
static RdnaInstruction Smem(std::uint32_t pc, std::uint32_t opcode, std::uint32_t destination, std::uint32_t base, std::uint32_t offset) {
const std::array<std::uint32_t, 2> code{(0x3du << 26u) | (opcode << 18u) | (destination << 6u) | (base / 2u), (125u << 25u) | offset};
return DecodeRdnaSmem(pc, code, 0u);
}
static RdnaInstruction Ds(std::uint32_t pc, std::uint32_t opcode, std::uint32_t offset0, std::uint32_t offset1, std::uint32_t address, std::uint32_t destination) {
const std::array<std::uint32_t, 2> code{(0x36u << 26u) | (opcode << 18u) | (offset1 << 8u) | offset0, (destination << 24u) | address};
return DecodeRdnaDs(pc, code, 0u);
}
static void Translate(IrProgram& program, std::initializer_list<RdnaInstruction> instructions) {
auto& block = program.CreateBlock();
program.SetEntryBlock(block);
program.BlockOrder() = {&block};
program.Metadata().blockInfo.resize(1);
TranslationContext context(program, block, 256);
for (const auto& instruction : instructions) {
context.TranslateInstruction(instruction);
}
}
static std::vector<std::string> Lines(const std::string& dump, const std::string& opcode) {
std::vector<std::string> lines;
std::istringstream stream(dump);
for (std::string line; std::getline(stream, line);) {
if (line.find(" " + opcode + " ") != std::string::npos) lines.push_back(line);
}
return lines;
}
static void RequireLines(const std::string& dump, const std::string& opcode, std::initializer_list<const char*> suffixes) {
const auto lines = Lines(dump, opcode);
Require(lines.size() == suffixes.size(), dump + "\nexpected " + std::to_string(suffixes.size()) + " " + opcode + " lines, found " + std::to_string(lines.size()));
std::size_t index = 0;
for (const std::string suffix : suffixes) {
const auto& line = lines[index++];
Require(line.size() >= suffix.size() && line.compare(line.size() - suffix.size(), suffix.size(), suffix) == 0, dump + "\nexpected \"" + line + "\" to end with \"" + suffix + "\"");
}
}
static void CheckOffsets() {
IrProgram program;
Translate(program, {
Smem(0x12de0u, 0x00u, 4u, 0u, 0x54u),
Smem(0x12de8u, 0x0au, 12u, 8u, 0x60u),
Ds(0x12df0u, 0x37u, 3u, 9u, 1u, 2u),
});
const auto dump = ProgramToString(program);
RequireLines(dump, "LoadAddressU32", {" [pc=0x00012de0 off=0x54]"});
RequireLines(dump, "ReadConstBuffer", {" [pc=0x00012de8 off=0x60]", " [pc=0x00012de8 off=0x64]", " [pc=0x00012de8 off=0x68]", " [pc=0x00012de8 off=0x6c]"});
RequireLines(dump, "LoadSharedU32", {" [pc=0x00012df0 off=0xc]", " [pc=0x00012df0 off=0x24]"});
Require(dump.find(" %1 = GetScalarRegister s0 (U32; uses=1)\n") != std::string::npos, dump + "\nexpected register reads without memory fields");
}
static void CheckMissingMemoryInfo() {
IrProgram program;
Translate(program, {Smem(0x40u, 0x00u, 4u, 0u, 0x54u)});
program.Resources().memoryInfo.clear();
const auto dump = ProgramToString(program);
RequireLines(dump, "LoadAddressU32", {" [pc=0x00000040 mem=?]"});
bool rejected = false;
try {
ValidateProgram(program, false);
} catch (const std::runtime_error& error) {
rejected = std::string(error.what()) == "LoadAddressU32 has an invalid memory-info index";
Require(rejected, std::string("unexpected validation failure: ") + error.what());
}
Require(rejected, "validation accepted a memory instruction without memory info");
}
int main() {
try {
CheckOffsets();
CheckMissingMemoryInfo();
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
return 1;
}
return 0;
}