mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 00:22:57 -05:00
v_cmp_X_f16 and v_cmpx_X_f16 compared denormal halves as their exact values in every mode. On RDNA2 (gfx1031) f16 denormal modes 0 and 2 flush both inputs to a zero of the same sign first, so v_cmp_eq_f16 0x0000, 0x0001 is true there. Both inputs now go through flushF16InputValue; modes 1 and 3 and shaders without a known float mode are translated as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
286 lines
16 KiB
C++
286 lines
16 KiB
C++
#include "Translation/CompareInstructions.hpp"
|
|
#include "Translation/TranslationContext.hpp"
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <optional>
|
|
#include <stdexcept>
|
|
#include <utility>
|
|
|
|
namespace ShaderRecompiler {
|
|
|
|
namespace {
|
|
|
|
std::optional<std::uint32_t> constantBits(const IrValue& value) {
|
|
if (value.HasImmediate()) return value.ImmediateU32();
|
|
const IrOpcode opcode = value.Opcode();
|
|
if (opcode != IrOpcode::BitwiseAnd32 && opcode != IrOpcode::BitwiseOr32 && opcode != IrOpcode::BitwiseXor32) return std::nullopt;
|
|
const auto lhs = constantBits(*value.Argument(0));
|
|
const auto rhs = constantBits(*value.Argument(1));
|
|
if (!lhs.has_value() || !rhs.has_value()) return std::nullopt;
|
|
if (opcode == IrOpcode::BitwiseAnd32) return *lhs & *rhs;
|
|
if (opcode == IrOpcode::BitwiseOr32) return *lhs | *rhs;
|
|
return *lhs ^ *rhs;
|
|
}
|
|
|
|
IrOpcode mirroredFloatCompare(IrOpcode opcode) {
|
|
switch (opcode) {
|
|
case IrOpcode::FPOrdLessThan32: return IrOpcode::FPOrdGreaterThan32;
|
|
case IrOpcode::FPUnordLessThan32: return IrOpcode::FPUnordGreaterThan32;
|
|
case IrOpcode::FPOrdGreaterThan32: return IrOpcode::FPOrdLessThan32;
|
|
case IrOpcode::FPUnordGreaterThan32: return IrOpcode::FPUnordLessThan32;
|
|
case IrOpcode::FPOrdLessThanEqual32: return IrOpcode::FPOrdGreaterThanEqual32;
|
|
case IrOpcode::FPUnordLessThanEqual32: return IrOpcode::FPUnordGreaterThanEqual32;
|
|
case IrOpcode::FPOrdGreaterThanEqual32: return IrOpcode::FPOrdLessThanEqual32;
|
|
case IrOpcode::FPUnordGreaterThanEqual32: return IrOpcode::FPUnordLessThanEqual32;
|
|
case IrOpcode::FPOrdEqual32:
|
|
case IrOpcode::FPUnordEqual32:
|
|
case IrOpcode::FPOrdNotEqual32:
|
|
case IrOpcode::FPUnordNotEqual32: return opcode;
|
|
default: throw std::runtime_error("f32 compare against a constant: unexpected compare opcode");
|
|
}
|
|
}
|
|
|
|
bool unorderedInfinityCompareIsTrue(IrOpcode opcode, bool constantIsLhs, std::uint32_t constant) {
|
|
if (constant == 0x7f800000u) {
|
|
return constantIsLhs ? opcode == IrOpcode::FPUnordGreaterThanEqual32 : opcode == IrOpcode::FPUnordLessThanEqual32;
|
|
}
|
|
if (constant == 0xff800000u) {
|
|
return constantIsLhs ? opcode == IrOpcode::FPUnordLessThanEqual32 : opcode == IrOpcode::FPUnordGreaterThanEqual32;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
struct OrderedRelation {
|
|
IrOpcode integerOpcode;
|
|
bool unordered;
|
|
};
|
|
|
|
OrderedRelation orderedRelationOf(IrOpcode opcode) {
|
|
switch (opcode) {
|
|
case IrOpcode::FPOrdEqual32: return {IrOpcode::IEqual32, false};
|
|
case IrOpcode::FPUnordEqual32: return {IrOpcode::IEqual32, true};
|
|
case IrOpcode::FPOrdNotEqual32: return {IrOpcode::INotEqual32, false};
|
|
case IrOpcode::FPUnordNotEqual32: return {IrOpcode::INotEqual32, true};
|
|
case IrOpcode::FPOrdLessThan32: return {IrOpcode::SLessThan32, false};
|
|
case IrOpcode::FPUnordLessThan32: return {IrOpcode::SLessThan32, true};
|
|
case IrOpcode::FPOrdLessThanEqual32: return {IrOpcode::SLessThanEqual32, false};
|
|
case IrOpcode::FPUnordLessThanEqual32: return {IrOpcode::SLessThanEqual32, true};
|
|
case IrOpcode::FPOrdGreaterThan32: return {IrOpcode::SGreaterThan32, false};
|
|
case IrOpcode::FPUnordGreaterThan32: return {IrOpcode::SGreaterThan32, true};
|
|
case IrOpcode::FPOrdGreaterThanEqual32: return {IrOpcode::SGreaterThanEqual32, false};
|
|
case IrOpcode::FPUnordGreaterThanEqual32: return {IrOpcode::SGreaterThanEqual32, true};
|
|
default: throw std::runtime_error("f32 compare keeping denormals: unexpected compare opcode");
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void TranslateCompareInstruction(IrBuilder& builder, const RdnaInstruction& instruction) {
|
|
throw std::runtime_error("TranslateCompareInstruction not implemented");
|
|
}
|
|
|
|
void TranslationContext::emitCompareResult(const RdnaInstruction& inst, IrU1 value, bool scalar, bool cmpx) {
|
|
if (scalar) {
|
|
ir.SetScc(value.Value());
|
|
return;
|
|
}
|
|
IrValue& masked = ir.LogicalAnd(ir.GetExec(), value.Value());
|
|
if (cmpx) {
|
|
const std::array<IrU32, 2> mask = ballotMask(IrU1(masked));
|
|
ir.SetExec(masked);
|
|
ir.SetExecLo(mask[0].Value());
|
|
ir.SetExecHi(mask[1].Value());
|
|
return;
|
|
}
|
|
writeMask(inst.destination, IrU1(masked));
|
|
}
|
|
|
|
void TranslationContext::emitCompareConstant(const RdnaInstruction& inst, bool value, bool scalar, bool cmpx) {
|
|
emitCompareResult(inst, IrU1(ir.ConstantBool(value)), scalar, cmpx);
|
|
}
|
|
|
|
void TranslationContext::emitIntegerCompare(const RdnaInstruction& inst, IrOpcode opcode, IrType type, bool scalar, bool cmpx) {
|
|
IrValue* lhs = readOperand(sourceAt(inst, 0u), type);
|
|
IrValue* rhs = readOperand(sourceAt(inst, 1u), type);
|
|
emitCompareResult(inst, IrU1(ir.Emit(opcode, IrType::U1, {lhs, rhs})), scalar, cmpx);
|
|
}
|
|
|
|
void TranslationContext::emitInteger16Compare(const RdnaInstruction& inst, IrOpcode opcode, bool signedValue, bool cmpx) {
|
|
const IrU32 lhs = readU16AsU32(sourceAt(inst, 0u), signedValue);
|
|
const IrU32 rhs = readU16AsU32(sourceAt(inst, 1u), signedValue);
|
|
emitCompareResult(inst, IrU1(ir.Emit(opcode, IrType::U1, {&lhs.Value(), &rhs.Value()})), false, cmpx);
|
|
}
|
|
|
|
void TranslationContext::emitFloatCompare(const RdnaInstruction& inst, IrOpcode opcode, bool half, bool cmpx, bool swap) {
|
|
IrValue* lhs = nullptr;
|
|
IrValue* rhs = nullptr;
|
|
if (half) {
|
|
lhs = &flushF16InputValue(readF16AsF32(sourceAt(inst, 0u))).Value();
|
|
rhs = &flushF16InputValue(readF16AsF32(sourceAt(inst, 1u))).Value();
|
|
} else {
|
|
IrValue* lhsBits = readOperand(sourceAt(inst, 0u), IrType::U32);
|
|
IrValue* rhsBits = readOperand(sourceAt(inst, 1u), IrType::U32);
|
|
if (swap) std::swap(lhsBits, rhsBits);
|
|
const auto lhsConstant = constantBits(*lhsBits);
|
|
const auto rhsConstant = constantBits(*rhsBits);
|
|
if (lhsConstant.has_value() && !rhsConstant.has_value() && unorderedInfinityCompareIsTrue(opcode, true, *lhsConstant)) {
|
|
emitCompareConstant(inst, true, false, cmpx);
|
|
return;
|
|
}
|
|
if (!lhsConstant.has_value() && rhsConstant.has_value() && unorderedInfinityCompareIsTrue(opcode, false, *rhsConstant)) {
|
|
emitCompareConstant(inst, true, false, cmpx);
|
|
return;
|
|
}
|
|
if ((f32DenormalFlush & 1u) != 0u && lhsConstant.has_value() != rhsConstant.has_value()) {
|
|
const IrOpcode oriented = lhsConstant.has_value() ? mirroredFloatCompare(opcode) : opcode;
|
|
emitCompareResult(inst, floatCompareWithConstant(oriented, IrU32(lhsConstant.has_value() ? *rhsBits : *lhsBits), lhsConstant.has_value() ? *lhsConstant : *rhsConstant), false, cmpx);
|
|
return;
|
|
}
|
|
if ((f32DenormalFlush & 1u) == 0u) {
|
|
emitCompareResult(inst, floatCompareKeepingDenormals(opcode, IrU32(*lhsBits), IrU32(*rhsBits)), false, cmpx);
|
|
return;
|
|
}
|
|
lhs = &ir.BitCastF32(flushF32Denormal(IrU32(*lhsBits)).Value());
|
|
rhs = &ir.BitCastF32(flushF32Denormal(IrU32(*rhsBits)).Value());
|
|
emitCompareResult(inst, IrU1(ir.Emit(opcode, IrType::U1, {lhs, rhs})), false, cmpx);
|
|
return;
|
|
}
|
|
if (swap) std::swap(lhs, rhs);
|
|
emitCompareResult(inst, IrU1(ir.Emit(opcode, IrType::U1, {lhs, rhs})), false, cmpx);
|
|
}
|
|
|
|
IrU1 TranslationContext::floatCompareKeepingDenormals(IrOpcode opcode, IrU32 lhsBits, IrU32 rhsBits) {
|
|
const auto magnitude = [&](IrU32 bits) -> IrValue& { return ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffffffu)); };
|
|
const auto order = [&](IrU32 bits) -> IrValue& {
|
|
IrValue& absolute = magnitude(bits);
|
|
IrValue& negative = ir.INotEqual(ir.BitwiseAnd(bits.Value(), ir.Constant(0x80000000u)), ir.Constant(0u));
|
|
return ir.Select(negative, ir.ISub(ir.Constant(0u), absolute), absolute);
|
|
};
|
|
const auto isNan = [&](IrU32 bits) -> IrValue& { return ir.UGreaterThan(magnitude(bits), ir.Constant(0x7f800000u)); };
|
|
const OrderedRelation relation = orderedRelationOf(opcode);
|
|
IrValue& ordering = ir.Emit(relation.integerOpcode, IrType::U1, {&order(lhsBits), &order(rhsBits)});
|
|
IrValue& eitherNan = ir.LogicalOr(isNan(lhsBits), isNan(rhsBits));
|
|
return IrU1(relation.unordered ? ir.LogicalOr(eitherNan, ordering) : ir.LogicalAnd(ir.LogicalNot(eitherNan), ordering));
|
|
}
|
|
|
|
IrU1 TranslationContext::floatCompareWithConstant(IrOpcode opcode, IrU32 variableBits, std::uint32_t constant) {
|
|
IrValue& value = ir.BitCastF32(variableBits.Value());
|
|
const auto compare = [&](IrOpcode compareOpcode, IrValue& lhs, std::uint32_t rhs) { return IrU1(ir.Emit(compareOpcode, IrType::U1, {&lhs, &ir.BitCastF32(ir.Constant(rhs))})); };
|
|
if ((constant & 0x7f800000u) != 0u) return compare(opcode, value, constant);
|
|
IrValue& magnitude = ir.BitCastF32(ir.BitwiseAnd(variableBits.Value(), ir.Constant(0x7fffffffu)));
|
|
constexpr std::uint32_t MinNormal = 0x00800000u;
|
|
constexpr std::uint32_t NegativeMinNormal = 0x80800000u;
|
|
switch (opcode) {
|
|
case IrOpcode::FPOrdLessThan32: return compare(IrOpcode::FPOrdLessThanEqual32, value, NegativeMinNormal);
|
|
case IrOpcode::FPUnordLessThan32: return compare(IrOpcode::FPUnordLessThanEqual32, value, NegativeMinNormal);
|
|
case IrOpcode::FPOrdLessThanEqual32: return compare(IrOpcode::FPOrdLessThan32, value, MinNormal);
|
|
case IrOpcode::FPUnordLessThanEqual32: return compare(IrOpcode::FPUnordLessThan32, value, MinNormal);
|
|
case IrOpcode::FPOrdGreaterThan32: return compare(IrOpcode::FPOrdGreaterThanEqual32, value, MinNormal);
|
|
case IrOpcode::FPUnordGreaterThan32: return compare(IrOpcode::FPUnordGreaterThanEqual32, value, MinNormal);
|
|
case IrOpcode::FPOrdGreaterThanEqual32: return compare(IrOpcode::FPOrdGreaterThan32, value, NegativeMinNormal);
|
|
case IrOpcode::FPUnordGreaterThanEqual32: return compare(IrOpcode::FPUnordGreaterThan32, value, NegativeMinNormal);
|
|
case IrOpcode::FPOrdEqual32: return compare(IrOpcode::FPOrdLessThan32, magnitude, MinNormal);
|
|
case IrOpcode::FPUnordEqual32: return compare(IrOpcode::FPUnordLessThan32, magnitude, MinNormal);
|
|
case IrOpcode::FPOrdNotEqual32: return compare(IrOpcode::FPOrdGreaterThanEqual32, magnitude, MinNormal);
|
|
case IrOpcode::FPUnordNotEqual32: return compare(IrOpcode::FPUnordGreaterThanEqual32, magnitude, MinNormal);
|
|
default: throw std::runtime_error("f32 compare against a constant: unexpected compare opcode");
|
|
}
|
|
}
|
|
|
|
void TranslationContext::emitInteger64Order(const RdnaInstruction& inst, bool signedValue, bool swap, bool negate, bool cmpx) {
|
|
IrValue* lhs = readOperand(sourceAt(inst, swap ? 1u : 0u), IrType::U64);
|
|
IrValue* rhs = readOperand(sourceAt(inst, swap ? 0u : 1u), IrType::U64);
|
|
IrValue& less = ir.Emit(signedValue ? IrOpcode::SLessThan64 : IrOpcode::ULessThan64, IrType::U1, {lhs, rhs});
|
|
emitCompareResult(inst, IrU1(negate ? ir.LogicalNot(less) : less), false, cmpx);
|
|
}
|
|
|
|
void TranslationContext::emitFloatOrderedCompare(const RdnaInstruction& inst, bool ordered, bool half, bool cmpx) {
|
|
IrValue* lhs = half ? &readF16AsF32(sourceAt(inst, 0u)).Value() : readOperand(sourceAt(inst, 0u), IrType::F32);
|
|
IrValue* rhs = half ? &readF16AsF32(sourceAt(inst, 1u)).Value() : readOperand(sourceAt(inst, 1u), IrType::F32);
|
|
IrValue& lhsNan = ir.Emit(IrOpcode::FPIsNan32, IrType::U1, {lhs});
|
|
IrValue& rhsNan = ir.Emit(IrOpcode::FPIsNan32, IrType::U1, {rhs});
|
|
IrValue& unordered = ir.LogicalOr(lhsNan, rhsNan);
|
|
IrValue& result = ordered ? ir.LogicalNot(unordered) : unordered;
|
|
emitCompareResult(inst, IrU1(result), false, cmpx);
|
|
}
|
|
|
|
void TranslationContext::emitFloatClassCompare(const RdnaInstruction& inst, bool cmpx) {
|
|
IrValue* value = readOperand(sourceAt(inst, 0u), IrType::F32);
|
|
IrValue* mask = readOperand(sourceAt(inst, 1u), IrType::U32);
|
|
emitCompareResult(inst, IrU1(ir.Emit(IrOpcode::FPCmpClass32, IrType::U1, {value, mask})), false, cmpx);
|
|
}
|
|
|
|
IrU1 TranslationContext::float64IsNan(const std::array<IrU32, 2>& bits) {
|
|
IrValue& magnitude = ir.ConstructU64(bits[0].Value(), ir.BitwiseAnd(bits[1].Value(), ir.Constant(0x7fffffffu)));
|
|
return IrU1(ir.Emit(IrOpcode::UGreaterThan64, IrType::U1, {&magnitude, &ir.ConstantU64(0x7ff0000000000000ull)}));
|
|
}
|
|
|
|
IrU64 TranslationContext::float64OrderKey(const std::array<IrU32, 2>& bits) {
|
|
IrValue& magnitude = ir.ConstructU64(bits[0].Value(), ir.BitwiseAnd(bits[1].Value(), ir.Constant(0x7fffffffu)));
|
|
IrValue& signWord = ir.ShiftRightArithmetic(bits[1].Value(), ir.Constant(31u));
|
|
IrValue& signMask = ir.ConstructU64(signWord, signWord);
|
|
IrValue& negativeMagnitude = ir.Emit(IrOpcode::BitwiseAnd64, IrType::U64, {&magnitude, &signMask});
|
|
IrValue& doubled = ir.Emit(IrOpcode::ShiftLeftLogical64, IrType::U64, {&negativeMagnitude, &ir.Constant(1u)});
|
|
return IrU64(ir.Emit(IrOpcode::ISub64, IrType::U64, {&magnitude, &doubled}));
|
|
}
|
|
|
|
void TranslationContext::emitFloat64Compare(const RdnaInstruction& inst, bool less, bool equal, bool greater, bool unordered, bool cmpx) {
|
|
const std::array<IrU32, 2> lhs = readF64Bits(sourceAt(inst, 0u));
|
|
const std::array<IrU32, 2> rhs = readF64Bits(sourceAt(inst, 1u));
|
|
const IrU1 lhsNan = float64IsNan(lhs);
|
|
const IrU1 rhsNan = float64IsNan(rhs);
|
|
IrValue& isUnordered = ir.LogicalOr(lhsNan.Value(), rhsNan.Value());
|
|
const IrU64 lhsKey = float64OrderKey(lhs);
|
|
const IrU64 rhsKey = float64OrderKey(rhs);
|
|
IrValue* relation = nullptr;
|
|
const auto include = [&](IrValue& term) {
|
|
relation = relation == nullptr ? &term : &ir.LogicalOr(*relation, term);
|
|
};
|
|
if (less) {
|
|
include(ir.Emit(IrOpcode::SLessThan64, IrType::U1, {&lhsKey.Value(), &rhsKey.Value()}));
|
|
}
|
|
if (equal) {
|
|
include(ir.Emit(IrOpcode::IEqual64, IrType::U1, {&lhsKey.Value(), &rhsKey.Value()}));
|
|
}
|
|
if (greater) {
|
|
include(ir.Emit(IrOpcode::SLessThan64, IrType::U1, {&rhsKey.Value(), &lhsKey.Value()}));
|
|
}
|
|
if (relation == nullptr) {
|
|
relation = &ir.ConstantBool(false);
|
|
}
|
|
IrValue& result = unordered ? ir.LogicalOr(isUnordered, *relation) : ir.LogicalAnd(ir.LogicalNot(isUnordered), *relation);
|
|
emitCompareResult(inst, IrU1(result), false, cmpx);
|
|
}
|
|
|
|
void TranslationContext::emitFloat64ClassCompare(const RdnaInstruction& inst, bool cmpx) {
|
|
const RdnaOperand& maskOperand = sourceAt(inst, 1u);
|
|
if (maskOperand.absolute || maskOperand.negate) {
|
|
throw std::runtime_error("TranslationContext::emitFloat64ClassCompare class mask does not take source modifiers");
|
|
}
|
|
const std::array<IrU32, 2> bits = readF64Bits(sourceAt(inst, 0u));
|
|
const IrU32 mask = readU32(maskOperand);
|
|
IrValue& zero = ir.Constant(0u);
|
|
IrValue& negative = ir.INotEqual(ir.BitwiseAnd(bits[1].Value(), ir.Constant(0x80000000u)), zero);
|
|
IrValue& exponent = ir.BitwiseAnd(bits[1].Value(), ir.Constant(0x7ff00000u));
|
|
IrValue& fraction = ir.BitwiseOr(ir.BitwiseAnd(bits[1].Value(), ir.Constant(0x000fffffu)), bits[0].Value());
|
|
IrValue& fractionNonZero = ir.INotEqual(fraction, zero);
|
|
IrValue& quiet = ir.INotEqual(ir.BitwiseAnd(bits[1].Value(), ir.Constant(0x00080000u)), zero);
|
|
IrValue& nan = ir.Select(quiet, ir.Constant(1u), zero);
|
|
IrValue& infinity = ir.Select(negative, ir.Constant(2u), ir.Constant(9u));
|
|
IrValue& normal = ir.Select(negative, ir.Constant(3u), ir.Constant(8u));
|
|
IrValue& denormal = ir.Select(negative, ir.Constant(4u), ir.Constant(7u));
|
|
IrValue& signedZero = ir.Select(negative, ir.Constant(5u), ir.Constant(6u));
|
|
IrValue& exponentMax = ir.Select(fractionNonZero, nan, infinity);
|
|
IrValue& exponentZero = ir.Select(fractionNonZero, denormal, signedZero);
|
|
IrValue& finite = ir.Select(ir.IEqual(exponent, zero), exponentZero, normal);
|
|
IrValue& index = ir.Select(ir.IEqual(exponent, ir.Constant(0x7ff00000u)), exponentMax, finite);
|
|
IrValue& selected = ir.BitwiseAnd(ir.ShiftRightLogical(mask.Value(), index), ir.Constant(1u));
|
|
emitCompareResult(inst, IrU1(ir.INotEqual(selected, zero)), false, cmpx);
|
|
}
|
|
|
|
void TranslateCompareInstruction(TranslationContext& context, const RdnaInstruction& instruction) {
|
|
throw std::runtime_error("TranslateCompareInstruction not implemented");
|
|
}
|
|
|
|
}
|