Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

JIT: recover types from helper calls and more #20447

Merged
merged 5 commits into from
Oct 24, 2018
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions src/System.Private.CoreLib/src/System/Internal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ private static async Task AsyncHelper3()
// typed as matching instantiations of mscorlib copies of WinRT interfaces (IIterable<T>, IVector<T>,
// IMap<K, V>, ...) which is necessary to generate all required IL stubs.

[MethodImplAttribute(MethodImplOptions.NoOptimization)]
private static void CommonlyUsedWinRTRedirectedInterfaceStubs()
{
WinRT_IEnumerable<byte>(null, null, null);
Expand Down
4 changes: 4 additions & 0 deletions src/System.Private.CoreLib/src/System/RtType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4532,6 +4532,10 @@ internal override string FormatTypeName(bool serialization)
}
}

// This method looks like an attractive inline but expands to two calls,
// neither of which can be inlined or optimized further. So block it
// from inlining.
[MethodImpl(MethodImplOptions.NoInlining)]

Choose a reason for hiding this comment

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

It would be nice if in the future the JIT could discover this fact and have a way to persist it so the next time we try to ngen/jit it we consult the database of known methods for this kind of information.
Perhaphs could add an option to ngen/crossgen to create a simplist .IBC data file that contained this information and then check it in along with the real .IBC data.

Copy link
Member Author

Choose a reason for hiding this comment

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

I like the idea but do not know how we'd actually do something like this.

I think it might be easier to improve the inlining heuristics in this area (a relatively small method body has multiple sequential calls that are not themselves inline candidates). A strong enough conviction here would automatically propagate noinline into the runtime metadata when prejitting.

Copy link
Member Author

Choose a reason for hiding this comment

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

Opened #20526 to track this idea.

private string GetCachedName(TypeNameKind kind)
{
return Cache.GetName(kind);
Expand Down
166 changes: 166 additions & 0 deletions src/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16563,6 +16563,114 @@ CORINFO_CLASS_HANDLE Compiler::gtGetClassHandle(GenTree* tree, bool* isExact, bo
objClass = sig.retTypeClass;
}
}
else if (call->gtCallType == CT_HELPER)
{
const CorInfoHelpFunc helper = eeGetHelperNum(call->gtCallMethHnd);
bool isCastHelper = false;

switch (helper)
{
case CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE:
case CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE_MAYBENULL:
{
// Note for some runtimes these helpers return exact types.
//
// But in those cases the types are also sealed, so there's no
// need to claim exactness here.
const bool helperResultNonNull = (helper == CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE);
CORINFO_CLASS_HANDLE runtimeType = info.compCompHnd->getBuiltinClass(CLASSID_RUNTIME_TYPE);

objClass = runtimeType;
*isNonNull = helperResultNonNull;
break;
}

case CORINFO_HELP_CHKCASTCLASS:
case CORINFO_HELP_CHKCASTANY:
case CORINFO_HELP_CHKCASTARRAY:
case CORINFO_HELP_CHKCASTINTERFACE:
case CORINFO_HELP_CHKCASTCLASS_SPECIAL:
isCastHelper = true;
__fallthrough;

case CORINFO_HELP_ISINSTANCEOFINTERFACE:
case CORINFO_HELP_ISINSTANCEOFARRAY:
case CORINFO_HELP_ISINSTANCEOFCLASS:
case CORINFO_HELP_ISINSTANCEOFANY:
{
// Fetch the class handle from the helper call arglist
GenTreeArgList* args = call->gtCallArgs;
GenTree* typeArg = args->Current();
CORINFO_CLASS_HANDLE castHnd = gtGetHelperArgClassHandle(typeArg);

// We generally assume the type being cast to the best type
AndyAyersMS marked this conversation as resolved.
Show resolved Hide resolved
// for the result, unless it is an interface type.
//
// Todo: when we have default interface methods then this
AndyAyersMS marked this conversation as resolved.
Show resolved Hide resolved
// might not be the best assumption.
if (castHnd != nullptr)
{
DWORD attrs = info.compCompHnd->getClassAttribs(castHnd);

if ((attrs & CORINFO_FLG_INTERFACE) != 0)
{
castHnd = nullptr;
}
}

// If we don't have a good estimate for the type we can use the
// type from the value being cast instead.
if (castHnd == nullptr)
{
GenTree* valueArg = args->Rest()->Current();
castHnd = gtGetClassHandle(valueArg, isExact, isNonNull);
}

// We don't know at jit time if the cast will succeed or fail, but if it
// fails at runtime then an exception is thrown for cast helpers, or the
// result is set null for instance helpers.
//
// So it safe to claim the result has the cast type, and that if we have a
// cast the result is not null.
//
// Note we don't know for sure that it is exactly this type.
if (castHnd != nullptr)
{
objClass = castHnd;
*isExact = false;
*isNonNull = isCastHelper;
Copy link

@pentp pentp Oct 17, 2018

Choose a reason for hiding this comment

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

CORINFO_HELP_CHKCAST* methods guarantee a non-null result only if the result type is a struct. Is this checked somehow before that already?

Copy link

Choose a reason for hiding this comment

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

It looks like in some cases the helpers are called only after a null check and direct MT compare have been done inline, but in other cases the helper is called directly, e.g. for array casts.

Copy link
Member Author

Choose a reason for hiding this comment

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

No, it's not always checked. Thanks for catching this...

}

break;
}

default:
break;
}
}

break;
AndyAyersMS marked this conversation as resolved.
Show resolved Hide resolved
}

case GT_INTRINSIC:
{
GenTreeIntrinsic* intrinsic = obj->AsIntrinsic();

switch (intrinsic->gtIntrinsicId)
AndyAyersMS marked this conversation as resolved.
Show resolved Hide resolved
{
case CORINFO_INTRINSIC_Object_GetType:
{
CORINFO_CLASS_HANDLE runtimeType = info.compCompHnd->getBuiltinClass(CLASSID_RUNTIME_TYPE);

objClass = runtimeType;
*isExact = false;
*isNonNull = true;
break;
}

default:
break;
}

break;
}
Expand Down Expand Up @@ -16606,7 +16714,65 @@ CORINFO_CLASS_HANDLE Compiler::gtGetClassHandle(GenTree* tree, bool* isExact, bo
*isExact = false;
*isNonNull = false;
}
else if (base->OperGet() == GT_ADD)
{
// This could be a static field access.
//
// See if op1 is a static field base helper call
// and if so, op2 will have the field info.
GenTree* op1 = base->gtOp.gtOp1;
GenTree* op2 = base->gtOp.gtOp2;

bool op1IsStaticFieldBase = false;

if (op1->OperGet() == GT_CALL)
{
GenTreeCall* op1Call = op1->AsCall();

if (op1Call->gtCallType == CT_HELPER)
{
const CorInfoHelpFunc helper = eeGetHelperNum(op1Call->gtCallMethHnd);
switch (helper)
{
// We are looking for a REF type so only need to check for the GC base helpers
case CORINFO_HELP_GETGENERICS_GCSTATIC_BASE:
case CORINFO_HELP_GETSHARED_GCSTATIC_BASE:
case CORINFO_HELP_GETSHARED_GCSTATIC_BASE_NOCTOR:
case CORINFO_HELP_GETSHARED_GCSTATIC_BASE_DYNAMICCLASS:
case CORINFO_HELP_GETGENERICS_GCTHREADSTATIC_BASE:
case CORINFO_HELP_GETSHARED_GCTHREADSTATIC_BASE:
case CORINFO_HELP_GETSHARED_GCTHREADSTATIC_BASE_NOCTOR:
case CORINFO_HELP_GETSHARED_GCTHREADSTATIC_BASE_DYNAMICCLASS:
op1IsStaticFieldBase = true;
AndyAyersMS marked this conversation as resolved.
Show resolved Hide resolved
default:
break;
}
}
}

if (op1IsStaticFieldBase && (op2->OperGet() == GT_CNS_INT))
{
FieldSeqNode* fieldSeq = op2->AsIntCon()->gtFieldSeq;

if (fieldSeq != nullptr)
{
while (fieldSeq->m_next != nullptr)
{
fieldSeq = fieldSeq->m_next;
}

CORINFO_FIELD_HANDLE fieldHnd = fieldSeq->m_fieldHnd;
CORINFO_CLASS_HANDLE fieldClass = nullptr;
CorInfoType fieldCorType = info.compCompHnd->getFieldType(fieldHnd, &fieldClass);
if (fieldCorType == CORINFO_TYPE_CLASS)
{
objClass = fieldClass;
}
}
}
}
}

break;
}

Expand Down