shader: specialize bounded pointer and packed image tables

This commit is contained in:
nmzik
2026-10-13 01:12:19 +02:00
parent c9ec82e32e
commit 1fa79d5cac
5 changed files with 219 additions and 24 deletions
@@ -604,8 +604,20 @@ void FoldInstruction(Block& block, Block::iterator instruction,
});
return;
case ValueOpcode::BitwiseAnd32:
if (!FoldU32(inst, [](uint32_t a, uint32_t b) { return a & b; })) {
ReplaceBinaryIdentity(inst, Type::U32, 0xffffffffu);
if (FoldU32(inst, [](uint32_t a, uint32_t b) { return a & b; }) ||
ReplaceBinaryIdentity(inst, Type::U32, UINT32_MAX)) return;
for (uint32_t arm = 0; arm < 2u; ++arm) {
const auto mask = Arg(inst, arm);
const auto* shifted = Arg(inst, arm ^ 1u).TryInstruction();
if (!IsImmediate(mask, Type::U32) || shifted == nullptr ||
shifted->GetOpcode() != ValueOpcode::ShiftLeftLogical32 || !HasOneUse(*shifted)) continue;
const auto shift = Arg(*shifted, 1);
if (!IsImmediate(shift, Type::U32) || shift.U32() >= 32u) continue;
const auto masked = worklist.Prepend(block, instruction, ValueOpcode::BitwiseAnd32,
{Arg(*shifted, 0), Value(mask.U32() >> shift.U32())});
Replace(inst, Value(&*worklist.Prepend(block, instruction, ValueOpcode::ShiftLeftLogical32,
{Value(&*masked), shift})));
return;
}
return;
case ValueOpcode::BitwiseAnd64:
@@ -282,14 +282,14 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
}
return true;
};
const auto read_keys = [&](const ShaderBufferResource& material, uint64_t first,
const auto read_keys = [&](uint64_t base, uint64_t size, uint64_t first,
uint64_t step, uint64_t count) {
keys.resize(count);
if (step == 4u) {
return ReadScalarTable(material.Base48(), material.GetSize(), first, runtime, keys);
return ReadScalarTable(base, size, first, runtime, keys);
}
for (auto& key: keys) {
if (!ReadScalarTable(material.Base48(), material.GetSize(), first, runtime, {&key, 1}))
if (!ReadScalarTable(base, size, first, runtime, {&key, 1}))
return false;
first += step;
}
@@ -468,9 +468,17 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
if (selected[key]) keys.push_back(key);
} else if (!indirect.selector_first.IsEmpty()) {
ShaderBufferResource material;
uint64_t base = 0, size = UINT64_MAX;
uint32_t first = 0, count = 0;
if (!DecodeBufferDescriptor(material_value, material) ||
!clean.Evaluate(indirect.selector_first, first) ||
if (material_value.dword_count == 2u) {
if (selector->stride == 0u) return false;
base = (uint64_t {material_value.dwords[1]} << 32u) | material_value.dwords[0];
} else {
if (!DecodeBufferDescriptor(material_value, material)) return false;
base = material.Base48();
size = material.GetSize();
}
if (!clean.Evaluate(indirect.selector_first, first) ||
!evaluate_count(count) || count > MaxIndirectDescriptorProbes)
return false;
// SMEM uses its instruction's byte stride; V# stride only supplies its bounds.
@@ -481,9 +489,9 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
uint64_t {first} + count > material.NumRecords())) return false;
const auto end = count != 0u
? (uint64_t {first} + count - 1u) * step + selector->offset + 4u : 0u;
if (end > uint64_t {UINT32_MAX} + 1u || end > material.GetSize())
if (end > uint64_t {UINT32_MAX} + 1u || end > size)
return false;
if (!read_keys(material, uint64_t {first} * step + selector->offset, step, count))
if (!read_keys(base, size, uint64_t {first} * step + selector->offset, step, count))
return false;
} else if (!indirect.selector_mask.IsEmpty()) {
uint32_t mask = 0;
@@ -521,7 +529,7 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
const auto count = std::min(domain_count,
available < 4u ? 0u : (available - 4u) / selector->stride + 1u);
if (count > MaxIndirectDescriptorProbes ||
!read_keys(material, selector->offset, selector->stride, count)) return false;
!read_keys(material.Base48(), material.GetSize(), selector->offset, selector->stride, count)) return false;
if (count < domain_count) keys.push_back(0u); // An out-of-bounds scalar read returns zero.
}
if (selector != nullptr && selector->byte_ranges.empty()) {
@@ -1933,7 +1933,7 @@ private:
bool MatchScalarMaterialKey(Value key, DescriptorSource::IndirectDescriptor& indirect,
DescriptorSource& material_source) {
const auto* read = key.Resolve().TryInstruction();
if (read == nullptr || read->GetOpcode() != ValueOpcode::ReadConstBuffer) return false;
if (read == nullptr) return false;
uint32_t memory_index = 0;
const auto* memory = ScalarReadMemory(*read, memory_index);
Value index;
@@ -1941,8 +1941,8 @@ private:
DescriptorSource count_source;
uint32_t row_stride = 0;
uint32_t offset = 0, stride = 0;
if (memory == nullptr || memory->kind != ResourceKind::ScalarBuffer ||
memory->offset > INT32_MAX || !MemoryIndexBelongsTo(memory_index, *read)) return false;
if (memory == nullptr || memory->offset > INT32_MAX ||
!MemoryIndexBelongsTo(memory_index, *read)) return false;
if (MatchTableOffset(read->Arg(1), index, offset, stride) &&
((offset | stride | memory->offset) & 3u) == 0u &&
uint64_t {offset} + memory->offset <= UINT32_MAX) {
@@ -1951,6 +1951,8 @@ private:
count = bound->Arg(1);
}
}
// Raw pointers have no V# bounds for enumerating an unbounded selector domain.
if (memory->kind == ResourceKind::ScalarAddress && count.IsEmpty()) return false;
const bool rows = count.IsEmpty() && memory->offset == 0u &&
MatchCountedRows(*read, count_source, row_stride);
if (rows) {
@@ -2476,7 +2478,9 @@ private:
if (indirect.key_count.IsEmpty() &&
!MatchUniformizedMaterialKey(key, handle, indirect, material_source) &&
!MatchReversedImageRange(key, indirect)) {
return false;
const auto maximum = SelectorMaximum(key);
if (maximum >= MaxIndirectDescriptorProbes) return false;
indirect.key_count = Value(maximum + 1u);
}
if ((table_offset & 3u) != 0u) return false;
} else {