diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ConstructedEETypeNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ConstructedEETypeNode.cs index da169eabdc5..21abb42d86c 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ConstructedEETypeNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ConstructedEETypeNode.cs @@ -26,6 +26,14 @@ public override bool ShouldSkipEmittingObjectNode(NodeFactory factory) return false; } + public override bool InterestingForDynamicDependencyAnalysis + { + get + { + return _type.IsDefType && _type.HasGenericVirtualMethod(); + } + } + protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory) { DefType closestDefType = _type.GetClosestDefType(); @@ -89,6 +97,12 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact dependencyList.Add(factory.TypeNonGCStaticsSymbol((MetadataType)_type), "Class constructor"); } + // Generated type contains generic virtual methods that will get added to the GVM tables + if (TypeGVMEntriesNode.TypeNeedsGVMTableEntries(_type)) + { + dependencyList.Add(new DependencyListEntry(factory.TypeGVMEntries(_type), "Type with generic virtual methods")); + } + return dependencyList; } diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/GVMDependenciesNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/GVMDependenciesNode.cs new file mode 100644 index 00000000000..23da8552da4 --- /dev/null +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/GVMDependenciesNode.cs @@ -0,0 +1,178 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Collections.Generic; + +using ILCompiler.DependencyAnalysisFramework; +using Internal.Text; +using Internal.TypeSystem; + +namespace ILCompiler.DependencyAnalysis +{ + /// + /// This analysis node is used for computing GVM dependencies for the following cases: + /// 1) Derived types where the GVM is overridden + /// 2) Variant-interfaces GVMs + /// This analysis node will ensure that the proper GVM instantiations are compiled on types. + /// + internal class GVMDependenciesNode : DependencyNodeCore + { + private MethodDesc _method; + + public GVMDependenciesNode(MethodDesc method) + { + Debug.Assert(!method.IsRuntimeDeterminedExactMethod); + Debug.Assert(method.IsVirtual && method.HasInstantiation); + _method = method; + } + + public override bool HasConditionalStaticDependencies => false; + public override bool InterestingForDynamicDependencyAnalysis => false; + public override bool StaticDependenciesAreComputed => true; + protected override string GetName() => "__GVMDependenciesNode_" + NodeFactory.NameMangler.GetMangledMethodName(_method); + + public override IEnumerable GetStaticDependencies(NodeFactory context) + { + return Array.Empty(); + } + public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) + { + return Array.Empty(); + } + + public override bool HasDynamicDependencies + { + get + { + if (_method.IsCanonicalMethod(CanonicalFormKind.Specific)) + return false; + + if (_method.OwningType.IsCanonicalSubtype(CanonicalFormKind.Universal) && + _method.OwningType != _method.OwningType.ConvertToCanonForm(CanonicalFormKind.Universal)) + return false; + + return true; + } + } + + public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory factory) + { + Debug.Assert(_method.IsVirtual && _method.HasInstantiation); + + List dynamicDependencies = new List(); + + for (int i = firstNode; i < markedNodes.Count; i++) + { + DependencyNodeCore entry = markedNodes[i]; + EETypeNode entryAsEETypeNode = entry as EETypeNode; + + if (entryAsEETypeNode == null) + continue; + + TypeDesc potentialOverrideType = entryAsEETypeNode.Type; + if (!(potentialOverrideType is DefType)) + continue; + + Debug.Assert(!potentialOverrideType.IsRuntimeDeterminedSubtype); + + if (_method.OwningType.HasSameTypeDefinition(potentialOverrideType) && potentialOverrideType.IsInterface && (potentialOverrideType != _method.OwningType)) + { + if (_method.OwningType.CanCastTo(potentialOverrideType)) + { + // Variance expansion + MethodDesc matchingMethodOnRelatedVariantMethod = potentialOverrideType.GetMethod(_method.Name, _method.GetTypicalMethodDefinition().Signature); + matchingMethodOnRelatedVariantMethod = _method.Context.GetInstantiatedMethod(matchingMethodOnRelatedVariantMethod, _method.Instantiation); + dynamicDependencies.Add(new CombinedDependencyListEntry(factory.GVMDependencies(matchingMethodOnRelatedVariantMethod), null, "GVM Variant Interface dependency")); + } + } + + // If this is an interface gvm, look for types that implement the interface + // and other instantantiations that have the same canonical form. + // This ensure the various slot numbers remain equivalent across all types where there is an equivalence + // relationship in the vtable. + if (_method.OwningType.IsInterface) + { + if (potentialOverrideType.IsInterface) + continue; + + foreach (DefType interfaceImpl in potentialOverrideType.RuntimeInterfaces) + { + if (interfaceImpl.ConvertToCanonForm(CanonicalFormKind.Specific) == _method.OwningType.ConvertToCanonForm(CanonicalFormKind.Specific)) + { + // Find if the type implements this method. (Note, do this comparision against the generic definition of the method, not the + // specific method instantiation that is "method" + MethodDesc genericDefinition = interfaceImpl.GetMethod(_method.Name, _method.GetTypicalMethodDefinition().Signature); + + MethodDesc slotDecl; + TypeDesc currentType = potentialOverrideType; + do + { + slotDecl = currentType.ResolveInterfaceMethodToVirtualMethodOnType(genericDefinition); + currentType = currentType.BaseType; + } + while (slotDecl == null && currentType != null); + + if (slotDecl != null) + CreateDependencyForMethodSlotAndInstantiation(slotDecl, dynamicDependencies, factory); + } + } + } + else + { + // TODO: Ensure GVM Canon Target + + TypeDesc overrideTypeCanonCur = potentialOverrideType; + TypeDesc methodCanonContainingType = _method.OwningType; + while (overrideTypeCanonCur != null) + { + if (overrideTypeCanonCur.ConvertToCanonForm(CanonicalFormKind.Specific) == methodCanonContainingType.ConvertToCanonForm(CanonicalFormKind.Specific)) + { + MethodDesc methodDefInDerivedType = potentialOverrideType.GetMethod(_method.Name, _method.GetTypicalMethodDefinition().Signature); + if(methodDefInDerivedType != null) + CreateDependencyForMethodSlotAndInstantiation(methodDefInDerivedType, dynamicDependencies, factory); + + MethodDesc slotDecl = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(_method); + if (slotDecl != null) + CreateDependencyForMethodSlotAndInstantiation(slotDecl.GetMethodDefinition(), dynamicDependencies, factory); + } + + overrideTypeCanonCur = overrideTypeCanonCur.BaseType; + } + } + } + return dynamicDependencies; + } + + private void CreateDependencyForMethodSlotAndInstantiation(MethodDesc methodDef, List dynamicDependencies, NodeFactory factory) + { + Debug.Assert(methodDef != null); + Debug.Assert(!methodDef.Signature.IsStatic); + + MethodDesc derivedMethodInstantiation = _method.Context.GetInstantiatedMethod(methodDef, _method.Instantiation); + + // Universal canonical instantiations should be entirely universal canon + if (derivedMethodInstantiation.IsCanonicalMethod(CanonicalFormKind.Universal)) + { + derivedMethodInstantiation = derivedMethodInstantiation.GetCanonMethodTarget(CanonicalFormKind.Universal); + } + + // TODO: verify for invalid instantiations, like List? + bool validInstantiation = + derivedMethodInstantiation.IsSharedByGenericInstantiations || // Non-exact methods are always valid instantiations (always pass constraints check) + derivedMethodInstantiation.CheckConstraints(); // Verify that the instantiation does not violate constraints + + if (validInstantiation) + { + bool getUnboxingStub = (derivedMethodInstantiation.OwningType.IsValueType || derivedMethodInstantiation.OwningType.IsEnum); + dynamicDependencies.Add(new CombinedDependencyListEntry(factory.MethodEntrypoint(derivedMethodInstantiation, getUnboxingStub), null, "DerivedMethodInstantiation")); + } + else + { + // TODO: universal generics + } + } + } +} diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/GenericVirtualMethodTableNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/GenericVirtualMethodTableNode.cs new file mode 100644 index 00000000000..59f3e10473f --- /dev/null +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/GenericVirtualMethodTableNode.cs @@ -0,0 +1,144 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using System.Diagnostics; +using System.Collections.Generic; + +using Internal.Text; +using Internal.TypeSystem; +using Internal.NativeFormat; + +namespace ILCompiler.DependencyAnalysis +{ + /// + /// Represents a map between reflection metadata and generated method bodies. + /// + internal sealed class GenericVirtualMethodTableNode : ObjectNode, ISymbolNode + { + private ObjectAndOffsetSymbolNode _endSymbol; + private ExternalReferencesTableNode _externalReferences; + private Dictionary> _gvmImplemenations; + + public GenericVirtualMethodTableNode(ExternalReferencesTableNode externalReferences) + { + _endSymbol = new ObjectAndOffsetSymbolNode(this, 0, "__gvm_table_End", true); + _externalReferences = externalReferences; + _gvmImplemenations = new Dictionary>(); + } + + public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) + { + sb.Append(nameMangler.CompilationUnitPrefix).Append("__gvm_table"); + } + + public ISymbolNode EndSymbol => _endSymbol; + public int Offset => 0; + public override bool IsShareable => false; + public override ObjectNodeSection Section => ObjectNodeSection.DataSection; + public override bool StaticDependenciesAreComputed => true; + protected override string GetName() => this.GetMangledName(); + + public static DependencyList GetGenericVirtualMethodImplementationDependencies(NodeFactory factory, MethodDesc callingMethod, MethodDesc implementationMethod) + { + Debug.Assert(!callingMethod.OwningType.IsInterface); + + DependencyList dependencyNodes = new DependencyList(); + + // Compute the open method signatures + MethodDesc openCallingMethod = callingMethod.GetTypicalMethodDefinition(); + MethodDesc openImplementationMethod = implementationMethod.GetTypicalMethodDefinition(); + + var openCallingMethodNameAndSig = factory.NativeLayout.MethodNameAndSignatureVertex(openCallingMethod); + var openImplementationMethodNameAndSig = factory.NativeLayout.MethodNameAndSignatureVertex(openImplementationMethod); + + dependencyNodes.Add(new DependencyListEntry(factory.NativeLayout.PlacedSignatureVertex(openCallingMethodNameAndSig), "gvm table needed signature")); + dependencyNodes.Add(new DependencyListEntry(factory.NativeLayout.PlacedSignatureVertex(openImplementationMethodNameAndSig), "gvm table needed signature")); + + return dependencyNodes; + } + + private void AddGenericVirtualMethodImplementation(NodeFactory factory, MethodDesc callingMethod, MethodDesc implementationMethod) + { + Debug.Assert(!callingMethod.OwningType.IsInterface); + + // Compute the open method signatures + MethodDesc openCallingMethod = callingMethod.GetTypicalMethodDefinition(); + MethodDesc openImplementationMethod = implementationMethod.GetTypicalMethodDefinition(); + + // Insert open method signatures into the GVM map + if (!_gvmImplemenations.ContainsKey(openCallingMethod)) + _gvmImplemenations[openCallingMethod] = new Dictionary(); + + _gvmImplemenations[openCallingMethod][openImplementationMethod.OwningType] = openImplementationMethod; + } + + public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) + { + // This node does not trigger generation of other nodes. + if (relocsOnly) + return new ObjectData(Array.Empty(), Array.Empty(), 1, new ISymbolNode[] { this }); + + // Build the GVM table entries from the list of interesting GVMTableEntryNodes + foreach (var interestingEntry in factory.MetadataManager.GetTypeGVMEntries()) + { + foreach (var typeGVMEntryInfo in interestingEntry.ScanForGenericVirtualMethodEntries()) + { + AddGenericVirtualMethodImplementation(factory, typeGVMEntryInfo.CallingMethod, typeGVMEntryInfo.ImplementationMethod); + } + } + + // Ensure the native layout blob has been saved + factory.MetadataManager.NativeLayoutInfo.SaveNativeLayoutInfoWriter(factory); + + NativeWriter nativeFormatWriter = new NativeWriter(); + VertexHashtable gvmHashtable = new VertexHashtable(); + + Section gvmHashtableSection = nativeFormatWriter.NewSection(); + gvmHashtableSection.Place(gvmHashtable); + + // Emit the GVM target information entries + foreach (var gvmEntry in _gvmImplemenations) + { + Debug.Assert(!gvmEntry.Key.OwningType.IsInterface); + + foreach (var implementationEntry in gvmEntry.Value) + { + MethodDesc callingMethod = gvmEntry.Key; + TypeDesc implementationType = implementationEntry.Key; + MethodDesc implementationMethod = implementationEntry.Value; + + uint callingTypeId = _externalReferences.GetIndex(factory.NecessaryTypeSymbol(callingMethod.OwningType)); + Vertex vertex = nativeFormatWriter.GetUnsignedConstant(callingTypeId); + + uint targetTypeId = _externalReferences.GetIndex(factory.NecessaryTypeSymbol(implementationType)); + vertex = nativeFormatWriter.GetTuple(vertex, nativeFormatWriter.GetUnsignedConstant(targetTypeId)); + + var nameAndSig = factory.NativeLayout.PlacedSignatureVertex(factory.NativeLayout.MethodNameAndSignatureVertex(callingMethod)); + vertex = nativeFormatWriter.GetTuple(vertex, nativeFormatWriter.GetUnsignedConstant((uint)nameAndSig.SavedVertex.VertexOffset)); + + nameAndSig = factory.NativeLayout.PlacedSignatureVertex(factory.NativeLayout.MethodNameAndSignatureVertex(implementationMethod)); + vertex = nativeFormatWriter.GetTuple(vertex, nativeFormatWriter.GetUnsignedConstant((uint)nameAndSig.SavedVertex.VertexOffset)); + + int hashCode = callingMethod.OwningType.GetHashCode(); + hashCode = ((hashCode << 13) ^ hashCode) ^ implementationType.GetHashCode(); + + gvmHashtable.Append((uint)hashCode, gvmHashtableSection.Place(vertex)); + } + } + + // Zero out the dictionary so that we AV if someone tries to insert after we're done. + _gvmImplemenations = null; + + MemoryStream stream = new MemoryStream(); + nativeFormatWriter.Save(stream); + byte[] streamBytes = stream.ToArray(); + + _endSymbol.SetSymbolOffset(streamBytes.Length); + + return new ObjectData(streamBytes, Array.Empty(), 1, new ISymbolNode[] { this, _endSymbol }); + } + } +} \ No newline at end of file diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/InterfaceGenericVirtualMethodTableNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/InterfaceGenericVirtualMethodTableNode.cs new file mode 100644 index 00000000000..f732d2fe304 --- /dev/null +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/InterfaceGenericVirtualMethodTableNode.cs @@ -0,0 +1,213 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using System.Diagnostics; +using System.Collections.Generic; + +using Internal.Text; +using Internal.TypeSystem; +using Internal.NativeFormat; + +namespace ILCompiler.DependencyAnalysis +{ + /// + /// Represents a map between reflection metadata and generated method bodies. + /// + internal sealed class InterfaceGenericVirtualMethodTableNode : ObjectNode, ISymbolNode + { + private ObjectAndOffsetSymbolNode _endSymbol; + private ExternalReferencesTableNode _externalReferences; + private Dictionary> _interfaceGvmSlots; + private Dictionary>> _interfaceImpls; + + public InterfaceGenericVirtualMethodTableNode(ExternalReferencesTableNode externalReferences) + { + _endSymbol = new ObjectAndOffsetSymbolNode(this, 0, "__interface_gvm_table_End", true); + _externalReferences = externalReferences; + _interfaceGvmSlots = new Dictionary>(); + _interfaceImpls = new Dictionary>>(); + } + + public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) + { + sb.Append(nameMangler.CompilationUnitPrefix).Append("__interface_gvm_table"); + } + public ISymbolNode EndSymbol => _endSymbol; + public int Offset => 0; + public override bool IsShareable => false; + public override ObjectNodeSection Section => ObjectNodeSection.DataSection; + public override bool StaticDependenciesAreComputed => true; + protected override string GetName() => this.GetMangledName(); + + public static DependencyList GetGenericVirtualMethodImplementationDependencies(NodeFactory factory, MethodDesc callingMethod, TypeDesc implementationType, MethodDesc implementationMethod) + { + Debug.Assert(callingMethod.OwningType.IsInterface); + + DependencyList dependencyNodes = new DependencyList(); + + // Compute the open method signatures + MethodDesc openCallingMethod = callingMethod.GetTypicalMethodDefinition(); + MethodDesc openImplementationMethod = implementationMethod.GetTypicalMethodDefinition(); + TypeDesc openImplementationType = implementationType.GetTypeDefinition(); + + var openCallingMethodNameAndSig = factory.NativeLayout.MethodNameAndSignatureVertex(openCallingMethod); + var openImplementationMethodNameAndSig = factory.NativeLayout.MethodNameAndSignatureVertex(openImplementationMethod); + + dependencyNodes.Add(new DependencyListEntry(factory.NativeLayout.PlacedSignatureVertex(openCallingMethodNameAndSig), "gvm table needed signature")); + dependencyNodes.Add(new DependencyListEntry(factory.NativeLayout.PlacedSignatureVertex(openImplementationMethodNameAndSig), "gvm table needed signature")); + + if (!openImplementationType.IsInterface) + { + for(int index = 0; index < implementationType.RuntimeInterfaces.Length; index++) + { + if (implementationType.RuntimeInterfaces[index] == callingMethod.OwningType) + { + TypeDesc currentInterface = openImplementationType.RuntimeInterfaces[index]; + var currentInterfaceSignature = factory.NativeLayout.TypeSignatureVertex(currentInterface); + dependencyNodes.Add(new DependencyListEntry(factory.NativeLayout.PlacedSignatureVertex(currentInterfaceSignature), "gvm table needed signature")); + } + } + } + + return dependencyNodes; + } + + private void AddGenericVirtualMethodImplementation(NodeFactory factory, MethodDesc callingMethod, TypeDesc implementationType, MethodDesc implementationMethod) + { + Debug.Assert(callingMethod.OwningType.IsInterface); + + // Compute the open method signatures + MethodDesc openCallingMethod = callingMethod.GetTypicalMethodDefinition(); + MethodDesc openImplementationMethod = implementationMethod.GetTypicalMethodDefinition(); + TypeDesc openImplementationType = implementationType.GetTypeDefinition(); + + // Add the entry to the interface GVM slots mapping table + if (!_interfaceGvmSlots.ContainsKey(openCallingMethod)) + _interfaceGvmSlots[openCallingMethod] = new HashSet(); + _interfaceGvmSlots[openCallingMethod].Add(openImplementationMethod); + + // If the implementation method is implementing some interface method, compute which + // interface explicitly implemented on the type that the current method implements an interface method for. + // We need this because at runtime, the interfaces explicitly implemented on the type will have + // runtime-determined signatures that we can use to make generic substitutions and check for interface matching. + if (!openImplementationType.IsInterface) + { + if (!_interfaceImpls.ContainsKey(openImplementationMethod)) + _interfaceImpls[openImplementationMethod] = new Dictionary>(); + if (!_interfaceImpls[openImplementationMethod].ContainsKey(openImplementationType)) + _interfaceImpls[openImplementationMethod][openImplementationType] = new HashSet(); + + int numIfacesAdded = 0; + for (int index = 0; index < implementationType.RuntimeInterfaces.Length; index++) + { + if (implementationType.RuntimeInterfaces[index] == callingMethod.OwningType) + { + _interfaceImpls[openImplementationMethod][openImplementationType].Add(index); + numIfacesAdded++; + } + } + + Debug.Assert(numIfacesAdded > 0); + } + } + + public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) + { + // This node does not trigger generation of other nodes. + if (relocsOnly) + return new ObjectData(Array.Empty(), Array.Empty(), 1, new ISymbolNode[] { this }); + + // Build the GVM table entries from the list of interesting GVMTableEntryNodes + foreach (var interestingEntry in factory.MetadataManager.GetTypeGVMEntries()) + { + foreach (var typeGVMEntryInfo in interestingEntry.ScanForInterfaceGenericVirtualMethodEntries()) + { + AddGenericVirtualMethodImplementation(factory, typeGVMEntryInfo.CallingMethod, typeGVMEntryInfo.ImplementationType, typeGVMEntryInfo.ImplementationMethod); + } + } + + // Ensure the native layout blob has been saved + factory.MetadataManager.NativeLayoutInfo.SaveNativeLayoutInfoWriter(factory); + + NativeWriter nativeFormatWriter = new NativeWriter(); + VertexHashtable gvmHashtable = new VertexHashtable(); + + Section gvmHashtableSection = nativeFormatWriter.NewSection(); + gvmHashtableSection.Place(gvmHashtable); + + // Emit the interface slot resolution entries + foreach (var gvmEntry in _interfaceGvmSlots) + { + Debug.Assert(gvmEntry.Key.OwningType.IsInterface); + + MethodDesc callingMethod = gvmEntry.Key; + + // Emit the method signature and containing type of the current interface method + uint typeId = _externalReferences.GetIndex(factory.NecessaryTypeSymbol(callingMethod.OwningType)); + var nameAndSig = factory.NativeLayout.PlacedSignatureVertex(factory.NativeLayout.MethodNameAndSignatureVertex(callingMethod)); + Vertex vertex = nativeFormatWriter.GetTuple( + nativeFormatWriter.GetUnsignedConstant(typeId), + nativeFormatWriter.GetUnsignedConstant((uint)nameAndSig.SavedVertex.VertexOffset)); + + // Emit the method name / sig and containing type of each GVM target method for the current interface method entry + vertex = nativeFormatWriter.GetTuple(vertex, nativeFormatWriter.GetUnsignedConstant((uint)gvmEntry.Value.Count)); + foreach (MethodDesc implementationMethod in gvmEntry.Value) + { + nameAndSig = factory.NativeLayout.PlacedSignatureVertex(factory.NativeLayout.MethodNameAndSignatureVertex(implementationMethod)); + typeId = _externalReferences.GetIndex(factory.NecessaryTypeSymbol(implementationMethod.OwningType)); + vertex = nativeFormatWriter.GetTuple( + vertex, + nativeFormatWriter.GetUnsignedConstant((uint)nameAndSig.SavedVertex.VertexOffset), + nativeFormatWriter.GetUnsignedConstant(typeId)); + + // Emit the interface GVM slot details for each type that implements the interface methods + { + Debug.Assert(_interfaceImpls.ContainsKey(implementationMethod)); + + var ifaceImpls = _interfaceImpls[implementationMethod]; + + // First, emit how many types have method implementations for this interface method entry + vertex = nativeFormatWriter.GetTuple(vertex, nativeFormatWriter.GetUnsignedConstant((uint)ifaceImpls.Count)); + + // Emit each type that implements the interface method, and the interface signatures for the interfaces implemented by the type + foreach (var currentImpl in ifaceImpls) + { + TypeDesc implementationType = currentImpl.Key; + + typeId = _externalReferences.GetIndex(factory.NecessaryTypeSymbol(implementationType)); + vertex = nativeFormatWriter.GetTuple(vertex, nativeFormatWriter.GetUnsignedConstant(typeId)); + + // Emit information on which interfaces the current method entry provides implementations for + vertex = nativeFormatWriter.GetTuple(vertex, nativeFormatWriter.GetUnsignedConstant((uint)currentImpl.Value.Count)); + foreach (var ifaceId in currentImpl.Value) + { + // Emit the signature of the current interface implemented by the method + Debug.Assert(((uint)ifaceId) < implementationType.RuntimeInterfaces.Length); + TypeDesc currentInterface = implementationType.RuntimeInterfaces[ifaceId]; + var typeSig = factory.NativeLayout.PlacedSignatureVertex(factory.NativeLayout.TypeSignatureVertex(currentInterface)); + vertex = nativeFormatWriter.GetTuple(vertex, nativeFormatWriter.GetUnsignedConstant((uint)typeSig.SavedVertex.VertexOffset)); + } + } + } + } + + int hashCode = callingMethod.OwningType.GetHashCode(); + gvmHashtable.Append((uint)hashCode, gvmHashtableSection.Place(vertex)); + } + + // Zero out the dictionary so that we AV if someone tries to insert after we're done. + _interfaceGvmSlots = null; + + MemoryStream stream = new MemoryStream(); + nativeFormatWriter.Save(stream); + byte[] streamBytes = stream.ToArray(); + + _endSymbol.SetSymbolOffset(streamBytes.Length); + + return new ObjectData(streamBytes, Array.Empty(), 1, new ISymbolNode[] { this, _endSymbol }); + } + } +} \ No newline at end of file diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/MethodCodeNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/MethodCodeNode.cs index fb45db873af..64c691e864e 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/MethodCodeNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/MethodCodeNode.cs @@ -107,6 +107,13 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact dependencies = dependencies ?? new DependencyList(); dependencies.AddRange(exactMethodInstantiationDependencies); } + + if (_method.IsVirtual) + { + // Generic virtual methods dependency tracking + dependencies = dependencies ?? new DependencyList(); + dependencies.Add(new DependencyListEntry(factory.GVMDependencies(_method), "GVM Dependencies Support")); + } } return dependencies; diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs index 083017c3ab5..42d875fecf9 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs @@ -192,6 +192,16 @@ private void CreateNodeCaches() return new FatFunctionPointerNode(method); }); + _gvmDependenciesNode = new NodeCache(method => + { + return new GVMDependenciesNode(method); + }); + + _gvmTableEntries = new NodeCache(type => + { + return new TypeGVMEntriesNode(type); + }); + _shadowConcreteMethods = new NodeCache(method => { return new ShadowConcreteMethodNode(method, @@ -520,6 +530,18 @@ public IMethodNode FatFunctionPointer(MethodDesc method) return _fatFunctionPointers.GetOrAdd(method); } + private NodeCache _gvmDependenciesNode; + internal GVMDependenciesNode GVMDependencies(MethodDesc method) + { + return _gvmDependenciesNode.GetOrAdd(method); + } + + private NodeCache _gvmTableEntries; + internal TypeGVMEntriesNode TypeGVMEntries(TypeDesc type) + { + return _gvmTableEntries.GetOrAdd(type); + } + private NodeCache _shadowConcreteMethods; public IMethodNode ShadowConcreteMethod(MethodDesc method) diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ReadyToRunHelperNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ReadyToRunHelperNode.cs index 477eef6dd80..ddd4f29292f 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ReadyToRunHelperNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ReadyToRunHelperNode.cs @@ -156,6 +156,27 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact dependencyList.Add(factory.VirtualMethodUse((MethodDesc)_target), "ReadyToRun Virtual Method Address Load"); return dependencyList; } + else if (_id == ReadyToRunHelperId.ResolveGenericVirtualMethod) + { + MethodDesc method = _target as MethodDesc; + Debug.Assert(method != null && method.HasInstantiation && method.IsVirtual); + + DependencyList dependencyList = new DependencyList(); + + // GVM dependency tracking + dependencyList.Add(new DependencyListEntry(factory.GVMDependencies(method), "GVM LDToken Support")); + + // GVM entry point + if (!method.IsAbstract) + { + // TODO: shared generic entry point + + // Target method could be an interface GVM, and in that case won't have an entry point as a dependency + dependencyList.Add(new DependencyListEntry(factory.MethodEntrypoint(method), "R2RHelperNode GVM target method")); + } + + return dependencyList; + } else { return null; diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/TypeGVMEntriesNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/TypeGVMEntriesNode.cs new file mode 100644 index 00000000000..4a33b9eaebb --- /dev/null +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/TypeGVMEntriesNode.cs @@ -0,0 +1,120 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +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, TypeDesc implementationType) + { + CallingMethod = callingMethod; + ImplementationMethod = implementationMethod; + ImplementationType = implementationType; + } + public MethodDesc CallingMethod { get; private set; } + public MethodDesc ImplementationMethod { get; private set; } + public TypeDesc ImplementationType { get; private set; } + } + + private TypeDesc _associatedType; + private DependencyList _staticDependencies; + + public TypeGVMEntriesNode(TypeDesc associatedType) + { + Debug.Assert(!associatedType.IsRuntimeDeterminedSubtype); + Debug.Assert(TypeNeedsGVMTableEntries(associatedType)); + _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() => "__TypeGVMEntriesNode_" + NodeFactory.NameMangler.GetMangledTypeName(_associatedType); + public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) + { + return Array.Empty(); + } + public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory context) + { + return Array.Empty(); + } + + public override IEnumerable GetStaticDependencies(NodeFactory context) + { + if (_staticDependencies == null) + { + _staticDependencies = new DependencyList(); + + foreach(var entry in ScanForGenericVirtualMethodEntries()) + _staticDependencies.AddRange(GenericVirtualMethodTableNode.GetGenericVirtualMethodImplementationDependencies(context, entry.CallingMethod, entry.ImplementationMethod)); + + foreach (var entry in ScanForInterfaceGenericVirtualMethodEntries()) + _staticDependencies.AddRange(InterfaceGenericVirtualMethodTableNode.GetGenericVirtualMethodImplementationDependencies(context, entry.CallingMethod, entry.ImplementationType, entry.ImplementationMethod)); + } + + return _staticDependencies; + } + + public static bool TypeNeedsGVMTableEntries(TypeDesc type) + { + // Only non-interface deftypes can have entries for their GVMs in the GVM hashtables. + // Interface GVM entries are computed for types that implemenent the interface (not for the interface on its own) + if(!type.IsDefType || type.IsInterface) + return false; + + return type.HasGenericVirtualMethod(); + } + + public IEnumerable ScanForGenericVirtualMethodEntries() + { + foreach (var method in _associatedType.GetMethods()) + { + if (!method.IsVirtual || !method.HasInstantiation) + continue; + + MethodDesc slotDecl = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(method); + Debug.Assert(slotDecl != null); + yield return new TypeGVMEntryInfo(slotDecl, method, null); + } + } + + public IEnumerable ScanForInterfaceGenericVirtualMethodEntries() + { + foreach (var iface in _associatedType.RuntimeInterfaces) + { + foreach (var method in iface.GetMethods()) + { + if (!method.HasInstantiation || method.Signature.IsStatic) + continue; + + MethodDesc slotDecl; + TypeDesc currentType = _associatedType; + do + { + slotDecl = currentType.ResolveInterfaceMethodToVirtualMethodOnType(method); + currentType = currentType.BaseType; + } + while (slotDecl == null && currentType != null); + + Debug.Assert(slotDecl != null); + yield return new TypeGVMEntryInfo(method, slotDecl, _associatedType); + } + } + } + } +} \ No newline at end of file diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/VTableSliceNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/VTableSliceNode.cs index 666b8569aca..f82e6f4732e 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/VTableSliceNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/VTableSliceNode.cs @@ -60,6 +60,10 @@ public EagerlyBuiltVTableSliceNode(TypeDesc type) if (!method.IsVirtual) continue; + // GVMs are not emitted in the type's vtable. + if (method.HasInstantiation) + continue; + slots.Add(method); } @@ -121,6 +125,8 @@ public override IReadOnlyList Slots public void AddEntry(NodeFactory factory, MethodDesc virtualMethod) { + // GVMs are not emitted in the type's vtable. + Debug.Assert(!virtualMethod.HasInstantiation); Debug.Assert(virtualMethod.IsVirtual); #if DEBUG Debug.Assert(!_slotsCommitted); diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/VirtualMethodUseNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/VirtualMethodUseNode.cs index 259207ec60f..7dff15fe339 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/VirtualMethodUseNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/VirtualMethodUseNode.cs @@ -32,7 +32,7 @@ protected override void OnMarked(NodeFactory factory) // If the VTable slice is getting built on demand, the fact that the virtual method is used means // that the slot is used. var lazyVTableSlice = factory.VTable(_decl.OwningType) as LazilyBuiltVTableSliceNode; - if (lazyVTableSlice != null) + if (lazyVTableSlice != null && !_decl.HasInstantiation) lazyVTableSlice.AddEntry(factory, _decl); } diff --git a/src/ILCompiler.Compiler/src/Compiler/MetadataGeneration.cs b/src/ILCompiler.Compiler/src/Compiler/MetadataGeneration.cs index ff39e1b514c..f1da9479109 100644 --- a/src/ILCompiler.Compiler/src/Compiler/MetadataGeneration.cs +++ b/src/ILCompiler.Compiler/src/Compiler/MetadataGeneration.cs @@ -43,6 +43,7 @@ public class MetadataGeneration private HashSet _typesWithEETypesGenerated = new HashSet(); private HashSet _methodDefinitionsGenerated = new HashSet(); private HashSet _methodsGenerated = new HashSet(); + private List _typeGVMEntries = new List(); private Dictionary _dynamicInvokeThunks = new Dictionary(); @@ -104,7 +105,13 @@ public void AddToReadyToRunHeader(ReadyToRunHeaderNode header) var genericsHashtable = new GenericsHashtableNode(nativeReferencesTableNode); header.Add(BlobIdToReadyToRunSection(ReflectionMapBlob.GenericsHashtable), genericsHashtable, genericsHashtable, genericsHashtable.EndSymbol); - // This one should go last + var genericVirtualMethodTableNode = new GenericVirtualMethodTableNode(commonFixupsTableNode); + header.Add(BlobIdToReadyToRunSection(ReflectionMapBlob.GenericVirtualMethodTable), genericVirtualMethodTableNode, genericVirtualMethodTableNode, genericVirtualMethodTableNode.EndSymbol); + + var interfaceGenericVirtualMethodTableNode = new InterfaceGenericVirtualMethodTableNode(commonFixupsTableNode); + header.Add(BlobIdToReadyToRunSection(ReflectionMapBlob.InterfaceGenericVirtualMethodTable), interfaceGenericVirtualMethodTableNode, interfaceGenericVirtualMethodTableNode, interfaceGenericVirtualMethodTableNode.EndSymbol); + + // The external references tables should go last header.Add(BlobIdToReadyToRunSection(ReflectionMapBlob.CommonFixupsTable), commonFixupsTableNode, commonFixupsTableNode, commonFixupsTableNode.EndSymbol); header.Add(BlobIdToReadyToRunSection(ReflectionMapBlob.NativeReferences), nativeReferencesTableNode, nativeReferencesTableNode, nativeReferencesTableNode.EndSymbol); } @@ -143,6 +150,12 @@ private void Graph_NewMarkedNode(DependencyNodeCore obj) { _cctorContextsGenerated.Add(nonGcStaticSectionNode); } + + var gvmEntryNode = obj as TypeGVMEntriesNode; + if (gvmEntryNode != null) + { + _typeGVMEntries.Add(gvmEntryNode); + } } public bool HasReflectionInvokeStub(MethodDesc method) @@ -384,6 +397,11 @@ internal IEnumerable GetArrayTypeMapping() return _arrayTypesGenerated; } + internal IEnumerable GetTypeGVMEntries() + { + return _typeGVMEntries; + } + internal IEnumerable GetCompiledMethods() { return _methodsGenerated; diff --git a/src/ILCompiler.Compiler/src/Compiler/TypeExtensions.cs b/src/ILCompiler.Compiler/src/Compiler/TypeExtensions.cs index 93286b1cdf9..fc6c804c365 100644 --- a/src/ILCompiler.Compiler/src/Compiler/TypeExtensions.cs +++ b/src/ILCompiler.Compiler/src/Compiler/TypeExtensions.cs @@ -84,5 +84,20 @@ public static bool AcquiresInstMethodTableFromThis(this MethodDesc method) !method.Signature.IsStatic && !method.ImplementationType.IsValueType; } + + + /// + /// Gets a value indicating whether this type has any generic virtual methods. + /// + public static bool HasGenericVirtualMethod(this TypeDesc type) + { + foreach (var method in type.GetAllMethods()) + { + if (method.IsVirtual && method.HasInstantiation) + return true; + } + + return false; + } } } diff --git a/src/ILCompiler.Compiler/src/ILCompiler.Compiler.csproj b/src/ILCompiler.Compiler/src/ILCompiler.Compiler.csproj index 2330e0a02f3..71e22fd64b5 100644 --- a/src/ILCompiler.Compiler/src/ILCompiler.Compiler.csproj +++ b/src/ILCompiler.Compiler/src/ILCompiler.Compiler.csproj @@ -102,13 +102,17 @@ + + + + + - @@ -131,7 +135,7 @@ - + diff --git a/src/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.SignatureParsing.cs b/src/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.SignatureParsing.cs index aafd0ee2675..8ceef7f4cbe 100644 --- a/src/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.SignatureParsing.cs +++ b/src/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.SignatureParsing.cs @@ -91,8 +91,7 @@ public bool TryGetMethodNameAndSignatureFromNativeLayoutSignature(RuntimeSignatu nameAndSignature = null; NativeReader reader = GetNativeLayoutInfoReader(signature.ModuleHandle); - uint offset = signature.NativeLayoutOffset; - NativeParser parser = new NativeParser(reader, offset); + NativeParser parser = new NativeParser(reader, signature.NativeLayoutOffset); if (parser.IsNull) return false; @@ -109,8 +108,7 @@ public bool TryGetMethodNameAndSignaturePointersFromNativeLayoutSignature(IntPtr methodSig = default(RuntimeSignature); NativeReader reader = GetNativeLayoutInfoReader(module); - uint offset = methodNameAndSigToken; - NativeParser parser = new NativeParser(reader, offset); + NativeParser parser = new NativeParser(reader, methodNameAndSigToken); if (parser.IsNull) return false;