shader: report descriptor materialization rejection context

This commit is contained in:
nmzik
2026-10-15 15:50:21 +02:00
parent e291589177
commit df92a08990
2 changed files with 61 additions and 21 deletions
@@ -549,26 +549,44 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
}; };
uint64_t table_base = 0; uint64_t table_base = 0;
uint64_t table_size = UINT64_MAX; // Scalar addresses have no buffer descriptor bounds. uint64_t table_size = UINT64_MAX; // Scalar addresses have no buffer descriptor bounds.
DescriptorValue table_value;
ShaderBufferResource table;
const auto failed = [&](std::string_view reason, std::string_view details = {}) {
const auto* pointer = indirect.pointer_table ? &*indirect.pointer_table : nullptr;
return SpecializationFail(fmt::format(
"indirect {} resource={} source={} table_source={} words={:08x},{:08x},{:08x},{:08x} "
"base={:#x} size={} type={} runtime_stride={} records={} swizzle={} addtid={} oob={} "
"maximum={} offset={} immediate={} record_bytes={} child_count={} child_stride={} "
"child_offset={} indexed_pointer={} candidates={} capacity={} {}",
reason, resource_index, source_index, indirect.table_source,
table_value.dwords[0], table_value.dwords[1], table_value.dwords[2], table_value.dwords[3],
table_base, table_size, table.Type(), table.Stride(), table.NumRecords(),
table.SwizzleEnabled(), table.AddTid(), table.OutOfBounds(),
indirect.table_maximum ? fmt::format("{}", *indirect.table_maximum) : "none",
indirect.table_offset, indirect.table_immediate, indirect.table_record_bytes,
pointer != nullptr ? pointer->count : 1u, pointer != nullptr ? pointer->stride : 0u,
pointer != nullptr ? pointer->offset : 0u, pointer != nullptr && pointer->indexed,
descriptors.size(), maximum_resources, details));
};
const auto read_failed = [&](std::string_view reason, uint32_t row, uint32_t child,
uint64_t base, uint64_t offset, size_t bytes) {
return failed(reason, fmt::format("row={} child={} read_base={:#x} read_offset={:#x} "
"read_address={:#x} requested_bytes={}", row, child, base, offset,
(base & AddressMask & ~uint64_t {3}) + (offset & ~uint64_t {3}), bytes));
};
const auto normalization_failed = [&](uint64_t offset, uint32_t key, uint64_t step, const auto normalization_failed = [&](uint64_t offset, uint32_t key, uint64_t step,
std::string_view key_kind) { std::string_view key_kind) {
return SpecializationFail(fmt::format( return failed("candidate", fmt::format("candidate_offset={:#x} {}={} stride={} step={}",
"indirect candidate resource={} table_source={} table_base={:#x} table_size={} " offset & ~uint64_t {3}, key_kind, key, indirect.table_stride, step));
"candidate_offset={:#x} {}={} stride={} step={} maximum={} offset={} immediate={}",
resource_index, indirect.table_source, table_base & AddressMask & ~uint64_t {3}, table_size,
offset & ~uint64_t {3}, key_kind, key, indirect.table_stride, step,
indirect.table_maximum ? fmt::format("{}", *indirect.table_maximum) : "none",
indirect.table_offset, indirect.table_immediate));
}; };
if (sources.empty()) { if (sources.empty()) {
if (selector == nullptr) keys.clear(); if (selector == nullptr) keys.clear();
DescriptorValue material_value; DescriptorValue material_value;
DescriptorValue table_value;
if ((selector != nullptr && !selector_keys->resolved && if ((selector != nullptr && !selector_keys->resolved &&
!clean.EvaluateDescriptor(selector->source, material_value)) || !clean.EvaluateDescriptor(selector->source, material_value)) ||
!clean.EvaluateDescriptor(indirect.table_source, table_value)) { !clean.EvaluateDescriptor(indirect.table_source, table_value)) {
return false; return false;
} }
ShaderBufferResource table;
if (table_value.dword_count == 2u) { if (table_value.dword_count == 2u) {
table_base = (static_cast<uint64_t>(table_value.dwords[1]) << 32u) | table_value.dwords[0]; table_base = (static_cast<uint64_t>(table_value.dwords[1]) << 32u) | table_value.dwords[0];
} else if (DecodeBufferDescriptor(table_value, table)) { } else if (DecodeBufferDescriptor(table_value, table)) {
@@ -587,7 +605,7 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
(table.Type() != 0u || table.SwizzleEnabled() || table.AddTid() || (table.Type() != 0u || table.SwizzleEnabled() || table.AddTid() ||
table.OutOfBounds() != 0u || (table_base & 3u) != 0u || table.OutOfBounds() != 0u || (table_base & 3u) != 0u ||
(table.Stride() & 3u) != 0u || table.Stride() < indirect.table_record_bytes))) (table.Stride() & 3u) != 0u || table.Stride() < indirect.table_record_bytes)))
return false; return failed("table geometry");
const auto* pointer = indirect.pointer_table ? &*indirect.pointer_table : nullptr; const auto* pointer = indirect.pointer_table ? &*indirect.pointer_table : nullptr;
const bool indexed_pointer = pointer != nullptr && pointer->indexed; const bool indexed_pointer = pointer != nullptr && pointer->indexed;
const auto child_count = pointer != nullptr ? pointer->count : 1u; const auto child_count = pointer != nullptr ? pointer->count : 1u;
@@ -595,7 +613,7 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
(!indirect.table_maximum || table.Type() != 0u || table.SwizzleEnabled() || table.AddTid() || (!indirect.table_maximum || table.Type() != 0u || table.SwizzleEnabled() || table.AddTid() ||
table.OutOfBounds() != 0u || (table_base & 3u) != 0u || (table.Stride() & 3u) != 0u || table.OutOfBounds() != 0u || (table_base & 3u) != 0u || (table.Stride() & 3u) != 0u ||
uint64_t {indirect.table_immediate} + 8u > table.Stride() || uint64_t {indirect.table_immediate} + 8u > table.Stride() ||
*indirect.table_maximum >= table.NumRecords())) return false; *indirect.table_maximum >= table.NumRecords())) return failed("indexed pointer geometry");
const bool affine = indirect.table_stride != 0u || indexed_pointer; const bool affine = indirect.table_stride != 0u || indexed_pointer;
const bool image_heap = dword_count == 8u && pointer == nullptr; const bool image_heap = dword_count == 8u && pointer == nullptr;
const uint64_t step = indexed_pointer ? table.Stride() const uint64_t step = indexed_pointer ? table.Stride()
@@ -611,12 +629,13 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
? std::min((table_size - first - 4u) / step + 1u, domain) ? std::min((table_size - first - 4u) / step + 1u, domain)
: 0u; : 0u;
if (child_count == 0u || (!image_heap && count * child_count > MaxIndirectDescriptorProbes)) if (child_count == 0u || (!image_heap && count * child_count > MaxIndirectDescriptorProbes))
return false; return failed("candidate count", fmt::format("rows={} limit={}", count, MaxIndirectDescriptorProbes));
// Affine SMEM offsets wrap in U32 before adding the aligned immediate. // Affine SMEM offsets wrap in U32 before adding the aligned immediate.
// Image heaps retain one bulk snapshot and their absolute byte-offset mapping. // Image heaps retain one bulk snapshot and their absolute byte-offset mapping.
if (image_heap) { if (image_heap) {
keys.resize(table_size / 4u); keys.resize(table_size / 4u);
if (!ReadScalarTable(table_base, table_size, 0u, runtime, keys)) return false; if (!ReadScalarTable(table_base, table_size, 0u, runtime, keys))
return read_failed("image heap read", 0u, 0u, table_base, 0u, keys.size() * sizeof(uint32_t));
} }
const auto mapping_offset = snapshot.flattened_srt.size(); const auto mapping_offset = snapshot.flattened_srt.size();
descriptors[resource_index] = {.dword_count = dword_count}; descriptors[resource_index] = {.dword_count = dword_count};
@@ -626,7 +645,8 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
uint64_t pointer_base = 0u; uint64_t pointer_base = 0u;
if (pointer != nullptr) { if (pointer != nullptr) {
std::array<uint32_t, 2> address {}; std::array<uint32_t, 2> address {};
if (!ReadScalarTable(table_base, table_size, table_offset, runtime, address)) return false; if (!ReadScalarTable(table_base, table_size, table_offset, runtime, address))
return read_failed("parent pointer read", word, 0u, table_base, table_offset, sizeof(address));
pointer_base = (uint64_t {address[1]} << 32u) | address[0]; pointer_base = (uint64_t {address[1]} << 32u) | address[0];
} }
for (uint32_t child = 0; child < child_count; ++child) { for (uint32_t child = 0; child < child_count; ++child) {
@@ -634,7 +654,9 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
if (pointer != nullptr) { if (pointer != nullptr) {
if (!ReadScalarTable(pointer_base, UINT64_MAX, if (!ReadScalarTable(pointer_base, UINT64_MAX,
uint64_t {child} * pointer->stride + pointer->offset, runtime, uint64_t {child} * pointer->stride + pointer->offset, runtime,
std::span(candidate.dwords).first(read_dwords))) return false; std::span(candidate.dwords).first(read_dwords)))
return read_failed("child descriptor read", word, child, pointer_base,
uint64_t {child} * pointer->stride + pointer->offset, descriptor_bytes);
} else if (!image_heap) { } else if (!image_heap) {
if (!ReadScalarTable(table_base, table_size, table_offset, runtime, if (!ReadScalarTable(table_base, table_size, table_offset, runtime,
std::span(candidate.dwords).first(read_dwords))) return false; std::span(candidate.dwords).first(read_dwords))) return false;
@@ -708,7 +730,7 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
(indirect.table_immediate & ~3u); (indirect.table_immediate & ~3u);
if (!ReadScalarTable(table_base, table_size, table_offset, runtime, if (!ReadScalarTable(table_base, table_size, table_offset, runtime,
std::span(candidate.dwords).first(read_dwords))) { std::span(candidate.dwords).first(read_dwords))) {
return false; return read_failed("table descriptor read", key, 0u, table_base, table_offset, descriptor_bytes);
} }
} else if (!clean.EvaluateDescriptor(sources[entry], candidate)) { } else if (!clean.EvaluateDescriptor(sources[entry], candidate)) {
return false; return false;
@@ -1469,7 +1491,8 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
SrtWalker walker(program, observed, SrtWalker walker(program, observed,
capture_reads || program.requires_specialization_memory ? &clean : nullptr); capture_reads || program.requires_specialization_memory ? &clean : nullptr);
if (!walker.RefreshFlatBuffer(snapshot.flattened_srt)) { if (!walker.RefreshFlatBuffer(snapshot.flattened_srt)) {
return false; return SpecializationFail(fmt::format("SRT refresh complete={} reads={} user_data={}",
program.srt_plan_complete, program.srt_reads.size(), runtime.user_data.size()));
} }
const auto active = std::span<const uint8_t>(program.active_sources); const auto active = std::span<const uint8_t>(program.active_sources);
std::array<uint32_t, 3> workgroup_counts {}; std::array<uint32_t, 3> workgroup_counts {};
@@ -1501,12 +1524,18 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
return false; return false;
} }
if (active.empty() || active[source]) { if (active.empty() || active[source]) {
return (capture_reads && written ? clean : walker).EvaluateDescriptor(source, value); return (capture_reads && written ? clean : walker).EvaluateDescriptor(source, value) ||
SpecializationFail(fmt::format("descriptor evaluation source={}", source));
} }
value = {}; value = {};
value.dword_count = program.descriptor_sources[source].dword_count; value.dword_count = program.descriptor_sources[source].dword_count;
return true; return true;
}; };
const auto resource_failed = [&](std::string_view kind, uint32_t resource, uint32_t source,
size_t candidates, uint32_t capacity) {
return SpecializationFail(fmt::format("indirect {} resource={} source={} candidates={} capacity={}",
kind, resource, source, candidates, capacity));
};
snapshot.buffers.resize(program.info.buffers.size()); snapshot.buffers.resize(program.info.buffers.size());
specialization.buffers.assign(program.info.buffers.size(), {}); specialization.buffers.assign(program.info.buffers.size(), {});
for (uint32_t i = 0; i < snapshot.buffers.size(); ++i) { for (uint32_t i = 0; i < snapshot.buffers.size(); ++i) {
@@ -1527,7 +1556,7 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
return NormalizeIndirectBuffer( return NormalizeIndirectBuffer(
value, source->indirect_descriptor->indexed, base); value, source->indirect_descriptor->indexed, base);
})) }))
return false; return resource_failed("buffer", i, base.source, snapshot.buffers.size(), UINT32_MAX);
} }
} else if (!evaluate(base.source, snapshot.buffers[i], base.written)) { } else if (!evaluate(base.source, snapshot.buffers[i], base.written)) {
return false; return false;
@@ -1593,7 +1622,7 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
value.dwords.fill(0u); value.dwords.fill(0u);
return true; return true;
})) { })) {
return false; return resource_failed("image", i, image.source, snapshot.images.size(), UINT32_MAX);
} }
} else { } else {
if (!evaluate(image.source, snapshot.images[i], image.written)) { if (!evaluate(image.source, snapshot.images[i], image.written)) {
@@ -1620,7 +1649,8 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
if ((active.empty() || active[source]) && !MaterializeIndirectDescriptor( if ((active.empty() || active[source]) && !MaterializeIndirectDescriptor(
program, source, i, 4u, 4u, observed, clean, snapshot, snapshot.samplers, program, source, i, 4u, 4u, observed, clean, snapshot, snapshot.samplers,
specialization.samplers, ShaderInfo::MaxSamplers, specialization.samplers, ShaderInfo::MaxSamplers,
[](DescriptorValue&) { return true; })) return false; [](DescriptorValue&) { return true; }))
return resource_failed("sampler", i, source, snapshot.samplers.size(), ShaderInfo::MaxSamplers);
} else { } else {
if (indirect != nullptr) source = indirect->sources[0]; if (indirect != nullptr) source = indirect->sources[0];
if (!evaluate(source, snapshot.samplers[i])) return false; if (!evaluate(source, snapshot.samplers[i])) return false;
+10
View File
@@ -950,6 +950,9 @@ void TestNestedPointerImageTable() {
memory.fail_address = objects + 0x3d0u; memory.fail_address = objects + 0x3d0u;
Check(!MaterializeResources(plan, runtime, snapshot, specialization), Check(!MaterializeResources(plan, runtime, snapshot, specialization),
"failed nested image pointee read was treated as a null descriptor"); "failed nested image pointee read was treated as a null descriptor");
memory.fail_address = objects + 0x3f8u;
Check(!MaterializeResources(plan, runtime, snapshot, specialization),
"failed nested sampler pointee read was treated as a null descriptor");
memory.fail_address = 0x1800u; memory.fail_address = 0x1800u;
*at(table) = 0u; *at(table) = 0u;
Check(!MaterializeResources(plan, runtime, snapshot, specialization), Check(!MaterializeResources(plan, runtime, snapshot, specialization),
@@ -1154,6 +1157,10 @@ void TestIndexedPointerImageTable() {
Check(!MaterializeResources(plan, runtime, snapshot, specialization) && memory.reads == reads, Check(!MaterializeResources(plan, runtime, snapshot, specialization) && memory.reads == reads,
"incomplete indexed parent domain became null images or performed partial candidate reads"); "incomplete indexed parent domain became null images or performed partial candidate reads");
} }
user_data[2] = UINT32_MAX;
const auto oversized_reads = memory.reads;
Check(!MaterializeResources(plan, runtime, snapshot, specialization) && memory.reads == oversized_reads,
"oversized indexed parent table performed candidate reads");
user_data[2] = 2u; user_data[2] = 2u;
for (const uint32_t bad_stride : {0u, 4u, 161u}) { for (const uint32_t bad_stride : {0u, 4u, 161u}) {
user_data[1] = 1u | (bad_stride << 16u); user_data[1] = 1u | (bad_stride << 16u);
@@ -1169,6 +1176,9 @@ void TestIndexedPointerImageTable() {
"indexed pointer table accepted unsupported RDNA2 geometry before reading candidates"); "indexed pointer table accepted unsupported RDNA2 geometry before reading candidates");
user_data[word] &= ~(1u << bit); user_data[word] &= ~(1u << bit);
} }
memory.fail_address = high + table + stride;
Check(!MaterializeResources(plan, runtime, snapshot, specialization),
"unreadable indexed parent pointer was treated as a null image");
memory.fail_address = high + objects + 2u * 368u + 64u; memory.fail_address = high + objects + 2u * 368u + 64u;
Check(!MaterializeResources(plan, runtime, snapshot, specialization), Check(!MaterializeResources(plan, runtime, snapshot, specialization),
"unreadable nested child was silently converted to a null image"); "unreadable nested child was silently converted to a null image");