Skip to content

Commit

Permalink
Optimization for full range checks (#70145) (#70222)
Browse files Browse the repository at this point in the history
  • Loading branch information
SkiFoD authored Jun 19, 2022
1 parent 246d9bb commit 27182d4
Show file tree
Hide file tree
Showing 4 changed files with 359 additions and 0 deletions.
11 changes: 11 additions & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -1243,6 +1243,16 @@ class IntegralRange
assert(lowerBound <= upperBound);
}

SymbolicIntegerValue GetLowerBound()
{
return m_lowerBound;
}

SymbolicIntegerValue GetUpperBound()
{
return m_upperBound;
}

bool Contains(int64_t value) const;

bool Contains(IntegralRange other) const
Expand Down Expand Up @@ -5719,6 +5729,7 @@ class Compiler
GenTree* fgOptimizeCastOnAssignment(GenTreeOp* asg);
GenTree* fgOptimizeEqualityComparisonWithConst(GenTreeOp* cmp);
GenTree* fgOptimizeRelationalComparisonWithConst(GenTreeOp* cmp);
GenTree* fgOptimizeRelationalComparisonWithFullRangeConst(GenTreeOp* cmp);
#ifdef FEATURE_HW_INTRINSICS
GenTree* fgOptimizeHWIntrinsic(GenTreeHWIntrinsic* node);
#endif
Expand Down
132 changes: 132 additions & 0 deletions src/coreclr/jit/morph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11358,6 +11358,21 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac)
assert(op2 == tree->AsOp()->gtGetOp2());
}

if (opts.OptimizationEnabled() && fgGlobalMorph)
{
if (op2->IsIntegralConst() || op1->IsIntegralConst())
{
if (tree->OperIs(GT_GT, GT_LT, GT_LE, GT_GE))
{
tree = fgOptimizeRelationalComparisonWithFullRangeConst(tree->AsOp());
if (tree->OperIs(GT_CNS_INT))
{
return tree;
}
}
}
}

COMPARE:

noway_assert(tree->OperIsCompare());
Expand Down Expand Up @@ -12563,6 +12578,123 @@ GenTree* Compiler::fgOptimizeEqualityComparisonWithConst(GenTreeOp* cmp)
return cmp;
}

//------------------------------------------------------------------------
// fgOptimizeRelationalComparisonWithFullRangeConst: optimizes a comparison operation.
//
// Recognizes "Always false"/"Always true" comparisons against various full range constant operands and morphs
// them into zero/one.
//
// Arguments:
// cmp - the GT_LT/GT_GT tree to morph.
//
// Return Value:
// 1. The unmodified "cmp" tree.
// 2. A CNS_INT node containing zero.
// 3. A CNS_INT node containing one.
// Assumptions:
// The second operand is an integral constant or the first operand is an integral constant.
//
GenTree* Compiler::fgOptimizeRelationalComparisonWithFullRangeConst(GenTreeOp* cmp)
{
if (gtTreeHasSideEffects(cmp, GTF_SIDE_EFFECT))
{
return cmp;
}

int64_t lhsMin;
int64_t lhsMax;
if (cmp->gtGetOp1()->IsIntegralConst())
{
lhsMin = cmp->gtGetOp1()->AsIntConCommon()->IntegralValue();
lhsMax = lhsMin;
}
else
{
IntegralRange lhsRange = IntegralRange::ForNode(cmp->gtGetOp1(), this);
lhsMin = IntegralRange::SymbolicToRealValue(lhsRange.GetLowerBound());
lhsMax = IntegralRange::SymbolicToRealValue(lhsRange.GetUpperBound());
}

int64_t rhsMin;
int64_t rhsMax;
if (cmp->gtGetOp2()->IsIntegralConst())
{
rhsMin = cmp->gtGetOp2()->AsIntConCommon()->IntegralValue();
rhsMax = rhsMin;
}
else
{
IntegralRange rhsRange = IntegralRange::ForNode(cmp->gtGetOp2(), this);
rhsMin = IntegralRange::SymbolicToRealValue(rhsRange.GetLowerBound());
rhsMax = IntegralRange::SymbolicToRealValue(rhsRange.GetUpperBound());
}

genTreeOps op = cmp->gtOper;
if ((op != GT_LT) && (op != GT_LE))
{
op = GenTree::SwapRelop(op);
std::swap(lhsMin, rhsMin);
std::swap(lhsMax, rhsMax);
}

GenTree* ret = nullptr;

if (cmp->IsUnsigned())
{
if ((lhsMin < 0) && (lhsMax >= 0))
{
// [0, (uint64_t)lhsMax] U [(uint64_t)lhsMin, MaxValue]
lhsMin = 0;
lhsMax = -1;
}

if ((rhsMin < 0) && (rhsMax >= 0))
{
// [0, (uint64_t)rhsMax] U [(uint64_t)rhsMin, MaxValue]
rhsMin = 0;
rhsMax = -1;
}

if (((op == GT_LT) && ((uint64_t)lhsMax < (uint64_t)rhsMin)) ||
((op == GT_LE) && ((uint64_t)lhsMax <= (uint64_t)rhsMin)))
{
ret = gtNewOneConNode(TYP_INT);
}
else if (((op == GT_LT) && ((uint64_t)lhsMin >= (uint64_t)rhsMax)) ||
((op == GT_LE) && ((uint64_t)lhsMin > (uint64_t)rhsMax)))
{
ret = gtNewZeroConNode(TYP_INT);
}
}
else
{
// [x0, x1] < [y0, y1] is false if x0 >= y1
// [x0, x1] <= [y0, y1] is false if x0 > y1
if (((op == GT_LT) && (lhsMin >= rhsMax)) || (((op == GT_LE) && (lhsMin > rhsMax))))
{
ret = gtNewZeroConNode(TYP_INT);
}
// [x0, x1] < [y0, y1] is true if x1 < y0
else if ((op == GT_LT) && (lhsMax < rhsMin))
{
ret = gtNewOneConNode(TYP_INT);
}
}

if (ret != nullptr)
{
fgUpdateConstTreeValueNumber(ret);

DEBUG_DESTROY_NODE(cmp);

INDEBUG(ret->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED);

return ret;
}

return cmp;
}

//------------------------------------------------------------------------
// fgOptimizeRelationalComparisonWithConst: optimizes a comparison operation.
//
Expand Down
204 changes: 204 additions & 0 deletions src/tests/JIT/opt/Compares/compares.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

// unit test for the full range comparison optimization

using System;
using System.Runtime.CompilerServices;

public class FullRangeComparisonTest
{
// Class for testing side effects promotion
public class SideEffects
{
public byte B;
}

[MethodImpl(MethodImplOptions.NoInlining)]
public static bool EqualsOrGreaterThan_MinValue_RHSConst_Byte(byte b) => b >= 0;

[MethodImpl(MethodImplOptions.NoInlining)]
public static bool EqualsOrGreaterThan_MinValue_RHSConst_Short(short s) => s >= short.MinValue;

[MethodImpl(MethodImplOptions.NoInlining)]
public static bool EqualsOrGreaterThan_MinValue_RHSConst_Int(int i) => i >= int.MinValue;

[MethodImpl(MethodImplOptions.NoInlining)]
public static bool EqualsOrGreaterThan_MinValue_RHSConst_Long(long l) => l >= long.MinValue;

[MethodImpl(MethodImplOptions.NoInlining)]
public static bool EqualsOrLessThan_MaxValue_RHSConst_Byte(byte b) => b <= byte.MaxValue;

[MethodImpl(MethodImplOptions.NoInlining)]
public static bool EqualsOrLessThan_MaxValue_RHSConst_Short(short s) => s <= short.MaxValue;

[MethodImpl(MethodImplOptions.NoInlining)]
public static bool EqualsOrLessThan_MaxValue_RHSConst_Int(int i) => i <= int.MaxValue;

[MethodImpl(MethodImplOptions.NoInlining)]
public static bool EqualsOrLessThan_MaxValue_RHSConst_Long(long l) => l <= long.MaxValue;

[MethodImpl(MethodImplOptions.NoInlining)]
public static bool EqualsOrLessThan_MaxValue_RHSConst_UShort(ushort us) => us <= ushort.MaxValue;

[MethodImpl(MethodImplOptions.NoInlining)]
public static bool EqualsOrLessThan_MaxValue_RHSConst_UInt(uint ui) => ui <= uint.MaxValue;

[MethodImpl(MethodImplOptions.NoInlining)]
public static bool EqualsOrLessThan_MaxValue_RHSConst_ULong(ulong ul) => ul <= uint.MaxValue;

[MethodImpl(MethodImplOptions.NoInlining)]
public static bool EqualsOrLessThan_MinValue_LHSConst_Byte(byte b) => 0 <= b;
[MethodImpl(MethodImplOptions.NoInlining)]
public static bool EqualsOrGreaterThan_MinValue_LHSConst_Short(short i) => short.MinValue <= i;

[MethodImpl(MethodImplOptions.NoInlining)]
public static bool EqualsOrLessThan_MinValue_LHSConst_Int(int i) => int.MinValue <= i;

[MethodImpl(MethodImplOptions.NoInlining)]
public static bool EqualsOrLessThan_MinValue_LHSConst_Long(long l) => long.MinValue <= l;

[MethodImpl(MethodImplOptions.NoInlining)]
public static bool EqualsOrGreaterThan_MaxValue_LHSConst_Byte(byte b) => byte.MaxValue >= b;

[MethodImpl(MethodImplOptions.NoInlining)]
public static bool EqualsOrGreaterThan_MaxValue_LHSConst_Short(short s) => short.MaxValue >= s;

[MethodImpl(MethodImplOptions.NoInlining)]
public static bool EqualsOrGreaterThan_MaxValue_LHSConst_Int(int i) => int.MaxValue >= i;

[MethodImpl(MethodImplOptions.NoInlining)]
public static bool EqualsOrGreaterThan_MaxValue_LHSConst_Long(long l) => long.MaxValue >= l;

[MethodImpl(MethodImplOptions.NoInlining)]
public static bool EqualsOrGreaterThan_MaxValue_LHSConst_SideEffects(SideEffects c) => c.B <= 255;
public static int Main()
{
// Optimize comparison with full range values
// RHS Const Optimization
if (!EqualsOrGreaterThan_MinValue_RHSConst_Byte(10))
{
Console.WriteLine("FullRangeComparisonTest:EqualsOrGreaterThan_MinValue_RHSConst_Byte(10) failed");
return 101;
}

if (!EqualsOrGreaterThan_MinValue_RHSConst_Short(-10))
{
Console.WriteLine("FullRangeComparisonTest:EqualsOrGreaterThan_MinValue_RHSConst_Short(10) failed");
return 101;
}

if (!EqualsOrGreaterThan_MinValue_RHSConst_Int(-10))
{
Console.WriteLine("FullRangeComparisonTest:EqualsOrGreaterThan_MinValue_RHSConst_Int(10) failed");
return 101;
}

if (!EqualsOrGreaterThan_MinValue_RHSConst_Long(-10))
{
Console.WriteLine("FullRangeComparisonTest:EqualsOrGreaterThan_MinValue_RHSConst_Long(10) failed");
return 101;
}

if (!EqualsOrLessThan_MaxValue_RHSConst_Byte(10))
{
Console.WriteLine("FullRangeComparisonTest:EqualsOrLessThan_MaxValue_RHSConst_Byte(10) failed");
return 101;
}

if (!EqualsOrLessThan_MaxValue_RHSConst_Short(10))
{
Console.WriteLine("FullRangeComparisonTest:EqualsOrLessThan_MaxValue_RHSConst_Short(10) failed");
return 101;
}

if (!EqualsOrLessThan_MaxValue_RHSConst_Int(10))
{
Console.WriteLine("FullRangeComparisonTest:EqualsOrLessThan_MaxValue_RHSConst_Int(10) failed");
return 101;
}

if (!EqualsOrLessThan_MaxValue_RHSConst_Long(10))
{
Console.WriteLine("FullRangeComparisonTest:EqualsOrLessThan_MaxValue_RHSConst_Long(10) failed");
return 101;
}

// LHS Const Optimization
if (!EqualsOrLessThan_MinValue_LHSConst_Byte(10))
{
Console.WriteLine("FullRangeComparisonTest:EqualsOrLessThan_MinValue_LHSConst_Byte(10) failed");
return 101;
}

if (!EqualsOrLessThan_MinValue_LHSConst_Int(-10))
{
Console.WriteLine("FullRangeComparisonTest:EqualsOrLessThan_MinValue_LHSConst_Int(10) failed");
return 101;
}

if (!EqualsOrLessThan_MinValue_LHSConst_Long(-10))
{
Console.WriteLine("FullRangeComparisonTest:EqualsOrLessThan_MinValue_LHSConst_Long(10) failed");
return 101;
}

if (!EqualsOrGreaterThan_MaxValue_LHSConst_Byte(10))
{
Console.WriteLine("FullRangeComparisonTest:EqualsOrGreaterThan_MaxValue_LHSConst_Byte(10) failed");
return 101;
}

if (!EqualsOrGreaterThan_MaxValue_LHSConst_Int(10))
{
Console.WriteLine("FullRangeComparisonTest:EqualsOrGreaterThan_MaxValue_LHSConst_Int(10) failed");
return 101;
}

if (!EqualsOrGreaterThan_MaxValue_LHSConst_Long(10))
{
Console.WriteLine("FullRangeComparisonTest:EqualsOrGreaterThan_MaxValue_LHSConst_Long(10) failed");
return 101;
}


// Unsigned values
if (!EqualsOrLessThan_MaxValue_RHSConst_UShort(10))
{
Console.WriteLine("FullRangeComparisonTest:EqualsOrLessThan_MaxValue_RHSConst_UShort(10) failed");
return 101;
}

if (!EqualsOrLessThan_MaxValue_RHSConst_UInt(10))
{
Console.WriteLine("FullRangeComparisonTest:EqualsOrLessThan_MaxValue_RHSConst_UInt(10) failed");
return 101;
}

if (!EqualsOrLessThan_MaxValue_RHSConst_ULong(10))
{
Console.WriteLine("FullRangeComparisonTest:EqualsOrLessThan_MaxValue_RHSConst_ULong(10) failed");
return 101;
}

// Side effects persist
try
{
EqualsOrGreaterThan_MaxValue_LHSConst_SideEffects(null);
Console.WriteLine("FullRangeComparisonTest:EqualsOrGreaterThan_MaxValue_LHSConst_SideEffects(null) failed");
return 101;
}
catch (NullReferenceException ex)
{

}
catch (Exception ex)
{
Console.WriteLine("FullRangeComparisonTest:EqualsOrGreaterThan_MaxValue_LHSConst_SideEffects(null) failed");
return 101;
}

Console.WriteLine("PASSED");
return 100;
}
}
12 changes: 12 additions & 0 deletions src/tests/JIT/opt/Compares/compares.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>PdbOnly</DebugType>
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildProjectName).cs" />
</ItemGroup>
</Project>

0 comments on commit 27182d4

Please sign in to comment.