Skip to content

GC polling in unboxing JIT helpers #32353

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

Merged
merged 7 commits into from
Feb 16, 2020
Merged
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 @@ -165,6 +165,9 @@ private static CastResult TryGet(nuint source, nuint target)
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern object ChkCastAny_NoCacheLookup(void* toTypeHnd, object obj);

[MethodImpl(MethodImplOptions.InternalCall)]
private static extern ref byte Unbox_Helper(void* toTypeHnd, object obj);

// IsInstanceOf test used for unusual cases (naked type parameters, variant generic types)
// Unlike the IsInstanceOfInterface and IsInstanceOfClass functions,
// this test must deal with all kinds of type tests
Expand Down Expand Up @@ -476,5 +479,17 @@ private static CastResult TryGet(nuint source, nuint target)
slowPath:
return ChkCastHelper(toTypeHnd, obj);
}

[DebuggerHidden]
[StackTraceHidden]
[DebuggerStepThrough]
private static ref byte Unbox(void* toTypeHnd, object obj)
{
// this will throw NullReferenceException if obj is null, attributed to the user code, as expected.
if (RuntimeHelpers.GetMethodTable(obj) == toTypeHnd)
return ref obj.GetRawData();

return ref Unbox_Helper(toTypeHnd, obj);
}
}
}
2 changes: 1 addition & 1 deletion src/coreclr/src/inc/jithelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@

DYNAMICJITHELPER(CORINFO_HELP_BOX, JIT_Box, CORINFO_HELP_SIG_REG_ONLY)
JITHELPER(CORINFO_HELP_BOX_NULLABLE, JIT_Box, CORINFO_HELP_SIG_REG_ONLY)
JITHELPER(CORINFO_HELP_UNBOX, JIT_Unbox, CORINFO_HELP_SIG_REG_ONLY)
DYNAMICJITHELPER(CORINFO_HELP_UNBOX, NULL, CORINFO_HELP_SIG_REG_ONLY)
JITHELPER(CORINFO_HELP_UNBOX_NULLABLE, JIT_Unbox_Nullable, CORINFO_HELP_SIG_4_STACK)

JITHELPER(CORINFO_HELP_GETREFANY, JIT_GetRefAny, CORINFO_HELP_SIG_8_STACK)
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/src/vm/ecall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ void ECall::PopulateManagedCastHelpers()
pDest = pMD->GetMultiCallableAddrOfCode();
SetJitHelperFunction(CORINFO_HELP_CHKCASTCLASS_SPECIAL, pDest);

pMD = MscorlibBinder::GetMethod((BinderMethodID)(METHOD__CASTHELPERS__UNBOX));
pDest = pMD->GetMultiCallableAddrOfCode();
SetJitHelperFunction(CORINFO_HELP_UNBOX, pDest);

#endif //CROSSGEN_COMPILE
}

Expand Down
1 change: 1 addition & 0 deletions src/coreclr/src/vm/ecalllist.h
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,7 @@ FCFuncEnd()
FCFuncStart(gCastHelpers)
FCFuncElement("IsInstanceOfAny_NoCacheLookup", ::IsInstanceOfAny_NoCacheLookup)
FCFuncElement("ChkCastAny_NoCacheLookup", ::ChkCastAny_NoCacheLookup)
FCFuncElement("Unbox_Helper", ::Unbox_Helper)
FCFuncEnd()

FCFuncStart(gArrayFuncs)
Expand Down
16 changes: 5 additions & 11 deletions src/coreclr/src/vm/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8534,7 +8534,7 @@ void Interpreter::Unbox()
CorElementType type1 = pMT1->GetInternalCorElementType();
CorElementType type2 = pMT2->GetInternalCorElementType();

// we allow enums and their primtive type to be interchangable
// we allow enums and their primitive type to be interchangable
if (type1 == type2)
{
if ((pMT1->IsEnum() || pMT1->IsTruePrimitive()) &&
Copy link
Member

Choose a reason for hiding this comment

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

This place too?

Expand Down Expand Up @@ -8695,17 +8695,11 @@ void Interpreter::UnboxAny()
}
else
{
CorElementType type1 = pMT1->GetInternalCorElementType();
CorElementType type2 = pMT2->GetInternalCorElementType();

// we allow enums and their primtive type to be interchangable
if (type1 == type2)
{
if ((pMT1->IsEnum() || pMT1->IsTruePrimitive()) &&
if (pMT1->GetInternalCorElementType() == pMT2->GetInternalCorElementType() &&
(pMT1->IsEnum() || pMT1->IsTruePrimitive()) &&
(pMT2->IsEnum() || pMT2->IsTruePrimitive()))
{
res = OpStackGet<Object*>(tos)->UnBox();
}
{
res = OpStackGet<Object*>(tos)->UnBox();
}
}

Expand Down
79 changes: 33 additions & 46 deletions src/coreclr/src/vm/jithelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2972,6 +2972,7 @@ NOINLINE HCIMPL3(VOID, JIT_Unbox_Nullable_Framed, void * destPtr, MethodTable* t
{
COMPlusThrowInvalidCastException(&objRef, TypeHandle(typeMT));
}
HELPER_METHOD_POLL();
HELPER_METHOD_FRAME_END();
}
HCIMPLEND
Expand All @@ -2991,6 +2992,7 @@ HCIMPL3(VOID, JIT_Unbox_Nullable, void * destPtr, CORINFO_CLASS_HANDLE type, Obj
if (Nullable::UnBoxNoGC(destPtr, objRef, typeMT))
{
// exact match (type equivalence not needed)
FC_GC_POLL();
return;
}

Expand All @@ -3001,23 +3003,33 @@ HCIMPL3(VOID, JIT_Unbox_Nullable, void * destPtr, CORINFO_CLASS_HANDLE type, Obj
HCIMPLEND

/*************************************************************/
/* framed helper that handles full-blown type equivalence */
NOINLINE HCIMPL2(LPVOID, JIT_Unbox_Helper_Framed, CORINFO_CLASS_HANDLE type, Object* obj)
/* framed Unbox helper that handles enums and full-blown type equivalence */
NOINLINE HCIMPL2(LPVOID, Unbox_Helper_Framed, MethodTable* pMT1, Object* obj)
{
FCALL_CONTRACT;

LPVOID result = NULL;
MethodTable* pMT2 = obj->GetMethodTable();

OBJECTREF objRef = ObjectToOBJECTREF(obj);
HELPER_METHOD_FRAME_BEGIN_RET_1(objRef);
if (TypeHandle(type).IsEquivalentTo(objRef->GetTypeHandle()))
HELPER_METHOD_POLL();

if (pMT1->GetInternalCorElementType() == pMT2->GetInternalCorElementType() &&
Copy link
Member

Choose a reason for hiding this comment

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

Moving this inside HELPER_METHOD_FRAME will regress performance for enums...

Copy link
Member Author

Choose a reason for hiding this comment

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

We return a ref to the boxed value. If I want to poll, I need to protect the box. Is there a way to protect without a frame?


In reply to: 379777249 [](ancestors = 379777249)

Copy link
Member

Choose a reason for hiding this comment

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

One way to do this is to use fast path without HMF when g_TrapReturningThreads.LoadWithoutBarrier() is zero, and use polling show path with HMF otherwise. It would duplicate a bit of the logic, but it should not be a huge problem for the unbox helper.

Copy link
Member

Choose a reason for hiding this comment

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

Another way to do this would be to introduce FC_GC_POLL_EX variant for byrefs, but that would be quite a bit of plumbing. I do not think it is worth it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Right. A flag check would not trash the refs and should be much cheaper.

Copy link
Member Author

Choose a reason for hiding this comment

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

With suggested changes (g_TrapReturningThreads and GetVerifierCorElementType) the enum case is slightly faster than originally.

That is - as long as g_TrapReturningThreads is checked after comparing GetVerifierCorElementType.
Not sure why the order of compares matters. It is nearly the same codegen. I am guessing the rarely taken branch is more predictable this way.

Copy link
Member

Choose a reason for hiding this comment

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

You should be also able to make the enum case faster by using GetVerifierCorElementType. It should make the IsEnum/IsTruePrimitiveType conditions unnecessary.

(pMT1->IsEnum() || pMT1->IsTruePrimitive()) &&
(pMT2->IsEnum() || pMT2->IsTruePrimitive()))
{
// we allow enums and their primitive type to be interchangable
result = objRef->GetData();
}
else if (pMT1->IsEquivalentTo(pMT2))
{
// the structures are equivalent
result = objRef->GetData();
}
else
{
COMPlusThrowInvalidCastException(&objRef, TypeHandle(type));
COMPlusThrowInvalidCastException(&objRef, TypeHandle(pMT1));
}
HELPER_METHOD_FRAME_END();

Expand All @@ -3026,59 +3038,34 @@ NOINLINE HCIMPL2(LPVOID, JIT_Unbox_Helper_Framed, CORINFO_CLASS_HANDLE type, Obj
HCIMPLEND

/*************************************************************/
/* the uncommon case for the helper below (allowing enums to be unboxed
as their underlying type */
LPVOID __fastcall JIT_Unbox_Helper(CORINFO_CLASS_HANDLE type, Object* obj)
/* Unbox helper that handles enums */
HCIMPL2(LPVOID, Unbox_Helper, CORINFO_CLASS_HANDLE type, Object* obj)
{
FCALL_CONTRACT;

TypeHandle typeHnd(type);
// boxable types have method tables
_ASSERTE(!typeHnd.IsTypeDesc());

CorElementType type1 = typeHnd.GetInternalCorElementType();

// we allow enums and their primtive type to be interchangable
MethodTable* pMT1 = typeHnd.AsMethodTable();
// must be a value type
_ASSERTE(pMT1->IsValueType());

MethodTable* pMT2 = obj->GetMethodTable();
CorElementType type2 = pMT2->GetInternalCorElementType();
if (type1 == type2)

// we allow enums and their primitive type to be interchangable.
// if suspension is requested, defer to the framed helper.
if (pMT1->GetInternalCorElementType() == pMT2->GetInternalCorElementType() &&
(pMT1->IsEnum() || pMT1->IsTruePrimitive()) &&
(pMT2->IsEnum() || pMT2->IsTruePrimitive()) &&
g_TrapReturningThreads.LoadWithoutBarrier() == 0)
{
MethodTable* pMT1 = typeHnd.AsMethodTable();
if (pMT1 && (pMT1->IsEnum() || pMT1->IsTruePrimitive()) &&
(pMT2->IsEnum() || pMT2->IsTruePrimitive()))
{
_ASSERTE(CorTypeInfo::IsPrimitiveType_NoThrow(type1));
return(obj->GetData());
}
return obj->GetData();
}

// Even less common cases (type equivalence) go to a framed helper.
// Fall back to a framed helper that can also handle GC suspension and type equivalence.
ENDFORBIDGC();
Copy link
Member

Choose a reason for hiding this comment

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

You need to keep ENDFORBIDGC(); before forwarding the helper call. It will fix some of the test failures hit by the CI.

return HCCALL2(JIT_Unbox_Helper_Framed, type, obj);
}

/*************************************************************/
HCIMPL2(LPVOID, JIT_Unbox, CORINFO_CLASS_HANDLE type, Object* obj)
{
FCALL_CONTRACT;

TypeHandle typeHnd(type);
VALIDATEOBJECT(obj);
_ASSERTE(!typeHnd.IsTypeDesc()); // boxable types have method tables

// This has been tuned so that branch predictions are good
// (fall through for forward branches) for the common case
if (obj != NULL) {
if (obj->GetMethodTable() == typeHnd.AsMethodTable())
return(obj->GetData());
else {
// Stuff the uncommon case into a helper so that
// its register needs don't cause spills that effect
// the common case above.
return JIT_Unbox_Helper(type, obj);
}
}

FCThrow(kNullReferenceException);
return HCCALL2(Unbox_Helper_Framed, pMT1, obj);
}
HCIMPLEND

Expand Down
1 change: 1 addition & 0 deletions src/coreclr/src/vm/jitinterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ extern "C" FCDECL2(VOID, JIT_WriteBarrierEnsureNonHeapTarget, Object **dst, Obje

extern "C" FCDECL2(Object*, ChkCastAny_NoCacheLookup, CORINFO_CLASS_HANDLE type, Object* obj);
extern "C" FCDECL2(Object*, IsInstanceOfAny_NoCacheLookup, CORINFO_CLASS_HANDLE type, Object* obj);
extern "C" FCDECL2(LPVOID, Unbox_Helper, CORINFO_CLASS_HANDLE type, Object* obj);

extern "C" FCDECL1(void, JIT_InternalThrow, unsigned exceptNum);
extern "C" FCDECL1(void*, JIT_InternalThrowFromHelper, unsigned exceptNum);
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/src/vm/metasig.h
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ DEFINE_METASIG(SM(IntPtr_IntPtr_Int_Int_IntPtr_RetVoid, I I i i I, v))
DEFINE_METASIG(SM(IntPtr_RefObj_IntPtr_Obj_RetVoid, I r(j) I j, v))
DEFINE_METASIG(SM(Obj_Int_RetVoid, j i,v))
DEFINE_METASIG(SM(PtrVoid_Obj_RetObj, P(v) j, j))
DEFINE_METASIG(SM(PtrVoid_Obj_RetRefByte, P(v) j, r(b)))

DEFINE_METASIG(SM(Flt_RetFlt, f, f))
DEFINE_METASIG(SM(Dbl_RetDbl, d, d))
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/src/vm/methodtable.h
Original file line number Diff line number Diff line change
Expand Up @@ -2652,7 +2652,7 @@ class MethodTable
// GetInternalCorElementType() retrieves the internal representation of the type. It's not always
// appropiate to use this. For example, we treat enums as their underlying type or some structs are
// optimized to be ints. To get the signature type or the verifier type (same as signature except for
// enums are normalized to the primtive type that underlies them), use the APIs in Typehandle.h
// enums are normalized to the primitive type that underlies them), use the APIs in Typehandle.h
//
// * code:TypeHandle.GetSignatureCorElementType()
// * code:TypeHandle.GetVerifierCorElementType()
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/src/vm/mscorlib.h
Original file line number Diff line number Diff line change
Expand Up @@ -1461,6 +1461,7 @@ DEFINE_METHOD(CASTHELPERS, CHKCASTANY, ChkCastAny, SM_Ptr
DEFINE_METHOD(CASTHELPERS, CHKCASTINTERFACE, ChkCastInterface, SM_PtrVoid_Obj_RetObj)
DEFINE_METHOD(CASTHELPERS, CHKCASTCLASS, ChkCastClass, SM_PtrVoid_Obj_RetObj)
DEFINE_METHOD(CASTHELPERS, CHKCASTCLASSSPECIAL, ChkCastClassSpecial, SM_PtrVoid_Obj_RetObj)
DEFINE_METHOD(CASTHELPERS, UNBOX, Unbox, SM_PtrVoid_Obj_RetRefByte)

DEFINE_CLASS_U(CompilerServices, LAHashDependentHashTracker, LAHashDependentHashTrackerObject)
DEFINE_FIELD_U(_dependentHandle, LAHashDependentHashTrackerObject,_dependentHandle)
Expand Down