shader: expose wave32 feature guards through ballot mask simplification

This commit is contained in:
nmzik
2026-10-01 18:50:04 +04:00
parent 6409be2874
commit 35759e48cf
5 changed files with 224 additions and 6 deletions
@@ -4,6 +4,7 @@
#include <algorithm>
#include <bit>
#include <cstdint>
#include <unordered_map>
#include <unordered_set>
namespace Libs::Graphics::ShaderRecompiler::IR {
@@ -660,12 +661,110 @@ void FoldInstruction(Block& block, Block::iterator instruction,
}
}
// In wave32, extracting this invocation's bit from a ballot recovers its predicate.
// Keep that identity through scalar EXEC operations and their loop-carried mask Phis.
class LaneMaskProjection {
public:
explicit LaneMaskProjection(std::unordered_set<Inst*>& lowered_ancillary)
: m_lowered_ancillary(lowered_ancillary) {}
void Fold(Inst& inst) {
if (inst.GetOpcode() != ValueOpcode::INotEqual32 || !Immediate(Arg(inst, 1), 0u)) return;
const auto* bit = Arg(inst, 0).TryInstruction();
if (bit == nullptr || bit->GetOpcode() != ValueOpcode::BitwiseAnd32 ||
!Immediate(Arg(*bit, 1), 1u)) return;
const auto* shift = Arg(*bit, 0).TryInstruction();
if (shift == nullptr || shift->GetOpcode() != ValueOpcode::ShiftRightLogical32) return;
const auto* index = Arg(*shift, 1).TryInstruction();
if (index == nullptr || index->GetOpcode() != ValueOpcode::BitwiseAnd32 ||
!Immediate(Arg(*index, 1), 31u)) return;
const auto* lane = Arg(*index, 0).TryInstruction();
if (lane == nullptr || lane->GetOpcode() != ValueOpcode::LaneId) return;
m_visited.clear();
m_grounded = false;
if (CanProject(Arg(*shift, 0)) && m_grounded) Replace(inst, Project(Arg(*shift, 0)));
}
private:
static bool Immediate(Value value, uint32_t expected) {
return IsImmediate(value, Type::U32) && value.U32() == expected;
}
static Value BallotPredicate(const Inst& inst) {
if (inst.GetOpcode() != ValueOpcode::CompositeExtractU32x4 ||
!Immediate(Arg(inst, 1), 0u)) return {};
const auto* source = Arg(inst, 0).TryInstruction();
return source != nullptr && source->GetOpcode() == ValueOpcode::Ballot
? Arg(*source, 0) : Value {};
}
bool CanProject(Value value) {
if (value.IsImmediate()) {
const bool supported = Immediate(value, 0u) || Immediate(value, UINT32_MAX);
m_grounded |= supported;
return supported;
}
const auto* inst = value.TryInstruction();
if (inst == nullptr) return false;
if (m_values.contains(inst) || !BallotPredicate(*inst).IsEmpty()) {
m_grounded = true;
return true;
}
if (!m_visited.insert(inst).second) return true;
const auto op = inst->GetOpcode();
if (op != ValueOpcode::BitwiseAnd32 && op != ValueOpcode::BitwiseOr32 &&
op != ValueOpcode::BitwiseNot32 && op != ValueOpcode::SelectU32 && op != ValueOpcode::Phi)
return false;
for (size_t arg = op == ValueOpcode::SelectU32 ? 1u : 0u; arg < inst->NumArgs(); ++arg) {
if (!CanProject(Arg(*inst, arg))) return false;
}
return inst->NumArgs() != 0;
}
Value Project(Value value) {
if (value.IsImmediate()) return Value(value.U32() != 0u);
auto* source = value.TryInstruction();
if (const auto found = m_values.find(source); found != m_values.end()) return found->second.Resolve();
if (const auto predicate = BallotPredicate(*source); !predicate.IsEmpty()) return predicate;
auto* block = source->Parent();
const auto where = std::find_if(block->begin(), block->end(),
[source](const Inst& candidate) { return &candidate == source; });
const auto op = source->GetOpcode() == ValueOpcode::BitwiseAnd32 ? ValueOpcode::LogicalAnd
: source->GetOpcode() == ValueOpcode::BitwiseOr32 ? ValueOpcode::LogicalOr
: source->GetOpcode() == ValueOpcode::BitwiseNot32 ? ValueOpcode::LogicalNot
: source->GetOpcode() == ValueOpcode::SelectU32 ? ValueOpcode::SelectU1
: ValueOpcode::Phi;
auto result = op == ValueOpcode::Phi ? block->PrependNewInst(where, op)
: op == ValueOpcode::LogicalNot
? block->PrependNewInst(where, op, {Value(false)})
: op == ValueOpcode::SelectU1
? block->PrependNewInst(where, op, {Arg(*source, 0), Value(false), Value(false)})
: block->PrependNewInst(where, op, {Value(false), Value(false)});
m_values.emplace(source, Value(&*result));
if (op == ValueOpcode::Phi) result->SetFlags(Type::U1);
for (size_t arg = op == ValueOpcode::SelectU1 ? 1u : 0u; arg < source->NumArgs(); ++arg) {
const auto projected = Project(Arg(*source, arg));
if (op == ValueOpcode::Phi) result->AddPhiOperand(source->PhiBlock(arg), projected);
else result->SetArg(arg, projected);
}
FoldInstruction(*block, result, m_lowered_ancillary);
return Value(&*result).Resolve();
}
std::unordered_set<Inst*>& m_lowered_ancillary;
std::unordered_set<const Inst*> m_visited;
std::unordered_map<const Inst*, Value> m_values;
bool m_grounded = false;
};
} // namespace
void ConstantPropagationPass(const BlockList& blocks) {
void ConstantPropagationPass(const BlockList& blocks, uint32_t wave_size) {
std::unordered_set<Inst*> lowered_ancillary;
LaneMaskProjection mask_projection(lowered_ancillary);
for (auto* block: blocks) {
for (auto inst = block->begin(); inst != block->end(); ++inst) {
if (wave_size == 32u) mask_projection.Fold(*inst);
FoldInstruction(*block, inst, lowered_ancillary);
}
}