@@ -3447,8 +3447,10 @@ GenTree* Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd,
34473447 case NI_System_String_get_Length:
34483448 case NI_System_Span_get_Item:
34493449 case NI_System_Span_get_Length:
3450+ case NI_System_Span_Slice:
34503451 case NI_System_ReadOnlySpan_get_Item:
34513452 case NI_System_ReadOnlySpan_get_Length:
3453+ case NI_System_ReadOnlySpan_Slice:
34523454 case NI_System_BitConverter_DoubleToInt64Bits:
34533455 case NI_System_BitConverter_Int32BitsToSingle:
34543456 case NI_System_BitConverter_Int64BitsToDouble:
@@ -3885,6 +3887,132 @@ GenTree* Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd,
38853887 break ;
38863888 }
38873889
3890+ case NI_System_Span_Slice:
3891+ case NI_System_ReadOnlySpan_Slice:
3892+ {
3893+ optMethodFlags |= OMF_HAS_ARRAYREF ;
3894+
3895+ // Have start, stack pointer-to Span<T> s on the stack. Expand to:
3896+ //
3897+ // For Span<T>
3898+ // BoundsCheck(start, s->_length + 1) // ArgumentOutOfRange on failure
3899+ // tmp._reference = s->_reference + (nuint)(uint)start * sizeof(T)
3900+ // tmp._length = s->_length - start
3901+ // tmp
3902+ //
3903+ // For ReadOnlySpan<T> -- same expansion, the only difference is the result type.
3904+ //
3905+ // Signature should show one class type parameter, which
3906+ // we need to examine.
3907+ assert (sig->sigInst .classInstCount == 1 );
3908+ assert (sig->numArgs == 1 );
3909+ CORINFO_CLASS_HANDLE spanElemHnd = sig->sigInst .classInst [0 ];
3910+ const unsigned elemSize = info.compCompHnd ->getClassSize (spanElemHnd);
3911+ assert (elemSize > 0 );
3912+
3913+ const bool isReadOnly = (ni == NI_System_ReadOnlySpan_Slice);
3914+
3915+ JITDUMP (" \n impIntrinsic: Expanding %sSpan<T>.Slice(int), T=%s, sizeof(T)=%u\n " ,
3916+ isReadOnly ? " ReadOnly" : " " , eeGetClassName (spanElemHnd), elemSize);
3917+
3918+ GenTree* start = impPopStack ().val ;
3919+ GenTree* ptrToSpan = impPopStack ().val ;
3920+ GenTree* startClone = nullptr ;
3921+ GenTree* ptrToSpanClone = nullptr ;
3922+ assert (genActualType (start) == TYP_INT );
3923+ assert (ptrToSpan->TypeIs (TYP_BYREF , TYP_I_IMPL ));
3924+
3925+ #if defined(DEBUG)
3926+ if (verbose)
3927+ {
3928+ printf (" with ptr-to-span\n " );
3929+ gtDispTree (ptrToSpan);
3930+ printf (" and start\n " );
3931+ gtDispTree (start);
3932+ }
3933+ #endif // defined(DEBUG)
3934+
3935+ // We need 'start' three times (bounds check, byref offset, new length),
3936+ // so spill it once and clone the resulting LclVar reads as needed.
3937+ start = impCloneExpr (start, &startClone, CHECK_SPILL_ALL , nullptr DEBUGARG (" Span.Slice start" ));
3938+ GenTree* startClone2 = gtCloneExpr (startClone);
3939+
3940+ if (impIsAddressInLocal (ptrToSpan))
3941+ {
3942+ ptrToSpanClone = gtCloneExpr (ptrToSpan);
3943+ }
3944+ else
3945+ {
3946+ ptrToSpan = impCloneExpr (ptrToSpan, &ptrToSpanClone, CHECK_SPILL_ALL ,
3947+ nullptr DEBUGARG (" Span.Slice ptrToSpan" ));
3948+ }
3949+
3950+ // Read input length.
3951+ CORINFO_FIELD_HANDLE lengthHnd = info.compCompHnd ->getFieldInClass (clsHnd, 1 );
3952+ const unsigned lengthOffset = info.compCompHnd ->getFieldOffset (lengthHnd);
3953+
3954+ GenTreeFieldAddr* lengthFieldAddr = gtNewFieldAddrNode (lengthHnd, ptrToSpan, lengthOffset);
3955+ GenTree* length = gtNewIndir (TYP_INT , lengthFieldAddr);
3956+ lengthFieldAddr->SetIsSpanLength (true );
3957+
3958+ // Length is needed twice: once for the bounds check (as length + 1)
3959+ // and once for the new length (length - start).
3960+ GenTree* lengthClone = nullptr ;
3961+ length = impCloneExpr (length, &lengthClone, CHECK_SPILL_ALL , nullptr DEBUGARG (" Span.Slice length" ));
3962+
3963+ // Bounds check: throw ArgumentOutOfRangeException if (uint)start > (uint)length,
3964+ // which is equivalent to (uint)start >= (uint)(length + 1).
3965+ //
3966+ // Use TYP_INT (not widened to TYP_LONG) so that downstream range-check
3967+ // elimination (rangecheck.cpp) can actually reason about the bound -- it bails on
3968+ // TYP_LONG. The (length + 1) addition is in TYP_INT and would wrap to int.MinValue
3969+ // when _length == int.MaxValue; codegen still does the comparison unsigned and
3970+ // produces the correct result, but assertion-prop / range-check passes lose some
3971+ // information at that boundary (a Span<byte> with int.MaxValue elements is the
3972+ // only case that could be affected).
3973+ GenTree* boundInt = gtNewOperNode (GT_ADD , TYP_INT , length, gtNewIconNode (1 ));
3974+ GenTree* boundsCheck =
3975+ new (this , GT_BOUNDS_CHECK ) GenTreeBoundsChk (start, boundInt, SCK_ARG_RNG_EXCPN );
3976+
3977+ // Read input reference.
3978+ CORINFO_FIELD_HANDLE ptrHnd = info.compCompHnd ->getFieldInClass (clsHnd, 0 );
3979+ const unsigned ptrOffset = info.compCompHnd ->getFieldOffset (ptrHnd);
3980+ GenTreeFieldAddr* dataFieldAddr = gtNewFieldAddrNode (ptrHnd, ptrToSpanClone, ptrOffset);
3981+ GenTree* oldRef = gtNewIndir (TYP_BYREF , dataFieldAddr);
3982+
3983+ // newRef = oldRef + (nuint)(uint)start * sizeof(T)
3984+ GenTree* offset = impImplicitIorI4Cast (startClone, TYP_I_IMPL , /* zeroExtend */ true );
3985+ if (elemSize != 1 )
3986+ {
3987+ GenTree* sizeofNode = gtNewIconNode (static_cast <ssize_t >(elemSize), TYP_I_IMPL );
3988+ offset = gtNewOperNode (GT_MUL , TYP_I_IMPL , offset, sizeofNode);
3989+ }
3990+ GenTree* newRef = gtNewOperNode (GT_ADD , TYP_BYREF , oldRef, offset);
3991+
3992+ // newLength = length - start
3993+ GenTree* newLength = gtNewOperNode (GT_SUB , TYP_INT , lengthClone, startClone2);
3994+
3995+ // Allocate a temp local for the result Span and initialize its fields.
3996+ CORINFO_CLASS_HANDLE retSpanHnd = sig->retTypeClass ;
3997+ unsigned spanTempNum = lvaGrabTemp (true DEBUGARG (" Span<T> for Slice" ));
3998+ lvaSetStruct (spanTempNum, retSpanHnd, false );
3999+
4000+ GenTree* lengthFieldStore =
4001+ gtNewStoreLclFldNode (spanTempNum, TYP_INT , OFFSETOF__CORINFO_Span__length, newLength);
4002+ GenTree* dataFieldStore =
4003+ gtNewStoreLclFldNode (spanTempNum, TYP_BYREF , OFFSETOF__CORINFO_Span__reference, newRef);
4004+
4005+ // Statements are appended in order, and GT_BOUNDS_CHECK carries GTF_EXCEPT, so the
4006+ // check executes before any of the temp-span field stores. Match the field-store
4007+ // order used by impCreateSpanIntrinsic (length, then reference).
4008+ impAppendTree (boundsCheck, CHECK_SPILL_NONE , impCurStmtDI);
4009+ impAppendTree (lengthFieldStore, CHECK_SPILL_NONE , impCurStmtDI);
4010+ impAppendTree (dataFieldStore, CHECK_SPILL_NONE , impCurStmtDI);
4011+
4012+ retNode = impCreateLocalNode (spanTempNum DEBUGARG (0 ));
4013+ break ;
4014+ }
4015+
38884016 case NI_System_Span_get_Length:
38894017 case NI_System_ReadOnlySpan_get_Length:
38904018 {
@@ -10672,6 +10800,18 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method)
1067210800 {
1067310801 result = NI_System_ReadOnlySpan_get_Length;
1067410802 }
10803+ else if (strcmp (methodName, " Slice" ) == 0 )
10804+ {
10805+ // Only the single-argument Slice(int) overload is intrinsic; the
10806+ // two-argument Slice(int, int) overload shares the same method name
10807+ // but is not [Intrinsic] and must not be matched here.
10808+ CORINFO_SIG_INFO sliceSig;
10809+ info.compCompHnd ->getMethodSig (method, &sliceSig);
10810+ if (sliceSig.numArgs == 1 )
10811+ {
10812+ result = NI_System_ReadOnlySpan_Slice;
10813+ }
10814+ }
1067510815 }
1067610816 else if (strcmp (className, " RuntimeType" ) == 0 )
1067710817 {
@@ -10710,6 +10850,18 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method)
1071010850 {
1071110851 result = NI_System_Span_get_Length;
1071210852 }
10853+ else if (strcmp (methodName, " Slice" ) == 0 )
10854+ {
10855+ // Only the single-argument Slice(int) overload is intrinsic; the
10856+ // two-argument Slice(int, int) overload shares the same method name
10857+ // but is not [Intrinsic] and must not be matched here.
10858+ CORINFO_SIG_INFO sliceSig;
10859+ info.compCompHnd ->getMethodSig (method, &sliceSig);
10860+ if (sliceSig.numArgs == 1 )
10861+ {
10862+ result = NI_System_Span_Slice;
10863+ }
10864+ }
1071310865 }
1071410866 else if (strcmp (className, " SpanHelpers" ) == 0 )
1071510867 {
0 commit comments