Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RuntimeHelpers.CreateSpan<T> #61079

Merged
merged 21 commits into from
Nov 30, 2021
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f5a5d9c
Add non-intrinsic implementation for CreateSpan<T>. (#60451)
AaronRobinsonMSFT Oct 15, 2021
52a7dd3
[hackathon] Implement CreateSpan<T> intrinsic. (#60498)
AaronRobinsonMSFT Oct 16, 2021
5917c2c
Fix CreateSpan build failure (#60510)
davidwrighton Oct 17, 2021
fc74779
Most static initialization data fields are of a structure type, but i…
davidwrighton Oct 17, 2021
b18bb64
Fix CreateSpan in R2R code generation
davidwrighton Nov 1, 2021
1950ef1
Convert CreateSpan test to IL
davidwrighton Nov 1, 2021
320452c
Remove CreateSpan C# based test as the Roslyn used in the main branch…
davidwrighton Nov 2, 2021
415ea45
Add xml doc comment for CreateSpan api
davidwrighton Nov 2, 2021
0636c79
Fix xml comment
davidwrighton Nov 2, 2021
cd636f0
Update src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
davidwrighton Nov 2, 2021
510c0d2
Update src/coreclr/tools/Common/TypeSystem/Common/Instantiation.cs
davidwrighton Nov 2, 2021
0dac4b1
Apply suggestions from code review
jkotas Nov 2, 2021
4987e01
Update based on code review feedback to improve generated code
davidwrighton Nov 2, 2021
f6b01c3
Merge branch 'CreateSpanIntrinsic' of github.com:davidwrighton/runtim…
davidwrighton Nov 2, 2021
2356d02
Fix JIT formatting issues
davidwrighton Nov 2, 2021
fbf632b
MustExpand intrinsic for NativeAOT
davidwrighton Nov 2, 2021
6aec0ab
Respond to code review
davidwrighton Nov 18, 2021
7546a6f
Merge branch 'main' of github.com:dotnet/runtime into CreateSpanIntri…
davidwrighton Nov 18, 2021
38f080c
Adjust to changes in JIT
davidwrighton Nov 18, 2021
34303ee
Fix HELPER_METHOD_FRAME define usage
davidwrighton Nov 18, 2021
2b0aeb6
Merge branch 'main' of github.com:dotnet/runtime into CreateSpanIntri…
davidwrighton Nov 30, 2021
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
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_PROTECT(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);
davidwrighton marked this conversation as resolved.
Show resolved Hide resolved
#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 @@ -4457,6 +4457,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.
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
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;
davidwrighton marked this conversation as resolved.
Show resolved Hide resolved
}
}
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)
davidwrighton marked this conversation as resolved.
Show resolved Hide resolved
{
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 @@ -13123,6 +13123,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