perf(shader): skip the tiny product check that a constant factor settles
With f32 denormals flushed, every f32 multiply and fma checks whether the exact product is below 2^-126 (#1850): two exponent extracts, a 32-bit significand multiply-high and a chain of compares and selects. When a factor is a literal or inline constant, its exponent settles the check during translation: - a factor that is zero (after the input flush), infinite, NaN or at least 1 in magnitude never gives a tiny product, so the check is not emitted; - a power-of-two factor never carries out of the significand product, so the multiply-high is not emitted. Constant sources are folded with their abs, neg and denormal flush, so the factor reaches the check as a constant.
This commit is contained in:
parent
6cc0503dac
commit
809178603f
|
|
@ -931,6 +931,14 @@ if(BUILD_TESTING)
|
|||
set_tests_properties(vector_fused_multiply_add PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test_executable(vector_constant_factor_tininess_tests ${LIBS_INCLUDE_DIR}/tests/VectorConstantFactorTininess.cpp)
|
||||
target_link_libraries(vector_constant_factor_tininess_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
configure_windows_unwind(vector_constant_factor_tininess_tests)
|
||||
add_test(NAME vector_constant_factor_tininess COMMAND vector_constant_factor_tininess_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(vector_constant_factor_tininess PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test_executable(scalar_saveexec_tests ${LIBS_INCLUDE_DIR}/tests/ScalarSaveexec.cpp)
|
||||
target_link_libraries(scalar_saveexec_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
configure_windows_unwind(scalar_saveexec_tests)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,165 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Threads = 32;
|
||||
constexpr std::uint32_t Inputs = 4;
|
||||
constexpr std::uint32_t Results = 16;
|
||||
alignas(256) std::array<std::uint32_t, Threads * Inputs> Input{};
|
||||
alignas(256) std::array<std::uint32_t, Threads * Results> Output{};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 48> Code{
|
||||
0x34020084u, 0x34060086u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u,
|
||||
0x7e3402f4u, 0x7e3602f0u, 0x7e3802ffu, 0x3f7ffffeu, 0x7e3a02f2u, 0x7e3c02ffu, 0x00400000u, 0x7e3e02f6u,
|
||||
0x7e4002ffu, 0x3e800000u, 0x7e4202f4u,
|
||||
0x101408f4u, 0x1016091au,
|
||||
0x101808f0u, 0x101a091bu,
|
||||
0x101c08ffu, 0x3f7ffffeu, 0x101e091cu,
|
||||
0x102008ffu, 0x3f800000u, 0x1022091du,
|
||||
0x102408ffu, 0x00400000u, 0x1026091eu,
|
||||
0x0e2808f6u, 0x0e2a091fu,
|
||||
0x582c0b04u, 0x3e800000u, 0xd54b0017u, 0x04164104u,
|
||||
0xd54b0218u, 0x4415e904u, 0xd54b0219u, 0x44164304u,
|
||||
0xe0781000u, 0x80010a03u, 0xe0781010u, 0x80010e03u, 0xe0781020u, 0x80011203u, 0xe0781030u, 0x80011603u,
|
||||
0xbf810000u,
|
||||
};
|
||||
|
||||
constexpr std::uint32_t Rows[32][2] = {
|
||||
{0x00800000u, 0x00000000u}, {0x80800000u, 0x00000000u}, {0x00800001u, 0x00000000u}, {0x00ffffffu, 0x00000000u},
|
||||
{0x01000000u, 0x00000000u}, {0x017fffffu, 0x00000000u}, {0x01800000u, 0x00000000u}, {0x00000001u, 0x00000000u},
|
||||
{0x807fffffu, 0x00000000u}, {0x00000000u, 0x00000000u}, {0x80000000u, 0x80000000u}, {0x7f800000u, 0x00000000u},
|
||||
{0xff800000u, 0x00000000u}, {0x7fc00000u, 0x00000000u}, {0x7f7fffffu, 0x00000000u}, {0x3f800000u, 0x00000000u},
|
||||
{0xbf800000u, 0x3f800000u}, {0x00c00000u, 0x00000000u}, {0x80ffffffu, 0x80000000u}, {0x01000001u, 0x00000000u},
|
||||
{0x3f7fffffu, 0x00000000u}, {0x40490fdbu, 0x00000000u}, {0x00fffffeu, 0x00000000u}, {0x81800000u, 0x00000000u},
|
||||
{0x017ffffeu, 0x00000000u}, {0x01800001u, 0x00000000u}, {0x0080ffffu, 0x00000000u}, {0x7f000000u, 0x00000000u},
|
||||
{0x00800000u, 0x3f800000u}, {0x817fffffu, 0x80000000u}, {0x017fffffu, 0x00800000u}, {0x017fffffu, 0x80000001u}
|
||||
};
|
||||
|
||||
constexpr const char* Names[8] = {
|
||||
"v_mul_f32 2.0",
|
||||
"v_mul_f32 0.5",
|
||||
"v_mul_f32 0x3f7ffffe",
|
||||
"v_mul_f32 0x3f800000",
|
||||
"v_mul_f32 0x00400000",
|
||||
"v_mul_legacy_f32 4.0",
|
||||
"v_fmamk_f32 0x3e800000",
|
||||
"v_fma_f32 -|2.0|"
|
||||
};
|
||||
|
||||
struct Pinned {
|
||||
std::uint32_t tid;
|
||||
std::uint32_t pair;
|
||||
std::uint32_t value;
|
||||
};
|
||||
|
||||
constexpr std::array<Pinned, 6> TinyProducts{{
|
||||
{0u, 2u, 0x00000000u},
|
||||
{3u, 1u, 0x00000000u},
|
||||
{18u, 1u, 0x80000000u},
|
||||
{5u, 6u, 0x00000000u},
|
||||
{29u, 6u, 0x80000000u},
|
||||
{31u, 6u, 0x00000000u},
|
||||
}};
|
||||
|
||||
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(data);
|
||||
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), bytes, 0x31016facu};
|
||||
}
|
||||
|
||||
std::string Hex(std::uint32_t value) {
|
||||
char text[16];
|
||||
std::snprintf(text, sizeof(text), "0x%08x", value);
|
||||
return text;
|
||||
}
|
||||
|
||||
bool IsNan(std::uint32_t value) {
|
||||
return (value & 0x7fffffffu) > 0x7f800000u;
|
||||
}
|
||||
|
||||
void Run(AgcDriver::VulkanDevice& device, const std::optional<ShaderRecompiler::ShaderFloatMode>& floatMode) {
|
||||
Input.fill(0u);
|
||||
for (std::uint32_t tid = 0; tid < Threads; ++tid) std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), &Input[tid * Inputs]);
|
||||
Output.fill(0xdeadbeefu);
|
||||
std::vector<std::uint32_t> userData(8, 0u);
|
||||
const auto input = BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size() * 4u));
|
||||
const auto output = BufferDescriptor(Output.data(), static_cast<std::uint32_t>(Output.size() * 4u));
|
||||
std::copy(input.begin(), input.end(), userData.begin());
|
||||
std::copy(output.begin(), output.end(), userData.begin() + 4);
|
||||
const std::span<const std::uint32_t> code(Code);
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
|
||||
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1};
|
||||
ShaderRecompiler::RecompileRequest request{
|
||||
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
|
||||
{32, 0, userData, compute, std::nullopt, std::nullopt, memory},
|
||||
device.Target(),
|
||||
{0, 0, 0, 128}
|
||||
};
|
||||
request.useCache = false;
|
||||
request.context.floatMode = floatMode;
|
||||
const auto result = ShaderRecompiler::Recompile(request);
|
||||
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
|
||||
device.WaitIdle();
|
||||
}
|
||||
|
||||
void Check(const char* mode) {
|
||||
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
|
||||
const std::uint32_t* out = &Output[tid * Results];
|
||||
for (std::uint32_t pair = 0; pair < 8u; ++pair) {
|
||||
const std::uint32_t constant = out[pair * 2u];
|
||||
const std::uint32_t reg = out[pair * 2u + 1u];
|
||||
const std::string where = std::string("f32 constant factor: lane ") + std::to_string(tid) + " " + mode + " " + Names[pair];
|
||||
if (IsNan(reg)) {
|
||||
Require(IsNan(constant), where + " is " + Hex(constant) + " with a constant and a NaN with a register");
|
||||
continue;
|
||||
}
|
||||
Require(constant == reg, where + " is " + Hex(constant) + " with a constant and " + Hex(reg) + " with a register");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CheckTinyProducts(const char* mode) {
|
||||
for (const Pinned& pinned : TinyProducts) {
|
||||
const std::uint32_t actual = Output[pinned.tid * Results + pinned.pair * 2u];
|
||||
Require(actual == pinned.value, std::string("f32 constant factor: lane ") + std::to_string(pinned.tid) + " " + mode + " " + Names[pinned.pair] + " is " + Hex(actual) + ", expected the flushed tiny product " + Hex(pinned.value));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
Run(*device, std::nullopt);
|
||||
Check("no float mode");
|
||||
CheckTinyProducts("no float mode");
|
||||
Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, false});
|
||||
Check("IEEE=0 f32 denormals flushed");
|
||||
CheckTinyProducts("IEEE=0 f32 denormals flushed");
|
||||
Run(*device, ShaderRecompiler::ShaderFloatMode{0x00u, false, true, false});
|
||||
Check("IEEE=1 all denormals flushed");
|
||||
CheckTinyProducts("IEEE=1 all denormals flushed");
|
||||
Run(*device, ShaderRecompiler::ShaderFloatMode{0xf0u, true, false, false});
|
||||
Check("f32 denormals kept");
|
||||
std::puts("f32 constant factor tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
#include "Translation/TranslationContext.hpp"
|
||||
#include "RdnaDecoder/RdnaVectorOpDecoder.hpp"
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace ShaderRecompiler;
|
||||
|
||||
namespace {
|
||||
|
||||
enum class Tininess {
|
||||
None,
|
||||
ExponentsOnly,
|
||||
Full,
|
||||
};
|
||||
|
||||
void Require(bool value, const std::string& message) {
|
||||
if (!value) throw std::runtime_error("f32 constant factor: " + message);
|
||||
}
|
||||
|
||||
const IrBlock& Translate(IrProgram& program, const std::string& name, const std::vector<std::uint32_t>& words, RdnaOpcode opcode, std::uint32_t floatMode) {
|
||||
const RdnaInstruction instruction = DecodeRdnaVectorOp(std::span<const std::uint32_t>(words), 0u);
|
||||
Require(instruction.op == opcode, name + " decodes to another opcode");
|
||||
Require(instruction.wordCount == words.size(), name + " has the wrong length");
|
||||
auto& block = program.CreateBlock();
|
||||
program.SetEntryBlock(block);
|
||||
TranslationContext context(program, block, 256);
|
||||
context.SetFloatMode(ShaderFloatMode{floatMode, true, false, false});
|
||||
context.TranslateInstruction(instruction);
|
||||
return block;
|
||||
}
|
||||
|
||||
void Check(const std::string& name, const std::vector<std::uint32_t>& words, RdnaOpcode opcode, Tininess expected, std::uint32_t floatMode = 0xc0u) {
|
||||
IrProgram program;
|
||||
const auto& block = Translate(program, name, words, opcode, floatMode);
|
||||
std::uint32_t multiplyHigh = 0u;
|
||||
std::uint32_t tinyCompares = 0u;
|
||||
for (const auto* value : block.Instructions()) {
|
||||
if (value->Opcode() == IrOpcode::UMulHi) ++multiplyHigh;
|
||||
if (value->Opcode() == IrOpcode::ULessThan32 && value->Argument(1)->HasImmediate() && value->Argument(1)->ImmediateU32() == 128u) ++tinyCompares;
|
||||
}
|
||||
const std::uint32_t expectedCompares = expected == Tininess::None ? 0u : 1u;
|
||||
const std::uint32_t expectedMultiplyHigh = expected == Tininess::Full ? 1u : 0u;
|
||||
Require(tinyCompares == expectedCompares, name + " emits " + std::to_string(tinyCompares) + " tiny product checks, expected " + std::to_string(expectedCompares));
|
||||
Require(multiplyHigh == expectedMultiplyHigh, name + " emits " + std::to_string(multiplyHigh) + " significand products, expected " + std::to_string(expectedMultiplyHigh));
|
||||
}
|
||||
|
||||
void CheckFoldedSource(const std::string& name, const std::vector<std::uint32_t>& words, RdnaOpcode opcode, IrOpcode product, std::uint32_t index, std::uint32_t expected) {
|
||||
IrProgram program;
|
||||
const auto& block = Translate(program, name, words, opcode, 0xc0u);
|
||||
for (const auto* value : block.Instructions()) {
|
||||
if (value->Opcode() != product) continue;
|
||||
const IrValue* source = value->Argument(index);
|
||||
Require(source->Opcode() == IrOpcode::BitCastF32U32 && source->Argument(0)->HasImmediate(), name + " source " + std::to_string(index) + " is not a constant");
|
||||
Require(source->Argument(0)->ImmediateU32() == expected, name + " source " + std::to_string(index) + " is folded to the wrong constant");
|
||||
return;
|
||||
}
|
||||
Require(false, name + " emits no product");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
Check("v_mul_f32 v5, v6, v7", {0x100a0f06u}, RdnaOpcode::VMulF32, Tininess::Full);
|
||||
Check("v_mul_f32 v5, 2.0, v7", {0x100a0ef4u}, RdnaOpcode::VMulF32, Tininess::None);
|
||||
Check("v_mul_f32 v5, -4.0, v7", {0x100a0ef7u}, RdnaOpcode::VMulF32, Tininess::None);
|
||||
Check("v_mul_f32 v5, 1.0, v7", {0x100a0ef2u}, RdnaOpcode::VMulF32, Tininess::None);
|
||||
Check("v_mul_f32 v5, 0x7f800000, v7", {0x100a0effu, 0x7f800000u}, RdnaOpcode::VMulF32, Tininess::None);
|
||||
Check("v_mul_f32 v5, 0x00400000, v7", {0x100a0effu, 0x00400000u}, RdnaOpcode::VMulF32, Tininess::None);
|
||||
Check("v_mul_f32 v5, 1, v7", {0x100a0e81u}, RdnaOpcode::VMulF32, Tininess::None);
|
||||
Check("v_mul_f32 v5, 0.5, v7", {0x100a0ef0u}, RdnaOpcode::VMulF32, Tininess::ExponentsOnly);
|
||||
Check("v_mul_f32 v5, 0x3e800000, v7", {0x100a0effu, 0x3e800000u}, RdnaOpcode::VMulF32, Tininess::ExponentsOnly);
|
||||
Check("v_mul_f32 v5, 0x3f7fffff, v7", {0x100a0effu, 0x3f7fffffu}, RdnaOpcode::VMulF32, Tininess::Full);
|
||||
Check("v_mul_f32 v5, 0.15915494, v7", {0x100a0ef8u}, RdnaOpcode::VMulF32, Tininess::Full);
|
||||
Check("v_mul_legacy_f32 v5, 4.0, v7", {0x0e0a0ef6u}, RdnaOpcode::VMulLegacyF32, Tininess::None);
|
||||
Check("v_fmamk_f32 v5, v6, 0x40400000, v7", {0x580a0f06u, 0x40400000u}, RdnaOpcode::VMadmkF32, Tininess::None);
|
||||
Check("v_fmaak_f32 v5, v6, v7, 0x40400000", {0x5a0a0f06u, 0x40400000u}, RdnaOpcode::VMadakF32, Tininess::Full);
|
||||
Check("v_fma_f32 v5, v6, -|2.0|, v7", {0xd54b0205u, 0x441de906u}, RdnaOpcode::VFmaF32, Tininess::None);
|
||||
Check("v_fma_f32 v5, v6, -|0.5|, v7", {0xd54b0205u, 0x441de106u}, RdnaOpcode::VFmaF32, Tininess::ExponentsOnly);
|
||||
Check("v_mul_f32 v5, 2.0, v7 with f32 denormals kept", {0x100a0ef4u}, RdnaOpcode::VMulF32, Tininess::None, 0xf0u);
|
||||
Check("v_mul_f32 v5, v6, v7 with f32 denormals kept", {0x100a0f06u}, RdnaOpcode::VMulF32, Tininess::None, 0xf0u);
|
||||
CheckFoldedSource("v_fma_f32 v5, v6, -|2.0|, v7", {0xd54b0205u, 0x441de906u}, RdnaOpcode::VFmaF32, IrOpcode::FPFma32, 1u, 0xc0000000u);
|
||||
CheckFoldedSource("v_mul_f32 v5, 0x00400000, v7", {0x100a0effu, 0x00400000u}, RdnaOpcode::VMulF32, IrOpcode::FPMul32, 0u, 0x00000000u);
|
||||
CheckFoldedSource("v_mul_f32 v5, 0x80400000, v7", {0x100a0effu, 0x80400000u}, RdnaOpcode::VMulF32, IrOpcode::FPMul32, 0u, 0x80000000u);
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,24 @@ DppMoveFlags dppFlags(const RdnaOperand& operand) {
|
|||
return {control, static_cast<std::uint8_t>(operand.dppRowMask), static_cast<std::uint8_t>(operand.dppBankMask), operand.dppFetchInactive, operand.dppBoundCtrl};
|
||||
}
|
||||
|
||||
const IrValue* constantF32Bits(const IrValue& value) {
|
||||
if (value.Opcode() != IrOpcode::BitCastF32U32) return nullptr;
|
||||
const IrValue* bits = value.Argument(0);
|
||||
return bits->HasImmediate() ? bits : nullptr;
|
||||
}
|
||||
|
||||
bool keepsProductOutOfTinyRange(const IrValue& factor) {
|
||||
const IrValue* bits = constantF32Bits(factor);
|
||||
if (bits == nullptr) return false;
|
||||
const std::uint32_t exponent = (bits->ImmediateU32() >> 23u) & 0xffu;
|
||||
return exponent == 0u || exponent >= 127u;
|
||||
}
|
||||
|
||||
bool hasUnitSignificand(const IrValue& factor) {
|
||||
const IrValue* bits = constantF32Bits(factor);
|
||||
return bits != nullptr && (bits->ImmediateU32() & 0x007fffffu) == 0u;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const RdnaOperand& TranslationContext::sourceAt(const RdnaInstruction& inst, std::uint32_t index) {
|
||||
|
|
@ -127,12 +145,16 @@ RdnaOperand TranslationContext::plainOperand(const RdnaOperand& operand) {
|
|||
|
||||
IrU32 TranslationContext::flushF32Denormal(IrU32 bits) {
|
||||
if ((f32DenormalFlush & 1u) == 0u) return bits;
|
||||
if (bits.Value().HasImmediate()) {
|
||||
const std::uint32_t value = bits.Value().ImmediateU32();
|
||||
return (value & 0x7f800000u) == 0u ? IrU32(ir.Constant(value & 0x80000000u)) : bits;
|
||||
}
|
||||
const IrU1 denormal(ir.IEqual(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7f800000u)), ir.Constant(0u)));
|
||||
return IrU32(ir.Select(denormal.Value(), ir.BitwiseAnd(bits.Value(), ir.Constant(0x80000000u)), bits.Value()));
|
||||
}
|
||||
|
||||
IrF32 TranslationContext::flushTinyProduct(IrValue* lhs, IrValue* rhs, IrValue* product, IrValue* addend) {
|
||||
if ((f32DenormalFlush & 2u) == 0u) return IrF32(*product);
|
||||
if ((f32DenormalFlush & 2u) == 0u || keepsProductOutOfTinyRange(*lhs) || keepsProductOutOfTinyRange(*rhs)) return IrF32(*product);
|
||||
IrValue& lhsBits = ir.BitCastU32(*lhs);
|
||||
IrValue& rhsBits = ir.BitCastU32(*rhs);
|
||||
const auto exponent = [&](IrValue& bits) -> IrValue& { return ir.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {&bits, &ir.Constant(23u), &ir.Constant(8u)}); };
|
||||
|
|
@ -140,7 +162,7 @@ IrF32 TranslationContext::flushTinyProduct(IrValue* lhs, IrValue* rhs, IrValue*
|
|||
IrValue& lhsExponent = exponent(lhsBits);
|
||||
IrValue& rhsExponent = exponent(rhsBits);
|
||||
const auto finiteNonZero = [&](IrValue& value) -> IrValue& { return ir.LogicalAnd(ir.INotEqual(value, ir.Constant(0u)), ir.INotEqual(value, ir.Constant(0xffu))); };
|
||||
IrValue& carry = ir.Select(ir.UGreaterThan(ir.Emit(IrOpcode::UMulHi, IrType::U32, {&significand(lhsBits), &significand(rhsBits)}), ir.Constant(0x7fffu)), ir.Constant(1u), ir.Constant(0u));
|
||||
IrValue& carry = hasUnitSignificand(*lhs) || hasUnitSignificand(*rhs) ? ir.Constant(0u) : ir.Select(ir.UGreaterThan(ir.Emit(IrOpcode::UMulHi, IrType::U32, {&significand(lhsBits), &significand(rhsBits)}), ir.Constant(0x7fffu)), ir.Constant(1u), ir.Constant(0u));
|
||||
IrValue* tiny = &ir.LogicalAnd(ir.LogicalAnd(finiteNonZero(lhsExponent), finiteNonZero(rhsExponent)), ir.ULessThan(ir.IAdd(ir.IAdd(lhsExponent, rhsExponent), carry), ir.Constant(128u)));
|
||||
if (addend != nullptr) tiny = &ir.LogicalAnd(*tiny, ir.IEqual(ir.BitwiseAnd(ir.BitCastU32(*addend), ir.Constant(0x7fffffffu)), ir.Constant(0u)));
|
||||
IrValue& sign = ir.BitwiseAnd(ir.BitwiseXor(lhsBits, rhsBits), ir.Constant(0x80000000u));
|
||||
|
|
@ -173,11 +195,17 @@ IrValue* TranslationContext::readOperand(const RdnaOperand& operand, IrType type
|
|||
return &ir.ConstructU64(pair[0].Value(), pair[1].Value());
|
||||
}
|
||||
IrU32 bits = applyBitSourceModifiers(operand, readRawU32(operand));
|
||||
if (operand.absolute) {
|
||||
bits = IrU32(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffffffu)));
|
||||
}
|
||||
if (operand.negate) {
|
||||
bits = IrU32(ir.BitwiseXor(bits.Value(), ir.Constant(0x80000000u)));
|
||||
if (bits.Value().HasImmediate()) {
|
||||
const std::uint32_t value = bits.Value().ImmediateU32();
|
||||
const std::uint32_t magnitude = operand.absolute ? value & 0x7fffffffu : value;
|
||||
bits = IrU32(ir.Constant(operand.negate ? magnitude ^ 0x80000000u : magnitude));
|
||||
} else {
|
||||
if (operand.absolute) {
|
||||
bits = IrU32(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffffffu)));
|
||||
}
|
||||
if (operand.negate) {
|
||||
bits = IrU32(ir.BitwiseXor(bits.Value(), ir.Constant(0x80000000u)));
|
||||
}
|
||||
}
|
||||
if (TypesOverlap(type, IrType::F32) && !TypesOverlap(type, IrType::U32)) {
|
||||
return &ir.BitCastF32(flushF32Denormal(bits).Value());
|
||||
|
|
|
|||
Loading…
Reference in New Issue