mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 10:56:58 +02:00
shader: link scalar leaf calls only for SWAPPC programs
Co-authored-by: Behzad <behzadkhan1736@gmail.com>
This commit is contained in:
@@ -9950,6 +9950,105 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
void CheckScalarLeafCall() {
|
||||
constexpr const char* name = "ScalarLeafCall";
|
||||
constexpr uintptr_t base = 0x0000000204800000ull;
|
||||
constexpr uint64_t size = 0x10000;
|
||||
constexpr auto table = base + 0x100;
|
||||
constexpr auto first = base + 0x1000;
|
||||
constexpr auto second = base + 0x2000;
|
||||
constexpr auto output = base + 0x4000;
|
||||
std::vector<u32> code{
|
||||
EncodeSMovB32(20, InlineU32(0)),
|
||||
0xbeea03ffu, static_cast<u32>(base), 0xbeeb03ffu, static_cast<u32>(base >> 32u),
|
||||
0xf4080135u, 0xfa000000u, 0xbf8cc07fu, 0xf4240382u, 0xfa000020u,
|
||||
0xbf8cc07fu, 0xbf130e80u, 0xbf840004u, 0xf4240402u, 0xfa000028u,
|
||||
0xbf8cc07fu, 0xbe8e210eu, EncodeVop1(0x01, 1, 20),
|
||||
EncodeMubuf0(0x1c, 0, false), EncodeMubuf1(1, 0, 0)};
|
||||
AppendEnd(&code);
|
||||
ShaderMapUserData(reinterpret_cast<uint64_t>(code.data()),
|
||||
{.type = Prospero::ShaderBinaryType::kCs,
|
||||
.code_size_bytes = static_cast<u32>(code.size() * sizeof(u32))});
|
||||
EnsureRuntimeContext();
|
||||
int64_t physical = -1;
|
||||
Require(name, "allocate", LibKernel::Memory::KernelAllocateDirectMemory(
|
||||
0, LibKernel::Memory::KernelGetDirectMemorySize(), size, size, 0, &physical) == 0,
|
||||
"call fixture allocation failed");
|
||||
void* mapped = reinterpret_cast<void*>(base);
|
||||
Require(name, "map", LibKernel::Memory::KernelMapDirectMemory(
|
||||
&mapped, size, 0x3, 0x10, physical, size) == 0 && mapped == reinterpret_cast<void*>(base),
|
||||
"call fixture mapping failed");
|
||||
std::memset(mapped, 0, size);
|
||||
const ShaderBufferResource table_desc{{static_cast<u32>(table),
|
||||
static_cast<u32>(table >> 32u) | (16u << 16u), 8, 0x5204u}};
|
||||
LibKernel::Memory::WriteBacking(base, table_desc.fields, sizeof(table_desc.fields));
|
||||
const std::array leaf{EncodeSMovB32(20, 255), 41u, EncodeSop1(0x21, 125, 14)};
|
||||
for (const auto address : {first, second})
|
||||
LibKernel::Memory::WriteBacking(address, leaf.data(), sizeof(leaf));
|
||||
|
||||
RenderContext context(m_runtime_context);
|
||||
context.InitializeGpu(nullptr);
|
||||
LibKernel::Memory::InstallGpuResources(&context);
|
||||
context.GetGpu().SendCommandSync([&] {
|
||||
CommandProcessor processor(context, 0);
|
||||
processor.BufferInit();
|
||||
context.MapMemory(base, size);
|
||||
auto& cache = context.GetBufferCache();
|
||||
auto& shaders = processor.GetShCtx();
|
||||
shaders.SetCsShader({.data_addr = reinterpret_cast<uint64_t>(code.data()),
|
||||
.num_thread_x = 1, .num_thread_y = 1, .num_thread_z = 1,
|
||||
.wave_size = 64, .user_sgpr = 4});
|
||||
ShaderBufferResource out{};
|
||||
out.UpdateAddress48(output);
|
||||
out.fields[2] = 4;
|
||||
out.fields[3] = DstSel(4, 5, 6, 7) |
|
||||
(static_cast<u32>(Prospero::BufferFormat::k32UInt) << 12u);
|
||||
for (u32 i = 0; i < 4; ++i)
|
||||
shaders.SetCsUserSgpr(i, out.fields[i], HW::UserSgprType::Unknown);
|
||||
uint64_t original_id = 0;
|
||||
struct Case { uint64_t address; u32 value; bool update; };
|
||||
for (const auto test : {Case{first, 41, false}, Case{first, 41, false},
|
||||
Case{second, 41, false}, Case{second, 73, true},
|
||||
Case{first, 41, false}}) {
|
||||
Require(name, "refresh function pointer", context.InvalidateMemory(table + 0x20, 8),
|
||||
"function table escaped mapped memory");
|
||||
const uint64_t target = test.address | 3u;
|
||||
LibKernel::Memory::WriteBacking(table + 0x20, &target, sizeof(target));
|
||||
if (test.update) {
|
||||
auto [buffer, offset] = cache.ObtainBuffer(test.address + 4, 4, true);
|
||||
buffer->Fill(offset, 4, test.value);
|
||||
Require(name, "GPU-written function code", cache.HasGpuDirtyBytes(test.address + 4, 4),
|
||||
"code mutation did not remain GPU-owned before refresh");
|
||||
}
|
||||
ShaderComputeInputInfo input{};
|
||||
const auto program = context.GetPipelineCache().GetComputeProgram(
|
||||
shaders.GetCs(), processor.GetCtx().GetShaderRegisters(), input);
|
||||
if (original_id == 0) original_id = program.id;
|
||||
Require(name, "exact function identity",
|
||||
(program.id == original_id) == (test.value == 41),
|
||||
"helper bytes were stale or identical relocated/restored code recompiled");
|
||||
Require(name, "current code dependency",
|
||||
std::ranges::find(input.stage.resources->specialization_reads,
|
||||
std::pair<uint64_t, uint64_t>{test.address, sizeof(leaf)}) !=
|
||||
input.stage.resources->specialization_reads.end(),
|
||||
"function relocation retained an old code dependency");
|
||||
processor.DispatchDirect(1, 1, 1, 0x41u);
|
||||
cache.ReadMemory(output, sizeof(u32));
|
||||
u32 actual = 0;
|
||||
Require(name, "scalar leaf execution and return",
|
||||
LibKernel::Memory::TryReadBacking(output, &actual, sizeof(actual)) && actual == test.value,
|
||||
"caller did not observe the leaf's scalar result after return");
|
||||
}
|
||||
context.UnmapMemory(base, size);
|
||||
context.GetCommandScheduler().Finish();
|
||||
});
|
||||
LibKernel::Memory::InstallGpuResources(nullptr);
|
||||
context.ShutdownGpu();
|
||||
Require(name, "release", LibKernel::Memory::KernelCheckedReleaseDirectMemory(physical, size) == 0,
|
||||
"call fixture release failed");
|
||||
std::printf("[gpu] %-32s ok\n", name);
|
||||
}
|
||||
|
||||
void CheckNativeIndirectDispatch() {
|
||||
constexpr const char *name = "NativeIndirectDispatch";
|
||||
constexpr uintptr_t base = 0x0000000204600000ull;
|
||||
@@ -42796,6 +42895,7 @@ int main(int argc, char **argv) {
|
||||
if (argc == 2 && std::strcmp(argv[1], "--gpu-command-lane-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
vulkan.CheckGpuCommandLane();
|
||||
vulkan.CheckScalarLeafCall();
|
||||
CheckAgcSystemTable(vulkan.RuntimeRenderer());
|
||||
CheckPm4IndirectControlFlow(vulkan.RuntimeRenderer());
|
||||
CheckPm4WaitResume(vulkan.RuntimeRenderer());
|
||||
@@ -43290,6 +43390,7 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
vulkan.CheckGpuSuspendPoint();
|
||||
vulkan.CheckGpuCommandLane();
|
||||
vulkan.CheckScalarLeafCall();
|
||||
if (skipped_device_checks) {
|
||||
std::printf(
|
||||
"ShaderRecompilerComputeTests: device rasterization checks skipped, this "
|
||||
|
||||
@@ -10308,6 +10308,147 @@ void TestNewShaderRecompilerBranchConditionForms() {
|
||||
}
|
||||
}
|
||||
|
||||
void TestMemoryFedScalarLeafCall() {
|
||||
using namespace ShaderRecompiler;
|
||||
constexpr uint64_t base = 0xfe0040000ull;
|
||||
const uint32_t shader[] = {
|
||||
0xbeea03ffu, 0xe0040000u, 0xbeeb03ffu, 0x0000000fu,
|
||||
0xf4080135u, 0xfa000000u, 0xbf8cc07fu, 0xf4240382u, 0xfa000020u,
|
||||
0xbf8cc07fu, 0xbf130e80u, 0xbf840004u, 0xf4240402u, 0xfa000028u,
|
||||
0xbf8cc07fu, 0xbe8e210eu, 0xbf810000u};
|
||||
struct Memory {
|
||||
std::array<uint32_t, 128> words{};
|
||||
uint64_t end = base + 0x10c;
|
||||
bool clean = true;
|
||||
} memory;
|
||||
std::copy_n(std::array{0xe0040060u, 0x0010000fu, 8u, 0x5204u}.begin(),
|
||||
4, memory.words.begin());
|
||||
memory.words[32] = static_cast<uint32_t>(base + 0x103); // SWAPPC aligns the old pair.
|
||||
memory.words[33] = static_cast<uint32_t>(base >> 32u);
|
||||
memory.words[64] = EncodeSMovB32(3, 255);
|
||||
memory.words[65] = 0x12345678;
|
||||
memory.words[66] = EncodeSop1(0x21, 125, 14);
|
||||
const auto read = [](void *data, uint64_t address, std::span<uint32_t> out) {
|
||||
const auto& m = *static_cast<Memory*>(data);
|
||||
if (!m.clean || address < base || address > m.end || out.size_bytes() > m.end - address)
|
||||
return false;
|
||||
std::memcpy(out.data(), reinterpret_cast<const uint8_t*>(m.words.data()) + address - base,
|
||||
out.size_bytes());
|
||||
return true;
|
||||
};
|
||||
IR::SrtRuntime runtime{.read_memory = read, .userdata = &memory, .read_specialization_memory = read};
|
||||
const auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
auto source = PrepareShaderSource(shader, options);
|
||||
Check(source.call && source.decoded.instructions[source.call->instruction].branch_target == UINT32_MAX,
|
||||
"memory-fed SWAPPC did not retain an unresolved target");
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
ExpectFatal([&] { (void)TranslateProgram(source.decoded, options); },
|
||||
"unresolved SWAPPC silently fell through");
|
||||
#endif
|
||||
const auto& linked = RefreshShaderSource(source, runtime);
|
||||
Check(source.revision == 1 && linked.instructions.back().branch_target == 0x40 &&
|
||||
linked.instructions[source.call->instruction].branch_target == 0x44 &&
|
||||
source.reads.back() == std::pair<uint64_t, uint64_t>{base + 0x100, 12},
|
||||
"aliased call target, exact leaf range, or continuation was lost");
|
||||
const auto* storage = linked.instructions.data();
|
||||
const auto* scratch = source.linked->observed_function.data();
|
||||
(void)RefreshShaderSource(source, runtime);
|
||||
Check(source.revision == 1 && source.linked->decoded.instructions.data() == storage &&
|
||||
source.linked->observed_function.data() == scratch,
|
||||
"unchanged leaf rebuilt decoded instructions or refresh storage");
|
||||
auto translated = TranslateProgram(linked, options);
|
||||
translated.program.source_reads = source.reads;
|
||||
const auto plan = IR::ExtractResourcePlan(translated.program);
|
||||
Check(plan.source_reads == source.reads && plan.capture_specialization_reads,
|
||||
"linked code dependencies were omitted from resource alias validation");
|
||||
|
||||
std::vector<uint32_t> writer(std::begin(shader), std::end(shader) - 1);
|
||||
writer.insert(writer.end(), {EncodeVop1(0x01, 1, 3), EncodeMubuf0(0x1c, 0, false),
|
||||
EncodeMubuf1(1, 6, 0), EncodeSopp(0x01)});
|
||||
std::array<uint32_t, 64> user_data{};
|
||||
user_data[25] = static_cast<uint32_t>(base >> 32u);
|
||||
user_data[26] = 4;
|
||||
user_data[27] = 3u << 28u;
|
||||
auto write_options = options;
|
||||
write_options.user_data = user_data;
|
||||
auto write_runtime = runtime;
|
||||
write_runtime.user_data = user_data;
|
||||
auto write_source = PrepareShaderSource(writer, write_options);
|
||||
auto write_program = TranslateProgram(RefreshShaderSource(write_source, write_runtime), write_options);
|
||||
write_program.program.source_reads = write_source.reads;
|
||||
auto write_plan = IR::ExtractResourcePlan(write_program.program);
|
||||
IR::ResourceSnapshot snapshot;
|
||||
IR::ResourceSpecialization specialization;
|
||||
const auto written = std::ranges::find_if(write_plan.info.buffers,
|
||||
[](const auto& buffer) { return buffer.written; });
|
||||
Check(written != write_plan.info.buffers.end(), "caller store lost its writable binding");
|
||||
const auto output_index = static_cast<size_t>(written - write_plan.info.buffers.begin());
|
||||
for (const auto offset : {0x80u, 0x100u, 0x1f0u}) {
|
||||
user_data[24] = static_cast<uint32_t>(base + offset);
|
||||
Check(IR::MaterializeResources(write_plan, write_runtime, snapshot, specialization) &&
|
||||
snapshot.buffers[output_index].dwords[0] == user_data[24] &&
|
||||
snapshot.buffers[output_index].dwords[2] == 4 &&
|
||||
std::ranges::find(snapshot.specialization_reads,
|
||||
std::pair<uint64_t, uint64_t>{base + 0x80, 4}) != snapshot.specialization_reads.end() &&
|
||||
std::ranges::find(snapshot.specialization_reads,
|
||||
std::pair<uint64_t, uint64_t>{base + 0x100, 12}) != snapshot.specialization_reads.end(),
|
||||
"call dependencies or writable ranges were lost before binding validation");
|
||||
}
|
||||
|
||||
// Relocating identical code updates dependencies without rebuilding the source.
|
||||
std::copy_n(memory.words.begin() + 64, 3, memory.words.begin() + 96);
|
||||
memory.words[32] = static_cast<uint32_t>(base + 0x180);
|
||||
memory.end = base + 0x18c;
|
||||
(void)RefreshShaderSource(source, runtime);
|
||||
Check(source.revision == 1 && source.reads.back().first == base + 0x180,
|
||||
"identical relocated leaf recompiled or retained its old memory dependency");
|
||||
memory.words[97] = 0x87654321;
|
||||
(void)RefreshShaderSource(source, runtime);
|
||||
Check(source.revision == 2 && source.linked->code.back() == EncodeSop1(0x21, 125, 14),
|
||||
"changed leaf bytes retained stale source");
|
||||
// A newly selected shorter function may end before the old bulk-read length.
|
||||
memory.words[96] = EncodeSMovB32(3, 129);
|
||||
memory.words[97] = EncodeSop1(0x21, 125, 14);
|
||||
memory.end = base + 0x188;
|
||||
(void)RefreshShaderSource(source, runtime);
|
||||
Check(source.revision == 3 && source.reads.back().second == 8,
|
||||
"shorter exact-boundary leaf was not decoded after a failed old-span read");
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
memory.clean = false;
|
||||
ExpectFatal([&] { (void)RefreshShaderSource(source, runtime); },
|
||||
"unreadable call dependencies reused stale linked code");
|
||||
memory.clean = true;
|
||||
auto ordinary = runtime;
|
||||
ordinary.read_specialization_memory = nullptr;
|
||||
ExpectFatal([&] { (void)RefreshShaderSource(source, ordinary); },
|
||||
"call target query accepted an ordinary non-strict memory reader");
|
||||
std::vector<uint32_t> escaping(std::begin(shader), std::end(shader) - 1);
|
||||
escaping.insert(escaping.end(), {EncodeVop1(0x01, 1, 14),
|
||||
EncodeMubuf0(0x1c, 0, false), EncodeMubuf1(1, 0, 0), EncodeSopp(0x01)});
|
||||
ExpectFatal([&] {
|
||||
auto escaping_source = PrepareShaderSource(escaping, options);
|
||||
(void)TranslateProgram(RefreshShaderSource(escaping_source, runtime), options);
|
||||
}, "saved native return PC escaped into GPU data");
|
||||
memory.words[96] = EncodeSMovB32(14, 129);
|
||||
ExpectFatal([&] { (void)RefreshShaderSource(source, runtime); },
|
||||
"leaf clobbering its return pair was linked");
|
||||
// AND_B64 writes the high half despite the decoder's generic scalar width.
|
||||
const uint32_t high_writer[] = {
|
||||
EncodeSMovB32(15, 129), EncodeSop2(0x0f, 14, 128, 128),
|
||||
EncodeSop1(0x21, 14, 14), EncodeSopp(0x01)};
|
||||
auto pair_source = PrepareShaderSource(high_writer, options);
|
||||
IR::SrtWalker pair_walker(pair_source.call_targets, runtime);
|
||||
uint32_t high = 1;
|
||||
Check(pair_walker.Evaluate(pair_source.call->target[1], high) && high == 0,
|
||||
"scalar pair producer used a stale preceding high-half definition");
|
||||
const uint32_t joined[] = {
|
||||
EncodeSopp(0x04, 2), EncodeSMovB32(14, 129), EncodeSopp(0x02, 1),
|
||||
EncodeSMovB32(14, 130), EncodeSop1(0x21, 14, 14), EncodeSopp(0x01)};
|
||||
ExpectFatal([&] { (void)PrepareShaderSource(joined, options); },
|
||||
"ambiguous reaching definitions were accepted for a call target");
|
||||
#endif
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerSetpcBranch() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSop1(0x1f, 4, 0), // s_getpc_b64 s[4:5]
|
||||
@@ -15445,6 +15586,7 @@ int main() {
|
||||
TestNewShaderRecompilerPixelImageSampleLodSelection();
|
||||
TestNewShaderRecompilerBranchConditionForms();
|
||||
TestNewShaderRecompilerSetpcBranch();
|
||||
TestMemoryFedScalarLeafCall();
|
||||
TestFusedShaderHandoffPreservesRegisters();
|
||||
TestMeshExportStorage();
|
||||
TestMergedShaderUserDataSnapshot();
|
||||
|
||||
Reference in New Issue
Block a user