diff --git a/src/coreclr/src/jit/morph.cpp b/src/coreclr/src/jit/morph.cpp index 1bf6af9148c71..3cc695867844e 100644 --- a/src/coreclr/src/jit/morph.cpp +++ b/src/coreclr/src/jit/morph.cpp @@ -12255,8 +12255,8 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) case GT_RUNTIMELOOKUP: return fgMorphTree(op1); -#ifdef TARGET_ARM case GT_INTRINSIC: +#ifdef TARGET_ARM if (tree->AsIntrinsic()->gtIntrinsicName == NI_System_Math_Round) { switch (tree->TypeGet()) @@ -12268,9 +12268,37 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) default: unreached(); } + break; } - break; #endif + if ((tree->AsIntrinsic()->gtIntrinsicName == NI_System_Math_Pow) && op2->IsCnsFltOrDbl()) + { + double power = op2->AsDblCon()->gtDconVal; + if (power == 1.0) + { + // Math.Pow(x, 1) -> x + return op1; + } + if (power == 2.0) + { + // Math.Pow(x, 2) -> x*x + if (op1->OperIsLeaf() || optValnumCSE_phase) + { + GenTree* arg0Clone = op1; + if (!op1->OperIsLeaf()) + { + // if op1 is not a leaf op we need to introduce a variable + // and it should be done after LoopHoisting phase + arg0Clone = fgMakeMultiUse(&op1); + } + tree = gtNewOperNode(GT_MUL, tree->TypeGet(), op1, gtCloneExpr(arg0Clone)); + INDEBUG(tree->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED); + return tree; + } + } + } + break; + case GT_LIST: // Special handling for the arg list. return fgMorphArgList(tree->AsArgList(), mac); diff --git a/src/coreclr/tests/src/JIT/Directed/intrinsic/pow/pow_cns.cs b/src/coreclr/tests/src/JIT/Directed/intrinsic/pow/pow_cns.cs new file mode 100644 index 0000000000000..bf192ef41487d --- /dev/null +++ b/src/coreclr/tests/src/JIT/Directed/intrinsic/pow/pow_cns.cs @@ -0,0 +1,209 @@ +using System; +using System.Runtime.CompilerServices; + +class MathPowTests +{ + static int returnCode = 100; + + static int Main(string[] args) + { + var tests = new MathPowTests(); + tests.TestCorrectnessDouble(); + tests.TestCorrectnessFloat(); + tests.TestFieldArg(); + tests.TestCallArg1(); + tests.TestCallArgN1(); + tests.TestCallArg2(); + tests.TestWithInlining(); + + float x = 0, y = 0; + tests.TestRefArgs(ref x, ref y); + return returnCode; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static T ToVar(T arg) => arg; + + public static void AssertEquals(double arg, double a, double b, [CallerLineNumber] int line = 0) + { + if (BitConverter.DoubleToInt64Bits(a) != + BitConverter.DoubleToInt64Bits(b)) + { + returnCode++; + Console.WriteLine($"Failed: {a} != {b}, ({BitConverter.DoubleToInt64Bits(a):X} != {BitConverter.DoubleToInt64Bits(b):X}), arg={arg} ({BitConverter.DoubleToInt64Bits(arg):X}) line:{line}"); + } + } + + public static void AssertEquals(float arg, float a, float b, [CallerLineNumber] int line = 0) + { + if (BitConverter.SingleToInt32Bits(a) != + BitConverter.SingleToInt32Bits(b)) + { + returnCode++; + Console.WriteLine($"Failed: {a} != {b}, ({BitConverter.SingleToInt32Bits(a):X} != {BitConverter.SingleToInt32Bits(b):X}), arg={arg} ({BitConverter.SingleToInt32Bits(arg):X}) line:{line}"); + } + } + + private double[] testDblValues = + { + -1000, -2, -1, -0.0, 0, 1, 2, 1000, + Math.PI, Math.E, + double.MinValue, double.MaxValue, + double.PositiveInfinity, double.NegativeInfinity + }; + + public void TestCorrectnessDouble() + { + foreach (double testValue in testDblValues) + { + AssertEquals(testValue, Math.Pow(testValue, 0.0), Math.Pow(testValue, ToVar(0.0))); + AssertEquals(testValue, Math.Pow(testValue, -0.0), Math.Pow(testValue, ToVar(-0.0))); + AssertEquals(testValue, Math.Pow(testValue, 1.0), Math.Pow(testValue, ToVar(1.0))); + AssertEquals(testValue, Math.Pow(testValue, -1.0), Math.Pow(testValue, ToVar(-1.0))); + AssertEquals(testValue, Math.Pow(testValue, 2.0), Math.Pow(testValue, ToVar(2.0))); + AssertEquals(testValue, Math.Pow(testValue, 3.0), Math.Pow(testValue, ToVar(3.0))); + AssertEquals(testValue, Math.Pow(testValue, 4.0), Math.Pow(testValue, ToVar(4.0))); + } + } + + private float[] testFltValues = + { + -1000, -2, -1, -0.0f, 0, 1, 2, 1000, + MathF.PI, MathF.E, + float.MinValue, float.MaxValue, + float.PositiveInfinity, float.NegativeInfinity + }; + + public void TestCorrectnessFloat() + { + foreach (float testValue in testFltValues) + { + AssertEquals(testValue, MathF.Pow(testValue, 0.0f), MathF.Pow(testValue, ToVar(0.0f))); + AssertEquals(testValue, MathF.Pow(testValue, -0.0f), MathF.Pow(testValue, ToVar(-0.0f))); + AssertEquals(testValue, MathF.Pow(testValue, 1.0f), MathF.Pow(testValue, ToVar(1.0f))); + AssertEquals(testValue, MathF.Pow(testValue, -1.0f), MathF.Pow(testValue, ToVar(-1.0f))); + AssertEquals(testValue, MathF.Pow(testValue, 2.0f), MathF.Pow(testValue, ToVar(2.0f))); + AssertEquals(testValue, MathF.Pow(testValue, 3.0f), MathF.Pow(testValue, ToVar(3.0f))); + AssertEquals(testValue, MathF.Pow(testValue, 4.0f), MathF.Pow(testValue, ToVar(4.0f))); + } + } + + + private float testField1 = MathF.PI; + private float testField2 = MathF.PI; + + [MethodImpl(MethodImplOptions.NoInlining)] + public void TestFieldArg() + { + AssertEquals(testField1, MathF.Pow(testField1, 2.0f), MathF.Pow(testField2, ToVar(2.0f))); + } + + + private int sideeffects = 0; + [MethodImpl(MethodImplOptions.NoInlining)] + private double TestCall1() + { + sideeffects++; + return Math.PI; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private double TestCall2() => Math.PI; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public void TestCallArg1() + { + sideeffects = 0; + AssertEquals(Math.PI, Math.Pow(TestCall1(), 1.0), Math.Pow(TestCall2(), ToVar(1.0))); + if (sideeffects != 1) + returnCode++; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public void TestCallArgN1() + { + sideeffects = 0; + AssertEquals(Math.PI, Math.Pow(TestCall1(), -1.0), Math.Pow(TestCall2(), ToVar(-1.0))); + if (sideeffects != 1) + returnCode++; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public void TestCallArg2() + { + sideeffects = 0; + AssertEquals(Math.PI, Math.Pow(TestCall1(), 2.0), Math.Pow(TestCall2(), ToVar(2.0))); + // make sure it's not optimized into + // `TestCall1() * TestCall1()`: + if (sideeffects != 1) + returnCode++; + } + + + [MethodImpl(MethodImplOptions.NoInlining)] + public void TestRefArgs(ref float x, ref float y) + { + AssertEquals(x, Math.Pow(x, 2.0), Math.Pow(y, ToVar(2.0))); + } + + + // inlining tests + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float InlineablePow0(float x, float y) => MathF.Pow(x, y); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float InlineablePow1(float x, float y) => MathF.Pow(x, y + 1.0f); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float InlineablePow2(float x, float y) => MathF.Pow(x, y + 2.0f); + + [MethodImpl(MethodImplOptions.NoInlining)] + public float NonInlineablePow0(float x, float y) => MathF.Pow(x, y); + [MethodImpl(MethodImplOptions.NoInlining)] + public float NonInlineablePow1(float x, float y) => MathF.Pow(x, y + 1.0f); + [MethodImpl(MethodImplOptions.NoInlining)] + public float NonInlineablePow2(float x, float y) => MathF.Pow(x, y + 2.0f); + + + [MethodImpl(MethodImplOptions.NoInlining)] + public bool Test0(float x) => + BitConverter.SingleToInt32Bits(InlineablePow0(x, 2.0f)) == + BitConverter.SingleToInt32Bits(NonInlineablePow0(x, 2.0f)); + + [MethodImpl(MethodImplOptions.NoInlining)] + public bool Test0_1(float x) => + BitConverter.SingleToInt32Bits(InlineablePow0(x, 1.0f)) == + BitConverter.SingleToInt32Bits(NonInlineablePow0(x, 1.0f)); + + [MethodImpl(MethodImplOptions.NoInlining)] + public bool Test1(float x) => + BitConverter.SingleToInt32Bits(InlineablePow1(x, 1.0f)) == + BitConverter.SingleToInt32Bits(NonInlineablePow1(x, 1.0f)); + + [MethodImpl(MethodImplOptions.NoInlining)] + public bool Test1_1(float x) => + BitConverter.SingleToInt32Bits(InlineablePow1(x, 2.0f)) == + BitConverter.SingleToInt32Bits(NonInlineablePow1(x, 2.0f)); + + [MethodImpl(MethodImplOptions.NoInlining)] + public bool Test2(float x) => + BitConverter.SingleToInt32Bits(InlineablePow2(x, -0.0f)) == + BitConverter.SingleToInt32Bits(NonInlineablePow2(x, -0.0f)); + + [MethodImpl(MethodImplOptions.NoInlining)] + public bool Test2_1(float x) => + BitConverter.SingleToInt32Bits(InlineablePow2(x, 0.0f)) == + BitConverter.SingleToInt32Bits(NonInlineablePow2(x, 0.0f)); + + public void TestWithInlining() + { + foreach (float value in testFltValues) + { + if (!Test0(value) || !Test0_1(value) || !Test1(value) || + !Test1_1(value) || !Test2(value) || !Test2_1(value)) + { + returnCode++; + } + } + } +} diff --git a/src/coreclr/tests/src/JIT/Directed/intrinsic/pow/pow_cns_r.csproj b/src/coreclr/tests/src/JIT/Directed/intrinsic/pow/pow_cns_r.csproj new file mode 100644 index 0000000000000..0c54ca7ded007 --- /dev/null +++ b/src/coreclr/tests/src/JIT/Directed/intrinsic/pow/pow_cns_r.csproj @@ -0,0 +1,12 @@ + + + Exe + + + None + False + + + + + diff --git a/src/coreclr/tests/src/JIT/Directed/intrinsic/pow/pow_cns_ro.csproj b/src/coreclr/tests/src/JIT/Directed/intrinsic/pow/pow_cns_ro.csproj new file mode 100644 index 0000000000000..01969c8348b33 --- /dev/null +++ b/src/coreclr/tests/src/JIT/Directed/intrinsic/pow/pow_cns_ro.csproj @@ -0,0 +1,12 @@ + + + Exe + + + None + True + + + + +