* Update the JIT to track `Span.Length` and `ReadOnlySpan.Length` as "never negative"
* Updating the "is never negative" info to be tracked in LclVarDsc
* Apply formatting patch
* Ensure lvIsNeverNegative is propagated to shadows
* Update src/coreclr/jit/gentree.h
Co-authored-by: SingleAccretion <62474226+SingleAccretion@users.noreply.github.com>
* Responding to PR feedback
* Adding two asserts
* Rename GenTreeField::IsNeverNegative to IsSpanLength and add a comment
* Fix an assert
Co-authored-by: SingleAccretion <62474226+SingleAccretion@users.noreply.github.com>
break;
case GT_LCL_VAR:
- if (compiler->lvaGetDesc(node->AsLclVar())->lvNormalizeOnStore())
+ {
+ LclVarDsc* const varDsc = compiler->lvaGetDesc(node->AsLclVar());
+
+ if (varDsc->lvNormalizeOnStore())
{
rangeType = compiler->lvaGetDesc(node->AsLclVar())->TypeGet();
}
+
+ if (varDsc->IsNeverNegative())
+ {
+ return {SymbolicIntegerValue::Zero, UpperBoundForType(rangeType)};
+ }
break;
+ }
case GT_CNS_INT:
if (node->IsIntegralConst(0) || node->IsIntegralConst(1))
break;
#endif // defined(FEATURE_HW_INTRINSICS)
+ case GT_FIELD:
+ {
+ if (node->AsField()->IsSpanLength())
+ {
+ return {SymbolicIntegerValue::Zero, UpperBoundForType(rangeType)};
+ }
+ break;
+ }
+
default:
break;
}
unsigned char lvIsOSRLocal : 1; // Root method local in an OSR method. Any stack home will be on the Tier0 frame.
// Initial value will be defined by Tier0. Requires special handing in prolog.
+private:
+ unsigned char lvIsNeverNegative : 1; // The local is known to be never negative
+
+public:
union {
unsigned lvFieldLclStart; // The index of the local var representing the first field in the promoted struct
// local. For implicit byref parameters, this gets hijacked between
}
#endif
+ // Is this is local never negative?
+ bool IsNeverNegative() const
+ {
+ return lvIsNeverNegative;
+ }
+
+ // Is this is local never negative?
+ void SetIsNeverNegative(bool value)
+ {
+ lvIsNeverNegative = value;
+ }
+
/////////////////////
regNumber GetArgInitReg() const
#ifdef FEATURE_READYTORUN
copy->AsField()->gtFieldLookup = tree->AsField()->gtFieldLookup;
#endif
+
+ if (tree->AsField()->IsSpanLength())
+ {
+ copy->AsField()->SetIsSpanLength(true);
+ }
}
else if (tree->OperIs(GT_ADD, GT_SUB))
{
goto DONE;
case GT_LCL_VAR_ADDR:
+ {
copy = new (this, oper) GenTreeLclVar(oper, tree->TypeGet(), tree->AsLclVar()->GetLclNum());
goto DONE;
+ }
case GT_LCL_FLD_ADDR:
+ {
copy = new (this, oper)
GenTreeLclFld(oper, tree->TypeGet(), tree->AsLclFld()->GetLclNum(), tree->AsLclFld()->GetLclOffs());
goto DONE;
+ }
default:
NO_WAY("Cloning of node not supported");
#ifdef FEATURE_READYTORUN
copy->AsField()->gtFieldLookup = tree->AsField()->gtFieldLookup;
#endif
+
+ if ((oper == GT_FIELD) && tree->AsField()->IsSpanLength())
+ {
+ copy->AsField()->SetIsSpanLength(true);
+ }
break;
case GT_BOX:
return AsLclFld()->GetLayout();
}
+//------------------------------------------------------------------------
+// GenTreeLclVar::IsNeverNegative: Gets true if the lcl var is never negative; otherwise false.
+//
+// Arguments:
+// comp - the compiler instance
+//
+// Return Value:
+// true if the lcl var is never negative; otherwise false.
+//
+bool GenTreeLclVar::IsNeverNegative(Compiler* comp) const
+{
+ assert(OperIs(GT_LCL_VAR));
+ return comp->lvaGetDesc(GetLclNum())->IsNeverNegative();
+}
+
#if defined(TARGET_XARCH) && defined(FEATURE_HW_INTRINSICS)
//------------------------------------------------------------------------
// GetResultOpNumForFMA: check if the result is written into one of the operands.
{
return AsIntConCommon()->IntegralValue() >= 0;
}
+
+ if (OperIs(GT_LCL_VAR))
+ {
+ if (AsLclVar()->IsNeverNegative(comp))
+ {
+ // This is an early exit, it doesn't cover all cases
+ return true;
+ }
+ }
+ else if (OperIs(GT_FIELD))
+ {
+ if (AsField()->IsSpanLength())
+ {
+ // This is an early exit, it doesn't cover all cases
+ return true;
+ }
+ }
+
// TODO-Casts: extend IntegralRange to handle constants
return IntegralRange::ForNode((GenTree*)this, comp).IsPositive();
}
unsigned int GetFieldCount(Compiler* compiler) const;
var_types GetFieldTypeByIndex(Compiler* compiler, unsigned idx);
+ bool IsNeverNegative(Compiler* comp) const;
+
//-------------------------------------------------------------------
// clearOtherRegFlags: clear GTF_* flags associated with gtOtherRegs
//
{
CORINFO_FIELD_HANDLE gtFldHnd;
DWORD gtFldOffset;
- bool gtFldMayOverlap;
+ bool gtFldMayOverlap : 1;
+
+private:
+ bool gtFldIsSpanLength : 1;
+
+public:
#ifdef FEATURE_READYTORUN
CORINFO_CONST_LOOKUP gtFieldLookup;
#endif
GenTreeField(genTreeOps oper, var_types type, GenTree* obj, CORINFO_FIELD_HANDLE fldHnd, DWORD offs)
- : GenTreeUnOp(oper, type, obj), gtFldHnd(fldHnd), gtFldOffset(offs), gtFldMayOverlap(false)
+ : GenTreeUnOp(oper, type, obj)
+ , gtFldHnd(fldHnd)
+ , gtFldOffset(offs)
+ , gtFldMayOverlap(false)
+ , gtFldIsSpanLength(false)
{
#ifdef FEATURE_READYTORUN
gtFieldLookup.addr = nullptr;
return (gtFlags & GTF_FLD_VOLATILE) != 0;
}
+ bool IsSpanLength() const
+ {
+ // This is limited to span length today rather than a more general "IsNeverNegative"
+ // to help avoid confusion around propagating the value to promoted lcl vars.
+ //
+ // Extending this support more in the future will require additional work and
+ // considerations to help ensure it is correctly used since people may want
+ // or intend to use this as more of a "point in time" feature like GTF_IND_NONNULL
+
+ assert(OperIs(GT_FIELD));
+ return gtFldIsSpanLength;
+ }
+
+ void SetIsSpanLength(bool value)
+ {
+ gtFldIsSpanLength = value;
+ }
+
bool IsInstance() const
{
return GetFldObj() != nullptr;
shadowVarDsc->lvIsUnsafeBuffer = varDsc->lvIsUnsafeBuffer;
shadowVarDsc->lvIsPtr = varDsc->lvIsPtr;
+ if (varDsc->IsNeverNegative())
+ {
+ shadowVarDsc->SetIsNeverNegative(true);
+ }
+
#ifdef DEBUG
if (verbose)
{
// Bounds check
CORINFO_FIELD_HANDLE lengthHnd = info.compCompHnd->getFieldInClass(clsHnd, 1);
const unsigned lengthOffset = info.compCompHnd->getFieldOffset(lengthHnd);
- GenTree* length = gtNewFieldRef(TYP_INT, lengthHnd, ptrToSpan, lengthOffset);
+
+ GenTreeField* length = gtNewFieldRef(TYP_INT, lengthHnd, ptrToSpan, lengthOffset);
+ length->SetIsSpanLength(true);
+
GenTree* boundsCheck = new (this, GT_BOUNDS_CHECK) GenTreeBoundsChk(index, length, SCK_RNGCHK_FAIL);
// Element access
CORINFO_FIELD_HANDLE lengthHnd = info.compCompHnd->getFieldInClass(clsHnd, 1);
const unsigned lengthOffset = info.compCompHnd->getFieldOffset(lengthHnd);
- return gtNewFieldRef(TYP_INT, lengthHnd, ptrToSpan, lengthOffset);
+ GenTreeField* fieldRef = gtNewFieldRef(TYP_INT, lengthHnd, ptrToSpan, lengthOffset);
+ fieldRef->SetIsSpanLength(true);
+ return fieldRef;
}
case NI_System_RuntimeTypeHandle_GetValueInternal:
{
GenTreeFlags lclVarFlags = node->gtFlags & (GTF_NODE_MASK | GTF_DONT_CSE);
+ if (node->OperIs(GT_FIELD) && node->AsField()->IsSpanLength())
+ {
+ m_compiler->lvaGetDesc(fieldLclNum)->SetIsNeverNegative(true);
+ }
+
if ((user != nullptr) && user->OperIs(GT_ASG) && (user->AsOp()->gtOp1 == node))
{
lclVarFlags |= GTF_VAR_DEF;