Compare commits

..

13 Commits

Author SHA1 Message Date
nmzik ac64d202af shader: implement BUFFER_ATOMIC_FMAX 2026-08-05 02:59:28 +02:00
nmzik df44ad3fac shader: implement BUFFER_ATOMIC_FMIN, unhandled IR opcode now fails 2026-08-05 02:59:28 +02:00
nmzik 0a2f4814ac refactor(shader): centralize opcode metadata and lookup paths 2026-08-05 02:59:28 +02:00
nmzik e6b4707a5d renderer: support R8 uint render targets 2026-08-05 02:59:28 +02:00
nmzik 9a3468e3a0 shader: implement V_MIN_U32 2026-08-05 02:59:28 +02:00
nmzik 73da43a7be shader: implement V_CNDMASK_B32 2026-08-05 02:59:28 +02:00
nmzik aa88d94755 shader: implement V_SUB_NC_U32 2026-08-05 02:59:27 +02:00
nmzik 51562cd8d2 tests: use valid image descriptors in shader CFG cases 2026-08-05 02:59:27 +02:00
nmzik 822ca3c10b shader: implement S_CMP_EQ_U64 2026-08-05 02:59:27 +02:00
nmzik 2b6fd699e7 shader: implement V_CMP_EQ_I64 2026-08-05 02:59:27 +02:00
nmzik b9aa0dc3b3 fix KernelLseek lock leak
Co-authored-by: Stefanos Costa <stefanos.costa1999@gmail.com>
2026-08-05 02:25:05 +02:00
nmzik f380d69b9d fix AudioOut pacing
Co-authored-by: Stefanos Costa <stefanos.costa1999@gmail.com>
2026-08-05 02:17:25 +02:00
DeathWhispers f06d7e6084 launcher: disable Vulkan validation by default
Keep validation opt-in while validation-layer errors remain fatal.

Refs #161
2026-08-04 22:09:40 +02:00
30 changed files with 1464 additions and 997 deletions
@@ -22,6 +22,10 @@ struct RenderTargetFormatMapping {
};
constexpr RenderTargetFormatMapping kRenderTargetFormats[] = {
{Prospero::ChannelLayout::k8,
Prospero::ChannelType::kUInt,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR8Uint, 1}},
{Prospero::ChannelLayout::k8_8,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
@@ -84,6 +84,8 @@ bool InstructionMaySplitSpirvBlock(const IR::Instruction& inst) {
case IR::Opcode::AtomicAndU32:
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:
@@ -57,6 +57,8 @@ constexpr MemoryOpcodeInfo MUBUF_OPS[] = {
{0x39u, Opcode::BufferAtomicAnd, 1, 32},
{0x3au, Opcode::BufferAtomicOr, 1, 32},
{0x3bu, Opcode::BufferAtomicXor, 1, 32},
{0x3fu, Opcode::BufferAtomicFMin, 1, 32},
{0x40u, Opcode::BufferAtomicFMax, 1, 32},
};
constexpr MemoryOpcodeInfo MTBUF_OPS[] = {
@@ -0,0 +1,45 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_DECOMPILER_OPCODETABLE_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_DECOMPILER_OPCODETABLE_H_
#include "graphics/shader/recompiler/decompiler/ShaderDecoder.h"
#include <cstddef>
namespace Libs::Graphics::ShaderRecompiler::Decoder::Detail {
struct OpcodeMap {
uint32_t encoding = 0;
Opcode decoded = Opcode::Unknown;
};
template <typename Entry, size_t N>
constexpr const Entry* FindOpcode(const Entry (&table)[N], uint32_t encoding) {
for (const auto& entry: table) {
if (entry.encoding == encoding) {
return &entry;
}
}
return nullptr;
}
template <typename Entry, size_t N>
constexpr Opcode LookupOpcode(const Entry (&table)[N], uint32_t encoding) {
const auto* entry = FindOpcode(table, encoding);
return entry != nullptr ? entry->decoded : Opcode::Unsupported;
}
template <typename Entry, size_t N>
constexpr bool HasUniqueEncodings(const Entry (&table)[N]) {
for (size_t i = 0; i < N; i++) {
for (size_t j = i + 1; j < N; j++) {
if (table[i].encoding == table[j].encoding) {
return false;
}
}
}
return true;
}
} // namespace Libs::Graphics::ShaderRecompiler::Decoder::Detail
#endif // EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_DECOMPILER_OPCODETABLE_H_
@@ -1,14 +1,11 @@
#include "graphics/shader/recompiler/decompiler/ScalarAluOps.h"
#include <iterator>
#include "graphics/shader/recompiler/decompiler/OpcodeTable.h"
namespace Libs::Graphics::ShaderRecompiler::Decoder {
namespace {
struct OpcodeMap {
uint32_t opcode = 0;
Opcode ir = Opcode::Unknown;
};
using Detail::OpcodeMap;
constexpr OpcodeMap SOP2_OPS[] = {
{0x00u, Opcode::SAddU32}, {0x01u, Opcode::SSubU32}, {0x02u, Opcode::SAddI32},
@@ -58,7 +55,8 @@ constexpr OpcodeMap SOPC_OPS[] = {
{0x03u, Opcode::SCmpGeI32}, {0x04u, Opcode::SCmpLtI32}, {0x05u, Opcode::SCmpLeI32},
{0x06u, Opcode::SCmpEqU32}, {0x07u, Opcode::SCmpLgU32}, {0x08u, Opcode::SCmpGtU32},
{0x09u, Opcode::SCmpGeU32}, {0x0au, Opcode::SCmpLtU32}, {0x0bu, Opcode::SCmpLeU32},
{0x0cu, Opcode::SBitcmp0B32}, {0x0du, Opcode::SBitcmp1B32}, {0x13u, Opcode::SCmpLgU64},
{0x0cu, Opcode::SBitcmp0B32}, {0x0du, Opcode::SBitcmp1B32}, {0x12u, Opcode::SCmpEqU64},
{0x13u, Opcode::SCmpLgU64},
};
constexpr OpcodeMap SOPK_OPS[] = {
@@ -78,15 +76,11 @@ constexpr OpcodeMap SOPP_OPS[] = {
{0x0au, Opcode::SBarrier}, {0x0cu, Opcode::SWaitcnt}, {0x0eu, Opcode::SSleep},
{0x10u, Opcode::SSendmsg}, {0x16u, Opcode::STtraceData}, {0x20u, Opcode::SInstPrefetch},
};
Opcode Lookup(const OpcodeMap* ops, uint32_t count, uint32_t opcode) {
for (uint32_t i = 0; i < count; i++) {
if (ops[i].opcode == opcode) {
return ops[i].ir;
}
}
return Opcode::Unsupported;
}
static_assert(Detail::HasUniqueEncodings(SOP1_OPS));
static_assert(Detail::HasUniqueEncodings(SOP2_OPS));
static_assert(Detail::HasUniqueEncodings(SOPK_OPS));
static_assert(Detail::HasUniqueEncodings(SOPC_OPS));
static_assert(Detail::HasUniqueEncodings(SOPP_OPS));
bool DecodeBinarySources(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
Instruction& inst, uint32_t ssrc0, uint32_t ssrc1, std::string* error) {
@@ -111,7 +105,7 @@ bool DecodeSop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
inst.word = word;
inst.family = Family::SOP1;
inst.opcode_id = opcode;
inst.opcode = Lookup(SOP1_OPS, static_cast<uint32_t>(std::size(SOP1_OPS)), opcode);
inst.opcode = Detail::LookupOpcode(SOP1_OPS, opcode);
SetRawWords(inst, code, word_index, 1);
if (inst.opcode == Opcode::Unsupported) {
@@ -153,7 +147,7 @@ bool DecodeSop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
inst.word = word;
inst.family = Family::SOP2;
inst.opcode_id = opcode;
inst.opcode = Lookup(SOP2_OPS, static_cast<uint32_t>(std::size(SOP2_OPS)), opcode);
inst.opcode = Detail::LookupOpcode(SOP2_OPS, opcode);
SetRawWords(inst, code, word_index, 1);
if (inst.opcode == Opcode::Unsupported) {
@@ -180,7 +174,7 @@ bool DecodeSopk(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
inst.word = word;
inst.family = Family::SOPK;
inst.opcode_id = opcode;
inst.opcode = Lookup(SOPK_OPS, static_cast<uint32_t>(std::size(SOPK_OPS)), opcode);
inst.opcode = Detail::LookupOpcode(SOPK_OPS, opcode);
inst.src0.kind = OperandKind::IntegerInlineConstant;
inst.src0.signed_val = imm;
inst.src0.value = static_cast<uint32_t>(imm);
@@ -237,7 +231,7 @@ bool DecodeSopc(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
inst.word = word;
inst.family = Family::SOPC;
inst.opcode_id = opcode;
inst.opcode = Lookup(SOPC_OPS, static_cast<uint32_t>(std::size(SOPC_OPS)), opcode);
inst.opcode = Detail::LookupOpcode(SOPC_OPS, opcode);
inst.dst.kind = OperandKind::Scc;
SetRawWords(inst, code, word_index, 1);
@@ -261,7 +255,7 @@ bool DecodeSopp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
inst.word = word;
inst.family = Family::SOPP;
inst.opcode_id = opcode;
inst.opcode = Lookup(SOPP_OPS, static_cast<uint32_t>(std::size(SOPP_OPS)), opcode);
inst.opcode = Detail::LookupOpcode(SOPP_OPS, opcode);
inst.src0.kind = OperandKind::LiteralConstant;
inst.src0.value = simm;
inst.src0.signed_val = static_cast<int16_t>(simm);
@@ -77,6 +77,16 @@ std::string FormatBinary(const Instruction& inst) {
return text;
}
std::string FormatSources(const Instruction& inst) {
std::string text = fmt::format("0x{:08x}: {}", inst.pc, OpcodeToString(inst.opcode));
const Operand* sources[] = {&inst.src0, &inst.src1, &inst.src2, &inst.src3};
for (uint32_t i = 0; i < inst.src_count && i < 4u; i++) {
text += i == 0 ? " " : ", ";
text += OperandToString(*sources[i]);
}
return text;
}
std::string FormatMemory(const Instruction& inst) {
std::string text = fmt::format("0x{:08x}: {} {}", inst.pc, OpcodeToString(inst.opcode).c_str(),
OperandToString(inst.dst).c_str());
@@ -511,6 +521,7 @@ std::string OpcodeToString(Opcode opcode) {
case Opcode::SCmpGeU32: return "s_cmp_ge_u32";
case Opcode::SCmpLtU32: return "s_cmp_lt_u32";
case Opcode::SCmpLeU32: return "s_cmp_le_u32";
case Opcode::SCmpEqU64: return "s_cmp_eq_u64";
case Opcode::SCmpLgU64: return "s_cmp_lg_u64";
case Opcode::VNop: return "v_nop";
case Opcode::VMovB32: return "v_mov_b32";
@@ -766,6 +777,7 @@ std::string OpcodeToString(Opcode opcode) {
case Opcode::VCmpNeU32: return "v_cmp_ne_u32";
case Opcode::VCmpGeU32: return "v_cmp_ge_u32";
case Opcode::VCmpTU32: return "v_cmp_t_u32";
case Opcode::VCmpEqI64: return "v_cmp_eq_i64";
case Opcode::VCmpNeU64: return "v_cmp_ne_u64";
case Opcode::VCmpxLtU32: return "v_cmpx_lt_u32";
case Opcode::VCmpxEqU32: return "v_cmpx_eq_u32";
@@ -823,6 +835,8 @@ std::string OpcodeToString(Opcode opcode) {
case Opcode::BufferAtomicAnd: return "buffer_atomic_and";
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";
@@ -983,6 +997,9 @@ std::string InstructionToString(const Instruction& inst) {
inst.pc, FamilyToString(inst.family).c_str(), inst.opcode_id,
RawWordsToString(inst).c_str(), inst.unsupported_reason.c_str());
}
if (inst.family == Family::SOPC) {
return WithUnsupportedReason(inst, FormatSources(inst));
}
switch (inst.opcode) {
case Opcode::SMovB32:
@@ -1110,6 +1127,8 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::BufferAtomicAnd:
case Opcode::BufferAtomicOr:
case Opcode::BufferAtomicXor:
case Opcode::BufferAtomicFMin:
case Opcode::BufferAtomicFMax:
case Opcode::BufferLoadSbyte:
case Opcode::BufferLoadSshort:
case Opcode::FlatLoadUbyte:
@@ -1171,25 +1190,6 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::DsWriteB128:
case Opcode::DsWriteAddtidB32:
case Opcode::DsReadAddtidB32: return WithUnsupportedReason(inst, FormatMemory(inst));
case Opcode::SCmpEqU32:
case Opcode::SCmpEqI32:
case Opcode::SCmpLgU32:
case Opcode::SCmpLgI32:
case Opcode::SCmpGtU32:
case Opcode::SCmpGtI32:
case Opcode::SCmpGeU32:
case Opcode::SCmpGeI32:
case Opcode::SCmpLtU32:
case Opcode::SCmpLtI32:
case Opcode::SCmpLeU32:
case Opcode::SCmpLeI32:
case Opcode::SBitcmp0B32:
case Opcode::SBitcmp1B32:
case Opcode::SCmpLgU64:
return WithUnsupportedReason(inst, fmt::format("0x{:08x}: {} {}, {}", inst.pc,
OpcodeToString(inst.opcode).c_str(),
OperandToString(inst.src0).c_str(),
OperandToString(inst.src1).c_str()));
default: return WithUnsupportedReason(inst, FormatBinary(inst));
}
}
@@ -118,6 +118,7 @@ enum class Opcode {
SCmpGeU32,
SCmpLtU32,
SCmpLeU32,
SCmpEqU64,
SCmpLgU64,
VNop,
@@ -374,6 +375,7 @@ enum class Opcode {
VCmpNeU32,
VCmpGeU32,
VCmpTU32,
VCmpEqI64,
VCmpNeU64,
VCmpxLtU32,
VCmpxEqU32,
@@ -432,6 +434,8 @@ enum class Opcode {
BufferAtomicAnd,
BufferAtomicOr,
BufferAtomicXor,
BufferAtomicFMin,
BufferAtomicFMax,
FlatLoadUbyte,
FlatLoadSbyte,
FlatLoadUshort,
@@ -1,43 +1,91 @@
#include "graphics/shader/recompiler/decompiler/VectorAluOps.h"
#include "graphics/shader/recompiler/decompiler/OpcodeTable.h"
#include <fmt/format.h>
#include <iterator>
namespace Libs::Graphics::ShaderRecompiler::Decoder {
namespace {
struct OpcodeMap {
uint32_t opcode = 0;
Opcode ir = Opcode::Unknown;
using Detail::OpcodeMap;
// These profiles describe the selectors that the current lowering implements. They are not
// architectural SDWA legality classes.
enum class Vop2SdwaProfile {
None,
Cndmask,
Float32,
Float16,
IntegerFullDestination,
IntegerPartialDestination,
ReverseLogicalRight,
ReverseLogicalLeft,
Bitwise,
Count,
};
constexpr OpcodeMap VOP2_OPS[] = {
{0x01u, Opcode::VCndmaskB32}, {0x02u, Opcode::VDot2cF32F16},
{0x03u, Opcode::VAddF32}, {0x04u, Opcode::VSubF32},
{0x05u, Opcode::VSubrevF32}, {0x08u, Opcode::VMulF32},
{0x09u, Opcode::VMulI32I24}, {0x0bu, Opcode::VMulU32U24},
{0x0fu, Opcode::VMinF32}, {0x10u, Opcode::VMaxF32},
{0x11u, Opcode::VMinI32}, {0x12u, Opcode::VMaxI32},
{0x13u, Opcode::VMinU32}, {0x14u, Opcode::VMaxU32},
{0x15u, Opcode::VLshrB32}, {0x16u, Opcode::VLshrrevB32},
{0x17u, Opcode::VAshrI32}, {0x18u, Opcode::VAshrrevI32},
{0x19u, Opcode::VLshlB32}, {0x1au, Opcode::VLshlrevB32},
{0x1bu, Opcode::VAndB32}, {0x1cu, Opcode::VOrB32},
{0x1du, Opcode::VXorB32}, {0x1eu, Opcode::VXnorB32},
{0x1fu, Opcode::VMacF32}, {0x20u, Opcode::VMadmkF32},
{0x21u, Opcode::VMadakF32}, {0x22u, Opcode::VBcntU32B32},
{0x23u, Opcode::VMbcntLoU32B32}, {0x24u, Opcode::VMbcntHiU32B32},
{0x25u, Opcode::VAddNcU32}, {0x28u, Opcode::VAddcU32},
{0x26u, Opcode::VSubNcU32}, {0x27u, Opcode::VSubrevNcU32},
{0x2bu, Opcode::VMacF32}, {0x2cu, Opcode::VMadmkF32},
{0x2du, Opcode::VMadakF32}, {0x2fu, Opcode::VCvtPkrtzF16F32},
{0x32u, Opcode::VAddF16}, {0x33u, Opcode::VSubF16},
{0x34u, Opcode::VSubrevF16}, {0x35u, Opcode::VMulF16},
{0x36u, Opcode::VFmacF16}, {0x37u, Opcode::VFmamkF16},
{0x38u, Opcode::VFmaakF16}, {0x39u, Opcode::VMaxF16},
{0x3au, Opcode::VMinF16}, {0x3cu, Opcode::VPkFmacF16},
struct Vop2OpcodeInfo {
uint32_t encoding = 0;
Opcode decoded = Opcode::Unknown;
Vop2SdwaProfile sdwa_profile = Vop2SdwaProfile::None;
};
constexpr Vop2OpcodeInfo VOP2_OPS[] = {
{0x01u, Opcode::VCndmaskB32, Vop2SdwaProfile::Cndmask},
{0x02u, Opcode::VDot2cF32F16},
{0x03u, Opcode::VAddF32, Vop2SdwaProfile::Float32},
{0x04u, Opcode::VSubF32, Vop2SdwaProfile::Float32},
{0x05u, Opcode::VSubrevF32},
{0x08u, Opcode::VMulF32, Vop2SdwaProfile::Float32},
{0x09u, Opcode::VMulI32I24, Vop2SdwaProfile::IntegerFullDestination},
{0x0bu, Opcode::VMulU32U24, Vop2SdwaProfile::IntegerFullDestination},
{0x0fu, Opcode::VMinF32},
{0x10u, Opcode::VMaxF32},
{0x11u, Opcode::VMinI32},
{0x12u, Opcode::VMaxI32},
{0x13u, Opcode::VMinU32, Vop2SdwaProfile::IntegerPartialDestination},
{0x14u, Opcode::VMaxU32, Vop2SdwaProfile::IntegerFullDestination},
{0x15u, Opcode::VLshrB32},
{0x16u, Opcode::VLshrrevB32, Vop2SdwaProfile::ReverseLogicalRight},
{0x17u, Opcode::VAshrI32},
{0x18u, Opcode::VAshrrevI32},
{0x19u, Opcode::VLshlB32},
{0x1au, Opcode::VLshlrevB32, Vop2SdwaProfile::ReverseLogicalLeft},
{0x1bu, Opcode::VAndB32, Vop2SdwaProfile::Bitwise},
{0x1cu, Opcode::VOrB32, Vop2SdwaProfile::Bitwise},
{0x1du, Opcode::VXorB32, Vop2SdwaProfile::Bitwise},
{0x1eu, Opcode::VXnorB32, Vop2SdwaProfile::Bitwise},
{0x1fu, Opcode::VMacF32},
{0x20u, Opcode::VMadmkF32},
{0x21u, Opcode::VMadakF32},
{0x22u, Opcode::VBcntU32B32},
{0x23u, Opcode::VMbcntLoU32B32},
{0x24u, Opcode::VMbcntHiU32B32},
{0x25u, Opcode::VAddNcU32, Vop2SdwaProfile::IntegerFullDestination},
{0x28u, Opcode::VAddcU32},
{0x26u, Opcode::VSubNcU32, Vop2SdwaProfile::IntegerPartialDestination},
{0x27u, Opcode::VSubrevNcU32, Vop2SdwaProfile::IntegerFullDestination},
{0x2bu, Opcode::VMacF32},
{0x2cu, Opcode::VMadmkF32},
{0x2du, Opcode::VMadakF32},
{0x2fu, Opcode::VCvtPkrtzF16F32},
{0x32u, Opcode::VAddF16, Vop2SdwaProfile::Float16},
{0x33u, Opcode::VSubF16, Vop2SdwaProfile::Float16},
{0x34u, Opcode::VSubrevF16, Vop2SdwaProfile::Float16},
{0x35u, Opcode::VMulF16, Vop2SdwaProfile::Float16},
{0x36u, Opcode::VFmacF16},
{0x37u, Opcode::VFmamkF16},
{0x38u, Opcode::VFmaakF16},
{0x39u, Opcode::VMaxF16, Vop2SdwaProfile::Float16},
{0x3au, Opcode::VMinF16, Vop2SdwaProfile::Float16},
{0x3cu, Opcode::VPkFmacF16},
};
static_assert(Detail::HasUniqueEncodings(VOP2_OPS));
constexpr Opcode LookupVop2Opcode(uint32_t encoding) {
return Detail::LookupOpcode(VOP2_OPS, encoding);
}
constexpr OpcodeMap VOP1_OPS[] = {
{0x00u, Opcode::VNop},
{0x01u, Opcode::VMovB32},
@@ -135,37 +183,59 @@ constexpr OpcodeMap VOP3_ENCODED_VOP1_OPS[] = {
{0x5eu, Opcode::VRndneF16},
};
constexpr OpcodeMap VOPC_OPS[] = {
{0x00u, Opcode::VCmpFF32}, {0x01u, Opcode::VCmpLtF32}, {0x02u, Opcode::VCmpEqF32},
{0x03u, Opcode::VCmpLeF32}, {0x04u, Opcode::VCmpGtF32}, {0x05u, Opcode::VCmpLgF32},
{0x06u, Opcode::VCmpGeF32}, {0x07u, Opcode::VCmpOF32}, {0x08u, Opcode::VCmpUF32},
{0x09u, Opcode::VCmpNgeF32}, {0x0au, Opcode::VCmpNlgF32}, {0x0bu, Opcode::VCmpNgtF32},
{0x0cu, Opcode::VCmpNleF32}, {0x0du, Opcode::VCmpNeqF32}, {0x0eu, Opcode::VCmpNltF32},
{0x0fu, Opcode::VCmpTruF32}, {0x11u, Opcode::VCmpxLtF32}, {0x12u, Opcode::VCmpxEqF32},
{0x13u, Opcode::VCmpxLeF32}, {0x14u, Opcode::VCmpxGtF32}, {0x15u, Opcode::VCmpxLgF32},
{0x16u, Opcode::VCmpxGeF32}, {0x19u, Opcode::VCmpxNgeF32}, {0x1au, Opcode::VCmpxNlgF32},
{0x1bu, Opcode::VCmpxNgtF32}, {0x1cu, Opcode::VCmpxNleF32}, {0x1du, Opcode::VCmpxNeqF32},
{0x1eu, Opcode::VCmpxNltF32}, {0x80u, Opcode::VCmpFI32}, {0x81u, Opcode::VCmpLtI32},
{0x82u, Opcode::VCmpEqI32}, {0x83u, Opcode::VCmpLeI32}, {0x84u, Opcode::VCmpGtI32},
{0x85u, Opcode::VCmpNeI32}, {0x86u, Opcode::VCmpGeI32}, {0x87u, Opcode::VCmpTI32},
{0x88u, Opcode::VCmpClassF32}, {0x89u, Opcode::VCmpLtI16}, {0x8au, Opcode::VCmpEqI16},
{0x8bu, Opcode::VCmpLeI16}, {0x8cu, Opcode::VCmpGtI16}, {0x8du, Opcode::VCmpNeI16},
{0x8eu, Opcode::VCmpGeI16}, {0x91u, Opcode::VCmpxLtI32}, {0x92u, Opcode::VCmpxEqI32},
{0x93u, Opcode::VCmpxLeI32}, {0x94u, Opcode::VCmpxGtI32}, {0x95u, Opcode::VCmpxNeI32},
{0x96u, Opcode::VCmpxGeI32}, {0xa9u, Opcode::VCmpLtU16}, {0xaau, Opcode::VCmpEqU16},
{0xabu, Opcode::VCmpLeU16}, {0xacu, Opcode::VCmpGtU16}, {0xadu, Opcode::VCmpNeU16},
{0xaeu, Opcode::VCmpGeU16}, {0xc0u, Opcode::VCmpFU32}, {0xc1u, Opcode::VCmpLtU32},
{0xc2u, Opcode::VCmpEqU32}, {0xc3u, Opcode::VCmpLeU32}, {0xc4u, Opcode::VCmpGtU32},
{0xc5u, Opcode::VCmpNeU32}, {0xc6u, Opcode::VCmpGeU32}, {0xc7u, Opcode::VCmpTU32},
{0xd1u, Opcode::VCmpxLtU32}, {0xd2u, Opcode::VCmpxEqU32}, {0xd3u, Opcode::VCmpxLeU32},
{0xd4u, Opcode::VCmpxGtU32}, {0xd5u, Opcode::VCmpxNeU32}, {0xd6u, Opcode::VCmpxGeU32},
{0xe5u, Opcode::VCmpNeU64}, {0xc9u, Opcode::VCmpLtF16}, {0xcau, Opcode::VCmpEqF16},
{0xcbu, Opcode::VCmpLeF16}, {0xccu, Opcode::VCmpGtF16}, {0xcdu, Opcode::VCmpLgF16},
{0xceu, Opcode::VCmpGeF16}, {0xedu, Opcode::VCmpNeqF16}, {0xd9u, Opcode::VCmpxLtF16},
{0xdau, Opcode::VCmpxEqF16}, {0xdbu, Opcode::VCmpxLeF16}, {0xdcu, Opcode::VCmpxGtF16},
{0xdeu, Opcode::VCmpxGeF16}, {0xfdu, Opcode::VCmpxNeqF16}, {0xfeu, Opcode::VCmpxNltF16},
struct VopcOpcodeInfo {
uint32_t encoding = 0;
Opcode decoded = Opcode::Unknown;
bool supports_dpp = true;
};
constexpr VopcOpcodeInfo VOPC_OPS[] = {
{0x00u, Opcode::VCmpFF32}, {0x01u, Opcode::VCmpLtF32},
{0x02u, Opcode::VCmpEqF32}, {0x03u, Opcode::VCmpLeF32},
{0x04u, Opcode::VCmpGtF32}, {0x05u, Opcode::VCmpLgF32},
{0x06u, Opcode::VCmpGeF32}, {0x07u, Opcode::VCmpOF32},
{0x08u, Opcode::VCmpUF32}, {0x09u, Opcode::VCmpNgeF32},
{0x0au, Opcode::VCmpNlgF32}, {0x0bu, Opcode::VCmpNgtF32},
{0x0cu, Opcode::VCmpNleF32}, {0x0du, Opcode::VCmpNeqF32},
{0x0eu, Opcode::VCmpNltF32}, {0x0fu, Opcode::VCmpTruF32},
{0x11u, Opcode::VCmpxLtF32}, {0x12u, Opcode::VCmpxEqF32},
{0x13u, Opcode::VCmpxLeF32}, {0x14u, Opcode::VCmpxGtF32},
{0x15u, Opcode::VCmpxLgF32}, {0x16u, Opcode::VCmpxGeF32},
{0x19u, Opcode::VCmpxNgeF32}, {0x1au, Opcode::VCmpxNlgF32},
{0x1bu, Opcode::VCmpxNgtF32}, {0x1cu, Opcode::VCmpxNleF32},
{0x1du, Opcode::VCmpxNeqF32}, {0x1eu, Opcode::VCmpxNltF32},
{0x80u, Opcode::VCmpFI32}, {0x81u, Opcode::VCmpLtI32},
{0x82u, Opcode::VCmpEqI32}, {0x83u, Opcode::VCmpLeI32},
{0x84u, Opcode::VCmpGtI32}, {0x85u, Opcode::VCmpNeI32},
{0x86u, Opcode::VCmpGeI32}, {0x87u, Opcode::VCmpTI32},
{0x88u, Opcode::VCmpClassF32}, {0x89u, Opcode::VCmpLtI16},
{0x8au, Opcode::VCmpEqI16}, {0x8bu, Opcode::VCmpLeI16},
{0x8cu, Opcode::VCmpGtI16}, {0x8du, Opcode::VCmpNeI16},
{0x8eu, Opcode::VCmpGeI16}, {0x91u, Opcode::VCmpxLtI32},
{0x92u, Opcode::VCmpxEqI32}, {0x93u, Opcode::VCmpxLeI32},
{0x94u, Opcode::VCmpxGtI32}, {0x95u, Opcode::VCmpxNeI32},
{0x96u, Opcode::VCmpxGeI32}, {0xa9u, Opcode::VCmpLtU16},
{0xaau, Opcode::VCmpEqU16}, {0xabu, Opcode::VCmpLeU16},
{0xacu, Opcode::VCmpGtU16}, {0xadu, Opcode::VCmpNeU16},
{0xaeu, Opcode::VCmpGeU16}, {0xc0u, Opcode::VCmpFU32},
{0xc1u, Opcode::VCmpLtU32}, {0xc2u, Opcode::VCmpEqU32},
{0xc3u, Opcode::VCmpLeU32}, {0xc4u, Opcode::VCmpGtU32},
{0xc5u, Opcode::VCmpNeU32}, {0xc6u, Opcode::VCmpGeU32},
{0xc7u, Opcode::VCmpTU32}, {0xa2u, Opcode::VCmpEqI64, false},
{0xd1u, Opcode::VCmpxLtU32}, {0xd2u, Opcode::VCmpxEqU32},
{0xd3u, Opcode::VCmpxLeU32}, {0xd4u, Opcode::VCmpxGtU32},
{0xd5u, Opcode::VCmpxNeU32}, {0xd6u, Opcode::VCmpxGeU32},
{0xe5u, Opcode::VCmpNeU64, false}, {0xc9u, Opcode::VCmpLtF16},
{0xcau, Opcode::VCmpEqF16}, {0xcbu, Opcode::VCmpLeF16},
{0xccu, Opcode::VCmpGtF16}, {0xcdu, Opcode::VCmpLgF16},
{0xceu, Opcode::VCmpGeF16}, {0xedu, Opcode::VCmpNeqF16},
{0xd9u, Opcode::VCmpxLtF16}, {0xdau, Opcode::VCmpxEqF16},
{0xdbu, Opcode::VCmpxLeF16}, {0xdcu, Opcode::VCmpxGtF16},
{0xdeu, Opcode::VCmpxGeF16}, {0xfdu, Opcode::VCmpxNeqF16},
{0xfeu, Opcode::VCmpxNltF16},
};
static_assert(Detail::HasUniqueEncodings(VOPC_OPS));
constexpr OpcodeMap VOP3_OPS[] = {
{0x141u, Opcode::VMadF32}, {0x142u, Opcode::VMadI32I24},
{0x143u, Opcode::VMadU32U24}, {0x176u, Opcode::VMadU64U32},
@@ -214,16 +284,6 @@ constexpr OpcodeMap VOP3P_OPS[] = {
{0x22u, Opcode::VMadMixhiF16},
};
Opcode Lookup(const OpcodeMap* ops, uint32_t count, uint32_t opcode) {
for (uint32_t i = 0; i < count; i++) {
const auto& op = ops[i];
if (op.opcode == opcode) {
return op.ir;
}
}
return Opcode::Unsupported;
}
bool IsVop2LiteralMadOpcode(uint32_t opcode) {
return opcode == 0x20u || opcode == 0x21u || opcode == 0x2cu || opcode == 0x2du;
}
@@ -234,24 +294,23 @@ bool IsUnsupportedVop3EncodedVop2Alias(uint32_t opcode) {
}
Opcode LookupVop3Opcode(uint32_t opcode) {
const auto direct = Lookup(VOP3_OPS, static_cast<uint32_t>(std::size(VOP3_OPS)), opcode);
const auto direct = Detail::LookupOpcode(VOP3_OPS, opcode);
if (direct != Opcode::Unsupported) {
return direct;
}
if (opcode <= 0xffu) {
return Lookup(VOPC_OPS, static_cast<uint32_t>(std::size(VOPC_OPS)), opcode);
return Detail::LookupOpcode(VOPC_OPS, opcode);
}
if (opcode >= 0x100u && opcode <= 0x13fu) {
if (IsUnsupportedVop3EncodedVop2Alias(opcode - 0x100u)) {
return Opcode::Unsupported;
}
return Lookup(VOP2_OPS, static_cast<uint32_t>(std::size(VOP2_OPS)), opcode - 0x100u);
return LookupVop2Opcode(opcode - 0x100u);
}
if (opcode >= 0x180u && opcode <= 0x1ffu) {
return Lookup(VOP3_ENCODED_VOP1_OPS,
static_cast<uint32_t>(std::size(VOP3_ENCODED_VOP1_OPS)), opcode - 0x180u);
return Detail::LookupOpcode(VOP3_ENCODED_VOP1_OPS, opcode - 0x180u);
}
return Lookup(VOP3_OPS, static_cast<uint32_t>(std::size(VOP3_OPS)), opcode);
return Detail::LookupOpcode(VOP3_OPS, opcode);
}
bool IsVop3EncodedVopc(uint32_t opcode) {
@@ -791,7 +850,6 @@ Vop2SdwaFields DecodeVop2SdwaFields(uint32_t modifier) {
}
struct Vop2SdwaRule {
Opcode opcode = Opcode::Unknown;
uint32_t dst_selectors = SdwaSelFull();
uint32_t src0_selectors = SdwaSelFull();
uint32_t src1_selectors = SdwaSelFull();
@@ -804,46 +862,27 @@ constexpr uint32_t SdwaSelAll() {
}
constexpr Vop2SdwaRule VOP2_SDWA_RULES[] = {
{Opcode::VCndmaskB32, SdwaSelWords(), SdwaSelWords() | SdwaSelFull(),
SdwaSelWords() | SdwaSelFull(), true, false},
{Opcode::VAddF32, SdwaSelFull(), SdwaSelFull(), SdwaSelFull(), false, true},
{Opcode::VSubF32, SdwaSelFull(), SdwaSelFull(), SdwaSelFull(), false, true},
{Opcode::VMulF32, SdwaSelFull(), SdwaSelFull(), SdwaSelFull(), false, true},
{Opcode::VAddF16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords() | SdwaSelFull(),
SdwaSelWords() | SdwaSelFull(), true, true},
{Opcode::VSubF16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords() | SdwaSelFull(),
SdwaSelWords() | SdwaSelFull(), true, true},
{Opcode::VSubrevF16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords() | SdwaSelFull(),
SdwaSelWords() | SdwaSelFull(), true, true},
{Opcode::VMulF16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords() | SdwaSelFull(),
SdwaSelWords() | SdwaSelFull(), true, true},
{Opcode::VMaxF16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords() | SdwaSelFull(),
SdwaSelWords() | SdwaSelFull(), true, true},
{Opcode::VMinF16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords() | SdwaSelFull(),
SdwaSelWords() | SdwaSelFull(), true, true},
{Opcode::VMulI32I24, SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
{Opcode::VMulU32U24, SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
{Opcode::VMinU32, SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
{Opcode::VMaxU32, SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
{Opcode::VLshrrevB32, SdwaSelFull(), SdwaSelFull(), SdwaSelWords() | SdwaSelFull(), false,
false},
{Opcode::VLshlrevB32, SdwaSelFull(), SdwaSelFull(), SdwaSelAll(), false, false},
{Opcode::VAndB32, SdwaSelWords() | SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), true, false},
{Opcode::VOrB32, SdwaSelWords() | SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), true, false},
{Opcode::VXorB32, SdwaSelWords() | SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), true, false},
{Opcode::VXnorB32, SdwaSelWords() | SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), true, false},
{Opcode::VAddNcU32, SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
{Opcode::VSubNcU32, SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
{Opcode::VSubrevNcU32, SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
{},
{SdwaSelWords() | SdwaSelFull(), SdwaSelWords() | SdwaSelFull(), SdwaSelWords() | SdwaSelFull(),
true, false},
{SdwaSelFull(), SdwaSelFull(), SdwaSelFull(), false, true},
{SdwaSelWords() | SdwaSelFull(), SdwaSelWords() | SdwaSelFull(), SdwaSelWords() | SdwaSelFull(),
true, true},
{SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
{SdwaSelAll(), SdwaSelAll(), SdwaSelAll(), true, false},
{SdwaSelFull(), SdwaSelFull(), SdwaSelWords() | SdwaSelFull(), false, false},
{SdwaSelFull(), SdwaSelFull(), SdwaSelAll(), false, false},
{SdwaSelWords() | SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), true, false},
};
static_assert(sizeof(VOP2_SDWA_RULES) / sizeof(VOP2_SDWA_RULES[0]) ==
static_cast<size_t>(Vop2SdwaProfile::Count));
const Vop2SdwaRule* FindVop2SdwaRule(Opcode opcode) {
for (const auto& rule: VOP2_SDWA_RULES) {
if (rule.opcode == opcode) {
return &rule;
}
const Vop2SdwaRule* FindVop2SdwaRule(uint32_t encoding) {
const auto* info = Detail::FindOpcode(VOP2_OPS, encoding);
if (info == nullptr || info->sdwa_profile == Vop2SdwaProfile::None) {
return nullptr;
}
return nullptr;
return &VOP2_SDWA_RULES[static_cast<size_t>(info->sdwa_profile)];
}
bool IsVop2SdwaDestinationSupported(const Vop2SdwaRule& rule, const Vop2SdwaFields& fields) {
@@ -881,7 +920,7 @@ bool ValidateVop2Sdwa(Instruction& inst, uint32_t opcode, const Vop2SdwaFields&
return true;
}
const auto* rule = FindVop2SdwaRule(inst.opcode);
const auto* rule = FindVop2SdwaRule(opcode);
if (rule == nullptr) {
SetUnsupported(inst, Family::VOP2, opcode,
"VOP2 SDWA modifier is not supported for opcode");
@@ -1135,6 +1174,11 @@ bool DecodeVopcDpp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_in
SetUnsupported(inst, Family::VOPC, opcode, "VOPC opcode is not implemented");
return true;
}
const auto* info = Detail::FindOpcode(VOPC_OPS, opcode);
if (info == nullptr || !info->supports_dpp) {
SetUnsupported(inst, Family::VOPC, opcode, "VOPC DPP modifier is not supported for opcode");
return true;
}
if (!DecodeScalarSource(src0 + 256u, pc, inst.src0, error) ||
!DecodeVectorGpr(vsrc1, inst.src1, error)) {
return false;
@@ -1427,7 +1471,7 @@ bool DecodeVop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
inst.word = word;
inst.family = Family::VOP2;
inst.opcode_id = opcode;
inst.opcode = Lookup(VOP2_OPS, static_cast<uint32_t>(std::size(VOP2_OPS)), opcode);
inst.opcode = LookupVop2Opcode(opcode);
SetRawWords(inst, code, word_index, 1);
switch (opcode) {
@@ -1468,7 +1512,7 @@ bool DecodeVop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
inst.word = word;
inst.family = Family::VOP1;
inst.opcode_id = opcode;
inst.opcode = Lookup(VOP1_OPS, static_cast<uint32_t>(std::size(VOP1_OPS)), opcode);
inst.opcode = Detail::LookupOpcode(VOP1_OPS, opcode);
SetRawWords(inst, code, word_index, 1);
if (inst.opcode == Opcode::Unsupported) {
@@ -1509,7 +1553,7 @@ bool DecodeVopc(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
inst.word = word;
inst.family = Family::VOPC;
inst.opcode_id = opcode;
inst.opcode = Lookup(VOPC_OPS, static_cast<uint32_t>(std::size(VOPC_OPS)), opcode);
inst.opcode = Detail::LookupOpcode(VOPC_OPS, opcode);
inst.dst.kind = IsVopcCompareExec(inst.opcode) ? OperandKind::ExecLo : OperandKind::VccLo;
SetRawWords(inst, code, word_index, 1);
@@ -1723,7 +1767,7 @@ bool DecodeVop3p(uint32_t pc, std::span<const uint32_t> code, uint32_t word_inde
inst.word = word0;
inst.family = Family::VOP3P;
inst.opcode_id = opcode;
inst.opcode = Lookup(VOP3P_OPS, static_cast<uint32_t>(std::size(VOP3P_OPS)), opcode);
inst.opcode = Detail::LookupOpcode(VOP3P_OPS, opcode);
SetRawWords(inst, code, word_index, 2);
if (inst.opcode == Opcode::Unsupported) {
@@ -131,7 +131,9 @@ bool IsAtomic(IR::Opcode op) {
case IR::Opcode::AtomicUMaxU32:
case IR::Opcode::AtomicAndU32:
case IR::Opcode::AtomicOrU32:
case IR::Opcode::AtomicXorU32: return true;
case IR::Opcode::AtomicXorU32:
case IR::Opcode::AtomicFMinF32:
case IR::Opcode::AtomicFMaxF32: return true;
default: return false;
}
}
@@ -141,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");
}
@@ -200,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) &&
@@ -389,7 +400,7 @@ bool ProgramRequiresExactSubgroupSize(const IR::Program& program) {
if (Emitter::InstructionHasDppSource(inst)) {
return true;
}
if (Emitter::IsCompareOpcode(inst.op) && !Emitter::IsSccOperand(inst.dst)) {
if (IR::IsCompareOpcode(inst.op) && !Emitter::IsSccOperand(inst.dst)) {
return true;
}
if ((IsExecDestination(inst.dst) || IsExecDestination(inst.dst2)) &&
@@ -488,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());
@@ -1486,17 +1486,20 @@ void EmitBitCompareB32(EmitterState& state, const IR::Instruction& inst, bool bi
EmitStoreU32(state, inst.dst, ret);
}
void EmitCompareNeU64(EmitterState& state, const IR::Instruction& inst) {
const auto lhs_low = EmitSequentialValueLoad(state, inst.src[0], 0);
const auto lhs_high = EmitSequentialValueLoad(state, inst.src[0], 1);
const auto rhs_low = EmitSequentialValueLoad(state, inst.src[1], 0);
const auto rhs_high = EmitSequentialValueLoad(state, inst.src[1], 1);
const auto ne_low = state.builder.AllocateId();
const auto ne_high = state.builder.AllocateId();
const auto cond = state.builder.AllocateId();
state.builder.AddFunction({OpINotEqual, state.bool_type, ne_low, lhs_low, rhs_low});
state.builder.AddFunction({OpINotEqual, state.bool_type, ne_high, lhs_high, rhs_high});
state.builder.AddFunction({OpLogicalOr, state.bool_type, cond, ne_low, ne_high});
void EmitCompareU64(EmitterState& state, const IR::Instruction& inst) {
const bool equal = inst.op == IR::Opcode::CompareEqU64;
const auto lhs_low = EmitSequentialValueLoad(state, inst.src[0], 0);
const auto lhs_high = EmitSequentialValueLoad(state, inst.src[0], 1);
const auto rhs_low = EmitSequentialValueLoad(state, inst.src[1], 0);
const auto rhs_high = EmitSequentialValueLoad(state, inst.src[1], 1);
const auto low = state.builder.AllocateId();
const auto high = state.builder.AllocateId();
const auto cond = state.builder.AllocateId();
const auto compare_op = equal ? OpIEqual : OpINotEqual;
state.builder.AddFunction({compare_op, state.bool_type, low, lhs_low, rhs_low});
state.builder.AddFunction({compare_op, state.bool_type, high, lhs_high, rhs_high});
state.builder.AddFunction(
{equal ? OpLogicalAnd : OpLogicalOr, state.bool_type, cond, low, high});
EmitCompareResult(state, inst.dst, cond);
}
@@ -75,8 +75,6 @@ bool IsSccOperand(const IR::Operand& operand) {
return operand.kind == IR::OperandKind::Register && operand.reg.file == IR::RegisterFile::Scc;
}
bool IsCompareOpcode(IR::Opcode op);
void CollectMaskStateRegisters(std::vector<RegisterBinding>& registers) {
CollectRegister(registers, {IR::RegisterFile::Exec, 0});
CollectRegister(registers, {IR::RegisterFile::Exec, 1});
@@ -189,11 +187,11 @@ void CollectRegisters(const IR::Program& program, std::vector<RegisterBinding>&
if (inst.op == IR::Opcode::BitCountU64 || inst.op == IR::Opcode::FindMsbFromHighU64) {
CollectSequentialRegisters(registers, inst.src[0], 2);
}
if (inst.op == IR::Opcode::CompareNeU64) {
if (IR::IsCompare64Opcode(inst.op)) {
CollectSequentialRegisters(registers, inst.src[0], 2);
CollectSequentialRegisters(registers, inst.src[1], 2);
}
if (IsCompareOpcode(inst.op) && inst.dst.kind == IR::OperandKind::Register &&
if (IR::IsCompareOpcode(inst.op) && inst.dst.kind == IR::OperandKind::Register &&
inst.dst.reg.file != IR::RegisterFile::Scc) {
CollectSequentialRegisters(registers, inst.dst, 2);
}
@@ -384,93 +382,6 @@ bool ProgramNeedsSubgroupShuffle(const IR::Program& program) {
return false;
}
bool IsCompareOpcode(IR::Opcode op) {
switch (op) {
case IR::Opcode::CompareFalse:
case IR::Opcode::CompareTrue:
case IR::Opcode::CompareEqU32:
case IR::Opcode::CompareNeU32:
case IR::Opcode::CompareGtU32:
case IR::Opcode::CompareGeU32:
case IR::Opcode::CompareLtU32:
case IR::Opcode::CompareLeU32:
case IR::Opcode::CompareNeU64:
case IR::Opcode::CompareMaskEqU32:
case IR::Opcode::CompareMaskNeU32:
case IR::Opcode::CompareMaskGtU32:
case IR::Opcode::CompareMaskGeU32:
case IR::Opcode::CompareMaskLtU32:
case IR::Opcode::CompareMaskLeU32:
case IR::Opcode::CompareEqI32:
case IR::Opcode::CompareNeI32:
case IR::Opcode::CompareGtI32:
case IR::Opcode::CompareGeI32:
case IR::Opcode::CompareLtI32:
case IR::Opcode::CompareLeI32:
case IR::Opcode::CompareEqI16:
case IR::Opcode::CompareNeI16:
case IR::Opcode::CompareGtI16:
case IR::Opcode::CompareGeI16:
case IR::Opcode::CompareLtI16:
case IR::Opcode::CompareLeI16:
case IR::Opcode::CompareMaskEqI32:
case IR::Opcode::CompareMaskNeI32:
case IR::Opcode::CompareMaskGtI32:
case IR::Opcode::CompareMaskGeI32:
case IR::Opcode::CompareMaskLtI32:
case IR::Opcode::CompareMaskLeI32:
case IR::Opcode::CompareEqU16:
case IR::Opcode::CompareNeU16:
case IR::Opcode::CompareGtU16:
case IR::Opcode::CompareGeU16:
case IR::Opcode::CompareLtU16:
case IR::Opcode::CompareLeU16:
case IR::Opcode::CompareEqF32:
case IR::Opcode::CompareNeF32:
case IR::Opcode::CompareGtF32:
case IR::Opcode::CompareGeF32:
case IR::Opcode::CompareLtF32:
case IR::Opcode::CompareLeF32:
case IR::Opcode::CompareOrderedF32:
case IR::Opcode::CompareUnorderedF32:
case IR::Opcode::CompareUnordEqF32:
case IR::Opcode::CompareUnordNeF32:
case IR::Opcode::CompareUnordGtF32:
case IR::Opcode::CompareUnordGeF32:
case IR::Opcode::CompareUnordLtF32:
case IR::Opcode::CompareUnordLeF32:
case IR::Opcode::CompareClassF32:
case IR::Opcode::CompareEqF16:
case IR::Opcode::CompareNeF16:
case IR::Opcode::CompareGtF16:
case IR::Opcode::CompareGeF16:
case IR::Opcode::CompareLtF16:
case IR::Opcode::CompareLeF16:
case IR::Opcode::CompareUnordNeF16:
case IR::Opcode::CompareMaskEqF16:
case IR::Opcode::CompareMaskNeF16:
case IR::Opcode::CompareMaskGtF16:
case IR::Opcode::CompareMaskGeF16:
case IR::Opcode::CompareMaskLtF16:
case IR::Opcode::CompareMaskLeF16:
case IR::Opcode::CompareMaskUnordNeF16:
case IR::Opcode::CompareMaskUnordGeF16:
case IR::Opcode::CompareMaskEqF32:
case IR::Opcode::CompareMaskNeF32:
case IR::Opcode::CompareMaskGtF32:
case IR::Opcode::CompareMaskGeF32:
case IR::Opcode::CompareMaskLtF32:
case IR::Opcode::CompareMaskLeF32:
case IR::Opcode::CompareMaskUnordEqF32:
case IR::Opcode::CompareMaskUnordNeF32:
case IR::Opcode::CompareMaskUnordGtF32:
case IR::Opcode::CompareMaskUnordGeF32:
case IR::Opcode::CompareMaskUnordLtF32:
case IR::Opcode::CompareMaskUnordLeF32: return true;
default: return false;
}
}
bool ProgramNeedsSubgroupLocalInvocationId(const IR::Program& program) {
for (const auto& block: program.blocks) {
for (const auto& inst: block.instructions) {
@@ -481,7 +392,7 @@ bool ProgramNeedsSubgroupLocalInvocationId(const IR::Program& program) {
InstructionHasDppSource(inst)) {
return true;
}
if (IsCompareOpcode(inst.op)) {
if (IR::IsCompareOpcode(inst.op)) {
return true;
}
if (inst.dst.kind == IR::OperandKind::Register &&
@@ -516,7 +516,8 @@ void EmitInstruction(EmitterState& state, const IR::Instruction& inst) {
case IR::Opcode::CompareGeU32: EmitCompareU32(state, inst, OpUGreaterThanEqual); break;
case IR::Opcode::CompareLtU32: EmitCompareU32(state, inst, OpULessThan); break;
case IR::Opcode::CompareLeU32: EmitCompareU32(state, inst, OpULessThanEqual); break;
case IR::Opcode::CompareNeU64: EmitCompareNeU64(state, inst); break;
case IR::Opcode::CompareEqU64:
case IR::Opcode::CompareNeU64: EmitCompareU64(state, inst); break;
case IR::Opcode::CompareMaskEqU32: EmitCompareMaskU32(state, inst, OpIEqual); break;
case IR::Opcode::CompareMaskNeU32: EmitCompareMaskU32(state, inst, OpINotEqual); break;
case IR::Opcode::CompareMaskGtU32: EmitCompareMaskU32(state, inst, OpUGreaterThan); break;
@@ -774,6 +775,12 @@ void EmitInstruction(EmitterState& state, const IR::Instruction& inst) {
case IR::Opcode::AtomicXorU32:
EmitGuardedByExec(state, [&]() { EmitAtomicU32(state, inst, OpAtomicXor); });
break;
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;
@@ -850,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;
@@ -611,8 +614,6 @@ bool IsMaskRegisterFile(IR::RegisterFile file);
bool IsSccOperand(const IR::Operand& operand);
bool IsCompareOpcode(IR::Opcode op);
void CollectMaskStateRegisters(std::vector<RegisterBinding>& registers);
void CollectSequentialRegisters(std::vector<RegisterBinding>& registers, const IR::Operand& base,
@@ -651,8 +652,6 @@ bool ProgramNeedsSubgroupBallot(const IR::Program& program);
bool ProgramNeedsSubgroupShuffle(const IR::Program& program);
bool IsCompareOpcode(IR::Opcode op);
bool ProgramNeedsSubgroupLocalInvocationId(const IR::Program& program);
uint32_t PointerForRegister(const EmitterState& state, IR::Register reg);
@@ -1015,6 +1014,9 @@ 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);
void EmitLoadSrtDword(EmitterState& state, const IR::Instruction& inst);
@@ -1345,7 +1347,7 @@ void EmitCompareI16(EmitterState& state, const IR::Instruction& inst, uint32_t o
void EmitBitCompareB32(EmitterState& state, const IR::Instruction& inst, bool bit_set);
void EmitCompareNeU64(EmitterState& state, const IR::Instruction& inst);
void EmitCompareU64(EmitterState& state, const IR::Instruction& inst);
void EmitCompareConstant(EmitterState& state, const IR::Instruction& inst, bool value);
@@ -1577,19 +1579,22 @@ uint32_t EmitValueOrZeroIfCondition(EmitterState& state, uint32_t condition, Fn&
}
const auto then_label = state.builder.AllocateId();
const auto then_exit = state.builder.AllocateId();
const auto else_label = state.builder.AllocateId();
const auto merge_label = state.builder.AllocateId();
state.builder.AddFunction({OpSelectionMerge, merge_label, SelectionControlNone});
state.builder.AddFunction({OpBranchConditional, condition, then_label, else_label});
state.builder.AddFunction({OpLabel, then_label});
const auto then_value = fn();
state.builder.AddFunction({OpBranch, then_exit});
state.builder.AddFunction({OpLabel, then_exit});
state.builder.AddFunction({OpBranch, merge_label});
state.builder.AddFunction({OpLabel, else_label});
state.builder.AddFunction({OpBranch, merge_label});
state.builder.AddFunction({OpLabel, merge_label});
const auto value = state.builder.AllocateId();
state.builder.AddFunction(
{OpPhi, state.uint_type, value, then_value, then_label, ConstantU32(state, 0), else_label});
{OpPhi, state.uint_type, value, then_value, then_exit, ConstantU32(state, 0), else_label});
return value;
}
@@ -640,8 +640,8 @@ void EmitMemoryStoreU32(EmitterState& state, const IR::Instruction& inst, IR::Re
}
template <typename Fn>
void EmitAtomicUpdateU32(EmitterState& state, uint32_t pointer, IR::ResourceKind kind,
Fn&& desired_value) {
uint32_t EmitAtomicUpdateU32(EmitterState& state, uint32_t pointer, IR::ResourceKind kind,
Fn&& desired_value) {
const auto scope = kind == IR::ResourceKind::Lds ? ScopeWorkgroup : ScopeDevice;
const auto memory = kind == IR::ResourceKind::Lds ? MemorySemanticsWorkgroupMemory
: MemorySemanticsUniformMemory;
@@ -675,6 +675,7 @@ void EmitAtomicUpdateU32(EmitterState& state, uint32_t pointer, IR::ResourceKind
const auto semantics = MemorySemanticsAcquireRelease | memory;
state.builder.AddFunction(
{OpMemoryBarrier, ConstantU32(state, scope), ConstantU32(state, semantics)});
return observed;
}
void EmitMemoryStoreSubDwordU32(EmitterState& state, const IR::Instruction& inst,
@@ -1181,6 +1182,79 @@ void EmitAtomicU32(EmitterState& state, const IR::Instruction& inst, uint32_t op
EmitStoreU32(state, inst.dst, old);
}
namespace {
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;
uint32_t key = 0;
};
const auto Classify = [&](uint32_t bits) {
ClassifiedBits cls;
const auto abs_bits = EmitAndConstant(state, bits, 0x7fffffffu);
const auto exponent_bits = EmitAndConstant(state, abs_bits, 0x7f800000u);
const auto mantissa_bits = EmitAndConstant(state, abs_bits, 0x007fffffu);
const auto exponent_max =
EmitCompareU32Constant(state, OpIEqual, exponent_bits, 0x7f800000u);
const auto mantissa_nonzero = EmitCompareU32Constant(state, OpINotEqual, mantissa_bits, 0);
const auto negative = EmitCompareU32Constant(state, OpINotEqual,
EmitAndConstant(state, bits, 0x80000000u), 0);
const auto negative_key = state.builder.AllocateId();
const auto positive_key = state.builder.AllocateId();
// Map all non-NaN IEEE-754 encodings to monotonically increasing unsigned keys.
state.builder.AddFunction({OpNot, state.uint_type, negative_key, bits});
state.builder.AddFunction(
{OpBitwiseXor, state.uint_type, positive_key, bits, ConstantU32(state, 0x80000000u)});
cls.nan = EmitLogicalAndBool(state, exponent_max, mantissa_nonzero);
cls.zero = EmitCompareU32Constant(state, OpIEqual, abs_bits, 0);
cls.key = EmitSelectValueU32(state, negative, negative_key, positive_key);
return cls;
};
const auto lhs = Classify(lhs_bits);
const auto rhs = Classify(rhs_bits);
const auto any_nan = EmitLogicalOrBool(state, lhs.nan, rhs.nan);
// The key transform orders -0 below +0, while the floating comparison treats them as equal.
const auto both_zero = EmitLogicalAndBool(state, lhs.zero, rhs.zero);
const auto ordered_nonzero =
EmitLogicalNotBool(state, EmitLogicalOrBool(state, any_nan, both_zero));
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);
}
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);
const auto src_u32 = EmitValueLoad(state, inst.src[0]);
const auto old = EmitValueOrZeroIfCondition(state, in_bounds, [&]() {
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 replace =
EmitF32BitsOrderedCompare(state, src_u32, old_u32, comparison_opcode);
state.builder.AddFunction(
{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,
@@ -30,7 +30,9 @@ bool IsAtomic(Opcode op) {
case Opcode::AtomicUMaxU32:
case Opcode::AtomicAndU32:
case Opcode::AtomicOrU32:
case Opcode::AtomicXorU32: return true;
case Opcode::AtomicXorU32:
case Opcode::AtomicFMinF32:
case Opcode::AtomicFMaxF32: return true;
default: return false;
}
}
+44 -12
View File
@@ -490,13 +490,26 @@ bool IsScalarMinMaxOpcode(Decoder::Opcode opcode) {
}
}
bool ResolveIrOpcode(Decoder::Opcode decoded, Opcode& lowered, std::string* error) {
const auto opcode = LookupIrOpcode(decoded);
if (!opcode.has_value()) {
if (error != nullptr) {
*error = fmt::format("decoded opcode has no IR lowering: {}",
Decoder::OpcodeToString(decoded));
}
return false;
}
lowered = *opcode;
return true;
}
bool LowerScalarMinMaxWithScc(const Decoder::Instruction& decoded, BasicBlock& block,
std::string* error) {
Instruction result;
result.pc = decoded.pc;
result.op = LookupIrOpcode(decoded.opcode);
result.src_count = 2;
if (!LowerRegisterOperand(decoded.dst, result.dst, error) ||
if (!ResolveIrOpcode(decoded.opcode, result.op, error) ||
!LowerRegisterOperand(decoded.dst, result.dst, error) ||
!LowerSourceOperand(decoded.src0, result.src[0], error) ||
!LowerSourceOperand(decoded.src1, result.src[1], error)) {
return false;
@@ -962,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;
}
}
@@ -1051,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) {
@@ -1104,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);
}
@@ -1116,23 +1155,16 @@ bool LowerDecodedInstruction(const Decoder::Instruction& inst, BasicBlock& block
if (VectorByteConvertIndex(inst.opcode) <= 3u) {
return LowerVectorByteConvert(inst, block, error);
}
if (!IsImplemented(inst.opcode)) {
if (error != nullptr) {
*error = fmt::format("decoded opcode has no IR lowering yet: {}",
Decoder::InstructionToString(inst).c_str());
}
return false;
}
return LowerImplemented(inst, block, error);
}
bool LowerImplemented(const Decoder::Instruction& decoded, BasicBlock& block, std::string* error) {
Instruction inst;
inst.pc = decoded.pc;
inst.op = LookupIrOpcode(decoded.opcode);
inst.src_count = decoded.src_count;
if (!LowerRegisterOperand(decoded.dst, inst.dst, error)) {
if (!ResolveIrOpcode(decoded.opcode, inst.op, error) ||
!LowerRegisterOperand(decoded.dst, inst.dst, error)) {
return false;
}
ApplyDppDestinationMask(decoded, inst.dst);
+35 -337
View File
@@ -8,350 +8,48 @@
#include "graphics/shader/shader.h"
#include <array>
#include <string_view>
#include <vector>
namespace Libs::Graphics::ShaderRecompiler::IR {
enum class Opcode {
ControlNop,
Waitcnt,
Barrier,
Sendmsg,
TtraceData,
InstPrefetch,
MoveU32,
MoveF32Bits,
MoveRelDestU32,
MoveRelSourceU32,
MoveU64,
WqmB64,
SaveexecB32,
SaveexecB64,
ReadFirstLaneU32,
ReadLaneU32,
WriteLaneU32,
Permlane16B32,
Permlanex16B32,
AbsI32,
IAddU32,
IAddCarryU32,
ISubBorrowU32,
ScalarAddCarryU32,
ScalarSubBorrowU32,
ScalarSubBorrowCarryU32,
ScalarSignedAddOverflowI32,
ScalarSignedSubOverflowI32,
ScalarShiftLeftAddCarryU32,
ISubU32,
IMulU32,
UMulHighU32,
SMulHighI32,
IMadI24U32,
UMadU24U32,
UMadU64U32,
SadU32,
IAdd3U32,
IMulI24U32,
UMulU24U32,
IMinI32,
IMaxI32,
IMin3I32,
IMax3I32,
IMed3I32,
UMinU32,
UMaxU32,
UMin3U32,
UMax3U32,
UMed3U32,
BitwiseAndU32,
BitwiseAndU64,
BitwiseAndNotU32,
BitwiseAndNotU64,
BitwiseOrU32,
BitwiseOrU64,
BitwiseOrNotU32,
BitwiseOrNotU64,
BitwiseAndOrU32,
BitwiseOr3U32,
BitwiseXorU32,
BitwiseXorU64,
BitwiseXor3U32,
BitwiseNandU32,
BitwiseNandU64,
BitwiseNorU32,
BitwiseNorU64,
BitwiseXnorU32,
BitwiseXnorU64,
BitwiseNotU32,
BitwiseNotU64,
BitClearU32,
BitSetU32,
BitReverseU32,
BitCountU32,
BitCountU64,
BitReplicateB64B32,
BitCountAddU32,
MaskedBitCountLowU32,
MaskedBitCountHighU32,
FindLsbU32,
FindMsbFromHighU32,
FindMsbFromHighU64,
BitFieldMaskU32,
BitFieldMaskU64,
BitFieldExtractU32,
BitFieldExtractU64,
BitFieldExtract3U32,
BitFieldExtract3I32,
BitFieldInsertSelectU32,
BitCompare0B32,
BitCompare1B32,
AlignBitU32,
ShiftLeftAddU32,
AddShiftLeftU32,
XorAddU32,
ShiftLeftOrU32,
ShiftLeftLogicalU32,
ShiftLeftLogicalU64,
ShiftLeftLogicalU16,
ShiftRightLogicalU32,
ShiftRightLogicalU64,
ShiftRightLogicalU16,
ShiftRightArithmeticI32,
ShiftRightArithmeticI16,
SelectU32,
SelectMaskU32,
SelectF32Bits,
SelectMaskF32Bits,
SelectU64,
PackLowLowU16,
PackLowHighU16,
PackHighHighU16,
CompareFalse,
CompareTrue,
CompareEqU32,
CompareNeU32,
CompareGtU32,
CompareGeU32,
CompareLtU32,
CompareLeU32,
CompareNeU64,
CompareMaskEqU32,
CompareMaskNeU32,
CompareMaskGtU32,
CompareMaskGeU32,
CompareMaskLtU32,
CompareMaskLeU32,
CompareEqI32,
CompareNeI32,
CompareGtI32,
CompareGeI32,
CompareLtI32,
CompareLeI32,
CompareEqI16,
CompareNeI16,
CompareGtI16,
CompareGeI16,
CompareLtI16,
CompareLeI16,
CompareMaskEqI32,
CompareMaskNeI32,
CompareMaskGtI32,
CompareMaskGeI32,
CompareMaskLtI32,
CompareMaskLeI32,
CompareEqU16,
CompareNeU16,
CompareGtU16,
CompareGeU16,
CompareLtU16,
CompareLeU16,
CompareEqF32,
CompareNeF32,
CompareGtF32,
CompareGeF32,
CompareLtF32,
CompareLeF32,
CompareOrderedF32,
CompareUnorderedF32,
CompareUnordEqF32,
CompareUnordNeF32,
CompareUnordGtF32,
CompareUnordGeF32,
CompareUnordLtF32,
CompareUnordLeF32,
CompareClassF32,
CompareEqF16,
CompareNeF16,
CompareGtF16,
CompareGeF16,
CompareLtF16,
CompareLeF16,
CompareUnordNeF16,
CompareMaskEqF16,
CompareMaskNeF16,
CompareMaskGtF16,
CompareMaskGeF16,
CompareMaskLtF16,
CompareMaskLeF16,
CompareMaskUnordNeF16,
CompareMaskUnordGeF16,
CompareMaskEqF32,
CompareMaskNeF32,
CompareMaskGtF32,
CompareMaskGeF32,
CompareMaskLtF32,
CompareMaskLeF32,
CompareMaskUnordEqF32,
CompareMaskUnordNeF32,
CompareMaskUnordGtF32,
CompareMaskUnordGeF32,
CompareMaskUnordLtF32,
CompareMaskUnordLeF32,
ConvertByteU32ToF32,
ConvertU32ToF32,
ConvertI32ToF32,
ConvertF32ToU32,
ConvertF32ToI32,
ConvertF32ToF16,
ConvertF16ToF32,
ConvertU16ToF16,
ConvertF16ToU16,
ConvertI16ToF16,
ConvertF16ToI16,
ConvertRoundPlusInfF32ToI32,
ConvertFloorF32ToI32,
ConvertI4ToOffsetF32,
LdexpF32,
PackF32ToF16Rtz,
PackSnorm2x16F32,
PackUnorm2x16F32,
PackU16U32,
PackU8F32,
PackB32F16,
PackedMadI16,
PackedMulLoU16,
PackedAddI16,
PackedSubI16,
PackedLshlrevB16,
PackedLshrrevB16,
PackedAshrrevI16,
PackedMaxI16,
PackedMinI16,
PackedMadU16,
PackedAddU16,
PackedSubU16,
PackedMaxU16,
PackedMinU16,
PackedAddF16,
PackedMulF16,
PackedMinF16,
PackedMaxF16,
PackedFmaF16,
AddF16,
SubF16,
MulF16,
MinF16,
MaxF16,
FmaF16,
MadMixF16,
IAddU16,
ISubI16,
IMinI16,
IMaxI16,
UMinU16,
UMaxU16,
RcpF32,
FractF32,
TruncF32,
CeilF32,
RoundEvenF32,
FloorF32,
Exp2F32,
Log2F32,
InverseSqrtF32,
SqrtF32,
RcpF16,
SqrtF16,
InverseSqrtF16,
Log2F16,
Exp2F16,
FloorF16,
CeilF16,
TruncF16,
RoundEvenF16,
SinF32,
CosF32,
CubeIdF32,
CubeScF32,
CubeTcF32,
CubeMaF32,
FAddF32,
FSubF32,
FMulF32,
FMinF32,
FMaxF32,
FMadF32,
Dot2AccF32F16,
FMin3F32,
FMax3F32,
FMed3F32,
Min3F16,
Max3F16,
Med3F16,
LoadSrtDword,
SLoadDword,
SBufferLoadDword,
BufferLoadUbyte,
BufferLoadSbyte,
BufferLoadUshort,
BufferLoadSshort,
BufferLoadDword,
BufferStoreByte,
BufferStoreShort,
BufferStoreDword,
AtomicSwapU32,
AtomicAddU32,
AtomicSubU32,
AtomicSMinI32,
AtomicUMinU32,
AtomicSMaxI32,
AtomicUMaxU32,
AtomicAndU32,
AtomicOrU32,
AtomicXorU32,
FlatLoadUbyte,
FlatLoadSbyte,
FlatLoadUshort,
FlatLoadSshort,
FlatLoadDword,
FlatStoreByte,
FlatStoreShort,
FlatStoreDword,
DsReadUbyte,
DsReadSbyte,
DsReadUshort,
DsReadSshort,
DsReadB32,
DsWriteByte,
DsWriteShort,
DsWriteB32,
DsMinF32,
DsMaxF32,
DsSwizzleB32,
DsConsume,
DsAppend,
DsWriteAddtidB32,
DsReadAddtidB32,
ImageGetResinfo,
ImageGetLod,
ImageLoad,
ImageStore,
ImageSample,
ImageGather4,
LoadInputF32,
Export,
#define IR_OPCODE(name, category) name,
#include "graphics/shader/recompiler/ir/ShaderIROpcodes.inc"
#undef IR_OPCODE
Count,
};
enum class OpcodeClass { General, Compare, Compare64 };
struct OpcodeInfo {
std::string_view name;
OpcodeClass opcode_class = OpcodeClass::General;
};
inline constexpr OpcodeInfo OPCODE_INFO[] = {
#define IR_OPCODE(name, category) {#name, OpcodeClass::category},
#include "graphics/shader/recompiler/ir/ShaderIROpcodes.inc"
#undef IR_OPCODE
};
static_assert(sizeof(OPCODE_INFO) / sizeof(OPCODE_INFO[0]) == static_cast<size_t>(Opcode::Count));
constexpr const OpcodeInfo& GetOpcodeInfo(Opcode opcode) {
return OPCODE_INFO[static_cast<size_t>(opcode)];
}
constexpr std::string_view OpcodeName(Opcode opcode) {
return GetOpcodeInfo(opcode).name;
}
constexpr bool IsCompareOpcode(Opcode opcode) {
return GetOpcodeInfo(opcode).opcode_class != OpcodeClass::General;
}
constexpr bool IsCompare64Opcode(Opcode opcode) {
return GetOpcodeInfo(opcode).opcode_class == OpcodeClass::Compare64;
}
enum class RegisterFile { Scalar, Vector, Vcc, Exec, Scc, M0 };
struct Register {
@@ -3,16 +3,17 @@
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include <optional>
namespace Libs::Graphics::ShaderRecompiler::IR {
void SetError(std::string* error, const char* message);
Opcode LookupIrOpcode(Decoder::Opcode opcode);
bool IsImplemented(Decoder::Opcode opcode);
bool IsReversedBinary(Decoder::Opcode opcode);
bool IsVectorCarryOutOpcode(Decoder::Opcode opcode);
bool ScalarResultWritesSccNonZero(Decoder::Opcode opcode);
bool ScalarResultIs64Bit(Decoder::Opcode opcode);
std::optional<Opcode> LookupIrOpcode(Decoder::Opcode opcode);
bool IsReversedBinary(Decoder::Opcode opcode);
bool IsVectorCarryOutOpcode(Decoder::Opcode opcode);
bool ScalarResultWritesSccNonZero(Decoder::Opcode opcode);
bool ScalarResultIs64Bit(Decoder::Opcode opcode);
Operand MakeSccOperand();
Operand MakeImmediateU32(uint32_t value);
@@ -157,345 +157,7 @@ std::string InstructionToString(const Instruction& inst) {
text += ExportInfoToString(inst.export_info);
return text;
}
switch (inst.op) {
case Opcode::ControlNop: text += "ControlNop"; break;
case Opcode::Waitcnt: text += "Waitcnt"; break;
case Opcode::Barrier: text += "Barrier"; break;
case Opcode::Sendmsg: text += "Sendmsg"; break;
case Opcode::TtraceData: text += "TtraceData"; break;
case Opcode::InstPrefetch: text += "InstPrefetch"; break;
case Opcode::MoveU32: text += "MoveU32"; break;
case Opcode::MoveF32Bits: text += "MoveF32Bits"; break;
case Opcode::MoveRelDestU32: text += "MoveRelDestU32"; break;
case Opcode::MoveRelSourceU32: text += "MoveRelSourceU32"; break;
case Opcode::MoveU64: text += "MoveU64"; break;
case Opcode::WqmB64: text += "WqmB64"; break;
case Opcode::SaveexecB32: text += "SaveexecB32"; break;
case Opcode::SaveexecB64: text += "SaveexecB64"; break;
case Opcode::ReadFirstLaneU32: text += "ReadFirstLaneU32"; break;
case Opcode::ReadLaneU32: text += "ReadLaneU32"; break;
case Opcode::WriteLaneU32: text += "WriteLaneU32"; break;
case Opcode::Permlane16B32: text += "Permlane16B32"; break;
case Opcode::Permlanex16B32: text += "Permlanex16B32"; break;
case Opcode::AbsI32: text += "AbsI32"; break;
case Opcode::IAddU32: text += "IAddU32"; break;
case Opcode::IAddCarryU32: text += "IAddCarryU32"; break;
case Opcode::ISubBorrowU32: text += "ISubBorrowU32"; break;
case Opcode::ScalarAddCarryU32: text += "ScalarAddCarryU32"; break;
case Opcode::ScalarSubBorrowU32: text += "ScalarSubBorrowU32"; break;
case Opcode::ScalarSubBorrowCarryU32: text += "ScalarSubBorrowCarryU32"; break;
case Opcode::ScalarSignedAddOverflowI32: text += "ScalarSignedAddOverflowI32"; break;
case Opcode::ScalarSignedSubOverflowI32: text += "ScalarSignedSubOverflowI32"; break;
case Opcode::ScalarShiftLeftAddCarryU32: text += "ScalarShiftLeftAddCarryU32"; break;
case Opcode::ISubU32: text += "ISubU32"; break;
case Opcode::IMulU32: text += "IMulU32"; break;
case Opcode::UMulHighU32: text += "UMulHighU32"; break;
case Opcode::SMulHighI32: text += "SMulHighI32"; break;
case Opcode::IMadI24U32: text += "IMadI24U32"; break;
case Opcode::UMadU24U32: text += "UMadU24U32"; break;
case Opcode::UMadU64U32: text += "UMadU64U32"; break;
case Opcode::SadU32: text += "SadU32"; break;
case Opcode::IAdd3U32: text += "IAdd3U32"; break;
case Opcode::IMulI24U32: text += "IMulI24U32"; break;
case Opcode::UMulU24U32: text += "UMulU24U32"; break;
case Opcode::IMinI32: text += "IMinI32"; break;
case Opcode::IMaxI32: text += "IMaxI32"; break;
case Opcode::IMin3I32: text += "IMin3I32"; break;
case Opcode::IMax3I32: text += "IMax3I32"; break;
case Opcode::IMed3I32: text += "IMed3I32"; break;
case Opcode::UMinU32: text += "UMinU32"; break;
case Opcode::UMaxU32: text += "UMaxU32"; break;
case Opcode::UMin3U32: text += "UMin3U32"; break;
case Opcode::UMax3U32: text += "UMax3U32"; break;
case Opcode::UMed3U32: text += "UMed3U32"; break;
case Opcode::BitwiseAndU32: text += "BitwiseAndU32"; break;
case Opcode::BitwiseAndU64: text += "BitwiseAndU64"; break;
case Opcode::BitwiseAndNotU32: text += "BitwiseAndNotU32"; break;
case Opcode::BitwiseAndNotU64: text += "BitwiseAndNotU64"; break;
case Opcode::BitwiseOrU32: text += "BitwiseOrU32"; break;
case Opcode::BitwiseOrU64: text += "BitwiseOrU64"; break;
case Opcode::BitwiseOrNotU32: text += "BitwiseOrNotU32"; break;
case Opcode::BitwiseOrNotU64: text += "BitwiseOrNotU64"; break;
case Opcode::BitwiseAndOrU32: text += "BitwiseAndOrU32"; break;
case Opcode::BitwiseOr3U32: text += "BitwiseOr3U32"; break;
case Opcode::BitwiseXorU32: text += "BitwiseXorU32"; break;
case Opcode::BitwiseXorU64: text += "BitwiseXorU64"; break;
case Opcode::BitwiseXor3U32: text += "BitwiseXor3U32"; break;
case Opcode::BitwiseNandU32: text += "BitwiseNandU32"; break;
case Opcode::BitwiseNandU64: text += "BitwiseNandU64"; break;
case Opcode::BitwiseNorU32: text += "BitwiseNorU32"; break;
case Opcode::BitwiseNorU64: text += "BitwiseNorU64"; break;
case Opcode::BitwiseXnorU32: text += "BitwiseXnorU32"; break;
case Opcode::BitwiseXnorU64: text += "BitwiseXnorU64"; break;
case Opcode::BitwiseNotU32: text += "BitwiseNotU32"; break;
case Opcode::BitwiseNotU64: text += "BitwiseNotU64"; break;
case Opcode::BitClearU32: text += "BitClearU32"; break;
case Opcode::BitSetU32: text += "BitSetU32"; break;
case Opcode::BitReverseU32: text += "BitReverseU32"; break;
case Opcode::BitCountU32: text += "BitCountU32"; break;
case Opcode::BitCountU64: text += "BitCountU64"; break;
case Opcode::BitReplicateB64B32: text += "BitReplicateB64B32"; break;
case Opcode::BitCountAddU32: text += "BitCountAddU32"; break;
case Opcode::MaskedBitCountLowU32: text += "MaskedBitCountLowU32"; break;
case Opcode::MaskedBitCountHighU32: text += "MaskedBitCountHighU32"; break;
case Opcode::FindLsbU32: text += "FindLsbU32"; break;
case Opcode::FindMsbFromHighU32: text += "FindMsbFromHighU32"; break;
case Opcode::FindMsbFromHighU64: text += "FindMsbFromHighU64"; break;
case Opcode::BitFieldMaskU32: text += "BitFieldMaskU32"; break;
case Opcode::BitFieldMaskU64: text += "BitFieldMaskU64"; break;
case Opcode::BitFieldExtractU32: text += "BitFieldExtractU32"; break;
case Opcode::BitFieldExtractU64: text += "BitFieldExtractU64"; break;
case Opcode::BitFieldExtract3U32: text += "BitFieldExtract3U32"; break;
case Opcode::BitFieldExtract3I32: text += "BitFieldExtract3I32"; break;
case Opcode::BitFieldInsertSelectU32: text += "BitFieldInsertSelectU32"; break;
case Opcode::BitCompare0B32: text += "BitCompare0B32"; break;
case Opcode::BitCompare1B32: text += "BitCompare1B32"; break;
case Opcode::AlignBitU32: text += "AlignBitU32"; break;
case Opcode::ShiftLeftAddU32: text += "ShiftLeftAddU32"; break;
case Opcode::AddShiftLeftU32: text += "AddShiftLeftU32"; break;
case Opcode::XorAddU32: text += "XorAddU32"; break;
case Opcode::ShiftLeftOrU32: text += "ShiftLeftOrU32"; break;
case Opcode::ShiftLeftLogicalU32: text += "ShiftLeftLogicalU32"; break;
case Opcode::ShiftLeftLogicalU64: text += "ShiftLeftLogicalU64"; break;
case Opcode::ShiftLeftLogicalU16: text += "ShiftLeftLogicalU16"; break;
case Opcode::ShiftRightLogicalU32: text += "ShiftRightLogicalU32"; break;
case Opcode::ShiftRightLogicalU64: text += "ShiftRightLogicalU64"; break;
case Opcode::ShiftRightLogicalU16: text += "ShiftRightLogicalU16"; break;
case Opcode::ShiftRightArithmeticI32: text += "ShiftRightArithmeticI32"; break;
case Opcode::ShiftRightArithmeticI16: text += "ShiftRightArithmeticI16"; break;
case Opcode::SelectU32: text += "SelectU32"; break;
case Opcode::SelectMaskU32: text += "SelectMaskU32"; break;
case Opcode::SelectF32Bits: text += "SelectF32Bits"; break;
case Opcode::SelectMaskF32Bits: text += "SelectMaskF32Bits"; break;
case Opcode::SelectU64: text += "SelectU64"; break;
case Opcode::PackLowLowU16: text += "PackLowLowU16"; break;
case Opcode::PackLowHighU16: text += "PackLowHighU16"; break;
case Opcode::PackHighHighU16: text += "PackHighHighU16"; break;
case Opcode::CompareFalse: text += "CompareFalse"; break;
case Opcode::CompareTrue: text += "CompareTrue"; break;
case Opcode::CompareEqU32: text += "CompareEqU32"; break;
case Opcode::CompareNeU32: text += "CompareNeU32"; break;
case Opcode::CompareGtU32: text += "CompareGtU32"; break;
case Opcode::CompareGeU32: text += "CompareGeU32"; break;
case Opcode::CompareLtU32: text += "CompareLtU32"; break;
case Opcode::CompareLeU32: text += "CompareLeU32"; break;
case Opcode::CompareNeU64: text += "CompareNeU64"; break;
case Opcode::CompareMaskEqU32: text += "CompareMaskEqU32"; break;
case Opcode::CompareMaskNeU32: text += "CompareMaskNeU32"; break;
case Opcode::CompareMaskGtU32: text += "CompareMaskGtU32"; break;
case Opcode::CompareMaskGeU32: text += "CompareMaskGeU32"; break;
case Opcode::CompareMaskLtU32: text += "CompareMaskLtU32"; break;
case Opcode::CompareMaskLeU32: text += "CompareMaskLeU32"; break;
case Opcode::CompareEqI32: text += "CompareEqI32"; break;
case Opcode::CompareNeI32: text += "CompareNeI32"; break;
case Opcode::CompareGtI32: text += "CompareGtI32"; break;
case Opcode::CompareGeI32: text += "CompareGeI32"; break;
case Opcode::CompareLtI32: text += "CompareLtI32"; break;
case Opcode::CompareLeI32: text += "CompareLeI32"; break;
case Opcode::CompareEqI16: text += "CompareEqI16"; break;
case Opcode::CompareNeI16: text += "CompareNeI16"; break;
case Opcode::CompareGtI16: text += "CompareGtI16"; break;
case Opcode::CompareGeI16: text += "CompareGeI16"; break;
case Opcode::CompareLtI16: text += "CompareLtI16"; break;
case Opcode::CompareLeI16: text += "CompareLeI16"; break;
case Opcode::CompareMaskEqI32: text += "CompareMaskEqI32"; break;
case Opcode::CompareMaskNeI32: text += "CompareMaskNeI32"; break;
case Opcode::CompareMaskGtI32: text += "CompareMaskGtI32"; break;
case Opcode::CompareMaskGeI32: text += "CompareMaskGeI32"; break;
case Opcode::CompareMaskLtI32: text += "CompareMaskLtI32"; break;
case Opcode::CompareMaskLeI32: text += "CompareMaskLeI32"; break;
case Opcode::CompareEqU16: text += "CompareEqU16"; break;
case Opcode::CompareNeU16: text += "CompareNeU16"; break;
case Opcode::CompareGtU16: text += "CompareGtU16"; break;
case Opcode::CompareGeU16: text += "CompareGeU16"; break;
case Opcode::CompareLtU16: text += "CompareLtU16"; break;
case Opcode::CompareLeU16: text += "CompareLeU16"; break;
case Opcode::CompareEqF32: text += "CompareEqF32"; break;
case Opcode::CompareNeF32: text += "CompareNeF32"; break;
case Opcode::CompareGtF32: text += "CompareGtF32"; break;
case Opcode::CompareGeF32: text += "CompareGeF32"; break;
case Opcode::CompareLtF32: text += "CompareLtF32"; break;
case Opcode::CompareLeF32: text += "CompareLeF32"; break;
case Opcode::CompareOrderedF32: text += "CompareOrderedF32"; break;
case Opcode::CompareUnorderedF32: text += "CompareUnorderedF32"; break;
case Opcode::CompareUnordEqF32: text += "CompareUnordEqF32"; break;
case Opcode::CompareUnordNeF32: text += "CompareUnordNeF32"; break;
case Opcode::CompareUnordGtF32: text += "CompareUnordGtF32"; break;
case Opcode::CompareUnordGeF32: text += "CompareUnordGeF32"; break;
case Opcode::CompareUnordLtF32: text += "CompareUnordLtF32"; break;
case Opcode::CompareUnordLeF32: text += "CompareUnordLeF32"; break;
case Opcode::CompareClassF32: text += "CompareClassF32"; break;
case Opcode::CompareEqF16: text += "CompareEqF16"; break;
case Opcode::CompareNeF16: text += "CompareNeF16"; break;
case Opcode::CompareGtF16: text += "CompareGtF16"; break;
case Opcode::CompareGeF16: text += "CompareGeF16"; break;
case Opcode::CompareLtF16: text += "CompareLtF16"; break;
case Opcode::CompareLeF16: text += "CompareLeF16"; break;
case Opcode::CompareUnordNeF16: text += "CompareUnordNeF16"; break;
case Opcode::CompareMaskEqF16: text += "CompareMaskEqF16"; break;
case Opcode::CompareMaskNeF16: text += "CompareMaskNeF16"; break;
case Opcode::CompareMaskGtF16: text += "CompareMaskGtF16"; break;
case Opcode::CompareMaskGeF16: text += "CompareMaskGeF16"; break;
case Opcode::CompareMaskLtF16: text += "CompareMaskLtF16"; break;
case Opcode::CompareMaskLeF16: text += "CompareMaskLeF16"; break;
case Opcode::CompareMaskUnordNeF16: text += "CompareMaskUnordNeF16"; break;
case Opcode::CompareMaskUnordGeF16: text += "CompareMaskUnordGeF16"; break;
case Opcode::CompareMaskEqF32: text += "CompareMaskEqF32"; break;
case Opcode::CompareMaskNeF32: text += "CompareMaskNeF32"; break;
case Opcode::CompareMaskGtF32: text += "CompareMaskGtF32"; break;
case Opcode::CompareMaskGeF32: text += "CompareMaskGeF32"; break;
case Opcode::CompareMaskLtF32: text += "CompareMaskLtF32"; break;
case Opcode::CompareMaskLeF32: text += "CompareMaskLeF32"; break;
case Opcode::CompareMaskUnordEqF32: text += "CompareMaskUnordEqF32"; break;
case Opcode::CompareMaskUnordNeF32: text += "CompareMaskUnordNeF32"; break;
case Opcode::CompareMaskUnordGtF32: text += "CompareMaskUnordGtF32"; break;
case Opcode::CompareMaskUnordGeF32: text += "CompareMaskUnordGeF32"; break;
case Opcode::CompareMaskUnordLtF32: text += "CompareMaskUnordLtF32"; break;
case Opcode::CompareMaskUnordLeF32: text += "CompareMaskUnordLeF32"; break;
case Opcode::ConvertByteU32ToF32: text += "ConvertByteU32ToF32"; break;
case Opcode::ConvertU32ToF32: text += "ConvertU32ToF32"; break;
case Opcode::ConvertI32ToF32: text += "ConvertI32ToF32"; break;
case Opcode::ConvertF32ToU32: text += "ConvertF32ToU32"; break;
case Opcode::ConvertF32ToI32: text += "ConvertF32ToI32"; break;
case Opcode::ConvertF32ToF16: text += "ConvertF32ToF16"; break;
case Opcode::ConvertF16ToF32: text += "ConvertF16ToF32"; break;
case Opcode::ConvertU16ToF16: text += "ConvertU16ToF16"; break;
case Opcode::ConvertF16ToU16: text += "ConvertF16ToU16"; break;
case Opcode::ConvertI16ToF16: text += "ConvertI16ToF16"; break;
case Opcode::ConvertF16ToI16: text += "ConvertF16ToI16"; break;
case Opcode::ConvertRoundPlusInfF32ToI32: text += "ConvertRoundPlusInfF32ToI32"; break;
case Opcode::ConvertFloorF32ToI32: text += "ConvertFloorF32ToI32"; break;
case Opcode::ConvertI4ToOffsetF32: text += "ConvertI4ToOffsetF32"; break;
case Opcode::LdexpF32: text += "LdexpF32"; break;
case Opcode::PackF32ToF16Rtz: text += "PackF32ToF16Rtz"; break;
case Opcode::PackSnorm2x16F32: text += "PackSnorm2x16F32"; break;
case Opcode::PackUnorm2x16F32: text += "PackUnorm2x16F32"; break;
case Opcode::PackU16U32: text += "PackU16U32"; break;
case Opcode::PackU8F32: text += "PackU8F32"; break;
case Opcode::PackB32F16: text += "PackB32F16"; break;
case Opcode::PackedMadI16: text += "PackedMadI16"; break;
case Opcode::PackedMulLoU16: text += "PackedMulLoU16"; break;
case Opcode::PackedAddI16: text += "PackedAddI16"; break;
case Opcode::PackedSubI16: text += "PackedSubI16"; break;
case Opcode::PackedLshlrevB16: text += "PackedLshlrevB16"; break;
case Opcode::PackedLshrrevB16: text += "PackedLshrrevB16"; break;
case Opcode::PackedAshrrevI16: text += "PackedAshrrevI16"; break;
case Opcode::PackedMaxI16: text += "PackedMaxI16"; break;
case Opcode::PackedMinI16: text += "PackedMinI16"; break;
case Opcode::PackedMadU16: text += "PackedMadU16"; break;
case Opcode::PackedAddU16: text += "PackedAddU16"; break;
case Opcode::PackedSubU16: text += "PackedSubU16"; break;
case Opcode::PackedMaxU16: text += "PackedMaxU16"; break;
case Opcode::PackedMinU16: text += "PackedMinU16"; break;
case Opcode::PackedAddF16: text += "PackedAddF16"; break;
case Opcode::PackedMulF16: text += "PackedMulF16"; break;
case Opcode::PackedMinF16: text += "PackedMinF16"; break;
case Opcode::PackedMaxF16: text += "PackedMaxF16"; break;
case Opcode::PackedFmaF16: text += "PackedFmaF16"; break;
case Opcode::AddF16: text += "AddF16"; break;
case Opcode::SubF16: text += "SubF16"; break;
case Opcode::MulF16: text += "MulF16"; break;
case Opcode::MinF16: text += "MinF16"; break;
case Opcode::MaxF16: text += "MaxF16"; break;
case Opcode::FmaF16: text += "FmaF16"; break;
case Opcode::MadMixF16: text += "MadMixF16"; break;
case Opcode::IAddU16: text += "IAddU16"; break;
case Opcode::ISubI16: text += "ISubI16"; break;
case Opcode::IMinI16: text += "IMinI16"; break;
case Opcode::IMaxI16: text += "IMaxI16"; break;
case Opcode::UMinU16: text += "UMinU16"; break;
case Opcode::UMaxU16: text += "UMaxU16"; break;
case Opcode::RcpF32: text += "RcpF32"; break;
case Opcode::FractF32: text += "FractF32"; break;
case Opcode::TruncF32: text += "TruncF32"; break;
case Opcode::CeilF32: text += "CeilF32"; break;
case Opcode::RoundEvenF32: text += "RoundEvenF32"; break;
case Opcode::FloorF32: text += "FloorF32"; break;
case Opcode::Exp2F32: text += "Exp2F32"; break;
case Opcode::Log2F32: text += "Log2F32"; break;
case Opcode::InverseSqrtF32: text += "InverseSqrtF32"; break;
case Opcode::SqrtF32: text += "SqrtF32"; break;
case Opcode::RcpF16: text += "RcpF16"; break;
case Opcode::SqrtF16: text += "SqrtF16"; break;
case Opcode::InverseSqrtF16: text += "InverseSqrtF16"; break;
case Opcode::Log2F16: text += "Log2F16"; break;
case Opcode::Exp2F16: text += "Exp2F16"; break;
case Opcode::FloorF16: text += "FloorF16"; break;
case Opcode::CeilF16: text += "CeilF16"; break;
case Opcode::TruncF16: text += "TruncF16"; break;
case Opcode::RoundEvenF16: text += "RoundEvenF16"; break;
case Opcode::SinF32: text += "SinF32"; break;
case Opcode::CosF32: text += "CosF32"; break;
case Opcode::CubeIdF32: text += "CubeIdF32"; break;
case Opcode::CubeScF32: text += "CubeScF32"; break;
case Opcode::CubeTcF32: text += "CubeTcF32"; break;
case Opcode::CubeMaF32: text += "CubeMaF32"; break;
case Opcode::FAddF32: text += "FAddF32"; break;
case Opcode::FSubF32: text += "FSubF32"; break;
case Opcode::FMulF32: text += "FMulF32"; break;
case Opcode::FMinF32: text += "FMinF32"; break;
case Opcode::FMaxF32: text += "FMaxF32"; break;
case Opcode::FMadF32: text += "FMadF32"; break;
case Opcode::Dot2AccF32F16: text += "Dot2AccF32F16"; break;
case Opcode::FMin3F32: text += "FMin3F32"; break;
case Opcode::FMax3F32: text += "FMax3F32"; break;
case Opcode::FMed3F32: text += "FMed3F32"; break;
case Opcode::Min3F16: text += "Min3F16"; break;
case Opcode::Max3F16: text += "Max3F16"; break;
case Opcode::Med3F16: text += "Med3F16"; break;
case Opcode::LoadSrtDword: text += "LoadSrtDword"; break;
case Opcode::SLoadDword: text += "SLoadDword"; break;
case Opcode::SBufferLoadDword: text += "SBufferLoadDword"; break;
case Opcode::BufferLoadUbyte: text += "BufferLoadUbyte"; break;
case Opcode::BufferLoadSbyte: text += "BufferLoadSbyte"; break;
case Opcode::BufferLoadUshort: text += "BufferLoadUshort"; break;
case Opcode::BufferLoadSshort: text += "BufferLoadSshort"; break;
case Opcode::BufferLoadDword: text += "BufferLoadDword"; break;
case Opcode::BufferStoreByte: text += "BufferStoreByte"; break;
case Opcode::BufferStoreShort: text += "BufferStoreShort"; break;
case Opcode::BufferStoreDword: text += "BufferStoreDword"; break;
case Opcode::AtomicSwapU32: text += "AtomicSwapU32"; break;
case Opcode::AtomicAddU32: text += "AtomicAddU32"; break;
case Opcode::AtomicSubU32: text += "AtomicSubU32"; break;
case Opcode::AtomicSMinI32: text += "AtomicSMinI32"; break;
case Opcode::AtomicUMinU32: text += "AtomicUMinU32"; break;
case Opcode::AtomicSMaxI32: text += "AtomicSMaxI32"; break;
case Opcode::AtomicUMaxU32: text += "AtomicUMaxU32"; break;
case Opcode::AtomicAndU32: text += "AtomicAndU32"; break;
case Opcode::AtomicOrU32: text += "AtomicOrU32"; break;
case Opcode::AtomicXorU32: text += "AtomicXorU32"; break;
case Opcode::FlatLoadUbyte: text += "FlatLoadUbyte"; break;
case Opcode::FlatLoadSbyte: text += "FlatLoadSbyte"; break;
case Opcode::FlatLoadUshort: text += "FlatLoadUshort"; break;
case Opcode::FlatLoadSshort: text += "FlatLoadSshort"; break;
case Opcode::FlatLoadDword: text += "FlatLoadDword"; break;
case Opcode::FlatStoreByte: text += "FlatStoreByte"; break;
case Opcode::FlatStoreShort: text += "FlatStoreShort"; break;
case Opcode::FlatStoreDword: text += "FlatStoreDword"; break;
case Opcode::DsReadUbyte: text += "DsReadUbyte"; break;
case Opcode::DsReadSbyte: text += "DsReadSbyte"; break;
case Opcode::DsReadUshort: text += "DsReadUshort"; break;
case Opcode::DsReadSshort: text += "DsReadSshort"; break;
case Opcode::DsReadB32: text += "DsReadB32"; break;
case Opcode::DsWriteByte: text += "DsWriteByte"; break;
case Opcode::DsWriteShort: text += "DsWriteShort"; break;
case Opcode::DsWriteB32: text += "DsWriteB32"; break;
case Opcode::DsMinF32: text += "DsMinF32"; break;
case Opcode::DsMaxF32: text += "DsMaxF32"; break;
case Opcode::DsSwizzleB32: text += "DsSwizzleB32"; break;
case Opcode::DsConsume: text += "DsConsume"; break;
case Opcode::DsAppend: text += "DsAppend"; break;
case Opcode::DsWriteAddtidB32: text += "DsWriteAddtidB32"; break;
case Opcode::DsReadAddtidB32: text += "DsReadAddtidB32"; break;
case Opcode::ImageGetResinfo: text += "ImageGetResinfo"; break;
case Opcode::ImageGetLod: text += "ImageGetLod"; break;
case Opcode::ImageLoad: text += "ImageLoad"; break;
case Opcode::ImageStore: text += "ImageStore"; break;
case Opcode::ImageSample: text += "ImageSample"; break;
case Opcode::ImageGather4: text += "ImageGather4"; break;
case Opcode::LoadInputF32: text += "LoadInputF32"; break;
case Opcode::Export: text += "Export"; break;
}
text += OpcodeName(inst.op);
text += " ";
text += OperandToString(inst.dst);
if (inst.dst2.kind != OperandKind::Null) {
@@ -569,6 +569,10 @@ bool LowerMemoryInstruction(const Decoder::Instruction& decoded, BasicBlock& blo
return LowerBufferAtomicDword(decoded, block, Opcode::AtomicOrU32, error);
case Decoder::Opcode::BufferAtomicXor:
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:
@@ -673,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;
}
}
@@ -729,6 +738,8 @@ bool IsMemoryOpcode(Decoder::Opcode opcode) {
case Decoder::Opcode::BufferAtomicAnd:
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:
@@ -82,6 +82,7 @@ constexpr LowerMap LOWER_OPS[] = {
{Decoder::Opcode::SCmpGeU32, Opcode::CompareGeU32},
{Decoder::Opcode::SCmpLtU32, Opcode::CompareLtU32},
{Decoder::Opcode::SCmpLeU32, Opcode::CompareLeU32},
{Decoder::Opcode::SCmpEqU64, Opcode::CompareEqU64},
{Decoder::Opcode::SCmpLgU64, Opcode::CompareNeU64},
{Decoder::Opcode::VReadfirstlaneB32, Opcode::ReadFirstLaneU32},
{Decoder::Opcode::VReadlaneB32, Opcode::ReadLaneU32},
@@ -325,6 +326,7 @@ constexpr LowerMap LOWER_OPS[] = {
{Decoder::Opcode::VCmpNeU32, Opcode::CompareNeU32},
{Decoder::Opcode::VCmpGeU32, Opcode::CompareGeU32},
{Decoder::Opcode::VCmpTU32, Opcode::CompareTrue},
{Decoder::Opcode::VCmpEqI64, Opcode::CompareEqU64},
{Decoder::Opcode::VCmpNeU64, Opcode::CompareNeU64},
{Decoder::Opcode::VCmpxLtU32, Opcode::CompareMaskLtU32},
{Decoder::Opcode::VCmpxEqU32, Opcode::CompareMaskEqU32},
@@ -350,6 +352,8 @@ constexpr LowerMap LOWER_OPS[] = {
{Decoder::Opcode::BufferAtomicAnd, Opcode::AtomicAndU32},
{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},
@@ -432,22 +436,13 @@ void SetError(std::string* error, const char* message) {
}
}
Opcode LookupIrOpcode(Decoder::Opcode opcode) {
std::optional<Opcode> LookupIrOpcode(Decoder::Opcode opcode) {
for (const auto& op: LOWER_OPS) {
if (op.decoded == opcode) {
return op.ir;
}
}
return Opcode::MoveU32;
}
bool IsImplemented(Decoder::Opcode opcode) {
for (const auto& op: LOWER_OPS) {
if (op.decoded == opcode) {
return true;
}
}
return false;
return std::nullopt;
}
bool IsReversedBinary(Decoder::Opcode opcode) {
@@ -0,0 +1,342 @@
// Central IR opcode catalog. Keep semantic categories here so enum identity, logging,
// and generic analysis cannot drift apart.
IR_OPCODE(ControlNop, General)
IR_OPCODE(Waitcnt, General)
IR_OPCODE(Barrier, General)
IR_OPCODE(Sendmsg, General)
IR_OPCODE(TtraceData, General)
IR_OPCODE(InstPrefetch, General)
IR_OPCODE(MoveU32, General)
IR_OPCODE(MoveF32Bits, General)
IR_OPCODE(MoveRelDestU32, General)
IR_OPCODE(MoveRelSourceU32, General)
IR_OPCODE(MoveU64, General)
IR_OPCODE(WqmB64, General)
IR_OPCODE(SaveexecB32, General)
IR_OPCODE(SaveexecB64, General)
IR_OPCODE(ReadFirstLaneU32, General)
IR_OPCODE(ReadLaneU32, General)
IR_OPCODE(WriteLaneU32, General)
IR_OPCODE(Permlane16B32, General)
IR_OPCODE(Permlanex16B32, General)
IR_OPCODE(AbsI32, General)
IR_OPCODE(IAddU32, General)
IR_OPCODE(IAddCarryU32, General)
IR_OPCODE(ISubBorrowU32, General)
IR_OPCODE(ScalarAddCarryU32, General)
IR_OPCODE(ScalarSubBorrowU32, General)
IR_OPCODE(ScalarSubBorrowCarryU32, General)
IR_OPCODE(ScalarSignedAddOverflowI32, General)
IR_OPCODE(ScalarSignedSubOverflowI32, General)
IR_OPCODE(ScalarShiftLeftAddCarryU32, General)
IR_OPCODE(ISubU32, General)
IR_OPCODE(IMulU32, General)
IR_OPCODE(UMulHighU32, General)
IR_OPCODE(SMulHighI32, General)
IR_OPCODE(IMadI24U32, General)
IR_OPCODE(UMadU24U32, General)
IR_OPCODE(UMadU64U32, General)
IR_OPCODE(SadU32, General)
IR_OPCODE(IAdd3U32, General)
IR_OPCODE(IMulI24U32, General)
IR_OPCODE(UMulU24U32, General)
IR_OPCODE(IMinI32, General)
IR_OPCODE(IMaxI32, General)
IR_OPCODE(IMin3I32, General)
IR_OPCODE(IMax3I32, General)
IR_OPCODE(IMed3I32, General)
IR_OPCODE(UMinU32, General)
IR_OPCODE(UMaxU32, General)
IR_OPCODE(UMin3U32, General)
IR_OPCODE(UMax3U32, General)
IR_OPCODE(UMed3U32, General)
IR_OPCODE(BitwiseAndU32, General)
IR_OPCODE(BitwiseAndU64, General)
IR_OPCODE(BitwiseAndNotU32, General)
IR_OPCODE(BitwiseAndNotU64, General)
IR_OPCODE(BitwiseOrU32, General)
IR_OPCODE(BitwiseOrU64, General)
IR_OPCODE(BitwiseOrNotU32, General)
IR_OPCODE(BitwiseOrNotU64, General)
IR_OPCODE(BitwiseAndOrU32, General)
IR_OPCODE(BitwiseOr3U32, General)
IR_OPCODE(BitwiseXorU32, General)
IR_OPCODE(BitwiseXorU64, General)
IR_OPCODE(BitwiseXor3U32, General)
IR_OPCODE(BitwiseNandU32, General)
IR_OPCODE(BitwiseNandU64, General)
IR_OPCODE(BitwiseNorU32, General)
IR_OPCODE(BitwiseNorU64, General)
IR_OPCODE(BitwiseXnorU32, General)
IR_OPCODE(BitwiseXnorU64, General)
IR_OPCODE(BitwiseNotU32, General)
IR_OPCODE(BitwiseNotU64, General)
IR_OPCODE(BitClearU32, General)
IR_OPCODE(BitSetU32, General)
IR_OPCODE(BitReverseU32, General)
IR_OPCODE(BitCountU32, General)
IR_OPCODE(BitCountU64, General)
IR_OPCODE(BitReplicateB64B32, General)
IR_OPCODE(BitCountAddU32, General)
IR_OPCODE(MaskedBitCountLowU32, General)
IR_OPCODE(MaskedBitCountHighU32, General)
IR_OPCODE(FindLsbU32, General)
IR_OPCODE(FindMsbFromHighU32, General)
IR_OPCODE(FindMsbFromHighU64, General)
IR_OPCODE(BitFieldMaskU32, General)
IR_OPCODE(BitFieldMaskU64, General)
IR_OPCODE(BitFieldExtractU32, General)
IR_OPCODE(BitFieldExtractU64, General)
IR_OPCODE(BitFieldExtract3U32, General)
IR_OPCODE(BitFieldExtract3I32, General)
IR_OPCODE(BitFieldInsertSelectU32, General)
IR_OPCODE(BitCompare0B32, General)
IR_OPCODE(BitCompare1B32, General)
IR_OPCODE(AlignBitU32, General)
IR_OPCODE(ShiftLeftAddU32, General)
IR_OPCODE(AddShiftLeftU32, General)
IR_OPCODE(XorAddU32, General)
IR_OPCODE(ShiftLeftOrU32, General)
IR_OPCODE(ShiftLeftLogicalU32, General)
IR_OPCODE(ShiftLeftLogicalU64, General)
IR_OPCODE(ShiftLeftLogicalU16, General)
IR_OPCODE(ShiftRightLogicalU32, General)
IR_OPCODE(ShiftRightLogicalU64, General)
IR_OPCODE(ShiftRightLogicalU16, General)
IR_OPCODE(ShiftRightArithmeticI32, General)
IR_OPCODE(ShiftRightArithmeticI16, General)
IR_OPCODE(SelectU32, General)
IR_OPCODE(SelectMaskU32, General)
IR_OPCODE(SelectF32Bits, General)
IR_OPCODE(SelectMaskF32Bits, General)
IR_OPCODE(SelectU64, General)
IR_OPCODE(PackLowLowU16, General)
IR_OPCODE(PackLowHighU16, General)
IR_OPCODE(PackHighHighU16, General)
IR_OPCODE(CompareFalse, Compare)
IR_OPCODE(CompareTrue, Compare)
IR_OPCODE(CompareEqU32, Compare)
IR_OPCODE(CompareNeU32, Compare)
IR_OPCODE(CompareGtU32, Compare)
IR_OPCODE(CompareGeU32, Compare)
IR_OPCODE(CompareLtU32, Compare)
IR_OPCODE(CompareLeU32, Compare)
IR_OPCODE(CompareEqU64, Compare64)
IR_OPCODE(CompareNeU64, Compare64)
IR_OPCODE(CompareMaskEqU32, Compare)
IR_OPCODE(CompareMaskNeU32, Compare)
IR_OPCODE(CompareMaskGtU32, Compare)
IR_OPCODE(CompareMaskGeU32, Compare)
IR_OPCODE(CompareMaskLtU32, Compare)
IR_OPCODE(CompareMaskLeU32, Compare)
IR_OPCODE(CompareEqI32, Compare)
IR_OPCODE(CompareNeI32, Compare)
IR_OPCODE(CompareGtI32, Compare)
IR_OPCODE(CompareGeI32, Compare)
IR_OPCODE(CompareLtI32, Compare)
IR_OPCODE(CompareLeI32, Compare)
IR_OPCODE(CompareEqI16, Compare)
IR_OPCODE(CompareNeI16, Compare)
IR_OPCODE(CompareGtI16, Compare)
IR_OPCODE(CompareGeI16, Compare)
IR_OPCODE(CompareLtI16, Compare)
IR_OPCODE(CompareLeI16, Compare)
IR_OPCODE(CompareMaskEqI32, Compare)
IR_OPCODE(CompareMaskNeI32, Compare)
IR_OPCODE(CompareMaskGtI32, Compare)
IR_OPCODE(CompareMaskGeI32, Compare)
IR_OPCODE(CompareMaskLtI32, Compare)
IR_OPCODE(CompareMaskLeI32, Compare)
IR_OPCODE(CompareEqU16, Compare)
IR_OPCODE(CompareNeU16, Compare)
IR_OPCODE(CompareGtU16, Compare)
IR_OPCODE(CompareGeU16, Compare)
IR_OPCODE(CompareLtU16, Compare)
IR_OPCODE(CompareLeU16, Compare)
IR_OPCODE(CompareEqF32, Compare)
IR_OPCODE(CompareNeF32, Compare)
IR_OPCODE(CompareGtF32, Compare)
IR_OPCODE(CompareGeF32, Compare)
IR_OPCODE(CompareLtF32, Compare)
IR_OPCODE(CompareLeF32, Compare)
IR_OPCODE(CompareOrderedF32, Compare)
IR_OPCODE(CompareUnorderedF32, Compare)
IR_OPCODE(CompareUnordEqF32, Compare)
IR_OPCODE(CompareUnordNeF32, Compare)
IR_OPCODE(CompareUnordGtF32, Compare)
IR_OPCODE(CompareUnordGeF32, Compare)
IR_OPCODE(CompareUnordLtF32, Compare)
IR_OPCODE(CompareUnordLeF32, Compare)
IR_OPCODE(CompareClassF32, Compare)
IR_OPCODE(CompareEqF16, Compare)
IR_OPCODE(CompareNeF16, Compare)
IR_OPCODE(CompareGtF16, Compare)
IR_OPCODE(CompareGeF16, Compare)
IR_OPCODE(CompareLtF16, Compare)
IR_OPCODE(CompareLeF16, Compare)
IR_OPCODE(CompareUnordNeF16, Compare)
IR_OPCODE(CompareMaskEqF16, Compare)
IR_OPCODE(CompareMaskNeF16, Compare)
IR_OPCODE(CompareMaskGtF16, Compare)
IR_OPCODE(CompareMaskGeF16, Compare)
IR_OPCODE(CompareMaskLtF16, Compare)
IR_OPCODE(CompareMaskLeF16, Compare)
IR_OPCODE(CompareMaskUnordNeF16, Compare)
IR_OPCODE(CompareMaskUnordGeF16, Compare)
IR_OPCODE(CompareMaskEqF32, Compare)
IR_OPCODE(CompareMaskNeF32, Compare)
IR_OPCODE(CompareMaskGtF32, Compare)
IR_OPCODE(CompareMaskGeF32, Compare)
IR_OPCODE(CompareMaskLtF32, Compare)
IR_OPCODE(CompareMaskLeF32, Compare)
IR_OPCODE(CompareMaskUnordEqF32, Compare)
IR_OPCODE(CompareMaskUnordNeF32, Compare)
IR_OPCODE(CompareMaskUnordGtF32, Compare)
IR_OPCODE(CompareMaskUnordGeF32, Compare)
IR_OPCODE(CompareMaskUnordLtF32, Compare)
IR_OPCODE(CompareMaskUnordLeF32, Compare)
IR_OPCODE(ConvertByteU32ToF32, General)
IR_OPCODE(ConvertU32ToF32, General)
IR_OPCODE(ConvertI32ToF32, General)
IR_OPCODE(ConvertF32ToU32, General)
IR_OPCODE(ConvertF32ToI32, General)
IR_OPCODE(ConvertF32ToF16, General)
IR_OPCODE(ConvertF16ToF32, General)
IR_OPCODE(ConvertU16ToF16, General)
IR_OPCODE(ConvertF16ToU16, General)
IR_OPCODE(ConvertI16ToF16, General)
IR_OPCODE(ConvertF16ToI16, General)
IR_OPCODE(ConvertRoundPlusInfF32ToI32, General)
IR_OPCODE(ConvertFloorF32ToI32, General)
IR_OPCODE(ConvertI4ToOffsetF32, General)
IR_OPCODE(LdexpF32, General)
IR_OPCODE(PackF32ToF16Rtz, General)
IR_OPCODE(PackSnorm2x16F32, General)
IR_OPCODE(PackUnorm2x16F32, General)
IR_OPCODE(PackU16U32, General)
IR_OPCODE(PackU8F32, General)
IR_OPCODE(PackB32F16, General)
IR_OPCODE(PackedMadI16, General)
IR_OPCODE(PackedMulLoU16, General)
IR_OPCODE(PackedAddI16, General)
IR_OPCODE(PackedSubI16, General)
IR_OPCODE(PackedLshlrevB16, General)
IR_OPCODE(PackedLshrrevB16, General)
IR_OPCODE(PackedAshrrevI16, General)
IR_OPCODE(PackedMaxI16, General)
IR_OPCODE(PackedMinI16, General)
IR_OPCODE(PackedMadU16, General)
IR_OPCODE(PackedAddU16, General)
IR_OPCODE(PackedSubU16, General)
IR_OPCODE(PackedMaxU16, General)
IR_OPCODE(PackedMinU16, General)
IR_OPCODE(PackedAddF16, General)
IR_OPCODE(PackedMulF16, General)
IR_OPCODE(PackedMinF16, General)
IR_OPCODE(PackedMaxF16, General)
IR_OPCODE(PackedFmaF16, General)
IR_OPCODE(AddF16, General)
IR_OPCODE(SubF16, General)
IR_OPCODE(MulF16, General)
IR_OPCODE(MinF16, General)
IR_OPCODE(MaxF16, General)
IR_OPCODE(FmaF16, General)
IR_OPCODE(MadMixF16, General)
IR_OPCODE(IAddU16, General)
IR_OPCODE(ISubI16, General)
IR_OPCODE(IMinI16, General)
IR_OPCODE(IMaxI16, General)
IR_OPCODE(UMinU16, General)
IR_OPCODE(UMaxU16, General)
IR_OPCODE(RcpF32, General)
IR_OPCODE(FractF32, General)
IR_OPCODE(TruncF32, General)
IR_OPCODE(CeilF32, General)
IR_OPCODE(RoundEvenF32, General)
IR_OPCODE(FloorF32, General)
IR_OPCODE(Exp2F32, General)
IR_OPCODE(Log2F32, General)
IR_OPCODE(InverseSqrtF32, General)
IR_OPCODE(SqrtF32, General)
IR_OPCODE(RcpF16, General)
IR_OPCODE(SqrtF16, General)
IR_OPCODE(InverseSqrtF16, General)
IR_OPCODE(Log2F16, General)
IR_OPCODE(Exp2F16, General)
IR_OPCODE(FloorF16, General)
IR_OPCODE(CeilF16, General)
IR_OPCODE(TruncF16, General)
IR_OPCODE(RoundEvenF16, General)
IR_OPCODE(SinF32, General)
IR_OPCODE(CosF32, General)
IR_OPCODE(CubeIdF32, General)
IR_OPCODE(CubeScF32, General)
IR_OPCODE(CubeTcF32, General)
IR_OPCODE(CubeMaF32, General)
IR_OPCODE(FAddF32, General)
IR_OPCODE(FSubF32, General)
IR_OPCODE(FMulF32, General)
IR_OPCODE(FMinF32, General)
IR_OPCODE(FMaxF32, General)
IR_OPCODE(FMadF32, General)
IR_OPCODE(Dot2AccF32F16, General)
IR_OPCODE(FMin3F32, General)
IR_OPCODE(FMax3F32, General)
IR_OPCODE(FMed3F32, General)
IR_OPCODE(Min3F16, General)
IR_OPCODE(Max3F16, General)
IR_OPCODE(Med3F16, General)
IR_OPCODE(LoadSrtDword, General)
IR_OPCODE(SLoadDword, General)
IR_OPCODE(SBufferLoadDword, General)
IR_OPCODE(BufferLoadUbyte, General)
IR_OPCODE(BufferLoadSbyte, General)
IR_OPCODE(BufferLoadUshort, General)
IR_OPCODE(BufferLoadSshort, General)
IR_OPCODE(BufferLoadDword, General)
IR_OPCODE(BufferStoreByte, General)
IR_OPCODE(BufferStoreShort, General)
IR_OPCODE(BufferStoreDword, General)
IR_OPCODE(AtomicSwapU32, General)
IR_OPCODE(AtomicAddU32, General)
IR_OPCODE(AtomicSubU32, General)
IR_OPCODE(AtomicSMinI32, General)
IR_OPCODE(AtomicUMinU32, General)
IR_OPCODE(AtomicSMaxI32, General)
IR_OPCODE(AtomicUMaxU32, General)
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)
IR_OPCODE(FlatLoadSshort, General)
IR_OPCODE(FlatLoadDword, General)
IR_OPCODE(FlatStoreByte, General)
IR_OPCODE(FlatStoreShort, General)
IR_OPCODE(FlatStoreDword, General)
IR_OPCODE(DsReadUbyte, General)
IR_OPCODE(DsReadSbyte, General)
IR_OPCODE(DsReadUshort, General)
IR_OPCODE(DsReadSshort, General)
IR_OPCODE(DsReadB32, General)
IR_OPCODE(DsWriteByte, General)
IR_OPCODE(DsWriteShort, General)
IR_OPCODE(DsWriteB32, General)
IR_OPCODE(DsMinF32, General)
IR_OPCODE(DsMaxF32, General)
IR_OPCODE(DsSwizzleB32, General)
IR_OPCODE(DsConsume, General)
IR_OPCODE(DsAppend, General)
IR_OPCODE(DsWriteAddtidB32, General)
IR_OPCODE(DsReadAddtidB32, General)
IR_OPCODE(ImageGetResinfo, General)
IR_OPCODE(ImageGetLod, General)
IR_OPCODE(ImageLoad, General)
IR_OPCODE(ImageStore, General)
IR_OPCODE(ImageSample, General)
IR_OPCODE(ImageGather4, General)
IR_OPCODE(LoadInputF32, General)
IR_OPCODE(Export, General)
+1 -3
View File
@@ -803,7 +803,7 @@ int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence) {
return KERNEL_ERROR_ESPIPE;
}
file->mutex.Lock();
Common::LockGuard lock(file->mutex);
bool is_invalid = file->f.IsInvalid();
@@ -828,8 +828,6 @@ int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence) {
EXIT_IF(pos != offset);
file->mutex.Unlock();
if (is_invalid) {
LOGF("\tfile is invalid\n");
return KERNEL_ERROR_EIO;
+1 -1
View File
@@ -88,7 +88,7 @@ public:
bool fullscreen_enabled = false;
int vblank_frequency = 60;
int console_language = DEFAULT_CONSOLE_LANGUAGE;
bool vulkan_validation_enabled = true;
bool vulkan_validation_enabled = false;
bool shader_validation_enabled = true;
ShaderOptimizationType shader_optimization_type = ShaderOptimizationType::Performance;
ShaderLogDirection shader_log_direction = ShaderLogDirection::Silent;
+19 -5
View File
@@ -82,6 +82,7 @@ public:
Id AudioOutOpen(int type, uint32_t samples_num, uint32_t freq, Format format);
bool AudioOutClose(Id handle);
bool AudioOutValid(Id handle);
bool AudioOutHasDevice(Id handle);
bool AudioOutSetVolume(Id handle, uint32_t bitflag, const int* volume);
uint32_t AudioOutOutputs(OutputParam* params, uint32_t num, bool blocking = true);
bool AudioOutGetStatus(Id handle, int* type, int* channels_num);
@@ -152,6 +153,10 @@ void AudioOutClose(int handle) {
}
}
bool AudioOutHasDevice(int handle) {
return g_audio != nullptr && handle > 0 && g_audio->AudioOutHasDevice(Audio::Id(handle));
}
uint32_t AudioOutOutputs(const OutputParam* params, uint32_t num, bool blocking) {
if (g_audio == nullptr || params == nullptr || num == 0) {
return 0;
@@ -451,6 +456,13 @@ bool Audio::AudioOutValid(Id handle) {
m_out_ports[handle.GetId()].used);
}
bool Audio::AudioOutHasDevice(Id handle) {
Common::LockGuard lock(m_mutex);
return (handle.GetId() >= 0 && handle.GetId() < OUT_PORTS_MAX &&
m_out_ports[handle.GetId()].used && m_out_ports[handle.GetId()].audio_device != 0);
}
bool Audio::AudioOutGetStatus(Id handle, int* type, int* channels_num) {
Common::LockGuard lock(m_mutex);
@@ -516,16 +528,18 @@ uint32_t Audio::AudioOutOutputs(OutputParam* params, uint32_t num, bool blocking
max_wait_time = (wait_time > max_wait_time ? wait_time : max_wait_time);
}
bool all_ports_have_device = true;
bool any_port_has_device = false;
for (uint32_t i = 0; i < num; i++) {
if (m_out_ports[params[i].handle.GetId()].audio_device == 0) {
all_ports_have_device = false;
if (m_out_ports[params[i].handle.GetId()].audio_device != 0) {
any_port_has_device = true;
break;
}
}
// Device-backed ports are paced by the SDL queue above.
if (blocking && max_wait_time != 0 && !all_ports_have_device) {
// One real output device is enough to pace the whole synchronized batch. Applying the fallback
// when a vibration port is present would rate-limit the device-backed ports to exactly 1x and
// prevent their SDL queues from building an underrun cushion.
if (blocking && max_wait_time != 0 && !any_port_has_device) {
Common::Thread::SleepMicro(max_wait_time);
}
+1
View File
@@ -26,6 +26,7 @@ static constexpr int OUT_PORTS_MAX = 32;
int AudioOutOpen(int type, uint32_t samples_num, uint32_t freq, Format format);
void AudioOutClose(int handle);
bool AudioOutHasDevice(int handle);
uint32_t AudioOutOutputs(const OutputParam* params, uint32_t num, bool blocking = true);
} // namespace Libs::Audio::AudioInternal
+21 -2
View File
@@ -336,6 +336,17 @@ static AudioOut2PortStateEntry* audioout2_find_port_locked(AudioOut2PortHandle p
return nullptr;
}
static bool audioout2_context_has_queueable_device(AudioOut2ContextHandle ctx) {
Common::LockGuard lock(g_audioout2_port_mutex);
for (const auto& state: g_audioout2_ports) {
if (state.used && state.context == ctx && state.audio_handle > 0 &&
state.pcm_data != nullptr && AudioInternal::AudioOutHasDevice(state.audio_handle)) {
return true;
}
}
return false;
}
static void audioout2_queue_context_audio(AudioOut2ContextHandle ctx, bool blocking) {
std::vector<AudioInternal::OutputParam> params;
params.reserve(AudioInternal::OUT_PORTS_MAX);
@@ -493,15 +504,23 @@ int KYTY_SYSV_ABI AudioOut2ContextPush(AudioOut2ContextHandle ctx, uint32_t bloc
uint32_t sleep_micros = audioout2_grain_micros(512);
for (;;) {
// Only a synchronous submission carrying PCM to a real device can rely on the SDL queue for
// pacing. Async pushes must retain queue-depth backpressure, and a handle without PCM (or a
// vibration/failed-open handle) has no downstream operation that can block this call.
const bool use_device_clock =
blocking != 0 && audioout2_context_has_queueable_device(ctx);
g_audioout2_context_mutex.Lock();
if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) {
audioout2_update_context_locked(state);
sleep_micros = audioout2_grain_micros(state->num_grains);
if (state->queued < state->queue_depth) {
if (state->queued < state->queue_depth || use_device_clock) {
if (state->queued == 0) {
state->last_update = LibKernel::KernelGetProcessTime();
}
state->queued++;
if (state->queued < state->queue_depth) {
state->queued++;
}
g_audioout2_context_mutex.Unlock();
audioout2_queue_context_audio(ctx, blocking != 0);
return OK;
+123 -4
View File
@@ -19,6 +19,8 @@ namespace AudioOut2 = Libs::Audio::AudioOut2;
std::mutex g_device_mutex;
std::condition_variable g_device_cv;
std::vector<int> g_live_devices;
std::vector<int> g_device_backed_handles;
std::vector<bool> g_output_blocking;
int g_next_device = 1;
int g_open_waiters = 0;
bool g_block_opens = false;
@@ -61,6 +63,17 @@ struct PortState {
uint64_t reserved[6];
};
struct Attribute {
uint32_t attribute_id;
int32_t reserved;
const void* value;
size_t value_size;
};
struct Pcm {
const void* data;
};
const auto* AsParam(const PortParam* param) {
return reinterpret_cast<const AudioOut2::AudioOut2PortParam*>(param);
}
@@ -73,6 +86,10 @@ auto* AsState(PortState* state) {
return reinterpret_cast<AudioOut2::AudioOut2PortState*>(state);
}
const auto* AsAttribute(const Attribute* attribute) {
return reinterpret_cast<const AudioOut2::AudioOut2Attribute*>(attribute);
}
PortParam MakeParam(uint32_t data_format = 0x200) {
PortParam param {};
param.data_format = data_format;
@@ -80,9 +97,9 @@ PortParam MakeParam(uint32_t data_format = 0x200) {
return param;
}
AudioOut2::AudioOut2ContextHandle CreateContext() {
AudioOut2::AudioOut2ContextHandle CreateContext(uint32_t queue_depth = 4) {
ContextParam param {};
param.queue_depth = 4;
param.queue_depth = queue_depth;
param.num_grains = 512;
AudioOut2::AudioOut2ContextHandle context = 0;
Check(AudioOut2::AudioOut2ContextCreate(AsParam(&param), nullptr, 0, &context) == OK,
@@ -112,6 +129,23 @@ int LiveDeviceCount() {
return static_cast<int>(g_live_devices.size());
}
void SetPcm(AudioOut2::AudioOut2PortHandle port, const void* data) {
const Pcm pcm {data};
const Attribute attribute {0, 0, &pcm, sizeof(pcm)};
Check(AudioOut2::AudioOut2PortSetAttributes(port, AsAttribute(&attribute), 1) == OK,
"setting PCM failed");
}
void ResetOutputCalls() {
std::lock_guard lock(g_device_mutex);
g_output_blocking.clear();
}
std::vector<bool> OutputCalls() {
std::lock_guard lock(g_device_mutex);
return g_output_blocking;
}
void TestSlotReuse() {
const auto context = CreateContext();
const auto param = MakeParam();
@@ -202,14 +236,83 @@ void TestContextDestroyCancelsPendingCreate() {
Check(LiveDeviceCount() == 0, "cancelled port create leaked a device");
}
void TestSynchronousDevicePushBypassesModelledQueue() {
const auto context = CreateContext(1);
const auto param = MakeParam();
AudioOut2::AudioOut2PortHandle port = 0;
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(&param), &port) == OK,
"device port create failed");
uint32_t pcm[512] {};
SetPcm(port, pcm);
ResetOutputCalls();
Check(AudioOut2::AudioOut2ContextPush(context, 1) == OK, "first sync push failed");
Check(AudioOut2::AudioOut2ContextPush(context, 1) == OK,
"device-paced sync push was blocked by modelled queue");
const auto calls = OutputCalls();
Check(calls.size() == 2, "sync pushes did not reach the device backend");
Check(calls[0] && calls[1], "sync pushes lost their blocking mode");
AudioOut2::AudioOut2PortDestroy(port);
AudioOut2::AudioOut2ContextDestroy(context);
}
void TestAsynchronousDevicePushKeepsQueueBounded() {
const auto context = CreateContext(1);
const auto param = MakeParam();
AudioOut2::AudioOut2PortHandle port = 0;
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(&param), &port) == OK,
"device port create failed");
uint32_t pcm[512] {};
SetPcm(port, pcm);
ResetOutputCalls();
Check(AudioOut2::AudioOut2ContextPush(context, 0) == OK, "first async push failed");
Check(AudioOut2::AudioOut2ContextPush(context, 0) != OK,
"full async queue accepted another buffer");
const auto calls = OutputCalls();
Check(calls.size() == 1 && !calls[0], "rejected async push reached the device backend");
uint32_t queued = 0;
uint32_t available = 0;
Check(AudioOut2::AudioOut2ContextGetQueueLevel(context, &queued, &available) == OK,
"queue-level query failed");
Check(queued == 1 && available == 0, "async queue level does not match accepted pushes");
AudioOut2::AudioOut2PortDestroy(port);
AudioOut2::AudioOut2ContextDestroy(context);
}
void TestHandleWithoutPcmDoesNotBypassQueue() {
const auto context = CreateContext(1);
const auto param = MakeParam();
AudioOut2::AudioOut2PortHandle port = 0;
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(&param), &port) == OK,
"device port create failed");
ResetOutputCalls();
Check(AudioOut2::AudioOut2ContextPush(context, 1) == OK, "empty sync push failed");
Check(AudioOut2::AudioOut2ContextPush(context, 0) != OK,
"handle without PCM bypassed queue backpressure");
Check(OutputCalls().empty(), "empty push reached the device backend");
AudioOut2::AudioOut2PortDestroy(port);
AudioOut2::AudioOut2ContextDestroy(context);
}
} // namespace
namespace Libs::Audio::AudioInternal {
int AudioOutOpen(int /*type*/, uint32_t /*samples_num*/, uint32_t /*freq*/, Format /*format*/) {
int AudioOutOpen(int type, uint32_t /*samples_num*/, uint32_t /*freq*/, Format /*format*/) {
std::unique_lock lock(g_device_mutex);
const int handle = g_next_device++;
g_live_devices.push_back(handle);
if (type != 10) {
g_device_backed_handles.push_back(handle);
}
g_open_waiters++;
g_device_cv.notify_all();
g_device_cv.wait(lock, []() { return !g_block_opens; });
@@ -222,9 +325,22 @@ void AudioOutClose(int handle) {
if (it != g_live_devices.end()) {
g_live_devices.erase(it);
}
const auto device_it =
std::find(g_device_backed_handles.begin(), g_device_backed_handles.end(), handle);
if (device_it != g_device_backed_handles.end()) {
g_device_backed_handles.erase(device_it);
}
}
uint32_t AudioOutOutputs(const OutputParam* /*params*/, uint32_t /*num*/, bool /*blocking*/) {
bool AudioOutHasDevice(int handle) {
std::lock_guard lock(g_device_mutex);
return std::find(g_device_backed_handles.begin(), g_device_backed_handles.end(), handle) !=
g_device_backed_handles.end();
}
uint32_t AudioOutOutputs(const OutputParam* /*params*/, uint32_t /*num*/, bool blocking) {
std::lock_guard lock(g_device_mutex);
g_output_blocking.push_back(blocking);
return 0;
}
@@ -244,6 +360,9 @@ int main() {
TestFullTableRecovers();
TestConcurrentCreates();
TestContextDestroyCancelsPendingCreate();
TestSynchronousDevicePushBypassesModelledQueue();
TestAsynchronousDevicePushKeepsQueueBounded();
TestHandleWithoutPcmDoesNotBypassQueue();
std::printf("AudioOut2PortTests: all cases passed\n");
return 0;
}
+338 -4
View File
@@ -9050,6 +9050,8 @@ CoverageClass ClassifyOpcode(ShaderOpcode opcode, const std::set<ShaderOpcode>&
case Opcode::BufferAtomicAnd:
case Opcode::BufferAtomicOr:
case Opcode::BufferAtomicXor:
case Opcode::BufferAtomicFMin:
case Opcode::BufferAtomicFMax:
case Opcode::FlatLoadUbyte:
case Opcode::FlatLoadSbyte:
case Opcode::FlatLoadUshort:
@@ -9580,6 +9582,8 @@ TestCase ScalarCompareOps() {
code.push_back(EncodeSMovB32(11, InlineU32(0)));
code.push_back(EncodeSMovB32(12, InlineU32(2)));
code.push_back(EncodeSMovB32(13, InlineU32(0)));
code.push_back(EncodeSMovB32(14, InlineU32(1)));
code.push_back(EncodeSMovB32(15, InlineU32(1)));
u32 dst = 20;
auto append_compare = [&](u32 opcode, u32 src0, u32 src1) {
@@ -9597,9 +9601,13 @@ TestCase ScalarCompareOps() {
append_compare(0x08, 4, 3);
append_compare(0x09, 4, 4);
append_compare(0x0b, 3, 4);
append_compare(0x12, 10, 10);
append_compare(0x12, 10, 12);
append_compare(0x12, 10, 14);
append_compare(0x13, 10, 12);
append_compare(0x13, 10, 14);
for (u32 i = 0; i < 11u; i++) {
for (u32 i = 0; i < 15u; i++) {
AppendStoreSgpr(&code, 20u + i, i);
}
AppendEnd(&code);
@@ -9607,10 +9615,10 @@ TestCase ScalarCompareOps() {
return {"ScalarCompareOps",
code,
{},
std::vector<u32>(11, 1),
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1},
{O::SMovB32, O::SCmpEqI32, O::SCmpLgI32, O::SCmpGtI32, O::SCmpGeI32, O::SCmpLtI32,
O::SCmpLeI32, O::SCmpLgU32, O::SCmpGtU32, O::SCmpGeU32, O::SCmpLeU32, O::SCmpLgU64,
O::SCselectB32, O::VMovB32, O::BufferStoreDword, O::SEndpgm}};
O::SCmpLeI32, O::SCmpLgU32, O::SCmpGtU32, O::SCmpGeU32, O::SCmpLeU32, O::SCmpEqU64,
O::SCmpLgU64, O::SCselectB32, O::VMovB32, O::BufferStoreDword, O::SEndpgm}};
}
TestCase ScalarShiftAddAndMaskOps() {
@@ -10110,6 +10118,73 @@ TestCase VectorIntegerOps() {
O::VCndmaskB32, O::BufferStoreDword, O::SEndpgm}};
}
TestCase Vop2SdwaSubNcExactByte2Destination() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendVMovU32(&code, 11, 2);
code.push_back(0x4c1616f9u);
code.push_back(0x0686128du);
AppendStoreVgpr(&code, 11, 0);
AppendEnd(&code);
TestCase test;
test.name = "Vop2SdwaSubNcExactByte2Destination";
test.code = code;
test.expected = {0x000b0002u};
test.opcodes = {O::VMovB32, O::VSubNcU32, O::BufferStoreDword, O::SEndpgm};
test.required_spirv = {"OpISub", "OpShiftLeftLogical", "OpBitwiseOr"};
return test;
}
TestCase Vop2SdwaSubNcPreservesByteAndWordDestinations() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendVMovU32(&code, 0, 3);
for (u32 dst = 10; dst <= 15; dst++) {
AppendVMovLiteral(&code, dst, 0xa1b2c3d4u);
}
for (u32 sel = 0; sel <= 5; sel++) {
code.push_back(EncodeVop2(0x26, 10 + sel, 249, 0));
code.push_back(EncodeVop2Sdwa(InlineU32(20), sel, 2, 6, 6, 0, 0, 0, 0, 0, 0, 1));
AppendStoreVgpr(&code, 10 + sel, sel);
}
AppendEnd(&code);
return {"Vop2SdwaSubNcPreservesByteAndWordDestinations",
code,
{},
{0xa1b2c311u, 0xa1b211d4u, 0xa111c3d4u, 0x11b2c3d4u, 0xa1b20011u,
0x0011c3d4u},
{O::VMovB32, O::VSubNcU32, O::BufferStoreDword, O::SEndpgm}};
}
TestCase Vop2SdwaMinU32PreservesWordDestination() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendVMovLiteral(&code, 10, 0xa1b2c3d4u);
AppendVMovU32(&code, 12, 7);
code.push_back(0x261418f9u);
code.push_back(0x0686149fu);
AppendStoreVgpr(&code, 10, 0);
AppendVMovLiteral(&code, 10, 0xa1b2c3d4u);
AppendVMovU32(&code, 12, 64);
code.push_back(0x261418f9u);
code.push_back(0x0686149fu);
AppendStoreVgpr(&code, 10, 1);
AppendEnd(&code);
TestCase test;
test.name = "Vop2SdwaMinU32PreservesWordDestination";
test.code = code;
test.expected = {0xa1b20007u, 0xa1b2001fu};
test.opcodes = {O::VMovB32, O::VMinU32, O::BufferStoreDword, O::SEndpgm};
test.required_spirv = {"OpULessThan", "OpSelect", "OpBitwiseOr"};
return test;
}
TestCase VectorShiftCountsMaskLowBits() {
using O = ShaderOpcode;
@@ -11593,6 +11668,33 @@ TestCase VectorVop3CompareNeU64OnGpu() {
O::SEndpgm}};
}
TestCase VectorVop3CompareEqI64OnGpu() {
using O = ShaderOpcode;
std::vector<u32> code;
code.push_back(EncodeVop1(0x01, 1, InlineU32(1)));
code.push_back(EncodeSop1(0x04, 4, 126)); // s_mov_b64 s[4:5], exec
code.push_back(0xd4a2006au);
code.push_back(0x0000087eu); // v_cmp_eq_i64 vcc, exec, s[4:5]
code.push_back(EncodeVop2(0x01, 2, InlineU32(0), 1));
AppendStoreVgpr(&code, 2, 0);
AppendSMovLiteral(&code, 4, 0);
code.push_back(0xd4a2006au);
code.push_back(0x0000087eu); // low dword mismatch
code.push_back(EncodeVop2(0x01, 3, InlineU32(0), 1));
AppendStoreVgpr(&code, 3, 1);
AppendEnd(&code);
return {"VectorVop3CompareEqI64OnGpu",
code,
{},
{1, 0},
{O::VMovB32, O::SMovB64, O::VCmpEqI64, O::VCndmaskB32, O::SMovB32,
O::BufferStoreDword, O::SEndpgm}};
}
TestCase VectorCompareClassF32() {
using O = ShaderOpcode;
@@ -11708,6 +11810,32 @@ TestCase Vop2SdwaCndmaskSourceModifier() {
{O::VMovB32, O::VCmpTU32, O::VCndmaskB32, O::BufferStoreDword, O::SEndpgm}};
}
TestCase Vop2SdwaCndmaskFullDestinationWithSubDwordSource() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendVMovLiteral(&code, 0, 0xabcd1234u);
AppendVMovLiteral(&code, 1, 0x55667788u);
code.push_back(EncodeVopc(0xc0, Vgpr(0), 0)); // v_cmp_f_u32
code.push_back(0x020e02f9u);
code.push_back(0x06040600u);
AppendStoreVgpr(&code, 7, 0);
code.push_back(EncodeVopc(0xc7, Vgpr(0), 0)); // v_cmp_t_u32
code.push_back(0x020e02f9u);
code.push_back(0x06040600u);
AppendStoreVgpr(&code, 7, 1);
AppendEnd(&code);
TestCase test;
test.name = "Vop2SdwaCndmaskFullDestinationWithSubDwordSource";
test.code = code;
test.expected = {0x00001234u, 0x55667788u};
test.opcodes = {O::VMovB32, O::VCmpFU32, O::VCmpTU32, O::VCndmaskB32,
O::BufferStoreDword, O::SEndpgm};
test.required_spirv = {"OpBitFieldUExtract", "OpSelect"};
return test;
}
TestCase Vop3CndmaskUsesSgprMaskLaneBits() {
using O = ShaderOpcode;
@@ -13803,6 +13931,192 @@ TestCase BufferAtomicGlc0DoesNotReturnOldValue() {
{O::VMovB32, O::BufferAtomicAdd, O::BufferStoreDword, O::SEndpgm}};
}
TestCase BufferAtomicFMinExactRawGlcModes() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendVMovLiteral(&code, 0, 0x40000000u); // 2.0
code.push_back(0xe0fc0000u);
code.push_back(0x80010000u); // exact failing buffer_atomic_fmin, GLC=0
AppendStoreVgpr(&code, 0, 1);
AppendVMovLiteral(&code, 0, 0x3f800000u); // 1.0
code.push_back(0xe0fc4000u);
code.push_back(0x80010000u); // same instruction with GLC=1
AppendStoreVgpr(&code, 0, 2);
AppendEnd(&code);
TestCase test;
test.name = "BufferAtomicFMinExactRawGlcModes";
test.code = code;
test.initial = {0x40800000u, 0, 0}; // 4.0
test.expected = {0x3f800000u, 0x40000000u, 0x40000000u};
test.opcodes = {O::VMovB32, O::BufferAtomicFMin, O::BufferStoreDword, O::SEndpgm};
const auto descriptor =
MakeStructuredStorageBufferData(0, static_cast<u32>(test.initial.size() * sizeof(u32)));
std::copy_n(descriptor.begin(), 4, test.user_data.begin() + 4);
test.user_data[50] = 1u << 20u;
test.has_user_data = true;
return test;
}
TestCase BufferAtomicFMinSpecialValues() {
using O = ShaderOpcode;
const u32 values[] = {
0x40000000u, // 2.0
0x7f800000u, // +infinity
0xff800000u, // -infinity
0x3f800000u, // 1.0
0x7fc00000u, // quiet NaN
0x00000000u, // +0.0
0x80000000u, // -0.0
0x00000000u, // +0.0
0x80000001u, // smallest negative denorm
};
std::vector<u32> code;
for (u32 i = 0; i < static_cast<u32>(std::size(values)); i++) {
AppendVMovU32(&code, 20, i * 4u);
AppendVMovLiteral(&code, i, values[i]);
AppendBufferStoreOpcode(&code, 0x3f, i, 20, true);
}
for (u32 i = 0; i < static_cast<u32>(std::size(values)); i++) {
AppendStoreVgpr(&code, i, i + static_cast<u32>(std::size(values)));
}
AppendEnd(&code);
return {"BufferAtomicFMinSpecialValues",
code,
{0x40800000u, 0xbf800000u, 0x7f800000u, 0x7fc00000u, 0x3f800000u,
0x80000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0x40000000u, 0xbf800000u, 0xff800000u, 0x7fc00000u, 0x3f800000u,
0x80000000u, 0x00000000u, 0x00000000u, 0x80000001u, 0x40800000u,
0xbf800000u, 0x7f800000u, 0x7fc00000u, 0x3f800000u, 0x80000000u,
0x00000000u, 0x00000001u, 0x00000000u},
{O::VMovB32, O::BufferAtomicFMin, O::BufferStoreDword, O::SEndpgm}};
}
TestCase BufferAtomicFMinContendedWorkgroup() {
using O = ShaderOpcode;
std::vector<u32> code;
code.push_back(EncodeVop1(0x06, 1, Vgpr(0))); // v_cvt_f32_u32 v1, thread_id.x
AppendVMovU32(&code, 20, 0);
AppendBufferStoreOpcode(&code, 0x3f, 1, 20);
AppendEnd(&code);
TestCase test;
test.name = "BufferAtomicFMinContendedWorkgroup";
test.code = code;
test.initial = {0x42c80000u}; // 100.0
test.expected = {0x00000000u}; // min(100.0, 0.0 .. 63.0)
test.opcodes = {O::VCvtF32U32, O::VMovB32, O::BufferAtomicFMin, O::SEndpgm};
test.compute_info.threads_num[0] = 64;
test.compute_info.threads_num[1] = 1;
test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
test.has_compute_info = true;
return test;
}
TestCase BufferAtomicFMaxExactRawGlcModes() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendVMovLiteral(&code, 3, 0x40000000u); // 2.0
code.push_back(0xe100000cu);
code.push_back(0x80010300u); // exact failing buffer_atomic_fmax v3, offset 12, GLC=0
AppendStoreVgpr(&code, 3, 4);
AppendVMovLiteral(&code, 0, 0x40800000u); // 4.0
code.push_back(0xe100400cu);
code.push_back(0x80010000u); // same address with GLC=1
AppendStoreVgpr(&code, 0, 5);
AppendEnd(&code);
TestCase test;
test.name = "BufferAtomicFMaxExactRawGlcModes";
test.code = code;
test.initial = {0, 0, 0, 0x3f800000u, 0, 0}; // memory[3] = 1.0
test.expected = {0, 0, 0, 0x40800000u, 0x40000000u, 0x40000000u};
test.opcodes = {O::VMovB32, O::BufferAtomicFMax, O::BufferStoreDword, O::SEndpgm};
const auto descriptor =
MakeStructuredStorageBufferData(0, static_cast<u32>(test.initial.size() * sizeof(u32)));
std::copy_n(descriptor.begin(), 4, test.user_data.begin() + 4);
test.user_data[50] = 1u << 20u;
test.has_user_data = true;
return test;
}
TestCase BufferAtomicFMaxSpecialValues() {
using O = ShaderOpcode;
const u32 values[] = {
0x40000000u, // 2.0, finite update
0x40000000u, // 2.0, finite no-update
0x7f800000u, // incoming +infinity
0xff800000u, // incoming -infinity
0x3f800000u, // finite against old +infinity
0x3f800000u, // finite against old -infinity
0x3f800000u, // finite against old quiet NaN
0x7fcabcdeu, // incoming quiet NaN
0x3f800000u, // finite against old signaling NaN
0x7faabcdeu, // incoming signaling NaN
0x00000000u, // +0.0 against old -0.0
0x80000000u, // -0.0 against old +0.0
0x80000000u, // -0.0 against a negative denorm
0x80000001u, // negative denorm against -0.0
0x00000001u, // positive denorm against +0.0
0x00000000u, // +0.0 against a positive denorm
};
std::vector<u32> code;
for (u32 i = 0; i < static_cast<u32>(std::size(values)); i++) {
AppendVMovU32(&code, 20, i * 4u);
AppendVMovLiteral(&code, i, values[i]);
AppendBufferStoreOpcode(&code, 0x40, i, 20, true);
}
for (u32 i = 0; i < static_cast<u32>(std::size(values)); i++) {
AppendStoreVgpr(&code, i, i + static_cast<u32>(std::size(values)));
}
AppendEnd(&code);
return {"BufferAtomicFMaxSpecialValues",
code,
{0x3f800000u, 0x40800000u, 0xbf800000u, 0x3f800000u, 0x7f800000u,
0xff800000u, 0x7fc12345u, 0x3f800000u, 0x7fa54321u, 0x3f800000u,
0x80000000u, 0x00000000u, 0x80000001u, 0x80000000u, 0x00000000u,
0x00000001u, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0x40000000u, 0x40800000u, 0x7f800000u, 0x3f800000u, 0x7f800000u,
0x3f800000u, 0x7fc12345u, 0x3f800000u, 0x7fa54321u, 0x3f800000u,
0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000001u,
0x00000001u, 0x3f800000u, 0x40800000u, 0xbf800000u, 0x3f800000u,
0x7f800000u, 0xff800000u, 0x7fc12345u, 0x3f800000u, 0x7fa54321u,
0x3f800000u, 0x80000000u, 0x00000000u, 0x80000001u, 0x80000000u,
0x00000000u, 0x00000001u},
{O::VMovB32, O::BufferAtomicFMax, O::BufferStoreDword, O::SEndpgm}};
}
TestCase BufferAtomicFMaxContendedWorkgroup() {
using O = ShaderOpcode;
std::vector<u32> code;
code.push_back(EncodeVop1(0x06, 1, Vgpr(0))); // v_cvt_f32_u32 v1, thread_id.x
AppendVMovU32(&code, 20, 0);
AppendBufferStoreOpcode(&code, 0x40, 1, 20);
AppendEnd(&code);
TestCase test;
test.name = "BufferAtomicFMaxContendedWorkgroup";
test.code = code;
test.initial = {0xc2c80000u}; // -100.0
test.expected = {0x427c0000u}; // max(-100.0, 0.0 .. 63.0)
test.opcodes = {O::VCvtF32U32, O::VMovB32, O::BufferAtomicFMax, O::SEndpgm};
test.compute_info.threads_num[0] = 64;
test.compute_info.threads_num[1] = 1;
test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
test.has_compute_info = true;
return test;
}
std::vector<u32> MakeRgbaImage(u32 width, u32 height, u32 value = 0) {
return std::vector<u32>(static_cast<size_t>(width) * height * 4u, value);
}
@@ -15017,6 +15331,9 @@ std::vector<TestCase> MakeCases() {
AddCase(VectorMoves);
AddCase(VectorVop3MoveAppliesFloatSourceModifiers);
AddCase(VectorIntegerOps);
AddCase(Vop2SdwaSubNcExactByte2Destination);
AddCase(Vop2SdwaSubNcPreservesByteAndWordDestinations);
AddCase(Vop2SdwaMinU32PreservesWordDestination);
AddCase(VectorShiftCountsMaskLowBits);
AddCase(VectorVop3IntegerOps);
AddCase(VectorBfeI32ArithmeticShiftMasksField);
@@ -15060,10 +15377,12 @@ std::vector<TestCase> MakeCases() {
AddCase(VectorSpecialF32FlushesDenormalInputs);
AddCase(VectorSinCosMaxFiniteSpecialCases);
AddCase(VectorCompareOps);
AddCase(VectorVop3CompareEqI64OnGpu);
AddCase(VectorVop3CompareNeU64OnGpu);
AddCase(VectorCompareClassF32);
AddCase(VectorCompareF16Ops);
AddCase(Vop2SdwaCndmaskSourceModifier);
AddCase(Vop2SdwaCndmaskFullDestinationWithSubDwordSource);
AddCase(Vop3CndmaskUsesSgprMaskLaneBits);
AddCase(Vop3CndmaskAllowsDataSourceModifier);
AddCase(VectorCompareExecOps);
@@ -15148,6 +15467,12 @@ std::vector<TestCase> MakeCases() {
AddCase(DsSwizzleInvalidSourceLaneZero);
AddCase(BufferAtomicVariants);
AddCase(BufferAtomicGlc0DoesNotReturnOldValue);
AddCase(BufferAtomicFMinExactRawGlcModes);
AddCase(BufferAtomicFMinSpecialValues);
AddCase(BufferAtomicFMinContendedWorkgroup);
AddCase(BufferAtomicFMaxExactRawGlcModes);
AddCase(BufferAtomicFMaxSpecialValues);
AddCase(BufferAtomicFMaxContendedWorkgroup);
AddCase(ImageLoadVariants);
AddCase(ImageLoadR32UintUsesIntegerSampledImage);
AddCase(ImageLoad1DUsesScalarCoordinate);
@@ -15389,6 +15714,15 @@ void CheckEmbeddedFetchVertexOffset() {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
void CheckRenderTargetFormatContract() {
const auto r8_uint =
TextureGetRenderTargetFormat(Prospero::GpuEnumValue(Prospero::ChannelLayout::k8),
Prospero::GpuEnumValue(Prospero::ChannelType::kUInt),
Prospero::GpuEnumValue(Prospero::ChannelOrder::kStandard));
Require("RenderTargetFormat", "R8 uint",
r8_uint.format == vk::Format::eR8Uint && r8_uint.bytes_per_element == 1u &&
r8_uint.export_mapping.IsIdentity(),
"R8 uint render-target tuple was rejected");
const auto rgb565 = TextureGetRenderTargetFormat(16u, 0u, 0u);
Require("RenderTargetFormat", "RGB565 UNorm",
rgb565.format == vk::Format::eB5G6R5UnormPack16 && rgb565.bytes_per_element == 2u &&
+120
View File
@@ -623,6 +623,7 @@ ImageTestUserData(Prospero::ImageType type = Prospero::ImageType::kColor2D) {
std::array<uint32_t, 64> data {};
for (uint32_t start = 0; start + 3u < data.size(); start += 4u) {
data[start] = 0x1000u + start * 0x100u;
data[start + 1u] = Prospero::GpuEnumValue(Prospero::BufferFormat::k8UNorm) << 20u;
data[start + 2u] = UINT32_MAX;
data[start + 3u] = Prospero::GpuEnumValue(type) << 28u;
}
@@ -1169,6 +1170,8 @@ void TestNewShaderRecompilerMoreAluFamilies() {
EncodeVop2Sdwa(5, 6, 0, 4, 5),
0x025e6af9u,
0x16060635u, // v_cndmask_b32 v47, v53, -v53
0x020e02f9u,
0x06040600u, // v_cndmask_b32 v7, v0.lo, v1; full-width destination
0x100490f9u,
0x86860600u, // v_mul_f32 v2, s0, s72 (SDWA full)
EncodeVop2(0x35, 9, 249, 1),
@@ -1395,6 +1398,9 @@ void TestNewShaderRecompilerMoreAluFamilies() {
Check(Common::ContainsStr(result.decoded_dump, "v_cndmask_b32 v47, v53,") &&
Common::ContainsStr(result.decoded_dump, "v53.neg"),
"new decoder did not decode V_CNDMASK_B32 SDWA source modifier");
Check(Common::ContainsStr(result.decoded_dump, "v_cndmask_b32 v7, v0.sdwa(sel=4") &&
Common::ContainsStr(result.decoded_dump, "v1"),
"new decoder did not decode full-destination V_CNDMASK_B32 with SDWA source");
Check(Common::ContainsStr(result.decoded_dump, "v_add_f32 v125, v5.dpp"),
"new decoder did not decode VOP2 DPP source metadata");
Check(!Common::ContainsStr(result.decoded_dump, "VOP2 SDWA/DPP modifiers are not implemented"),
@@ -1699,6 +1705,8 @@ void TestNewShaderRecompilerMoreAluFamilies() {
Check(Common::ContainsStr(result.ir_dump, "SelectMaskF32Bits v47, vcc_lo, v53.neg, v53") &&
Common::ContainsStr(result.ir_dump, "v53.neg"),
"V_CNDMASK_B32 SDWA source modifier did not lower to float-bit select IR");
Check(Common::ContainsStr(result.ir_dump, "SelectMaskU32 v7, vcc_lo, v1, v0.sdwa(sel=4"),
"full-destination V_CNDMASK_B32 with SDWA source did not lower to integer select IR");
Check(Common::ContainsStr(result.ir_dump, "FAddF32 v125.dpp") &&
Common::ContainsStr(result.ir_dump, "v5.dpp"),
"VOP2 DPP source/destination did not lower to IR metadata");
@@ -2416,6 +2424,10 @@ void TestNewShaderRecompilerBootB16PackedAndSdwaOpcodes() {
0x0c860688u, // v_add_nc_u32 v5, 8, sign-extended v4.lo
0x4a0c0cf9u,
0x0d860688u, // v_add_nc_u32 v6, 8, sign-extended v6.hi
0x4c1616f9u,
0x0686128du, // v_sub_nc_u32 v11.byte2, 13, v11; preserve other destination bytes
0x261418f9u,
0x0686149fu, // v_min_u32 v10.word0, 31, v12; preserve upper destination word
0xbf810000u,
};
@@ -2464,6 +2476,10 @@ void TestNewShaderRecompilerBootB16PackedAndSdwaOpcodes() {
Check(Common::ContainsStr(result.decoded_dump, "v_add_nc_u32 v6") &&
Common::ContainsStr(result.decoded_dump, "v6.sdwa(sel=5,sext=1"),
"new decoder did not decode V_ADD_NC_U32 SDWA sign-extended high word");
Check(Common::ContainsStr(result.decoded_dump, "v_sub_nc_u32 v11.sdwa(sel=2"),
"new decoder did not decode V_SUB_NC_U32 SDWA byte-2 destination");
Check(Common::ContainsStr(result.decoded_dump, "v_min_u32 v10.sdwa(sel=4"),
"new decoder did not decode V_MIN_U32 SDWA low-word destination");
Check(!Common::ContainsStr(result.decoded_dump, "unsupported family=VOP2 opcode=0x00"),
"literal/SDWA extension words were decoded as phantom VOP2 instructions");
Check(!Common::ContainsStr(result.decoded_dump,
@@ -2497,6 +2513,10 @@ void TestNewShaderRecompilerBootB16PackedAndSdwaOpcodes() {
Check(Common::ContainsStr(result.ir_dump, "IAddU32 v6") &&
Common::ContainsStr(result.ir_dump, "v6.sdwa(sel=5,sext=1"),
"V_ADD_NC_U32 SDWA sign-extended high word did not lower to IR");
Check(Common::ContainsStr(result.ir_dump, "ISubU32 v11.sdwa(sel=2"),
"V_SUB_NC_U32 SDWA byte-2 destination did not lower to IR");
Check(Common::ContainsStr(result.ir_dump, "UMinU32 v10.sdwa(sel=4"),
"V_MIN_U32 SDWA low-word destination did not lower to IR");
Check(SpirvContainsOpcode(result.spirv, 128),
"SPIR-V binary does not contain OpIAdd for U16 operations");
Check(SpirvContainsOpcode(result.spirv, 202),
@@ -2895,6 +2915,61 @@ void CheckNewDecoderUnsupported(const uint32_t* shader, uint32_t words, const ch
"unsupported lowering error was not explicit");
}
void TestNewShaderRecompilerRejectsDppOn64BitCompares() {
const uint32_t opcodes[] = {0xa2u, 0xe5u}; // v_cmp_eq_i64, v_cmp_ne_u64
for (const auto opcode: opcodes) {
const uint32_t shader[] = {
EncodeVopc(opcode, 250u, 0u), // DPP escape in SRC0
EncodeVop2Dpp(0u),
0xbf810000u,
};
ShaderRecompiler::Decoder::Program program;
std::string error;
Check(ShaderRecompiler::Decoder::DecodeProgram(shader, program, &error), error.c_str());
Check(program.instructions.size() == 2u,
"64-bit VOPC DPP decode did not consume its modifier word");
const auto& compare = program.instructions.front();
Check(compare.opcode == ShaderRecompiler::Decoder::Opcode::Unsupported,
"64-bit VOPC illegally accepted a DPP modifier");
Check(Common::ContainsStr(compare.unsupported_reason,
"VOPC DPP modifier is not supported for opcode"),
"64-bit VOPC DPP rejection reason was not explicit");
}
}
void TestNewShaderRecompilerIrLookupMissFailsExplicitly() {
ShaderRecompiler::Decoder::Program decoded;
ShaderRecompiler::Decoder::Instruction missing;
missing.pc = 0u;
missing.family = ShaderRecompiler::Decoder::Family::VOP1;
missing.opcode = ShaderRecompiler::Decoder::Opcode::Unknown;
decoded.instructions.push_back(missing);
ShaderRecompiler::CFG::Graph cfg;
ShaderRecompiler::CFG::BasicBlock block;
block.inst_end = 1u;
cfg.blocks.push_back(block);
cfg.entry_block = 0u;
ShaderRecompiler::IR::Program ir;
std::string error;
Check(!ShaderRecompiler::IR::LowerProgram(decoded, cfg, ShaderType::Compute, 64u, ir, &error),
"missing decoder-to-IR mapping unexpectedly lowered as an instruction");
Check(Common::ContainsStr(error, "no IR lowering"),
"missing decoder-to-IR mapping did not report an explicit error");
Check(ir.blocks.empty(), "missing decoder-to-IR mapping emitted a fallback IR block");
decoded.instructions.front().family = ShaderRecompiler::Decoder::Family::MUBUF;
decoded.instructions.front().opcode = ShaderRecompiler::Decoder::Opcode::VAddNcU32;
error.clear();
Check(!ShaderRecompiler::IR::LowerProgram(decoded, cfg, ShaderType::Compute, 64u, ir, &error),
"memory-family opcode bypassed specialized memory lowering");
Check(Common::ContainsStr(error, "memory-family opcode") &&
Common::ContainsStr(error, "specialized IR lowering"),
"memory-family lowering bypass did not report an explicit error");
}
void TestNewShaderRecompilerMemoryFamilyLowering() {
const uint32_t shader[] = {
EncodeSmem0(0x00, 0, 4),
@@ -4722,6 +4797,10 @@ void TestNewShaderRecompilerAtomicLowering() {
EncodeMubuf1(5, 0, 1), // buffer_atomic_or
EncodeMubuf0(0x3b, 40, true, true),
EncodeMubuf1(6, 0, 1), // buffer_atomic_xor
0xe0fc0000u,
0x80010000u, // exact buffer_atomic_fmin v0, s[4:7], 0 (GLC=0)
0xe100000cu,
0x80010300u, // exact failing buffer_atomic_fmax v3, s[4:7], 12 (GLC=0)
EncodeDs0(0x00),
EncodeDs1(0, 2, 1), // ds_add_u32
EncodeDs0(0x01),
@@ -4776,6 +4855,10 @@ void TestNewShaderRecompilerAtomicLowering() {
"new decoder did not decode buffer atomic signed max");
Check(Common::ContainsStr(result.decoded_dump, "buffer_atomic_xor"),
"new decoder did not decode buffer atomic xor");
Check(Common::ContainsStr(result.decoded_dump, "buffer_atomic_fmin"),
"new decoder did not decode buffer atomic float min");
Check(Common::ContainsStr(result.decoded_dump, "buffer_atomic_fmax"),
"new decoder did not decode buffer atomic float max");
Check(Common::ContainsStr(result.decoded_dump, "ds_add_u32"),
"new decoder did not decode DS atomic add");
Check(Common::ContainsStr(result.decoded_dump, "ds_sub_u32"),
@@ -4808,6 +4891,10 @@ void TestNewShaderRecompilerAtomicLowering() {
"buffer atomic or did not lower to IR");
Check(Common::ContainsStr(result.ir_dump, "AtomicXorU32 v6"),
"buffer atomic xor did not lower to IR");
Check(Common::ContainsStr(result.ir_dump, "AtomicFMinF32 null, v0"),
"buffer atomic float min did not lower to IR without a GLC return");
Check(Common::ContainsStr(result.ir_dump, "AtomicFMaxF32 null, v3"),
"buffer atomic float max did not lower to IR without a GLC return");
Check(Common::ContainsStr(result.ir_dump, "AtomicAddU32 null, v2"),
"DS no-return atomic add did not lower to IR");
Check(Common::ContainsStr(result.ir_dump, "AtomicSubU32 null, v10"),
@@ -4851,6 +4938,8 @@ void TestNewShaderRecompilerAtomicLowering() {
Check(SpirvContainsOpcode(result.spirv, 240), "SPIR-V binary does not contain OpAtomicAnd");
Check(SpirvContainsOpcode(result.spirv, 241), "SPIR-V binary does not contain OpAtomicOr");
Check(SpirvContainsOpcode(result.spirv, 242), "SPIR-V binary does not contain OpAtomicXor");
Check(SpirvContainsOpcode(result.spirv, 230),
"SPIR-V binary does not contain OpAtomicCompareExchange for float min");
Check(SpirvContainsOpcode(result.spirv, 225),
"buffer atomic SPIR-V binary does not contain OpMemoryBarrier");
CheckSpirvBinaryValidates(result.spirv);
@@ -6638,6 +6727,35 @@ void TestNewShaderRecompilerNativeBindingPlan() {
rejected_spirv == rejected_before && rejected_error.find("atomic") != std::string::npos,
"malformed atomic instruction reached a fatal descriptor path");
auto malformed_float_atomic = result.program;
atomic_inst = FindBufferInstruction(&malformed_float_atomic);
Check(atomic_inst != nullptr, "native validation fixture lacks a float atomic candidate");
atomic_inst->op = ShaderRecompiler::IR::Opcode::AtomicFMaxF32;
atomic_inst->memory.kind = ShaderRecompiler::IR::ResourceKind::Lds;
atomic_inst->memory.resource = 0;
atomic_inst->src_count = 2;
rejected_spirv = rejected_before;
rejected_error.clear();
Check(!ShaderRecompiler::Spirv::EmitProgram(malformed_float_atomic, result.resources, nullptr,
nullptr, nullptr, rejected_spirv,
&rejected_error) &&
rejected_spirv == rejected_before &&
rejected_error.find("floating-point atomic") != std::string::npos,
"buffer-only floating-point atomic accepted a non-buffer resource");
auto invalid_ir_opcode = result.program;
Check(!invalid_ir_opcode.blocks.empty() && !invalid_ir_opcode.blocks.front().instructions.empty(),
"native validation fixture lacks an invalid-opcode candidate");
invalid_ir_opcode.blocks.front().instructions.front().op =
ShaderRecompiler::IR::Opcode::Count;
rejected_spirv = rejected_before;
rejected_error.clear();
Check(!ShaderRecompiler::Spirv::EmitProgram(invalid_ir_opcode, result.resources, nullptr, nullptr,
nullptr, rejected_spirv, &rejected_error) &&
rejected_spirv == rejected_before &&
rejected_error.find("opcode is out of range") != std::string::npos,
"out-of-range IR opcode was not rejected transactionally before emission");
auto overwide_atomic = result.program;
atomic_inst = FindBufferInstruction(&overwide_atomic);
Check(atomic_inst != nullptr, "native validation fixture lacks an overwide atomic candidate");
@@ -7417,6 +7535,8 @@ int main() {
TestNewShaderRecompilerScalarVectorAlu();
TestNewShaderRecompilerVop3LaneReadDestinationEncoding();
TestNewShaderRecompilerMoreAluFamilies();
TestNewShaderRecompilerRejectsDppOn64BitCompares();
TestNewShaderRecompilerIrLookupMissFailsExplicitly();
TestNewShaderRecompilerExpandedAluBatch();
TestNewShaderRecompilerVop3pPackedF16();
TestNewShaderRecompilerStagedShaderOps();