From: Egor Bogatov Date: Wed, 6 May 2020 19:00:31 +0000 (+0300) Subject: RyuJIT: x*2 -> x+x; x*1 -> x (fp) (#33024) X-Git-Tag: submit/tizen/20210909.063632~8148 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=5abde010ed0a53f196bba11cd18e839e9a0ce5fa;p=platform%2Fupstream%2Fdotnet%2Fruntime.git RyuJIT: x*2 -> x+x; x*1 -> x (fp) (#33024) * Optimize "x * 2" to "x + x" for floats * Add tests * Fix typo * Address feedback * Move to morph.cpp, also add "x*1" optimization * clean up * clean up * update op2 * Fix incorrect "fgMakeMultiUse" usage * update op1 * Address feedback * Add opts.OptimizationEnabled() --- diff --git a/src/coreclr/src/jit/morph.cpp b/src/coreclr/src/jit/morph.cpp index ab11d82..fdff5b7 100644 --- a/src/coreclr/src/jit/morph.cpp +++ b/src/coreclr/src/jit/morph.cpp @@ -13095,6 +13095,30 @@ DONE_MORPHING_CHILDREN: op2 = tree->AsOp()->gtOp2; } + // See if we can fold floating point operations (can regress minopts mode) + if (opts.OptimizationEnabled() && varTypeIsFloating(tree->TypeGet()) && !optValnumCSE_phase) + { + if ((oper == GT_MUL) && !op1->IsCnsFltOrDbl() && op2->IsCnsFltOrDbl()) + { + if (op2->AsDblCon()->gtDconVal == 2.0) + { + // Fold "x*2.0" to "x+x" + op2 = op1->OperIsLeaf() ? gtCloneExpr(op1) : fgMakeMultiUse(&tree->AsOp()->gtOp1); + op1 = tree->AsOp()->gtOp1; + oper = GT_ADD; + tree = gtNewOperNode(oper, tree->TypeGet(), op1, op2); + INDEBUG(tree->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED); + } + else if (op2->AsDblCon()->gtDconVal == 1.0) + { + // Fold "x*1.0" to "x" + DEBUG_DESTROY_NODE(op2); + DEBUG_DESTROY_NODE(tree); + return op1; + } + } + } + /* See if we can fold GT_ADD nodes. */ if (oper == GT_ADD) diff --git a/src/coreclr/tests/src/JIT/opt/InstructionCombining/MulToAdd.cs b/src/coreclr/tests/src/JIT/opt/InstructionCombining/MulToAdd.cs new file mode 100644 index 0000000..756d4e8 --- /dev/null +++ b/src/coreclr/tests/src/JIT/opt/InstructionCombining/MulToAdd.cs @@ -0,0 +1,133 @@ +// 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.Linq; +using System.Runtime.CompilerServices; + +// Test "X * 2" to "X + X" + +public class Program +{ + private static int resultCode = 100; + + public static int Main(string[] args) + { + float[] testValues = + { + 0, 0.01f, 1.333f, 1/3.0f, 0.5f, 1, 2, 3, 4, + MathF.PI, MathF.E, + float.MinValue, float.MaxValue, + int.MaxValue, long.MaxValue, + int.MinValue, long.MinValue, + float.NegativeInfinity, + float.PositiveInfinity, + float.NaN, + }; + + testValues = testValues.Concat(testValues.Select(v => -v)).ToArray(); + + foreach (float testValue in testValues) + { + var tf = new TestFloats(); + + // Case 1: argument + AssertEquals(tf.TestArg(testValue), tf.TestArg_var(testValue)); + + // Case 2: ref argument + float t1 = testValue, t2 = testValue; + tf.TestArgRef(ref t1); + tf.TestArgRef_var(ref t2); + AssertEquals(t1, t2); + + // Case 3: out argument + tf.TestArgOut(t1, out t1); + tf.TestArgOut_var(t2, out t2); + AssertEquals(t1, t2); + + // Case 4: field + tf.TestField(); + tf.TestField_var(); + AssertEquals(tf.field1, tf.field2); + + // Case 5: call + AssertEquals(tf.TestCall(), tf.TestCall_var()); + AssertEquals(tf.field1, tf.field2); + } + + return resultCode; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void AssertEquals(float expected, float actual) + { + int expectedBits = BitConverter.SingleToInt32Bits(expected); + int actualBits = BitConverter.SingleToInt32Bits(actual); + if (expectedBits != actualBits) + { + resultCode++; + Console.WriteLine($"AssertEquals: {expected} != {actual}"); + } + } +} + +public class TestFloats +{ + [MethodImpl(MethodImplOptions.NoInlining)] + public static T Var(T t) => t; + + + // Case 1: argument + + [MethodImpl(MethodImplOptions.NoInlining)] + public float TestArg(float x) => x * 2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public float TestArg_var(float x) => x * Var(2); + + + // Case 2: ref argument + + [MethodImpl(MethodImplOptions.NoInlining)] + public void TestArgRef(ref float x) => x *= 2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public void TestArgRef_var(ref float x) => x *= Var(2); + + + // Case 3: out argument + + [MethodImpl(MethodImplOptions.NoInlining)] + public void TestArgOut(float x, out float y) => y = x * 2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public void TestArgOut_var(float x, out float y) => y = x * Var(2); + + + // Case 4: field + + public float field1 = 3.14f; + [MethodImpl(MethodImplOptions.NoInlining)] + public void TestField() => field1 *= 2; + + public float field2 = 3.14f; + [MethodImpl(MethodImplOptions.NoInlining)] + public void TestField_var() => field2 *= Var(2); + + + // Case 5: Call + + [MethodImpl(MethodImplOptions.NoInlining)] + public float Call1() => field1++; // with side-effect + + [MethodImpl(MethodImplOptions.NoInlining)] + public float Call2() => field2++; // with side-effect + + + [MethodImpl(MethodImplOptions.NoInlining)] + public float TestCall() => Call1() * 2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public float TestCall_var() => Call2() * Var(2); +} diff --git a/src/coreclr/tests/src/JIT/opt/InstructionCombining/MulToAdd.csproj b/src/coreclr/tests/src/JIT/opt/InstructionCombining/MulToAdd.csproj new file mode 100644 index 0000000..0e72815 --- /dev/null +++ b/src/coreclr/tests/src/JIT/opt/InstructionCombining/MulToAdd.csproj @@ -0,0 +1,12 @@ + + + Exe + + + None + True + + + + +