From: Egor Bogatov Date: Tue, 24 Sep 2019 05:41:50 +0000 (+0300) Subject: Replace (val / 2) with (val * 0.5) in Jit (dotnet/coreclr#24584) X-Git-Tag: submit/tizen/20210909.063632~11030^2~543 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=f80616ef9c296bae5cd7763c65cda72ee67a80ce;p=platform%2Fupstream%2Fdotnet%2Fruntime.git Replace (val / 2) with (val * 0.5) in Jit (dotnet/coreclr#24584) * 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 --- diff --git a/src/coreclr/src/jit/morph.cpp b/src/coreclr/src/jit/morph.cpp index f352978..bd6f35b 100644 --- a/src/coreclr/src/jit/morph.cpp +++ b/src/coreclr/src/jit/morph.cpp @@ -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) { diff --git a/src/coreclr/src/jit/utils.cpp b/src/coreclr/src/jit/utils.cpp index 492a159..247cb22 100644 --- a/src/coreclr/src/jit/utils.cpp +++ b/src/coreclr/src/jit/utils.cpp @@ -2038,6 +2038,68 @@ float FloatingPointUtils::round(float x) return *reinterpret_cast(&bits); } +bool FloatingPointUtils::isNormal(double x) +{ + int64_t bits = reinterpret_cast(x); + bits &= 0x7FFFFFFFFFFFFFFF; + return (bits < 0x7FF0000000000000) && (bits != 0) && ((bits & 0x7FF0000000000000) != 0); +} + +bool FloatingPointUtils::isNormal(float x) +{ + int32_t bits = reinterpret_cast(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(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(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 diff --git a/src/coreclr/src/jit/utils.h b/src/coreclr/src/jit/utils.h index db7a781..54965f2 100644 --- a/src/coreclr/src/jit/utils.h +++ b/src/coreclr/src/jit/utils.h @@ -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 index 0000000..b4b29d8 --- /dev/null +++ b/src/coreclr/tests/src/JIT/opt/InstructionCombining/DivToMul.cs @@ -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(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 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 index 0000000..1abe39d --- /dev/null +++ b/src/coreclr/tests/src/JIT/opt/InstructionCombining/DivToMul.csproj @@ -0,0 +1,12 @@ + + + Exe + + + None + True + + + + +