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

Optimize comparison of unknown type with a typeof #31686

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -966,7 +966,7 @@ internal static object[] GetCustomAttributes(RuntimeType type, RuntimeType caTyp
for (int i = 0; i < pcas.Count; i++)
result.Add(pcas[i]);

while (type != (RuntimeType)typeof(object) && type != null)
while (type != typeof(object) && type != null)
{
AddCustomAttributes(ref result, type.GetRuntimeModule(), type.MetadataToken, caType, mustBeInheritable, result);
mustBeInheritable = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2325,7 +2325,6 @@ private static bool FilterApplyMethodBase(
internal static readonly RuntimeType ValueType = (RuntimeType)typeof(System.ValueType);

private static readonly RuntimeType ObjectType = (RuntimeType)typeof(object);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ObjectType doesn't seem to be used anywhere now, can it be removed like StringType was?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is used in RuntimeType.cs, the other part of this partial class. Removing that one would be a tiny deoptimization.

private static readonly RuntimeType StringType = (RuntimeType)typeof(string);
#endregion

#region Constructor
Expand Down Expand Up @@ -3074,7 +3073,7 @@ public override bool IsSubclassOf(Type type)
// pretty much everything is a subclass of object, even interfaces
// notice that interfaces are really odd because they do not have a BaseType
// yet IsSubclassOf(typeof(object)) returns true
if (rtType == ObjectType && rtType != this)
if (rtType == typeof(object) && rtType != this)
return true;

return false;
Expand Down Expand Up @@ -3317,7 +3316,6 @@ public override Type MakeArrayType(int rank)
private const BindingFlags BinderGetSetProperty = BindingFlags.GetProperty | BindingFlags.SetProperty;
private const BindingFlags BinderGetSetField = BindingFlags.GetField | BindingFlags.SetField;
private const BindingFlags BinderNonFieldGetSet = (BindingFlags)0x00FFF300;
private static readonly RuntimeType s_typedRef = (RuntimeType)typeof(TypedReference);

[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool CanValueSpecialCast(RuntimeType valueType, RuntimeType targetType);
Expand Down Expand Up @@ -3360,7 +3358,7 @@ public override Type MakeArrayType(int rank)
}
else if (value == null)
return value;
else if (this == s_typedRef)
else if (this == typeof(TypedReference))
// everything works for a typedref
return value;

Expand Down
13 changes: 7 additions & 6 deletions src/coreclr/src/inc/corinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -217,11 +217,11 @@ TODO: Talk about initializing strutures before use
#endif
#endif

SELECTANY const GUID JITEEVersionIdentifier = { /* 96fc0c0a-9f77-450d-9663-ee33ae0fcae8 */
0x96fc0c0a,
0x9f77,
0x450d,
{0x96, 0x63, 0xee, 0x33, 0xae, 0x0f, 0xca, 0xe8}
SELECTANY const GUID JITEEVersionIdentifier = { /* 49BFCD03-9379-4493-B96C-A572F8E42879 */
0x49bfcd03,
0x9379,
0x4493,
{0xb9, 0x6c, 0xa5, 0x72, 0xf8, 0xe4, 0x28, 0x79}
};

//////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -580,7 +580,8 @@ enum CorInfoHelpFunc
CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPEHANDLE, // Convert from a TypeHandle (native structure pointer) to RuntimeTypeHandle at run-time
CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPEHANDLE_MAYBENULL, // Convert from a TypeHandle (native structure pointer) to RuntimeTypeHandle at run-time, handle might point to a null type

CORINFO_HELP_ARE_TYPES_EQUIVALENT, // Check whether two TypeHandles (native structure pointers) are equivalent
CORINFO_HELP_ARE_TYPEHANDLES_EQUIVALENT, // Check whether two TypeHandles (native structure pointers) are equivalent
CORINFO_HELP_ARE_TYPEHANDLE_AND_TYPE_EQUIVALENT, // Checks whether a TypeHandle (native structure pointer) is equivalent to a RuntimeType

CORINFO_HELP_VIRTUAL_FUNC_PTR, // look up a virtual method at run-time
//CORINFO_HELP_VIRTUAL_FUNC_PTR_LOG, // look up a virtual method at run-time, with IBC logging
Expand Down
3 changes: 2 additions & 1 deletion src/coreclr/src/inc/jithelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,8 @@
JITHELPER(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPEHANDLE, JIT_GetRuntimeType, CORINFO_HELP_SIG_REG_ONLY)
JITHELPER(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPEHANDLE_MAYBENULL, JIT_GetRuntimeType_MaybeNull, CORINFO_HELP_SIG_REG_ONLY)

JITHELPER(CORINFO_HELP_ARE_TYPES_EQUIVALENT, NULL, CORINFO_HELP_SIG_REG_ONLY)
JITHELPER(CORINFO_HELP_ARE_TYPEHANDLES_EQUIVALENT, NULL, CORINFO_HELP_SIG_REG_ONLY)
JITHELPER(CORINFO_HELP_ARE_TYPEHANDLE_AND_TYPE_EQUIVALENT, JIT_AreTypeHandleAndTypeEquivalent, CORINFO_HELP_SIG_REG_ONLY)

JITHELPER(CORINFO_HELP_VIRTUAL_FUNC_PTR, JIT_VirtualFunctionPointer, CORINFO_HELP_SIG_4_STACK)
//JITHELPER(CORINFO_HELP_VIRTUAL_FUNC_PTR_LOG,JIT_VirtualFunctionPointerLogging)
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/src/inc/readytorun.h
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,8 @@ enum ReadyToRunHelper

// Stack probing helper
READYTORUN_HELPER_StackProbe = 0x111,

READYTORUN_HELPER_AreTypeHandleAndTypeEquivalent = 0x112,
};

//
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/src/inc/readytorunhelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,5 +122,7 @@ HELPER(READYTORUN_HELPER_MonitorExit, CORINFO_HELP_MON_EXIT,
HELPER(READYTORUN_HELPER_StackProbe, CORINFO_HELP_STACK_PROBE, )
#endif

HELPER(READYTORUN_HELPER_AreTypeHandleAndTypeEquivalent, CORINFO_HELP_ARE_TYPEHANDLE_AND_TYPE_EQUIVALENT,)

#undef HELPER
#undef OPTIMIZEFORSPEED
115 changes: 64 additions & 51 deletions src/coreclr/src/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12278,7 +12278,7 @@ GenTree* Compiler::gtCreateHandleCompare(genTreeOps oper,

// Emit a call to a runtime helper
GenTreeCall::Use* helperArgs = gtNewCallArgs(op1, op2);
GenTree* ret = gtNewHelperCallNode(CORINFO_HELP_ARE_TYPES_EQUIVALENT, TYP_INT, helperArgs);
GenTree* ret = gtNewHelperCallNode(CORINFO_HELP_ARE_TYPEHANDLES_EQUIVALENT, TYP_INT, helperArgs);
if (oper == GT_EQ)
{
ret = gtNewOperNode(GT_NE, TYP_INT, ret, gtNewIconNode(0, TYP_INT));
Expand Down Expand Up @@ -12306,6 +12306,7 @@ GenTree* Compiler::gtCreateHandleCompare(genTreeOps oper,
// Checks for
// typeof(...) == obj.GetType()
// typeof(...) == typeof(...)
// typeof(...) == someType
//
// And potentially optimizes away the need to obtain actual
// RuntimeType objects to do the comparison.
Expand All @@ -12320,20 +12321,12 @@ GenTree* Compiler::gtFoldTypeCompare(GenTree* tree)
return tree;
}

// Screen for the right kinds of operands
// Screen for the operands
GenTree* const op1 = tree->AsOp()->gtOp1;
const TypeProducerKind op1Kind = gtGetTypeProducerKind(op1);
if (op1Kind == TPK_Unknown)
{
return tree;
}

GenTree* const op2 = tree->AsOp()->gtOp2;
const TypeProducerKind op2Kind = gtGetTypeProducerKind(op2);
if (op2Kind == TPK_Unknown)
{
return tree;
}

// We must have a handle on one side or the other here to optimize,
// otherwise we can't be sure that optimizing is sound.
Expand Down Expand Up @@ -12438,16 +12431,6 @@ GenTree* Compiler::gtFoldTypeCompare(GenTree* tree)
}

// Just one operand creates a type from a handle.
//
// If the other operand is fetching the type from an object,
// we can sometimes optimize the type compare into a simpler
// method table comparison.
//
// TODO: if other operand is null...
if ((op1Kind != TPK_GetType) && (op2Kind != TPK_GetType))
{
return tree;
}

GenTree* const opHandle = op1IsFromHandle ? op1 : op2;
GenTree* const opOther = op1IsFromHandle ? op2 : op1;
Expand All @@ -12462,49 +12445,79 @@ GenTree* Compiler::gtFoldTypeCompare(GenTree* tree)
return tree;
}

// Ask the VM if this type can be equality tested by a simple method
// table comparison.
CorInfoInlineTypeCheck typeCheckInliningResult =
info.compCompHnd->canInlineTypeCheck(clsHnd, CORINFO_INLINE_TYPECHECK_SOURCE_VTABLE);
if (typeCheckInliningResult == CORINFO_INLINE_TYPECHECK_NONE)
// If the other operand is fetching the type from an object,
// we can sometimes optimize the type compare into a simpler
// method table comparison.
//
// TODO: if other operand is null...
if ((op1Kind == TPK_GetType) || (op2Kind == TPK_GetType))
{
return tree;
}
// Ask the VM if this type can be equality tested by a simple method
// table comparison.
CorInfoInlineTypeCheck typeCheckInliningResult =
info.compCompHnd->canInlineTypeCheck(clsHnd, CORINFO_INLINE_TYPECHECK_SOURCE_VTABLE);
if (typeCheckInliningResult == CORINFO_INLINE_TYPECHECK_NONE)
{
return tree;
}

// We're good to go.
JITDUMP("Optimizing compare of obj.GetType()"
" and type-from-handle to compare method table pointer\n");

// opHandleArgument is the method table we're looking for.
GenTree* const knownMT = opHandleArgument;

// Fetch object method table from the object itself.
GenTree* objOp = nullptr;

// Note we may see intrinsified or regular calls to GetType
if (opOther->OperGet() == GT_INTRINSIC)
{
objOp = opOther->AsUnOp()->gtOp1;
}
else
{
objOp = opOther->AsCall()->gtCallThisArg->GetNode();
}

// We're good to go.
JITDUMP("Optimizing compare of obj.GetType()"
" and type-from-handle to compare method table pointer\n");
GenTree* const objMT = gtNewOperNode(GT_IND, TYP_I_IMPL, objOp);

// opHandleArgument is the method table we're looking for.
GenTree* const knownMT = opHandleArgument;
// Update various flags
objMT->gtFlags |= GTF_EXCEPT;
compCurBB->bbFlags |= BBF_HAS_VTABREF;
optMethodFlags |= OMF_HAS_VTABLEREF;

// Fetch object method table from the object itself.
GenTree* objOp = nullptr;
// Compare the two method tables
GenTree* const compare = gtCreateHandleCompare(oper, objMT, knownMT, typeCheckInliningResult);

// Note we may see intrinsified or regular calls to GetType
if (opOther->OperGet() == GT_INTRINSIC)
// Drop any now irrelevant flags
compare->gtFlags |= tree->gtFlags & (GTF_RELOP_JMP_USED | GTF_RELOP_QMARK | GTF_DONT_CSE);

// And we're done
return compare;
}

// One of the operands is from a handle and we don't have special handling for the other one.
// Transform to a helper call that avoids materializing the runtime type for the handle.

JITDUMP("Optimizing compare of an unknown type"
" and type-from-handle to specialized helper\n");

GenTreeCall::Use* helperArgs = gtNewCallArgs(opHandleArgument, opOther);
GenTree* compare = gtNewHelperCallNode(CORINFO_HELP_ARE_TYPEHANDLE_AND_TYPE_EQUIVALENT, TYP_INT, helperArgs);
if (oper == GT_EQ)
{
objOp = opOther->AsUnOp()->gtOp1;
compare = gtNewOperNode(GT_NE, TYP_INT, compare, gtNewIconNode(0, TYP_INT));
}
else
{
objOp = opOther->AsCall()->gtCallThisArg->GetNode();
assert(oper == GT_NE);
compare = gtNewOperNode(GT_EQ, TYP_INT, compare, gtNewIconNode(0, TYP_INT));
}

GenTree* const objMT = gtNewOperNode(GT_IND, TYP_I_IMPL, objOp);

// Update various flags
objMT->gtFlags |= GTF_EXCEPT;
compCurBB->bbFlags |= BBF_HAS_VTABREF;
optMethodFlags |= OMF_HAS_VTABLEREF;

// Compare the two method tables
GenTree* const compare = gtCreateHandleCompare(oper, objMT, knownMT, typeCheckInliningResult);

// Drop any now irrelevant flags
compare->gtFlags |= tree->gtFlags & (GTF_RELOP_JMP_USED | GTF_RELOP_QMARK | GTF_DONT_CSE);
compare->gtFlags |= tree->gtFlags;

// And we're done
return compare;
}

Expand Down
3 changes: 2 additions & 1 deletion src/coreclr/src/jit/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1332,7 +1332,8 @@ void HelperCallProperties::init()
noThrow = true; // These return null for a failing cast
break;

case CORINFO_HELP_ARE_TYPES_EQUIVALENT:
case CORINFO_HELP_ARE_TYPEHANDLES_EQUIVALENT:
case CORINFO_HELP_ARE_TYPEHANDLE_AND_TYPE_EQUIVALENT:

isPure = true;
noThrow = true;
Expand Down
8 changes: 6 additions & 2 deletions src/coreclr/src/jit/valuenum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8870,8 +8870,12 @@ VNFunc Compiler::fgValueNumberJitHelperMethodVNFunc(CorInfoHelpFunc helpFunc)
vnf = VNF_TypeHandleToRuntimeTypeHandle;
break;

case CORINFO_HELP_ARE_TYPES_EQUIVALENT:
vnf = VNF_AreTypesEquivalent;
case CORINFO_HELP_ARE_TYPEHANDLES_EQUIVALENT:
vnf = VNF_AreTypeHandlesEquivalent;
break;

case CORINFO_HELP_ARE_TYPEHANDLE_AND_TYPE_EQUIVALENT:
vnf = VNF_AreTypeHandleAndTypeEquivalent;
break;

case CORINFO_HELP_READYTORUN_ISINSTANCEOF:
Expand Down
3 changes: 2 additions & 1 deletion src/coreclr/src/jit/valuenumfuncs.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ ValueNumFuncDef(ReadyToRunIsInstanceOf, 2, false, false, false) // Args: 0
ValueNumFuncDef(TypeHandleToRuntimeType, 1, false, false, false) // Args: 0: TypeHandle to translate
ValueNumFuncDef(TypeHandleToRuntimeTypeHandle, 1, false, false, false) // Args: 0: TypeHandle to translate

ValueNumFuncDef(AreTypesEquivalent, 2, false, false, false) // Args: 0: first TypeHandle, 1: second TypeHandle
ValueNumFuncDef(AreTypeHandlesEquivalent, 2, false, false, false) // Args: 0: first TypeHandle, 1: second TypeHandle
ValueNumFuncDef(AreTypeHandleAndTypeEquivalent, 2, false, false, false) // Args: 0: TypeHandle, 1: Type instance

ValueNumFuncDef(LdElemA, 3, false, false, false) // Args: 0: array value; 1: index value; 2: type handle of element.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,8 @@ public enum ReadyToRunHelper

StackProbe = 0x111,

AreTypeHandleAndTypeEquivalent = 0x112,

// **********************************************************************************************
//
// These are not actually part of the R2R file format. We have them here because it's convenient.
Expand Down
3 changes: 2 additions & 1 deletion src/coreclr/src/tools/Common/JitInterface/CorInfoHelpFunc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,8 @@ which is the right helper to use to allocate an object of a given type. */
CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPEHANDLE, // Convert from a TypeHandle (native structure pointer) to RuntimeType at run-time
CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPEHANDLE_MAYBENULL, // Convert from a TypeHandle (native structure pointer) to RuntimeTypeHandle at run-time, handle might point to a null type

CORINFO_HELP_ARE_TYPES_EQUIVALENT, // Check whether two TypeHandles (native structure pointers) are equivalent
CORINFO_HELP_ARE_TYPEHANDLES_EQUIVALENT, // Check whether two TypeHandles (native structure pointers) are equivalent
CORINFO_HELP_ARE_TYPEHANDLE_AND_TYPE_EQUIVALENT, // Checks whether a TypeHandle (native structure pointer) is equivalent to a RuntimeType

CORINFO_HELP_VIRTUAL_FUNC_PTR, // look up a virtual method at run-time
//CORINFO_HELP_VIRTUAL_FUNC_PTR_LOG, // look up a virtual method at run-time, with IBC logging
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,10 @@ private ISymbolNode GetHelperFtnUncached(CorInfoHelpFunc ftnNum)
id = ReadyToRunHelper.GCPoll;
break;

case CorInfoHelpFunc.CORINFO_HELP_ARE_TYPEHANDLE_AND_TYPE_EQUIVALENT:
id = ReadyToRunHelper.AreTypeHandleAndTypeEquivalent;
break;

case CorInfoHelpFunc.CORINFO_HELP_INITCLASS:
case CorInfoHelpFunc.CORINFO_HELP_INITINSTCLASS:
case CorInfoHelpFunc.CORINFO_HELP_THROW_ARGUMENTEXCEPTION:
Expand Down
10 changes: 5 additions & 5 deletions src/coreclr/src/tools/crossgen2/jitinterface/jitwrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ class CORJIT_FLAGS
uint64_t corJitFlags;
};

static const GUID JITEEVersionIdentifier = { /* 96fc0c0a-9f77-450d-9663-ee33ae0fcae8 */
0x96fc0c0a,
0x9f77,
0x450d,
{0x96, 0x63, 0xee, 0x33, 0xae, 0x0f, 0xca, 0xe8}
static const GUID JITEEVersionIdentifier = { /* 49BFCD03-9379-4493-B96C-A572F8E42879 */
0x49bfcd03,
0x9379,
0x4493,
{0xb9, 0x6c, 0xa5, 0x72, 0xf8, 0xe4, 0x28, 0x79}
};

class Jit
Expand Down
32 changes: 32 additions & 0 deletions src/coreclr/src/vm/jithelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
// EXCEPTION HELPERS
// DEBUGGER/PROFILER HELPERS
// GC HELPERS
// REFLECTION HELPERS
// INTEROP HELPERS
//
//========================================================================
Expand Down Expand Up @@ -5083,6 +5084,37 @@ HCIMPL0(void, JIT_DebugLogLoopCloning)
}
HCIMPLEND

//========================================================================
//
// REFLECTION HELPERS
//
//========================================================================

HCIMPL2(FC_BOOL_RET, JIT_AreTypeHandleAndTypeEquivalent, CORINFO_CLASS_HANDLE type, Object* obj)
{
FCALL_CONTRACT;

TypeHandle th(type);

OBJECTREF refObj = ObjectToOBJECTREF(obj);

if (!refObj)
{
FC_RETURN_BOOL(FALSE);
}

if (refObj->GetMethodTable() != g_pRuntimeTypeClass)
{
// Non-RuntimeType cannot be equal to a RuntimeType
FC_RETURN_BOOL(FALSE);
}

REFLECTCLASSBASEREF refType = (REFLECTCLASSBASEREF)refObj;

FC_RETURN_BOOL(refType->GetType() == th);
}
HCIMPLEND

//========================================================================
//
// INTEROP HELPERS
Expand Down
Loading