Replace (val / 2) with (val * 0.5) in Jit (dotnet/coreclr#24584)
authorEgor Bogatov <egorbo@gmail.com>
Tue, 24 Sep 2019 05:41:50 +0000 (08:41 +0300)
committerSergey Andreenko <seandree@microsoft.com>
Tue, 24 Sep 2019 05:41:50 +0000 (22:41 -0700)
* Replace "val / dcon" with "val * (1.0 / dcon)"

* fix formatting issue

* replace tree->gtOp.gtOp2 with op2

* Address feedback

* fix mantissa calculations

* fix double mantissa

* use frexp+isnormal

* use frexp

* rollback frexp impl

* Add isNormal(float/double)

* fix exponent in isPow2(double)

* cleanup

* Add tests

* cleanup

* drop _finitef

* improve tests

* rename to hasPreciseReciprocal

* Add comments

* fix formatting issues

* add more test cases

* undo some formatting changes

* undo some formatting changes

* Address mikedn's feedback

* forgot to replace c-cast with reinterpret_cast in IsNormal()

* add fixed seed to Random in DivToMul (to make potential failures reproducible)

* use TestLibrary.Generator.GetDouble() instead of Random

* Remove Random-based values

* Update utils.cpp

* Update utils.cpp

* Address feedback

Commit migrated from https://github.com/dotnet/coreclr/commit/40faef69c1d9e58e170af6c139b37b3fa56e1392

src/coreclr/src/jit/morph.cpp
src/coreclr/src/jit/utils.cpp
src/coreclr/src/jit/utils.h
src/coreclr/tests/src/JIT/opt/InstructionCombining/DivToMul.cs [new file with mode: 0644]
src/coreclr/tests/src/JIT/opt/InstructionCombining/DivToMul.csproj [new file with mode: 0644]

index f352978..bd6f35b 100644 (file)
@@ -11310,7 +11310,20 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac)
             break;
 
         case GT_DIV:
-
+            // Replace "val / dcon" with "val * (1.0 / dcon)" if dcon is a power of two.
+            // Powers of two within range are always exactly represented,
+            // so multiplication by the reciprocal is safe in this scenario
+            if (op2->IsCnsFltOrDbl())
+            {
+                double divisor = op2->AsDblCon()->gtDconVal;
+                if (((typ == TYP_DOUBLE) && FloatingPointUtils::hasPreciseReciprocal(divisor)) ||
+                    ((typ == TYP_FLOAT) && FloatingPointUtils::hasPreciseReciprocal(forceCastToFloat(divisor))))
+                {
+                    oper = GT_MUL;
+                    tree->ChangeOper(oper);
+                    op2->AsDblCon()->gtDconVal = 1.0 / divisor;
+                }
+            }
 #ifndef _TARGET_64BIT_
             if (typ == TYP_LONG)
             {
index 492a159..247cb22 100644 (file)
@@ -2038,6 +2038,68 @@ float FloatingPointUtils::round(float x)
     return *reinterpret_cast<float*>(&bits);
 }
 
+bool FloatingPointUtils::isNormal(double x)
+{
+    int64_t bits = reinterpret_cast<int64_t&>(x);
+    bits &= 0x7FFFFFFFFFFFFFFF;
+    return (bits < 0x7FF0000000000000) && (bits != 0) && ((bits & 0x7FF0000000000000) != 0);
+}
+
+bool FloatingPointUtils::isNormal(float x)
+{
+    int32_t bits = reinterpret_cast<int32_t&>(x);
+    bits &= 0x7FFFFFFF;
+    return (bits < 0x7F800000) && (bits != 0) && ((bits & 0x7F800000) != 0);
+}
+
+//------------------------------------------------------------------------
+// hasPreciseReciprocal: check double for precise reciprocal. E.g. 2.0 <--> 0.5
+//
+// Arguments:
+//    x - value to check for precise reciprocal
+//
+// Return Value:
+//    True if 'x' is a power of two value and is not denormal (denormals may not be well-defined
+//    on some platforms such as if the user modified the floating-point environment via a P/Invoke)
+//
+
+bool FloatingPointUtils::hasPreciseReciprocal(double x)
+{
+    if (!isNormal(x))
+    {
+        return false;
+    }
+
+    uint64_t i        = reinterpret_cast<uint64_t&>(x);
+    uint64_t exponent = (i >> 52) & 0x7FFul;   // 0x7FF mask drops the sign bit
+    uint64_t mantissa = i & 0xFFFFFFFFFFFFFul; // 0xFFFFFFFFFFFFF mask drops the sign + exponent bits
+    return mantissa == 0 && exponent != 0 && exponent != 1023;
+}
+
+//------------------------------------------------------------------------
+// hasPreciseReciprocal: check float for precise reciprocal. E.g. 2.0f <--> 0.5f
+//
+// Arguments:
+//    x - value to check for precise reciprocal
+//
+// Return Value:
+//    True if 'x' is a power of two value and is not denormal (denormals may not be well-defined
+//    on some platforms such as if the user modified the floating-point environment via a P/Invoke)
+//
+
+bool FloatingPointUtils::hasPreciseReciprocal(float x)
+{
+    if (!isNormal(x))
+    {
+        return false;
+    }
+
+    uint32_t i        = reinterpret_cast<uint32_t&>(x);
+    uint32_t exponent = (i >> 23) & 0xFFu; // 0xFF mask drops the sign bit
+    uint32_t mantissa = i & 0x7FFFFFu;     // 0x7FFFFF mask drops the sign + exponent bits
+    return mantissa == 0 && exponent != 0 && exponent != 127;
+}
+
 namespace MagicDivide
 {
 template <int TableBase = 0, int TableSize, typename Magic>
index db7a781..54965f2 100644 (file)
@@ -669,6 +669,14 @@ public:
     static double round(double x);
 
     static float round(float x);
+
+    static bool isNormal(double x);
+
+    static bool isNormal(float x);
+
+    static bool hasPreciseReciprocal(double x);
+
+    static bool hasPreciseReciprocal(float x);
 };
 
 // The CLR requires that critical section locks be initialized via its ClrCreateCriticalSection API...but
diff --git a/src/coreclr/tests/src/JIT/opt/InstructionCombining/DivToMul.cs b/src/coreclr/tests/src/JIT/opt/InstructionCombining/DivToMul.cs
new file mode 100644 (file)
index 0000000..b4b29d8
--- /dev/null
@@ -0,0 +1,187 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+// JIT is able to replace "x / 2" with "x * 0.5" where 2 is a power of two float
+// Make sure this optimization doesn't change the results
+
+public class Program
+{
+    private static int resultCode = 100;
+
+    public static int Main(string[] args)
+    {
+        // Some corner cases
+        var testValues = new List<double>(new []
+            {
+                0, 0.01, 1.333, 1/3.0, 0.5, 1, 2, 3, 4,
+                MathF.PI, MathF.E, Math.PI, Math.E,
+                float.MinValue, double.MinValue,
+                float.MaxValue, double.MaxValue,
+                int.MaxValue, long.MaxValue,
+                int.MinValue, long.MinValue,
+                float.NegativeInfinity, double.NegativeInfinity,
+                float.PositiveInfinity, double.PositiveInfinity,
+                float.NaN, double.NaN
+            });
+
+        foreach (double value in testValues)
+        {
+            TestPowOfTwo_Single((float)value);
+            TestPowOfTwo_Single(-(float)value);
+
+            TestPowOfTwo_Double(value);
+            TestPowOfTwo_Double(-value);
+
+            TestNotPowOfTwo_Single((float)value);
+            TestNotPowOfTwo_Single(-(float)value);
+
+            TestNotPowOfTwo_Double(value);
+            TestNotPowOfTwo_Double(-value);
+        }
+
+        return resultCode;
+    }
+
+    private static void TestPowOfTwo_Single(float x)
+    {
+        // TestPowOfTwo_Single should contain 19 'mul' and 19 'div' instructions
+        AssertEquals(expected: x / ConstToVar(2), actual: x / 2);
+        AssertEquals(expected: x / ConstToVar(4), actual: x / 4);
+        AssertEquals(expected: x / ConstToVar(8), actual: x / 8);
+        AssertEquals(expected: x / ConstToVar(16), actual: x / 16);
+
+        AssertEquals(expected: x / ConstToVar(134217728), actual: x / 134217728);
+        AssertEquals(expected: x / ConstToVar(268435456), actual: x / 268435456);
+        AssertEquals(expected: x / ConstToVar(536870912), actual: x / 536870912);
+        AssertEquals(expected: x / ConstToVar(1073741824), actual: x / 1073741824);
+
+        AssertEquals(expected: x / ConstToVar(0.5f), actual: x / 0.5f);
+        AssertEquals(expected: x / ConstToVar(0.25f), actual: x / 0.25f);
+        AssertEquals(expected: x / ConstToVar(0.125f), actual: x / 0.125f);
+        AssertEquals(expected: x / ConstToVar(0.0625f), actual: x / 0.0625f);
+
+        AssertEquals(expected: x / ConstToVar(0.0009765625f), actual: x / 0.0009765625f);
+        AssertEquals(expected: x / ConstToVar(0.00048828125f), actual: x / 0.00048828125f);
+        AssertEquals(expected: x / ConstToVar(0.00024414062f), actual: x / 0.00024414062f);
+        AssertEquals(expected: x / ConstToVar(0.00012207031f), actual: x / 0.00012207031f);
+
+        AssertEquals(expected: x / ConstToVar(-1073741824), actual: x / -1073741824);
+        AssertEquals(expected: x / ConstToVar(-0.00012207031f), actual: x / -0.00012207031f);
+        AssertEquals(expected: x / ConstToVar(-2147483648), actual: x / -2147483648);
+    }
+
+
+    private static void TestPowOfTwo_Double(double x)
+    {
+        // TestPowOfTwo_Double should contain 19 'mul' and 19 'div' instructions
+        AssertEquals(expected: x / ConstToVar(2), actual: x / 2);
+        AssertEquals(expected: x / ConstToVar(4), actual: x / 4);
+        AssertEquals(expected: x / ConstToVar(8), actual: x / 8);
+        AssertEquals(expected: x / ConstToVar(16), actual: x / 16);
+
+        AssertEquals(expected: x / ConstToVar(9007199254740992), actual: x / 9007199254740992);
+        AssertEquals(expected: x / ConstToVar(18014398509481984), actual: x / 18014398509481984);
+        AssertEquals(expected: x / ConstToVar(36028797018963970), actual: x / 36028797018963970);
+        AssertEquals(expected: x / ConstToVar(72057594037927940), actual: x / 72057594037927940);
+
+        AssertEquals(expected: x / ConstToVar(0.5), actual: x / 0.5);
+        AssertEquals(expected: x / ConstToVar(0.25), actual: x / 0.25);
+        AssertEquals(expected: x / ConstToVar(0.125), actual: x / 0.125);
+        AssertEquals(expected: x / ConstToVar(0.0625), actual: x / 0.0625);
+
+        AssertEquals(expected: x / ConstToVar(0.00390625), actual: x / 0.00390625);
+        AssertEquals(expected: x / ConstToVar(0.001953125), actual: x / 0.001953125);
+        AssertEquals(expected: x / ConstToVar(0.00048828125), actual: x / 0.00048828125);
+        AssertEquals(expected: x / ConstToVar(0.0001220703125), actual: x / 0.0001220703125);
+
+        AssertEquals(expected: x / ConstToVar(-1073741824), actual: x / -1073741824);
+        AssertEquals(expected: x / ConstToVar(-0.00012207031f), actual: x / -0.00012207031f);
+        AssertEquals(expected: x / ConstToVar(-2147483648), actual: x / -2147483648);
+    }
+
+    private static void TestNotPowOfTwo_Single(float x)
+    {
+        // TestNotPowOfTwo_Single should not contain 'mul' instructions (the optimization should not be applied here)
+        AssertEquals(expected: x / ConstToVar(3), actual: x / 3);
+        AssertEquals(expected: x / ConstToVar(9), actual: x / 9);
+        AssertEquals(expected: x / ConstToVar(2.5f), actual: x / 2.5f);
+        AssertEquals(expected: x / ConstToVar(0.51f), actual: x / 0.51f);
+
+        AssertEquals(expected: x / ConstToVar(-3), actual: x / -3);
+        AssertEquals(expected: x / ConstToVar(-9), actual: x / -9);
+        AssertEquals(expected: x / ConstToVar(-2.5f), actual: x / -2.5f);
+        AssertEquals(expected: x / ConstToVar(-0.51f), actual: x / -0.51f);
+
+        AssertEquals(expected: x / ConstToVar(0.0f), actual: x / 0.0f);
+        AssertEquals(expected: x / ConstToVar(1.0f), actual: x / 1.0f);
+        AssertEquals(expected: x / ConstToVar(-0.0f), actual: x / -0.0f);
+        AssertEquals(expected: x / ConstToVar(-1.0f), actual: x / -1.0f);
+
+        AssertEquals(expected: x / ConstToVar(float.Epsilon), actual: x / float.Epsilon);
+        AssertEquals(expected: x / ConstToVar(float.MinValue), actual: x / float.MinValue);
+        AssertEquals(expected: x / ConstToVar(float.MaxValue), actual: x / float.MaxValue);
+        AssertEquals(expected: x / ConstToVar(float.PositiveInfinity), actual: x / float.PositiveInfinity);
+        AssertEquals(expected: x / ConstToVar(float.NegativeInfinity), actual: x / float.NegativeInfinity);
+    }
+
+    private static void TestNotPowOfTwo_Double(double x)
+    {
+        // TestNotPowOfTwo_Double should not contain 'mul' instructions (the optimization should not be applied here)
+        AssertEquals(expected: x / ConstToVar(3), actual: x / 3);
+        AssertEquals(expected: x / ConstToVar(9), actual: x / 9);
+        AssertEquals(expected: x / ConstToVar(2.5), actual: x / 2.5);
+        AssertEquals(expected: x / ConstToVar(0.51), actual: x / 0.51);
+
+        AssertEquals(expected: x / ConstToVar(-3), actual: x / -3);
+        AssertEquals(expected: x / ConstToVar(-9), actual: x / -9);
+        AssertEquals(expected: x / ConstToVar(-2.5), actual: x / -2.5);
+        AssertEquals(expected: x / ConstToVar(-0.51), actual: x / -0.51);
+
+        AssertEquals(expected: x / ConstToVar(0.00024414062), actual: x / 0.00024414062);
+        AssertEquals(expected: x / ConstToVar(0.00012207031), actual: x / 0.00012207031);
+
+        AssertEquals(expected: x / ConstToVar(0.0), actual: x / 0.0);
+        AssertEquals(expected: x / ConstToVar(1.0), actual: x / 1.0);
+        AssertEquals(expected: x / ConstToVar(-0.0), actual: x / -0.0);
+        AssertEquals(expected: x / ConstToVar(-1.0), actual: x / -1.0);
+
+        AssertEquals(expected: x / ConstToVar(double.Epsilon), actual: x / double.Epsilon);
+        AssertEquals(expected: x / ConstToVar(double.MinValue), actual: x / double.MinValue);
+        AssertEquals(expected: x / ConstToVar(double.MaxValue), actual: x / double.MaxValue);
+        AssertEquals(expected: x / ConstToVar(double.PositiveInfinity), actual: x / double.PositiveInfinity);
+        AssertEquals(expected: x / ConstToVar(double.NegativeInfinity), actual: x / double.NegativeInfinity);
+    }
+
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private static void AssertEquals(float expected, float actual)
+    {
+        int expectedi = BitConverter.SingleToInt32Bits(expected);
+        int actuali = BitConverter.SingleToInt32Bits(actual);
+        if (expectedi != actuali)
+        {
+            resultCode--;
+            Console.WriteLine($"AssertEquals: {expected} != {actual}");
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private static void AssertEquals(double expected, double actual)
+    {
+        long expectedi = BitConverter.DoubleToInt64Bits(expected);
+        long actuali = BitConverter.DoubleToInt64Bits(actual);
+        if (expectedi != actuali)
+        {
+            resultCode--;
+            Console.WriteLine($"AssertEquals: {expected} != {actual}");
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private static T ConstToVar<T>(T v) => v;
+}
diff --git a/src/coreclr/tests/src/JIT/opt/InstructionCombining/DivToMul.csproj b/src/coreclr/tests/src/JIT/opt/InstructionCombining/DivToMul.csproj
new file mode 100644 (file)
index 0000000..1abe39d
--- /dev/null
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+  </PropertyGroup>
+  <PropertyGroup>
+    <DebugType>None</DebugType>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="DivToMul.cs" />
+  </ItemGroup>
+</Project>