Read image descriptors in one bounded span

This commit is contained in:
nmzik
2026-09-23 11:42:59 +04:00
parent 66f1a82365
commit d30dca78be
9 changed files with 112 additions and 78 deletions
+1 -1
View File
@@ -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<uint32_t>) {
++*static_cast<uint32_t *>(userdata);
return false;
}
+61 -16
View File
@@ -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<uint32_t> values) {
auto *memory = static_cast<TestMemory *>(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<uint32_t> values) {
auto *memory = static_cast<LinearTestMemory *>(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<uint32_t, 2> user_data{0x1000u, 0u};
std::array<uint32_t, 2> 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<uint32_t>(crossing - 344u);
endpoint.words[1] = static_cast<uint32_t>((crossing - 344u) >> 32u);
fill_table(endpoint, crossing);
endpoint.watched_address = crossing;
user_data = {static_cast<uint32_t>(endpoint.base),
static_cast<uint32_t>(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<uint32_t, 4> 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<uint32_t> words) {
auto &memory = *static_cast<TestMemory *>(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<uint32_t>) {
++*static_cast<uint32_t *>(data);
return false;
}};
+6 -5
View File
@@ -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<uint32_t> values) {
auto &memory = *static_cast<TestMemory *>(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;
}
+10 -10
View File
@@ -1240,13 +1240,13 @@ std::array<u32, 64> MakeNativeUserData(const std::array<u32, 64> *source) {
return data;
}
bool ReadTestMemory(void *userdata, uint64_t address, u32 *value) {
bool ReadTestMemory(void *userdata, uint64_t address, std::span<u32> values) {
const auto *data = static_cast<const std::vector<u32> *>(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<uint64_t>(data)) return false;
*word = *static_cast<uint32_t *>(data);
.read_specialization_memory = +[](void *data, uint64_t address, std::span<uint32_t> words) {
if (address != reinterpret_cast<uint64_t>(data) || words.size() != 1u) return false;
words[0] = *static_cast<uint32_t *>(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<uint64_t>(code.data()),
.userdata = &scalar_clear,
.read_specialization_memory = clean_scalar
? +[](void *data, uint64_t address, uint32_t *word) {
if (address != reinterpret_cast<uint64_t>(data)) return false;
*word = *static_cast<uint32_t *>(data);
? +[](void *data, uint64_t address, std::span<uint32_t> words) {
if (address != reinterpret_cast<uint64_t>(data) || words.size() != 1u) return false;
words[0] = *static_cast<uint32_t *>(data);
return true;
} : nullptr,
};
+12 -16
View File
@@ -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<uint32_t> values) {
if (address == 0 || values.empty()) {
return false;
}
std::memcpy(value, reinterpret_cast<const void *>(address), sizeof(*value));
std::memcpy(values.data(), reinterpret_cast<const void *>(address), values.size_bytes());
return true;
}
@@ -1425,11 +1425,8 @@ void SetImageTestFormat(std::array<uint32_t, 64> *data, uint32_t srsrc,
(*data)[format_dword] = static_cast<uint32_t>(format) << 20u;
}
bool ReadZeroTestMemory(void *, uint64_t, uint32_t *value) {
if (value == nullptr) {
return false;
}
*value = 0;
bool ReadZeroTestMemory(void *, uint64_t, std::span<uint32_t> 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<uint32_t> values) {
if (address == 0 || values.empty()) {
return false;
}
std::memcpy(value, reinterpret_cast<const void *>(address), sizeof(*value));
std::memcpy(values.data(), reinterpret_cast<const void *>(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<uint32_t> values) {
const auto *range = static_cast<const SrtHostRange *>(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<uint64_t>(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<const void *>(address), sizeof(*value));
std::memcpy(values.data(), reinterpret_cast<const void *>(address), values.size_bytes());
return true;
}