Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize Math.Pow(x, c) where c is 2, 1, -1 or 0 #31978

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
32 changes: 30 additions & 2 deletions src/coreclr/src/jit/morph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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);
Expand Down
209 changes: 209 additions & 0 deletions src/coreclr/tests/src/JIT/Directed/intrinsic/pow/pow_cns.cs
Original file line number Diff line number Diff line change
@@ -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>(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++;
}
}
}
}
12 changes: 12 additions & 0 deletions src/coreclr/tests/src/JIT/Directed/intrinsic/pow/pow_cns_r.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
</PropertyGroup>
<PropertyGroup>
<DebugType>None</DebugType>
<Optimize>False</Optimize>
</PropertyGroup>
<ItemGroup>
<Compile Include="pow_cns.cs" />
</ItemGroup>
</Project>
12 changes: 12 additions & 0 deletions src/coreclr/tests/src/JIT/Directed/intrinsic/pow/pow_cns_ro.csproj
Original file line number Diff line number Diff line change
@@ -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="pow_cns.cs" />
</ItemGroup>
</Project>