diff --git a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp index cdaf187b..1b732378 100644 --- a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp @@ -92,9 +92,9 @@ void PipelineCacheLog(fmt::format_string format, Args&&... args) { Log::WriteToConsoleAndLog(message); } -bool ReadShaderGuestMemory(void*, uint64_t address, uint32_t* value) { - return value != nullptr && - Libs::LibKernel::Memory::TryReadGpuCleanBacking(address, value, sizeof(*value)); +bool ReadShaderGuestMemory(void*, uint64_t address, std::span values) { + return !values.empty() && + Libs::LibKernel::Memory::TryReadGpuCleanBacking(address, values.data(), values.size_bytes()); } void DumpShaderSpirv(const char* stage_name, uint64_t shader_hash, diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp index 88fec815..17d5cb71 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp @@ -161,28 +161,23 @@ void MarkCleanFlatSlots(const ResourcePlan& program, const DescriptorSource* sou } } -bool ReadSpecializationWord(const SrtRuntime& runtime, uint64_t address, uint32_t& word) { - return runtime.read_specialization_memory != nullptr && - runtime.read_specialization_memory(runtime.userdata, address, &word); -} - -bool ReadScalarTableWord(uint64_t base, uint64_t size, uint32_t dynamic_offset, - uint32_t immediate_offset, const SrtRuntime& runtime, uint32_t& word) { - const auto byte_offset = static_cast(dynamic_offset) + immediate_offset; - const auto aligned = byte_offset & ~uint64_t {3}; - if (aligned > size || size - aligned < sizeof(uint32_t)) { - word = 0; +bool ReadScalarTable(uint64_t base, uint64_t size, uint32_t dynamic_offset, + const SrtRuntime& runtime, std::span words) { + const auto offset = static_cast(dynamic_offset) & ~uint64_t {3}; + const auto count = std::min(words.size(), offset < size ? (size - offset) / 4u : 0u); + std::ranges::fill(words.subspan(count), 0u); + if (count == 0u) { return true; } base &= AddressMask & ~uint64_t {3}; - if (aligned > AddressMask - base) { + if (offset > AddressMask - base) { return false; } - const auto address = base + aligned; - if (!ReadSpecializationWord(runtime, address, word)) { - return false; - } - return true; + const auto address = base + offset; + const auto prefix = words.first(count); + return prefix.size_bytes() - 1u <= AddressMask - address && + runtime.read_specialization_memory != nullptr && + runtime.read_specialization_memory(runtime.userdata, address, prefix); } bool MaterializeIndirectImage(const ResourcePlan& program, @@ -229,8 +224,8 @@ bool MaterializeIndirectImage(const ResourcePlan& program, keys.push_back(0u); for (uint64_t offset = residue; offset <= limit && probe_count != 0u; offset += step) { uint32_t key = 0; - if (!ReadScalarTableWord(material.Base48(), material.GetSize(), - static_cast(offset), 0u, runtime, key)) { + if (!ReadScalarTable(material.Base48(), material.GetSize(), + static_cast(offset), runtime, {&key, 1})) { return false; } keys.push_back(key); @@ -252,11 +247,8 @@ bool MaterializeIndirectImage(const ResourcePlan& program, DescriptorValue candidate; candidate.dword_count = 8u; const auto table_offset = (key << 5u) + indirect.table_offset; - for (uint32_t dword = 0; dword < candidate.dword_count; ++dword) { - if (!ReadScalarTableWord(table_base, table_size, table_offset, dword * sizeof(uint32_t), - runtime, candidate.dwords[dword])) { - return false; - } + if (!ReadScalarTable(table_base, table_size, table_offset, runtime, candidate.dwords)) { + return false; } if (NullImageDescriptor(candidate) || !ValidImageDescriptor(candidate, program.info.images[image_index].r128)) { diff --git a/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp b/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp index 5bf69fbd..4349d0c7 100644 --- a/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp +++ b/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp @@ -15,7 +15,7 @@ namespace Libs::Graphics::ShaderRecompiler::IR { SrtRuntime CleanRuntime(SrtRuntime runtime) { runtime.read_memory = runtime.read_specialization_memory != nullptr ? runtime.read_specialization_memory - : +[](void*, uint64_t, uint32_t*) { return false; }; + : +[](void*, uint64_t, std::span) { return false; }; return runtime; } @@ -635,7 +635,7 @@ bool SrtWalker::EvaluateRawRead(const Inst& inst, uint64_t& result) { } uint32_t word = 0; if (m_runtime.read_memory != nullptr) { - if (!m_runtime.read_memory(m_runtime.userdata, address, &word)) { + if (!m_runtime.read_memory(m_runtime.userdata, address, {&word, 1})) { return false; } } else { diff --git a/src/graphics/shader/recompiler/ir/passes/SrtWalker.h b/src/graphics/shader/recompiler/ir/passes/SrtWalker.h index a1b15c28..4b9ead83 100644 --- a/src/graphics/shader/recompiler/ir/passes/SrtWalker.h +++ b/src/graphics/shader/recompiler/ir/passes/SrtWalker.h @@ -9,7 +9,7 @@ namespace Libs::Graphics::ShaderRecompiler::IR { class Value; -using SrtMemoryReader = bool (*)(void* userdata, uint64_t address, uint32_t* value); +using SrtMemoryReader = bool (*)(void* userdata, uint64_t address, std::span values); struct SrtRuntime { std::span user_data; diff --git a/tests/ResourceMaterializationTests.cpp b/tests/ResourceMaterializationTests.cpp index 9069bf3f..2d27302c 100644 --- a/tests/ResourceMaterializationTests.cpp +++ b/tests/ResourceMaterializationTests.cpp @@ -14,7 +14,7 @@ void Check(bool value, const char *text) { } } -bool RejectSpecializationRead(void *userdata, uint64_t, uint32_t *) { +bool RejectSpecializationRead(void *userdata, uint64_t, std::span) { ++*static_cast(userdata); return false; } diff --git a/tests/ResourceTrackingTests.cpp b/tests/ResourceTrackingTests.cpp index 7755c1b4..2efc8179 100644 --- a/tests/ResourceTrackingTests.cpp +++ b/tests/ResourceTrackingTests.cpp @@ -138,14 +138,16 @@ struct TestMemory { uint32_t fail_after = UINT32_MAX; }; -bool ReadTestMemory(void *userdata, uint64_t address, uint32_t *value) { +bool ReadTestMemory(void *userdata, uint64_t address, std::span values) { auto *memory = static_cast(userdata); - if (memory == nullptr || value == nullptr || address < memory->base || - address - memory->base >= memory->words.size() * sizeof(uint32_t) || + if (memory == nullptr || address < memory->base || + values.size_bytes() > memory->words.size() * sizeof(uint32_t) || + address - memory->base > memory->words.size() * sizeof(uint32_t) - values.size_bytes() || memory->reads >= memory->fail_after) { return false; } - *value = memory->words[(address - memory->base) / sizeof(uint32_t)]; + std::copy_n(memory->words.begin() + (address - memory->base) / sizeof(uint32_t), + values.size(), values.begin()); memory->reads++; return true; } @@ -156,17 +158,29 @@ struct LinearTestMemory { uint64_t fail_address = UINT64_MAX; uint64_t watched_address = UINT64_MAX; uint32_t watched_reads = 0; + size_t watched_dwords = 0; + uint32_t reads = 0; + uint32_t descriptor_reads = 0; }; -bool ReadLinearTestMemory(void *userdata, uint64_t address, uint32_t *value) { +bool ReadLinearTestMemory(void *userdata, uint64_t address, std::span values) { auto *memory = static_cast(userdata); - if (memory == nullptr || value == nullptr || address < memory->base || - address - memory->base >= memory->words.size() * sizeof(uint32_t) || - (address & 3u) != 0u || address == memory->fail_address) { + if (memory == nullptr || address < memory->base || + values.size_bytes() > memory->words.size() * sizeof(uint32_t) || + address - memory->base > memory->words.size() * sizeof(uint32_t) - values.size_bytes() || + (address & 3u) != 0u || + (memory->fail_address >= address && memory->fail_address - address < values.size_bytes())) { return false; } - *value = memory->words[(address - memory->base) / sizeof(uint32_t)]; - if (address == memory->watched_address) ++memory->watched_reads; + std::copy_n(memory->words.begin() + (address - memory->base) / sizeof(uint32_t), + values.size(), values.begin()); + ++memory->reads; + if (values.size() == 8u) ++memory->descriptor_reads; + if (memory->watched_address >= address && + memory->watched_address - address < values.size_bytes()) { + ++memory->watched_reads; + memory->watched_dwords = values.size(); + } return true; } @@ -313,6 +327,22 @@ void TestInvariantIndirectImageMaterialization() { snapshot.images[0].dwords.begin()), "invariant indirect image table did not materialize"); + user_data[5] = 0u; + user_data[6] = 19u; + memory.fail_address = 0x2010u; + memory.watched_address = 0x2000u; + Check(MaterializeResources(resource_plan, runtime, snapshot, specialization) && + memory.watched_dwords == 4u && + std::equal(image_descriptor.begin(), image_descriptor.end(), + snapshot.images[0].dwords.begin()), + "partial scalar-buffer descriptor read crossed bounds instead of zeroing its tail"); + memory.fail_address = 0x2008u; + Check(!MaterializeResources(resource_plan, runtime, snapshot, specialization), + "unreadable memory inside the descriptor prefix was accepted"); + user_data[5] = 16u << 16u; + user_data[6] = 4u; + memory.watched_address = UINT64_MAX; + memory.fail_address = 0x1004u; Check(!MaterializeResources(resource_plan, runtime, snapshot, specialization), @@ -609,7 +639,7 @@ void TestGuardedDirectImageTable() { } }; fill_table(memory, table); - const std::array user_data{0x1000u, 0u}; + std::array user_data{0x1000u, 0u}; SrtRuntime runtime{.user_data = user_data, .read_memory = ReadLinearTestMemory, .userdata = &memory, .read_specialization_memory = ReadLinearTestMemory}; @@ -617,7 +647,8 @@ void TestGuardedDirectImageTable() { ResourceSpecialization specialization; Check(MaterializeResources(plan, runtime, snapshot, specialization) && snapshot.images.size() == 32u && specialization.images.size() == 32u && - snapshot.flattened_srt[specialization.images[0].indirect_mapping_offset] == 32u, + snapshot.flattened_srt[specialization.images[0].indirect_mapping_offset] == 32u && + memory.reads == 34u && memory.descriptor_reads == 32u, "direct table did not retain all 32 reachable descriptors"); memory.words[(table - memory.base) / 4u + 31u * 8u] = 0x987u; Check(MaterializeResources(plan, runtime, snapshot, specialization) && @@ -637,6 +668,20 @@ void TestGuardedDirectImageTable() { Check(!MaterializeResources(plan, runtime, snapshot, specialization) && wrapping.watched_reads == 0u, "direct table wrapped its descriptor address at the 48-bit boundary"); + + LinearTestMemory endpoint; + endpoint.base = (uint64_t{1} << 48u) - 0x1000u; + const auto crossing = (uint64_t{1} << 48u) - 16u; + endpoint.words[0] = static_cast(crossing - 344u); + endpoint.words[1] = static_cast((crossing - 344u) >> 32u); + fill_table(endpoint, crossing); + endpoint.watched_address = crossing; + user_data = {static_cast(endpoint.base), + static_cast(endpoint.base >> 32u)}; + runtime.userdata = &endpoint; + Check(!MaterializeResources(plan, runtime, snapshot, specialization) && + endpoint.watched_reads == 0u, + "batched descriptor read crossed the 48-bit endpoint"); } void TestUniformScalarBufferImage() { @@ -779,12 +824,12 @@ void TestComputeBufferFill() { std::array userdata{0x200000u, 4u << 16, 0x4000u, 0x14204u}; ResourceSnapshot snapshot; ResourceSpecialization specialization; - const auto Read = +[](void *data, uint64_t address, uint32_t *word) { + const auto Read = +[](void *data, uint64_t address, std::span words) { auto &memory = *static_cast(data); - if (address != memory.base) + if (address != memory.base || words.size() != 1u) return false; ++memory.reads; - *word = memory.words[0]; + words[0] = memory.words[0]; return true; }; Check(MaterializeResources( @@ -2051,7 +2096,7 @@ void TestConditionalIndirectImageMaterialization() { uint32_t reads = 0; const SrtRuntime runtime{ .user_data = user_data, .userdata = &reads, - .read_specialization_memory = [](void *data, uint64_t, uint32_t *) { + .read_specialization_memory = [](void *data, uint64_t, std::span) { ++*static_cast(data); return false; }}; diff --git a/tests/ScalarProvenanceTests.cpp b/tests/ScalarProvenanceTests.cpp index f91af58a..3dc1e3bf 100644 --- a/tests/ScalarProvenanceTests.cpp +++ b/tests/ScalarProvenanceTests.cpp @@ -79,13 +79,14 @@ struct TestMemory { uint32_t reads = 0; }; -bool ReadMemory(void *userdata, uint64_t address, uint32_t *value) { +bool ReadMemory(void *userdata, uint64_t address, std::span values) { auto &memory = *static_cast(userdata); - const auto it = memory.words.find(address); - if (it == memory.words.end()) { - return false; + for (auto &value : values) { + const auto it = memory.words.find(address); + if (it == memory.words.end()) return false; + value = it->second; + address += sizeof(uint32_t); } - *value = it->second; memory.reads++; return true; } diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 79ca42ed..df382d81 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -1240,13 +1240,13 @@ std::array MakeNativeUserData(const std::array *source) { return data; } -bool ReadTestMemory(void *userdata, uint64_t address, u32 *value) { +bool ReadTestMemory(void *userdata, uint64_t address, std::span values) { const auto *data = static_cast *>(userdata); - if (data == nullptr || value == nullptr || address % 4u != 0 || - address / 4u >= data->size()) { + if (data == nullptr || address % 4u != 0 || values.size() > data->size() || + address / 4u > data->size() - values.size()) { return false; } - *value = (*data)[address / 4u]; + std::copy_n(data->begin() + address / 4u, values.size(), values.begin()); return true; } @@ -12122,9 +12122,9 @@ public: Require(name, "stencil fill materialization", ShaderRecompiler::IR::MaterializeResources(stencil_plan, {.user_data = stencil_userdata, .userdata = &stencil_byte, - .read_specialization_memory = +[](void *data, uint64_t address, uint32_t *word) { - if (address != reinterpret_cast(data)) return false; - *word = *static_cast(data); + .read_specialization_memory = +[](void *data, uint64_t address, std::span words) { + if (address != reinterpret_cast(data) || words.size() != 1u) return false; + words[0] = *static_cast(data); return true; }}, stencil_snapshot, stencil_specialization), "captured stencil shader could not resolve its clear byte"); @@ -28001,9 +28001,9 @@ void CheckPs5GameExampleImageClearRuntimeShape() { .shader_base = reinterpret_cast(code.data()), .userdata = &scalar_clear, .read_specialization_memory = clean_scalar - ? +[](void *data, uint64_t address, uint32_t *word) { - if (address != reinterpret_cast(data)) return false; - *word = *static_cast(data); + ? +[](void *data, uint64_t address, std::span words) { + if (address != reinterpret_cast(data) || words.size() != 1u) return false; + words[0] = *static_cast(data); return true; } : nullptr, }; diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 969a1d89..d80e2c19 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -107,11 +107,11 @@ ShaderRecompiler::CompileOptions MakeCompileOptions(ShaderType stage) { return options; } -bool ReadHostTestMemory(void *, uint64_t address, uint32_t *value) { - if (address == 0 || value == nullptr) { +bool ReadHostTestMemory(void *, uint64_t address, std::span values) { + if (address == 0 || values.empty()) { return false; } - std::memcpy(value, reinterpret_cast(address), sizeof(*value)); + std::memcpy(values.data(), reinterpret_cast(address), values.size_bytes()); return true; } @@ -1425,11 +1425,8 @@ void SetImageTestFormat(std::array *data, uint32_t srsrc, (*data)[format_dword] = static_cast(format) << 20u; } -bool ReadZeroTestMemory(void *, uint64_t, uint32_t *value) { - if (value == nullptr) { - return false; - } - *value = 0; +bool ReadZeroTestMemory(void *, uint64_t, std::span values) { + std::ranges::fill(values, 0u); return true; } @@ -12101,11 +12098,11 @@ void CheckFlattenedReadSlots(const ShaderRecompiler::IR::Program &program, } } -bool ReadSrtHostDword(void *, uint64_t address, uint32_t *value) { - if (address == 0 || value == nullptr) { +bool ReadSrtHostDword(void *, uint64_t address, std::span values) { + if (address == 0 || values.empty()) { return false; } - std::memcpy(value, reinterpret_cast(address), sizeof(*value)); + std::memcpy(values.data(), reinterpret_cast(address), values.size_bytes()); return true; } @@ -12114,18 +12111,17 @@ struct SrtHostRange { size_t count; }; -bool ReadSrtHostRangeDword(void *userdata, uint64_t address, uint32_t *value) { +bool ReadSrtHostRangeDword(void *userdata, uint64_t address, std::span values) { const auto *range = static_cast(userdata); - if (range == nullptr || range->data == nullptr || range->count == 0 || - value == nullptr) { + if (range == nullptr || range->data == nullptr || range->count == 0 || values.empty()) { return false; } const auto base = reinterpret_cast(range->data); const auto size = range->count * sizeof(uint32_t); - if (address < base || address - base > size - sizeof(uint32_t)) { + if (address < base || values.size_bytes() > size || address - base > size - values.size_bytes()) { return false; } - std::memcpy(value, reinterpret_cast(address), sizeof(*value)); + std::memcpy(values.data(), reinterpret_cast(address), values.size_bytes()); return true; }