-
Notifications
You must be signed in to change notification settings - Fork 2.7k
JIT: recover types from helper calls and more #20447
Changes from 3 commits
2441b33
90a00f4
96b1c98
6609911
fea64cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} | ||
|
@@ -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; | ||
} | ||
|
||
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.