diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index b72d32e5ce2ea..3d96acbe79851 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -11909,6 +11909,21 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) return fgMorphCast(tree); case GT_MUL: + if (opts.OptimizationEnabled() && !optValnumCSE_phase && !tree->gtOverflow()) + { + // MUL(NEG(a), C) => MUL(a, NEG(C)) + if (op1->OperIs(GT_NEG) && !op1->gtGetOp1()->IsCnsIntOrI() && op2->IsCnsIntOrI() && + !op2->IsIconHandle()) + { + GenTree* newOp1 = op1->gtGetOp1(); + GenTree* newConst = gtNewIconNode(-op2->AsIntCon()->IconValue(), op2->TypeGet()); + DEBUG_DESTROY_NODE(op1); + DEBUG_DESTROY_NODE(op2); + tree->AsOp()->gtOp1 = newOp1; + tree->AsOp()->gtOp2 = newConst; + return fgMorphSmpOp(tree, mac); + } + } #ifndef TARGET_64BIT if (typ == TYP_LONG) @@ -12056,6 +12071,24 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) return fgMorphSmpOp(tree, mac); } + if (opts.OptimizationEnabled() && !optValnumCSE_phase) + { + // DIV(NEG(a), C) => DIV(a, NEG(C)) + if (op1->OperIs(GT_NEG) && !op1->gtGetOp1()->IsCnsIntOrI() && op2->IsCnsIntOrI() && + !op2->IsIconHandle()) + { + ssize_t op2Value = op2->AsIntCon()->IconValue(); + if (op2Value != 1 && op2Value != -1) // Div must throw exception for int(long).MinValue / -1. + { + tree->AsOp()->gtOp1 = op1->gtGetOp1(); + DEBUG_DESTROY_NODE(op1); + tree->AsOp()->gtOp2 = gtNewIconNode(-op2Value, op2->TypeGet()); + DEBUG_DESTROY_NODE(op2); + return fgMorphSmpOp(tree, mac); + } + } + } + #ifndef TARGET_64BIT if (typ == TYP_LONG) { @@ -13920,6 +13953,35 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) return child; } + // Distribute negation over simple multiplication/division expressions + if (opts.OptimizationEnabled() && !optValnumCSE_phase && tree->OperIs(GT_NEG) && + op1->OperIs(GT_MUL, GT_DIV)) + { + GenTreeOp* mulOrDiv = op1->AsOp(); + GenTree* op1op1 = mulOrDiv->gtGetOp1(); + GenTree* op1op2 = mulOrDiv->gtGetOp2(); + + if (!op1op1->IsCnsIntOrI() && op1op2->IsCnsIntOrI() && !op1op2->IsIconHandle()) + { + // NEG(MUL(a, C)) => MUL(a, -C) + // NEG(DIV(a, C)) => DIV(a, -C), except when C = {-1, 1} + ssize_t constVal = op1op2->AsIntCon()->IconValue(); + if ((mulOrDiv->OperIs(GT_DIV) && (constVal != -1) && (constVal != 1)) || + (mulOrDiv->OperIs(GT_MUL) && !mulOrDiv->gtOverflow())) + { + GenTree* newOp1 = op1op1; // a + GenTree* newOp2 = gtNewIconNode(-constVal, op1op2->TypeGet()); // -C + mulOrDiv->gtOp1 = newOp1; + mulOrDiv->gtOp2 = newOp2; + + DEBUG_DESTROY_NODE(tree); + DEBUG_DESTROY_NODE(op1op2); + + return mulOrDiv; + } + } + } + /* Any constant cases should have been folded earlier */ noway_assert(!op1->OperIsConst() || !opts.OptEnabled(CLFLG_CONSTANTFOLD) || optValnumCSE_phase); break; diff --git a/src/tests/JIT/opt/InstructionCombining/NegMulOrDivToConst.cs b/src/tests/JIT/opt/InstructionCombining/NegMulOrDivToConst.cs new file mode 100644 index 0000000000000..ddb97363678d4 --- /dev/null +++ b/src/tests/JIT/opt/InstructionCombining/NegMulOrDivToConst.cs @@ -0,0 +1,337 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Test the following optimizations: +// -(v * const) => v * -const; +// -v * const => v * -const; +// -(v / const) => v / -const; +// -v / const => v / -const; +// +// Note that C# spec tells that `int.MinValue / -1` result is implementation specific, but +// ecma-335 requires it to throw `System.ArithmeticException` in such case, so we should not +// change 1 and -1 sign. + +using System; +using System.Runtime.CompilerServices; + +namespace TestIntLimits +{ + class Program + { + [MethodImpl(MethodImplOptions.NoInlining)] + static int CheckMulNeg() + { + bool fail = false; + if (MulNeg7(3) != -7 * 3) + { + fail = true; + } + + if (MulNeg0(100) != 0) + { + fail = true; + } + + try + { + MulNegIntMin(2); + } + catch + { + fail = true; + } + + + try + { + CheckedMulNenIntMin(1); + fail = true; + } + catch + { } + + try + { + CheckedMulNenIntMin(0); + + } + catch + { + fail = true; + } + + if (NegMulIntMaxValue(1) != -int.MaxValue) + { + fail = true; + } + if (NegMulIntMaxValue(0) != 0) + { + fail = true; + } + if (NegMulIntMaxValue(-1) != int.MaxValue) + { + fail = true; + } + + try + { + CheckedMulNeg0(int.MinValue); + fail = true; + } + catch + { } + + + if (fail) + { + Console.WriteLine("CheckMulNeg failed"); + return 101; + } + return 100; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int MulNeg7(int a) => -a * 7; + + [MethodImpl(MethodImplOptions.NoInlining)] + static int MulNeg0(int a) => -a * 0; + + [MethodImpl(MethodImplOptions.NoInlining)] + static int MulNegIntMin(int a) => -a * int.MinValue; + + [MethodImpl(MethodImplOptions.NoInlining)] + static int CheckedMulNenIntMin(int a) + { + checked + { + return -a * int.MinValue; + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int NegMulIntMaxValue(int a) => -(a * int.MaxValue); + + [MethodImpl(MethodImplOptions.NoInlining)] + static int CheckedMulNeg0(int a) + { + checked + { + return -a * 0; + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int CheckNegMul() + { + bool fail = false; + if (NegMul7(3) != -7 * 3) + { + fail = true; + } + + try + { + NegMulIntMinValue(100); + } + catch + { + fail = true; + } + + + try + { + CheckedNegMulIntMinValue(1); + fail = true; + } + catch + { } + + try + { + CheckedNegMulIntMinValue(0); + + } + catch + { + fail = true; + } + + if (fail) + { + Console.WriteLine("CheckNegMul failed"); + return 101; + } + return 100; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int NegMul7(int a) => -(a * 7); + + [MethodImpl(MethodImplOptions.NoInlining)] + static int NegMulIntMinValue(int a) => -(a * int.MinValue); + + [MethodImpl(MethodImplOptions.NoInlining)] + static int CheckedNegMulIntMinValue(int a) + { + checked + { + return -(a * int.MinValue); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int CheckDivNeg() + { + bool fail = false; + + if (DivNeg11(110) != -10) + { + fail = true; + } + + if (LongDivNeg1000(100000000000) != -100000000) + { + fail = true; + } + + try + { + DivNegIntMinValue(1); + DivNegIntMinValue(int.MinValue); + LongDivNegLongMinValue(1); + LongDivNegLongMinValue(long.MinValue); + DivNeg1(int.MinValue); + LongDivNeg1(long.MinValue); + } + catch + { + fail = true; + } + + if (fail) + { + Console.WriteLine("CheckDivNeg failed"); + return 101; + } + return 100; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int DivNeg11(int a) => -a / 11; + + [MethodImpl(MethodImplOptions.NoInlining)] + static long LongDivNeg1000(long a) => -a / 1000; + + [MethodImpl(MethodImplOptions.NoInlining)] + static int DivNegIntMinValue(int a) => -a / int.MinValue; + + [MethodImpl(MethodImplOptions.NoInlining)] + static long LongDivNegLongMinValue(long a) => -a / long.MinValue; + + [MethodImpl(MethodImplOptions.NoInlining)] + static int DivNeg1(int a) => -a / 1; + + [MethodImpl(MethodImplOptions.NoInlining)] + static long LongDivNeg1(long a) => -a / 1; + + [MethodImpl(MethodImplOptions.NoInlining)] + static int CheckNegDiv() + { + bool fail = false; + + if (NegDiv11(110) != -10) + { + fail = true; + } + + if (LongNegDiv1000(100000000000) != -100000000) + { + fail = true; + } + + try + { + NegDivIntMinValue(1); + NegDivIntMinValue(int.MinValue); + LongNegDivLongMinValue(1); + LongNegDivLongMinValue(long.MinValue); + NegDiv1(int.MinValue); + LongNegDiv1(long.MinValue); + } + catch + { + fail = true; + } + + try + { + NegDivMinus1(int.MinValue); + fail = true; + } + catch + { } + + try + { + LongNegDivMinus1(long.MinValue); + fail = true; + } + catch + { } + + if (fail) + { + Console.WriteLine("CheckNegDiv failed"); + return 101; + } + return 100; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int NegDiv11(int a) => -(a / 11); + + [MethodImpl(MethodImplOptions.NoInlining)] + static long LongNegDiv1000(long a) => -(a / 1000); + + [MethodImpl(MethodImplOptions.NoInlining)] + static int NegDivIntMinValue(int a) => -(a / int.MinValue); + + [MethodImpl(MethodImplOptions.NoInlining)] + static long LongNegDivLongMinValue(long a) => -(a / long.MinValue); + + [MethodImpl(MethodImplOptions.NoInlining)] + static int NegDiv1(int a) => -(a / 1); + + [MethodImpl(MethodImplOptions.NoInlining)] + static long LongNegDiv1(long a) => -(a / 1); + + [MethodImpl(MethodImplOptions.NoInlining)] + static int NegDivMinus1(int a) => -(a / -1); + + [MethodImpl(MethodImplOptions.NoInlining)] + static long LongNegDivMinus1(long a) => -(a / -1); + + static int Main() + { + if (CheckMulNeg() != 100) + { + return 101; + } + if (CheckNegMul() != 100) + { + return 101; + } + if (CheckDivNeg() != 100) + { + return 101; + } + if (CheckNegDiv() != 100) + { + return 101; + } + + return 100; + } + } +} diff --git a/src/tests/JIT/opt/InstructionCombining/NegMulOrDivToConst.csproj b/src/tests/JIT/opt/InstructionCombining/NegMulOrDivToConst.csproj new file mode 100644 index 0000000000000..c6f2eea5464a4 --- /dev/null +++ b/src/tests/JIT/opt/InstructionCombining/NegMulOrDivToConst.csproj @@ -0,0 +1,12 @@ + + + Exe + + + None + True + + + + +