Merge pull request #365 from DotDebian/feat/vopc-f64-compares

feat(shader): implement the VOPC f64 compares
This commit is contained in:
Alexey 2026-10-03 11:01:18 +03:00 committed by GitHub
commit b4b261a806
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 577 additions and 1 deletions

View File

@ -0,0 +1,217 @@
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#include <array>
#include <bit>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <limits>
#include <string>
#include <vector>
namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t Threads = 32;
constexpr std::uint32_t Inputs = 8;
constexpr std::uint32_t Results = 4;
constexpr std::uint32_t Compares = 41;
constexpr std::uint32_t RandomRounds = 4;
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, 271> CompareCode{
0x34020083, 0x34060082, 0xe0302000, 0x80000401, 0xe0302004, 0x80000501, 0xe0302008, 0x80000601,
0xe030200c, 0x80000701, 0xe0302010, 0x80000801, 0xe0302014, 0x80000901, 0xe0302018, 0x80000e01,
0xe030201c, 0x80000f01, 0x7e140280, 0x7e160280, 0xbf8c3f70, 0x7c400d04, 0xd5010014, 0x01a90280,
0xd76f000a, 0x04290114, 0x7c420d04, 0xd5010014, 0x01a90280, 0xd76f000a, 0x04290314, 0x7c440d04,
0xd5010014, 0x01a90280, 0xd76f000a, 0x04290514, 0x7c460d04, 0xd5010014, 0x01a90280, 0xd76f000a,
0x04290714, 0x7c480d04, 0xd5010014, 0x01a90280, 0xd76f000a, 0x04290914, 0x7c4a0d04, 0xd5010014,
0x01a90280, 0xd76f000a, 0x04290b14, 0x7c4c0d04, 0xd5010014, 0x01a90280, 0xd76f000a, 0x04290d14,
0x7c4e0d04, 0xd5010014, 0x01a90280, 0xd76f000a, 0x04290f14, 0x7c500d04, 0xd5010014, 0x01a90280,
0xd76f000a, 0x04291114, 0x7c520d04, 0xd5010014, 0x01a90280, 0xd76f000a, 0x04291314, 0x7c540d04,
0xd5010014, 0x01a90280, 0xd76f000a, 0x04291514, 0x7c560d04, 0xd5010014, 0x01a90280, 0xd76f000a,
0x04291714, 0x7c580d04, 0xd5010014, 0x01a90280, 0xd76f000a, 0x04291914, 0x7c5a0d04, 0xd5010014,
0x01a90280, 0xd76f000a, 0x04291b14, 0x7c5c0d04, 0xd5010014, 0x01a90280, 0xd76f000a, 0x04291d14,
0x7c5e0d04, 0xd5010014, 0x01a90280, 0xd76f000a, 0x04291f14, 0x7d501104, 0xd5010014, 0x01a90280,
0xd76f000a, 0x04292114, 0xbe9e037e, 0x7e280280, 0x7c600d04, 0x7e280281, 0xbefe031e, 0xd76f000a,
0x04292314, 0xbe9e037e, 0x7e280280, 0x7c620d04, 0x7e280281, 0xbefe031e, 0xd76f000a, 0x04292514,
0xbe9e037e, 0x7e280280, 0x7c640d04, 0x7e280281, 0xbefe031e, 0xd76f000a, 0x04292714, 0xbe9e037e,
0x7e280280, 0x7c660d04, 0x7e280281, 0xbefe031e, 0xd76f000a, 0x04292914, 0xbe9e037e, 0x7e280280,
0x7c680d04, 0x7e280281, 0xbefe031e, 0xd76f000a, 0x04292b14, 0xbe9e037e, 0x7e280280, 0x7c6a0d04,
0x7e280281, 0xbefe031e, 0xd76f000a, 0x04292d14, 0xbe9e037e, 0x7e280280, 0x7c6c0d04, 0x7e280281,
0xbefe031e, 0xd76f000a, 0x04292f14, 0xbe9e037e, 0x7e280280, 0x7c6e0d04, 0x7e280281, 0xbefe031e,
0xd76f000a, 0x04293114, 0xbe9e037e, 0x7e280280, 0x7c700d04, 0x7e280281, 0xbefe031e, 0xd76f000a,
0x04293314, 0xbe9e037e, 0x7e280280, 0x7c720d04, 0x7e280281, 0xbefe031e, 0xd76f000a, 0x04293514,
0xbe9e037e, 0x7e280280, 0x7c740d04, 0x7e280281, 0xbefe031e, 0xd76f000a, 0x04293714, 0xbe9e037e,
0x7e280280, 0x7c760d04, 0x7e280281, 0xbefe031e, 0xd76f000a, 0x04293914, 0xbe9e037e, 0x7e280280,
0x7c780d04, 0x7e280281, 0xbefe031e, 0xd76f000a, 0x04293b14, 0xbe9e037e, 0x7e280280, 0x7c7a0d04,
0x7e280281, 0xbefe031e, 0xd76f000a, 0x04293d14, 0xbe9e037e, 0x7e280280, 0x7c7c0d04, 0x7e280281,
0xbefe031e, 0xd76f000a, 0x04293f14, 0xbe9e037e, 0x7e280280, 0x7c7e0d04, 0x7e280281, 0xbefe031e,
0xd76f000b, 0x042d0114, 0xbe9e037e, 0x7e280280, 0x7d701104, 0x7e280281, 0xbefe031e, 0xd76f000b,
0x042d0314, 0x7c4208ff, 0x40080000, 0xd5010014, 0x01a90280, 0xd76f000b, 0x042d0514, 0x7c4c08f2,
0xd5010014, 0x01a90280, 0xd76f000b, 0x042d0714, 0x7c4408f8, 0xd5010014, 0x01a90280, 0xd76f000b,
0x042d0914, 0x7c420881, 0xd5010014, 0x01a90280, 0xd76f000b, 0x042d0b14, 0xd421016a, 0x00020d04,
0xd5010014, 0x01a90280, 0xd76f000b, 0x042d0d14, 0xd4a8016a, 0x00021104, 0xd5010014, 0x01a90280,
0xd76f000b, 0x042d0f14, 0xbe9e037e, 0x7e280280, 0xd43b037e, 0x00020d04, 0x7e280281, 0xbefe031e,
0xd76f000b, 0x042d1114, 0xe0702000, 0x80010a03, 0xe0702004, 0x80010b03, 0xbf810000,
};
constexpr std::array<std::uint64_t, 20> Edges{
0x7ff8000000000000ull, 0x7ff0000000000001ull, 0xfff8000000000001ull, 0x7ff0000100000000ull, 0x7ff0000000000000ull,
0xfff0000000000000ull, 0x0000000000000000ull, 0x8000000000000000ull, 0x0000000000000001ull, 0x0000000000000002ull,
0x800fffffffffffffull, 0x0010000000000000ull, 0x3ff0000000000000ull, 0x3ff0000000000001ull, 0xbff0000000000000ull,
0x4008000000000000ull, 0xc008000000000000ull, 0x3fc45f306dc9c882ull, 0x3fc45f3060000000ull, 0x7fefffffffffffffull,
};
constexpr std::array<std::uint32_t, 16> Masks{
0x001u, 0x002u, 0x004u, 0x008u, 0x010u, 0x020u, 0x040u, 0x080u, 0x100u, 0x200u, 0x3ffu, 0x000u, 0x155u, 0x2aau, 0xfffffc00u, 0x207u,
};
constexpr std::uint32_t EdgeRounds = (Edges.size() * Edges.size() + Threads - 1u) / Threads;
void FillInput(std::uint32_t round) {
std::uint64_t state = 0x853c49e6748fea9bull + round;
const auto next = [&] {
state = state * 6364136223846793005ull + 1442695040888963407ull;
const auto high = static_cast<std::uint32_t>(state >> 32u);
state = state * 6364136223846793005ull + 1442695040888963407ull;
return (static_cast<std::uint64_t>(high) << 32u) | static_cast<std::uint32_t>(state >> 32u);
};
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
std::uint64_t a = 0u;
std::uint64_t b = 0u;
std::uint32_t mask = 0u;
if (round < EdgeRounds) {
const std::uint32_t pair = round * Threads + tid;
const std::uint32_t first = pair % Edges.size();
const std::uint32_t second = (pair / Edges.size()) % Edges.size();
a = Edges[first];
b = Edges[second];
mask = Masks[(first + second) % Masks.size()];
} else {
a = next();
b = tid % 5u == 0u ? a : (tid % 7u == 0u ? a ^ 0x8000000000000000ull : next());
mask = static_cast<std::uint32_t>(next());
}
auto* words = &Input[tid * Inputs];
words[0] = static_cast<std::uint32_t>(a);
words[1] = static_cast<std::uint32_t>(a >> 32u);
words[2] = static_cast<std::uint32_t>(b);
words[3] = static_cast<std::uint32_t>(b >> 32u);
words[4] = mask;
}
}
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t count) {
const auto address = reinterpret_cast<std::uintptr_t>(data);
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu) | (4u << 16u), count, 0x01016facu};
}
void Run(AgcDriver::VulkanDevice& device) {
Output.fill(0xdeadbeefu);
std::vector<std::uint32_t> userData(8, 0u);
const auto input = BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size()));
const auto output = BufferDescriptor(Output.data(), static_cast<std::uint32_t>(Output.size()));
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(CompareCode);
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}, 0, {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;
const auto result = ShaderRecompiler::Recompile(request);
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
device.WaitIdle();
}
std::uint32_t ClassBit(std::uint64_t bits) {
const bool negative = (bits >> 63u) != 0u;
switch (std::fpclassify(std::bit_cast<double>(bits))) {
case FP_NAN: return (bits & 0x0008000000000000ull) != 0u ? 1u : 0u;
case FP_INFINITE: return negative ? 2u : 9u;
case FP_NORMAL: return negative ? 3u : 8u;
case FP_SUBNORMAL: return negative ? 4u : 7u;
default: return negative ? 5u : 6u;
}
}
bool ClassMatch(std::uint64_t bits, std::uint32_t mask) {
return ((mask >> ClassBit(bits)) & 1u) != 0u;
}
void Check(std::uint32_t round) {
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
const auto* in = &Input[tid * Inputs];
const std::uint64_t abits = in[0] | (static_cast<std::uint64_t>(in[1]) << 32u);
const std::uint64_t bbits = in[2] | (static_cast<std::uint64_t>(in[3]) << 32u);
const std::uint32_t mask = in[4];
const double a = std::bit_cast<double>(abits);
const double b = std::bit_cast<double>(bbits);
const bool unordered = std::isnan(a) || std::isnan(b);
const std::array<bool, 17> base{
false,
a < b,
a == b,
a <= b,
a > b,
a < b || a > b,
a >= b,
!unordered,
unordered,
!(a >= b),
!(a < b || a > b),
!(a > b),
!(a <= b),
!(a == b),
!(a < b),
true,
ClassMatch(abits, mask),
};
std::array<bool, Compares> expected{};
for (std::uint32_t k = 0; k < base.size(); ++k) {
expected[k] = base[k];
expected[base.size() + k] = base[k];
}
expected[34] = 3.0 < a;
expected[35] = 1.0 >= a;
expected[36] = std::bit_cast<double>(0x3fc45f306dc9c882ull) == a;
expected[37] = std::numeric_limits<double>::denorm_min() < a;
expected[38] = std::fabs(a) < b;
expected[39] = ClassMatch(abits & 0x7fffffffffffffffull, mask);
expected[40] = !(std::fabs(a) > std::fabs(b));
for (std::uint32_t k = 0; k < expected.size(); ++k) {
const bool actual = ((Output[tid * Results + k / 32u] >> (k % 32u)) & 1u) != 0u;
Require(actual == expected[k], "vopc f64 compares: round " + std::to_string(round) + " thread " + std::to_string(tid) + " compare " + std::to_string(k) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[k]));
}
}
}
}
int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
for (std::uint32_t round = 0; round < EdgeRounds + RandomRounds; ++round) {
FillInput(round);
Run(*device);
Check(round);
}
std::puts("vopc f64 compare tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}

View File

@ -580,6 +580,40 @@ enum class RdnaOpcode : std::uint16_t {
VCmpxNlgF16,
VCmpxNleF16,
VCmpxTruF16,
VCmpFF64,
VCmpLtF64,
VCmpEqF64,
VCmpLeF64,
VCmpGtF64,
VCmpLgF64,
VCmpGeF64,
VCmpOF64,
VCmpUF64,
VCmpNgeF64,
VCmpNlgF64,
VCmpNgtF64,
VCmpNleF64,
VCmpNeqF64,
VCmpNltF64,
VCmpTruF64,
VCmpClassF64,
VCmpxFF64,
VCmpxLtF64,
VCmpxEqF64,
VCmpxLeF64,
VCmpxGtF64,
VCmpxLgF64,
VCmpxGeF64,
VCmpxOF64,
VCmpxUF64,
VCmpxNgeF64,
VCmpxNlgF64,
VCmpxNgtF64,
VCmpxNleF64,
VCmpxNeqF64,
VCmpxNltF64,
VCmpxTruF64,
VCmpxClassF64,
VCmpxLtU32,
VCmpxEqU32,
VCmpxLeU32,

View File

@ -529,6 +529,40 @@ bool IsVectorAluOpcode(RdnaOpcode opcode) {
case RdnaOpcode::VCmpxNlgF16:
case RdnaOpcode::VCmpxNleF16:
case RdnaOpcode::VCmpxTruF16:
case RdnaOpcode::VCmpFF64:
case RdnaOpcode::VCmpLtF64:
case RdnaOpcode::VCmpEqF64:
case RdnaOpcode::VCmpLeF64:
case RdnaOpcode::VCmpGtF64:
case RdnaOpcode::VCmpLgF64:
case RdnaOpcode::VCmpGeF64:
case RdnaOpcode::VCmpOF64:
case RdnaOpcode::VCmpUF64:
case RdnaOpcode::VCmpNgeF64:
case RdnaOpcode::VCmpNlgF64:
case RdnaOpcode::VCmpNgtF64:
case RdnaOpcode::VCmpNleF64:
case RdnaOpcode::VCmpNeqF64:
case RdnaOpcode::VCmpNltF64:
case RdnaOpcode::VCmpTruF64:
case RdnaOpcode::VCmpClassF64:
case RdnaOpcode::VCmpxFF64:
case RdnaOpcode::VCmpxLtF64:
case RdnaOpcode::VCmpxEqF64:
case RdnaOpcode::VCmpxLeF64:
case RdnaOpcode::VCmpxGtF64:
case RdnaOpcode::VCmpxLgF64:
case RdnaOpcode::VCmpxGeF64:
case RdnaOpcode::VCmpxOF64:
case RdnaOpcode::VCmpxUF64:
case RdnaOpcode::VCmpxNgeF64:
case RdnaOpcode::VCmpxNlgF64:
case RdnaOpcode::VCmpxNgtF64:
case RdnaOpcode::VCmpxNleF64:
case RdnaOpcode::VCmpxNeqF64:
case RdnaOpcode::VCmpxNltF64:
case RdnaOpcode::VCmpxTruF64:
case RdnaOpcode::VCmpxClassF64:
case RdnaOpcode::VCmpxNeU64:
case RdnaOpcode::VCmpxLtU32:
case RdnaOpcode::VCmpxEqU32:

View File

@ -384,6 +384,40 @@ constexpr VopcOpcodeInfo vopcOpcodes[] = {
{0xfau, RdnaOpcode::VCmpxNlgF16},
{0xfcu, RdnaOpcode::VCmpxNleF16},
{0xffu, RdnaOpcode::VCmpxTruF16},
{0x20u, RdnaOpcode::VCmpFF64, false},
{0x21u, RdnaOpcode::VCmpLtF64, false},
{0x22u, RdnaOpcode::VCmpEqF64, false},
{0x23u, RdnaOpcode::VCmpLeF64, false},
{0x24u, RdnaOpcode::VCmpGtF64, false},
{0x25u, RdnaOpcode::VCmpLgF64, false},
{0x26u, RdnaOpcode::VCmpGeF64, false},
{0x27u, RdnaOpcode::VCmpOF64, false},
{0x28u, RdnaOpcode::VCmpUF64, false},
{0x29u, RdnaOpcode::VCmpNgeF64, false},
{0x2au, RdnaOpcode::VCmpNlgF64, false},
{0x2bu, RdnaOpcode::VCmpNgtF64, false},
{0x2cu, RdnaOpcode::VCmpNleF64, false},
{0x2du, RdnaOpcode::VCmpNeqF64, false},
{0x2eu, RdnaOpcode::VCmpNltF64, false},
{0x2fu, RdnaOpcode::VCmpTruF64, false},
{0x30u, RdnaOpcode::VCmpxFF64, false},
{0x31u, RdnaOpcode::VCmpxLtF64, false},
{0x32u, RdnaOpcode::VCmpxEqF64, false},
{0x33u, RdnaOpcode::VCmpxLeF64, false},
{0x34u, RdnaOpcode::VCmpxGtF64, false},
{0x35u, RdnaOpcode::VCmpxLgF64, false},
{0x36u, RdnaOpcode::VCmpxGeF64, false},
{0x37u, RdnaOpcode::VCmpxOF64, false},
{0x38u, RdnaOpcode::VCmpxUF64, false},
{0x39u, RdnaOpcode::VCmpxNgeF64, false},
{0x3au, RdnaOpcode::VCmpxNlgF64, false},
{0x3bu, RdnaOpcode::VCmpxNgtF64, false},
{0x3cu, RdnaOpcode::VCmpxNleF64, false},
{0x3du, RdnaOpcode::VCmpxNeqF64, false},
{0x3eu, RdnaOpcode::VCmpxNltF64, false},
{0x3fu, RdnaOpcode::VCmpxTruF64, false},
{0xa8u, RdnaOpcode::VCmpClassF64, false},
{0xb8u, RdnaOpcode::VCmpxClassF64, false},
};
constexpr VectorOpcodeInfo vop3Opcodes[] = {
@ -716,7 +750,50 @@ bool isVop1FloatResultOpcode(RdnaOpcode opcode) {
}
}
bool isVopcFloat64CompareOpcode(RdnaOpcode opcode) {
switch (opcode) {
case RdnaOpcode::VCmpFF64:
case RdnaOpcode::VCmpLtF64:
case RdnaOpcode::VCmpEqF64:
case RdnaOpcode::VCmpLeF64:
case RdnaOpcode::VCmpGtF64:
case RdnaOpcode::VCmpLgF64:
case RdnaOpcode::VCmpGeF64:
case RdnaOpcode::VCmpOF64:
case RdnaOpcode::VCmpUF64:
case RdnaOpcode::VCmpNgeF64:
case RdnaOpcode::VCmpNlgF64:
case RdnaOpcode::VCmpNgtF64:
case RdnaOpcode::VCmpNleF64:
case RdnaOpcode::VCmpNeqF64:
case RdnaOpcode::VCmpNltF64:
case RdnaOpcode::VCmpTruF64:
case RdnaOpcode::VCmpClassF64:
case RdnaOpcode::VCmpxFF64:
case RdnaOpcode::VCmpxLtF64:
case RdnaOpcode::VCmpxEqF64:
case RdnaOpcode::VCmpxLeF64:
case RdnaOpcode::VCmpxGtF64:
case RdnaOpcode::VCmpxLgF64:
case RdnaOpcode::VCmpxGeF64:
case RdnaOpcode::VCmpxOF64:
case RdnaOpcode::VCmpxUF64:
case RdnaOpcode::VCmpxNgeF64:
case RdnaOpcode::VCmpxNlgF64:
case RdnaOpcode::VCmpxNgtF64:
case RdnaOpcode::VCmpxNleF64:
case RdnaOpcode::VCmpxNeqF64:
case RdnaOpcode::VCmpxNltF64:
case RdnaOpcode::VCmpxTruF64:
case RdnaOpcode::VCmpxClassF64: return true;
default: return false;
}
}
bool isVopcFloatCompareOpcode(RdnaOpcode opcode) {
if (isVopcFloat64CompareOpcode(opcode)) {
return true;
}
switch (opcode) {
case RdnaOpcode::VCmpFF32:
case RdnaOpcode::VCmpLtF32:
@ -1323,7 +1400,24 @@ bool isVopcCompareExec(RdnaOpcode opcode) {
case RdnaOpcode::VCmpxGeF16:
case RdnaOpcode::VCmpxNgtF16:
case RdnaOpcode::VCmpxNeqF16:
case RdnaOpcode::VCmpxNltF16: return true;
case RdnaOpcode::VCmpxNltF16:
case RdnaOpcode::VCmpxFF64:
case RdnaOpcode::VCmpxLtF64:
case RdnaOpcode::VCmpxEqF64:
case RdnaOpcode::VCmpxLeF64:
case RdnaOpcode::VCmpxGtF64:
case RdnaOpcode::VCmpxLgF64:
case RdnaOpcode::VCmpxGeF64:
case RdnaOpcode::VCmpxOF64:
case RdnaOpcode::VCmpxUF64:
case RdnaOpcode::VCmpxNgeF64:
case RdnaOpcode::VCmpxNlgF64:
case RdnaOpcode::VCmpxNgtF64:
case RdnaOpcode::VCmpxNleF64:
case RdnaOpcode::VCmpxNeqF64:
case RdnaOpcode::VCmpxNltF64:
case RdnaOpcode::VCmpxTruF64:
case RdnaOpcode::VCmpxClassF64: return true;
default: return false;
}
}
@ -1774,6 +1868,9 @@ RdnaInstruction DecodeRdnaVopc(std::uint32_t programCounter, std::span<const std
if (src0 == 249u) {
instruction.op = lookupVectorOpcode(vopcOpcodes, opcode, "VOPC SDWA modifier is not supported for opcode");
if (isVopcFloat64CompareOpcode(instruction.op)) {
throw std::invalid_argument("VOPC SDWA modifier is not supported for opcode");
}
instruction.destination.kind = isVopcCompareExec(instruction.op) ? RdnaOperandKind::ExecLo : RdnaOperandKind::VccLo;
decodeVopcSdwa(programCounter, code, wordIndex, vsrc1, instruction);
return instruction;

View File

@ -51,6 +51,7 @@ private:
IrU32 readU32(const RdnaOperand& operand);
std::array<IrU32, 2> readU32Pair(const RdnaOperand& operand);
IrU64 readU64(const RdnaOperand& operand);
std::array<IrU32, 2> readF64Bits(const RdnaOperand& operand);
IrF32 readF16LaneAsF32(const RdnaOperand& operand, bool highLane, bool packed = false);
IrF32 readF16AsF32(const RdnaOperand& operand);
IrU32 readF16SourceBits(const RdnaOperand& operand);
@ -120,6 +121,10 @@ private:
void emitInteger64Order(const RdnaInstruction& inst, bool signedValue, bool swap, bool negate, bool cmpx);
void emitFloatOrderedCompare(const RdnaInstruction& inst, bool ordered, bool half, bool cmpx);
void emitFloatClassCompare(const RdnaInstruction& inst, bool cmpx);
IrU1 float64IsNan(const std::array<IrU32, 2>& bits);
IrU64 float64OrderKey(const std::array<IrU32, 2>& bits);
void emitFloat64Compare(const RdnaInstruction& inst, bool less, bool equal, bool greater, bool unordered, bool cmpx);
void emitFloat64ClassCompare(const RdnaInstruction& inst, bool cmpx);
void vCvtF32Ubyte(const RdnaInstruction& inst, std::uint32_t byteIndex);
void vCvtF32U32(const RdnaInstruction& inst);
void vCvtF32I32(const RdnaInstruction& inst);

View File

@ -79,6 +79,74 @@ void TranslationContext::emitFloatClassCompare(const RdnaInstruction& inst, bool
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");
}

View File

@ -28,6 +28,25 @@ IrU64 TranslationContext::readU64(const RdnaOperand& operand) {
return IrU64(ir.ConstructU64(pair[0].Value(), pair[1].Value()));
}
std::array<IrU32, 2> TranslationContext::readF64Bits(const RdnaOperand& operand) {
std::array<IrU32, 2> bits{IrU32(ir.Constant(0u)), IrU32(ir.Constant(operand.value))};
if (operand.kind == RdnaOperandKind::FloatInlineConstant && operand.value == 0x3e22f983u) {
bits = {IrU32(ir.Constant(0x6dc9c882u)), IrU32(ir.Constant(0x3fc45f30u))};
} else if (operand.kind != RdnaOperandKind::LiteralConstant) {
RdnaOperand source = operand;
source.absolute = false;
source.negate = false;
bits = readU32Pair(source);
}
if (operand.absolute) {
bits[1] = IrU32(ir.BitwiseAnd(bits[1].Value(), ir.Constant(0x7fffffffu)));
}
if (operand.negate) {
bits[1] = IrU32(ir.BitwiseXor(bits[1].Value(), ir.Constant(0x80000000u)));
}
return bits;
}
std::array<IrU32, 2> TranslationContext::extractU64(IrU64 value) {
return {IrU32(ir.CompositeExtract(value.Value(), 0u)), IrU32(ir.CompositeExtract(value.Value(), 1u))};
}

View File

@ -534,6 +534,108 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) {
case RdnaOpcode::VCmpxClassF32:
emitFloatClassCompare(inst, true);
return true;
case RdnaOpcode::VCmpFF64:
emitCompareConstant(inst, false, false, false);
return true;
case RdnaOpcode::VCmpLtF64:
emitFloat64Compare(inst, true, false, false, false, false);
return true;
case RdnaOpcode::VCmpEqF64:
emitFloat64Compare(inst, false, true, false, false, false);
return true;
case RdnaOpcode::VCmpLeF64:
emitFloat64Compare(inst, true, true, false, false, false);
return true;
case RdnaOpcode::VCmpGtF64:
emitFloat64Compare(inst, false, false, true, false, false);
return true;
case RdnaOpcode::VCmpLgF64:
emitFloat64Compare(inst, true, false, true, false, false);
return true;
case RdnaOpcode::VCmpGeF64:
emitFloat64Compare(inst, false, true, true, false, false);
return true;
case RdnaOpcode::VCmpOF64:
emitFloat64Compare(inst, true, true, true, false, false);
return true;
case RdnaOpcode::VCmpUF64:
emitFloat64Compare(inst, false, false, false, true, false);
return true;
case RdnaOpcode::VCmpNgeF64:
emitFloat64Compare(inst, true, false, false, true, false);
return true;
case RdnaOpcode::VCmpNlgF64:
emitFloat64Compare(inst, false, true, false, true, false);
return true;
case RdnaOpcode::VCmpNgtF64:
emitFloat64Compare(inst, true, true, false, true, false);
return true;
case RdnaOpcode::VCmpNleF64:
emitFloat64Compare(inst, false, false, true, true, false);
return true;
case RdnaOpcode::VCmpNeqF64:
emitFloat64Compare(inst, true, false, true, true, false);
return true;
case RdnaOpcode::VCmpNltF64:
emitFloat64Compare(inst, false, true, true, true, false);
return true;
case RdnaOpcode::VCmpTruF64:
emitCompareConstant(inst, true, false, false);
return true;
case RdnaOpcode::VCmpClassF64:
emitFloat64ClassCompare(inst, false);
return true;
case RdnaOpcode::VCmpxFF64:
emitCompareConstant(inst, false, false, true);
return true;
case RdnaOpcode::VCmpxLtF64:
emitFloat64Compare(inst, true, false, false, false, true);
return true;
case RdnaOpcode::VCmpxEqF64:
emitFloat64Compare(inst, false, true, false, false, true);
return true;
case RdnaOpcode::VCmpxLeF64:
emitFloat64Compare(inst, true, true, false, false, true);
return true;
case RdnaOpcode::VCmpxGtF64:
emitFloat64Compare(inst, false, false, true, false, true);
return true;
case RdnaOpcode::VCmpxLgF64:
emitFloat64Compare(inst, true, false, true, false, true);
return true;
case RdnaOpcode::VCmpxGeF64:
emitFloat64Compare(inst, false, true, true, false, true);
return true;
case RdnaOpcode::VCmpxOF64:
emitFloat64Compare(inst, true, true, true, false, true);
return true;
case RdnaOpcode::VCmpxUF64:
emitFloat64Compare(inst, false, false, false, true, true);
return true;
case RdnaOpcode::VCmpxNgeF64:
emitFloat64Compare(inst, true, false, false, true, true);
return true;
case RdnaOpcode::VCmpxNlgF64:
emitFloat64Compare(inst, false, true, false, true, true);
return true;
case RdnaOpcode::VCmpxNgtF64:
emitFloat64Compare(inst, true, true, false, true, true);
return true;
case RdnaOpcode::VCmpxNleF64:
emitFloat64Compare(inst, false, false, true, true, true);
return true;
case RdnaOpcode::VCmpxNeqF64:
emitFloat64Compare(inst, true, false, true, true, true);
return true;
case RdnaOpcode::VCmpxNltF64:
emitFloat64Compare(inst, false, true, true, true, true);
return true;
case RdnaOpcode::VCmpxTruF64:
emitCompareConstant(inst, true, false, true);
return true;
case RdnaOpcode::VCmpxClassF64:
emitFloat64ClassCompare(inst, true);
return true;
case RdnaOpcode::VCvtF32Ubyte0:
vCvtF32Ubyte(inst, 0u);
return true;