shader: implement BUFFER_ATOMIC_FMAX

This commit is contained in:
nmzik
2026-08-05 02:33:26 +02:00
parent df44ad3fac
commit ac64d202af
15 changed files with 244 additions and 14 deletions
@@ -85,6 +85,7 @@ bool InstructionMaySplitSpirvBlock(const IR::Instruction& inst) {
case IR::Opcode::AtomicOrU32:
case IR::Opcode::AtomicXorU32:
case IR::Opcode::AtomicFMinF32:
case IR::Opcode::AtomicFMaxF32:
case IR::Opcode::FlatLoadUbyte:
case IR::Opcode::FlatLoadSbyte:
case IR::Opcode::FlatLoadUshort:
@@ -58,6 +58,7 @@ constexpr MemoryOpcodeInfo MUBUF_OPS[] = {
{0x3au, Opcode::BufferAtomicOr, 1, 32},
{0x3bu, Opcode::BufferAtomicXor, 1, 32},
{0x3fu, Opcode::BufferAtomicFMin, 1, 32},
{0x40u, Opcode::BufferAtomicFMax, 1, 32},
};
constexpr MemoryOpcodeInfo MTBUF_OPS[] = {
@@ -836,6 +836,7 @@ std::string OpcodeToString(Opcode opcode) {
case Opcode::BufferAtomicOr: return "buffer_atomic_or";
case Opcode::BufferAtomicXor: return "buffer_atomic_xor";
case Opcode::BufferAtomicFMin: return "buffer_atomic_fmin";
case Opcode::BufferAtomicFMax: return "buffer_atomic_fmax";
case Opcode::FlatLoadUbyte: return "flat_load_ubyte";
case Opcode::FlatLoadSbyte: return "flat_load_sbyte";
case Opcode::FlatLoadUshort: return "flat_load_ushort";
@@ -1127,6 +1128,7 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::BufferAtomicOr:
case Opcode::BufferAtomicXor:
case Opcode::BufferAtomicFMin:
case Opcode::BufferAtomicFMax:
case Opcode::BufferLoadSbyte:
case Opcode::BufferLoadSshort:
case Opcode::FlatLoadUbyte:
@@ -435,6 +435,7 @@ enum class Opcode {
BufferAtomicOr,
BufferAtomicXor,
BufferAtomicFMin,
BufferAtomicFMax,
FlatLoadUbyte,
FlatLoadSbyte,
FlatLoadUshort,
@@ -132,7 +132,8 @@ bool IsAtomic(IR::Opcode op) {
case IR::Opcode::AtomicAndU32:
case IR::Opcode::AtomicOrU32:
case IR::Opcode::AtomicXorU32:
case IR::Opcode::AtomicFMinF32: return true;
case IR::Opcode::AtomicFMinF32:
case IR::Opcode::AtomicFMaxF32: return true;
default: return false;
}
}
@@ -142,6 +143,9 @@ uint32_t BufferAddressOperandCount(const IR::Instruction& inst) {
}
bool ValidateInstructionContract(const IR::Instruction& inst, std::string* error) {
if (static_cast<size_t>(inst.op) >= static_cast<size_t>(IR::Opcode::Count)) {
return Fail(error, "IR instruction opcode is out of range");
}
if (inst.src_count > std::size(inst.src)) {
return Fail(error, "instruction source count exceeds fixed IR storage");
}
@@ -201,6 +205,12 @@ bool ValidateInstructionContract(const IR::Instruction& inst, std::string* error
inst.src_count != 2)) {
return Fail(error, "storage image instruction has an invalid resource class");
}
const auto float_atomic =
inst.op == IR::Opcode::AtomicFMinF32 || inst.op == IR::Opcode::AtomicFMaxF32;
if (float_atomic &&
(kind != IR::ResourceKind::Buffer || inst.src_count != buffer_address_count + 1u)) {
return Fail(error, "floating-point atomic instruction must target a buffer");
}
if (IsAtomic(inst.op) &&
!((kind == IR::ResourceKind::Buffer && inst.src_count == buffer_address_count + 1u) ||
((kind == IR::ResourceKind::Lds || kind == IR::ResourceKind::Gds) &&
@@ -489,6 +499,13 @@ bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resourc
AllocateBlockLabels(state, program);
AllocateDispatcherState(state, program);
EmitFunction(state, program);
if (state.unsupported_ir_instruction) {
char pc[9] {};
std::snprintf(pc, sizeof(pc), "%08x", state.unsupported_ir_pc);
return Fail(error, "SPIR-V emitter has no handler for IR opcode " +
std::string(IR::OpcodeName(state.unsupported_ir_opcode)) +
" at pc 0x" + pc);
}
ReportReserveExceeded(program, "registers", state.registers.size());
ReportReserveExceeded(program, "inputs", state.inputs.size());
@@ -778,6 +778,9 @@ void EmitInstruction(EmitterState& state, const IR::Instruction& inst) {
case IR::Opcode::AtomicFMinF32:
EmitGuardedByExec(state, [&]() { EmitAtomicFMinF32(state, inst); });
break;
case IR::Opcode::AtomicFMaxF32:
EmitGuardedByExec(state, [&]() { EmitAtomicFMaxF32(state, inst); });
break;
case IR::Opcode::FlatLoadUbyte: EmitFlatLoadUbyte(state, inst); break;
case IR::Opcode::FlatLoadSbyte: EmitFlatLoadSbyte(state, inst); break;
case IR::Opcode::FlatLoadUshort: EmitFlatLoadUshort(state, inst); break;
@@ -854,7 +857,13 @@ void EmitInstruction(EmitterState& state, const IR::Instruction& inst) {
}
EmitGuardedByExec(state, [&]() { EmitExport(state, inst); });
break;
default: break;
default:
if (!state.unsupported_ir_instruction) {
state.unsupported_ir_instruction = true;
state.unsupported_ir_opcode = inst.op;
state.unsupported_ir_pc = inst.pc;
}
break;
}
}
@@ -414,6 +414,9 @@ struct EmitterState {
bool needs_image_gather_extended = false;
bool needs_function_lds = false;
bool needs_pixel_valid_mask = false;
bool unsupported_ir_instruction = false;
IR::Opcode unsupported_ir_opcode = IR::Opcode::Count;
uint32_t unsupported_ir_pc = 0;
std::vector<RegisterBinding> registers;
std::vector<InputBinding> inputs;
std::vector<OutputBinding> outputs;
@@ -1012,6 +1015,7 @@ void EmitDeviceAtomicMemoryBarrier(EmitterState& state);
void EmitAtomicU32(EmitterState& state, const IR::Instruction& inst, uint32_t opcode);
void EmitAtomicFMinF32(EmitterState& state, const IR::Instruction& inst);
void EmitAtomicFMaxF32(EmitterState& state, const IR::Instruction& inst);
void EmitSLoadDword(EmitterState& state, const IR::Instruction& inst);
@@ -1184,7 +1184,8 @@ void EmitAtomicU32(EmitterState& state, const IR::Instruction& inst, uint32_t op
namespace {
uint32_t EmitF32BitsOrderedLessThan(EmitterState& state, uint32_t lhs_bits, uint32_t rhs_bits) {
uint32_t EmitF32BitsOrderedCompare(EmitterState& state, uint32_t lhs_bits, uint32_t rhs_bits,
uint32_t comparison_opcode) {
struct ClassifiedBits {
uint32_t nan = 0;
uint32_t zero = 0;
@@ -1219,14 +1220,13 @@ uint32_t EmitF32BitsOrderedLessThan(EmitterState& state, uint32_t lhs_bits, uint
const auto both_zero = EmitLogicalAndBool(state, lhs.zero, rhs.zero);
const auto ordered_nonzero =
EmitLogicalNotBool(state, EmitLogicalOrBool(state, any_nan, both_zero));
const auto less = state.builder.AllocateId();
state.builder.AddFunction({OpULessThan, state.bool_type, less, lhs.key, rhs.key});
return EmitLogicalAndBool(state, ordered_nonzero, less);
const auto comparison = state.builder.AllocateId();
state.builder.AddFunction({comparison_opcode, state.bool_type, comparison, lhs.key, rhs.key});
return EmitLogicalAndBool(state, ordered_nonzero, comparison);
}
} // namespace
void EmitAtomicFMinF32(EmitterState& state, const IR::Instruction& inst) {
void EmitAtomicFMinMaxF32(EmitterState& state, const IR::Instruction& inst,
uint32_t comparison_opcode) {
const auto index =
EmitMemoryDwordIndex(state, inst, inst.memory, 1, AddressSourceCount(inst, 1));
const auto in_bounds = EmitStorageBufferElementInBounds(state, inst.memory, index, inst.pc);
@@ -1235,15 +1235,26 @@ void EmitAtomicFMinF32(EmitterState& state, const IR::Instruction& inst) {
const auto pointer = EmitStorageBufferElementPointer(state, inst.memory, index, inst.pc);
return EmitAtomicUpdateU32(state, pointer, inst.memory.kind, [&](uint32_t old_u32) {
const auto replace_old = state.builder.AllocateId();
const auto less = EmitF32BitsOrderedLessThan(state, src_u32, old_u32);
const auto replace =
EmitF32BitsOrderedCompare(state, src_u32, old_u32, comparison_opcode);
state.builder.AddFunction(
{OpSelect, state.uint_type, replace_old, less, src_u32, old_u32});
{OpSelect, state.uint_type, replace_old, replace, src_u32, old_u32});
return replace_old;
});
});
EmitStoreU32(state, inst.dst, old);
}
} // namespace
void EmitAtomicFMinF32(EmitterState& state, const IR::Instruction& inst) {
EmitAtomicFMinMaxF32(state, inst, OpULessThan);
}
void EmitAtomicFMaxF32(EmitterState& state, const IR::Instruction& inst) {
EmitAtomicFMinMaxF32(state, inst, OpUGreaterThan);
}
void EmitSLoadDword(EmitterState& state, const IR::Instruction& inst) {
if (state.address_memory_variable == 0) {
ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::AddressMemory,
@@ -31,7 +31,8 @@ bool IsAtomic(Opcode op) {
case Opcode::AtomicAndU32:
case Opcode::AtomicOrU32:
case Opcode::AtomicXorU32:
case Opcode::AtomicFMinF32: return true;
case Opcode::AtomicFMinF32:
case Opcode::AtomicFMaxF32: return true;
default: return false;
}
}
+27 -1
View File
@@ -975,7 +975,12 @@ bool LowerControlInstruction(const Decoder::Instruction& decoded, BasicBlock& bl
return LowerControlMarker(decoded, block, Opcode::TtraceData, true, error);
case Decoder::Opcode::SInstPrefetch:
return LowerControlMarker(decoded, block, Opcode::InstPrefetch, true, error);
default: return false;
default:
if (error != nullptr) {
*error = fmt::format("control opcode has no specialized IR lowering: {}",
Decoder::OpcodeToString(decoded.opcode));
}
return false;
}
}
@@ -1064,6 +1069,18 @@ bool IsTerminatorOpcode(Decoder::Opcode opcode) {
bool LowerImplemented(const Decoder::Instruction& decoded, BasicBlock& block, std::string* error);
bool IsMemoryFamily(Decoder::Family family) {
switch (family) {
case Decoder::Family::SMEM:
case Decoder::Family::MUBUF:
case Decoder::Family::MTBUF:
case Decoder::Family::FLAT:
case Decoder::Family::DS:
case Decoder::Family::MIMG: return true;
default: return false;
}
}
bool LowerDecodedInstruction(const Decoder::Instruction& inst, BasicBlock& block,
std::string* error) {
switch (inst.opcode) {
@@ -1117,6 +1134,15 @@ bool LowerDecodedInstruction(const Decoder::Instruction& inst, BasicBlock& block
if (IsMemoryOpcode(inst.opcode)) {
return LowerMemoryInstruction(inst, block, error);
}
// Memory instructions require specialized lowering to populate MemoryInfo. Never let a
// newly decoded memory-family opcode fall through to generic register-only IR.
if (IsMemoryFamily(inst.family)) {
if (error != nullptr) {
*error = fmt::format("decoded memory-family opcode has no specialized IR lowering: {}",
Decoder::OpcodeToString(inst.opcode));
}
return false;
}
if (ScalarShiftLeftAddAmount(inst.opcode) != 0) {
return LowerScalarShiftLeftAdd(inst, block, error);
}
@@ -571,6 +571,8 @@ bool LowerMemoryInstruction(const Decoder::Instruction& decoded, BasicBlock& blo
return LowerBufferAtomicDword(decoded, block, Opcode::AtomicXorU32, error);
case Decoder::Opcode::BufferAtomicFMin:
return LowerBufferAtomicDword(decoded, block, Opcode::AtomicFMinF32, error);
case Decoder::Opcode::BufferAtomicFMax:
return LowerBufferAtomicDword(decoded, block, Opcode::AtomicFMaxF32, error);
case Decoder::Opcode::FlatLoadUbyte:
case Decoder::Opcode::FlatLoadSbyte:
case Decoder::Opcode::FlatLoadUshort:
@@ -675,7 +677,12 @@ bool LowerMemoryInstruction(const Decoder::Instruction& decoded, BasicBlock& blo
case Decoder::Opcode::ImageAtomicAnd:
case Decoder::Opcode::ImageAtomicOr:
case Decoder::Opcode::ImageAtomicXor: return LowerImageAtomicU32(decoded, block, error);
default: return false;
default:
if (error != nullptr) {
*error = fmt::format("memory opcode has no specialized IR lowering: {}",
Decoder::OpcodeToString(decoded.opcode));
}
return false;
}
}
@@ -732,6 +739,7 @@ bool IsMemoryOpcode(Decoder::Opcode opcode) {
case Decoder::Opcode::BufferAtomicOr:
case Decoder::Opcode::BufferAtomicXor:
case Decoder::Opcode::BufferAtomicFMin:
case Decoder::Opcode::BufferAtomicFMax:
case Decoder::Opcode::FlatLoadUbyte:
case Decoder::Opcode::FlatLoadSbyte:
case Decoder::Opcode::FlatLoadUshort:
@@ -353,6 +353,7 @@ constexpr LowerMap LOWER_OPS[] = {
{Decoder::Opcode::BufferAtomicOr, Opcode::AtomicOrU32},
{Decoder::Opcode::BufferAtomicXor, Opcode::AtomicXorU32},
{Decoder::Opcode::BufferAtomicFMin, Opcode::AtomicFMinF32},
{Decoder::Opcode::BufferAtomicFMax, Opcode::AtomicFMaxF32},
{Decoder::Opcode::FlatLoadUbyte, Opcode::FlatLoadUbyte},
{Decoder::Opcode::FlatLoadSbyte, Opcode::FlatLoadSbyte},
{Decoder::Opcode::FlatLoadSshort, Opcode::FlatLoadSshort},
@@ -308,6 +308,7 @@ IR_OPCODE(AtomicAndU32, General)
IR_OPCODE(AtomicOrU32, General)
IR_OPCODE(AtomicXorU32, General)
IR_OPCODE(AtomicFMinF32, General)
IR_OPCODE(AtomicFMaxF32, General)
IR_OPCODE(FlatLoadUbyte, General)
IR_OPCODE(FlatLoadSbyte, General)
IR_OPCODE(FlatLoadUshort, General)