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

NativeAOT: Guarded devirtualization #64497

Merged
merged 40 commits into from
Aug 15, 2022
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
17c3b8c
Initial implementation
EgorBo Jan 29, 2022
e0a25e0
basic getExactClasses impl
EgorBo Jan 29, 2022
ad64be3
Merge branch 'main' of https://github.com/dotnet/runtime into gdv-nat…
EgorBo Feb 25, 2022
9643359
fix merge conflicts
EgorBo Feb 25, 2022
10ed069
fix merge conflicts
EgorBo Feb 25, 2022
84b0b50
fix merge conflicts
EgorBo Feb 25, 2022
ee67913
fix merge conflicts
EgorBo Feb 25, 2022
07f053e
Implement GetImplementingClasses
EgorBo Feb 25, 2022
29c0f93
Merge branch 'main' of https://github.com/dotnet/runtime into gdv-nat…
EgorBo Feb 25, 2022
ec97ddb
Make checks more conservative
EgorBo Feb 25, 2022
0685c07
Another attempt
EgorBo Feb 25, 2022
1439bcc
Merge branch 'main' of github.com:dotnet/runtime into gdv-nativeaot
EgorBo Jul 17, 2022
cda167d
update guid
EgorBo Jul 17, 2022
527b302
Merge branch 'main' of github.com:dotnet/runtime into gdv-nativeaot
EgorBo Jul 18, 2022
0e8adab
Fix importer logic
EgorBo Jul 18, 2022
a1617c6
Update importer.cpp
EgorBo Jul 19, 2022
9ada528
Merge branch 'main' of github.com:dotnet/runtime into gdv-nativeaot
EgorBo Jul 21, 2022
46f84a5
Don't use GDV
EgorBo Jul 21, 2022
f87300f
Clean up
EgorBo Jul 21, 2022
cd4aa36
Refactoring
EgorBo Jul 21, 2022
d9721c0
Clean up
EgorBo Jul 21, 2022
b492373
Update importer.cpp
EgorBo Jul 21, 2022
d83f514
Make things more conservative
MichalStrehovsky Jul 22, 2022
be28832
Clean up
EgorBo Jul 22, 2022
1359e43
Merge branch 'gdv-nativeaot' of github.com:EgorBo/runtime-1 into gdv-…
EgorBo Jul 22, 2022
a73bdef
Clean up
EgorBo Jul 22, 2022
90b8bfa
Clean up
EgorBo Jul 22, 2022
e76b472
IDynamicInterfaceCastable and template type loader cases
MichalStrehovsky Jul 25, 2022
c30f1ee
Address feedback
EgorBo Jul 27, 2022
497b843
ignore types which require runtime lookup
EgorBo Jul 28, 2022
19d6690
Merge branch 'main' into gdv-nativeaot
EgorBo Aug 12, 2022
e19f094
Merge branch 'main' of github.com:dotnet/runtime into gdv-nativeaot
EgorBo Aug 12, 2022
7559015
address feedback
EgorBo Aug 12, 2022
e4eca63
fix build
EgorBo Aug 12, 2022
cb74fc9
Merge branch 'main' into gdv-nativeaot
MichalStrehovsky Aug 12, 2022
d170052
Restore old logic
MichalStrehovsky Aug 12, 2022
6bf87db
One more lost diff
MichalStrehovsky Aug 12, 2022
6728e6a
Merge branch 'main' of github.com:dotnet/runtime into gdv-nativeaot
EgorBo Aug 15, 2022
6cd67e6
add JitEnableExactDevirtualization
EgorBo Aug 15, 2022
292d1d8
fix build
EgorBo Aug 15, 2022
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
9 changes: 9 additions & 0 deletions src/coreclr/inc/corinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -2790,6 +2790,15 @@ 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
// the exact classes is greater than maxExactClasses or if more types might be loaded
// in future.
virtual int getExactClasses(
CORINFO_CLASS_HANDLE baseType, /* IN */
int maxExactClasses, /* IN */
CORINFO_CLASS_HANDLE* exactClsRet /* OUT */
) = 0;

// If the Arg is a CORINFO_TYPE_CLASS fetch the class handle associated with it
virtual CORINFO_CLASS_HANDLE getArgClass (
CORINFO_SIG_INFO* sig, /* IN */
Expand Down
5 changes: 5 additions & 0 deletions src/coreclr/inc/icorjitinfoimpl_generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,11 @@ CorInfoTypeWithMod getArgType(
CORINFO_ARG_LIST_HANDLE args,
CORINFO_CLASS_HANDLE* vcTypeRet) override;

int getExactClasses(
CORINFO_CLASS_HANDLE baseType,
int maxExactClasses,
CORINFO_CLASS_HANDLE* exactClsRet) override;

CORINFO_CLASS_HANDLE getArgClass(
CORINFO_SIG_INFO* sig,
CORINFO_ARG_LIST_HANDLE args) 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 = { /* 4efa8fe2-8489-4b61-aac9-b4df74af15b7 */
0x4efa8fe2,
0x8489,
0x4b61,
{0xaa, 0xc9, 0xb4, 0xdf, 0x74, 0xaf, 0x15, 0xb7}
constexpr GUID JITEEVersionIdentifier = { /* 1b9551b8-21f4-4233-9c90-f3eabd6a322b */
0x1b9551b8,
0x21f4,
0x4233,
{0x9c, 0x90, 0xf3, 0xea, 0xbd, 0x6a, 0x32, 0x2b}
};

//////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/ICorJitInfo_API_names.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ DEF_CLR_API(allocateArray)
DEF_CLR_API(freeArray)
DEF_CLR_API(getArgNext)
DEF_CLR_API(getArgType)
DEF_CLR_API(getExactClasses)
DEF_CLR_API(getArgClass)
DEF_CLR_API(getHFAType)
DEF_CLR_API(GetErrorHRESULT)
Expand Down
11 changes: 11 additions & 0 deletions src/coreclr/jit/ICorJitInfo_API_wrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,17 @@ CorInfoTypeWithMod WrapICorJitInfo::getArgType(
return temp;
}

int WrapICorJitInfo::getExactClasses(
CORINFO_CLASS_HANDLE baseType,
int maxExactClasses,
CORINFO_CLASS_HANDLE* exactClsRet)
{
API_ENTER(getExactClasses);
int temp = wrapHnd->getExactClasses(baseType, maxExactClasses, exactClsRet);
API_LEAVE(getExactClasses);
return temp;
}

CORINFO_CLASS_HANDLE WrapICorJitInfo::getArgClass(
CORINFO_SIG_INFO* sig,
CORINFO_ARG_LIST_HANDLE args)
Expand Down
10 changes: 10 additions & 0 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17771,6 +17771,16 @@ CORINFO_CLASS_HANDLE Compiler::gtGetClassHandle(GenTree* tree, bool* pIsExact, b
}
}

if ((objClass != NO_CLASS_HANDLE) && !*pIsExact && IsTargetAbi(CORINFO_NATIVEAOT_ABI))
{
CORINFO_CLASS_HANDLE exactClass;
if (info.compCompHnd->getExactClasses(objClass, 1, &exactClass) == 1)
{
*pIsExact = true;
objClass = exactClass;
}
}

return objClass;
}

Expand Down
10 changes: 10 additions & 0 deletions src/coreclr/jit/lclvars.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3174,6 +3174,16 @@ void Compiler::lvaSetClass(unsigned varNum, CORINFO_CLASS_HANDLE clsHnd, bool is
return;
}

if (clsHnd != NO_CLASS_HANDLE && !isExact && IsTargetAbi(CORINFO_NATIVEAOT_ABI))
Copy link
Member

Choose a reason for hiding this comment

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

Is there a meaningful perf saving from skipping getExactClasses call outside NATIVEAOT_ABI? I assume it would just be a no-op. Not doing this outside NativeAOT is a policy decision and JIT generally tries to leave those to the EE side.

Could we instead key this off of JitEnableDevirtualization (or maybe a new config switch specifically for this)? Just so we have some way to see the impact of this or disable it if it misbehaves. On ilc side we have --noscan that would disable this, but it also disables a bunch of other things because it's for the whole program analysis in general.

{
CORINFO_CLASS_HANDLE exactClass;
if (info.compCompHnd->getExactClasses(clsHnd, 1, &exactClass) == 1)
{
isExact = true;
clsHnd = exactClass;
}
}

// Else we should have a type handle.
assert(clsHnd != nullptr);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType
/// so it can answer this question.
/// </remarks>
public virtual bool CanConstructType(TypeDesc type) => true;

public virtual TypeDesc[] GetImplementingClasses(TypeDesc type) => null;
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
#endif
}
}
160 changes: 88 additions & 72 deletions src/coreclr/tools/Common/JitInterface/CorInfoBase.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ FUNCTIONS
void freeArray(void*array);
CORINFO_ARG_LIST_HANDLE getArgNext(CORINFO_ARG_LIST_HANDLE args);
CorInfoTypeWithMod getArgType(CORINFO_SIG_INFO* sig, CORINFO_ARG_LIST_HANDLE args, CORINFO_CLASS_HANDLE* vcTypeRet);
int getExactClasses(CORINFO_CLASS_HANDLE baseType, int maxExactClasses, CORINFO_CLASS_HANDLE* exactClsRet);
CORINFO_CLASS_HANDLE getArgClass(CORINFO_SIG_INFO* sig, CORINFO_ARG_LIST_HANDLE args);
CorInfoHFAElemType getHFAType(CORINFO_CLASS_HANDLE hClass);
JITINTERFACE_HRESULT GetErrorHRESULT(struct _EXCEPTION_POINTERS *pExceptionPointers);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,11 @@ public bool IsEffectivelySealed(TypeDesc type)
return _devirtualizationManager.IsEffectivelySealed(type);
}

public TypeDesc[] GetImplementingClasses(TypeDesc type)
{
return _devirtualizationManager.GetImplementingClasses(type);
}

public bool IsEffectivelySealed(MethodDesc method)
{
return _devirtualizationManager.IsEffectivelySealed(method);
Expand Down
117 changes: 115 additions & 2 deletions src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,8 @@ private class ScannedDevirtualizationManager : DevirtualizationManager
private HashSet<TypeDesc> _constructedTypes = new HashSet<TypeDesc>();
private HashSet<TypeDesc> _canonConstructedTypes = new HashSet<TypeDesc>();
private HashSet<TypeDesc> _unsealedTypes = new HashSet<TypeDesc>();
private Dictionary<TypeDesc, HashSet<TypeDesc>> _interfaceImplementators = new();
private HashSet<TypeDesc> _disqualifiedInterfaces = new();

public ScannedDevirtualizationManager(ImmutableArray<DependencyNodeCore<NodeFactory>> markedNodes)
{
Expand All @@ -381,7 +383,32 @@ public ScannedDevirtualizationManager(ImmutableArray<DependencyNodeCore<NodeFact

if (type != null)
{
if (!type.IsInterface)
if (type.IsCanonicalSubtype(CanonicalFormKind.Any))
{
foreach (DefType baseInterface in type.RuntimeInterfaces)
{
// If the interface is implemented on a template type, there might be
// no real upper bound on the number of actual classes implementing it
// due to MakeGenericType.
if (CanAssumeWholeProgramViewOnInterfaceUse(baseInterface))
_disqualifiedInterfaces.Add(baseInterface);
}
}

if (type.IsInterface)
{
if (((MetadataType)type).IsDynamicInterfaceCastableImplementation())
{
foreach (DefType baseInterface in type.RuntimeInterfaces)
{
// If the interface is implemented through IDynamicInterfaceCastable, there might be
// no real upper bound on the number of actual classes implementing it.
if (CanAssumeWholeProgramViewOnInterfaceUse(baseInterface))
_disqualifiedInterfaces.Add(baseInterface);
}
}
}
else
{
//
// We collect this information:
Expand All @@ -390,15 +417,28 @@ public ScannedDevirtualizationManager(ImmutableArray<DependencyNodeCore<NodeFact
// 2. What types are the base types of other types
// This is needed for optimizations. We use this information to effectively
// seal types that are not base types for any other type.
// 3. What types implement interfaces for which use we can assume whole
// program view.
//

if (!type.IsCanonicalSubtype(CanonicalFormKind.Any))
_constructedTypes.Add(type);

if (type is not MetadataType { IsAbstract: true })
{
// Record all interfaces this class implements to _interfaceImplementators
foreach (DefType baseInterface in type.RuntimeInterfaces)
{
if (CanAssumeWholeProgramViewOnInterfaceUse(baseInterface))
{
RecordImplementation(baseInterface, type);
}
}
}

TypeDesc canonType = type.ConvertToCanonForm(CanonicalFormKind.Specific);
_canonConstructedTypes.Add(canonType.GetClosestDefType());

bool hasNonAbstractTypeInHierarchy = canonType is not MetadataType mdType || !mdType.IsAbstract;
TypeDesc baseType = canonType.BaseType;
bool added = true;
while (baseType != null && added)
Expand All @@ -412,6 +452,61 @@ public ScannedDevirtualizationManager(ImmutableArray<DependencyNodeCore<NodeFact
}
}

private static bool CanAssumeWholeProgramViewOnInterfaceUse(DefType interfaceType)
{
if (!interfaceType.HasInstantiation)
{
return true;
}

foreach (GenericParameterDesc genericParam in interfaceType.GetTypeDefinition().Instantiation)
{
if (genericParam.Variance != GenericVariance.None)
{
// If the interface has any variance, this gets complicated.
// Skip for now.
return false;
}
}

if (((CompilerTypeSystemContext)interfaceType.Context).IsGenericArrayInterfaceType(interfaceType))
{
// Interfaces implemented by arrays also behave covariantly on arrays even though
// they're not actually variant. Skip for now.
return false;
}

if (interfaceType.IsCanonicalSubtype(CanonicalFormKind.Any)
|| interfaceType.ConvertToCanonForm(CanonicalFormKind.Specific) != interfaceType
|| interfaceType.Context.SupportsUniversalCanon)
{
// If the interface has a canonical form, we might not have a full view of all implementers.
// E.g. if we have:
// class Fooer<T> : IFooable<T> { }
// class Doer<T> : IFooable<T> { }
// And we instantiated Fooer<string>, but not Doer<string>. But we do have code for Doer<__Canon>.
// We might think we can devirtualize IFooable<string> to Fooer<string>, but someone could
// typeof(Doer<>).MakeGenericType(typeof(string)) and break our whole program view.
// This is only a problem if canonical form of the interface exists.
return false;
}

return true;
}

private void RecordImplementation(TypeDesc type, TypeDesc implType)
{
Debug.Assert(!implType.IsInterface);

HashSet<TypeDesc> implList;
if (!_interfaceImplementators.TryGetValue(type, out implList))
{
implList = new();
_interfaceImplementators[type] = implList;
}
implList.Add(implType);
}

public override bool IsEffectivelySealed(TypeDesc type)
{
// If we know we scanned a type that derives from this one, this for sure can't be reported as sealed.
Expand Down Expand Up @@ -456,6 +551,24 @@ protected override MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefTyp
}

public override bool CanConstructType(TypeDesc type) => _constructedTypes.Contains(type);

public override TypeDesc[] GetImplementingClasses(TypeDesc type)
{
if (_disqualifiedInterfaces.Contains(type))
return null;

if (type.IsInterface && _interfaceImplementators.TryGetValue(type, out HashSet<TypeDesc> implementations))
{
var types = new TypeDesc[implementations.Count];
int index = 0;
foreach (TypeDesc implementation in implementations)
{
types[index++] = implementation;
}
return types;
}
return null;
}
}

private class ScannedInliningPolicy : IInliningPolicy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2969,5 +2969,11 @@ private void updateEntryPointForTailCall(ref CORINFO_CONST_LOOKUP entryPoint)

entryPoint = CreateConstLookupToSymbol(newEntryPoint);
}

private int getExactClasses(CORINFO_CLASS_STRUCT_* baseType, int maxExactClasses, CORINFO_CLASS_STRUCT_** exactClsRet)
{
// Not implemented for R2R yet
return 0;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2154,5 +2154,43 @@ private void getFieldInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_MET
// TODO: We need to implement access checks for fields and methods. See JitInterface.cpp in mrtjit
// and STS::AccessCheck::CanAccess.
}

private int getExactClasses(CORINFO_CLASS_STRUCT_* baseType, int maxExactClasses, CORINFO_CLASS_STRUCT_** exactClsRet)
{
MetadataType type = HandleToObject(baseType) as MetadataType;
if (type == null)
{
return 0;
}

// type is already sealed, return it
if (_compilation.IsEffectivelySealed(type))
{
*exactClsRet = baseType;
return 1;
}

if (!type.IsInterface)
{
// TODO: handle classes
return 0;
}

TypeDesc[] implClasses = _compilation.GetImplementingClasses(type);
if (implClasses == null || implClasses.Length > maxExactClasses)
{
return 0;
}

int index = 0;
foreach (TypeDesc implClass in implClasses)
{
Debug.Assert(!implClass.IsCanonicalSubtype(CanonicalFormKind.Any));
exactClsRet[index++] = ObjectToHandle(implClass);
}

Debug.Assert(index <= maxExactClasses);
return index;
}
}
}
12 changes: 12 additions & 0 deletions src/coreclr/tools/aot/jitinterface/jitinterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ struct JitInterfaceCallbacks
void (* freeArray)(void * thisHandle, CorInfoExceptionClass** ppException, void* array);
CORINFO_ARG_LIST_HANDLE (* getArgNext)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_ARG_LIST_HANDLE args);
CorInfoTypeWithMod (* getArgType)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_SIG_INFO* sig, CORINFO_ARG_LIST_HANDLE args, CORINFO_CLASS_HANDLE* vcTypeRet);
int (* getExactClasses)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE baseType, int maxExactClasses, CORINFO_CLASS_HANDLE* exactClsRet);
CORINFO_CLASS_HANDLE (* getArgClass)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_SIG_INFO* sig, CORINFO_ARG_LIST_HANDLE args);
CorInfoHFAElemType (* getHFAType)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE hClass);
JITINTERFACE_HRESULT (* GetErrorHRESULT)(void * thisHandle, CorInfoExceptionClass** ppException, struct _EXCEPTION_POINTERS* pExceptionPointers);
Expand Down Expand Up @@ -1203,6 +1204,17 @@ class JitInterfaceWrapper : public ICorJitInfo
return temp;
}

virtual int getExactClasses(
CORINFO_CLASS_HANDLE baseType,
int maxExactClasses,
CORINFO_CLASS_HANDLE* exactClsRet)
{
CorInfoExceptionClass* pException = nullptr;
int temp = _callbacks->getExactClasses(_thisHandle, &pException, baseType, maxExactClasses, exactClsRet);
if (pException != nullptr) throw pException;
return temp;
}

virtual CORINFO_CLASS_HANDLE getArgClass(
CORINFO_SIG_INFO* sig,
CORINFO_ARG_LIST_HANDLE args)
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ LWM(GetAddrOfCaptureThreadGlobal, DWORD, DLDL)
LWM(GetArgClass, Agnostic_GetArgClass_Key, Agnostic_GetArgClass_Value)
LWM(GetArgNext, DWORDLONG, DWORDLONG)
LWM(GetArgType, Agnostic_GetArgType_Key, Agnostic_GetArgType_Value)
LWM(GetExactClasses, DLD, DLD)
LWM(GetArrayInitializationData, DLD, DWORDLONG)
LWM(GetArrayRank, DWORDLONG, DWORD)
LWM(GetArrayIntrinsicID, DWORDLONG, DWORD)
Expand Down
Loading