Skip to content

Commit

Permalink
RuntimeHelpers.CreateSpan<T> (#61079)
Browse files Browse the repository at this point in the history
Implement `RuntimeHelpers.CreateSpan<T>` #60948 

Implementation provides for
- Both non-intrinsic and intrinsic implementations in CoreCLR
- Non-intrinsic implementation in Mono
  - Mono implementation also implements untested big endian support

Co-authored-by: Aaron Robinson <arobins@microsoft.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
  • Loading branch information
3 people authored Nov 30, 2021
1 parent 85642f8 commit 3efb476
Show file tree
Hide file tree
Showing 20 changed files with 520 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ public static partial class RuntimeHelpers
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void InitializeArray(Array array, RuntimeFieldHandle fldHandle);

[MethodImpl(MethodImplOptions.InternalCall)]
private static extern unsafe void* GetSpanDataFrom(
RuntimeFieldHandle fldHandle,
RuntimeTypeHandle targetTypeHandle,
out int count);

// GetObjectValue is intended to allow value classes to be manipulated as 'Object'
// but have aliasing behavior of a value class. The intent is that you would use
// this function just before an assignment to a variable of type 'Object'. If the
Expand Down
43 changes: 43 additions & 0 deletions src/coreclr/classlibnative/bcltype/arraynative.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1153,3 +1153,46 @@ FCIMPL2_IV(void, ArrayNative::InitializeArray, ArrayBase* pArrayRef, FCALLRuntim
HELPER_METHOD_FRAME_END();
}
FCIMPLEND

FCIMPL3(void*, ArrayNative::GetSpanDataFrom, FCALLRuntimeFieldHandle structField, FCALLRuntimeTypeHandle targetType, INT32* count)
{
FCALL_CONTRACT;
struct
{
REFLECTFIELDREF refField;
} gc;
gc.refField = (REFLECTFIELDREF)ObjectToOBJECTREF(FCALL_RFH_TO_REFLECTFIELD(structField));
void* data;
HELPER_METHOD_FRAME_BEGIN_RET_1(gc);

FieldDesc* pField = (FieldDesc*)gc.refField->GetField();

if (!pField->IsRVA())
COMPlusThrow(kArgumentException);

TypeHandle targetTypeHandle = FCALL_RTH_TO_REFLECTCLASS(targetType)->GetType();
if (!CorTypeInfo::IsPrimitiveType(targetTypeHandle.GetSignatureCorElementType()) && !targetTypeHandle.IsEnum())
COMPlusThrow(kArgumentException);

DWORD totalSize = pField->LoadSize();
DWORD targetTypeSize = targetTypeHandle.GetSize();

// Report the RVA field to the logger.
g_IBCLogger.LogRVADataAccess(pField);

_ASSERTE(data != NULL && count != NULL);
data = pField->GetStaticAddressHandle(NULL);

if (AlignUp((UINT_PTR)data, targetTypeSize) != (UINT_PTR)data)
COMPlusThrow(kArgumentException);

*count = (INT32)totalSize / targetTypeSize;

#if BIGENDIAN
COMPlusThrow(kPlatformNotSupportedException);
#endif

HELPER_METHOD_FRAME_END();
return data;
}
FCIMPLEND
5 changes: 5 additions & 0 deletions src/coreclr/classlibnative/bcltype/arraynative.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#define _ARRAYNATIVE_H_

#include "fcall.h"
#include "runtimehandles.h"

struct FCALLRuntimeFieldHandle
{
Expand Down Expand Up @@ -45,6 +46,10 @@ class ArrayNative
// to a field.
static FCDECL2_IV(void, InitializeArray, ArrayBase* vArrayRef, FCALLRuntimeFieldHandle structField);

// This method will acquire data to create a span from a TypeHandle
// to a field.
static FCDECL3(void*, GetSpanDataFrom, FCALLRuntimeFieldHandle structField, FCALLRuntimeTypeHandle targetType, INT32* count);

private:
// Helper for CreateInstance
static void CheckElementType(TypeHandle elementType);
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 @@ -4483,6 +4483,7 @@ class Compiler
bool readonlyCall,
CorInfoIntrinsics intrinsicID);
GenTree* impInitializeArrayIntrinsic(CORINFO_SIG_INFO* sig);
GenTree* impCreateSpanIntrinsic(CORINFO_SIG_INFO* sig);

GenTree* impKeepAliveIntrinsic(GenTree* objToKeepAlive);

Expand Down
134 changes: 134 additions & 0 deletions src/coreclr/jit/importer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3652,6 +3652,120 @@ const char* Compiler::impGetIntrinsicName(CorInfoIntrinsics intrinsicID)

#endif // DEBUG

GenTree* Compiler::impCreateSpanIntrinsic(CORINFO_SIG_INFO* sig)
{
assert(sig->numArgs == 1);
assert(sig->sigInst.methInstCount == 1);

GenTree* fieldTokenNode = impStackTop(0).val;

//
// Verify that the field token is known and valid. Note that it's also
// possible for the token to come from reflection, in which case we cannot do
// the optimization and must therefore revert to calling the helper. You can
// see an example of this in bvt\DynIL\initarray2.exe (in Main).
//

// Check to see if the ldtoken helper call is what we see here.
if (fieldTokenNode->gtOper != GT_CALL || (fieldTokenNode->AsCall()->gtCallType != CT_HELPER) ||
(fieldTokenNode->AsCall()->gtCallMethHnd != eeFindHelper(CORINFO_HELP_FIELDDESC_TO_STUBRUNTIMEFIELD)))
{
return nullptr;
}

// Strip helper call away
fieldTokenNode = fieldTokenNode->AsCall()->gtCallArgs->GetNode();
if (fieldTokenNode->gtOper == GT_IND)
{
fieldTokenNode = fieldTokenNode->AsOp()->gtOp1;
}

// Check for constant
if (fieldTokenNode->gtOper != GT_CNS_INT)
{
return nullptr;
}

CORINFO_FIELD_HANDLE fieldToken = (CORINFO_FIELD_HANDLE)fieldTokenNode->AsIntCon()->gtCompileTimeHandle;
if (!fieldTokenNode->IsIconHandle(GTF_ICON_FIELD_HDL) || (fieldToken == nullptr))
{
return nullptr;
}

CORINFO_CLASS_HANDLE fieldOwnerHnd = info.compCompHnd->getFieldClass(fieldToken);

CORINFO_CLASS_HANDLE fieldClsHnd;
var_types fieldElementType =
JITtype2varType(info.compCompHnd->getFieldType(fieldToken, &fieldClsHnd, fieldOwnerHnd));
unsigned totalFieldSize;

// Most static initialization data fields are of some structure, but it is possible for them to be of various
// primitive types as well
if (fieldElementType == var_types::TYP_STRUCT)
{
totalFieldSize = info.compCompHnd->getClassSize(fieldClsHnd);
}
else
{
totalFieldSize = genTypeSize(fieldElementType);
}

// Limit to primitive or enum type - see ArrayNative::GetSpanDataFrom()
CORINFO_CLASS_HANDLE targetElemHnd = sig->sigInst.methInst[0];
if (info.compCompHnd->getTypeForPrimitiveValueClass(targetElemHnd) == CORINFO_TYPE_UNDEF)
{
return nullptr;
}

const unsigned targetElemSize = info.compCompHnd->getClassSize(targetElemHnd);
assert(targetElemSize != 0);

const unsigned count = totalFieldSize / targetElemSize;
if (count == 0)
{
return nullptr;
}

void* data = info.compCompHnd->getArrayInitializationData(fieldToken, totalFieldSize);
if (!data)
{
return nullptr;
}

//
// Ready to commit to the work
//

impPopStack();

// Turn count and pointer value into constants.
GenTree* lengthValue = gtNewIconNode(count, TYP_INT);
GenTree* pointerValue = gtNewIconHandleNode((size_t)data, GTF_ICON_CONST_PTR);

// Construct ReadOnlySpan<T> to return.
CORINFO_CLASS_HANDLE spanHnd = sig->retTypeClass;
unsigned spanTempNum = lvaGrabTemp(true DEBUGARG("ReadOnlySpan<T> for CreateSpan<T>"));
lvaSetStruct(spanTempNum, spanHnd, false);

CORINFO_FIELD_HANDLE pointerFieldHnd = info.compCompHnd->getFieldInClass(spanHnd, 0);
CORINFO_FIELD_HANDLE lengthFieldHnd = info.compCompHnd->getFieldInClass(spanHnd, 1);

GenTreeLclFld* pointerField = gtNewLclFldNode(spanTempNum, TYP_BYREF, 0);
pointerField->SetFieldSeq(GetFieldSeqStore()->CreateSingleton(pointerFieldHnd));
GenTree* pointerFieldAsg = gtNewAssignNode(pointerField, pointerValue);

GenTreeLclFld* lengthField = gtNewLclFldNode(spanTempNum, TYP_INT, TARGET_POINTER_SIZE);
lengthField->SetFieldSeq(GetFieldSeqStore()->CreateSingleton(lengthFieldHnd));
GenTree* lengthFieldAsg = gtNewAssignNode(lengthField, lengthValue);

// Now append a few statements the initialize the span
impAppendTree(lengthFieldAsg, (unsigned)CHECK_SPILL_NONE, impCurStmtDI);
impAppendTree(pointerFieldAsg, (unsigned)CHECK_SPILL_NONE, impCurStmtDI);

// And finally create a tree that points at the span.
return impCreateLocalNode(spanTempNum DEBUGARG(0));
}

//------------------------------------------------------------------------
// impIntrinsic: possibly expand intrinsic call into alternate IR sequence
//
Expand Down Expand Up @@ -3811,6 +3925,12 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,
return new (this, GT_LABEL) GenTree(GT_LABEL, TYP_I_IMPL);
}

if ((ni == NI_System_Runtime_CompilerServices_RuntimeHelpers_CreateSpan) && IsTargetAbi(CORINFO_CORERT_ABI))
{
// CreateSpan must be expanded for NativeAOT
mustExpand = true;
}

GenTree* retNode = nullptr;

// Under debug and minopts, only expand what is required.
Expand Down Expand Up @@ -4079,6 +4199,12 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,
break;
}

case NI_System_Runtime_CompilerServices_RuntimeHelpers_CreateSpan:
{
retNode = impCreateSpanIntrinsic(sig);
break;
}

case NI_System_Span_get_Item:
case NI_System_ReadOnlySpan_get_Item:
{
Expand Down Expand Up @@ -5195,6 +5321,14 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method)
result = SimdAsHWIntrinsicInfo::lookupId(&sig, className, methodName, enclosingClassName, sizeOfVectorT);
}
#endif // FEATURE_HW_INTRINSICS
else if ((strcmp(namespaceName, "System.Runtime.CompilerServices") == 0) &&
(strcmp(className, "RuntimeHelpers") == 0))
{
if (strcmp(methodName, "CreateSpan") == 0)
{
result = NI_System_Runtime_CompilerServices_RuntimeHelpers_CreateSpan;
}
}
else if (strncmp(namespaceName, "System.Runtime.Intrinsics", 25) == 0)
{
// We go down this path even when FEATURE_HW_INTRINSICS isn't enabled
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/jit/namedintrinsiclist.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ enum NamedIntrinsic : unsigned short
NI_System_Array_GetUpperBound,
NI_System_Object_MemberwiseClone,

NI_System_Runtime_CompilerServices_RuntimeHelpers_CreateSpan,

NI_System_String_get_Chars,
NI_System_String_get_Length,
NI_System_Span_get_Item,
Expand Down
34 changes: 29 additions & 5 deletions src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,8 @@ private void CompileMethodCleanup()
_actualInstructionSetUnsupported = default(InstructionSetFlags);
#endif

_instantiationToJitVisibleInstantiation = null;

_pgoResults.Clear();
}

Expand Down Expand Up @@ -662,6 +664,25 @@ private bool Get_CORINFO_METHOD_INFO(MethodDesc method, MethodIL methodIL, CORIN
return true;
}

private Dictionary<Instantiation, IntPtr[]> _instantiationToJitVisibleInstantiation = null;
private CORINFO_CLASS_STRUCT_** GetJitInstantiation(Instantiation inst)
{
IntPtr [] jitVisibleInstantiation;
if (_instantiationToJitVisibleInstantiation == null)
{
_instantiationToJitVisibleInstantiation = new Dictionary<Instantiation, IntPtr[]>();
}

if (!_instantiationToJitVisibleInstantiation.TryGetValue(inst, out jitVisibleInstantiation))
{
jitVisibleInstantiation = new IntPtr[inst.Length];
for (int i = 0; i < inst.Length; i++)
jitVisibleInstantiation[i] = (IntPtr)ObjectToHandle(inst[i]);
_instantiationToJitVisibleInstantiation.Add(inst, jitVisibleInstantiation);
}
return (CORINFO_CLASS_STRUCT_**)GetPin(jitVisibleInstantiation);
}

private void Get_CORINFO_SIG_INFO(MethodDesc method, CORINFO_SIG_INFO* sig, bool suppressHiddenArgument = false)
{
Get_CORINFO_SIG_INFO(method.Signature, sig);
Expand All @@ -684,12 +705,15 @@ private void Get_CORINFO_SIG_INFO(MethodDesc method, CORINFO_SIG_INFO* sig, bool
// JIT doesn't care what the instantiation is and this is expensive.
Instantiation owningTypeInst = method.OwningType.Instantiation;
sig->sigInst.classInstCount = (uint)owningTypeInst.Length;
if (owningTypeInst.Length > 0)
if (owningTypeInst.Length != 0)
{
sig->sigInst.classInst = GetJitInstantiation(owningTypeInst);
}

sig->sigInst.methInstCount = (uint)method.Instantiation.Length;
if (method.Instantiation.Length != 0)
{
var classInst = new IntPtr[owningTypeInst.Length];
for (int i = 0; i < owningTypeInst.Length; i++)
classInst[i] = (IntPtr)ObjectToHandle(owningTypeInst[i]);
sig->sigInst.classInst = (CORINFO_CLASS_STRUCT_**)GetPin(classInst);
sig->sigInst.methInst = GetJitInstantiation(method.Instantiation);
}
}

Expand Down
22 changes: 21 additions & 1 deletion src/coreclr/tools/Common/TypeSystem/Common/Instantiation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Internal.TypeSystem
/// Represents a generic instantiation - a collection of generic parameters
/// or arguments of a generic type or a generic method.
/// </summary>
public struct Instantiation
public struct Instantiation : IEquatable<Instantiation>
{
private TypeDesc[] _genericParameters;

Expand Down Expand Up @@ -113,5 +113,25 @@ public bool MoveNext()
return true;
}
}

public bool Equals(Instantiation other)
{
if (_genericParameters.Length != other._genericParameters.Length)
return false;

for (int i = 0; i < _genericParameters.Length; i++)
{
if (_genericParameters[i] != other._genericParameters[i])
return false;
}
return true;
}
public override bool Equals(object o)
{
if (o is Instantiation inst)
return Equals(inst);
return false;
}
public override int GetHashCode() => ComputeGenericInstanceHashCode(1);
}
}
1 change: 1 addition & 0 deletions src/coreclr/vm/ecalllist.h
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,7 @@ FCFuncEnd()
FCFuncStart(gRuntimeHelpers)
FCFuncElement("GetObjectValue", ObjectNative::GetObjectValue)
FCIntrinsic("InitializeArray", ArrayNative::InitializeArray, CORINFO_INTRINSIC_InitializeArray)
FCFuncElement("GetSpanDataFrom", ArrayNative::GetSpanDataFrom)
FCFuncElement("PrepareDelegate", ReflectionInvocation::PrepareDelegate)
FCFuncElement("GetHashCode", ObjectNative::GetHashCode)
FCFuncElement("Equals", ObjectNative::Equals)
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/vm/runtimehandles.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ typedef RuntimeTypeHandle FCALLRuntimeTypeHandle;
#define FCALL_RTH_TO_REFLECTCLASS(x) (x).pRuntimeTypeDONOTUSEDIRECTLY

class RuntimeTypeHandle {
ReflectClassBaseObject *pRuntimeTypeDONOTUSEDIRECTLY;
public:
ReflectClassBaseObject *pRuntimeTypeDONOTUSEDIRECTLY;

// Static method on RuntimeTypeHandle
static FCDECL1(Object*, AllocateComObject, void* pClassFactory);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,13 @@ public static void PrepareConstrainedRegionsNoOP()
internal static bool IsPrimitiveType(this CorElementType et)
// COR_ELEMENT_TYPE_I1,I2,I4,I8,U1,U2,U4,U8,R4,R8,I,U,CHAR,BOOLEAN
=> ((1 << (int)et) & 0b_0011_0000_0000_0011_1111_1111_1100) != 0;

/// <summary>Provide a fast way to access constant data stored in a module as a ReadOnlySpan{T}</summary>
/// <param name="fldHandle">A field handle that specifies the location of the data to be referred to by the ReadOnlySpan{T}. The Rva of the field must be aligned on a natural boundary of type T</param>
/// <returns>A ReadOnlySpan{T} of the data stored in the field</returns>
/// <exception cref="ArgumentException"><paramref name="fldHandle"/> does not refer to a field which is an Rva, is misaligned, or T is of an invalid type.</exception>
/// <remarks>This method is intended for compiler user rather than use directly in code. T must be one of byte, sbyte, char, short, ushort, int, long, ulong, float, or double.</remarks>
[Intrinsic]
public static unsafe ReadOnlySpan<T> CreateSpan<T>(RuntimeFieldHandle fldHandle) => new ReadOnlySpan<T>(GetSpanDataFrom(fldHandle, typeof(T).TypeHandle, out int length), length);
}
}
1 change: 1 addition & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13203,6 +13203,7 @@ public static void ExecuteCodeWithGuaranteedCleanup(System.Runtime.CompilerServi
public static T[] GetSubArray<T>(T[] array, System.Range range) { throw null; }
public static object GetUninitializedObject([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type type) { throw null; }
public static void InitializeArray(System.Array array, System.RuntimeFieldHandle fldHandle) { }
public static ReadOnlySpan<T> CreateSpan<T>(System.RuntimeFieldHandle fldHandle) { throw null; }
public static bool IsReferenceOrContainsReferences<T>() { throw null; }
[System.ObsoleteAttribute("The Constrained Execution Region (CER) feature is not supported.", DiagnosticId = "SYSLIB0004", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")]
public static void PrepareConstrainedRegions() { }
Expand Down
Loading

0 comments on commit 3efb476

Please sign in to comment.