Skip to content

Commit

Permalink
JIT: Support for devirtualizing array interface methods
Browse files Browse the repository at this point in the history
Update JIT and runtime to devirtualize interface calls on arrays
over non-shared element types.

Shared types are not (yet) handled.

Add intrinsic and inlining attributes to key methods in the BCL.
This allows the JIT to devirtualize and inline enumerator creation
and devirtualize and inline all methods that access the enumerator.

And this in turn allows the enumerator to be stack allocated.

However, the enumerator fields are not (yet) physically promoted,
because of an optimization in the BCL to return a static empty
array enumerator. So the object being accessed later is ambiguous.

Progress towards dotnet#62457.
  • Loading branch information
AndyAyersMS authored and MichalStrehovsky committed Sep 30, 2024
1 parent 2917e51 commit ccb4215
Show file tree
Hide file tree
Showing 23 changed files with 458 additions and 170 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,8 @@ private SZArrayHelper()
Debug.Fail("Hey! How'd I get here?");
}

[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal IEnumerator<T> GetEnumerator<T>()
{
// ! Warning: "this" is an array, not an SZArrayHelper. See comments above
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/inc/corinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -2123,6 +2123,12 @@ class ICorStaticInfo
CORINFO_CLASS_HANDLE elemType
) = 0;

// Given T, return the type of the SZArrayHelper enumerator
// Returns null if the type can't be determined exactly.
virtual CORINFO_CLASS_HANDLE getSZArrayHelperEnumeratorClass(
CORINFO_CLASS_HANDLE elemType
) = 0;

// Given resolved token that corresponds to an intrinsic classified to
// get a raw handle (NI_System_Activator_AllocatorOf etc.), fetch the
// handle associated with the token. If this is not possible at
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/inc/icorjitinfoimpl_generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ CORINFO_CLASS_HANDLE getDefaultComparerClass(
CORINFO_CLASS_HANDLE getDefaultEqualityComparerClass(
CORINFO_CLASS_HANDLE elemType) override;

CORINFO_CLASS_HANDLE getSZArrayHelperEnumeratorClass(
CORINFO_CLASS_HANDLE elemType) override;

void expandRawHandleIntrinsic(
CORINFO_RESOLVED_TOKEN* pResolvedToken,
CORINFO_METHOD_HANDLE callerHandle,
Expand Down
10 changes: 5 additions & 5 deletions src/coreclr/inc/jiteeversionguid.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ typedef const GUID *LPCGUID;
#define GUID_DEFINED
#endif // !GUID_DEFINED

constexpr GUID JITEEVersionIdentifier = { /* b75a5475-ff22-4078-9551-2024ce03d383 */
0xb75a5475,
0xff22,
0x4078,
{0x95, 0x51, 0x20, 0x24, 0xce, 0x03, 0xd3, 0x83}
constexpr GUID JITEEVersionIdentifier = { /* 61da137a-bc95-4a59-859c-1725c88e678e */
0x61da137a,
0xbc95,
0x4a59,
{0x85, 0x9c, 0x17, 0x25, 0xc8, 0x8e, 0x67, 0x8e}
};

//////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/ICorJitInfo_names_generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ DEF_CLR_API(resolveVirtualMethod)
DEF_CLR_API(getUnboxedEntry)
DEF_CLR_API(getDefaultComparerClass)
DEF_CLR_API(getDefaultEqualityComparerClass)
DEF_CLR_API(getSZArrayHelperEnumeratorClass)
DEF_CLR_API(expandRawHandleIntrinsic)
DEF_CLR_API(isIntrinsicType)
DEF_CLR_API(getUnmanagedCallConv)
Expand Down
9 changes: 9 additions & 0 deletions src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,15 @@ CORINFO_CLASS_HANDLE WrapICorJitInfo::getDefaultEqualityComparerClass(
return temp;
}

CORINFO_CLASS_HANDLE WrapICorJitInfo::getSZArrayHelperEnumeratorClass(
CORINFO_CLASS_HANDLE elemType)
{
API_ENTER(getSZArrayHelperEnumeratorClass);
CORINFO_CLASS_HANDLE temp = wrapHnd->getSZArrayHelperEnumeratorClass(elemType);
API_LEAVE(getSZArrayHelperEnumeratorClass);
return temp;
}

void WrapICorJitInfo::expandRawHandleIntrinsic(
CORINFO_RESOLVED_TOKEN* pResolvedToken,
CORINFO_METHOD_HANDLE callerHandle,
Expand Down
95 changes: 89 additions & 6 deletions src/coreclr/jit/importercalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,13 @@ var_types Compiler::impImportCall(OPCODE opcode,
// Devirtualization may change which method gets invoked. Update our local cache.
//
methHnd = callInfo->hMethod;

// If we devirtualized to an intrinsic, assume this is one of the special cases.
//
if ((callInfo->methodFlags & CORINFO_FLG_INTRINSIC) != 0)
{
call->AsCall()->gtCallMoreFlags |= GTF_CALL_M_SPECIAL_INTRINSIC;
}
}
else if (call->AsCall()->IsDelegateInvoke())
{
Expand Down Expand Up @@ -1349,12 +1356,26 @@ var_types Compiler::impImportCall(OPCODE opcode,
eeGetCallSiteSig(pResolvedToken->token, pResolvedToken->tokenScope, pResolvedToken->tokenContext, sig);
}

CORINFO_CLASS_HANDLE retTypeClass = sig->retTypeClass;

// Sometimes "call" is not a GT_CALL (if we imported an intrinsic that didn't turn into a call)
if (!bIntrinsicImported)
{
assert(call->IsCall());
GenTreeCall* const origCall = call->AsCall();

GenTreeCall* origCall = call->AsCall();
// If the call is a special intrisic, we may know a more exact return type.
//
if (origCall->IsSpecialIntrinsic())
{
CORINFO_CLASS_HANDLE updatedRetTypeClass = impGetSpecialIntrinsicExactReturnType(origCall);

if (updatedRetTypeClass != NO_CLASS_HANDLE)
{
JITDUMP("Updating method return type to %s\n", eeGetClassName(updatedRetTypeClass));
retTypeClass = updatedRetTypeClass;
}
}

const bool isFatPointerCandidate = origCall->IsFatPointerCandidate();
const bool isInlineCandidate = origCall->IsInlineCandidate();
Expand All @@ -1363,7 +1384,7 @@ var_types Compiler::impImportCall(OPCODE opcode,
if (varTypeIsStruct(callRetTyp))
{
// Need to treat all "split tree" cases here, not just inline candidates
call = impFixupCallStructReturn(call->AsCall(), sig->retTypeClass);
call = impFixupCallStructReturn(call->AsCall(), retTypeClass);
callRetTyp = call->TypeGet();
}

Expand Down Expand Up @@ -1516,7 +1537,7 @@ var_types Compiler::impImportCall(OPCODE opcode,
}
}

typeInfo tiRetVal = verMakeTypeInfo(sig->retType, sig->retTypeClass);
typeInfo tiRetVal = verMakeTypeInfo(sig->retType, retTypeClass);
impPushOnStack(call, tiRetVal);
}

Expand Down Expand Up @@ -4673,6 +4694,13 @@ GenTree* Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd,
break;
}

case NI_System_SZArrayHelper_GetEnumerator:
{
// We may know the exact type this returns
isSpecial = true;
break;
}

case NI_System_BitConverter_DoubleToInt64Bits:
{
GenTree* op1 = impStackTop().val;
Expand Down Expand Up @@ -8091,8 +8119,18 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call,
if (derivedMethod != nullptr)
{
assert(exactContext != nullptr);
assert(((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_CLASS);
derivedClass = (CORINFO_CLASS_HANDLE)((size_t)exactContext & ~CORINFO_CONTEXTFLAGS_MASK);

if (((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_CLASS)
{
derivedClass = (CORINFO_CLASS_HANDLE)((size_t)exactContext & ~CORINFO_CONTEXTFLAGS_MASK);
}
else
{
// Array interface devirt can return a generic method of the non-generic SZArrayHelper class.
//
assert(((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD);
derivedClass = info.compCompHnd->getMethodClass(derivedMethod);
}
}

DWORD derivedMethodAttribs = 0;
Expand Down Expand Up @@ -8526,7 +8564,7 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call,
//
if (pExactContextHandle != nullptr)
{
*pExactContextHandle = MAKE_CLASSCONTEXT(derivedClass);
*pExactContextHandle = exactContext;
}

// We might have created a new recursive tail call candidate.
Expand Down Expand Up @@ -8731,6 +8769,43 @@ CORINFO_CLASS_HANDLE Compiler::impGetSpecialIntrinsicExactReturnType(GenTreeCall
break;
}

case NI_System_SZArrayHelper_GetEnumerator:
{
// Expect one method generic parameter; figure out which it is.
CORINFO_SIG_INFO sig;
info.compCompHnd->getMethodSig(methodHnd, &sig);
assert(sig.sigInst.methInstCount == 1);
assert(sig.sigInst.classInstCount == 0);

CORINFO_CLASS_HANDLE typeHnd = sig.sigInst.methInst[0];
assert(typeHnd != nullptr);

CallArg* instParam = call->gtArgs.FindWellKnownArg(WellKnownArg::InstParam);
if (instParam != nullptr)
{
assert(instParam->GetNext() == nullptr);
CORINFO_CLASS_HANDLE hClass = gtGetHelperArgClassHandle(instParam->GetNode());
if (hClass != NO_CLASS_HANDLE)
{
typeHnd = getTypeInstantiationArgument(hClass, 0);
}
}

result = info.compCompHnd->getSZArrayHelperEnumeratorClass(typeHnd);

if (result != NO_CLASS_HANDLE)
{
JITDUMP("Special intrinsic for type %s: return type is %s\n", eeGetClassName(typeHnd),
result != nullptr ? eeGetClassName(result) : "unknown");
}
else
{
JITDUMP("Special intrinsic for type %s: type undetermined, so deferring opt\n",
eeGetClassName(typeHnd));
}
break;
}

default:
{
JITDUMP("This special intrinsic not handled, sorry...\n");
Expand Down Expand Up @@ -10214,6 +10289,14 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method)
result = NI_System_String_EndsWith;
}
}
else if (strcmp(className, "SZArrayHelper") == 0)
{
if (strcmp(methodName, "GetEnumerator") == 0)
{
result = NI_System_SZArrayHelper_GetEnumerator;
}
}

break;
}

Expand Down
5 changes: 5 additions & 0 deletions src/coreclr/jit/namedintrinsiclist.h
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,11 @@ enum NamedIntrinsic : unsigned short
NI_PRIMITIVE_TrailingZeroCount,

NI_PRIMITIVE_END,

//
// Array Intrinsics
//
NI_System_SZArrayHelper_GetEnumerator,
};

#endif // _NAMEDINTRINSICLIST_H_
7 changes: 7 additions & 0 deletions src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1521,6 +1521,13 @@ static CORINFO_RESOLVED_TOKEN CreateResolvedTokenFromMethod(CorInfoImpl jitInter
return comparer != null ? ObjectToHandle(comparer) : null;
}

private CORINFO_CLASS_STRUCT_* getSZArrayHelperEnumeratorClass(CORINFO_CLASS_STRUCT_* elemType)
{
// TBD
_ = HandleToObject(elemType);
return null;
}

private bool isIntrinsicType(CORINFO_CLASS_STRUCT_* classHnd)
{
TypeDesc type = HandleToObject(classHnd);
Expand Down
Loading

0 comments on commit ccb4215

Please sign in to comment.