mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-25 04:16:55 +01:00
shader: complete faulting SRT walker loads as zero reads
This commit is contained in:
@@ -176,6 +176,14 @@ bool IsRuntimeUniformOp(ValueOpcode op) {
|
||||
}
|
||||
}
|
||||
|
||||
thread_local const Xbyak::CodeGenerator* t_running_walker = nullptr;
|
||||
|
||||
// Every guest load is mov eax, [r10] (41 8b 02), so a fault completes it as a zero read.
|
||||
void LoadGuest(Xbyak::CodeGenerator& code, const Xbyak::RegExp& address) {
|
||||
code.lea(code.r10, code.ptr[address]);
|
||||
code.mov(code.eax, code.dword[code.r10]);
|
||||
}
|
||||
|
||||
class RuntimeValidator {
|
||||
public:
|
||||
explicit RuntimeValidator(const ResourcePlan& program, RuntimeValueType type,
|
||||
@@ -449,8 +457,13 @@ void CompileSrtWalker(ResourcePlan& program) {
|
||||
// descend through its child pointers. Seed the descriptor evaluator's memo.
|
||||
const auto visit = [&](auto&& self, int32_t pointer_offset, const Reads& reads) -> void {
|
||||
code.push(code.rdi);
|
||||
code.mov(code.rdi, code.qword[code.rdi + pointer_offset]);
|
||||
code.and_(code.rdi, code.r9);
|
||||
LoadGuest(code, code.rdi + pointer_offset + 4);
|
||||
code.shl(code.rax, 32);
|
||||
code.mov(code.r11, code.rax);
|
||||
LoadGuest(code, code.rdi + pointer_offset);
|
||||
code.or_(code.rax, code.r11);
|
||||
code.and_(code.rax, code.r9);
|
||||
code.mov(code.rdi, code.rax);
|
||||
const auto first = reads.begin()->first;
|
||||
const auto last = reads.rbegin()->first;
|
||||
if (first < 0) {
|
||||
@@ -467,9 +480,9 @@ void CompileSrtWalker(ResourcePlan& program) {
|
||||
const auto index = read->value.ResolveInstruction()->EvaluationIndex(program.evaluation_value_count);
|
||||
using Entry = ResourcePlan::EvaluationContext::Entry;
|
||||
const auto slot = code.rdx + index * sizeof(Entry);
|
||||
code.mov(code.r10d, code.dword[code.rdi + offset]);
|
||||
code.mov(code.dword[code.rsi + read->flat_offset * sizeof(uint32_t)], code.r10d);
|
||||
code.mov(code.qword[slot + offsetof(Entry, value)], code.r10);
|
||||
LoadGuest(code, code.rdi + offset);
|
||||
code.mov(code.dword[code.rsi + read->flat_offset * sizeof(uint32_t)], code.eax);
|
||||
code.mov(code.qword[slot + offsetof(Entry, value)], code.rax);
|
||||
code.mov(code.qword[slot + offsetof(Entry, generation)], code.rcx);
|
||||
}
|
||||
for (const auto& [offset, read]: reads) {
|
||||
@@ -1146,8 +1159,11 @@ bool SrtWalker::RefreshFlatBuffer(std::vector<uint32_t>& flat) {
|
||||
m_context.values.resize(m_program.evaluation_value_count);
|
||||
using Walker = bool KYTY_SYSV_ABI (*)(const uint32_t*, uint32_t*,
|
||||
ResourcePlan::EvaluationContext::Entry*, uint64_t, size_t);
|
||||
return m_program.srt_code->getCode<Walker>()(m_runtime.user_data.data(), flat.data(),
|
||||
t_running_walker = m_program.srt_code.get();
|
||||
const bool walked = m_program.srt_code->getCode<Walker>()(m_runtime.user_data.data(), flat.data(),
|
||||
m_context.values.data(), m_context.generation, m_runtime.user_data.size());
|
||||
t_running_walker = nullptr;
|
||||
return walked;
|
||||
}
|
||||
const auto refresh = [&](uint32_t slot) {
|
||||
if (slot >= m_program.srt_reads.size()) return false;
|
||||
@@ -1228,5 +1244,12 @@ bool ValidateWaveUniformValue(const ResourcePlan& program, Value value) {
|
||||
return RuntimeValidator(program, RuntimeValueType::Any, true).Run(value);
|
||||
}
|
||||
|
||||
uint32_t SrtWalkerLoadLength(uint64_t rip) {
|
||||
if (t_running_walker == nullptr ||
|
||||
rip - reinterpret_cast<uint64_t>(t_running_walker->getCode()) >= t_running_walker->getSize())
|
||||
return 0;
|
||||
const auto* code = reinterpret_cast<const uint8_t*>(rip);
|
||||
return code[0] == 0x41u && code[1] == 0x8bu && code[2] == 0x02u ? 3 : 0;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
@@ -79,6 +79,9 @@ bool ValidateRuntimeValue(const ResourcePlan& program, Value value,
|
||||
// This does not imply that the host can evaluate the value.
|
||||
bool ValidateWaveUniformValue(const ResourcePlan& program, Value value);
|
||||
void CompileSrtWalker(ResourcePlan& program);
|
||||
// Length of the guest load at rip if it belongs to the walker running on this thread, else 0.
|
||||
// The exception handler completes such a load as a zero read.
|
||||
uint32_t SrtWalkerLoadLength(uint64_t rip);
|
||||
// Uses the strict reader for values that affect shader specialization.
|
||||
SrtRuntime CleanRuntime(SrtRuntime runtime);
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "common/threads.h"
|
||||
#include "common/virtualMemory.h"
|
||||
#include "graphics/host_gpu/pageManager.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
|
||||
#include "kernel/fileSystem.h"
|
||||
#include "kernel/memory.h"
|
||||
#include "kernel/pthread.h"
|
||||
@@ -672,6 +673,11 @@ static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exc
|
||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::GpuFaults);
|
||||
return true;
|
||||
}
|
||||
if (const auto length =
|
||||
Libs::Graphics::ShaderRecompiler::IR::SrtWalkerLoadLength(info->exception_address)) {
|
||||
Loader::X64InstructionEmulator::SkipLoad(info->native_context, length);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Report whatever guest context can be read safely before terminating: which guest thread
|
||||
// faulted, the register file, the faulting code bytes and the top of its stack.
|
||||
|
||||
@@ -822,4 +822,10 @@ bool TryEmulate(void* native_context) {
|
||||
TryEmulateCpuExtensions(context);
|
||||
}
|
||||
|
||||
void SkipLoad(void* native_context, size_t length) {
|
||||
Context context {static_cast<decltype(Context::native)>(native_context)};
|
||||
context.Gpr(0) = 0;
|
||||
context.Advance(length);
|
||||
}
|
||||
|
||||
} // namespace Loader::X64InstructionEmulator
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#ifndef KYTY_LOADER_X64_INSTRUCTION_EMULATOR_H_
|
||||
#define KYTY_LOADER_X64_INSTRUCTION_EMULATOR_H_
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace Loader::X64InstructionEmulator {
|
||||
|
||||
[[nodiscard]] bool TryEmulate(void* native_context);
|
||||
// Completes a faulting load into EAX as a zero read and resumes after it.
|
||||
void SkipLoad(void* native_context, size_t length);
|
||||
|
||||
} // namespace Loader::X64InstructionEmulator
|
||||
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <csignal>
|
||||
#include <sys/mman.h>
|
||||
#include <ucontext.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
void Check(bool value, const char *text) {
|
||||
@@ -227,6 +233,24 @@ void TestNativeNestedSrtRefresh() {
|
||||
std::vector<uint32_t>{(*data)[1], table[0], table[1], (*data)[2]},
|
||||
"native SRT refresh lost pointer masking, alignment, or current values");
|
||||
}
|
||||
#if defined(__linux__)
|
||||
struct sigaction action {}, previous {};
|
||||
action.sa_sigaction = +[](int, siginfo_t *, void *context) {
|
||||
auto ®isters = static_cast<ucontext_t *>(context)->uc_mcontext.gregs;
|
||||
const auto length = SrtWalkerLoadLength(static_cast<uint64_t>(registers[REG_RIP]));
|
||||
if (length == 0) std::abort();
|
||||
registers[REG_RAX] = 0;
|
||||
registers[REG_RIP] += length;
|
||||
};
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
auto *page = mmap(nullptr, 4096, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
Check(page != MAP_FAILED && sigaction(SIGSEGV, &action, &previous) == 0 &&
|
||||
MaterializeResources(plan, {.user_data = pointer_words(page)}, snapshot, specialization) &&
|
||||
snapshot.flattened_srt == std::vector<uint32_t>(4, 0u),
|
||||
"faulting walker loads of a table and a null pointer did not read zero");
|
||||
sigaction(SIGSEGV, &previous, nullptr);
|
||||
munmap(page, 4096);
|
||||
#endif
|
||||
Check(!MaterializeResources(plan, {.user_data = std::span(user_data).first(1)},
|
||||
snapshot, specialization),
|
||||
"native SRT walk accepted truncated user data");
|
||||
|
||||
Reference in New Issue
Block a user