using System;
using System.Diagnostics;
namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
{
static class InstEmitVfpConvert
{
public static void Vcvta(CodeGenContext context, uint rd, uint rm, bool op, uint size)
{
if (size == 3)
{
// F64 -> S32/U32 conversion on SIMD is not supported, so we convert it to a GPR, then insert it back into the SIMD register.
if (op)
{
InstEmitNeonCommon.EmitScalarUnaryToGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.FcvtasFloat);
}
else
{
InstEmitNeonCommon.EmitScalarUnaryToGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.FcvtauFloat);
}
}
else if (op)
{
InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FcvtasS, context.Arm64Assembler.FcvtasSH);
}
else
{
InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FcvtauS, context.Arm64Assembler.FcvtauSH);
}
}
public static void Vcvtb(CodeGenContext context, uint rd, uint rm, uint sz, uint op)
{
EmitVcvtbVcvtt(context, rd, rm, sz, op, top: false);
}
public static void Vcvtm(CodeGenContext context, uint rd, uint rm, bool op, uint size)
{
if (size == 3)
{
// F64 -> S32/U32 conversion on SIMD is not supported, so we convert it to a GPR, then insert it back into the SIMD register.
if (op)
{
InstEmitNeonCommon.EmitScalarUnaryToGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.FcvtmsFloat);
}
else
{
InstEmitNeonCommon.EmitScalarUnaryToGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.FcvtmuFloat);
}
}
else if (op)
{
InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FcvtmsS, context.Arm64Assembler.FcvtmsSH);
}
else
{
InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FcvtmuS, context.Arm64Assembler.FcvtmuSH);
}
}
public static void Vcvtn(CodeGenContext context, uint rd, uint rm, bool op, uint size)
{
if (size == 3)
{
// F64 -> S32/U32 conversion on SIMD is not supported, so we convert it to a GPR, then insert it back into the SIMD register.
if (op)
{
InstEmitNeonCommon.EmitScalarUnaryToGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.FcvtnsFloat);
}
else
{
InstEmitNeonCommon.EmitScalarUnaryToGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.FcvtnuFloat);
}
}
else if (op)
{
InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FcvtnsS, context.Arm64Assembler.FcvtnsSH);
}
else
{
InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FcvtnuS, context.Arm64Assembler.FcvtnuSH);
}
}
public static void Vcvtp(CodeGenContext context, uint rd, uint rm, bool op, uint size)
{
if (size == 3)
{
// F64 -> S32/U32 conversion on SIMD is not supported, so we convert it to a GPR, then insert it back into the SIMD register.
if (op)
{
InstEmitNeonCommon.EmitScalarUnaryToGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.FcvtpsFloat);
}
else
{
InstEmitNeonCommon.EmitScalarUnaryToGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.FcvtpuFloat);
}
}
else if (op)
{
InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FcvtpsS, context.Arm64Assembler.FcvtpsSH);
}
else
{
InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FcvtpuS, context.Arm64Assembler.FcvtpuSH);
}
}
public static void VcvtDs(CodeGenContext context, uint rd, uint rm, uint size)
{
bool doubleToSingle = size == 3;
using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped();
if (doubleToSingle)
{
// Double to single.
using ScopedRegister rmReg = InstEmitNeonCommon.MoveScalarToSide(context, rm, false);
context.Arm64Assembler.FcvtFloat(tempRegister.Operand, rmReg.Operand, 0, 1);
InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rd, true);
}
else
{
// Single to double.
using ScopedRegister rmReg = InstEmitNeonCommon.MoveScalarToSide(context, rm, true);
context.Arm64Assembler.FcvtFloat(tempRegister.Operand, rmReg.Operand, 1, 0);
InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rd, false);
}
}
public static void VcvtIv(CodeGenContext context, uint rd, uint rm, bool unsigned, uint size)
{
if (size == 3)
{
// F64 -> S32/U32 conversion on SIMD is not supported, so we convert it to a GPR, then insert it back into the SIMD register.
if (unsigned)
{
InstEmitNeonCommon.EmitScalarUnaryToGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.FcvtzuFloatInt);
}
else
{
InstEmitNeonCommon.EmitScalarUnaryToGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.FcvtzsFloatInt);
}
}
else
{
if (unsigned)
{
InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FcvtzuIntS, context.Arm64Assembler.FcvtzuIntSH);
}
else
{
InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FcvtzsIntS, context.Arm64Assembler.FcvtzsIntSH);
}
}
}
public static void VcvtVi(CodeGenContext context, uint rd, uint rm, bool unsigned, uint size)
{
if (size == 3)
{
// S32/U32 -> F64 conversion on SIMD is not supported, so we convert it to a GPR, then insert it back into the SIMD register.
if (unsigned)
{
InstEmitNeonCommon.EmitScalarUnaryFromGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.UcvtfFloatInt);
}
else
{
InstEmitNeonCommon.EmitScalarUnaryFromGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.ScvtfFloatInt);
}
}
else
{
if (unsigned)
{
InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.UcvtfIntS, context.Arm64Assembler.UcvtfIntSH);
}
else
{
InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.ScvtfIntS, context.Arm64Assembler.ScvtfIntSH);
}
}
}
public static void VcvtXv(CodeGenContext context, uint rd, uint imm5, bool sx, uint sf, uint op, bool u)
{
Debug.Assert(op >> 1 == 0);
bool unsigned = u;
bool toFixed = op == 1;
uint size = sf;
uint fbits = Math.Clamp((sx ? 32u : 16u) - imm5, 1, 8u << (int)size);
if (toFixed)
{
if (unsigned)
{
InstEmitNeonCommon.EmitScalarUnaryFixedF(context, rd, rd, fbits, size, is16Bit: false, context.Arm64Assembler.FcvtzuFixS);
}
else
{
InstEmitNeonCommon.EmitScalarUnaryFixedF(context, rd, rd, fbits, size, is16Bit: false, context.Arm64Assembler.FcvtzsFixS);
}
}
else
{
if (unsigned)
{
InstEmitNeonCommon.EmitScalarUnaryFixedF(context, rd, rd, fbits, size, is16Bit: !sx, context.Arm64Assembler.UcvtfFixS);
}
else
{
InstEmitNeonCommon.EmitScalarUnaryFixedF(context, rd, rd, fbits, size, is16Bit: !sx, context.Arm64Assembler.ScvtfFixS);
}
}
}
public static void VcvtrIv(CodeGenContext context, uint rd, uint rm, uint op, uint size)
{
bool unsigned = (op & 1) == 0;
Debug.Assert(size == 1 || size == 2 || size == 3);
bool singleRegs = size != 3;
using ScopedRegister rmReg = InstEmitNeonCommon.MoveScalarToSide(context, rm, singleRegs);
using ScopedRegister tempRegister = InstEmitNeonCommon.PickSimdRegister(context.RegisterAllocator, rmReg);
// Round using the FPCR rounding mode first, since the FCVTZ instructions will use the round to zero mode.
context.Arm64Assembler.FrintiFloat(tempRegister.Operand, rmReg.Operand, size ^ 2u);
if (unsigned)
{
if (size == 1)
{
context.Arm64Assembler.FcvtzuIntSH(tempRegister.Operand, tempRegister.Operand);
}
else
{
context.Arm64Assembler.FcvtzuIntS(tempRegister.Operand, tempRegister.Operand, size & 1);
}
}
else
{
if (size == 1)
{
context.Arm64Assembler.FcvtzsIntSH(tempRegister.Operand, tempRegister.Operand);
}
else
{
context.Arm64Assembler.FcvtzsIntS(tempRegister.Operand, tempRegister.Operand, size & 1);
}
}
InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rd, singleRegs);
}
public static void Vcvtt(CodeGenContext context, uint rd, uint rm, uint sz, uint op)
{
EmitVcvtbVcvtt(context, rd, rm, sz, op, top: true);
}
public static void EmitVcvtbVcvtt(CodeGenContext context, uint rd, uint rm, uint sz, uint op, bool top)
{
bool usesDouble = sz == 1;
bool convertFromHalf = op == 0;
using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped();
if (convertFromHalf)
{
// Half to single/double.
using ScopedRegister rmReg = InstEmitNeonCommon.Move16BitScalarToSide(context, rm, top);
context.Arm64Assembler.FcvtFloat(tempRegister.Operand, rmReg.Operand, usesDouble ? 1u : 0u, 3u);
InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rd, !usesDouble);
}
else
{
// Single/double to half.
using ScopedRegister rmReg = InstEmitNeonCommon.MoveScalarToSide(context, rm, !usesDouble);
context.Arm64Assembler.FcvtFloat(tempRegister.Operand, rmReg.Operand, 3u, usesDouble ? 1u : 0u);
InstEmitNeonCommon.Insert16BitResult(context, tempRegister.Operand, rd, top);
}
}
}
}