Skip to content
Draft
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
1 change: 1 addition & 0 deletions src/coreclr/jit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ set( JIT_HEADERS
phase.h
priorityqueue.h
promotion.h
modularrange.h
rangecheck.h
rangecheckcloning.h
rationalize.h
Expand Down
4 changes: 2 additions & 2 deletions src/coreclr/jit/assertionprop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4408,7 +4408,7 @@ GenTree* Compiler::optAssertionProp_AddMulSub(ASSERT_VALARG_TP assertions,
// optAssertionProp_ModDiv: Optimizes DIV/UDIV/MOD/UMOD via assertions
// 1) Convert DIV/MOD to UDIV/UMOD if both operands are proven to be never negative
// 2) Marks DIV/UDIV/MOD/UMOD with GTF_DIV_MOD_NO_BY_ZERO if divisor is proven to be never zero
// 3) Marks DIV/UDIV/MOD/UMOD with GTF_DIV_MOD_NO_OVERFLOW if both operands are proven to be never negative
// 3) Marks DIV/MOD with GTF_DIV_MOD_NO_OVERFLOW if either operand is proven to be never negative
//
// Arguments:
// assertions - set of live assertions
Expand Down Expand Up @@ -4444,7 +4444,7 @@ GenTree* Compiler::optAssertionProp_ModDiv(ASSERT_VALARG_TP assertions,

if (op2IsNotZero && ((tree->gtFlags & GTF_DIV_MOD_NO_BY_ZERO) == 0))
{
JITDUMP("Divisor for DIV/MOD is proven to be never negative...\n")
JITDUMP("Divisor for DIV/MOD is proven to be never zero...\n")
tree->gtFlags |= GTF_DIV_MOD_NO_BY_ZERO;
tree->SetHasOrderingSideEffect();
changed = true;
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4839,6 +4839,10 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl
DoPhase(this, PHASE_VN_BASED_INTRINSIC_EXPAND, &Compiler::fgVNBasedIntrinsicExpansion);
}

// Record what the SSA based phases proved about locals on their descriptors, which outlive them
//
DoPhase(this, PHASE_RECORD_FACTS_ON_LOCALS, &Compiler::fgRecordFactsOnLocals);

// Conservatively mark all VNs as stale
vnStore = nullptr;

Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -6829,6 +6829,7 @@ class Compiler
bool fgExpandStackArrayAllocation(BasicBlock* pBlock, Statement* stmt, GenTreeCall* call);

PhaseStatus fgVNBasedIntrinsicExpansion();
PhaseStatus fgRecordFactsOnLocals();
bool fgVNBasedIntrinsicExpansionForCall(BasicBlock** pBlock, Statement* stmt, GenTreeCall* call);
bool fgVNBasedIntrinsicExpansionForCall_ReadUtf8(BasicBlock** pBlock, Statement* stmt, GenTreeCall* call);

Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/compphases.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ CompPhaseNameMacro(PHASE_OPTIMIZE_INDEX_CHECKS, "Optimize index checks",
CompPhaseNameMacro(PHASE_OPTIMIZE_VALNUM_CSES, "Optimize Valnum CSEs", false, -1, false)
CompPhaseNameMacro(PHASE_VN_COPY_PROP, "VN based copy prop", false, -1, false)
CompPhaseNameMacro(PHASE_VN_BASED_INTRINSIC_EXPAND, "VN based intrinsic expansion", false, -1, false)
CompPhaseNameMacro(PHASE_RECORD_FACTS_ON_LOCALS, "Record facts on locals", false, -1, false)
CompPhaseNameMacro(PHASE_OPTIMIZE_BRANCHES, "Redundant branch opts", false, -1, false)
CompPhaseNameMacro(PHASE_BOUNDS_CHECK_COALESCE, "Coalesce bounds checks", false, -1, false)
CompPhaseNameMacro(PHASE_ASSERTION_PROP_MAIN, "Assertion prop", false, -1, false)
Expand Down
76 changes: 41 additions & 35 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8842,16 +8842,29 @@ ExceptionSetFlags GenTree::OperExceptions(Compiler* comp)
return ExceptionSetFlags::None;
}

// Only the GTF_DIV_MOD_NO flags and constant operands decide, see SetDivModExceptionFlags
GenTree* op1 = this->gtGetOp1()->gtSkipReloadOrCopy();
GenTree* op2 = this->gtGetOp2()->gtSkipReloadOrCopy();

ExceptionSetFlags exSetFlags = ExceptionSetFlags::None;

if (!(this->gtFlags & GTF_DIV_MOD_NO_BY_ZERO) && !this->gtGetOp2()->gtSkipReloadOrCopy()->IsNeverZero())
if (!(this->gtFlags & GTF_DIV_MOD_NO_BY_ZERO) && !op2->IsNeverZero())
{
exSetFlags |= ExceptionSetFlags::DivideByZeroException;
}

if (this->OperIs(GT_DIV, GT_MOD) && this->CanDivOrModPossiblyOverflow(comp))
if (this->OperIs(GT_DIV, GT_MOD) && !(this->gtFlags & GTF_DIV_MOD_NO_OVERFLOW))
{
exSetFlags |= ExceptionSetFlags::ArithmeticException;
// Only the minimum value of the type divided by '-1' overflows
bool const divisorMayBeMinusOne = !op2->IsIntegralConst() || op2->IsIntegralConst(-1);
bool const dividendMayBeMin =
!op1->IsIntegralConst() ||
(op1->AsIntConCommon()->IntegralValue() == (this->TypeIs(TYP_INT) ? INT32_MIN : INT64_MIN));

if (divisorMayBeMinusOne && dividendMayBeMin)
{
exSetFlags |= ExceptionSetFlags::ArithmeticException;
}
}

return exSetFlags;
Expand Down Expand Up @@ -35333,52 +35346,45 @@ bool GenTree::IsNeverZero() const
return false;
}

//------------------------------------------------------------------------
// CanDivOrModPossiblyOverflow: returns true if the given tree is known
// to possibly overflow on a division.
// Only valid for integral types.
// Only valid for signed-div/signed-mod.
//------------------------------------------------------------------------------
// SetDivModExceptionFlags : Set GTF_EXCEPT and the GTF_DIV_MOD_NO_* flags as appropriate
// on a division or remainder node from what its operands prove.
//
// Arguments:
// comp - Compiler object, needed for IsNeverNegativeOne
// comp - compiler instance
//
// Return Value:
// true if the given tree is known to possibly overflow on a division
// Notes:
// Everything consulted here is a property of the operand's value, so the flags hold wherever
// the node ends up. A fact that holds only below a dominating check needs the node pinned
// there as well, as assertion propagation does with SetHasOrderingSideEffect.
//
bool GenTree::CanDivOrModPossiblyOverflow(Compiler* comp) const
void GenTree::SetDivModExceptionFlags(Compiler* comp)
{
assert(this->OperIs(GT_DIV, GT_MOD));
assert(varTypeIsIntegral(this));
assert(OperIs(GT_DIV, GT_MOD, GT_UDIV, GT_UMOD));

if (this->gtFlags & GTF_DIV_MOD_NO_OVERFLOW)
return false;

GenTree* op1 = this->gtGetOp1()->gtSkipReloadOrCopy();
GenTree* op2 = this->gtGetOp2()->gtSkipReloadOrCopy();

// If the divisor is known to never be '-1', we cannot overflow.
if (op2->IsNeverNegativeOne(comp))
return false;

// If the dividend is a constant with a minimum value with respect to the division's type, then we might overflow
// as we do not know if the divisor will be '-1' or not at this point.
if (op1->IsIntegralConst())
if (!varTypeIsFloating(this))
{
if (this->TypeIs(TYP_INT) && op1->IsIntegralConst(INT32_MIN))
GenTree* divisor = gtGetOp2()->gtSkipReloadOrCopy();

if (divisor->IsNeverZero())
{
return true;
gtFlags |= GTF_DIV_MOD_NO_BY_ZERO;
}
else if (this->TypeIs(TYP_LONG) && (op1->AsIntConCommon()->IntegralValue() == INT64_MIN))

if (OperIs(GT_DIV, GT_MOD) && divisor->IsNeverNegativeOne(comp))
{
return true;
gtFlags |= GTF_DIV_MOD_NO_OVERFLOW;
}
}

// Dividend is not a minimum value; therefore we cannot overflow.
return false;
if (OperMayThrow(comp))
{
gtFlags |= GTF_EXCEPT;
return;
}

// Not enough known information; therefore we might overflow.
return true;
gtFlags &= ~GTF_EXCEPT;
gtFlags |= (gtGetOp1()->gtFlags | gtGetOp2()->gtFlags) & GTF_EXCEPT;
}

#if defined(FEATURE_HW_INTRINSICS)
Expand Down
6 changes: 3 additions & 3 deletions src/coreclr/jit/gentree.h
Original file line number Diff line number Diff line change
Expand Up @@ -525,9 +525,9 @@ enum GenTreeFlags : unsigned
GTF_OVERFLOW = 0x10000000, // Supported for: GT_ADD, GT_SUB, GT_MUL and GT_CAST.
// Requires an overflow check. Use gtOverflow(Ex)() to check this flag.

GTF_DIV_MOD_NO_BY_ZERO = 0x20000000, // GT_DIV, GT_MOD -- Div or mod definitely does not divide-by-zero.
GTF_DIV_MOD_NO_BY_ZERO = 0x20000000, // GT_DIV, GT_UDIV, GT_MOD, GT_UMOD -- definitely does not divide by zero.

GTF_DIV_MOD_NO_OVERFLOW = 0x40000000, // GT_DIV, GT_MOD -- Div or mod definitely does not overflow.
GTF_DIV_MOD_NO_OVERFLOW = 0x40000000, // GT_DIV, GT_MOD -- definitely does not overflow.

GTF_ALLOCOBJ_EMPTY_STATIC = 0x80000000, // GT_ALLOCOBJ -- allocation site is part of an empty static pattern

Expand Down Expand Up @@ -2506,7 +2506,7 @@ struct GenTree
bool IsNeverNegative(Compiler* comp) const;
bool IsNeverNegativeOne(Compiler* comp) const;
bool IsNeverZero() const;
bool CanDivOrModPossiblyOverflow(Compiler* comp) const;
void SetDivModExceptionFlags(Compiler* comp);

bool IsReuseRegVal() const
{
Expand Down
168 changes: 168 additions & 0 deletions src/coreclr/jit/modularrange.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#pragma once

// A contiguous interval on the ring of integers modulo mask + 1. Endpoints are inclusive.
// lower > upper denotes a range that crosses zero. The mask must be 2^n - 1. Empty is represented separately from a
// full-ring interval. For an 8-bit domain, (uint8)(X + 10) < 20 describes [246..255] U [0..9], represented by
// lower=246, upper=9.
class ModularRange
{
target_size_t m_lower = 0;
target_size_t m_upper = 0;
bool m_empty = true;

public:
enum class Intersection
{
Empty,
Single,
Multiple,
Unknown
};

ModularRange() = default;
ModularRange(target_size_t lower, target_size_t upper)
: m_lower(lower)
, m_upper(upper)
, m_empty(false)
{
}

bool IsEmpty() const
{
return m_empty;
}
target_size_t Lower() const
{
assert(!m_empty);
return m_lower;
}
target_size_t Upper() const
{
assert(!m_empty);
return m_upper;
}
target_size_t Width(target_size_t mask) const
{
assert(!m_empty);
return (m_upper - m_lower) & mask;
}
bool IsFull(target_size_t mask) const
{
return !m_empty && Width(mask) == mask;
}
bool Contains(target_size_t value, target_size_t mask) const
{
return !m_empty && (((value - m_lower) & mask) <= Width(mask));
}
bool Equals(const ModularRange& other, target_size_t mask) const
{
return m_empty ? other.m_empty
: !other.m_empty && ((m_lower == other.m_lower && m_upper == other.m_upper) ||
(IsFull(mask) && other.IsFull(mask)));
}

// Describe all X satisfying (X + offset) oper bound. A signed comparison is the unsigned
// comparison of the value with the sign bit flipped. All arithmetic here wraps.
static ModularRange FromComparison(
genTreeOps oper, target_size_t bound, bool isUnsigned, target_size_t mask, target_size_t offset = 0)
{
assert(mask != 0 && (mask & (mask + 1)) == 0);
target_size_t bias = isUnsigned ? 0 : (mask >> 1) + 1;
target_size_t limit = (bound + bias) & mask;
target_size_t lower = 0;
target_size_t upper = mask;
switch (oper)
{
case GT_LT:
// x < cns -> [min, cns - 1], empty when cns is min
if (limit == 0)
return {};
upper = limit - 1;
break;
case GT_LE:
// x <= cns -> [min, cns]
upper = limit;
break;
case GT_GT:
// x > cns -> [cns + 1, max], empty when cns is max
if (limit == mask)
return {};
lower = limit + 1;
break;
case GT_GE:
// x >= cns -> [cns, max]
lower = limit;
break;
case GT_EQ:
// x == cns -> [cns, cns]
lower = upper = limit;
break;
case GT_NE:
// x != cns -> [cns + 1, cns - 1], the rest of the ring
lower = (limit + 1) & mask;
upper = (limit - 1) & mask;
break;
default:
assert(!"Unsupported comparison");
return {};
}
return {(lower - bias - offset) & mask, (upper - bias - offset) & mask};
}

// Intersect two ranges. Rotate this range to [0, width]. The other range then has either
// one linear piece or two. Decline intersections with two disjoint pieces.
//
// E.g., in a signed 32-bit domain:
//
// x >= 100 [100 .. MAX] and x <= 10 [MIN .. 10] -> Empty
// if (x >= 100) { if (x <= 10) } // always false
// x >= 100 [100 .. MAX] and x >= 10 [10 .. MAX] -> Single [100 .. MAX], the first range
// if (x >= 100) { if (x >= 10) } // always true
// x >= 10 [10 .. MAX] and x <= 100 [MIN .. 100] -> Single [10 .. 100]
// (x - 10) u<= 90
// x != 5 [6 .. 4] and x != 6 [7 .. 5] -> Single [7 .. 4], wrapping
// (x - 7) u<= -3, all values but 5 and 6
// x != 5 [6 .. 4] and x != 100 [101 .. 99] -> Multiple: [6 .. 99] and [101 .. 4]
Intersection Intersect(const ModularRange& other, target_size_t mask, ModularRange* result) const
{
if (m_empty || other.m_empty)
{
*result = {};
return Intersection::Empty;
}
if (IsFull(mask))
{
*result = other;
return Intersection::Single;
}
if (other.IsFull(mask))
{
*result = *this;
return Intersection::Single;
}

target_size_t width = Width(mask);
target_size_t lower = (other.m_lower - m_lower) & mask;
target_size_t upper = (other.m_upper - m_lower) & mask;
if (lower <= upper)
{
upper = std::min(upper, width);
if (lower > upper)
{
*result = {};
return Intersection::Empty;
}
*result = {(m_lower + lower) & mask, (m_lower + upper) & mask};
return Intersection::Single;
}
if (lower <= width)
{
return Intersection::Multiple;
}
*result = {m_lower, (m_lower + std::min(upper, width)) & mask};
return Intersection::Single;
}
};
20 changes: 1 addition & 19 deletions src/coreclr/jit/morph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7947,25 +7947,7 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, bool* optAssertionPropDone)
case GT_UMOD:
#endif // TARGET_LOONGARCH64 || TARGET_RISCV64
{
#if defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
if (!varTypeIsFloating(tree->gtType))
{
ExceptionSetFlags exSetFlags = tree->OperExceptions(this);

if ((oper == GT_DIV) || (oper == GT_MOD))
{
if ((exSetFlags & ExceptionSetFlags::ArithmeticException) == ExceptionSetFlags::None)
{
tree->gtFlags |= GTF_DIV_MOD_NO_OVERFLOW;
}
}

if ((exSetFlags & ExceptionSetFlags::DivideByZeroException) == ExceptionSetFlags::None)
{
tree->gtFlags |= GTF_DIV_MOD_NO_BY_ZERO;
}
}
#endif // TARGET_ARM64 || TARGET_LOONGARCH64 || TARGET_RISCV64
tree->SetDivModExceptionFlags(this);

if (opts.OptimizationDisabled())
{
Expand Down
Loading
Loading