shader: share runtime value checks

This commit is contained in:
nmzik
2026-09-05 13:16:54 +04:00
parent a379304362
commit ebe0979563
5 changed files with 159 additions and 77 deletions
@@ -134,16 +134,29 @@ bool IsRuntimeUniformOp(ValueOpcode op) {
class RuntimeValidator {
public:
explicit RuntimeValidator(const ResourcePlan& program, Value active_mask = {})
: m_program(program), m_active_mask(active_mask.Resolve()) {}
explicit RuntimeValidator(const ResourcePlan& program, RuntimeValueType type)
: m_program(program), m_type(type) {}
bool Run(Value value) { return Validate(value); }
private:
bool Validate(Value value) {
value = value.Resolve();
bool ValidateArguments(const Inst& inst, bool require_uniform) {
for (size_t index = 0; index < inst.NumArgs(); index++) {
if (!Validate(inst.Arg(index), require_uniform)) return false;
}
return true;
}
bool Validate(Value value, bool require_uniform = true) {
value = value.Resolve();
// Host floating-point evaluation does not model shader rounding/denormal modes.
if (m_type == RuntimeValueType::Integer &&
TypesOverlap(value.GetType(), Type::F16 | Type::F32 | Type::F32x2)) {
return false;
}
const auto* inst = value.TryInstruction();
if (inst == nullptr) {
if (!require_uniform) return true;
switch (value.GetType()) {
case Type::U1:
case Type::U8:
@@ -155,15 +168,37 @@ private:
}
}
if (!m_visiting.insert(inst).second) {
return false;
return !require_uniform;
}
const auto finish = [&](bool valid) {
m_visiting.erase(inst);
return valid;
};
const auto op = inst->GetOpcode();
if (op == ValueOpcode::ReadConst) {
const auto slot = inst->NumArgs() == 2 ? inst->Arg(1).Resolve() : Value {};
if (inst->NumArgs() != 2 || inst->Arg(0).Resolve().TryInstruction() == nullptr ||
inst->Arg(0).Resolve().TryInstruction()->GetOpcode() !=
ValueOpcode::GetSrtResource ||
!slot.IsImmediate() || slot.GetType() != Type::U32 ||
slot.U32() >= m_program.srt_reads.size()) {
return finish(false);
}
if (m_type == RuntimeValueType::Integer) {
const auto active_mask = m_active_mask;
m_active_mask = {};
const bool valid = Validate(m_program.srt_reads[slot.U32()].value);
m_active_mask = active_mask;
if (!valid) return finish(false);
}
}
if (!require_uniform) return finish(ValidateArguments(*inst, false));
if (!m_active_mask.IsEmpty() && IsRuntimeSelect(op) && inst->NumArgs() == 3 &&
inst->Arg(0).Resolve() == m_active_mask) {
// Empty EXEC reads lane zero, so ignored operands still require integer types.
if (m_type == RuntimeValueType::Integer && !Validate(inst->Arg(2), false)) {
return finish(false);
}
return finish(Validate(inst->Arg(1)));
}
if (op == ValueOpcode::UndefU1 || op == ValueOpcode::UndefU8 ||
@@ -189,6 +224,9 @@ private:
return finish(true);
}
if (op == ValueOpcode::Phi) {
if (m_type == RuntimeValueType::Integer && !ValidateArguments(*inst, false)) {
return finish(false);
}
const auto invariant = ResolveInvariantPhi(m_program, value);
if (invariant.IsEmpty()) {
return finish(false);
@@ -200,7 +238,14 @@ private:
inst->Arg(1).GetType() != Type::U1) {
return finish(false);
}
return finish(RuntimeValidator(m_program, inst->Arg(1)).Run(inst->Arg(0)));
if (m_type == RuntimeValueType::Integer && !Validate(inst->Arg(1), false)) {
return finish(false);
}
const auto active_mask = m_active_mask;
m_active_mask = inst->Arg(1).Resolve();
const bool valid = Validate(inst->Arg(0));
m_active_mask = active_mask;
return finish(valid);
}
if (op == ValueOpcode::GetSrtResource) {
if (inst->NumArgs() != 0) {
@@ -208,16 +253,7 @@ private:
}
return finish(true);
}
if (op == ValueOpcode::ReadConst) {
const auto slot = inst->NumArgs() == 2 ? inst->Arg(1).Resolve() : Value {};
if (inst->NumArgs() != 2 || inst->Arg(0).Resolve().TryInstruction() == nullptr ||
inst->Arg(0).Resolve().TryInstruction()->GetOpcode() !=
ValueOpcode::GetSrtResource ||
!slot.IsImmediate() || slot.GetType() != Type::U32 ||
slot.U32() >= m_program.srt_reads.size()) {
return finish(false);
}
} else if (op == ValueOpcode::LoadAddressU32 || op == ValueOpcode::ReadConstBuffer) {
if (op == ValueOpcode::LoadAddressU32 || op == ValueOpcode::ReadConstBuffer) {
const auto expected = op == ValueOpcode::LoadAddressU32
? ValueOpcode::GetAddressResource
: ValueOpcode::GetBufferResource;
@@ -243,10 +279,12 @@ private:
}
if (op == ValueOpcode::GetBufferResource || op == ValueOpcode::GetImageResource ||
op == ValueOpcode::GetSamplerResource || op == ValueOpcode::GetAddressResource) {
const size_t expected = op == ValueOpcode::GetBufferResource ? 4u
: op == ValueOpcode::GetImageResource ? 8u
: op == ValueOpcode::GetSamplerResource ? 4u
: 2u;
size_t expected = 4u;
if (op == ValueOpcode::GetImageResource) {
expected = 8u;
} else if (op == ValueOpcode::GetAddressResource) {
expected = 2u;
}
if (inst->NumArgs() != expected) {
return finish(false);
}
@@ -254,15 +292,11 @@ private:
op != ValueOpcode::LoadAddressU32 && !IsRuntimeUniformOp(op)) {
return finish(false);
}
for (size_t index = 0; index < inst->NumArgs(); index++) {
if (!Validate(inst->Arg(index))) {
return finish(false);
}
}
return finish(true);
return finish(ValidateArguments(*inst, true));
}
const ResourcePlan& m_program;
RuntimeValueType m_type;
Value m_active_mask;
std::unordered_set<const Inst*> m_visiting;
};
@@ -952,7 +986,10 @@ bool EvaluateRuntimeSourcesImpl(const ResourcePlan& program, std::span<const uin
clean_runtime.read_memory = runtime.read_specialization_memory;
Evaluator clean_evaluator(program, clean_runtime);
Evaluator evaluator(program, runtime, clean_flat_slots, &clean_evaluator);
std::vector<uint8_t> active(program.descriptor_sources.size(), 1u);
std::vector<uint8_t> active;
if (evaluate_flat) {
active.assign(program.descriptor_sources.size(), 1u);
}
if (evaluate_flat && !program.control_flow.empty()) {
for (const auto& block: program.control_flow) {
for (const auto source: block.sources) {
@@ -991,9 +1028,11 @@ bool EvaluateRuntimeSourcesImpl(const ResourcePlan& program, std::span<const uin
}
DescriptorValue value;
value.dword_count = source->dword_count;
for (uint32_t index = 0; active[source_index] && index < source->dword_count; index++) {
if (!evaluator.Evaluate(source->dwords[index], value.dwords[index])) {
return false;
if (!evaluate_flat || active[source_index]) {
for (uint32_t index = 0; index < source->dword_count; index++) {
if (!evaluator.Evaluate(source->dwords[index], value.dwords[index])) {
return false;
}
}
}
evaluated.push_back(value);
@@ -1021,8 +1060,8 @@ bool EvaluateRuntimeSourcesImpl(const ResourcePlan& program, std::span<const uin
} // namespace
bool ValidateRuntimeValue(const ResourcePlan& program, Value value) {
return RuntimeValidator(program).Run(value);
bool ValidateRuntimeValue(const ResourcePlan& program, Value value, RuntimeValueType type) {
return RuntimeValidator(program, type).Run(value);
}
void BuildSrtPlan(Program& program) {