From 28e6b9363426a2d0b5f5ac39d6e1bdf258bfe003 Mon Sep 17 00:00:00 2001 From: LDj3SNuD <35856442+LDj3SNuD@users.noreply.github.com> Date: Tue, 23 Oct 2018 16:12:45 +0200 Subject: [PATCH] Fix Fcvtl_V and Fcvtn_V; fix half to float conv. and add float to half conv. (full FP emu.). Add 4 FP Tests. (#468) * Update CpuTest.cs * Update CpuTestSimd.cs * Superseded. * Update AInstEmitSimdCvt.cs * Update ASoftFloat.cs * Nit. * Update PackageReferences. * Update AInstEmitSimdArithmetic.cs * Update AVectorHelper.cs * Update ASoftFloat.cs * Update ASoftFallback.cs * Update AThreadState.cs * Create FPType.cs * Create FPExc.cs * Create FPCR.cs * Create FPSR.cs * Update ARoundMode.cs * Update APState.cs * Avoid an unwanted implicit cast of the operator >= to long, continuing to check for negative values. Remove a leftover. * Nits. --- Instruction/AInstEmitSimdArithmetic.cs | 8 - Instruction/AInstEmitSimdCmp.cs | 17 +- Instruction/AInstEmitSimdCvt.cs | 52 ++- Instruction/AInstEmitSimdHelper.cs | 10 +- Instruction/ASoftFallback.cs | 39 +- Instruction/ASoftFloat.cs | 606 +++++++++++++++++++++---- Instruction/AVectorHelper.cs | 8 +- State/APState.cs | 4 +- State/ARoundMode.cs | 4 +- State/AThreadState.cs | 17 +- State/FPCR.cs | 11 + State/FPExc.cs | 12 + State/FPSR.cs | 8 + State/FPType.cs | 11 + 14 files changed, 653 insertions(+), 154 deletions(-) create mode 100644 State/FPCR.cs create mode 100644 State/FPExc.cs create mode 100644 State/FPSR.cs create mode 100644 State/FPType.cs diff --git a/Instruction/AInstEmitSimdArithmetic.cs b/Instruction/AInstEmitSimdArithmetic.cs index 7ba08f5..5a5e50f 100644 --- a/Instruction/AInstEmitSimdArithmetic.cs +++ b/Instruction/AInstEmitSimdArithmetic.cs @@ -835,8 +835,6 @@ namespace ChocolArm64.Instruction { Context.EmitLdarg(ATranslatedSub.StateArgIdx); - Context.EmitCallPropGet(typeof(AThreadState), nameof(AThreadState.Fpcr)); - if (Op.Size == 0) { AVectorHelper.EmitCall(Context, nameof(AVectorHelper.RoundF)); @@ -862,8 +860,6 @@ namespace ChocolArm64.Instruction { Context.EmitLdarg(ATranslatedSub.StateArgIdx); - Context.EmitCallPropGet(typeof(AThreadState), nameof(AThreadState.Fpcr)); - if (SizeF == 0) { AVectorHelper.EmitCall(Context, nameof(AVectorHelper.RoundF)); @@ -938,8 +934,6 @@ namespace ChocolArm64.Instruction { Context.EmitLdarg(ATranslatedSub.StateArgIdx); - Context.EmitCallPropGet(typeof(AThreadState), nameof(AThreadState.Fpcr)); - if (Op.Size == 0) { AVectorHelper.EmitCall(Context, nameof(AVectorHelper.RoundF)); @@ -963,8 +957,6 @@ namespace ChocolArm64.Instruction { Context.EmitLdarg(ATranslatedSub.StateArgIdx); - Context.EmitCallPropGet(typeof(AThreadState), nameof(AThreadState.Fpcr)); - if (Op.Size == 0) { AVectorHelper.EmitCall(Context, nameof(AVectorHelper.RoundF)); diff --git a/Instruction/AInstEmitSimdCmp.cs b/Instruction/AInstEmitSimdCmp.cs index 97f7623..cd3480e 100644 --- a/Instruction/AInstEmitSimdCmp.cs +++ b/Instruction/AInstEmitSimdCmp.cs @@ -284,11 +284,11 @@ namespace ChocolArm64.Instruction { if (Op.Size == 0) { - Context.EmitLdc_R4(0); + Context.EmitLdc_R4(0f); } - else /* if (SizeF == 1) */ + else /* if (Op.Size == 1) */ { - Context.EmitLdc_R8(0); + Context.EmitLdc_R8(0d); } } else @@ -378,7 +378,7 @@ namespace ChocolArm64.Instruction } else { - Context.EmitLdc_I8(0); + Context.EmitLdc_I8(0L); } AILLabel LblTrue = new AILLabel(); @@ -422,7 +422,7 @@ namespace ChocolArm64.Instruction Context.Emit(OpCodes.And); - Context.EmitLdc_I8(0); + Context.EmitLdc_I8(0L); Context.Emit(OpCodes.Bne_Un_S, LblTrue); @@ -455,8 +455,9 @@ namespace ChocolArm64.Instruction int SizeF = Op.Size & 1; int Bytes = Op.GetBitsCount() >> 3; + int Elems = Bytes >> SizeF + 2; - for (int Index = 0; Index < Bytes >> SizeF + 2; Index++) + for (int Index = 0; Index < Elems; Index++) { EmitFcmp(Context, ILOp, Index, Scalar: false); } @@ -483,11 +484,11 @@ namespace ChocolArm64.Instruction } else if (SizeF == 0) { - Context.EmitLdc_R4(0); + Context.EmitLdc_R4(0f); } else /* if (SizeF == 1) */ { - Context.EmitLdc_R8(0); + Context.EmitLdc_R8(0d); } AILLabel LblTrue = new AILLabel(); diff --git a/Instruction/AInstEmitSimdCvt.cs b/Instruction/AInstEmitSimdCvt.cs index 76d984a..f277069 100644 --- a/Instruction/AInstEmitSimdCvt.cs +++ b/Instruction/AInstEmitSimdCvt.cs @@ -78,7 +78,7 @@ namespace ChocolArm64.Instruction int Elems = 4 >> SizeF; - int Part = Context.CurrOp.RegisterSize == ARegisterSize.SIMD128 ? Elems : 0; + int Part = Op.RegisterSize == ARegisterSize.SIMD128 ? Elems : 0; for (int Index = 0; Index < Elems; Index++) { @@ -87,7 +87,9 @@ namespace ChocolArm64.Instruction EmitVectorExtractZx(Context, Op.Rn, Part + Index, 1); Context.Emit(OpCodes.Conv_U2); - Context.EmitCall(typeof(ASoftFloat), nameof(ASoftFloat.ConvertHalfToSingle)); + Context.EmitLdarg(ATranslatedSub.StateArgIdx); + + Context.EmitCall(typeof(ASoftFloat16_32), nameof(ASoftFloat16_32.FPConvert)); } else /* if (SizeF == 1) */ { @@ -96,8 +98,11 @@ namespace ChocolArm64.Instruction Context.Emit(OpCodes.Conv_R8); } - EmitVectorInsertF(Context, Op.Rd, Index, SizeF); + EmitVectorInsertTmpF(Context, Index, SizeF); } + + Context.EmitLdvectmp(); + Context.EmitStvec(Op.Rd); } public static void Fcvtms_Gp(AILEmitterCtx Context) @@ -118,28 +123,39 @@ namespace ChocolArm64.Instruction int Elems = 4 >> SizeF; - int Part = Context.CurrOp.RegisterSize == ARegisterSize.SIMD128 ? Elems : 0; + int Part = Op.RegisterSize == ARegisterSize.SIMD128 ? Elems : 0; + + if (Part != 0) + { + Context.EmitLdvec(Op.Rd); + Context.EmitStvectmp(); + } for (int Index = 0; Index < Elems; Index++) { - EmitVectorExtractF(Context, Op.Rd, Index, SizeF); + EmitVectorExtractF(Context, Op.Rn, Index, SizeF); if (SizeF == 0) { - //TODO: This need the half precision floating point type, - //that is not yet supported on .NET. We should probably - //do our own implementation on the meantime. - throw new NotImplementedException(); + Context.EmitLdarg(ATranslatedSub.StateArgIdx); + + Context.EmitCall(typeof(ASoftFloat32_16), nameof(ASoftFloat32_16.FPConvert)); + + Context.Emit(OpCodes.Conv_U8); + EmitVectorInsertTmp(Context, Part + Index, 1); } else /* if (SizeF == 1) */ { Context.Emit(OpCodes.Conv_R4); - EmitVectorInsertF(Context, Op.Rd, Part + Index, 0); + EmitVectorInsertTmpF(Context, Part + Index, 0); } } - if (Op.RegisterSize == ARegisterSize.SIMD64) + Context.EmitLdvectmp(); + Context.EmitStvec(Op.Rd); + + if (Part == 0) { EmitVectorZeroUpper(Context, Op.Rd); } @@ -445,8 +461,9 @@ namespace ChocolArm64.Instruction int FBits = GetFBits(Context); int Bytes = Op.GetBitsCount() >> 3; + int Elems = Bytes >> SizeI; - for (int Index = 0; Index < (Bytes >> SizeI); Index++) + for (int Index = 0; Index < Elems; Index++) { EmitVectorExtract(Context, Op.Rn, Index, SizeI, Signed); @@ -534,8 +551,9 @@ namespace ChocolArm64.Instruction int FBits = GetFBits(Context); int Bytes = Op.GetBitsCount() >> 3; + int Elems = Bytes >> SizeI; - for (int Index = 0; Index < (Bytes >> SizeI); Index++) + for (int Index = 0; Index < Elems; Index++) { EmitVectorExtractF(Context, Op.Rn, Index, SizeF); @@ -640,11 +658,11 @@ namespace ChocolArm64.Instruction { if (Size == 0) { - Context.EmitLdc_R4(MathF.Pow(2, FBits)); + Context.EmitLdc_R4(MathF.Pow(2f, FBits)); } else if (Size == 1) { - Context.EmitLdc_R8(Math.Pow(2, FBits)); + Context.EmitLdc_R8(Math.Pow(2d, FBits)); } else { @@ -661,11 +679,11 @@ namespace ChocolArm64.Instruction { if (Size == 0) { - Context.EmitLdc_R4(1f / MathF.Pow(2, FBits)); + Context.EmitLdc_R4(1f / MathF.Pow(2f, FBits)); } else if (Size == 1) { - Context.EmitLdc_R8(1 / Math.Pow(2, FBits)); + Context.EmitLdc_R8(1d / Math.Pow(2d, FBits)); } else { diff --git a/Instruction/AInstEmitSimdHelper.cs b/Instruction/AInstEmitSimdHelper.cs index dd39f52..ff08283 100644 --- a/Instruction/AInstEmitSimdHelper.cs +++ b/Instruction/AInstEmitSimdHelper.cs @@ -1274,8 +1274,6 @@ namespace ChocolArm64.Instruction { ThrowIfInvalid(Index, Size); - IAOpCodeSimd Op = (IAOpCodeSimd)Context.CurrOp; - Context.EmitLdvec(Reg); Context.EmitLdc_I4(Index); Context.EmitLdc_I4(Size); @@ -1470,12 +1468,12 @@ namespace ChocolArm64.Instruction private static void ThrowIfInvalid(int Index, int Size) { - if ((uint)Size > 3) + if ((uint)Size > 3u) { throw new ArgumentOutOfRangeException(nameof(Size)); } - if ((uint)Index >= 16 >> Size) + if ((uint)Index >= 16u >> Size) { throw new ArgumentOutOfRangeException(nameof(Index)); } @@ -1483,12 +1481,12 @@ namespace ChocolArm64.Instruction private static void ThrowIfInvalidF(int Index, int Size) { - if ((uint)Size > 1) + if ((uint)Size > 1u) { throw new ArgumentOutOfRangeException(nameof(Size)); } - if ((uint)Index >= 4 >> Size) + if ((uint)Index >= 4u >> Size) { throw new ArgumentOutOfRangeException(nameof(Index)); } diff --git a/Instruction/ASoftFallback.cs b/Instruction/ASoftFallback.cs index 3c5c5c4..b69e2c7 100644 --- a/Instruction/ASoftFallback.cs +++ b/Instruction/ASoftFallback.cs @@ -112,13 +112,13 @@ namespace ChocolArm64.Instruction if (op > TMaxValue) { - SetFpsrQCFlag(State); + State.SetFpsrFlag(FPSR.QC); return TMaxValue; } else if (op < TMinValue) { - SetFpsrQCFlag(State); + State.SetFpsrFlag(FPSR.QC); return TMinValue; } @@ -137,13 +137,13 @@ namespace ChocolArm64.Instruction if (op > (long)TMaxValue) { - SetFpsrQCFlag(State); + State.SetFpsrFlag(FPSR.QC); return TMaxValue; } else if (op < (long)TMinValue) { - SetFpsrQCFlag(State); + State.SetFpsrFlag(FPSR.QC); return TMinValue; } @@ -161,7 +161,7 @@ namespace ChocolArm64.Instruction if (op > (ulong)TMaxValue) { - SetFpsrQCFlag(State); + State.SetFpsrFlag(FPSR.QC); return TMaxValue; } @@ -179,7 +179,7 @@ namespace ChocolArm64.Instruction if (op > TMaxValue) { - SetFpsrQCFlag(State); + State.SetFpsrFlag(FPSR.QC); return TMaxValue; } @@ -193,7 +193,7 @@ namespace ChocolArm64.Instruction { if (op == long.MinValue) { - SetFpsrQCFlag(State); + State.SetFpsrFlag(FPSR.QC); return long.MaxValue; } @@ -209,7 +209,7 @@ namespace ChocolArm64.Instruction if ((~(op1 ^ op2) & (op1 ^ Add)) < 0L) { - SetFpsrQCFlag(State); + State.SetFpsrFlag(FPSR.QC); if (op1 < 0L) { @@ -232,7 +232,7 @@ namespace ChocolArm64.Instruction if ((Add < op1) && (Add < op2)) { - SetFpsrQCFlag(State); + State.SetFpsrFlag(FPSR.QC); return ulong.MaxValue; } @@ -248,7 +248,7 @@ namespace ChocolArm64.Instruction if (((op1 ^ op2) & (op1 ^ Sub)) < 0L) { - SetFpsrQCFlag(State); + State.SetFpsrFlag(FPSR.QC); if (op1 < 0L) { @@ -271,7 +271,7 @@ namespace ChocolArm64.Instruction if (op1 < op2) { - SetFpsrQCFlag(State); + State.SetFpsrFlag(FPSR.QC); return ulong.MinValue; } @@ -292,7 +292,7 @@ namespace ChocolArm64.Instruction if ((~op2 & Add) < 0L) { - SetFpsrQCFlag(State); + State.SetFpsrFlag(FPSR.QC); return long.MaxValue; } @@ -306,7 +306,7 @@ namespace ChocolArm64.Instruction // op1 from (ulong)long.MaxValue + 1UL to ulong.MaxValue // op2 from (long)ulong.MinValue to long.MaxValue - SetFpsrQCFlag(State); + State.SetFpsrFlag(FPSR.QC); return long.MaxValue; } @@ -319,7 +319,7 @@ namespace ChocolArm64.Instruction if (Add > (ulong)long.MaxValue) { - SetFpsrQCFlag(State); + State.SetFpsrFlag(FPSR.QC); return long.MaxValue; } @@ -341,7 +341,7 @@ namespace ChocolArm64.Instruction if ((Add < (ulong)op1) && (Add < op2)) { - SetFpsrQCFlag(State); + State.SetFpsrFlag(FPSR.QC); return ulong.MaxValue; } @@ -366,7 +366,7 @@ namespace ChocolArm64.Instruction if (Add < (long)ulong.MinValue) { - SetFpsrQCFlag(State); + State.SetFpsrFlag(FPSR.QC); return ulong.MinValue; } @@ -376,13 +376,6 @@ namespace ChocolArm64.Instruction } } } - - private static void SetFpsrQCFlag(AThreadState State) - { - const int QCFlagBit = 27; - - State.Fpsr |= 1 << QCFlagBit; - } #endregion #region "Count" diff --git a/Instruction/ASoftFloat.cs b/Instruction/ASoftFloat.cs index 2d9a9f0..0912257 100644 --- a/Instruction/ASoftFloat.cs +++ b/Instruction/ASoftFloat.cs @@ -195,41 +195,535 @@ namespace ChocolArm64.Instruction ulong result = x_sign | (result_exp << 52) | fraction; return BitConverter.Int64BitsToDouble((long)result); } + } - public static float ConvertHalfToSingle(ushort x) + static class ASoftFloat16_32 + { + public static float FPConvert(ushort ValueBits, AThreadState State) { - uint x_sign = (uint)(x >> 15) & 0x0001; - uint x_exp = (uint)(x >> 10) & 0x001F; - uint x_mantissa = (uint)x & 0x03FF; + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat16_32.FPConvert: State.Fpcr = 0x{State.Fpcr:X8}"); - if (x_exp == 0 && x_mantissa == 0) + double Real = ValueBits.FPUnpackCV(out FPType Type, out bool Sign, State); + + float Result; + + if (Type == FPType.SNaN || Type == FPType.QNaN) { - // Zero - return BitConverter.Int32BitsToSingle((int)(x_sign << 31)); - } - - if (x_exp == 0x1F) - { - // NaN or Infinity - return BitConverter.Int32BitsToSingle((int)((x_sign << 31) | 0x7F800000 | (x_mantissa << 13))); - } - - int exponent = (int)x_exp - 15; - - if (x_exp == 0) - { - // Denormal - x_mantissa <<= 1; - while ((x_mantissa & 0x0400) == 0) + if (State.GetFpcrFlag(FPCR.DN)) { - x_mantissa <<= 1; - exponent--; + Result = FPDefaultNaN(); } - x_mantissa &= 0x03FF; + else + { + Result = FPConvertNaN(ValueBits); + } + + if (Type == FPType.SNaN) + { + FPProcessException(FPExc.InvalidOp, State); + } + } + else if (Type == FPType.Infinity) + { + Result = FPInfinity(Sign); + } + else if (Type == FPType.Zero) + { + Result = FPZero(Sign); + } + else + { + Result = FPRoundCV(Real, State); } - uint new_exp = (uint)((exponent + 127) & 0xFF) << 23; - return BitConverter.Int32BitsToSingle((int)((x_sign << 31) | new_exp | (x_mantissa << 13))); + return Result; + } + + private static float FPDefaultNaN() + { + return -float.NaN; + } + + private static float FPInfinity(bool Sign) + { + return Sign ? float.NegativeInfinity : float.PositiveInfinity; + } + + private static float FPZero(bool Sign) + { + return Sign ? -0f : +0f; + } + + private static float FPMaxNormal(bool Sign) + { + return Sign ? float.MinValue : float.MaxValue; + } + + private static double FPUnpackCV(this ushort ValueBits, out FPType Type, out bool Sign, AThreadState State) + { + Sign = (~(uint)ValueBits & 0x8000u) == 0u; + + uint Exp16 = ((uint)ValueBits & 0x7C00u) >> 10; + uint Frac16 = (uint)ValueBits & 0x03FFu; + + double Real; + + if (Exp16 == 0u) + { + if (Frac16 == 0u) + { + Type = FPType.Zero; + Real = 0d; + } + else + { + Type = FPType.Nonzero; // Subnormal. + Real = Math.Pow(2d, -14) * ((double)Frac16 * Math.Pow(2d, -10)); + } + } + else if (Exp16 == 0x1Fu && !State.GetFpcrFlag(FPCR.AHP)) + { + if (Frac16 == 0u) + { + Type = FPType.Infinity; + Real = Math.Pow(2d, 1000); + } + else + { + Type = (~Frac16 & 0x0200u) == 0u ? FPType.QNaN : FPType.SNaN; + Real = 0d; + } + } + else + { + Type = FPType.Nonzero; // Normal. + Real = Math.Pow(2d, (int)Exp16 - 15) * (1d + (double)Frac16 * Math.Pow(2d, -10)); + } + + return Sign ? -Real : Real; + } + + private static float FPRoundCV(double Real, AThreadState State) + { + const int MinimumExp = -126; + + const int E = 8; + const int F = 23; + + bool Sign; + double Mantissa; + + if (Real < 0d) + { + Sign = true; + Mantissa = -Real; + } + else + { + Sign = false; + Mantissa = Real; + } + + int Exponent = 0; + + while (Mantissa < 1d) + { + Mantissa *= 2d; + Exponent--; + } + + while (Mantissa >= 2d) + { + Mantissa /= 2d; + Exponent++; + } + + if (State.GetFpcrFlag(FPCR.FZ) && Exponent < MinimumExp) + { + State.SetFpsrFlag(FPSR.UFC); + + return FPZero(Sign); + } + + uint BiasedExp = (uint)Math.Max(Exponent - MinimumExp + 1, 0); + + if (BiasedExp == 0u) + { + Mantissa /= Math.Pow(2d, MinimumExp - Exponent); + } + + uint IntMant = (uint)Math.Floor(Mantissa * Math.Pow(2d, F)); + double Error = Mantissa * Math.Pow(2d, F) - (double)IntMant; + + if (BiasedExp == 0u && (Error != 0d || State.GetFpcrFlag(FPCR.UFE))) + { + FPProcessException(FPExc.Underflow, State); + } + + bool OverflowToInf; + bool RoundUp; + + switch (State.FPRoundingMode()) + { + default: + case ARoundMode.ToNearest: + RoundUp = (Error > 0.5d || (Error == 0.5d && (IntMant & 1u) == 1u)); + OverflowToInf = true; + break; + + case ARoundMode.TowardsPlusInfinity: + RoundUp = (Error != 0d && !Sign); + OverflowToInf = !Sign; + break; + + case ARoundMode.TowardsMinusInfinity: + RoundUp = (Error != 0d && Sign); + OverflowToInf = Sign; + break; + + case ARoundMode.TowardsZero: + RoundUp = false; + OverflowToInf = false; + break; + } + + if (RoundUp) + { + IntMant++; + + if (IntMant == (uint)Math.Pow(2d, F)) + { + BiasedExp = 1u; + } + + if (IntMant == (uint)Math.Pow(2d, F + 1)) + { + BiasedExp++; + IntMant >>= 1; + } + } + + float Result; + + if (BiasedExp >= (uint)Math.Pow(2d, E) - 1u) + { + Result = OverflowToInf ? FPInfinity(Sign) : FPMaxNormal(Sign); + + FPProcessException(FPExc.Overflow, State); + + Error = 1d; + } + else + { + Result = BitConverter.Int32BitsToSingle( + (int)((Sign ? 1u : 0u) << 31 | (BiasedExp & 0xFFu) << 23 | (IntMant & 0x007FFFFFu))); + } + + if (Error != 0d) + { + FPProcessException(FPExc.Inexact, State); + } + + return Result; + } + + private static float FPConvertNaN(ushort ValueBits) + { + return BitConverter.Int32BitsToSingle( + (int)(((uint)ValueBits & 0x8000u) << 16 | 0x7FC00000u | ((uint)ValueBits & 0x01FFu) << 13)); + } + + private static void FPProcessException(FPExc Exc, AThreadState State) + { + int Enable = (int)Exc + 8; + + if ((State.Fpcr & (1 << Enable)) != 0) + { + throw new NotImplementedException("floating-point trap handling"); + } + else + { + State.Fpsr |= 1 << (int)Exc; + } + } + } + + static class ASoftFloat32_16 + { + public static ushort FPConvert(float Value, AThreadState State) + { + Debug.WriteLineIf(State.Fpcr != 0, $"ASoftFloat32_16.FPConvert: State.Fpcr = 0x{State.Fpcr:X8}"); + + double Real = Value.FPUnpackCV(out FPType Type, out bool Sign, State, out uint ValueBits); + + bool AltHp = State.GetFpcrFlag(FPCR.AHP); + + ushort ResultBits; + + if (Type == FPType.SNaN || Type == FPType.QNaN) + { + if (AltHp) + { + ResultBits = FPZero(Sign); + } + else if (State.GetFpcrFlag(FPCR.DN)) + { + ResultBits = FPDefaultNaN(); + } + else + { + ResultBits = FPConvertNaN(ValueBits); + } + + if (Type == FPType.SNaN || AltHp) + { + FPProcessException(FPExc.InvalidOp, State); + } + } + else if (Type == FPType.Infinity) + { + if (AltHp) + { + ResultBits = (ushort)((Sign ? 1u : 0u) << 15 | 0x7FFFu); + + FPProcessException(FPExc.InvalidOp, State); + } + else + { + ResultBits = FPInfinity(Sign); + } + } + else if (Type == FPType.Zero) + { + ResultBits = FPZero(Sign); + } + else + { + ResultBits = FPRoundCV(Real, State); + } + + return ResultBits; + } + + private static ushort FPDefaultNaN() + { + return (ushort)0x7E00u; + } + + private static ushort FPInfinity(bool Sign) + { + return Sign ? (ushort)0xFC00u : (ushort)0x7C00u; + } + + private static ushort FPZero(bool Sign) + { + return Sign ? (ushort)0x8000u : (ushort)0x0000u; + } + + private static ushort FPMaxNormal(bool Sign) + { + return Sign ? (ushort)0xFBFFu : (ushort)0x7BFFu; + } + + private static double FPUnpackCV(this float Value, out FPType Type, out bool Sign, AThreadState State, out uint ValueBits) + { + ValueBits = (uint)BitConverter.SingleToInt32Bits(Value); + + Sign = (~ValueBits & 0x80000000u) == 0u; + + uint Exp32 = (ValueBits & 0x7F800000u) >> 23; + uint Frac32 = ValueBits & 0x007FFFFFu; + + double Real; + + if (Exp32 == 0u) + { + if (Frac32 == 0u || State.GetFpcrFlag(FPCR.FZ)) + { + Type = FPType.Zero; + Real = 0d; + + if (Frac32 != 0u) FPProcessException(FPExc.InputDenorm, State); + } + else + { + Type = FPType.Nonzero; // Subnormal. + Real = Math.Pow(2d, -126) * ((double)Frac32 * Math.Pow(2d, -23)); + } + } + else if (Exp32 == 0xFFu) + { + if (Frac32 == 0u) + { + Type = FPType.Infinity; + Real = Math.Pow(2d, 1000); + } + else + { + Type = (~Frac32 & 0x00400000u) == 0u ? FPType.QNaN : FPType.SNaN; + Real = 0d; + } + } + else + { + Type = FPType.Nonzero; // Normal. + Real = Math.Pow(2d, (int)Exp32 - 127) * (1d + (double)Frac32 * Math.Pow(2d, -23)); + } + + return Sign ? -Real : Real; + } + + private static ushort FPRoundCV(double Real, AThreadState State) + { + const int MinimumExp = -14; + + const int E = 5; + const int F = 10; + + bool Sign; + double Mantissa; + + if (Real < 0d) + { + Sign = true; + Mantissa = -Real; + } + else + { + Sign = false; + Mantissa = Real; + } + + int Exponent = 0; + + while (Mantissa < 1d) + { + Mantissa *= 2d; + Exponent--; + } + + while (Mantissa >= 2d) + { + Mantissa /= 2d; + Exponent++; + } + + uint BiasedExp = (uint)Math.Max(Exponent - MinimumExp + 1, 0); + + if (BiasedExp == 0u) + { + Mantissa /= Math.Pow(2d, MinimumExp - Exponent); + } + + uint IntMant = (uint)Math.Floor(Mantissa * Math.Pow(2d, F)); + double Error = Mantissa * Math.Pow(2d, F) - (double)IntMant; + + if (BiasedExp == 0u && (Error != 0d || State.GetFpcrFlag(FPCR.UFE))) + { + FPProcessException(FPExc.Underflow, State); + } + + bool OverflowToInf; + bool RoundUp; + + switch (State.FPRoundingMode()) + { + default: + case ARoundMode.ToNearest: + RoundUp = (Error > 0.5d || (Error == 0.5d && (IntMant & 1u) == 1u)); + OverflowToInf = true; + break; + + case ARoundMode.TowardsPlusInfinity: + RoundUp = (Error != 0d && !Sign); + OverflowToInf = !Sign; + break; + + case ARoundMode.TowardsMinusInfinity: + RoundUp = (Error != 0d && Sign); + OverflowToInf = Sign; + break; + + case ARoundMode.TowardsZero: + RoundUp = false; + OverflowToInf = false; + break; + } + + if (RoundUp) + { + IntMant++; + + if (IntMant == (uint)Math.Pow(2d, F)) + { + BiasedExp = 1u; + } + + if (IntMant == (uint)Math.Pow(2d, F + 1)) + { + BiasedExp++; + IntMant >>= 1; + } + } + + ushort ResultBits; + + if (!State.GetFpcrFlag(FPCR.AHP)) + { + if (BiasedExp >= (uint)Math.Pow(2d, E) - 1u) + { + ResultBits = OverflowToInf ? FPInfinity(Sign) : FPMaxNormal(Sign); + + FPProcessException(FPExc.Overflow, State); + + Error = 1d; + } + else + { + ResultBits = (ushort)((Sign ? 1u : 0u) << 15 | (BiasedExp & 0x1Fu) << 10 | (IntMant & 0x03FFu)); + } + } + else + { + if (BiasedExp >= (uint)Math.Pow(2d, E)) + { + ResultBits = (ushort)((Sign ? 1u : 0u) << 15 | 0x7FFFu); + + FPProcessException(FPExc.InvalidOp, State); + + Error = 0d; + } + else + { + ResultBits = (ushort)((Sign ? 1u : 0u) << 15 | (BiasedExp & 0x1Fu) << 10 | (IntMant & 0x03FFu)); + } + } + + if (Error != 0d) + { + FPProcessException(FPExc.Inexact, State); + } + + return ResultBits; + } + + private static ushort FPConvertNaN(uint ValueBits) + { + return (ushort)((ValueBits & 0x80000000u) >> 16 | 0x7E00u | (ValueBits & 0x003FE000u) >> 13); + } + + private static void FPProcessException(FPExc Exc, AThreadState State) + { + int Enable = (int)Exc + 8; + + if ((State.Fpcr & (1 << Enable)) != 0) + { + throw new NotImplementedException("floating-point trap handling"); + } + else + { + State.Fpsr |= 1 << (int)Exc; + } } } @@ -756,56 +1250,31 @@ namespace ChocolArm64.Instruction return Result; } - private enum FPType - { - Nonzero, - Zero, - Infinity, - QNaN, - SNaN - } - - private enum FPExc - { - InvalidOp, - DivideByZero, - Overflow, - Underflow, - Inexact, - InputDenorm = 7 - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float FPDefaultNaN() { return -float.NaN; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float FPInfinity(bool Sign) { return Sign ? float.NegativeInfinity : float.PositiveInfinity; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float FPZero(bool Sign) { return Sign ? -0f : +0f; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float FPTwo(bool Sign) { return Sign ? -2f : +2f; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float FPOnePointFive(bool Sign) { return Sign ? -1.5f : +1.5f; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float FPNeg(this float Value) { return -Value; @@ -927,8 +1396,6 @@ namespace ChocolArm64.Instruction private static float FPProcessNaN(FPType Type, uint Op, AThreadState State) { - const int DNBit = 25; // Default NaN mode control bit. - if (Type == FPType.SNaN) { Op |= 1u << 22; @@ -936,7 +1403,7 @@ namespace ChocolArm64.Instruction FPProcessException(FPExc.InvalidOp, State); } - if ((State.Fpcr & (1 << DNBit)) != 0) + if (State.GetFpcrFlag(FPCR.DN)) { return FPDefaultNaN(); } @@ -1482,56 +1949,31 @@ namespace ChocolArm64.Instruction return Result; } - private enum FPType - { - Nonzero, - Zero, - Infinity, - QNaN, - SNaN - } - - private enum FPExc - { - InvalidOp, - DivideByZero, - Overflow, - Underflow, - Inexact, - InputDenorm = 7 - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static double FPDefaultNaN() { return -double.NaN; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static double FPInfinity(bool Sign) { return Sign ? double.NegativeInfinity : double.PositiveInfinity; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static double FPZero(bool Sign) { return Sign ? -0d : +0d; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static double FPTwo(bool Sign) { return Sign ? -2d : +2d; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static double FPOnePointFive(bool Sign) { return Sign ? -1.5d : +1.5d; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static double FPNeg(this double Value) { return -Value; @@ -1653,8 +2095,6 @@ namespace ChocolArm64.Instruction private static double FPProcessNaN(FPType Type, ulong Op, AThreadState State) { - const int DNBit = 25; // Default NaN mode control bit. - if (Type == FPType.SNaN) { Op |= 1ul << 51; @@ -1662,7 +2102,7 @@ namespace ChocolArm64.Instruction FPProcessException(FPExc.InvalidOp, State); } - if ((State.Fpcr & (1 << DNBit)) != 0) + if (State.GetFpcrFlag(FPCR.DN)) { return FPDefaultNaN(); } diff --git a/Instruction/AVectorHelper.cs b/Instruction/AVectorHelper.cs index 7f9d98c..41e865b 100644 --- a/Instruction/AVectorHelper.cs +++ b/Instruction/AVectorHelper.cs @@ -105,9 +105,9 @@ namespace ChocolArm64.Instruction Value < ulong.MinValue ? ulong.MinValue : (ulong)Value; } - public static double Round(double Value, int Fpcr) + public static double Round(double Value, AThreadState State) { - switch ((ARoundMode)((Fpcr >> 22) & 3)) + switch (State.FPRoundingMode()) { case ARoundMode.ToNearest: return Math.Round (Value); case ARoundMode.TowardsPlusInfinity: return Math.Ceiling (Value); @@ -118,9 +118,9 @@ namespace ChocolArm64.Instruction throw new InvalidOperationException(); } - public static float RoundF(float Value, int Fpcr) + public static float RoundF(float Value, AThreadState State) { - switch ((ARoundMode)((Fpcr >> 22) & 3)) + switch (State.FPRoundingMode()) { case ARoundMode.ToNearest: return MathF.Round (Value); case ARoundMode.TowardsPlusInfinity: return MathF.Ceiling (Value); diff --git a/State/APState.cs b/State/APState.cs index f55431a..aaf0ff0 100644 --- a/State/APState.cs +++ b/State/APState.cs @@ -3,7 +3,7 @@ using System; namespace ChocolArm64.State { [Flags] - public enum APState + enum APState { VBit = 28, CBit = 29, @@ -20,4 +20,4 @@ namespace ChocolArm64.State NZCV = NZ | CV } -} \ No newline at end of file +} diff --git a/State/ARoundMode.cs b/State/ARoundMode.cs index 9896f30..297d013 100644 --- a/State/ARoundMode.cs +++ b/State/ARoundMode.cs @@ -1,10 +1,10 @@ namespace ChocolArm64.State { - public enum ARoundMode + enum ARoundMode { ToNearest = 0, TowardsPlusInfinity = 1, TowardsMinusInfinity = 2, TowardsZero = 3 } -} \ No newline at end of file +} diff --git a/State/AThreadState.cs b/State/AThreadState.cs index e4953b0..fbfac5b 100644 --- a/State/AThreadState.cs +++ b/State/AThreadState.cs @@ -145,5 +145,20 @@ namespace ChocolArm64.State { Undefined?.Invoke(this, new AInstUndefinedEventArgs(Position, RawOpCode)); } + + internal bool GetFpcrFlag(FPCR Flag) + { + return (Fpcr & (1 << (int)Flag)) != 0; + } + + internal void SetFpsrFlag(FPSR Flag) + { + Fpsr |= 1 << (int)Flag; + } + + internal ARoundMode FPRoundingMode() + { + return (ARoundMode)((Fpcr >> (int)FPCR.RMode) & 3); + } } -} \ No newline at end of file +} diff --git a/State/FPCR.cs b/State/FPCR.cs new file mode 100644 index 0000000..8f47cf9 --- /dev/null +++ b/State/FPCR.cs @@ -0,0 +1,11 @@ +namespace ChocolArm64.State +{ + enum FPCR + { + UFE = 11, + RMode = 22, + FZ = 24, + DN = 25, + AHP = 26 + } +} diff --git a/State/FPExc.cs b/State/FPExc.cs new file mode 100644 index 0000000..a665957 --- /dev/null +++ b/State/FPExc.cs @@ -0,0 +1,12 @@ +namespace ChocolArm64.State +{ + enum FPExc + { + InvalidOp = 0, + DivideByZero = 1, + Overflow = 2, + Underflow = 3, + Inexact = 4, + InputDenorm = 7 + } +} diff --git a/State/FPSR.cs b/State/FPSR.cs new file mode 100644 index 0000000..d71cde7 --- /dev/null +++ b/State/FPSR.cs @@ -0,0 +1,8 @@ +namespace ChocolArm64.State +{ + enum FPSR + { + UFC = 3, + QC = 27 + } +} diff --git a/State/FPType.cs b/State/FPType.cs new file mode 100644 index 0000000..b00f5fe --- /dev/null +++ b/State/FPType.cs @@ -0,0 +1,11 @@ +namespace ChocolArm64.State +{ + enum FPType + { + Nonzero, + Zero, + Infinity, + QNaN, + SNaN + } +}