Skip to content

Commit

Permalink
Make Type.IsEnum and Type.GetEnumUnderlyingType intrinsics (#71685)
Browse files Browse the repository at this point in the history
* Make Type.GetTypeCode an intrinsic

Makes Type.GetTypeCode a JIT intrinsic for primitive types,
enums and strings.

Makes Type.IsEnum use intrinsics instead of IsSubclassOf

Makes Enum.GetUnderlyingType use Type.GetTypeCode

Moves the legacy FCall to InternalGetUnderlyingTypeImpl,
so that the non-intrinsic GetTypeCode can use it.

Introduces JIT tests checking the generated codegen.

* Change IsEnum to an intrinsic

* Fallback to the FCall for nint/nuint enums

* Fix compilation

* Add missing Intrinsic

* Add typeof support to IsKnownConstant

* Use gtIsTypeHandleToRuntimeTypeHelper

* Add __reftype tests

* Make more places use gtIsTypeof

* Update EnumTests.cs

* Add impTypeGetTypeCode to the header

* Update EnumGetUnderlyingTypeEnums.il

* Make the IsActualEnum helper an intrinsic

* Remove GetTypeCode intrinsics

* Create a new JIT-EE api, add GetEnumUnderlyingType back

* Optimize GetTypeCode with IsKnownConstant

* Change the code to make the inliner happy

* Handle all types in GetTypeCode

* Check for custom types

* Fix build, do suggested changes

Co-authored-by: Andy Ayers <andya@microsoft.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
  • Loading branch information
3 people committed Nov 11, 2022
1 parent 00e6482 commit 16b28c7
Show file tree
Hide file tree
Showing 42 changed files with 903 additions and 229 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3397,6 +3397,7 @@ public override unsafe bool IsEnum
// This returns true for actual enum types only.
internal unsafe bool IsActualEnum
{
[Intrinsic]
get
{
TypeHandle th = GetNativeTypeHandle();
Expand Down
14 changes: 12 additions & 2 deletions src/coreclr/inc/corinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -2270,7 +2270,7 @@ class ICorStaticInfo

//------------------------------------------------------------------------------
// printObjectDescription: Prints a (possibly truncated) textual UTF8 representation of the given
// object to a preallocated buffer. It's intended to be used only for debug/diagnostic
// object to a preallocated buffer. It's intended to be used only for debug/diagnostic
// purposes such as JitDisasm. The buffer is null-terminated (even if truncated).
//
// Arguments:
Expand Down Expand Up @@ -2635,6 +2635,16 @@ class ICorStaticInfo
CORINFO_CLASS_HANDLE cls2
) = 0;

// Returns TypeCompareState::Must if cls is known to be an enum.
// For enums with known exact type returns the underlying
// type in underlyingType when the provided pointer is
// non-NULL.
// Returns TypeCompareState::May when a runtime check is required.
virtual TypeCompareState isEnum(
CORINFO_CLASS_HANDLE cls,
CORINFO_CLASS_HANDLE* underlyingType
) = 0;

// Given a class handle, returns the Parent type.
// For COMObjectType, it returns Class Handle of System.Object.
// Returns 0 if System.Object is passed in.
Expand Down Expand Up @@ -2849,7 +2859,7 @@ class ICorStaticInfo
CORINFO_CLASS_HANDLE *vcTypeRet /* OUT */
) = 0;

// Obtains a list of exact classes for a given base type. Returns 0 if the number of
// Obtains a list of exact classes for a given base type. Returns 0 if the number of
// the exact classes is greater than maxExactClasses or if more types might be loaded
// in future.
virtual int getExactClasses(
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/inc/icorjitinfoimpl_generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,10 @@ bool isMoreSpecificType(
CORINFO_CLASS_HANDLE cls1,
CORINFO_CLASS_HANDLE cls2) override;

TypeCompareState isEnum(
CORINFO_CLASS_HANDLE cls,
CORINFO_CLASS_HANDLE* underlyingType) override;

CORINFO_CLASS_HANDLE getParentType(
CORINFO_CLASS_HANDLE cls) override;

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 = { /* 77b6df16-d27f-4118-9dfd-d8073ff20fb6 */
0x77b6df16,
0xd27f,
0x4118,
{0x9d, 0xfd, 0xd8, 0x7, 0x3f, 0xf2, 0xf, 0xb6}
constexpr GUID JITEEVersionIdentifier = { /* e452af1d-0a1a-44a8-a5b3-ef6074b8ab4a */
0xe452af1d,
0x0a1a,
0x44a8,
{0xa5, 0xb3, 0xef, 0x60, 0x74, 0xb8, 0xab, 0x4a}
};

//////////////////////////////////////////////////////////////////////////////////////////////////////////
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 @@ -88,6 +88,7 @@ DEF_CLR_API(compareTypesForCast)
DEF_CLR_API(compareTypesForEquality)
DEF_CLR_API(mergeClasses)
DEF_CLR_API(isMoreSpecificType)
DEF_CLR_API(isEnum)
DEF_CLR_API(getParentType)
DEF_CLR_API(getChildType)
DEF_CLR_API(satisfiesClassConstraints)
Expand Down
10 changes: 10 additions & 0 deletions src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,16 @@ bool WrapICorJitInfo::isMoreSpecificType(
return temp;
}

TypeCompareState WrapICorJitInfo::isEnum(
CORINFO_CLASS_HANDLE cls,
CORINFO_CLASS_HANDLE* underlyingType)
{
API_ENTER(isEnum);
TypeCompareState temp = wrapHnd->isEnum(cls, underlyingType);
API_LEAVE(isEnum);
return temp;
}

CORINFO_CLASS_HANDLE WrapICorJitInfo::getParentType(
CORINFO_CLASS_HANDLE cls)
{
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -2878,6 +2878,8 @@ class Compiler
CORINFO_CLASS_HANDLE gtGetHelperArgClassHandle(GenTree* array);
// Get the class handle for a field
CORINFO_CLASS_HANDLE gtGetFieldClassHandle(CORINFO_FIELD_HANDLE fieldHnd, bool* pIsExact, bool* pIsNonNull);
// Check if this tree is a typeof()
bool gtIsTypeof(GenTree* tree, CORINFO_CLASS_HANDLE* handle = nullptr);

GenTree* gtCallGetDefinedRetBufLclAddr(GenTreeCall* call);

Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/jit/fgbasic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1163,6 +1163,8 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed
break;

// These are foldable if the first argument is a constant
case NI_System_Type_get_IsEnum:
case NI_System_Type_GetEnumUnderlyingType:
case NI_System_Type_get_IsValueType:
case NI_System_Type_get_IsByRefLike:
case NI_System_Type_GetTypeFromHandle:
Expand Down
36 changes: 36 additions & 0 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18335,6 +18335,42 @@ CORINFO_CLASS_HANDLE Compiler::gtGetFieldClassHandle(CORINFO_FIELD_HANDLE fieldH
return fieldClass;
}

//------------------------------------------------------------------------
// gtIsTypeof: Checks if the tree is a typeof()
//
// Arguments:
// tree - the tree that is checked
// handle - (optional, default nullptr) - if non-null is set to the type
//
// Return Value:
// Is the tree typeof()
//
bool Compiler::gtIsTypeof(GenTree* tree, CORINFO_CLASS_HANDLE* handle)
{
if (tree->IsCall())
{
GenTreeCall* call = tree->AsCall();
if (gtIsTypeHandleToRuntimeTypeHelper(call))
{
assert(call->gtArgs.CountArgs() == 1);
CORINFO_CLASS_HANDLE hClass = gtGetHelperArgClassHandle(call->gtArgs.GetArgByIndex(0)->GetEarlyNode());
if (hClass != NO_CLASS_HANDLE)
{
if (handle != nullptr)
{
*handle = hClass;
}
return true;
}
}
}
if (handle != nullptr)
{
*handle = NO_CLASS_HANDLE;
}
return false;
}

//------------------------------------------------------------------------
// gtCallGetDefinedRetBufLclAddr:
// Get the tree corresponding to the address of the retbuf that this call defines.
Expand Down
52 changes: 17 additions & 35 deletions src/coreclr/jit/importer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2932,37 +2932,24 @@ GenTree* Compiler::impTypeIsAssignable(GenTree* typeTo, GenTree* typeFrom)
//
// to true/false

if (typeTo->IsCall() && typeFrom->IsCall())
// make sure both arguments are `typeof()`
CORINFO_CLASS_HANDLE hClassTo = NO_CLASS_HANDLE;
CORINFO_CLASS_HANDLE hClassFrom = NO_CLASS_HANDLE;
if (gtIsTypeof(typeTo, &hClassTo) && gtIsTypeof(typeFrom, &hClassFrom))
{
// make sure both arguments are `typeof()`
CORINFO_METHOD_HANDLE hTypeof = eeFindHelper(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE);
if ((typeTo->AsCall()->gtCallMethHnd == hTypeof) && (typeFrom->AsCall()->gtCallMethHnd == hTypeof))
TypeCompareState castResult = info.compCompHnd->compareTypesForCast(hClassFrom, hClassTo);
if (castResult == TypeCompareState::May)
{
assert((typeTo->AsCall()->gtArgs.CountArgs() == 1) && (typeFrom->AsCall()->gtArgs.CountArgs() == 1));
CORINFO_CLASS_HANDLE hClassTo =
gtGetHelperArgClassHandle(typeTo->AsCall()->gtArgs.GetArgByIndex(0)->GetEarlyNode());
CORINFO_CLASS_HANDLE hClassFrom =
gtGetHelperArgClassHandle(typeFrom->AsCall()->gtArgs.GetArgByIndex(0)->GetEarlyNode());

if (hClassTo == NO_CLASS_HANDLE || hClassFrom == NO_CLASS_HANDLE)
{
return nullptr;
}

TypeCompareState castResult = info.compCompHnd->compareTypesForCast(hClassFrom, hClassTo);
if (castResult == TypeCompareState::May)
{
// requires runtime check
// e.g. __Canon, COMObjects, Nullable
return nullptr;
}
// requires runtime check
// e.g. __Canon, COMObjects, Nullable
return nullptr;
}

GenTreeIntCon* retNode = gtNewIconNode((castResult == TypeCompareState::Must) ? 1 : 0);
impPopStack(); // drop both CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE calls
impPopStack();
GenTreeIntCon* retNode = gtNewIconNode((castResult == TypeCompareState::Must) ? 1 : 0);
impPopStack(); // drop both CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE calls
impPopStack();

return retNode;
}
return retNode;
}

return nullptr;
Expand Down Expand Up @@ -13391,15 +13378,10 @@ void Compiler::impInlineRecordArgInfo(InlineInfo* pInlineInfo,
return;
}
}
else
else if (gtIsTypeof(curArgVal))
{
if (curArgVal->IsHelperCall() && gtIsTypeHandleToRuntimeTypeHelper(curArgVal->AsCall()) &&
(gtGetHelperArgClassHandle(curArgVal->AsCall()->gtArgs.GetArgByIndex(0)->GetEarlyNode()) !=
NO_CLASS_HANDLE))
{
inlCurArgInfo->argIsInvariant = true;
inlCurArgInfo->argHasSideEff = false;
}
inlCurArgInfo->argIsInvariant = true;
inlCurArgInfo->argHasSideEff = false;
}

bool isExact = false;
Expand Down
77 changes: 57 additions & 20 deletions src/coreclr/jit/importercalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2587,12 +2587,12 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,
case NI_System_Runtime_CompilerServices_RuntimeHelpers_IsKnownConstant:
{
GenTree* op1 = impPopStack().val;
if (op1->OperIsConst())
if (op1->OperIsConst() || gtIsTypeof(op1))
{
// op1 is a known constant, replace with 'true'.
retNode = gtNewIconNode(1);
JITDUMP("\nExpanding RuntimeHelpers.IsKnownConstant to true early\n");
// We can also consider FTN_ADDR and typeof(T) here
// We can also consider FTN_ADDR here
}
else
{
Expand Down Expand Up @@ -2854,6 +2854,7 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,
break;
}

case NI_System_Type_get_IsEnum:
case NI_System_Type_get_IsValueType:
case NI_System_Type_get_IsByRefLike:
{
Expand All @@ -2865,35 +2866,56 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,
// to `true` or `false`
// e.g., `typeof(int).IsValueType` => `true`
// e.g., `typeof(Span<int>).IsByRefLike` => `true`
if (impStackTop().val->IsCall())
CORINFO_CLASS_HANDLE hClass = NO_CLASS_HANDLE;
if (gtIsTypeof(impStackTop().val, &hClass))
{
GenTreeCall* call = impStackTop().val->AsCall();
if (call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE))
switch (ni)
{
assert(call->gtArgs.CountArgs() == 1);
CORINFO_CLASS_HANDLE hClass =
gtGetHelperArgClassHandle(call->gtArgs.GetArgByIndex(0)->GetEarlyNode());
if (hClass != NO_CLASS_HANDLE)
case NI_System_Type_get_IsEnum:
{
switch (ni)
TypeCompareState state = info.compCompHnd->isEnum(hClass, nullptr);
if (state == TypeCompareState::May)
{
case NI_System_Type_get_IsValueType:
retNode = gtNewIconNode(eeIsValueClass(hClass) ? 1 : 0);
break;
case NI_System_Type_get_IsByRefLike:
retNode = gtNewIconNode(
(info.compCompHnd->getClassAttribs(hClass) & CORINFO_FLG_BYREF_LIKE) ? 1 : 0);
break;
default:
NO_WAY("Intrinsic not supported in this path.");
retNode = nullptr;
break;
}
impPopStack(); // drop CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE call
retNode = gtNewIconNode(state == TypeCompareState::Must ? 1 : 0);
break;
}
case NI_System_Type_get_IsValueType:
retNode = gtNewIconNode(eeIsValueClass(hClass) ? 1 : 0);
break;
case NI_System_Type_get_IsByRefLike:
retNode = gtNewIconNode(
(info.compCompHnd->getClassAttribs(hClass) & CORINFO_FLG_BYREF_LIKE) ? 1 : 0);
break;
default:
NO_WAY("Intrinsic not supported in this path.");
}
if (retNode != nullptr)
{
impPopStack(); // drop CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE call
}
}
break;
}

case NI_System_Type_GetEnumUnderlyingType:
{
GenTree* type = impStackTop().val;
CORINFO_CLASS_HANDLE hClassEnum = NO_CLASS_HANDLE;
CORINFO_CLASS_HANDLE hClassUnderlying = NO_CLASS_HANDLE;
if (gtIsTypeof(type, &hClassEnum) && (hClassEnum != NO_CLASS_HANDLE) &&
(info.compCompHnd->isEnum(hClassEnum, &hClassUnderlying) == TypeCompareState::Must) &&
(hClassUnderlying != NO_CLASS_HANDLE))
{
GenTree* handle = gtNewIconEmbClsHndNode(hClassUnderlying);
retNode = gtNewHelperCallNode(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE, TYP_REF, handle);
impPopStack();
}
break;
}

case NI_System_Threading_Thread_get_ManagedThreadId:
{
if (impStackTop().val->OperIs(GT_RET_EXPR))
Expand Down Expand Up @@ -7140,6 +7162,13 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method)
result = NI_System_RuntimeTypeHandle_GetValueInternal;
}
}
else if (strcmp(className, "RuntimeType") == 0)
{
if (strcmp(methodName, "get_IsActualEnum") == 0)
{
result = NI_System_Type_get_IsEnum;
}
}
else if (strcmp(className, "Type") == 0)
{
if (strcmp(methodName, "get_IsValueType") == 0)
Expand Down Expand Up @@ -7170,6 +7199,14 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method)
{
result = NI_System_Type_GetTypeFromHandle;
}
else if (strcmp(methodName, "get_IsEnum") == 0)
{
result = NI_System_Type_get_IsEnum;
}
else if (strcmp(methodName, "GetEnumUnderlyingType") == 0)
{
result = NI_System_Type_GetEnumUnderlyingType;
}
}
else if (strcmp(className, "String") == 0)
{
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/morph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11020,7 +11020,7 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA
assert(!optValnumCSE_phase);

JITDUMP("\nExpanding RuntimeHelpers.IsKnownConstant to ");
if (op1->OperIsConst())
if (op1->OperIsConst() || gtIsTypeof(op1))
{
// We're lucky to catch a constant here while importer was not
JITDUMP("true\n");
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 @@ -60,6 +60,8 @@ enum NamedIntrinsic : unsigned short
NI_System_GC_KeepAlive,
NI_System_Threading_Thread_get_CurrentThread,
NI_System_Threading_Thread_get_ManagedThreadId,
NI_System_Type_get_IsEnum,
NI_System_Type_GetEnumUnderlyingType,
NI_System_Type_get_IsValueType,
NI_System_Type_get_IsByRefLike,
NI_System_Type_IsAssignableFrom,
Expand Down
Loading

0 comments on commit 16b28c7

Please sign in to comment.