From b5d6fab5b40a6948b515b4f8f118c58baad573fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 10 Jul 2025 15:08:35 +0200 Subject: [PATCH 1/9] Limit expansion in `TemplateConstructableTypes` The template type loader is a limited type loader that we use in native AOT to support scenarios such as `MakeGenericType`. We keep around extra MethodTables and metadata to be able to construct new type instantiations (e.g. `List`) at runtime from template instantiations (e.g. `List<__Canon>`) we made at compile time. The template instantiation is a `MethodTable` like any other that we make a copy of and patch up with the help of the metadata ("native layout metadata"). Patching up involves e.g. building interface list (`List` should have `IList` in the interface list). This patching up may involve loading more new types from templates (e.g. the mentioned `IList` that needs to be loadable from a `IList<__Canon>` template MethodTable). The job of the compiler is to figure out all the templates we might need (recursively) to build a type. This is done in places using a rather non-exact `TemplateConstructableTypes` call that just decomposes the type and makes templates for _everything_. Some of these might not be actually needed. This is an attempt to somewhat limit it. --- .../NativeLayoutVertexNode.cs | 46 +++---------------- .../NodeFactory.NativeLayout.cs | 9 ++-- 2 files changed, 12 insertions(+), 43 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs index c57f8ad072c233..5fa3d47158551d 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs @@ -650,27 +650,6 @@ protected override IMethodNode GetMethodEntrypointNode(NodeFactory factory) IMethodNode methodEntryPointNode = factory.AddressTakenMethodEntrypoint(_method, IsUnboxingStub); return methodEntryPointNode; } - - public override IEnumerable GetStaticDependencies(NodeFactory context) - { - DependencyList dependencies = (DependencyList)base.GetStaticDependencies(context); - - foreach (var arg in _method.Instantiation) - { - foreach (var dependency in context.NativeLayout.TemplateConstructableTypes(arg)) - { - dependencies.Add(new DependencyListEntry(dependency, "Dependencies to make a generic method template viable Method Instantiation")); - } - } - - - foreach (var dependency in context.NativeLayout.TemplateConstructableTypes(_method.OwningType)) - { - dependencies.Add(new DependencyListEntry(dependency, "Dependencies to make a generic method template viable OwningType")); - } - - return dependencies; - } } public sealed class NativeLayoutDictionarySignatureNode : NativeLayoutSavedVertexNode @@ -788,19 +767,6 @@ public NativeLayoutTemplateMethodLayoutVertexNode(NodeFactory factory, MethodDes public override IEnumerable GetStaticDependencies(NodeFactory context) { - foreach (var dependency in context.NativeLayout.TemplateConstructableTypes(_method.OwningType)) - { - yield return new DependencyListEntry(dependency, "method OwningType itself must be template loadable"); - } - - foreach (var type in _method.Instantiation) - { - foreach (var dependency in context.NativeLayout.TemplateConstructableTypes(type)) - { - yield return new DependencyListEntry(dependency, "method's instantiation arguments must be template loadable"); - } - } - foreach (GenericParameterDesc genericParam in _method.GetTypicalMethodDefinition().Instantiation) { foreach (TypeDesc typeConstraint in genericParam.TypeConstraints) @@ -1103,6 +1069,7 @@ public abstract class NativeLayoutTypeSignatureBasedGenericDictionarySlotNode : public NativeLayoutTypeSignatureBasedGenericDictionarySlotNode(NodeFactory factory, TypeDesc type) { + Debug.Assert(type.IsRuntimeDeterminedSubtype); _signature = factory.NativeLayout.TypeSignatureVertex(type); _type = type; } @@ -1239,6 +1206,7 @@ public sealed class NativeLayoutInterfaceDispatchGenericDictionarySlotNode : Nat public NativeLayoutInterfaceDispatchGenericDictionarySlotNode(NodeFactory factory, MethodDesc method) { + Debug.Assert(method.IsRuntimeDeterminedExactMethod); _signature = factory.NativeLayout.TypeSignatureVertex(method.OwningType); _method = method; } @@ -1250,16 +1218,14 @@ public sealed override IEnumerable GetStaticDependencies(No { yield return new DependencyListEntry(_signature, "TypeSignature"); - MethodDesc method = _method; - if (method.IsRuntimeDeterminedExactMethod) - method = method.GetCanonMethodTarget(CanonicalFormKind.Specific); + MethodDesc canonMethod = _method.GetCanonMethodTarget(CanonicalFormKind.Specific); - if (!factory.VTable(method.OwningType).HasKnownVirtualMethodUse) + if (!factory.VTable(canonMethod.OwningType).HasKnownVirtualMethodUse) { - yield return new DependencyListEntry(factory.VirtualMethodUse(method), "Slot number"); + yield return new DependencyListEntry(factory.VirtualMethodUse(canonMethod), "Slot number"); } - foreach (var dependency in factory.NativeLayout.TemplateConstructableTypes(method.OwningType)) + foreach (var dependency in factory.NativeLayout.TemplateConstructableTypes(_method.OwningType)) { yield return new DependencyListEntry(dependency, "template construction dependency"); } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.NativeLayout.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.NativeLayout.cs index 5918b71a451b34..84bec011b3133e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.NativeLayout.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.NativeLayout.cs @@ -150,6 +150,12 @@ private void CreateNodeCaches() // of just necessary. (Which is what the actual templates signatures will ensure) public IEnumerable TemplateConstructableTypes(TypeDesc type) { + if (!type.IsRuntimeDeterminedSubtype) + { + yield return _factory.MaximallyConstructableType(type); + yield break; + } + // Array types are the only parameterized types that have templates if (type.IsSzArray && !type.IsArrayTypeWithoutGenericInterfaces()) { @@ -160,8 +166,6 @@ public IEnumerable TemplateConstructableTypes(TypeDesc type) { yield return _factory.NativeLayout.TemplateTypeLayout(arrayCanonicalType); } - - yield return _factory.MaximallyConstructableType(arrayCanonicalType); } while (type.IsParameterizedType) @@ -184,7 +188,6 @@ public IEnumerable TemplateConstructableTypes(TypeDesc type) } TypeDesc canonicalType = type.ConvertToCanonForm(CanonicalFormKind.Specific); - yield return _factory.MaximallyConstructableType(canonicalType); // Add a dependency on the template for this type, if the canonical type should be generated into this binary. if (canonicalType.IsCanonicalSubtype(CanonicalFormKind.Any) && !_factory.NecessaryTypeSymbol(canonicalType).RepresentsIndirectionCell) From d702d951d172bb45e5e28f5da401f9f15871203a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 7 Aug 2025 10:48:01 +0200 Subject: [PATCH 2/9] More aggressive --- .../NativeLayoutVertexNode.cs | 109 +++++------------- .../NodeFactory.NativeLayout.cs | 60 ---------- .../SmokeTests/DynamicGenerics/TypeOfRepo.cs | 4 - .../DynamicGenerics/dictionaries.cs | 8 +- 4 files changed, 31 insertions(+), 150 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs index 5fa3d47158551d..206ab1e3729dc5 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs @@ -328,7 +328,20 @@ public NativeLayoutParameterizedTypeSignatureVertexNode(NodeFactory factory, Typ } public override IEnumerable GetStaticDependencies(NodeFactory context) { - return new DependencyListEntry[] { new DependencyListEntry(_parameterTypeSig, "NativeLayoutParameterizedTypeSignatureVertexNode parameter type signature") }; + if (!_type.ContainsSignatureVariables()) + { + if (_type.IsRuntimeDeterminedSubtype) + { + if (GenericTypesTemplateMap.IsArrayTypeEligibleForTemplate(_type)) + yield return new DependencyListEntry(context.NativeLayout.TemplateTypeLayout(_type.ConvertToCanonForm(CanonicalFormKind.Specific)), "Array template"); + } + else + { + yield return new DependencyListEntry(context.NecessaryTypeSymbol(_type), "Non-shared array"); + } + } + + yield return new DependencyListEntry(_parameterTypeSig, "NativeLayoutParameterizedTypeSignatureVertexNode parameter type signature"); } public override Vertex WriteVertex(NodeFactory factory) { @@ -428,6 +441,18 @@ public override IEnumerable GetStaticDependencies(NodeFacto { DependencyList dependencies = new DependencyList(); + if (!_type.ContainsSignatureVariables()) + { + if (_type.IsRuntimeDeterminedSubtype) + { + dependencies.Add(context.NativeLayout.TemplateTypeLayout(_type.ConvertToCanonForm(CanonicalFormKind.Specific)), "Generic type template"); + } + else + { + dependencies.Add(context.NecessaryTypeSymbol(_type), "Non-shared generic type"); + } + } + dependencies.Add(new DependencyListEntry(_genericTypeDefSig, "NativeLayoutInstantiatedTypeSignatureVertexNode generic definition signature")); foreach (var arg in _instantiationArgs) dependencies.Add(new DependencyListEntry(arg, "NativeLayoutInstantiatedTypeSignatureVertexNode instantiation argument signature")); @@ -871,21 +896,11 @@ public override IEnumerable GetStaticDependencies(NodeFacto yield return new DependencyListEntry(typeNode, "Template MethodTable"); - foreach (var dependency in context.NativeLayout.TemplateConstructableTypes(_type)) - { - yield return new DependencyListEntry(dependency, "type itslef must be template loadable"); - } - yield return new DependencyListEntry(context.GenericDictionaryLayout(_type.ConvertToCanonForm(CanonicalFormKind.Specific).GetClosestDefType()), "Dictionary layout"); foreach (TypeDesc iface in _type.RuntimeInterfaces) { yield return new DependencyListEntry(context.NativeLayout.TypeSignatureVertex(iface), "template interface list"); - - foreach (var dependency in context.NativeLayout.TemplateConstructableTypes(iface)) - { - yield return new DependencyListEntry(dependency, "interface type dependency must be template loadable"); - } } if (context.PreinitializationManager.HasLazyStaticConstructor(_type.ConvertToCanonForm(CanonicalFormKind.Specific))) @@ -926,11 +941,6 @@ public override IEnumerable GetStaticDependencies(NodeFacto if (_type.BaseType != null && _type.BaseType.IsRuntimeDeterminedSubtype) { yield return new DependencyListEntry(context.NativeLayout.PlacedSignatureVertex(context.NativeLayout.TypeSignatureVertex(_type.BaseType)), "template base type"); - - foreach (var dependency in context.NativeLayout.TemplateConstructableTypes(_type.BaseType)) - { - yield return new DependencyListEntry(dependency, "base type must be template loadable"); - } } } @@ -1080,11 +1090,6 @@ public NativeLayoutTypeSignatureBasedGenericDictionarySlotNode(NodeFactory facto public sealed override IEnumerable GetStaticDependencies(NodeFactory factory) { yield return new DependencyListEntry(_signature, "TypeSignature"); - - foreach (var dependency in factory.NativeLayout.TemplateConstructableTypes(_type)) - { - yield return new DependencyListEntry(dependency, "template construction dependency"); - } } protected sealed override Vertex WriteSignatureVertex(NativeWriter writer, NodeFactory factory) @@ -1169,10 +1174,7 @@ public sealed override IEnumerable GetStaticDependencies(No { yield return new DependencyListEntry(_signature, "TypeSignature"); - foreach (var dependency in factory.NativeLayout.TemplateConstructableTypes(_type)) - { - yield return new DependencyListEntry(dependency, "template construction dependency"); - } + yield return new DependencyListEntry(factory.NativeLayout.TemplateTypeLayout(_type.ConvertToCanonForm(CanonicalFormKind.Specific)), "Template"); } protected sealed override Vertex WriteSignatureVertex(NativeWriter writer, NodeFactory factory) @@ -1224,11 +1226,6 @@ public sealed override IEnumerable GetStaticDependencies(No { yield return new DependencyListEntry(factory.VirtualMethodUse(canonMethod), "Slot number"); } - - foreach (var dependency in factory.NativeLayout.TemplateConstructableTypes(_method.OwningType)) - { - yield return new DependencyListEntry(dependency, "template construction dependency"); - } } protected sealed override Vertex WriteSignatureVertex(NativeWriter writer, NodeFactory factory) @@ -1259,17 +1256,6 @@ public sealed override IEnumerable GetStaticDependencies(No { var dependencies = new DependencyList(); - foreach (var dependency in factory.NativeLayout.TemplateConstructableTypes(_method.OwningType)) - { - dependencies.Add(dependency, "template construction dependency for method OwningType"); - } - - foreach (var type in _method.Instantiation) - { - foreach (var dependency in factory.NativeLayout.TemplateConstructableTypes(type)) - dependencies.Add(dependency, "template construction dependency for method Instantiation types"); - } - GenericMethodsTemplateMap.GetTemplateMethodDependencies(ref dependencies, factory, _method.GetCanonMethodTarget(CanonicalFormKind.Specific)); dependencies.Add(factory.NativeLayout.MethodEntry(_method), "wrappednode"); @@ -1304,11 +1290,6 @@ public sealed override IEnumerable GetStaticDependencies(No { factory.NativeLayout.TypeSignatureVertex(_field.OwningType), "Owning type of field" } }; - foreach (var dependency in factory.NativeLayout.TemplateConstructableTypes(_field.OwningType)) - { - result.Add(dependency, "template construction dependency"); - } - var canonOwningType = (InstantiatedType)_field.OwningType.ConvertToCanonForm(CanonicalFormKind.Specific); FieldDesc canonField = factory.TypeSystemContext.GetFieldForInstantiatedType(_field.GetTypicalFieldDefinition(), canonOwningType); factory.MetadataManager.GetDependenciesDueToLdToken(ref result, factory, canonField); @@ -1343,17 +1324,6 @@ public sealed override IEnumerable GetStaticDependencies(No { var result = new DependencyList(); - foreach (var dependency in factory.NativeLayout.TemplateConstructableTypes(_method.OwningType)) - { - result.Add(dependency, "template construction dependency for method OwningType"); - } - - foreach (var type in _method.Instantiation) - { - foreach (var dependency in factory.NativeLayout.TemplateConstructableTypes(type)) - result.Add(dependency, "template construction dependency for method Instantiation types"); - } - factory.MetadataManager.GetDependenciesDueToLdToken(ref result, factory, _method.GetCanonMethodTarget(CanonicalFormKind.Specific)); result.Add(factory.NativeLayout.MethodEntry(_method), "wrappednode"); @@ -1418,20 +1388,6 @@ public sealed override IEnumerable GetStaticDependencies(No yield return new DependencyListEntry(constrainedMethodDescriptorNode, "ConstrainedMethodType"); - foreach (var dependency in factory.NativeLayout.TemplateConstructableTypes(_constrainedMethod.OwningType)) - { - yield return new DependencyListEntry(dependency, "template construction dependency constrainedMethod OwningType"); - } - - foreach (var type in _constrainedMethod.Instantiation) - { - foreach (var dependency in factory.NativeLayout.TemplateConstructableTypes(type)) - yield return new DependencyListEntry(dependency, "template construction dependency constrainedMethod Instantiation type"); - } - - foreach (var dependency in factory.NativeLayout.TemplateConstructableTypes(_constraintType)) - yield return new DependencyListEntry(dependency, "template construction dependency constraintType"); - if (_constrainedMethod.IsVirtual && _constrainedMethod.HasInstantiation) { MethodDesc canonMethod = _constrainedMethod.GetCanonMethodTarget(CanonicalFormKind.Specific); @@ -1500,17 +1456,6 @@ public sealed override IEnumerable GetStaticDependencies(No { DependencyList dependencies = new DependencyList(); - foreach (var dependency in factory.NativeLayout.TemplateConstructableTypes(_method.OwningType)) - { - dependencies.Add(dependency, "template construction dependency for method OwningType"); - } - - foreach (var type in _method.Instantiation) - { - foreach (var dependency in factory.NativeLayout.TemplateConstructableTypes(type)) - dependencies.Add(dependency, "template construction dependency for method Instantiation types"); - } - GenericMethodsTemplateMap.GetTemplateMethodDependencies(ref dependencies, factory, _method.GetCanonMethodTarget(CanonicalFormKind.Specific)); dependencies.Add(_wrappedNode, "wrappednode"); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.NativeLayout.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.NativeLayout.cs index 84bec011b3133e..4d74a095b6ecda 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.NativeLayout.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.NativeLayout.cs @@ -143,66 +143,6 @@ private void CreateNodeCaches() }); } - // Produce a set of dependencies that is necessary such that if this type - // needs to be used referenced from a NativeLayout template, that the template - // will be properly constructable. (This is done by ensuring that all - // canonical types in the deconstruction of the type are ConstructedEEType instead - // of just necessary. (Which is what the actual templates signatures will ensure) - public IEnumerable TemplateConstructableTypes(TypeDesc type) - { - if (!type.IsRuntimeDeterminedSubtype) - { - yield return _factory.MaximallyConstructableType(type); - yield break; - } - - // Array types are the only parameterized types that have templates - if (type.IsSzArray && !type.IsArrayTypeWithoutGenericInterfaces()) - { - TypeDesc arrayCanonicalType = type.ConvertToCanonForm(CanonicalFormKind.Specific); - - // Add a dependency on the template for this type, if the canonical type should be generated into this binary. - if (arrayCanonicalType.IsCanonicalSubtype(CanonicalFormKind.Any) && !_factory.NecessaryTypeSymbol(arrayCanonicalType).RepresentsIndirectionCell) - { - yield return _factory.NativeLayout.TemplateTypeLayout(arrayCanonicalType); - } - } - - while (type.IsParameterizedType) - { - type = ((ParameterizedType)type).ParameterType; - } - - if (type.IsFunctionPointer) - { - MethodSignature sig = ((FunctionPointerType)type).Signature; - foreach (var dependency in TemplateConstructableTypes(sig.ReturnType)) - yield return dependency; - - foreach (var param in sig) - foreach (var dependency in TemplateConstructableTypes(param)) - yield return dependency; - - // Nothing else to do for function pointers - yield break; - } - - TypeDesc canonicalType = type.ConvertToCanonForm(CanonicalFormKind.Specific); - - // Add a dependency on the template for this type, if the canonical type should be generated into this binary. - if (canonicalType.IsCanonicalSubtype(CanonicalFormKind.Any) && !_factory.NecessaryTypeSymbol(canonicalType).RepresentsIndirectionCell) - { - if (!_factory.TypeSystemContext.IsCanonicalDefinitionType(canonicalType, CanonicalFormKind.Any)) - yield return _factory.NativeLayout.TemplateTypeLayout(canonicalType); - } - - foreach (TypeDesc instantiationType in type.Instantiation) - { - foreach (var dependency in TemplateConstructableTypes(instantiationType)) - yield return dependency; - } - } - private NodeCache _typeSignatures; internal NativeLayoutTypeSignatureVertexNode TypeSignatureVertex(TypeDesc type) { diff --git a/src/tests/nativeaot/SmokeTests/DynamicGenerics/TypeOfRepo.cs b/src/tests/nativeaot/SmokeTests/DynamicGenerics/TypeOfRepo.cs index 160df9bf78e2c1..3a33614ac6595b 100644 --- a/src/tests/nativeaot/SmokeTests/DynamicGenerics/TypeOfRepo.cs +++ b/src/tests/nativeaot/SmokeTests/DynamicGenerics/TypeOfRepo.cs @@ -194,8 +194,6 @@ static TypeOf() s_TypeRepo["D_IFace"] = typeof(Dictionaries.IFace<>); s_TypeRepo["D_IFace3"] = typeof(Dictionaries.IFace3<>); s_TypeRepo["D_SingleUseArrayOnlyGen"] = typeof(Dictionaries.SingleUseArrayOnlyGen<>); - s_TypeRepo["D_GenericStruct"] = typeof(Dictionaries.GenericStruct<>); - s_TypeRepo["D_NullableTest"] = typeof(Dictionaries.NullableTest<>); s_TypeRepo["D_DelegateTarget"] = typeof(Dictionaries.DelegateTarget<>); s_TypeRepo["D_DelWithNullable"] = typeof(Dictionaries.DelWithNullable<>); @@ -448,8 +446,6 @@ static TypeOf() public static Type D_IFace { get { return s_TypeRepo["D_IFace"]; } } public static Type D_IFace3 { get { return s_TypeRepo["D_IFace3"]; } } public static Type D_SingleUseArrayOnlyGen { get { return s_TypeRepo["D_SingleUseArrayOnlyGen"]; } } - public static Type D_GenericStruct { get { return s_TypeRepo["D_GenericStruct"]; } } - public static Type D_NullableTest { get { return s_TypeRepo["D_NullableTest"]; } } public static Type D_DelegateTarget { get { return s_TypeRepo["D_DelegateTarget"]; } } public static Type D_DelWithNullable { get { return s_TypeRepo["D_DelWithNullable"]; } } public static Type TDT_MyStruct { get { return s_TypeRepo["TDT_MyStruct"]; } } diff --git a/src/tests/nativeaot/SmokeTests/DynamicGenerics/dictionaries.cs b/src/tests/nativeaot/SmokeTests/DynamicGenerics/dictionaries.cs index 6355dfc903ff92..cbc9cdd31bcbe5 100644 --- a/src/tests/nativeaot/SmokeTests/DynamicGenerics/dictionaries.cs +++ b/src/tests/nativeaot/SmokeTests/DynamicGenerics/dictionaries.cs @@ -349,12 +349,12 @@ public static void NullableTesting() } static void NullableTesting_Inner(Type arg1, Type arg2) { - var structOf = TypeOf.D_GenericStruct.MakeGenericType(arg1); + var structOf = typeof(GenericStruct<>).MakeGenericType(arg1); - var structInst1 = Activator.CreateInstance(TypeOf.D_GenericStruct.MakeGenericType(arg1)); - var structInst2 = Activator.CreateInstance(TypeOf.D_GenericStruct.MakeGenericType(arg2)); + var structInst1 = Activator.CreateInstance(typeof(GenericStruct<>).MakeGenericType(arg1)); + var structInst2 = Activator.CreateInstance(typeof(GenericStruct<>).MakeGenericType(arg2)); - var nullableTestOf = TypeOf.D_NullableTest.MakeGenericType(structOf); + var nullableTestOf = typeof(NullableTest<>).MakeGenericType(structOf); Base test = (Base)Activator.CreateInstance(nullableTestOf); // Type cast T -> T? From de3d75fe2a82218c6de69904b44e18ce13c6cc25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Fri, 8 Aug 2025 08:06:06 +0200 Subject: [PATCH 3/9] Trim interfaces referenced from native layout --- .../NativeLayoutVertexNode.cs | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs index 206ab1e3729dc5..3df053b694850e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs @@ -898,11 +898,6 @@ public override IEnumerable GetStaticDependencies(NodeFacto yield return new DependencyListEntry(context.GenericDictionaryLayout(_type.ConvertToCanonForm(CanonicalFormKind.Specific).GetClosestDefType()), "Dictionary layout"); - foreach (TypeDesc iface in _type.RuntimeInterfaces) - { - yield return new DependencyListEntry(context.NativeLayout.TypeSignatureVertex(iface), "template interface list"); - } - if (context.PreinitializationManager.HasLazyStaticConstructor(_type.ConvertToCanonForm(CanonicalFormKind.Specific))) { yield return new DependencyListEntry(context.MethodEntrypoint(_type.GetStaticConstructor().GetCanonMethodTarget(CanonicalFormKind.Specific)), "cctor for template"); @@ -944,8 +939,14 @@ public override IEnumerable GetStaticDependencies(NodeFacto } } - public override bool HasConditionalStaticDependencies => false; - public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) => null; + public override bool HasConditionalStaticDependencies => _type.RuntimeInterfaces.Length > 0; + public override IEnumerable GetConditionalStaticDependencies(NodeFactory factory) + { + foreach (TypeDesc iface in _type.RuntimeInterfaces) + { + yield return new CombinedDependencyListEntry(factory.NativeLayout.TypeSignatureVertex(iface), factory.InterfaceUse(iface.GetTypeDefinition()), "template interface list"); + } + } private static int CompareDictionaryEntries(KeyValuePair left, KeyValuePair right) { @@ -968,11 +969,16 @@ public override Vertex WriteVertex(NodeFactory factory) foreach (TypeDesc iface in _type.RuntimeInterfaces) { - implementedInterfacesList.Add(factory.NativeLayout.TypeSignatureVertex(iface)); + if (factory.InterfaceUse(iface.GetTypeDefinition()).Marked) + implementedInterfacesList.Add(factory.NativeLayout.TypeSignatureVertex(iface)); } - NativeLayoutPlacedVertexSequenceVertexNode implementedInterfaces = factory.NativeLayout.PlacedVertexSequence(implementedInterfacesList); - layoutInfo.Append(BagElementKind.ImplementedInterfaces, implementedInterfaces.WriteVertex(factory)); + if (implementedInterfacesList.Count > 0) + { + NativeLayoutPlacedVertexSequenceVertexNode implementedInterfaces = factory.NativeLayout.PlacedVertexSequence(implementedInterfacesList); + + layoutInfo.Append(BagElementKind.ImplementedInterfaces, implementedInterfaces.WriteVertex(factory)); + } } if (!factory.LazyGenericsPolicy.UsesLazyGenerics(_type) && templateLayout.Count > 0) From 66ec27314ee84ff1eab2347ae94744eac14e36b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 12 Aug 2025 06:42:36 +0200 Subject: [PATCH 4/9] Optimize away GVM resolution metadata Ever since GVM support was added to native AOT, we were generating the GVM resolution metadata for every type considered allocated. This included GVMs that were never even called (see `TypeGVMEntriesNode` that simply goes over everything on the type). This PR introduces tracking with method level granularity. I ran into this in a different PR where this was dragging `double`/`float` into compilation just because `int` implements generic math. --- .../ConstructedEETypeNode.cs | 10 +- .../Compiler/DependencyAnalysis/EETypeNode.cs | 2 - .../DependencyAnalysis/GVMDependenciesNode.cs | 15 ++- .../DependencyAnalysis/GVMMetadataNode.cs | 49 +++++++ .../GenericVirtualMethodTableNode.cs | 8 +- .../InterfaceGVMMetadataNode.cs | 65 ++++++++++ .../InterfaceGenericVirtualMethodTableNode.cs | 8 +- .../DependencyAnalysis/NodeFactory.cs | 49 ++++++- .../DependencyAnalysis/TypeGVMEntriesNode.cs | 122 ------------------ .../Compiler/MetadataManager.cs | 23 +++- .../ILCompiler.Compiler.csproj | 3 +- 11 files changed, 194 insertions(+), 160 deletions(-) create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GVMMetadataNode.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceGVMMetadataNode.cs delete mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeGVMEntriesNode.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ConstructedEETypeNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ConstructedEETypeNode.cs index 250b8964a31caa..20a0a0ebd5a3f7 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ConstructedEETypeNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ConstructedEETypeNode.cs @@ -52,15 +52,7 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact dependencyList.Add(factory.VTable(closestDefType), "VTable"); - if (_type.IsCanonicalSubtype(CanonicalFormKind.Any)) - { - // Track generic virtual methods that will get added to the GVM tables - if ((_virtualMethodAnalysisFlags & VirtualMethodAnalysisFlags.NeedsGvmEntries) != 0) - { - dependencyList.Add(new DependencyListEntry(factory.TypeGVMEntries(_type.GetTypeDefinition()), "Type with generic virtual methods")); - } - } - else + if (!_type.IsCanonicalSubtype(CanonicalFormKind.Any)) { factory.InteropStubManager.AddInterestingInteropConstructedTypeDependencies(ref dependencyList, factory, _type); } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs index c1e48b4901b1b6..d6ff866e7d0af5 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs @@ -628,8 +628,6 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact // Generated type contains generic virtual methods that will get added to the GVM tables if ((_virtualMethodAnalysisFlags & VirtualMethodAnalysisFlags.NeedsGvmEntries) != 0) { - dependencies.Add(new DependencyListEntry(factory.TypeGVMEntries(_type.GetTypeDefinition()), "Type with generic virtual methods")); - TypeDesc canonicalType = _type.ConvertToCanonForm(CanonicalFormKind.Specific); if (canonicalType != _type) dependencies.Add(factory.ConstructedTypeSymbol(canonicalType), "Type with generic virtual methods"); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GVMDependenciesNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GVMDependenciesNode.cs index 66047d79f08228..09ff03cd1d7b07 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GVMDependenciesNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GVMDependenciesNode.cs @@ -41,7 +41,12 @@ public GVMDependenciesNode(MethodDesc method) public override IEnumerable GetStaticDependencies(NodeFactory factory) { if (!_method.IsAbstract) + { yield return new DependencyListEntry(factory.GenericVirtualMethodImpl(_method), "Implementation of the generic virtual method"); + + if (!_method.OwningType.IsInterface) + yield return new DependencyListEntry(factory.GVMMetadata(_method.GetTypicalMethodDefinition(), _method.GetTypicalMethodDefinition()), "Implementation of the generic virtual method"); + } } public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) => null; @@ -124,11 +129,12 @@ public override IEnumerable SearchDynamicDependenci MethodDesc slotDecl = interfaceMethod.Signature.IsStatic ? potentialOverrideDefinition.InstantiateAsOpen().ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod) : potentialOverrideDefinition.InstantiateAsOpen().ResolveInterfaceMethodTarget(interfaceMethod); + DefaultInterfaceMethodResolution defaultResolution = DefaultInterfaceMethodResolution.None; if (slotDecl == null) { // The method might be implemented through a default interface method - var result = potentialOverrideDefinition.InstantiateAsOpen().ResolveInterfaceMethodToDefaultImplementationOnType(interfaceMethod, out slotDecl); - if (result != DefaultInterfaceMethodResolution.DefaultImplementation) + defaultResolution = potentialOverrideDefinition.InstantiateAsOpen().ResolveInterfaceMethodToDefaultImplementationOnType(interfaceMethod, out slotDecl); + if (defaultResolution != DefaultInterfaceMethodResolution.DefaultImplementation) { slotDecl = null; } @@ -147,6 +153,8 @@ public override IEnumerable SearchDynamicDependenci else dynamicDependencies.Add(new CombinedDependencyListEntry(factory.GVMDependencies(implementingMethodInstantiation.GetCanonMethodTarget(CanonicalFormKind.Specific)), null, "ImplementingMethodInstantiation")); + dynamicDependencies.Add(new CombinedDependencyListEntry(factory.InterfaceGVMMetadata(interfaceMethod, slotDecl.GetTypicalMethodDefinition(), potentialOverrideDefinition, defaultResolution), null, "Metadata")); + TypeSystemEntity origin = (implementingMethodInstantiation.OwningType != potentialOverrideType) ? potentialOverrideType : null; factory.MetadataManager.NoteOverridingMethod(_method, implementingMethodInstantiation, origin); } @@ -200,6 +208,9 @@ public override IEnumerable SearchDynamicDependenci dynamicDependencies.Add(new CombinedDependencyListEntry( factory.GenericVirtualMethodImpl(instantiatedTargetMethod), null, "DerivedMethodInstantiation")); + dynamicDependencies.Add(new CombinedDependencyListEntry(factory.GVMMetadata( + methodToResolve.GetTypicalMethodDefinition(), instantiatedTargetMethod.GetTypicalMethodDefinition()), null, "Metadata")); + factory.MetadataManager.NoteOverridingMethod(_method, instantiatedTargetMethod); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GVMMetadataNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GVMMetadataNode.cs new file mode 100644 index 00000000000000..09165b950a06c5 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GVMMetadataNode.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Collections.Generic; + +using ILCompiler.DependencyAnalysisFramework; +using Internal.TypeSystem; + +namespace ILCompiler.DependencyAnalysis +{ + internal sealed class GVMMetadataNode : SortableDependencyNode + { + public MethodDesc CallingMethod { get; } + public MethodDesc ImplementationMethod { get; } + public GVMMetadataNode(MethodDesc callingMethod, MethodDesc implementationMethod) + => (CallingMethod, ImplementationMethod) = (callingMethod, implementationMethod); + + protected override string GetName(NodeFactory context) => + $"GVM method: {CallingMethod}: {ImplementationMethod}"; + + public override IEnumerable GetStaticDependencies(NodeFactory factory) + { + var list = new DependencyList(); + GenericVirtualMethodTableNode.GetGenericVirtualMethodImplementationDependencies(ref list, factory, CallingMethod, ImplementationMethod); + return list; + } + + public override int ClassCode => 0x2898423; + + public override int CompareToImpl(ISortableNode other, CompilerComparer comparer) + { + var otherNode = (GVMMetadataNode)other; + + int result = comparer.Compare(CallingMethod, otherNode.CallingMethod); + if (result != 0) + return result; + + return comparer.Compare(ImplementationMethod, otherNode.ImplementationMethod); + } + + public override bool InterestingForDynamicDependencyAnalysis => false; + public override bool HasDynamicDependencies => false; + public override bool HasConditionalStaticDependencies => false; + public override bool StaticDependenciesAreComputed => true; + public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) => null; + public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory context) => null; + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodTableNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodTableNode.cs index 3df07d1581bca5..5386c644f1187f 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodTableNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodTableNode.cs @@ -80,13 +80,9 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) if (relocsOnly) return new ObjectData(Array.Empty(), Array.Empty(), 1, new ISymbolDefinitionNode[] { this }); - // Build the GVM table entries from the list of interesting GVMTableEntryNodes - foreach (var interestingEntry in factory.MetadataManager.GetTypeGVMEntries()) + foreach (var typeGVMEntryInfo in factory.MetadataManager.GetGVMMetadatas()) { - foreach (var typeGVMEntryInfo in interestingEntry.ScanForGenericVirtualMethodEntries()) - { - AddGenericVirtualMethodImplementation(typeGVMEntryInfo.CallingMethod, typeGVMEntryInfo.ImplementationMethod); - } + AddGenericVirtualMethodImplementation(typeGVMEntryInfo.CallingMethod, typeGVMEntryInfo.ImplementationMethod); } // Ensure the native layout blob has been saved diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceGVMMetadataNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceGVMMetadataNode.cs new file mode 100644 index 00000000000000..7238efce24b9b1 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceGVMMetadataNode.cs @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Collections.Generic; + +using ILCompiler.DependencyAnalysisFramework; +using Internal.TypeSystem; + +namespace ILCompiler.DependencyAnalysis +{ + internal sealed class InterfaceGVMMetadataNode : SortableDependencyNode + { + public MethodDesc CallingMethod { get; } + public MethodDesc ImplementationMethod { get; } + public TypeDesc ImplementationType { get; } + public DefaultInterfaceMethodResolution DefaultResolution { get; } + + public InterfaceGVMMetadataNode(MethodDesc callingMethod, MethodDesc implementationMethod, + TypeDesc implementationType, DefaultInterfaceMethodResolution defaultResolution) + => (CallingMethod, ImplementationMethod, ImplementationType, DefaultResolution) + = (callingMethod, implementationMethod, implementationType, defaultResolution); + + protected override string GetName(NodeFactory context) => + $"GVM interface method: {CallingMethod} on {ImplementationType}: {ImplementationMethod}, {DefaultResolution}"; + + public override IEnumerable GetStaticDependencies(NodeFactory factory) + { + var list = new DependencyList(); + InterfaceGenericVirtualMethodTableNode.GetGenericVirtualMethodImplementationDependencies(ref list, factory, CallingMethod, ImplementationType, ImplementationMethod); + return list; + } + + public override int ClassCode => 0x48bcaa1; + + public override int CompareToImpl(ISortableNode other, CompilerComparer comparer) + { + var otherNode = (InterfaceGVMMetadataNode)other; + + int result = comparer.Compare(ImplementationType, otherNode.ImplementationType); + if (result != 0) + return result; + + DefType[] interfaceList = ImplementationType.RuntimeInterfaces; + int thisIndex = Array.IndexOf(interfaceList, CallingMethod.OwningType); + int thatIndex = Array.IndexOf(interfaceList, otherNode.CallingMethod.OwningType); + + Debug.Assert(thisIndex >= 0 && thatIndex >= 0); + + result = Comparer.Default.Compare(thisIndex, thatIndex); + if (result != 0) + return result; + + return comparer.Compare(ImplementationMethod, otherNode.ImplementationMethod); + } + + public override bool InterestingForDynamicDependencyAnalysis => false; + public override bool HasDynamicDependencies => false; + public override bool HasConditionalStaticDependencies => false; + public override bool StaticDependenciesAreComputed => true; + public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) => null; + public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory context) => null; + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceGenericVirtualMethodTableNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceGenericVirtualMethodTableNode.cs index e1f432af8ff3c6..e994224d38bea2 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceGenericVirtualMethodTableNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceGenericVirtualMethodTableNode.cs @@ -125,13 +125,9 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) if (relocsOnly) return new ObjectData(Array.Empty(), Array.Empty(), 1, new ISymbolDefinitionNode[] { this }); - // Build the GVM table entries from the list of interesting GVMTableEntryNodes - foreach (var interestingEntry in factory.MetadataManager.GetTypeGVMEntries()) + foreach (var typeGVMEntryInfo in factory.MetadataManager.GetInterfaceGVMMetadatas()) { - foreach (var typeGVMEntryInfo in interestingEntry.ScanForInterfaceGenericVirtualMethodEntries()) - { - AddGenericVirtualMethodImplementation(typeGVMEntryInfo.CallingMethod, typeGVMEntryInfo.ImplementationType, typeGVMEntryInfo.ImplementationMethod, typeGVMEntryInfo.DefaultResolution); - } + AddGenericVirtualMethodImplementation(typeGVMEntryInfo.CallingMethod, typeGVMEntryInfo.ImplementationType, typeGVMEntryInfo.ImplementationMethod, typeGVMEntryInfo.DefaultResolution); } // Ensure the native layout blob has been saved diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs index ef6a4fed1c1949..9528f9dc29a810 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs @@ -339,9 +339,14 @@ private void CreateNodeCaches() return new ExactMethodInstantiationsEntryNode(method); }); - _gvmTableEntries = new NodeCache(type => + _gvmMetadatas = new NodeCache(key => { - return new TypeGVMEntriesNode(type); + return new GVMMetadataNode(key.CallingMethod, key.ImplementationMethod); + }); + + _interfaceGvmMetadatas = new NodeCache(key => + { + return new InterfaceGVMMetadataNode(key.CallingMethod, key.ImplementationMethod, key.ImplementationType, key.DefaultResolution); }); _addressTakenMethods = new NodeCache(method => @@ -1165,10 +1170,16 @@ public ExactMethodInstantiationsEntryNode ExactMethodInstantiationsHashtableEntr return _exactMethodEntries.GetOrAdd(method); } - private NodeCache _gvmTableEntries; - internal TypeGVMEntriesNode TypeGVMEntries(TypeDesc type) + private NodeCache _gvmMetadatas; + internal GVMMetadataNode GVMMetadata(MethodDesc callingMethod, MethodDesc implementationMethod) + { + return _gvmMetadatas.GetOrAdd(new GVMMetadataKey(callingMethod, implementationMethod)); + } + + private NodeCache _interfaceGvmMetadatas; + internal InterfaceGVMMetadataNode InterfaceGVMMetadata(MethodDesc callingMethod, MethodDesc implementationMethod, TypeDesc implementationType, DefaultInterfaceMethodResolution defaultResolution) { - return _gvmTableEntries.GetOrAdd(type); + return _interfaceGvmMetadatas.GetOrAdd(new InterfaceGVMMetadataKey(callingMethod, implementationMethod, implementationType, defaultResolution)); } private NodeCache _addressTakenMethods; @@ -1739,5 +1750,33 @@ private struct MethodILKey : IEquatable public override int GetHashCode() => MethodIL.OwningMethod.GetHashCode(); } + + private struct GVMMetadataKey : IEquatable + { + public readonly MethodDesc CallingMethod; + public readonly MethodDesc ImplementationMethod; + + public GVMMetadataKey(MethodDesc callingMethod, MethodDesc implementationMethod) + => (CallingMethod, ImplementationMethod) = (callingMethod, implementationMethod); + + public override bool Equals(object obj) => obj is GVMMetadataKey other && Equals(other); + public bool Equals(GVMMetadataKey other) => CallingMethod == other.CallingMethod && ImplementationMethod == other.ImplementationMethod; + public override int GetHashCode() => HashCode.Combine(CallingMethod, ImplementationMethod); + } + + private struct InterfaceGVMMetadataKey : IEquatable + { + public readonly MethodDesc CallingMethod; + public readonly MethodDesc ImplementationMethod; + public readonly TypeDesc ImplementationType; + public readonly DefaultInterfaceMethodResolution DefaultResolution; + + public InterfaceGVMMetadataKey(MethodDesc callingMethod, MethodDesc implementationMethod, TypeDesc implementationType, DefaultInterfaceMethodResolution resolution) + => (CallingMethod, ImplementationMethod, ImplementationType, DefaultResolution) = (callingMethod, implementationMethod, implementationType, resolution); + + public override bool Equals(object obj) => obj is InterfaceGVMMetadataKey other && Equals(other); + public bool Equals(InterfaceGVMMetadataKey other) => CallingMethod == other.CallingMethod && ImplementationType == other.ImplementationType; + public override int GetHashCode() => HashCode.Combine(CallingMethod, ImplementationType); + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeGVMEntriesNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeGVMEntriesNode.cs deleted file mode 100644 index e3a0b4d38ab17e..00000000000000 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeGVMEntriesNode.cs +++ /dev/null @@ -1,122 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.Diagnostics; - -using Internal.TypeSystem; -using ILCompiler.DependencyAnalysisFramework; - -namespace ILCompiler.DependencyAnalysis -{ - /// - /// This node is used for GVM dependency analysis and GVM tables building. Given an input - /// type, this node can scan the type for a list of GVM table entries, and compute their dependencies. - /// - internal sealed class TypeGVMEntriesNode : DependencyNodeCore - { - internal class TypeGVMEntryInfo - { - public TypeGVMEntryInfo(MethodDesc callingMethod, MethodDesc implementationMethod) - { - CallingMethod = callingMethod; - ImplementationMethod = implementationMethod; - } - public MethodDesc CallingMethod { get; } - public MethodDesc ImplementationMethod { get; } - } - - internal sealed class InterfaceGVMEntryInfo : TypeGVMEntryInfo - { - public InterfaceGVMEntryInfo(MethodDesc callingMethod, MethodDesc implementationMethod, - TypeDesc implementationType, DefaultInterfaceMethodResolution defaultResolution) - : base(callingMethod, implementationMethod) - { - ImplementationType = implementationType; - DefaultResolution = defaultResolution; - } - - public TypeDesc ImplementationType { get; } - public DefaultInterfaceMethodResolution DefaultResolution { get; } - } - - private readonly TypeDesc _associatedType; - private DependencyList _staticDependencies; - - public TypeGVMEntriesNode(TypeDesc associatedType) - { - Debug.Assert(associatedType.IsTypeDefinition); - _associatedType = associatedType; - } - - public TypeDesc AssociatedType => _associatedType; - - public override bool HasConditionalStaticDependencies => false; - public override bool HasDynamicDependencies => false; - public override bool InterestingForDynamicDependencyAnalysis => false; - public override bool StaticDependenciesAreComputed => true; - protected override string GetName(NodeFactory factory) => "__TypeGVMEntriesNode_" + factory.NameMangler.GetMangledTypeName(_associatedType); - public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) => null; - public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory context) => null; - - public override IEnumerable GetStaticDependencies(NodeFactory context) - { - if (_staticDependencies == null) - { - _staticDependencies = new DependencyList(); - - foreach (var entry in ScanForGenericVirtualMethodEntries()) - GenericVirtualMethodTableNode.GetGenericVirtualMethodImplementationDependencies(ref _staticDependencies, context, entry.CallingMethod, entry.ImplementationMethod); - - foreach (var entry in ScanForInterfaceGenericVirtualMethodEntries()) - InterfaceGenericVirtualMethodTableNode.GetGenericVirtualMethodImplementationDependencies(ref _staticDependencies, context, entry.CallingMethod, entry.ImplementationType, entry.ImplementationMethod); - } - - return _staticDependencies; - } - - public IEnumerable ScanForGenericVirtualMethodEntries() - { - foreach (MethodDesc decl in _associatedType.EnumAllVirtualSlots()) - { - // Non-Generic virtual methods are tracked by an orthogonal mechanism. - if (!decl.HasInstantiation) - continue; - - MethodDesc impl = _associatedType.FindVirtualFunctionTargetMethodOnObjectType(decl); - - if (impl.OwningType == _associatedType) - yield return new TypeGVMEntryInfo(decl, impl); - } - } - - public IEnumerable ScanForInterfaceGenericVirtualMethodEntries() - { - foreach (var iface in _associatedType.RuntimeInterfaces) - { - foreach (var method in iface.GetVirtualMethods()) - { - if (!method.HasInstantiation) - continue; - - DefaultInterfaceMethodResolution resolution = DefaultInterfaceMethodResolution.None; - MethodDesc slotDecl = method.Signature.IsStatic ? - _associatedType.ResolveInterfaceMethodToStaticVirtualMethodOnType(method) : _associatedType.ResolveInterfaceMethodTarget(method); - if (slotDecl == null) - { - resolution = _associatedType.ResolveInterfaceMethodToDefaultImplementationOnType(method, out slotDecl); - if (resolution != DefaultInterfaceMethodResolution.DefaultImplementation) - slotDecl = null; - } - - if (slotDecl != null - || resolution == DefaultInterfaceMethodResolution.Diamond - || resolution == DefaultInterfaceMethodResolution.Reabstraction) - { - yield return new InterfaceGVMEntryInfo(method, slotDecl, _associatedType, resolution); - } - } - } - } - } -} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs index 93a2e79d9df627..49eedb3b20a4c5 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs @@ -72,8 +72,8 @@ public abstract class MetadataManager : ICompilationRootProvider private readonly SortedSet _genericDictionariesGenerated = new SortedSet(CompilerComparer.Instance); private readonly SortedSet _methodBodiesGenerated = new SortedSet(CompilerComparer.Instance); private readonly SortedSet _frozenObjects = new SortedSet(CompilerComparer.Instance); - private readonly SortedSet _typeGVMEntries - = new SortedSet(Comparer.Create((a, b) => TypeSystemComparer.Instance.Compare(a.AssociatedType, b.AssociatedType))); + private readonly SortedSet _gvmMetadatas = new SortedSet(CompilerComparer.Instance); + private readonly SortedSet _interfaceGvmMetadatas = new SortedSet(CompilerComparer.Instance); private readonly SortedSet _typesWithDelegateMarshalling = new SortedSet(TypeSystemComparer.Instance); private readonly SortedSet _typesWithStructMarshalling = new SortedSet(TypeSystemComparer.Instance); private HashSet _templateMethodEntries = new HashSet(); @@ -286,10 +286,14 @@ protected virtual void Graph_NewMarkedNode(DependencyNodeCore obj) _typesWithThreadStaticsGenerated.Add(threadStaticsNode.Type); } - var gvmEntryNode = obj as TypeGVMEntriesNode; - if (gvmEntryNode != null) + if (obj is GVMMetadataNode gvmMetadataNode) { - _typeGVMEntries.Add(gvmEntryNode); + _gvmMetadatas.Add(gvmMetadataNode); + } + + if (obj is InterfaceGVMMetadataNode interfaceGvmMetadataNode) + { + _interfaceGvmMetadatas.Add(interfaceGvmMetadataNode); } var dictionaryNode = obj as GenericDictionaryNode; @@ -1024,9 +1028,14 @@ internal IEnumerable GetTypesWithStaticBases() internal bool HasThreadStaticBase(MetadataType type) => _typesWithThreadStaticsGenerated.Contains(type); internal bool HasConstructedEEType(TypeDesc type) => _typesWithConstructedEETypesGenerated.Contains(type); - internal IEnumerable GetTypeGVMEntries() + internal IEnumerable GetGVMMetadatas() + { + return _gvmMetadatas; + } + + internal IEnumerable GetInterfaceGVMMetadatas() { - return _typeGVMEntries; + return _interfaceGvmMetadatas; } internal IReadOnlyCollection GetCompiledGenericDictionaries() diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index 3ac5cfb27289d4..82997a013ba6cc 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -502,9 +502,10 @@ + - + From 43662aa8b8e25f006a533438044ac7fd00d49ad9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 12 Aug 2025 12:20:55 +0200 Subject: [PATCH 5/9] Revert "Optimize away GVM resolution metadata" This reverts commit 66ec27314ee84ff1eab2347ae94744eac14e36b4. --- .../ConstructedEETypeNode.cs | 10 +- .../Compiler/DependencyAnalysis/EETypeNode.cs | 2 + .../DependencyAnalysis/GVMDependenciesNode.cs | 15 +-- .../DependencyAnalysis/GVMMetadataNode.cs | 49 ------- .../GenericVirtualMethodTableNode.cs | 8 +- .../InterfaceGVMMetadataNode.cs | 65 ---------- .../InterfaceGenericVirtualMethodTableNode.cs | 8 +- .../DependencyAnalysis/NodeFactory.cs | 49 +------ .../DependencyAnalysis/TypeGVMEntriesNode.cs | 122 ++++++++++++++++++ .../Compiler/MetadataManager.cs | 23 +--- .../ILCompiler.Compiler.csproj | 3 +- 11 files changed, 160 insertions(+), 194 deletions(-) delete mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GVMMetadataNode.cs delete mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceGVMMetadataNode.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeGVMEntriesNode.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ConstructedEETypeNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ConstructedEETypeNode.cs index 20a0a0ebd5a3f7..250b8964a31caa 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ConstructedEETypeNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ConstructedEETypeNode.cs @@ -52,7 +52,15 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact dependencyList.Add(factory.VTable(closestDefType), "VTable"); - if (!_type.IsCanonicalSubtype(CanonicalFormKind.Any)) + if (_type.IsCanonicalSubtype(CanonicalFormKind.Any)) + { + // Track generic virtual methods that will get added to the GVM tables + if ((_virtualMethodAnalysisFlags & VirtualMethodAnalysisFlags.NeedsGvmEntries) != 0) + { + dependencyList.Add(new DependencyListEntry(factory.TypeGVMEntries(_type.GetTypeDefinition()), "Type with generic virtual methods")); + } + } + else { factory.InteropStubManager.AddInterestingInteropConstructedTypeDependencies(ref dependencyList, factory, _type); } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs index d6ff866e7d0af5..c1e48b4901b1b6 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs @@ -628,6 +628,8 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact // Generated type contains generic virtual methods that will get added to the GVM tables if ((_virtualMethodAnalysisFlags & VirtualMethodAnalysisFlags.NeedsGvmEntries) != 0) { + dependencies.Add(new DependencyListEntry(factory.TypeGVMEntries(_type.GetTypeDefinition()), "Type with generic virtual methods")); + TypeDesc canonicalType = _type.ConvertToCanonForm(CanonicalFormKind.Specific); if (canonicalType != _type) dependencies.Add(factory.ConstructedTypeSymbol(canonicalType), "Type with generic virtual methods"); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GVMDependenciesNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GVMDependenciesNode.cs index 09ff03cd1d7b07..66047d79f08228 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GVMDependenciesNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GVMDependenciesNode.cs @@ -41,12 +41,7 @@ public GVMDependenciesNode(MethodDesc method) public override IEnumerable GetStaticDependencies(NodeFactory factory) { if (!_method.IsAbstract) - { yield return new DependencyListEntry(factory.GenericVirtualMethodImpl(_method), "Implementation of the generic virtual method"); - - if (!_method.OwningType.IsInterface) - yield return new DependencyListEntry(factory.GVMMetadata(_method.GetTypicalMethodDefinition(), _method.GetTypicalMethodDefinition()), "Implementation of the generic virtual method"); - } } public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) => null; @@ -129,12 +124,11 @@ public override IEnumerable SearchDynamicDependenci MethodDesc slotDecl = interfaceMethod.Signature.IsStatic ? potentialOverrideDefinition.InstantiateAsOpen().ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod) : potentialOverrideDefinition.InstantiateAsOpen().ResolveInterfaceMethodTarget(interfaceMethod); - DefaultInterfaceMethodResolution defaultResolution = DefaultInterfaceMethodResolution.None; if (slotDecl == null) { // The method might be implemented through a default interface method - defaultResolution = potentialOverrideDefinition.InstantiateAsOpen().ResolveInterfaceMethodToDefaultImplementationOnType(interfaceMethod, out slotDecl); - if (defaultResolution != DefaultInterfaceMethodResolution.DefaultImplementation) + var result = potentialOverrideDefinition.InstantiateAsOpen().ResolveInterfaceMethodToDefaultImplementationOnType(interfaceMethod, out slotDecl); + if (result != DefaultInterfaceMethodResolution.DefaultImplementation) { slotDecl = null; } @@ -153,8 +147,6 @@ public override IEnumerable SearchDynamicDependenci else dynamicDependencies.Add(new CombinedDependencyListEntry(factory.GVMDependencies(implementingMethodInstantiation.GetCanonMethodTarget(CanonicalFormKind.Specific)), null, "ImplementingMethodInstantiation")); - dynamicDependencies.Add(new CombinedDependencyListEntry(factory.InterfaceGVMMetadata(interfaceMethod, slotDecl.GetTypicalMethodDefinition(), potentialOverrideDefinition, defaultResolution), null, "Metadata")); - TypeSystemEntity origin = (implementingMethodInstantiation.OwningType != potentialOverrideType) ? potentialOverrideType : null; factory.MetadataManager.NoteOverridingMethod(_method, implementingMethodInstantiation, origin); } @@ -208,9 +200,6 @@ public override IEnumerable SearchDynamicDependenci dynamicDependencies.Add(new CombinedDependencyListEntry( factory.GenericVirtualMethodImpl(instantiatedTargetMethod), null, "DerivedMethodInstantiation")); - dynamicDependencies.Add(new CombinedDependencyListEntry(factory.GVMMetadata( - methodToResolve.GetTypicalMethodDefinition(), instantiatedTargetMethod.GetTypicalMethodDefinition()), null, "Metadata")); - factory.MetadataManager.NoteOverridingMethod(_method, instantiatedTargetMethod); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GVMMetadataNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GVMMetadataNode.cs deleted file mode 100644 index 09165b950a06c5..00000000000000 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GVMMetadataNode.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; -using System.Collections.Generic; - -using ILCompiler.DependencyAnalysisFramework; -using Internal.TypeSystem; - -namespace ILCompiler.DependencyAnalysis -{ - internal sealed class GVMMetadataNode : SortableDependencyNode - { - public MethodDesc CallingMethod { get; } - public MethodDesc ImplementationMethod { get; } - public GVMMetadataNode(MethodDesc callingMethod, MethodDesc implementationMethod) - => (CallingMethod, ImplementationMethod) = (callingMethod, implementationMethod); - - protected override string GetName(NodeFactory context) => - $"GVM method: {CallingMethod}: {ImplementationMethod}"; - - public override IEnumerable GetStaticDependencies(NodeFactory factory) - { - var list = new DependencyList(); - GenericVirtualMethodTableNode.GetGenericVirtualMethodImplementationDependencies(ref list, factory, CallingMethod, ImplementationMethod); - return list; - } - - public override int ClassCode => 0x2898423; - - public override int CompareToImpl(ISortableNode other, CompilerComparer comparer) - { - var otherNode = (GVMMetadataNode)other; - - int result = comparer.Compare(CallingMethod, otherNode.CallingMethod); - if (result != 0) - return result; - - return comparer.Compare(ImplementationMethod, otherNode.ImplementationMethod); - } - - public override bool InterestingForDynamicDependencyAnalysis => false; - public override bool HasDynamicDependencies => false; - public override bool HasConditionalStaticDependencies => false; - public override bool StaticDependenciesAreComputed => true; - public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) => null; - public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory context) => null; - } -} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodTableNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodTableNode.cs index 5386c644f1187f..3df07d1581bca5 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodTableNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericVirtualMethodTableNode.cs @@ -80,9 +80,13 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) if (relocsOnly) return new ObjectData(Array.Empty(), Array.Empty(), 1, new ISymbolDefinitionNode[] { this }); - foreach (var typeGVMEntryInfo in factory.MetadataManager.GetGVMMetadatas()) + // Build the GVM table entries from the list of interesting GVMTableEntryNodes + foreach (var interestingEntry in factory.MetadataManager.GetTypeGVMEntries()) { - AddGenericVirtualMethodImplementation(typeGVMEntryInfo.CallingMethod, typeGVMEntryInfo.ImplementationMethod); + foreach (var typeGVMEntryInfo in interestingEntry.ScanForGenericVirtualMethodEntries()) + { + AddGenericVirtualMethodImplementation(typeGVMEntryInfo.CallingMethod, typeGVMEntryInfo.ImplementationMethod); + } } // Ensure the native layout blob has been saved diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceGVMMetadataNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceGVMMetadataNode.cs deleted file mode 100644 index 7238efce24b9b1..00000000000000 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceGVMMetadataNode.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Diagnostics; -using System.Collections.Generic; - -using ILCompiler.DependencyAnalysisFramework; -using Internal.TypeSystem; - -namespace ILCompiler.DependencyAnalysis -{ - internal sealed class InterfaceGVMMetadataNode : SortableDependencyNode - { - public MethodDesc CallingMethod { get; } - public MethodDesc ImplementationMethod { get; } - public TypeDesc ImplementationType { get; } - public DefaultInterfaceMethodResolution DefaultResolution { get; } - - public InterfaceGVMMetadataNode(MethodDesc callingMethod, MethodDesc implementationMethod, - TypeDesc implementationType, DefaultInterfaceMethodResolution defaultResolution) - => (CallingMethod, ImplementationMethod, ImplementationType, DefaultResolution) - = (callingMethod, implementationMethod, implementationType, defaultResolution); - - protected override string GetName(NodeFactory context) => - $"GVM interface method: {CallingMethod} on {ImplementationType}: {ImplementationMethod}, {DefaultResolution}"; - - public override IEnumerable GetStaticDependencies(NodeFactory factory) - { - var list = new DependencyList(); - InterfaceGenericVirtualMethodTableNode.GetGenericVirtualMethodImplementationDependencies(ref list, factory, CallingMethod, ImplementationType, ImplementationMethod); - return list; - } - - public override int ClassCode => 0x48bcaa1; - - public override int CompareToImpl(ISortableNode other, CompilerComparer comparer) - { - var otherNode = (InterfaceGVMMetadataNode)other; - - int result = comparer.Compare(ImplementationType, otherNode.ImplementationType); - if (result != 0) - return result; - - DefType[] interfaceList = ImplementationType.RuntimeInterfaces; - int thisIndex = Array.IndexOf(interfaceList, CallingMethod.OwningType); - int thatIndex = Array.IndexOf(interfaceList, otherNode.CallingMethod.OwningType); - - Debug.Assert(thisIndex >= 0 && thatIndex >= 0); - - result = Comparer.Default.Compare(thisIndex, thatIndex); - if (result != 0) - return result; - - return comparer.Compare(ImplementationMethod, otherNode.ImplementationMethod); - } - - public override bool InterestingForDynamicDependencyAnalysis => false; - public override bool HasDynamicDependencies => false; - public override bool HasConditionalStaticDependencies => false; - public override bool StaticDependenciesAreComputed => true; - public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) => null; - public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory context) => null; - } -} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceGenericVirtualMethodTableNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceGenericVirtualMethodTableNode.cs index e994224d38bea2..e1f432af8ff3c6 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceGenericVirtualMethodTableNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InterfaceGenericVirtualMethodTableNode.cs @@ -125,9 +125,13 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) if (relocsOnly) return new ObjectData(Array.Empty(), Array.Empty(), 1, new ISymbolDefinitionNode[] { this }); - foreach (var typeGVMEntryInfo in factory.MetadataManager.GetInterfaceGVMMetadatas()) + // Build the GVM table entries from the list of interesting GVMTableEntryNodes + foreach (var interestingEntry in factory.MetadataManager.GetTypeGVMEntries()) { - AddGenericVirtualMethodImplementation(typeGVMEntryInfo.CallingMethod, typeGVMEntryInfo.ImplementationType, typeGVMEntryInfo.ImplementationMethod, typeGVMEntryInfo.DefaultResolution); + foreach (var typeGVMEntryInfo in interestingEntry.ScanForInterfaceGenericVirtualMethodEntries()) + { + AddGenericVirtualMethodImplementation(typeGVMEntryInfo.CallingMethod, typeGVMEntryInfo.ImplementationType, typeGVMEntryInfo.ImplementationMethod, typeGVMEntryInfo.DefaultResolution); + } } // Ensure the native layout blob has been saved diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs index 9528f9dc29a810..ef6a4fed1c1949 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs @@ -339,14 +339,9 @@ private void CreateNodeCaches() return new ExactMethodInstantiationsEntryNode(method); }); - _gvmMetadatas = new NodeCache(key => + _gvmTableEntries = new NodeCache(type => { - return new GVMMetadataNode(key.CallingMethod, key.ImplementationMethod); - }); - - _interfaceGvmMetadatas = new NodeCache(key => - { - return new InterfaceGVMMetadataNode(key.CallingMethod, key.ImplementationMethod, key.ImplementationType, key.DefaultResolution); + return new TypeGVMEntriesNode(type); }); _addressTakenMethods = new NodeCache(method => @@ -1170,16 +1165,10 @@ public ExactMethodInstantiationsEntryNode ExactMethodInstantiationsHashtableEntr return _exactMethodEntries.GetOrAdd(method); } - private NodeCache _gvmMetadatas; - internal GVMMetadataNode GVMMetadata(MethodDesc callingMethod, MethodDesc implementationMethod) - { - return _gvmMetadatas.GetOrAdd(new GVMMetadataKey(callingMethod, implementationMethod)); - } - - private NodeCache _interfaceGvmMetadatas; - internal InterfaceGVMMetadataNode InterfaceGVMMetadata(MethodDesc callingMethod, MethodDesc implementationMethod, TypeDesc implementationType, DefaultInterfaceMethodResolution defaultResolution) + private NodeCache _gvmTableEntries; + internal TypeGVMEntriesNode TypeGVMEntries(TypeDesc type) { - return _interfaceGvmMetadatas.GetOrAdd(new InterfaceGVMMetadataKey(callingMethod, implementationMethod, implementationType, defaultResolution)); + return _gvmTableEntries.GetOrAdd(type); } private NodeCache _addressTakenMethods; @@ -1750,33 +1739,5 @@ private struct MethodILKey : IEquatable public override int GetHashCode() => MethodIL.OwningMethod.GetHashCode(); } - - private struct GVMMetadataKey : IEquatable - { - public readonly MethodDesc CallingMethod; - public readonly MethodDesc ImplementationMethod; - - public GVMMetadataKey(MethodDesc callingMethod, MethodDesc implementationMethod) - => (CallingMethod, ImplementationMethod) = (callingMethod, implementationMethod); - - public override bool Equals(object obj) => obj is GVMMetadataKey other && Equals(other); - public bool Equals(GVMMetadataKey other) => CallingMethod == other.CallingMethod && ImplementationMethod == other.ImplementationMethod; - public override int GetHashCode() => HashCode.Combine(CallingMethod, ImplementationMethod); - } - - private struct InterfaceGVMMetadataKey : IEquatable - { - public readonly MethodDesc CallingMethod; - public readonly MethodDesc ImplementationMethod; - public readonly TypeDesc ImplementationType; - public readonly DefaultInterfaceMethodResolution DefaultResolution; - - public InterfaceGVMMetadataKey(MethodDesc callingMethod, MethodDesc implementationMethod, TypeDesc implementationType, DefaultInterfaceMethodResolution resolution) - => (CallingMethod, ImplementationMethod, ImplementationType, DefaultResolution) = (callingMethod, implementationMethod, implementationType, resolution); - - public override bool Equals(object obj) => obj is InterfaceGVMMetadataKey other && Equals(other); - public bool Equals(InterfaceGVMMetadataKey other) => CallingMethod == other.CallingMethod && ImplementationType == other.ImplementationType; - public override int GetHashCode() => HashCode.Combine(CallingMethod, ImplementationType); - } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeGVMEntriesNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeGVMEntriesNode.cs new file mode 100644 index 00000000000000..e3a0b4d38ab17e --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeGVMEntriesNode.cs @@ -0,0 +1,122 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Diagnostics; + +using Internal.TypeSystem; +using ILCompiler.DependencyAnalysisFramework; + +namespace ILCompiler.DependencyAnalysis +{ + /// + /// This node is used for GVM dependency analysis and GVM tables building. Given an input + /// type, this node can scan the type for a list of GVM table entries, and compute their dependencies. + /// + internal sealed class TypeGVMEntriesNode : DependencyNodeCore + { + internal class TypeGVMEntryInfo + { + public TypeGVMEntryInfo(MethodDesc callingMethod, MethodDesc implementationMethod) + { + CallingMethod = callingMethod; + ImplementationMethod = implementationMethod; + } + public MethodDesc CallingMethod { get; } + public MethodDesc ImplementationMethod { get; } + } + + internal sealed class InterfaceGVMEntryInfo : TypeGVMEntryInfo + { + public InterfaceGVMEntryInfo(MethodDesc callingMethod, MethodDesc implementationMethod, + TypeDesc implementationType, DefaultInterfaceMethodResolution defaultResolution) + : base(callingMethod, implementationMethod) + { + ImplementationType = implementationType; + DefaultResolution = defaultResolution; + } + + public TypeDesc ImplementationType { get; } + public DefaultInterfaceMethodResolution DefaultResolution { get; } + } + + private readonly TypeDesc _associatedType; + private DependencyList _staticDependencies; + + public TypeGVMEntriesNode(TypeDesc associatedType) + { + Debug.Assert(associatedType.IsTypeDefinition); + _associatedType = associatedType; + } + + public TypeDesc AssociatedType => _associatedType; + + public override bool HasConditionalStaticDependencies => false; + public override bool HasDynamicDependencies => false; + public override bool InterestingForDynamicDependencyAnalysis => false; + public override bool StaticDependenciesAreComputed => true; + protected override string GetName(NodeFactory factory) => "__TypeGVMEntriesNode_" + factory.NameMangler.GetMangledTypeName(_associatedType); + public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) => null; + public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory context) => null; + + public override IEnumerable GetStaticDependencies(NodeFactory context) + { + if (_staticDependencies == null) + { + _staticDependencies = new DependencyList(); + + foreach (var entry in ScanForGenericVirtualMethodEntries()) + GenericVirtualMethodTableNode.GetGenericVirtualMethodImplementationDependencies(ref _staticDependencies, context, entry.CallingMethod, entry.ImplementationMethod); + + foreach (var entry in ScanForInterfaceGenericVirtualMethodEntries()) + InterfaceGenericVirtualMethodTableNode.GetGenericVirtualMethodImplementationDependencies(ref _staticDependencies, context, entry.CallingMethod, entry.ImplementationType, entry.ImplementationMethod); + } + + return _staticDependencies; + } + + public IEnumerable ScanForGenericVirtualMethodEntries() + { + foreach (MethodDesc decl in _associatedType.EnumAllVirtualSlots()) + { + // Non-Generic virtual methods are tracked by an orthogonal mechanism. + if (!decl.HasInstantiation) + continue; + + MethodDesc impl = _associatedType.FindVirtualFunctionTargetMethodOnObjectType(decl); + + if (impl.OwningType == _associatedType) + yield return new TypeGVMEntryInfo(decl, impl); + } + } + + public IEnumerable ScanForInterfaceGenericVirtualMethodEntries() + { + foreach (var iface in _associatedType.RuntimeInterfaces) + { + foreach (var method in iface.GetVirtualMethods()) + { + if (!method.HasInstantiation) + continue; + + DefaultInterfaceMethodResolution resolution = DefaultInterfaceMethodResolution.None; + MethodDesc slotDecl = method.Signature.IsStatic ? + _associatedType.ResolveInterfaceMethodToStaticVirtualMethodOnType(method) : _associatedType.ResolveInterfaceMethodTarget(method); + if (slotDecl == null) + { + resolution = _associatedType.ResolveInterfaceMethodToDefaultImplementationOnType(method, out slotDecl); + if (resolution != DefaultInterfaceMethodResolution.DefaultImplementation) + slotDecl = null; + } + + if (slotDecl != null + || resolution == DefaultInterfaceMethodResolution.Diamond + || resolution == DefaultInterfaceMethodResolution.Reabstraction) + { + yield return new InterfaceGVMEntryInfo(method, slotDecl, _associatedType, resolution); + } + } + } + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs index 49eedb3b20a4c5..93a2e79d9df627 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs @@ -72,8 +72,8 @@ public abstract class MetadataManager : ICompilationRootProvider private readonly SortedSet _genericDictionariesGenerated = new SortedSet(CompilerComparer.Instance); private readonly SortedSet _methodBodiesGenerated = new SortedSet(CompilerComparer.Instance); private readonly SortedSet _frozenObjects = new SortedSet(CompilerComparer.Instance); - private readonly SortedSet _gvmMetadatas = new SortedSet(CompilerComparer.Instance); - private readonly SortedSet _interfaceGvmMetadatas = new SortedSet(CompilerComparer.Instance); + private readonly SortedSet _typeGVMEntries + = new SortedSet(Comparer.Create((a, b) => TypeSystemComparer.Instance.Compare(a.AssociatedType, b.AssociatedType))); private readonly SortedSet _typesWithDelegateMarshalling = new SortedSet(TypeSystemComparer.Instance); private readonly SortedSet _typesWithStructMarshalling = new SortedSet(TypeSystemComparer.Instance); private HashSet _templateMethodEntries = new HashSet(); @@ -286,14 +286,10 @@ protected virtual void Graph_NewMarkedNode(DependencyNodeCore obj) _typesWithThreadStaticsGenerated.Add(threadStaticsNode.Type); } - if (obj is GVMMetadataNode gvmMetadataNode) + var gvmEntryNode = obj as TypeGVMEntriesNode; + if (gvmEntryNode != null) { - _gvmMetadatas.Add(gvmMetadataNode); - } - - if (obj is InterfaceGVMMetadataNode interfaceGvmMetadataNode) - { - _interfaceGvmMetadatas.Add(interfaceGvmMetadataNode); + _typeGVMEntries.Add(gvmEntryNode); } var dictionaryNode = obj as GenericDictionaryNode; @@ -1028,14 +1024,9 @@ internal IEnumerable GetTypesWithStaticBases() internal bool HasThreadStaticBase(MetadataType type) => _typesWithThreadStaticsGenerated.Contains(type); internal bool HasConstructedEEType(TypeDesc type) => _typesWithConstructedEETypesGenerated.Contains(type); - internal IEnumerable GetGVMMetadatas() - { - return _gvmMetadatas; - } - - internal IEnumerable GetInterfaceGVMMetadatas() + internal IEnumerable GetTypeGVMEntries() { - return _interfaceGvmMetadatas; + return _typeGVMEntries; } internal IReadOnlyCollection GetCompiledGenericDictionaries() diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index 82997a013ba6cc..3ac5cfb27289d4 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -502,10 +502,9 @@ - + - From bae990f9a7ed80e3c6772b59694623a2296c0f4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 27 Aug 2025 07:54:29 +0200 Subject: [PATCH 6/9] Throwaway troubleshooting check --- src/coreclr/nativeaot/Runtime/AsmOffsets.h | 1 + src/coreclr/runtime/amd64/AllocFast.S | 49 +++++++++++++++++++ src/coreclr/runtime/amd64/AllocFast.asm | 49 +++++++++++++++++++ .../tests/AssemblyInfo.cs | 2 +- 4 files changed, 100 insertions(+), 1 deletion(-) diff --git a/src/coreclr/nativeaot/Runtime/AsmOffsets.h b/src/coreclr/nativeaot/Runtime/AsmOffsets.h index 616c4847235e5d..b31402f48d8413 100644 --- a/src/coreclr/nativeaot/Runtime/AsmOffsets.h +++ b/src/coreclr/nativeaot/Runtime/AsmOffsets.h @@ -46,6 +46,7 @@ ASM_CONST( 6, 6, ARM64_ATOMICS_FEATURE_FLAG_BIT) ASM_OFFSET( 0, 0, MethodTable, m_usComponentSize) ASM_OFFSET( 0, 0, MethodTable, m_uFlags) ASM_OFFSET( 4, 4, MethodTable, m_uBaseSize) +ASM_OFFSET( C, 10, MethodTable, m_usNumVtableSlots) ASM_OFFSET( 14, 18, MethodTable, m_VTable) ASM_OFFSET( 0, 0, Thread, m_eeAllocContext) diff --git a/src/coreclr/runtime/amd64/AllocFast.S b/src/coreclr/runtime/amd64/AllocFast.S index 02091bea31083e..a7eb24342bdee5 100644 --- a/src/coreclr/runtime/amd64/AllocFast.S +++ b/src/coreclr/runtime/amd64/AllocFast.S @@ -12,6 +12,15 @@ LEAF_ENTRY RhpNewFast, _TEXT push_nonvol_reg rbx mov rbx, rdi +#ifdef FEATURE_NATIVEAOT + mov ax, [rdi + OFFSETOF__MethodTable__m_usNumVtableSlots] + test ax, ax + jnz LOCAL_LABEL(RhpNewFast_ValidMethodTable) + int 3 +LOCAL_LABEL(RhpNewFast_ValidMethodTable): +#endif + + // rax = ee_alloc_context pointer; trashes volatile registers INLINE_GET_ALLOC_CONTEXT_BASE @@ -81,6 +90,14 @@ NESTED_ENTRY RhpNewObject, _TEXT, NoHandler // RCX: transition frame +#ifdef FEATURE_NATIVEAOT + mov ax, [rdi + OFFSETOF__MethodTable__m_usNumVtableSlots] + test ax, ax + jnz LOCAL_LABEL(RhpNewObject_ValidMethodTable) + int 3 +LOCAL_LABEL(RhpNewObject_ValidMethodTable): +#endif + // Preserve the MethodTable in RBX mov rbx, rdi @@ -176,6 +193,14 @@ NESTED_END RhpNewObject, _TEXT // RSI == character/element count LEAF_ENTRY RhNewString, _TEXT +#ifdef FEATURE_NATIVEAOT + mov ax, [rdi + OFFSETOF__MethodTable__m_usNumVtableSlots] + test ax, ax + jnz LOCAL_LABEL(RhNewString_ValidMethodTable) + int 3 +LOCAL_LABEL(RhNewString_ValidMethodTable): +#endif + // we want to limit the element count to the non-negative 32-bit int range cmp rsi, MAX_STRING_LENGTH ja LOCAL_LABEL(StringSizeOverflow) @@ -203,6 +228,14 @@ LEAF_END RhNewString, _TEXT // ESI == element count LEAF_ENTRY RhpNewArrayFast, _TEXT +#ifdef FEATURE_NATIVEAOT + mov ax, [rdi + OFFSETOF__MethodTable__m_usNumVtableSlots] + test ax, ax + jnz LOCAL_LABEL(RhpNewArrayFast_ValidMethodTable) + int 3 +LOCAL_LABEL(RhpNewArrayFast_ValidMethodTable): +#endif + // we want to limit the element count to the non-negative 32-bit int range cmp rsi, 0x07fffffff ja LOCAL_LABEL(ArraySizeOverflow) @@ -232,6 +265,14 @@ LEAF_END RhpNewArrayFast, _TEXT // ESI == element count LEAF_ENTRY RhpNewPtrArrayFast, _TEXT +#ifdef FEATURE_NATIVEAOT + mov ax, [rdi + OFFSETOF__MethodTable__m_usNumVtableSlots] + test ax, ax + jnz LOCAL_LABEL(RhpNewPtrArrayFast_ValidMethodTable) + int 3 +LOCAL_LABEL(RhpNewPtrArrayFast_ValidMethodTable): +#endif + // Delegate overflow handling to the generic helper conservatively // The constant 0x8000000 is (0x40000000 / sizeof(void*)) // Some assemblers don't like an expression here, so the @@ -261,6 +302,14 @@ NESTED_ENTRY RhpNewVariableSizeObject, _TEXT, NoHandler PUSH_COOP_PINVOKE_FRAME rcx +#ifdef FEATURE_NATIVEAOT + mov ax, [rdi + OFFSETOF__MethodTable__m_usNumVtableSlots] + test ax, ax + jnz LOCAL_LABEL(RhpNewVariableSizeObject_ValidMethodTable) + int 3 +LOCAL_LABEL(RhpNewVariableSizeObject_ValidMethodTable): +#endif + // rcx: transition frame // Preserve the MethodTable in RBX diff --git a/src/coreclr/runtime/amd64/AllocFast.asm b/src/coreclr/runtime/amd64/AllocFast.asm index cda88f3bf8f1ec..3a7883f9a67410 100644 --- a/src/coreclr/runtime/amd64/AllocFast.asm +++ b/src/coreclr/runtime/amd64/AllocFast.asm @@ -9,6 +9,14 @@ include AsmMacros_Shared.inc ;; RCX == MethodTable LEAF_ENTRY RhpNewFast, _TEXT +ifdef FEATURE_NATIVEAOT + mov ax, [rcx + OFFSETOF__MethodTable__m_usNumVtableSlots] + test ax, ax + jnz RhpNewFast_ValidMethodTable + int 3 +RhpNewFast_ValidMethodTable: +endif + ;; rdx = ee_alloc_context pointer, TRASHES rax INLINE_GET_ALLOC_CONTEXT_BASE rdx, rax @@ -66,6 +74,14 @@ NESTED_ENTRY RhpNewObject, _TEXT ; R9: transition frame +ifdef FEATURE_NATIVEAOT + mov ax, [rcx + OFFSETOF__MethodTable__m_usNumVtableSlots] + test ax, ax + jnz RhpNewObject_ValidMethodTable + int 3 +RhpNewObject_ValidMethodTable: +endif + ;; Preserve the MethodTable in RSI mov rsi, rcx @@ -131,6 +147,15 @@ ENDM ; NEW_ARRAY_FAST ;; RDX == character/element count LEAF_ENTRY RhNewString, _TEXT +ifdef FEATURE_NATIVEAOT + mov ax, [rcx + OFFSETOF__MethodTable__m_usNumVtableSlots] + test ax, ax + jnz RhNewString_ValidMethodTable + int 3 +RhNewString_ValidMethodTable: +endif + + ; we want to limit the element count to the non-negative 32-bit int range cmp rdx, MAX_STRING_LENGTH ja StringSizeOverflow @@ -158,6 +183,14 @@ LEAF_END RhNewString, _TEXT ;; EDX == element count LEAF_ENTRY RhpNewArrayFast, _TEXT +ifdef FEATURE_NATIVEAOT + mov ax, [rcx + OFFSETOF__MethodTable__m_usNumVtableSlots] + test ax, ax + jnz RhpNewArrayFast_ValidMethodTable + int 3 +RhpNewArrayFast_ValidMethodTable: +endif + ; we want to limit the element count to the non-negative 32-bit int range cmp rdx, 07fffffffh ja ArraySizeOverflow @@ -187,6 +220,14 @@ LEAF_END RhpNewArrayFast, _TEXT ;; EDX == element count LEAF_ENTRY RhpNewPtrArrayFast, _TEXT +ifdef FEATURE_NATIVEAOT + mov ax, [rcx + OFFSETOF__MethodTable__m_usNumVtableSlots] + test ax, ax + jnz RhpNewPtrArrayFast_ValidMethodTable + int 3 +RhpNewPtrArrayFast_ValidMethodTable: +endif + ; Delegate overflow handling to the generic helper conservatively cmp rdx, (40000000h / 8) ; sizeof(void*) @@ -213,6 +254,14 @@ NESTED_ENTRY RhpNewVariableSizeObject, _TEXT PUSH_COOP_PINVOKE_FRAME r9 +ifdef FEATURE_NATIVEAOT + mov ax, [rcx + OFFSETOF__MethodTable__m_usNumVtableSlots] + test ax, ax + jnz RhpNewVariableSizeObject_ValidMethodTable + int 3 +RhpNewVariableSizeObject_ValidMethodTable: +endif + ; r9: transition frame ; Preserve the MethodTable in RSI diff --git a/src/libraries/System.Linq.Expressions/tests/AssemblyInfo.cs b/src/libraries/System.Linq.Expressions/tests/AssemblyInfo.cs index b3954f21c16df4..5e6be3f2923350 100644 --- a/src/libraries/System.Linq.Expressions/tests/AssemblyInfo.cs +++ b/src/libraries/System.Linq.Expressions/tests/AssemblyInfo.cs @@ -3,4 +3,4 @@ using Xunit; -[assembly: SkipOnCoreClr("Long running tests: https://github.com/dotnet/runtime/issues/12927", ~RuntimeConfiguration.Release)] +//[assembly: SkipOnCoreClr("Long running tests: https://github.com/dotnet/runtime/issues/12927", ~RuntimeConfiguration.Release)] From 343aba00d333b838c863ab709c7f6693e0e601b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 27 Aug 2025 07:54:54 +0200 Subject: [PATCH 7/9] Check we might want to keep --- src/coreclr/nativeaot/Runtime/MethodTable.cpp | 57 ++++++++++++++++++- .../nativeaot/Runtime/eventtrace_bulktype.cpp | 4 +- .../nativeaot/Runtime/inc/MethodTable.h | 5 ++ 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/coreclr/nativeaot/Runtime/MethodTable.cpp b/src/coreclr/nativeaot/Runtime/MethodTable.cpp index 60e4e595fc79ea..70b9afafb5506e 100644 --- a/src/coreclr/nativeaot/Runtime/MethodTable.cpp +++ b/src/coreclr/nativeaot/Runtime/MethodTable.cpp @@ -40,8 +40,10 @@ bool MethodTable::Validate(bool assertOnFail /* default: true */) { case CanonicalEEType: { + MethodTable* pBaseType = GetNonArrayBaseType(); + // If the parent type is NULL this had better look like Object. - if (!IsInterface() && (m_RelatedType.m_pBaseType == NULL)) + if (pBaseType == NULL) { if (IsValueType() || HasFinalizer() || @@ -51,6 +53,24 @@ bool MethodTable::Validate(bool assertOnFail /* default: true */) REPORT_FAILURE(); } } + else if (GetNumVtableSlots() == 0 + && !IsGCStaticMethodTable()) + { + // We only really expect zero vtable slots for GCStatic MethodTables, however + // to cover the unlikely case that we managed to trim out Equals/GetHashCode/ToString, + // check an invariant that says if a derived type has zero vtable slots, all bases need + // to have zero slots. + MethodTable* pCurrentType = pBaseType; + do + { + if (pCurrentType->GetNumVtableSlots() > GetNumVtableSlots()) + { + REPORT_FAILURE(); + } + pCurrentType = pCurrentType->GetNonArrayBaseType(); + } + while (pCurrentType != NULL); + } break; } @@ -59,7 +79,8 @@ bool MethodTable::Validate(bool assertOnFail /* default: true */) // The only parameter EETypes that can exist on the heap are arrays // Array types must have a related type. - if (m_RelatedType.m_pRelatedParameterType == NULL) + MethodTable* pParameterType = GetRelatedParameterType(); + if (pParameterType == NULL) REPORT_FAILURE(); // Component size cannot be zero in this case. @@ -73,6 +94,38 @@ bool MethodTable::Validate(bool assertOnFail /* default: true */) REPORT_FAILURE(); } + // Zero vtable slots is suspicious. To cover the unlikely case that we managed to trim + // out Equals/GetHashCode/ToString, compare with number of slots of Object class. + if (GetNumVtableSlots() == 0) + { + // Drill into the type to find System.Object + + MethodTable* pCurrentType = pParameterType; + + while (pCurrentType->IsParameterizedType()) + pCurrentType = pCurrentType->GetRelatedParameterType(); + + // We don't have facilities to unwrap function pointers, so just skip for now. + // We won't get System.Object from interfaces, skip too. + if (!pCurrentType->IsFunctionPointer() && !pCurrentType->IsInterface()) + { + do + { + MethodTable* pBaseType = pCurrentType->GetNonArrayBaseType(); + if (pBaseType == NULL) + { + // Found System.Object, now compare number of slots + if (pCurrentType->GetNumVtableSlots() > GetNumVtableSlots()) + { + REPORT_FAILURE(); + } + } + + pCurrentType = pBaseType; + } while (pCurrentType != NULL); + } + } + break; } diff --git a/src/coreclr/nativeaot/Runtime/eventtrace_bulktype.cpp b/src/coreclr/nativeaot/Runtime/eventtrace_bulktype.cpp index 31d0d43aadbdf2..f21b502a7b606a 100644 --- a/src/coreclr/nativeaot/Runtime/eventtrace_bulktype.cpp +++ b/src/coreclr/nativeaot/Runtime/eventtrace_bulktype.cpp @@ -283,9 +283,9 @@ int BulkTypeEventLogger::LogSingleType(MethodTable * pEEType) // Determine this MethodTable's module. RuntimeInstance * pRuntimeInstance = GetRuntimeInstance(); - // EEType for GC statics are not fully populated and they do not have a valid TypeManager. We will identify them by checking for `ElementType_Unknown`. + // EEType for GC statics are not fully populated and they do not have a valid TypeManager. ULONGLONG osModuleHandle = 0; - if (pEEType->GetElementType() != ElementType_Unknown) + if (!pEEType->IsGCStaticMethodTable()) { osModuleHandle = (ULONGLONG) pEEType->GetTypeManagerPtr()->AsTypeManager()->GetOsModuleHandle(); } diff --git a/src/coreclr/nativeaot/Runtime/inc/MethodTable.h b/src/coreclr/nativeaot/Runtime/inc/MethodTable.h index 89ff445f6174d9..0e8b6929c6c31c 100644 --- a/src/coreclr/nativeaot/Runtime/inc/MethodTable.h +++ b/src/coreclr/nativeaot/Runtime/inc/MethodTable.h @@ -205,6 +205,11 @@ class MethodTable Kinds GetKind(); + bool IsGCStaticMethodTable() + { + return GetElementType() == ElementType_Unknown; + } + bool IsArray() { EETypeElementType elementType = GetElementType(); From c5e507ad8c37d2edac3c10f9274efacd016f86b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 27 Aug 2025 09:47:43 +0200 Subject: [PATCH 8/9] Handle GC statics --- src/coreclr/runtime/amd64/AllocFast.S | 3 +++ src/coreclr/runtime/amd64/AllocFast.asm | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/coreclr/runtime/amd64/AllocFast.S b/src/coreclr/runtime/amd64/AllocFast.S index a7eb24342bdee5..5baeb5fa05b14e 100644 --- a/src/coreclr/runtime/amd64/AllocFast.S +++ b/src/coreclr/runtime/amd64/AllocFast.S @@ -16,6 +16,9 @@ LEAF_ENTRY RhpNewFast, _TEXT mov ax, [rdi + OFFSETOF__MethodTable__m_usNumVtableSlots] test ax, ax jnz LOCAL_LABEL(RhpNewFast_ValidMethodTable) + mov eax, [rdi] + and eax, 0x7C000000 + jz LOCAL_LABEL(RhpNewFast_ValidMethodTable) int 3 LOCAL_LABEL(RhpNewFast_ValidMethodTable): #endif diff --git a/src/coreclr/runtime/amd64/AllocFast.asm b/src/coreclr/runtime/amd64/AllocFast.asm index 3a7883f9a67410..08ef3624602fca 100644 --- a/src/coreclr/runtime/amd64/AllocFast.asm +++ b/src/coreclr/runtime/amd64/AllocFast.asm @@ -13,6 +13,9 @@ ifdef FEATURE_NATIVEAOT mov ax, [rcx + OFFSETOF__MethodTable__m_usNumVtableSlots] test ax, ax jnz RhpNewFast_ValidMethodTable + mov eax, [rcx] + and eax, 7C000000h + jz RhpNewFast_ValidMethodTable int 3 RhpNewFast_ValidMethodTable: endif From 5b89b46c83f6535be7a6baa74db33a1ca09bb95e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 27 Aug 2025 12:01:47 +0200 Subject: [PATCH 9/9] One more --- src/coreclr/runtime/amd64/AllocFast.S | 3 +++ src/coreclr/runtime/amd64/AllocFast.asm | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/coreclr/runtime/amd64/AllocFast.S b/src/coreclr/runtime/amd64/AllocFast.S index 5baeb5fa05b14e..4fa6638ed5f09e 100644 --- a/src/coreclr/runtime/amd64/AllocFast.S +++ b/src/coreclr/runtime/amd64/AllocFast.S @@ -97,6 +97,9 @@ NESTED_ENTRY RhpNewObject, _TEXT, NoHandler mov ax, [rdi + OFFSETOF__MethodTable__m_usNumVtableSlots] test ax, ax jnz LOCAL_LABEL(RhpNewObject_ValidMethodTable) + mov eax, [rdi] + and eax, 0x7C000000 + jz LOCAL_LABEL(RhpNewObject_ValidMethodTable) int 3 LOCAL_LABEL(RhpNewObject_ValidMethodTable): #endif diff --git a/src/coreclr/runtime/amd64/AllocFast.asm b/src/coreclr/runtime/amd64/AllocFast.asm index 08ef3624602fca..2116bb6914d536 100644 --- a/src/coreclr/runtime/amd64/AllocFast.asm +++ b/src/coreclr/runtime/amd64/AllocFast.asm @@ -81,6 +81,9 @@ ifdef FEATURE_NATIVEAOT mov ax, [rcx + OFFSETOF__MethodTable__m_usNumVtableSlots] test ax, ax jnz RhpNewObject_ValidMethodTable + mov eax, [rcx] + and eax, 7C000000h + jz RhpNewObject_ValidMethodTable int 3 RhpNewObject_ValidMethodTable: endif