Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Adding some new functions to System.Math and System.MathF #20788

Merged
merged 5 commits into from
Nov 5, 2018
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 81 additions & 18 deletions src/System.Private.CoreLib/shared/System/Math.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,76 @@ public static long BigMul(int a, int b)
return ((long)a) * b;
}

public static double BitDecrement(double x)
{
var bits = BitConverter.DoubleToInt64Bits(x);

if (((bits >> 32) & 0x7FF00000) >= 0x7FF00000)
{
// NaN returns NaN
// -Infinity returns -Infinity
// +Infinity returns double.MaxValue
return (bits == 0x7FF00000_00000000) ? double.MaxValue : x;
}

if (bits == 0x00000000_00000000)
{
// +0.0 returns -double.Epsilon
return -double.Epsilon;
}

// Negative values need to be incremented
// Positive values need to be decremented

bits += ((bits < 0) ? +1 : -1);
return BitConverter.Int64BitsToDouble(bits);
}

public static double BitIncrement(double x)
{
var bits = BitConverter.DoubleToInt64Bits(x);

if (((bits >> 32) & 0x7FF00000) >= 0x7FF00000)
{
// NaN returns NaN
// -Infinity returns double.MinValue
// +Infinity returns +Infinity
return (bits == unchecked((long)(0xFFF00000_00000000))) ? double.MinValue : x;
}

if (bits == unchecked((long)(0x80000000_00000000)))
{
// -0.0 returns double.Epsilon
return double.Epsilon;
}

// Negative values need to be decremented
// Positive values need to be incremented

bits += ((bits < 0) ? -1 : +1);
return BitConverter.Int64BitsToDouble(bits);
}

public static unsafe double CopySign(double x, double y)
{
// This method is required to work for all inputs,
// including NaN, so we operate on the raw bits.

var xbits = BitConverter.DoubleToInt64Bits(x);
var ybits = BitConverter.DoubleToInt64Bits(y);

// If the sign bits of x and y are not the same,
// flip the sign bit of x and return the new value;
// otherwise, just return x

if ((xbits ^ ybits) < 0)
{
return BitConverter.Int64BitsToDouble(xbits ^ long.MinValue);
tannergooding marked this conversation as resolved.
Show resolved Hide resolved
}

return x;
}

public static int DivRem(int a, int b, out int result)
{
// TODO https://github.com/dotnet/coreclr/issues/3439:
Expand Down Expand Up @@ -542,6 +612,11 @@ public static ulong Max(ulong val1, ulong val2)
return (val1 >= val2) ? val1 : val2;
}

public static double MaxMagnitude(double x, double y)
{
return Max(Abs(x), Abs(y));
}

[NonVersionable]
public static byte Min(byte val1, byte val2)
{
Expand Down Expand Up @@ -630,6 +705,11 @@ public static ulong Min(ulong val1, ulong val2)
return (val1 <= val2) ? val1 : val2;
}

public static double MinMagnitude(double x, double y)
{
return Min(Abs(x), Abs(y));
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static decimal Round(decimal d)
{
Expand Down Expand Up @@ -680,7 +760,7 @@ public static double Round(double a)
flrTempVal -= 1.0;
}

return copysign(flrTempVal, a);
return CopySign(flrTempVal, a);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down Expand Up @@ -810,23 +890,6 @@ public static unsafe double Truncate(double d)
return d;
}

private static unsafe double copysign(double x, double y)
{
var xbits = BitConverter.DoubleToInt64Bits(x);
var ybits = BitConverter.DoubleToInt64Bits(y);

// If the sign bits of x and y are not the same,
// flip the sign bit of x and return the new value;
// otherwise, just return x

if (((xbits ^ ybits) >> 63) != 0)
{
return BitConverter.Int64BitsToDouble(xbits ^ long.MinValue);
}

return x;
}

private static void ThrowMinMaxException<T>(T min, T max)
{
throw new ArgumentException(SR.Format(SR.Argument_MinMaxValue, min, max));
Expand Down
97 changes: 80 additions & 17 deletions src/System.Private.CoreLib/shared/System/MathF.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,76 @@ public static float Abs(float x)
return Math.Abs(x);
}

public static float BitDecrement(float x)
{
var bits = BitConverter.SingleToInt32Bits(x);

if ((bits & 0x7F800000) >= 0x7F800000)
{
// NaN returns NaN
// -Infinity returns -Infinity
// +Infinity returns float.MaxValue
return (bits == 0x7F800000) ? float.MaxValue : x;
}

if (bits == 0x00000000)
{
// +0.0 returns -float.Epsilon
return -float.Epsilon;
}

// Negative values need to be incremented
// Positive values need to be decremented

bits += ((bits < 0) ? +1 : -1);
return BitConverter.Int32BitsToSingle(bits);
}

public static float BitIncrement(float x)
{
var bits = BitConverter.SingleToInt32Bits(x);

if ((bits & 0x7F800000) >= 0x7F800000)
{
// NaN returns NaN
// -Infinity returns float.MinValue
// +Infinity returns +Infinity
return (bits == unchecked((int)(0xFF800000))) ? float.MinValue : x;
}

if (bits == unchecked((int)(0x80000000)))
{
// -0.0 returns float.Epsilon
return float.Epsilon;
}

// Negative values need to be decremented
// Positive values need to be incremented

bits += ((bits < 0) ? -1 : +1);
return BitConverter.Int32BitsToSingle(bits);
}

public static unsafe float CopySign(float x, float y)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was looking at the codegen for this method:

; Assembly listing for method MathF:CopySign(float,float):float
; Emitting BLENDED_CODE for X64 CPU with AVX - Windows
; optimized code
; rsp based frame
; partially interruptible
; Final local variable assignments
;
;  V00 arg0         [V00,T03] (  4,  3.50)   float  ->  mm0        
;  V01 arg1         [V01,T04] (  3,  3   )   float  ->  mm1        
;  V02 loc0         [V02,T00] (  3,  2.50)     int  ->  rax        
;  V03 loc1         [V03,T01] (  2,  2   )     int  ->  rdx        
;# V04 OutArgs      [V04    ] (  1,  1   )  lclBlk ( 0) [rsp+0x00]   "OutgoingArgSpace"
;  V05 tmp1         [V05,T05] (  2,  4   )   float  ->  [rsp+0x14]   do-not-enreg[F] ld-addr-op "Inlining Arg"
;  V06 tmp2         [V06,T06] (  2,  4   )   float  ->  [rsp+0x10]   do-not-enreg[F] ld-addr-op "Inlining Arg"
;  V07 tmp3         [V07,T02] (  2,  2   )     int  ->  [rsp+0x0C]   do-not-enreg[F] ld-addr-op "Inlining Arg"
;
; Lcl frame size = 24

G_M7953_IG01:
       sub      rsp, 24
       vzeroupper 

G_M7953_IG02:
       vmovss   dword ptr [rsp+14H], xmm0
       mov      eax, dword ptr [rsp+14H]
       vmovss   dword ptr [rsp+10H], xmm1
       mov      edx, dword ptr [rsp+10H]
       xor      edx, eax
       test     edx, edx
       jge      SHORT G_M7953_IG04
       xor      eax, 0xD1FFAB1E
       mov      dword ptr [rsp+0CH], eax
       vmovss   xmm0, dword ptr [rsp+0CH]

G_M7953_IG03:
       add      rsp, 24
       ret      

G_M7953_IG04:
       add      rsp, 24
       ret      

; Total bytes of code 61, prolog size 7 for method MathF:CopySign(float,float):float
; ============================================================

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following, which is produced from BitConverter.SingleToInt32Bits

vmovss   dword ptr [rsp+14H], xmm0
mov      eax, dword ptr [rsp+14H]

Could be:

movd eax, xmm0

Copy link
Member Author

@tannergooding tannergooding Nov 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following, which is produced by if ((xbits ^ ybits) < 0):

xor      edx, eax
test     edx, edx
jge      SHORT G_M7953_IG04

Could be:

xor edx, eax
jns SHORT G_M7953_IG04

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

G_M7953_IG03: and G_M7953_IG04: are the same, and could be folded together.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following, which is produced from BitConverter.Int32BitsToSingle

mov      dword ptr [rsp+0CH], eax
vmovss   xmm0, dword ptr [rsp+0CH]

Could be:

vmovd xmm0, eax

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means we should be generating something closer to the following, if possible:

G_M7953_IG01:
       vmovd    eax, xmm0
       vmovd    edx, xmm1
       xor      edx, eax
       jns      SHORT G_M7953_IG02
       xor      eax, 0xD1FFAB1E
       vmovd    xmm0, eax

G_M7953_IG02:
       ret

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as it specifies there is no transition penalty when using the AVX encoded 128-bit instructions,

Right, there is no transition penalty in VEX encoding, but we insert vzeroupper at the prolog to avoid SSE-to-AVX transition penalty from P/Invoke or corssgen-ed code calling JITed code.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at the prolog to avoid SSE-to-AVX transition penalty from P/Invoke or corssgen-ed code calling JITed code.

The architecture optimization manual seems to imply that there is no transition penalty when going from legacy 128-bit instructions to VEX-encoded 128-bit instructions (or vice-versa). Only when going from legacy 128-bit instructions to VEX-encoded 256-bit instructions (and that you avoid the penalty by using VZEROUPPER) and when going from VEX-encoded 256-bit instructions to legacy 128-bit instructions (where you cannot avoid the penalty). -- See the last line in the table, for Mixed Code Handling (and some surrounding text in the optimization manual)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, the optimization manual is confusing here. Yes, there is no transition penalty when going from legacy 128-bit instructions to VEX-encoded 128-bit instructions on Skylake (and newer) micro-architectures.
image

But the penalty still can happen on older architectures
image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see. There is a penalty on Broadwell and prior when going from Preserved Non-INIT Upper State to Dirty Upper State or Clean Upper State. However, it looks like Penalty D and Penalty B are approx the same (whether it is implicit to dirty, or explicit to clean via vzeroupper). The difference is whether there is an additional penalty when transitioning back into the legacy instructions (which will impact crossgen code)...

image
image
image

{
// This method is required to work for all inputs,
// including NaN, so we operate on the raw bits.

var xbits = BitConverter.SingleToInt32Bits(x);
var ybits = BitConverter.SingleToInt32Bits(y);

// If the sign bits of x and y are not the same,
// flip the sign bit of x and return the new value;
// otherwise, just return x

if ((xbits ^ ybits) < 0)
{
return BitConverter.Int32BitsToSingle(xbits ^ int.MinValue);
}

return x;
}

public static float IEEERemainder(float x, float y)
{
if (float.IsNaN(x))
Expand Down Expand Up @@ -118,12 +188,22 @@ public static float Max(float x, float y)
return Math.Max(x, y);
}

public static float MaxMagnitude(float x, float y)
{
return Max(Abs(x), Abs(y));
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Min(float x, float y)
{
return Math.Min(x, y);
}

public static float MinMagnitude(float x, float y)
{
return Min(Abs(x), Abs(y));
}

[Intrinsic]
public static float Round(float x)
{
Expand Down Expand Up @@ -214,22 +294,5 @@ public static unsafe float Truncate(float x)
ModF(x, &x);
return x;
}

private static unsafe float CopySign(float x, float y)
{
var xbits = BitConverter.SingleToInt32Bits(x);
var ybits = BitConverter.SingleToInt32Bits(y);

// If the sign bits of x and y are not the same,
// flip the sign bit of x and return the new value;
// otherwise, just return x

if (((xbits ^ ybits) >> 31) != 0)
{
return BitConverter.Int32BitsToSingle(xbits ^ int.MinValue);
}

return x;
}
}
}
12 changes: 12 additions & 0 deletions src/System.Private.CoreLib/src/System/Math.CoreCLR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,27 @@ public static partial class Math
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern double Floor(double d);

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern double FusedMultiplyAdd(double x, double y, double z);

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern int IlogB(double x);

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern double Log(double d);

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern double Log2(double x);

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern double Log10(double d);

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern double Pow(double x, double y);

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern double ScaleB(double x, int n);

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern double Sin(double a);

Expand Down
12 changes: 12 additions & 0 deletions src/System.Private.CoreLib/src/System/MathF.CoreCLR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,27 @@ public static partial class MathF
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern float Floor(float x);

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern float FusedMultiplyAdd(float x, float y, float z);

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern int IlogB(float x);

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern float Log(float x);

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern float Log2(float x);

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern float Log10(float x);

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern float Pow(float x, float y);

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern float ScaleB(float x, int n);

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern float Sin(float x);

Expand Down
36 changes: 36 additions & 0 deletions src/classlibnative/float/floatdouble.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,24 @@ FCIMPL2_VV(double, COMDouble::FMod, double x, double y)
return (double)fmod(x, y);
FCIMPLEND

/*=====================================FusedMultiplyAdd==========================
**
==============================================================================*/
FCIMPL3_VVV(double, COMDouble::FusedMultiplyAdd, double x, double y, double z)
FCALL_CONTRACT;

return (double)fma(x, y, z);
FCIMPLEND

/*=====================================Ilog2====================================
**
==============================================================================*/
FCIMPL1_V(int, COMDouble::IlogB, double x)
FCALL_CONTRACT;

return (int)ilogb(x);
FCIMPLEND

/*=====================================Log======================================
**
==============================================================================*/
Expand All @@ -209,6 +227,15 @@ FCIMPL1_V(double, COMDouble::Log, double x)
return (double)log(x);
FCIMPLEND

/*=====================================Log2=====================================
**
==============================================================================*/
FCIMPL1_V(double, COMDouble::Log2, double x)
FCALL_CONTRACT;

return (double)log2(x);
FCIMPLEND

/*====================================Log10=====================================
**
==============================================================================*/
Expand Down Expand Up @@ -236,6 +263,15 @@ FCIMPL2_VV(double, COMDouble::Pow, double x, double y)
return (double)pow(x, y);
FCIMPLEND

/*=====================================ScaleB===================================
**
==============================================================================*/
FCIMPL2_VI(double, COMDouble::ScaleB, double x, int n)
FCALL_CONTRACT;

return (double)scalbn(x, n);
FCIMPLEND

/*=====================================Sin======================================
**
==============================================================================*/
Expand Down
Loading