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

JIT: Range Checks for "(uint)i > cns" pattern #62864

Merged
merged 34 commits into from
Feb 25, 2022
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
f5bc205
tmp
EgorBo Dec 2, 2021
2757d64
Merge branch 'main' of https://github.com/dotnet/runtime into span-bo…
EgorBo Dec 14, 2021
1556eb8
Handle bound checks for ((uint)index cmp CNS) pattern
EgorBo Dec 15, 2021
be074e4
Fix typo
EgorBo Dec 15, 2021
1b14746
Fix build error
EgorBo Dec 15, 2021
31ab6d5
Fix 32bit platforms
EgorBo Dec 16, 2021
1b06196
Fix 32bit again
EgorBo Dec 16, 2021
81b5232
Update morph.cpp
EgorBo Dec 16, 2021
b13b852
Update morph.cpp
EgorBo Dec 16, 2021
fc5ceb2
Clean up
EgorBo Dec 17, 2021
1e14f8e
Clean up
EgorBo Dec 17, 2021
53a6405
Move to fgOptimizeRelationalComparisonWithCasts
EgorBo Dec 17, 2021
747339c
fix 32bit again
EgorBo Dec 17, 2021
f4b1ffa
fix SetUnsigned
EgorBo Dec 17, 2021
58f4f68
Clean up
EgorBo Dec 18, 2021
b9e68b0
Update morph.cpp
EgorBo Dec 18, 2021
929e53b
Merge branch 'main' of https://github.com/dotnet/runtime into bound-c…
EgorBo Dec 19, 2021
f4e82ce
test
EgorBo Dec 19, 2021
abc7d54
Introduce O1K_CONSTANT_LOOP_BND_UN
EgorBo Dec 19, 2021
3bf8534
Merge branch 'main' of https://github.com/dotnet/runtime into bound-c…
EgorBo Dec 19, 2021
aecf6a4
Update assertionprop.cpp
EgorBo Dec 20, 2021
96376f6
Merge branch 'bound-checks-constant-len' of https://github.com/EgorBo…
EgorBo Jan 15, 2022
1c9f406
Merge branch 'main' of https://github.com/dotnet/runtime into bound-c…
EgorBo Jan 27, 2022
ff58212
resolve conflicts
EgorBo Jan 27, 2022
61b1fa1
fix newline
EgorBo Jan 27, 2022
8ba164d
Merge branch 'main' of https://github.com/dotnet/runtime into bound-c…
EgorBo Jan 27, 2022
e4fff61
Merge branch 'main' of https://github.com/dotnet/runtime into bound-c…
EgorBo Feb 15, 2022
f2aa51a
Merge branch 'main' of https://github.com/dotnet/runtime into bound-c…
EgorBo Feb 18, 2022
58cec64
Address feedback
EgorBo Feb 18, 2022
10d9a0d
Apply suggestions from code review
EgorBo Feb 24, 2022
20505fd
Merge branch 'main' of https://github.com/dotnet/runtime into bound-c…
EgorBo Feb 24, 2022
a643eb6
Apply suggestions
EgorBo Feb 24, 2022
ffadcd7
use bashtoconst
EgorBo Feb 24, 2022
d2518c2
use bashtoconst
EgorBo Feb 25, 2022
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
41 changes: 27 additions & 14 deletions src/coreclr/jit/assertionprop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1007,6 +1007,11 @@ void Compiler::optPrintAssertion(AssertionDsc* curAssertion, AssertionIndex asse
printf("Const_Loop_Bnd");
vnStore->vnDump(this, curAssertion->op1.vn);
}
else if (curAssertion->op1.kind == O1K_CONSTANT_LOOP_BND_UN)
{
printf("Const_Loop_Bnd_Un");
vnStore->vnDump(this, curAssertion->op1.vn);
}
else if (curAssertion->op1.kind == O1K_VALUE_NUMBER)
{
printf("Value_Number");
Expand Down Expand Up @@ -1081,17 +1086,10 @@ void Compiler::optPrintAssertion(AssertionDsc* curAssertion, AssertionIndex asse
printf("MT(%08X)", dspPtr(curAssertion->op2.u1.iconVal));
assert(curAssertion->op2.u1.iconFlags != GTF_EMPTY);
}
else if (curAssertion->op1.kind == O1K_BOUND_OPER_BND)
{
assert(!optLocalAssertionProp);
vnStore->vnDump(this, curAssertion->op2.vn);
}
else if (curAssertion->op1.kind == O1K_BOUND_LOOP_BND)
{
assert(!optLocalAssertionProp);
vnStore->vnDump(this, curAssertion->op2.vn);
}
else if (curAssertion->op1.kind == O1K_CONSTANT_LOOP_BND)
else if ((curAssertion->op1.kind == O1K_BOUND_OPER_BND) ||
(curAssertion->op1.kind == O1K_BOUND_LOOP_BND) ||
(curAssertion->op1.kind == O1K_CONSTANT_LOOP_BND) ||
(curAssertion->op1.kind == O1K_CONSTANT_LOOP_BND_UN))
{
assert(!optLocalAssertionProp);
vnStore->vnDump(this, curAssertion->op2.vn);
Expand Down Expand Up @@ -1982,6 +1980,7 @@ void Compiler::optDebugCheckAssertion(AssertionDsc* assertion)
case O1K_BOUND_OPER_BND:
case O1K_BOUND_LOOP_BND:
case O1K_CONSTANT_LOOP_BND:
case O1K_CONSTANT_LOOP_BND_UN:
case O1K_VALUE_NUMBER:
assert(!optLocalAssertionProp);
break;
Expand Down Expand Up @@ -2079,8 +2078,9 @@ void Compiler::optCreateComplementaryAssertion(AssertionIndex assertionIndex,
}

AssertionDsc& candidateAssertion = *optGetAssertion(assertionIndex);
if (candidateAssertion.op1.kind == O1K_BOUND_OPER_BND || candidateAssertion.op1.kind == O1K_BOUND_LOOP_BND ||
candidateAssertion.op1.kind == O1K_CONSTANT_LOOP_BND)
if ((candidateAssertion.op1.kind == O1K_BOUND_OPER_BND) || (candidateAssertion.op1.kind == O1K_BOUND_LOOP_BND) ||
(candidateAssertion.op1.kind == O1K_CONSTANT_LOOP_BND) ||
(candidateAssertion.op1.kind == O1K_CONSTANT_LOOP_BND_UN))
{
AssertionDsc dsc = candidateAssertion;
dsc.assertionKind = dsc.assertionKind == OAK_EQUAL ? OAK_NOT_EQUAL : OAK_EQUAL;
Expand Down Expand Up @@ -2341,7 +2341,20 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree)
optCreateComplementaryAssertion(index, nullptr, nullptr);
return index;
}

else if (vnStore->IsVNConstantBoundUnsigned(relopVN))
{
AssertionDsc dsc;
dsc.assertionKind = OAK_NOT_EQUAL;
dsc.op1.kind = O1K_CONSTANT_LOOP_BND_UN;
dsc.op1.vn = relopVN;
dsc.op2.kind = O2K_CONST_INT;
dsc.op2.vn = vnStore->VNZeroForType(TYP_INT);
dsc.op2.u1.iconVal = 0;
dsc.op2.u1.iconFlags = GTF_EMPTY;
AssertionIndex index = optAddAssertion(&dsc);
optCreateComplementaryAssertion(index, nullptr, nullptr);
return index;
}
return NO_ASSERTION_INDEX;
}

Expand Down
9 changes: 8 additions & 1 deletion src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -6422,6 +6422,7 @@ class Compiler
GenTree* fgOptimizeEqualityComparisonWithConst(GenTreeOp* cmp);
GenTree* fgOptimizeRelationalComparisonWithConst(GenTreeOp* cmp);
GenTree* fgOptimizeCommutativeArithmetic(GenTreeOp* tree);
GenTree* fgOptimizeRelationalComparisonWithCasts(GenTreeOp* cmp);
GenTree* fgOptimizeAddition(GenTreeOp* add);
GenTree* fgOptimizeMultiply(GenTreeOp* mul);
GenTree* fgOptimizeBitwiseAnd(GenTreeOp* andOp);
Expand Down Expand Up @@ -7622,6 +7623,7 @@ class Compiler
O1K_BOUND_OPER_BND,
O1K_BOUND_LOOP_BND,
O1K_CONSTANT_LOOP_BND,
O1K_CONSTANT_LOOP_BND_UN,
O1K_EXACT_TYPE,
O1K_SUBTYPE,
O1K_VALUE_NUMBER,
Expand Down Expand Up @@ -7698,7 +7700,12 @@ class Compiler
bool IsConstantBound()
{
return ((assertionKind == OAK_EQUAL || assertionKind == OAK_NOT_EQUAL) &&
op1.kind == O1K_CONSTANT_LOOP_BND);
(op1.kind == O1K_CONSTANT_LOOP_BND));
}
bool IsConstantBoundUnsigned()
{
return ((assertionKind == OAK_EQUAL || assertionKind == OAK_NOT_EQUAL) &&
(op1.kind == O1K_CONSTANT_LOOP_BND_UN));
}
bool IsBoundsCheckNoThrow()
{
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/gentree.h
Original file line number Diff line number Diff line change
Expand Up @@ -2045,7 +2045,7 @@ struct GenTree

void SetUnsigned()
{
assert(OperIs(GT_ADD, GT_SUB, GT_CAST) || OperIsMul());
assert(OperIs(GT_ADD, GT_SUB, GT_CAST, GT_LE, GT_LT, GT_GT, GT_GE) || OperIsMul());
gtFlags |= GTF_UNSIGNED;
}

Expand Down
138 changes: 138 additions & 0 deletions src/coreclr/jit/morph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13886,6 +13886,144 @@ GenTree* Compiler::fgOptimizeBitwiseAnd(GenTreeOp* andOp)
return nullptr;
}

//------------------------------------------------------------------------
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
// fgOptimizeRelationalComparisonWithCasts: optimizes a comparison operation.
// Recognizes comparisons against various cast operands and tries to remove
// them. E.g.:
//
// * GE int
// +--* CAST long <- ulong <- uint
// | \--* X int
// \--* CNS_INT long
//
// to:
//
// * GE_un int
// +--* X int
// \--* CNS_INT int
//
// same for:
//
// * GE int
// +--* CAST long <- ulong <- uint
// | \--* X int
// \--* CAST long <- [u]long <- int
// \--* ARR_LEN int
//
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
// Arguments:
// tree - the GT_LE/GT_LT/GT_GE/GT_GT tree to morph.
//
// Return Value:
// Returns the same tree where operands might have narrower types
//
GenTree* Compiler::fgOptimizeRelationalComparisonWithCasts(GenTreeOp* tree)
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
{
assert(tree->OperIs(GT_LE, GT_LT, GT_GE, GT_GT));
EgorBo marked this conversation as resolved.
Show resolved Hide resolved

GenTree* castedOp = tree->gtGetOp1();
GenTree* knownPositiveOp = tree->gtGetOp2();
EgorBo marked this conversation as resolved.
Show resolved Hide resolved

// Caller is expected to call this function only if we have CAST nodes
assert(castedOp->OperIs(GT_CAST) || knownPositiveOp->OperIs(GT_CAST));

if (optValnumCSE_phase || gtIsActiveCSE_Candidate(tree) || gtIsActiveCSE_Candidate(castedOp) ||
gtIsActiveCSE_Candidate(knownPositiveOp))
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
{
// We're going to modify all of them
return tree;
}

if (!castedOp->TypeIs(TYP_LONG))
{
// We can extend this logic to handle small types as well, but currently it's done mostly to
// assist range check elimination
return tree;
}

bool knownPositiveIsOp2;
if (knownPositiveOp->IsIntegralConst() ||
((knownPositiveOp->OperIs(GT_CAST) && knownPositiveOp->gtGetOp1()->OperIs(GT_ARR_LENGTH))))
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
{
// op2 is either a LONG constant or (T)ARR_LENGTH
knownPositiveIsOp2 = true;
}
else
{
// op1 is either a LONG constant (yes, it's pretty normal for relops)
// or (T)ARR_LENGTH
castedOp = tree->gtGetOp2();
knownPositiveOp = tree->gtGetOp1();
knownPositiveIsOp2 = false;
}

if (castedOp->OperIs(GT_CAST) && varTypeIsLong(castedOp->CastToType()) && castedOp->gtGetOp1()->TypeIs(TYP_INT) &&
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
castedOp->IsUnsigned() && !castedOp->gtOverflow())
{
bool knownPositiveFitsIntoU32 = false;
if (knownPositiveOp->IsIntegralConst() &&
((UINT64)knownPositiveOp->AsIntConCommon()->IntegralValue() <= UINT_MAX))
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
{
// BTW, we can fold the whole condition if op2 doesn't fit into UINT_MAX.
knownPositiveFitsIntoU32 = true;
}
else if (knownPositiveOp->OperIs(GT_CAST) && varTypeIsLong(knownPositiveOp->CastToType()) &&
knownPositiveOp->gtGetOp1()->OperIs(GT_ARR_LENGTH))
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
{
knownPositiveFitsIntoU32 = true;
// TODO: we need to recognize Span._length here as well
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
}

if (!knownPositiveFitsIntoU32)
{
return tree;
}

JITDUMP("Removing redundant cast(s) for:\n")
DISPTREE(tree)
JITDUMP("\n\nto:\n\n")

tree->SetUnsigned();

// Drop cast from castedOp
if (knownPositiveIsOp2)
{
tree->gtOp1 = castedOp->gtGetOp1();
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
}
else
{
tree->gtOp2 = castedOp->gtGetOp1();
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
}
DEBUG_DESTROY_NODE(castedOp);

if (knownPositiveOp->OperIs(GT_CAST))
{
// Drop cast from knownPositiveOp too
if (knownPositiveIsOp2)
{
tree->gtOp2 = knownPositiveOp->gtGetOp1();
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
}
else
{
tree->gtOp1 = knownPositiveOp->gtGetOp1();
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
}
DEBUG_DESTROY_NODE(knownPositiveOp);
}
else
{
// Change type for constant from LONG to INT
knownPositiveOp->ChangeType(TYP_INT);
#ifndef TARGET_64BIT
assert(knownPositiveOp->OperIs(GT_CNS_LNG));
knownPositiveOp->ChangeOperUnchecked(GT_CNS_INT);
#endif
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
fgUpdateConstTreeValueNumber(knownPositiveOp);
}
DISPTREE(tree)
JITDUMP("\n")
}
return tree;
}

//------------------------------------------------------------------------
// fgPropagateCommaThrow: propagate a "comma throw" up the tree.
//
Expand Down
13 changes: 10 additions & 3 deletions src/coreclr/jit/rangecheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,7 @@ void RangeCheck::MergeEdgeAssertions(ValueNum normalLclVN, ASSERT_VALARG_TP asse
Limit limit(Limit::keUndef);
genTreeOps cmpOper = GT_NONE;
bool isConstantAssertion = false;
bool isUnsigned = false;

// Current assertion is of the form (i < len - cns) != 0
if (curAssertion->IsCheckedBoundArithBound())
Expand Down Expand Up @@ -658,7 +659,7 @@ void RangeCheck::MergeEdgeAssertions(ValueNum normalLclVN, ASSERT_VALARG_TP asse
}
}
// Current assertion is of the form (i < 100) != 0
else if (curAssertion->IsConstantBound())
else if (curAssertion->IsConstantBound() || curAssertion->IsConstantBoundUnsigned())
{
ValueNumStore::ConstantBoundInfo info;

Expand All @@ -671,8 +672,9 @@ void RangeCheck::MergeEdgeAssertions(ValueNum normalLclVN, ASSERT_VALARG_TP asse
continue;
}

limit = Limit(Limit::keConstant, info.constVal);
cmpOper = (genTreeOps)info.cmpOper;
limit = Limit(Limit::keConstant, info.constVal);
cmpOper = (genTreeOps)info.cmpOper;
isUnsigned = info.isUnsigned;
}
// Current assertion is of the form i == 100
else if (curAssertion->IsConstantInt32Assertion())
Expand Down Expand Up @@ -828,11 +830,16 @@ void RangeCheck::MergeEdgeAssertions(ValueNum normalLclVN, ASSERT_VALARG_TP asse
case GT_LT:
case GT_LE:
pRange->uLimit = limit;
if (isUnsigned)
{
pRange->lLimit = Limit(Limit::keConstant, 0);
}
break;

case GT_GT:
case GT_GE:
pRange->lLimit = limit;
// it doesn't matter if it's isUnsigned or not here - it's not negative anyway.
break;

case GT_EQ:
Expand Down
Loading