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

Optimization for full range checks (#70145) #70222

Merged
merged 13 commits into from
Jun 19, 2022
11 changes: 11 additions & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -1242,6 +1242,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 @@ -5690,6 +5700,7 @@ class Compiler
GenTree* fgOptimizeCast(GenTreeCast* cast);
GenTree* fgOptimizeEqualityComparisonWithConst(GenTreeOp* cmp);
GenTree* fgOptimizeRelationalComparisonWithConst(GenTreeOp* cmp);
GenTree* fgOptimizeRelationalComparisonWithFullRangeConst(GenTreeOp* cmp);
#ifdef FEATURE_HW_INTRINSICS
GenTree* fgOptimizeHWIntrinsic(GenTreeHWIntrinsic* node);
#endif
Expand Down
99 changes: 99 additions & 0 deletions src/coreclr/jit/morph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11406,6 +11406,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, GT_COMMA))
{
return tree;
}
}
}
}

COMPARE:

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

//------------------------------------------------------------------------
// fgOptimizeRelationalComparisonWithFullRangeConst: optimizes a comparison operation.
//
// Recognizes "Always false" comparisons against various full range constant operands and morphs
// them into zero.
//
// 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 GT_COMMA node containing side effects along with a CNS_INT node containing zero
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it needs to be updated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto for the "Always false" in the summary above.

// Assumptions:
// The second operand is an integral constant or the first operand is an integral constant.
//
GenTree* Compiler::fgOptimizeRelationalComparisonWithFullRangeConst(GenTreeOp* 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);
}

int64_t lefOpValue = lhsMin;
int64_t rightOpValue = rhsMax;

if (cmp->IsUnsigned() && lefOpValue == -1 && lhsMax == -1)
{
rightOpValue = -1;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's an example this catches?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the part of code, but I haven't come up with an idea how to get ride of the magic number (-1) yet. The idea here is that ulong and uint use this const.
For example: bool Test_M27(ulong v) => v <= ulong.MaxValue generates this tree:

     *  RETURN    int
 \--*  EQ        int
    +--*  GT        int
    |  +--*  LCL_VAR   long   V00 arg0
    |  \--*  CNS_INT   long   -1
    \--*  CNS_INT   int    0

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For unsigned comparisons a signed interval that spans 0 represents two distinct intervals, i.e. for [x, y]:

  1. if y < 0: represents [(unsigned)x, (unsigned)y]
  2. if x >= 0: represents [x, y]
  3. else: represents [0, y] U [(unsigned)x, MaxValue]

So the "generalized" code here would need to check these conditions. I agree this is a bit tricky, so it is fine with me to special case this, although it would be nice to find a way to shape the code such that we avoid the "normal" check below in the special case.

Also, I think the lefOpValue and rightOpValue are a bit confusing, I would just try to stick with lhsMin/Max and rhsMin/Max.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tricky part here is that when rhs is ulong then
rhsMin = IntegralRange::SymbolicToRealValue(rhsRange.GetLowerBound()); returns -9223372036854775808 instead of 0.
So what if we would use something like this:

 else if (cmp->IsUnsigned() && (op == GT_LT) && rhsMin < 0 && !isLhsConst)
    {
        ret = gtNewZeroConNode(TYP_INT);
    }
    else if (cmp->IsUnsigned() && (op == GT_LT) && lhsMin < 0 && isLhsConst)
    {
        ret = gtNewZeroConNode(TYP_INT);
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For unsigned comparisons a signed interval that spans 0 represents two distinct intervals, i.e. for [x, y]:

  1. if y < 0: represents [(unsigned)x, (unsigned)y]
  2. if x >= 0: represents [x, y]
  3. else: represents [0, y] U [(unsigned)x, MaxValue]

So the "generalized" code here would need to check these conditions. I agree this is a bit tricky, so it is fine with me to special case this, although it would be nice to find a way to shape the code such that we avoid the "normal" check below in the special case.

Could you please explain this idea in more details, I'm not sure I can understand how to apply this.

Copy link
Member

@jakobbotsch jakobbotsch Jun 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you would do something like:

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;
  }
}

int foldValue;
if (cmp->IsUnsigned())
{
  if ((op == GT_LT && ((uint64_t)lhsMax < (uint64_t)rhsMin) ||
      (op == GT_LE && ((uint64_t)lhsMax <= (uint64_t)rhsMin))
  {
    foldValue = 1;
  }
  else if ((op == GT_LT && ((uint64_t)lhsMin >= (uint64_t)rhsMax) ||
           (op == GT_LE && ((uint64_t)lhsMin > (uint64_t)rhsMax))
  {
    foldValue = 0;
  }
  else
  {
    return;
  }
}
else
{
  if ((op == GT_LT && (lhsMax < rhsMin) ||
      (op == GT_LE && (lhsMax <= rhsMin))
  {
    foldValue = 1;
  }
  else if ((op == GT_LT && (lhsMin >= rhsMax) ||
           (op == GT_LE && (lhsMin > rhsMax))
  {
    foldValue = 0;
  }
  else
  {
    return;
  }
}

// fold to foldValue here

But, it's hard to get all the cases right :-) I would probably double check with something like https://github.com/jakobbotsch/Fuzzlyn. I will definitely run that on this PR before merging.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example, running Fuzzlyn on your PR in the current shape quickly finds examples. I did:

> .\Fuzzlyn.exe --host C:\dev\dotnet\runtime2\artifacts\tests\coreclr\windows.x64.Checked\Tests\Core_Root\corerun.exe --num-programs 10000000 --parallelism 8
Found example with seed 10360326530109389226

> .\Fuzzlyn.exe --host C:\dev\dotnet\runtime2\artifacts\tests\coreclr\windows.x64.Checked\Tests\Core_Root\corerun.exe --reduce --seed 10360326530109389226
Simplifying Coarsely. Total elapsed: 00:00:10. Method 51/51.
Simplifying Statements. Total elapsed: 00:00:15. Iter: 107/107
Simplifying Expressions. Total elapsed: 00:00:16. Iter: 478/478
Simplifying Members. Total elapsed: 00:00:19. Iter: 9/9
Simplifying Statements. Total elapsed: 00:00:19. Iter: 12/12
Simplifying Expressions. Total elapsed: 00:00:19. Iter: 45/45
Simplifying Members. Total elapsed: 00:00:20. Iter: 7/7
Simplifying Statements. Total elapsed: 00:00:20. Iter: 8/8
Simplifying Expressions. Total elapsed: 00:00:20. Iter: 35/35
Simplifying Members. Total elapsed: 00:00:20. Iter: 6/6
Simplifying Statements. Total elapsed: 00:00:20. Iter: 8/8
Simplifying Expressions. Total elapsed: 00:00:20. Iter: 34/34
Simplifying Members. Total elapsed: 00:00:20. Iter: 6/6

which outputs:

// Generated by Fuzzlyn v1.5 on 2022-06-14 19:20:16
// Run on X64 Windows
// Seed: 10360326530109389226
// Reduced from 64.1 KiB to 0.5 KiB in 00:00:23
// Debug:
// Release: Outputs 0
public class Program
{
    public static IRuntime s_rt;
    public static uint s_4;
    public static void Main()
    {
        s_rt = new Runtime();
        bool vr1 = M1(0);
    }

    public static bool M1(short arg0)
    {
        if ((12729537629719743250UL < (uint)arg0))
        {
            s_rt.WriteLine(s_4);
        }

        return true;
    }
}

public interface IRuntime
{
    void WriteLine<T>(T value);
}

public class Runtime : IRuntime
{
    public void WriteLine<T>(T value) => System.Console.WriteLine(value);
}

This program is incorrect with the current PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like an amazing tool. I played with it a little bit and got something like this:

// Generated by Fuzzlyn v1.5 on 2022-06-15 09:15:55
// Run on X64 Windows
// Seed: 6538447736931409473
// Reduced from 109.8 KiB to 0.2 KiB in 00:00:53
// Debug: Outputs True
// Release: Outputs False
public class Program
{
    public static long s_33 = 1;
    public static bool s_50;
    public static void Main()
    {
        uint vr0 = (uint)(-s_33);
        s_50 = 4038847739U < vr0;
        System.Console.WriteLine(s_50);
    }
}

Does it mean that if I cut out the code (from main) and build it in Debug then it returns True and if I build it in Release then it returns False?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's what it means (and also tiered compilation has to be disabled).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, your treatment of unsigned comparisons is still wrong, you cannot use the signed comparisons for the interval checks in that case. It is probably the reason for this problem. I would suggest you shape the code somewhat like the example I posted earlier.


if ((op == GT_LT && lefOpValue == rightOpValue) || (op == GT_LE && lefOpValue > rightOpValue))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the first check be generalized to lhsMin >= rhsMax?
Also, I think this still needs special handling for unsigned comparisons. The easiest is probably to bail for negative intervals except for your special case above. For positive intervals you can use the signed comparisons.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the first check be generalized to lhsMin >= rhsMax?

It may work with GT_LT, but what about GT_LE?
If it is GT_LE then lhsMin>rhsMax and lhsMin>=rhsMax have different results. Right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, by "first check" I meant the GT_LT part.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then it would look like if ((op == GT_LE && lefOpValue > rightOpValue) || lhsMin>=rhsMax)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I just meant
((op == GT_LT) && (lhsMin >= rhsMax)) || ((op == GT_LE) && (lhsMin > rhsMax)))

{
GenTree* ret = gtNewZeroConNode(TYP_INT);

if (gtTreeHasSideEffects(cmp, GTF_SIDE_EFFECT))
{
return cmp;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to presave the side effects gave me many regressions, so I decided to cut it out for now.


ret->SetVNsFromNode(cmp);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ret->SetVNsFromNode(cmp);
fgUpdateConstTreeValueNumber(ret);

(Given that we folded, this is more precise)


DEBUG_DESTROY_NODE(cmp);

INDEBUG(ret->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED);

return ret;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't seem right that this can only fold things into false. What about when the comparison is always true?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't think of a case when the comparison is always true. There are 7 typical trees which I'm testing on (value types may vary but the trees are always generalized to the 7 cases):

  1. When const is on the right and MinValue
    example v >= int.MinValue:
    *  RETURN    int
    \--*  EQ        int
       +--*  LT        int
       |  +--*  LCL_VAR   int    V00 arg0
       |  \--*  CNS_INT   int    -0x80000000
       \--*  CNS_INT   int    0
  1. When const is on the left and MinValue
    example int.MinValue <= v
   *  RETURN    int
   \--*  EQ        int
      +--*  GT        int
      |  +--*  CNS_INT   int    -0x80000000
      |  \--*  LCL_VAR   int    V00 arg0

      \--*  CNS_INT   int    0
  1. When const is on the right and MaxValue
    example v <= int.MaxValue
 *  RETURN    int
 \--*  EQ        int
    +--*  GT        int
    |  +--*  LCL_VAR   int    V00 arg0
    |  \--*  CNS_INT   int    0x7FFFFFFF
    \--*  CNS_INT   int    0
  1. When const is on the left and MaxValue
    example int.MaxValue >= v
 *  RETURN    int
 \--*  EQ        int
    +--*  LT        int
    |  +--*  CNS_INT   int    0x7FFFFFFF
    |  \--*  LCL_VAR   int    V00 arg0
    \--*  CNS_INT   int    0
  1. When const is ulong/uint and value is MinValue
    example v >= ulong.MinValue
    example ulong.MinValue <= v
*  RETURN    int
\--*  CNS_INT   int    1
  1. When const is on the right and value type is ulong/uint and value is MaxValue
    example v <= ulong.MaxValue;
 \--*  EQ        int
    +--*  GT        int
    |  +--*  LCL_VAR   long   V00 arg0
    |  \--*  CNS_INT   long   -1
    \--*  CNS_INT   int    0
  1. When const is on the left and value type is ulong/uint and value is MaxValue
    example ulong.MaxValue >= v
*  RETURN    int
\--*  EQ        int
   +--*  LT        int
   |  +--*  CNS_INT   long   -1
   |  \--*  LCL_VAR   long   V00 arg0
   \--*  CNS_INT   int    0

Copy link
Contributor Author

@SkiFoD SkiFoD Jun 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So for example:
v >= int.MinValue generates a tree that is equal to return (v < int.MinValue) == false
int.MinValue <= v generates a tree that is equal to return (int.MinValue > v) == false
However in case of ulong/uint:
v <= ulong.MaxValue generates a tree that is equal to return (v > -1) == false ulong.MaxValue >= vgenerates a tree that is equal toreturn (-1 < v) == false
These trees look odd to me because (v > -1) is going to be always true.
Correct me please if I'm wrong.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One example would be:
bool Foo(sbyte i) => i > -129;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We generally do not see x >= y much here because there is no such IL instruction, so that's why the more "common" pattern i >= sbyte.MinValue is reversed by Roslyn. But we can still see such IR if we introduce it ourselves, so I think it makes sense to handle it (and also the above case).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know how to generate the always true condition but with GT_LE? I'm considering should we be bothered with such a case at all.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that you already have the intervals I think the actual check itself is so simple that there is no reason not to add it, it should not be more than a couple of lines.
[x0, x1] <= [y0, y1] is always true if x1 <= y0.

Copy link
Contributor Author

@SkiFoD SkiFoD Jun 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to apply this condition, which considering only LT for simplicity and it gave me 1700+ improvements during the spmi asmdiff run, which is suspicious :)

if (((op == GT_LT) && (lhsMin >= rhsMax)) || (((op == GT_LE) && (lhsMin > rhsMax))))
    {
        ret = gtNewZeroConNode(TYP_INT);
    }
    else if ((op == GT_LT) && rhsMax > lhsMax)
    {
        ret = gtNewOneConNode(TYP_INT);
    }

Then I tried to check which Op is const to make the condition more strict:

    //When lhs is constant
    *  RETURN    int
    \--*  LT       int
         +--*  CNS_INT   int    -129
          \--*  LCL_VAR   byte   V00 arg0
    else if ((op == GT_LT) && rhsMin > lhsMin && lhsMin == lhsMax)
    {
        ret = gtNewOneConNode(TYP_INT);
    }
    // When rhs is constant
      For cases like this:
      *  RETURN    int
      \--*  LT        int
          +--*  LCL_VAR   byte   V00 arg0
           \--*  CNS_INT   int    129

    else if ((op == GT_LT) && rhsMax > lhsMax && rhsMin == rhsMax)
    {
        ret = gtNewOneConNode(TYP_INT);
    }

It works fine and there are not so many improvements. What do you think of this?

Copy link
Member

@jakobbotsch jakobbotsch Jun 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[x0, x1] < [y0, y1] is true if x1 < y0. So I think
else if ((op == GT_LT) && rhsMax > lhsMax)
should be
else if ((op == GT_LT) && lhsMax < rhsMin).


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>