shader: complete faulting SRT walker loads as zero reads

This commit is contained in:
nmzik
2026-10-24 01:39:42 +02:00
parent f356fa3730
commit bdf48dcf3b
6 changed files with 72 additions and 6 deletions
@@ -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);
+6
View File
@@ -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.
+6
View File
@@ -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
+4
View File
@@ -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
+24
View File
@@ -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 &registers = 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");