Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit c06fb33

Browse files
authored
Jit intrinsics for Span<T>.get_Item and ReadOnlySpan<T>.get_Item. (#10910)
Implement these two methods as optional-expand jit intrinsics. Uses `GT_ARR_BOUNDS_CHECK` for the bounds check so in some cases downstream code is able to eliminate redundant checks. Fully general support (on par with arrays in most cases) is still work in progress. Update one bit of code in the optimizer that assumed it knew the tree types that appeared in a `GT_ARR_BOUNDS_CHECK`. Add benchmark tests for Span and ReadOnlySpan indexers. Tests ability of jit to reason about indexer properties with respect to loop bounds and related indexer uses. Some cases inspired by span indexer usage in Kestrel. Closes #10785. Also addresses lack of indexer inlining noted in #10031. Span indexers should now always be inlined, even when invoked from shared methods.
1 parent 81c5501 commit c06fb33

File tree

9 files changed

+1180
-6
lines changed

9 files changed

+1180
-6
lines changed

src/inc/corinfo.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,6 @@ TODO: Talk about initializing strutures before use
213213
#define SELECTANY extern __declspec(selectany)
214214
#endif
215215

216-
// Update this one
217216
SELECTANY const GUID JITEEVersionIdentifier = { /* f00b3f49-ddd2-49be-ba43-6e49ffa66959 */
218217
0xf00b3f49,
219218
0xddd2,
@@ -959,6 +958,8 @@ enum CorInfoIntrinsics
959958
CORINFO_INTRINSIC_GetManagedThreadId,
960959
CORINFO_INTRINSIC_ByReference_Ctor,
961960
CORINFO_INTRINSIC_ByReference_Value,
961+
CORINFO_INTRINSIC_Span_GetItem,
962+
CORINFO_INTRINSIC_ReadOnlySpan_GetItem,
962963

963964
CORINFO_INTRINSIC_Count,
964965
CORINFO_INTRINSIC_Illegal = -1, // Not a true intrinsic,

src/jit/importer.cpp

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3620,6 +3620,87 @@ GenTreePtr Compiler::impIntrinsic(GenTreePtr newobjThis,
36203620
retNode = field;
36213621
break;
36223622
}
3623+
case CORINFO_INTRINSIC_Span_GetItem:
3624+
case CORINFO_INTRINSIC_ReadOnlySpan_GetItem:
3625+
{
3626+
// Have index, stack pointer-to Span<T> s on the stack. Expand to:
3627+
//
3628+
// For Span<T>
3629+
// Comma
3630+
// BoundsCheck(index, s->_length)
3631+
// s->_pointer + index * sizeof(T)
3632+
//
3633+
// For ReadOnlySpan<T>
3634+
// Comma
3635+
// BoundsCheck(index, s->_length)
3636+
// *(s->_pointer + index * sizeof(T))
3637+
//
3638+
// Signature should show one class type parameter, which
3639+
// we need to examine.
3640+
assert(sig->sigInst.classInstCount == 1);
3641+
CORINFO_CLASS_HANDLE spanElemHnd = sig->sigInst.classInst[0];
3642+
const unsigned elemSize = info.compCompHnd->getClassSize(spanElemHnd);
3643+
assert(elemSize > 0);
3644+
3645+
const bool isReadOnly = (intrinsicID == CORINFO_INTRINSIC_ReadOnlySpan_GetItem);
3646+
3647+
JITDUMP("\nimpIntrinsic: Expanding %sSpan<T>.get_Item, T=%s, sizeof(T)=%u\n", isReadOnly ? "ReadOnly" : "",
3648+
info.compCompHnd->getClassName(spanElemHnd), elemSize);
3649+
3650+
GenTreePtr index = impPopStack().val;
3651+
GenTreePtr ptrToSpan = impPopStack().val;
3652+
GenTreePtr indexClone = nullptr;
3653+
GenTreePtr ptrToSpanClone = nullptr;
3654+
3655+
#if defined(DEBUG)
3656+
if (verbose)
3657+
{
3658+
printf("with ptr-to-span\n");
3659+
gtDispTree(ptrToSpan);
3660+
printf("and index\n");
3661+
gtDispTree(index);
3662+
}
3663+
#endif // defined(DEBUG)
3664+
3665+
// We need to use both index and ptr-to-span twice, so clone or spill.
3666+
index = impCloneExpr(index, &indexClone, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL,
3667+
nullptr DEBUGARG("Span.get_Item index"));
3668+
ptrToSpan = impCloneExpr(ptrToSpan, &ptrToSpanClone, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL,
3669+
nullptr DEBUGARG("Span.get_Item ptrToSpan"));
3670+
3671+
// Bounds check
3672+
CORINFO_FIELD_HANDLE lengthHnd = info.compCompHnd->getFieldInClass(clsHnd, 1);
3673+
const unsigned lengthOffset = info.compCompHnd->getFieldOffset(lengthHnd);
3674+
GenTreePtr length = gtNewFieldRef(TYP_INT, lengthHnd, ptrToSpan, lengthOffset, false);
3675+
GenTreePtr boundsCheck = new (this, GT_ARR_BOUNDS_CHECK)
3676+
GenTreeBoundsChk(GT_ARR_BOUNDS_CHECK, TYP_VOID, index, length, SCK_RNGCHK_FAIL);
3677+
3678+
// Element access
3679+
GenTreePtr indexIntPtr = impImplicitIorI4Cast(indexClone, TYP_I_IMPL);
3680+
GenTreePtr sizeofNode = gtNewIconNode(elemSize);
3681+
GenTreePtr mulNode = gtNewOperNode(GT_MUL, TYP_I_IMPL, indexIntPtr, sizeofNode);
3682+
CORINFO_FIELD_HANDLE ptrHnd = info.compCompHnd->getFieldInClass(clsHnd, 0);
3683+
const unsigned ptrOffset = info.compCompHnd->getFieldOffset(ptrHnd);
3684+
GenTreePtr data = gtNewFieldRef(TYP_BYREF, ptrHnd, ptrToSpanClone, ptrOffset, false);
3685+
GenTreePtr result = gtNewOperNode(GT_ADD, TYP_BYREF, data, mulNode);
3686+
3687+
// Prepare result
3688+
var_types resultType = JITtype2varType(sig->retType);
3689+
3690+
if (isReadOnly)
3691+
{
3692+
result = gtNewOperNode(GT_IND, resultType, result);
3693+
}
3694+
else
3695+
{
3696+
assert(resultType == result->TypeGet());
3697+
}
3698+
3699+
retNode = gtNewOperNode(GT_COMMA, resultType, boundsCheck, result);
3700+
3701+
break;
3702+
}
3703+
36233704
default:
36243705
/* Unknown intrinsic */
36253706
break;

src/jit/optimizer.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7638,6 +7638,15 @@ bool Compiler::optExtractArrIndex(GenTreePtr tree, ArrIndex* result, unsigned lh
76387638
{
76397639
return false;
76407640
}
7641+
7642+
// For span we may see gtArrLen is a local var or local field.
7643+
// We won't try and extract those.
7644+
const genTreeOps arrayOp = arrBndsChk->gtArrLen->gtOper;
7645+
7646+
if ((arrayOp == GT_LCL_VAR) || (arrayOp == GT_LCL_FLD))
7647+
{
7648+
return false;
7649+
}
76417650
if (arrBndsChk->gtArrLen->gtGetOp1()->gtOper != GT_LCL_VAR)
76427651
{
76437652
return false;

src/vm/jitinterface.cpp

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8623,7 +8623,7 @@ CorInfoIntrinsics CEEInfo::getIntrinsicID(CORINFO_METHOD_HANDLE methodHnd,
86238623
else
86248624
{
86258625
MethodTable * pMT = method->GetMethodTable();
8626-
if (pMT->IsByRefLike() && pMT->GetModule()->IsSystem())
8626+
if (pMT->GetModule()->IsSystem() && pMT->IsByRefLike())
86278627
{
86288628
if (pMT->HasSameTypeDefAs(g_pByReferenceClass))
86298629
{
@@ -8637,10 +8637,25 @@ CorInfoIntrinsics CEEInfo::getIntrinsicID(CORINFO_METHOD_HANDLE methodHnd,
86378637
_ASSERTE(strcmp(method->GetName(), "get_Value") == 0);
86388638
result = CORINFO_INTRINSIC_ByReference_Value;
86398639
}
8640-
*pMustExpand = true;
8640+
if (pMustExpand != nullptr)
8641+
{
8642+
*pMustExpand = true;
8643+
}
8644+
}
8645+
else if (pMT->HasSameTypeDefAs(MscorlibBinder::GetClass(CLASS__SPAN)))
8646+
{
8647+
if (method->HasSameMethodDefAs(MscorlibBinder::GetMethod(METHOD__SPAN__GET_ITEM)))
8648+
{
8649+
result = CORINFO_INTRINSIC_Span_GetItem;
8650+
}
8651+
}
8652+
else if (pMT->HasSameTypeDefAs(MscorlibBinder::GetClass(CLASS__READONLY_SPAN)))
8653+
{
8654+
if (method->HasSameMethodDefAs(MscorlibBinder::GetMethod(METHOD__READONLY_SPAN__GET_ITEM)))
8655+
{
8656+
result = CORINFO_INTRINSIC_ReadOnlySpan_GetItem;
8657+
}
86418658
}
8642-
8643-
// TODO-SPAN: Span<T> intrinsics for optimizations
86448659
}
86458660
}
86468661

src/vm/method.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2404,7 +2404,7 @@ BOOL MethodDesc::IsFCallOrIntrinsic()
24042404
if (IsFCall() || IsArray())
24052405
return TRUE;
24062406

2407-
// Intrinsic methods on ByReference<T> or Span<T>
2407+
// Intrinsic methods on ByReference<T>, Span<T>, or ReadOnlySpan<T>
24082408
MethodTable * pMT = GetMethodTable();
24092409
if (pMT->IsByRefLike() && pMT->GetModule()->IsSystem())
24102410
return TRUE;

src/vm/mscorlib.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,9 @@ DEFINE_CLASS(NULLABLE, System, Nullable`1)
653653

654654
DEFINE_CLASS(BYREFERENCE, System, ByReference`1)
655655
DEFINE_CLASS(SPAN, System, Span`1)
656+
DEFINE_METHOD(SPAN, GET_ITEM, get_Item, NoSig)
656657
DEFINE_CLASS(READONLY_SPAN, System, ReadOnlySpan`1)
658+
DEFINE_METHOD(READONLY_SPAN, GET_ITEM, get_Item, NoSig)
657659

658660
// Keep this in sync with System.Globalization.NumberFormatInfo
659661
DEFINE_CLASS_U(Globalization, NumberFormatInfo, NumberFormatInfo)

0 commit comments

Comments
 (0)