shader_recompiler: add SSA

This commit is contained in:
nmzik
2026-08-15 03:11:19 +02:00
parent bc2f077c37
commit b42380ced6
113 changed files with 40415 additions and 32616 deletions
@@ -0,0 +1,612 @@
#include "graphics/shader/recompiler/ir/passes/ConstantPropagation.h"
#include <algorithm>
#include <bit>
#include <cstdint>
namespace Libs::Graphics::ShaderRecompiler::IR {
namespace {
Value Arg(const Inst& inst, size_t index) {
return inst.Arg(index).Resolve();
}
bool IsImmediate(Value value, Type type) {
return value.IsImmediate() && value.GetType() == type;
}
void Replace(Inst& inst, Value value) {
inst.ReplaceUsesWith(value.Resolve());
}
template <typename Function>
bool FoldU32(Inst& inst, Function function) {
const auto lhs = Arg(inst, 0);
const auto rhs = Arg(inst, 1);
if (!IsImmediate(lhs, Type::U32) || !IsImmediate(rhs, Type::U32)) {
return false;
}
Replace(inst, Value(static_cast<uint32_t>(function(lhs.U32(), rhs.U32()))));
return true;
}
template <typename Function>
bool FoldU64(Inst& inst, Function function) {
const auto lhs = Arg(inst, 0);
const auto rhs = Arg(inst, 1);
if (!IsImmediate(lhs, Type::U64) || !IsImmediate(rhs, Type::U64)) {
return false;
}
Replace(inst, Value(static_cast<uint64_t>(function(lhs.U64(), rhs.U64()))));
return true;
}
template <typename Function>
bool FoldU64Shift(Inst& inst, Function function) {
const auto value = Arg(inst, 0);
const auto shift = Arg(inst, 1);
if (!IsImmediate(value, Type::U64) || !IsImmediate(shift, Type::U32)) {
return false;
}
Replace(inst, Value(static_cast<uint64_t>(function(value.U64(), shift.U32()))));
return true;
}
template <typename Function>
bool FoldU32Compare(Inst& inst, Function function) {
const auto lhs = Arg(inst, 0);
const auto rhs = Arg(inst, 1);
if (!IsImmediate(lhs, Type::U32) || !IsImmediate(rhs, Type::U32)) {
return false;
}
Replace(inst, Value(function(lhs.U32(), rhs.U32())));
return true;
}
template <typename Function>
bool FoldU64Compare(Inst& inst, Function function) {
const auto lhs = Arg(inst, 0);
const auto rhs = Arg(inst, 1);
if (!IsImmediate(lhs, Type::U64) || !IsImmediate(rhs, Type::U64)) {
return false;
}
Replace(inst, Value(function(lhs.U64(), rhs.U64())));
return true;
}
template <typename Function>
bool FoldLogical(Inst& inst, Function function) {
const auto lhs = Arg(inst, 0);
const auto rhs = Arg(inst, 1);
if (!IsImmediate(lhs, Type::U1) || !IsImmediate(rhs, Type::U1)) {
return false;
}
Replace(inst, Value(function(lhs.U1(), rhs.U1())));
return true;
}
bool ReplaceBinaryIdentity(Inst& inst, Type type, uint64_t identity) {
const auto lhs = Arg(inst, 0);
const auto rhs = Arg(inst, 1);
if (IsImmediate(lhs, type) &&
(type == Type::U32 ? lhs.U32() == identity : lhs.U64() == identity)) {
Replace(inst, rhs);
return true;
}
if (IsImmediate(rhs, type) &&
(type == Type::U32 ? rhs.U32() == identity : rhs.U64() == identity)) {
Replace(inst, lhs);
return true;
}
return false;
}
bool FoldSelect(Inst& inst) {
const auto condition = Arg(inst, 0);
const auto true_value = Arg(inst, 1);
const auto false_value = Arg(inst, 2);
if (IsImmediate(condition, Type::U1)) {
Replace(inst, condition.U1() ? true_value : false_value);
return true;
}
if (true_value == false_value) {
Replace(inst, true_value);
return true;
}
return false;
}
bool FoldPhi(Inst& inst) {
Value same;
for (size_t index = 0; index < inst.NumArgs(); index++) {
const auto value = Arg(inst, index);
if (value.TryInstruction() == &inst) {
continue;
}
if (same.IsEmpty()) {
same = value;
} else if (same != value) {
return false;
}
}
if (same.IsEmpty()) {
return false;
}
Replace(inst, same);
return true;
}
bool FoldBitCast(Inst& inst, ValueOpcode reverse) {
const auto value = Arg(inst, 0);
if (IsImmediate(value, Type::F32) && inst.GetOpcode() == ValueOpcode::BitCastU32F32) {
Replace(inst, Value(std::bit_cast<uint32_t>(value.F32Value())));
return true;
}
if (IsImmediate(value, Type::U32) && inst.GetOpcode() == ValueOpcode::BitCastF32U32) {
Replace(inst, Value::F32(std::bit_cast<float>(value.U32())));
return true;
}
if (IsImmediate(value, Type::F16) && inst.GetOpcode() == ValueOpcode::BitCastU16F16) {
Replace(inst, Value(value.F16Bits()));
return true;
}
if (IsImmediate(value, Type::U16) && inst.GetOpcode() == ValueOpcode::BitCastF16U16) {
Replace(inst, Value::F16(value.U16()));
return true;
}
if (auto* producer = value.TryInstruction();
producer != nullptr && producer->GetOpcode() == reverse) {
Replace(inst, producer->Arg(0));
return true;
}
return false;
}
bool FoldCompositeExtract(Inst& inst, ValueOpcode construct, size_t components) {
const auto composite = Arg(inst, 0);
const auto index = Arg(inst, 1);
if (!IsImmediate(index, Type::U32) || index.U32() >= components) {
return false;
}
const auto component = index.U32();
const auto* producer = composite.TryInstruction();
if (producer != nullptr && producer->GetOpcode() == construct) {
Replace(inst, producer->Arg(component));
return true;
}
if (component < 2u && producer != nullptr &&
producer->GetOpcode() == ValueOpcode::IAddCarry32) {
const auto lhs = producer->Arg(0).Resolve();
const auto rhs = producer->Arg(1).Resolve();
if (IsImmediate(lhs, Type::U32) && IsImmediate(rhs, Type::U32)) {
const auto sum = static_cast<uint64_t>(lhs.U32()) + rhs.U32();
Replace(inst, Value(component == 0u ? static_cast<uint32_t>(sum)
: static_cast<uint32_t>(sum >> 32u)));
return true;
}
}
if (component < 2u && producer != nullptr &&
producer->GetOpcode() == ValueOpcode::UnpackUint2x32) {
const auto packed = producer->Arg(0).Resolve();
if (IsImmediate(packed, Type::U64)) {
Replace(inst, Value(static_cast<uint32_t>(packed.U64() >> (component * 32u))));
return true;
}
}
return false;
}
void FoldInstruction(Inst& inst) {
switch (inst.GetOpcode()) {
case ValueOpcode::Phi: FoldPhi(inst); return;
case ValueOpcode::SelectU1:
case ValueOpcode::SelectU32:
case ValueOpcode::SelectF32: FoldSelect(inst); return;
case ValueOpcode::BitFieldInsert: {
const auto base = Arg(inst, 0);
const auto insert = Arg(inst, 1);
const auto offset = Arg(inst, 2);
const auto count = Arg(inst, 3);
if (IsImmediate(base, Type::U32) && IsImmediate(insert, Type::U32) &&
IsImmediate(offset, Type::U32) && IsImmediate(count, Type::U32) &&
offset.U32() <= 32u && count.U32() <= 32u - offset.U32()) {
if (count.U32() == 0u) {
Replace(inst, base);
return;
}
const auto mask = count.U32() == 32u
? UINT32_MAX
: ((uint32_t {1} << count.U32()) - 1u) << offset.U32();
Replace(inst,
Value((base.U32() & ~mask) | ((insert.U32() << offset.U32()) & mask)));
}
return;
}
case ValueOpcode::BitFieldUExtract:
case ValueOpcode::BitFieldSExtract: {
const auto value = Arg(inst, 0);
const auto offset = Arg(inst, 1);
const auto count = Arg(inst, 2);
if (!IsImmediate(value, Type::U32) || !IsImmediate(offset, Type::U32) ||
!IsImmediate(count, Type::U32) || offset.U32() > 32u ||
count.U32() > 32u - offset.U32()) {
return;
}
if (count.U32() == 0u) {
Replace(inst, Value(0u));
} else if (inst.GetOpcode() == ValueOpcode::BitFieldUExtract) {
const auto mask =
count.U32() == 32u ? UINT32_MAX : (uint32_t {1} << count.U32()) - 1u;
Replace(inst, Value((value.U32() >> offset.U32()) & mask));
} else {
const auto left = 32u - offset.U32() - count.U32();
const auto bits = value.U32() << left;
Replace(inst, Value(static_cast<uint32_t>(std::bit_cast<int32_t>(bits) >>
(left + offset.U32()))));
}
return;
}
case ValueOpcode::BitCastU16F16: FoldBitCast(inst, ValueOpcode::BitCastF16U16); return;
case ValueOpcode::BitCastF16U16: FoldBitCast(inst, ValueOpcode::BitCastU16F16); return;
case ValueOpcode::BitCastU32F32: FoldBitCast(inst, ValueOpcode::BitCastF32U32); return;
case ValueOpcode::BitCastF32U32: FoldBitCast(inst, ValueOpcode::BitCastU32F32); return;
case ValueOpcode::ConvertU16U32: {
const auto value = Arg(inst, 0);
if (IsImmediate(value, Type::U32)) {
Replace(inst, Value(static_cast<uint16_t>(value.U32())));
}
return;
}
case ValueOpcode::ConvertU32U16: {
const auto value = Arg(inst, 0);
if (IsImmediate(value, Type::U16)) {
Replace(inst, Value(static_cast<uint32_t>(value.U16())));
} else if (auto* producer = value.TryInstruction();
producer != nullptr && producer->GetOpcode() == ValueOpcode::ConvertU16U32) {
Replace(inst, producer->Arg(0));
}
return;
}
case ValueOpcode::ConvertU8U32: {
const auto value = Arg(inst, 0);
if (IsImmediate(value, Type::U32)) {
Replace(inst, Value(static_cast<uint8_t>(value.U32())));
}
return;
}
case ValueOpcode::ConvertU32U8: {
const auto value = Arg(inst, 0);
if (IsImmediate(value, Type::U8)) {
Replace(inst, Value(static_cast<uint32_t>(value.U8())));
} else if (auto* producer = value.TryInstruction();
producer != nullptr && producer->GetOpcode() == ValueOpcode::ConvertU8U32) {
Replace(inst, producer->Arg(0));
}
return;
}
case ValueOpcode::CompositeExtractU32x2:
FoldCompositeExtract(inst, ValueOpcode::CompositeConstructU32x2, 2);
return;
case ValueOpcode::CompositeExtractU32x4:
FoldCompositeExtract(inst, ValueOpcode::CompositeConstructU32x4, 4);
return;
case ValueOpcode::PackUint2x32: {
const auto value = Arg(inst, 0);
const auto* producer = value.TryInstruction();
if (producer != nullptr && producer->GetOpcode() == ValueOpcode::UnpackUint2x32) {
Replace(inst, producer->Arg(0));
} else if (producer != nullptr &&
producer->GetOpcode() == ValueOpcode::CompositeConstructU32x2) {
const auto low = producer->Arg(0).Resolve();
const auto high = producer->Arg(1).Resolve();
if (IsImmediate(low, Type::U32) && IsImmediate(high, Type::U32)) {
Replace(inst, Value(static_cast<uint64_t>(low.U32()) |
(static_cast<uint64_t>(high.U32()) << 32u)));
}
}
return;
}
case ValueOpcode::UnpackUint2x32: {
const auto value = Arg(inst, 0);
const auto* producer = value.TryInstruction();
if (producer != nullptr && producer->GetOpcode() == ValueOpcode::PackUint2x32) {
Replace(inst, producer->Arg(0));
}
return;
}
case ValueOpcode::IAdd32:
if (!FoldU32(inst, [](uint32_t a, uint32_t b) { return a + b; })) {
ReplaceBinaryIdentity(inst, Type::U32, 0u);
}
return;
case ValueOpcode::IAdd64:
if (!FoldU64(inst, [](uint64_t a, uint64_t b) { return a + b; })) {
ReplaceBinaryIdentity(inst, Type::U64, 0u);
}
return;
case ValueOpcode::ISub32: {
if (FoldU32(inst, [](uint32_t a, uint32_t b) { return a - b; })) {
return;
}
const auto rhs = Arg(inst, 1);
if (IsImmediate(rhs, Type::U32) && rhs.U32() == 0u) {
Replace(inst, Arg(inst, 0));
}
return;
}
case ValueOpcode::ISub64: {
if (FoldU64(inst, [](uint64_t a, uint64_t b) { return a - b; })) {
return;
}
const auto rhs = Arg(inst, 1);
if (IsImmediate(rhs, Type::U64) && rhs.U64() == 0u) {
Replace(inst, Arg(inst, 0));
}
return;
}
case ValueOpcode::IMul32:
if (!FoldU32(inst, [](uint32_t a, uint32_t b) { return a * b; })) {
ReplaceBinaryIdentity(inst, Type::U32, 1u);
}
return;
case ValueOpcode::IMul64:
if (!FoldU64(inst, [](uint64_t a, uint64_t b) { return a * b; })) {
ReplaceBinaryIdentity(inst, Type::U64, 1u);
}
return;
case ValueOpcode::UDiv32: {
const auto rhs = Arg(inst, 1);
if (IsImmediate(rhs, Type::U32) && rhs.U32() == 1u) {
Replace(inst, Arg(inst, 0));
} else if (IsImmediate(rhs, Type::U32) && rhs.U32() != 0u) {
FoldU32(inst, [](uint32_t a, uint32_t b) { return a / b; });
}
return;
}
case ValueOpcode::SMulHi:
FoldU32(inst, [](uint32_t a, uint32_t b) {
const auto product = static_cast<int64_t>(std::bit_cast<int32_t>(a)) *
static_cast<int64_t>(std::bit_cast<int32_t>(b));
return static_cast<uint32_t>(static_cast<uint64_t>(product) >> 32u);
});
return;
case ValueOpcode::UMulHi:
FoldU32(inst, [](uint32_t a, uint32_t b) {
return static_cast<uint32_t>((static_cast<uint64_t>(a) * b) >> 32u);
});
return;
case ValueOpcode::IAbs32: {
const auto value = Arg(inst, 0);
if (IsImmediate(value, Type::U32)) {
Replace(inst,
Value((value.U32() & 0x80000000u) != 0u ? 0u - value.U32() : value.U32()));
}
return;
}
case ValueOpcode::ShiftLeftLogical32:
if (!FoldU32(inst, [](uint32_t a, uint32_t b) { return a << (b & 31u); })) {
const auto shift = Arg(inst, 1);
if (IsImmediate(shift, Type::U32) && (shift.U32() & 31u) == 0u) {
Replace(inst, Arg(inst, 0));
}
}
return;
case ValueOpcode::ShiftRightLogical32:
if (!FoldU32(inst, [](uint32_t a, uint32_t b) { return a >> (b & 31u); })) {
const auto shift = Arg(inst, 1);
if (IsImmediate(shift, Type::U32) && (shift.U32() & 31u) == 0u) {
Replace(inst, Arg(inst, 0));
}
}
return;
case ValueOpcode::ShiftRightArithmetic32:
FoldU32(inst, [](uint32_t a, uint32_t b) {
return static_cast<uint32_t>(std::bit_cast<int32_t>(a) >> (b & 31u));
});
return;
case ValueOpcode::ShiftLeftLogical64:
if (!FoldU64Shift(inst, [](uint64_t a, uint32_t b) { return a << (b & 63u); })) {
const auto shift = Arg(inst, 1);
if (IsImmediate(shift, Type::U32) && (shift.U32() & 63u) == 0u) {
Replace(inst, Arg(inst, 0));
}
}
return;
case ValueOpcode::ShiftRightLogical64:
if (!FoldU64Shift(inst, [](uint64_t a, uint32_t b) { return a >> (b & 63u); })) {
const auto shift = Arg(inst, 1);
if (IsImmediate(shift, Type::U32) && (shift.U32() & 63u) == 0u) {
Replace(inst, Arg(inst, 0));
}
}
return;
case ValueOpcode::ShiftRightArithmetic64:
FoldU64Shift(inst, [](uint64_t a, uint32_t b) {
return static_cast<uint64_t>(std::bit_cast<int64_t>(a) >> (b & 63u));
});
return;
case ValueOpcode::BitwiseAnd32:
if (!FoldU32(inst, [](uint32_t a, uint32_t b) { return a & b; })) {
ReplaceBinaryIdentity(inst, Type::U32, 0xffffffffu);
}
return;
case ValueOpcode::BitwiseAnd64:
if (!FoldU64(inst, [](uint64_t a, uint64_t b) { return a & b; })) {
ReplaceBinaryIdentity(inst, Type::U64, UINT64_MAX);
}
return;
case ValueOpcode::BitwiseOr32:
case ValueOpcode::BitwiseXor32:
if (!FoldU32(inst, [opcode = inst.GetOpcode()](uint32_t a, uint32_t b) {
return opcode == ValueOpcode::BitwiseOr32 ? a | b : a ^ b;
})) {
ReplaceBinaryIdentity(inst, Type::U32, 0u);
}
return;
case ValueOpcode::BitwiseOr64:
if (!FoldU64(inst, [](uint64_t a, uint64_t b) { return a | b; })) {
ReplaceBinaryIdentity(inst, Type::U64, 0u);
}
return;
case ValueOpcode::BitwiseNot32: {
const auto value = Arg(inst, 0);
if (IsImmediate(value, Type::U32)) {
Replace(inst, Value(~value.U32()));
} else if (auto* producer = value.TryInstruction();
producer != nullptr && producer->GetOpcode() == ValueOpcode::BitwiseNot32) {
Replace(inst, producer->Arg(0));
}
return;
}
case ValueOpcode::BitCount32: {
const auto value = Arg(inst, 0);
if (IsImmediate(value, Type::U32)) {
Replace(inst, Value(static_cast<uint32_t>(std::popcount(value.U32()))));
}
return;
}
case ValueOpcode::BitCount64: {
const auto value = Arg(inst, 0);
if (IsImmediate(value, Type::U64)) {
Replace(inst, Value(static_cast<uint32_t>(std::popcount(value.U64()))));
}
return;
}
case ValueOpcode::SMin32:
case ValueOpcode::SMax32:
FoldU32(inst, [opcode = inst.GetOpcode()](uint32_t a, uint32_t b) {
const auto lhs = std::bit_cast<int32_t>(a);
const auto rhs = std::bit_cast<int32_t>(b);
return opcode == ValueOpcode::SMin32 ? (lhs < rhs ? a : b) : (lhs > rhs ? a : b);
});
return;
case ValueOpcode::UMin32:
FoldU32(inst, [](uint32_t a, uint32_t b) { return std::min(a, b); });
return;
case ValueOpcode::UMax32:
FoldU32(inst, [](uint32_t a, uint32_t b) { return std::max(a, b); });
return;
case ValueOpcode::IEqual32:
FoldU32Compare(inst, [](uint32_t a, uint32_t b) { return a == b; });
return;
case ValueOpcode::INotEqual32:
FoldU32Compare(inst, [](uint32_t a, uint32_t b) { return a != b; });
return;
case ValueOpcode::ULessThan32:
FoldU32Compare(inst, [](uint32_t a, uint32_t b) { return a < b; });
return;
case ValueOpcode::ULessThanEqual32:
FoldU32Compare(inst, [](uint32_t a, uint32_t b) { return a <= b; });
return;
case ValueOpcode::UGreaterThan32:
FoldU32Compare(inst, [](uint32_t a, uint32_t b) { return a > b; });
return;
case ValueOpcode::UGreaterThanEqual32:
FoldU32Compare(inst, [](uint32_t a, uint32_t b) { return a >= b; });
return;
case ValueOpcode::SLessThan32:
case ValueOpcode::SLessThanEqual32:
case ValueOpcode::SGreaterThan32:
case ValueOpcode::SGreaterThanEqual32:
FoldU32Compare(inst, [opcode = inst.GetOpcode()](uint32_t a, uint32_t b) {
const auto lhs = std::bit_cast<int32_t>(a);
const auto rhs = std::bit_cast<int32_t>(b);
switch (opcode) {
case ValueOpcode::SLessThan32: return lhs < rhs;
case ValueOpcode::SLessThanEqual32: return lhs <= rhs;
case ValueOpcode::SGreaterThan32: return lhs > rhs;
default: return lhs >= rhs;
}
});
return;
case ValueOpcode::IEqual64:
FoldU64Compare(inst, [](uint64_t a, uint64_t b) { return a == b; });
return;
case ValueOpcode::INotEqual64:
FoldU64Compare(inst, [](uint64_t a, uint64_t b) { return a != b; });
return;
case ValueOpcode::ULessThan64:
FoldU64Compare(inst, [](uint64_t a, uint64_t b) { return a < b; });
return;
case ValueOpcode::ULessThanEqual64:
FoldU64Compare(inst, [](uint64_t a, uint64_t b) { return a <= b; });
return;
case ValueOpcode::UGreaterThan64:
FoldU64Compare(inst, [](uint64_t a, uint64_t b) { return a > b; });
return;
case ValueOpcode::UGreaterThanEqual64:
FoldU64Compare(inst, [](uint64_t a, uint64_t b) { return a >= b; });
return;
case ValueOpcode::SLessThan64:
case ValueOpcode::SLessThanEqual64:
case ValueOpcode::SGreaterThan64:
case ValueOpcode::SGreaterThanEqual64:
FoldU64Compare(inst, [opcode = inst.GetOpcode()](uint64_t a, uint64_t b) {
const auto lhs = std::bit_cast<int64_t>(a);
const auto rhs = std::bit_cast<int64_t>(b);
switch (opcode) {
case ValueOpcode::SLessThan64: return lhs < rhs;
case ValueOpcode::SLessThanEqual64: return lhs <= rhs;
case ValueOpcode::SGreaterThan64: return lhs > rhs;
default: return lhs >= rhs;
}
});
return;
case ValueOpcode::LogicalAnd:
if (!FoldLogical(inst, [](bool a, bool b) { return a && b; })) {
const auto lhs = Arg(inst, 0);
const auto rhs = Arg(inst, 1);
if (IsImmediate(lhs, Type::U1)) {
Replace(inst, lhs.U1() ? rhs : lhs);
} else if (IsImmediate(rhs, Type::U1)) {
Replace(inst, rhs.U1() ? lhs : rhs);
}
}
return;
case ValueOpcode::LogicalOr:
if (!FoldLogical(inst, [](bool a, bool b) { return a || b; })) {
const auto lhs = Arg(inst, 0);
const auto rhs = Arg(inst, 1);
if (IsImmediate(lhs, Type::U1)) {
Replace(inst, lhs.U1() ? lhs : rhs);
} else if (IsImmediate(rhs, Type::U1)) {
Replace(inst, rhs.U1() ? rhs : lhs);
}
}
return;
case ValueOpcode::LogicalXor:
if (!FoldLogical(inst, [](bool a, bool b) { return a != b; })) {
const auto lhs = Arg(inst, 0);
const auto rhs = Arg(inst, 1);
if (IsImmediate(lhs, Type::U1) && !lhs.U1()) {
Replace(inst, rhs);
} else if (IsImmediate(rhs, Type::U1) && !rhs.U1()) {
Replace(inst, lhs);
}
}
return;
case ValueOpcode::LogicalNot: {
const auto value = Arg(inst, 0);
if (IsImmediate(value, Type::U1)) {
Replace(inst, Value(!value.U1()));
} else if (auto* producer = value.TryInstruction();
producer != nullptr && producer->GetOpcode() == ValueOpcode::LogicalNot) {
Replace(inst, producer->Arg(0));
}
return;
}
default: return;
}
}
} // namespace
void ConstantPropagationPass(const BlockList& blocks) {
for (auto* block: blocks) {
for (auto& inst: block->Instructions()) {
FoldInstruction(inst);
}
}
}
} // namespace Libs::Graphics::ShaderRecompiler::IR