shader: lower whole-wave OR scans to subgroup reductions

This commit is contained in:
Artyom Zabroda authored and nmzik committed 2026-10-30 04:31:24 +01:00
1 parent eb3cb47296
commit 0c2546e850
8 files changed
+80 -41

No files matched your search

@@ -859,6 +859,10 @@ uint32_t EmitSubgroupUMaxU32(ValueEmitContext& ctx, const IR::Inst& inst) {
return EmitSubgroupReduceU32<spv::OpGroupNonUniformUMax, EmitUMax32>(ctx, inst);
}
uint32_t EmitSubgroupOrU32(ValueEmitContext& ctx, const IR::Inst& inst) {
return EmitSubgroupReduceU32<spv::OpGroupNonUniformBitwiseOr, EmitOrU32>(ctx, inst);
}
uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto hit = state.builder.AllocateId();
@@ -236,6 +236,7 @@ uint32_t EmitReadFirstLane(ValueEmitContext& ctx, const IR::Inst& i
uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitSubgroupUMinU32(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitSubgroupUMaxU32(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitSubgroupOrU32(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitPermlane16U32(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitGetAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
@@ -235,7 +235,8 @@ void EmitDirectInstruction(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto op = inst.GetOpcode();
if (ctx.half != 0 &&
(op == Op::Ballot || op == Op::ReadFirstLane || op == Op::SubgroupUMinU32 ||
op == Op::SubgroupUMaxU32 || op == Op::ReadConstBuffer || op == Op::ReadRealtimeClock ||
op == Op::SubgroupUMaxU32 || op == Op::SubgroupOrU32 || op == Op::ReadConstBuffer ||
op == Op::ReadRealtimeClock ||
(op == Op::LoadAddressU32 && ctx.Memory(inst).kind == IR::ResourceKind::ScalarAddress))) {
// Whole-wave results, clocks and SMEM loads (SGPR operands, no EXEC) match in both halves.
ctx.Define(inst, ctx.other_half->Result(inst));
@@ -224,6 +224,7 @@ VALUE_OPCODE(ReadFirstLane, U32, U32, U1)
VALUE_OPCODE(ReadLane, U32, U32, U32)
VALUE_OPCODE(SubgroupUMinU32, U32, U32)
VALUE_OPCODE(SubgroupUMaxU32, U32, U32)
VALUE_OPCODE(SubgroupOrU32, U32, U32)
VALUE_OPCODE(WriteLane, U32, U32, U32, U32)
VALUE_OPCODE(Permlane16U32, U32, U32, U32, U32, U1, U32)
VALUE_OPCODE(PermuteU32, U32, U32, U32, U1)
@@ -1,7 +1,7 @@
#include "graphics/shader/recompiler/ir/passes/ReadLaneElimination.h"
#include <algorithm>
#include <optional>
#include <queue>
#include <unordered_map>
@@ -186,6 +186,16 @@ private:
ValueOpcode operation;
};
// The subgroup reduction that replaces a proven whole-wave scan of operation.
std::optional<ValueOpcode> WaveReduction(ValueOpcode operation) {
switch (operation) {
case ValueOpcode::UMin32: return ValueOpcode::SubgroupUMinU32;
case ValueOpcode::UMax32: return ValueOpcode::SubgroupUMaxU32;
case ValueOpcode::BitwiseOr32: return ValueOpcode::SubgroupOrU32;
default: return std::nullopt;
}
}
} // namespace
ReadLaneStats EliminateReadLane(Program& program, uint32_t wave_size) {
@@ -197,15 +207,12 @@ ReadLaneStats EliminateReadLane(Program& program, uint32_t wave_size) {
for (auto* block: program.blocks) {
for (auto& inst: *block) {
const auto opcode = inst.GetOpcode();
if (opcode == ValueOpcode::UMin32 || opcode == ValueOpcode::UMax32) {
if (const auto reduce = WaveReduction(opcode)) {
const auto seed = WaveReductionMatcher(block, opcode).Match(inst, wave_size);
if (!seed.IsEmpty()) {
const auto position = std::find_if(block->begin(), block->end(),
[&](const Inst& candidate) { return &candidate == &inst; });
const auto reduce = opcode == ValueOpcode::UMin32
? ValueOpcode::SubgroupUMinU32
: ValueOpcode::SubgroupUMaxU32;
auto& reduction = *block->PrependNewInst(position, reduce, {seed});
auto& reduction = *block->PrependNewInst(position, *reduce, {seed});
inst.ReplaceUsesWith(Value(&reduction));
stats.rewritten_reductions++;
}
@@ -524,7 +524,8 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
case ValueOpcode::Ballot: info.subgroup_ballot = true; break;
case ValueOpcode::LiveMask: info.subgroup_ballot = info.helper_invocation = true; break;
case ValueOpcode::SubgroupUMinU32:
case ValueOpcode::SubgroupUMaxU32: info.subgroup_arithmetic = true; break;
case ValueOpcode::SubgroupUMaxU32:
case ValueOpcode::SubgroupOrU32: info.subgroup_arithmetic = true; break;
case ValueOpcode::DppMoveU32:
case ValueOpcode::ReadFirstLane:
case ValueOpcode::ReadLane: {