// 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 Internal.Text; using Internal.TypeSystem; using Internal.NativeFormat; using Internal.Runtime; namespace ILCompiler.DependencyAnalysis { /// <summary> /// Represents a map between reflection metadata and generated method bodies. /// </summary> public sealed class InterfaceGenericVirtualMethodTableNode : ObjectNode, ISymbolDefinitionNode { private ExternalReferencesTableNode _externalReferences; private Dictionary<MethodDesc, HashSet<object>> _interfaceGvmSlots; private Dictionary<object, Dictionary<TypeDesc, HashSet<int>>> _interfaceImpls; public InterfaceGenericVirtualMethodTableNode(ExternalReferencesTableNode externalReferences) { _externalReferences = externalReferences; _interfaceGvmSlots = new Dictionary<MethodDesc, HashSet<object>>(); _interfaceImpls = new Dictionary<object, Dictionary<TypeDesc, HashSet<int>>>(); } public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) { sb.Append(nameMangler.CompilationUnitPrefix).Append("__interface_gvm_table"u8); } public int Offset => 0; public override bool IsShareable => false; public override ObjectNodeSection GetSection(NodeFactory factory) => _externalReferences.GetSection(factory); public override bool StaticDependenciesAreComputed => true; protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler); /// <summary> /// Helper method to compute the dependencies that would be needed by a hashtable entry for an interface GVM call. /// This helper is used by the TypeGVMEntriesNode, which is used by the dependency analysis to compute the /// GVM hashtable entries for the compiled types. /// The dependencies returned from this function will be reported as static dependencies of the TypeGVMEntriesNode, /// which we create for each type that has generic virtual methods. /// </summary> public static void GetGenericVirtualMethodImplementationDependencies(ref DependencyList dependencies, NodeFactory factory, MethodDesc callingMethod, TypeDesc implementationType, MethodDesc implementationMethod) { Debug.Assert(callingMethod.OwningType.IsInterface); // Compute the open method signatures MethodDesc openCallingMethod = callingMethod.GetTypicalMethodDefinition(); TypeDesc openImplementationType = implementationType.GetTypeDefinition(); factory.MetadataManager.GetNativeLayoutMetadataDependencies(ref dependencies, factory, openCallingMethod); // Implementation could be null if this is a default interface method reabstraction or diamond. We need to record those. if (implementationMethod != null) { MethodDesc openImplementationMethod = implementationMethod.GetTypicalMethodDefinition(); dependencies.Add(new DependencyListEntry(factory.NecessaryTypeSymbol(openImplementationMethod.OwningType), "interface gvm table implementation method owning type")); factory.MetadataManager.GetNativeLayoutMetadataDependencies(ref dependencies, factory, openImplementationMethod); } if (!openImplementationType.IsInterface) { for (int index = 0; index < openImplementationType.RuntimeInterfaces.Length; index++) { if (openImplementationType.RuntimeInterfaces[index] == callingMethod.OwningType) { TypeDesc currentInterface = openImplementationType.RuntimeInterfaces[index]; var currentInterfaceSignature = factory.NativeLayout.TypeSignatureVertex(currentInterface); dependencies.Add(new DependencyListEntry(factory.NativeLayout.PlacedSignatureVertex(currentInterfaceSignature), "interface gvm table interface signature")); } } } } private void AddGenericVirtualMethodImplementation(MethodDesc callingMethod, TypeDesc implementationType, MethodDesc implementationMethod, DefaultInterfaceMethodResolution resolution) { Debug.Assert(callingMethod.OwningType.IsInterface); // Compute the open method signatures MethodDesc openCallingMethod = callingMethod.GetTypicalMethodDefinition(); object openImplementationMethod = implementationMethod == null ? resolution : implementationMethod.GetTypicalMethodDefinition(); TypeDesc openImplementationType = implementationType.GetTypeDefinition(); // Add the entry to the interface GVM slots mapping table if (!_interfaceGvmSlots.ContainsKey(openCallingMethod)) _interfaceGvmSlots[openCallingMethod] = new HashSet<object>(); _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<TypeDesc, HashSet<int>>(); if (!_interfaceImpls[openImplementationMethod].ContainsKey(openImplementationType)) _interfaceImpls[openImplementationMethod][openImplementationType] = new HashSet<int>(); int numIfacesAdded = 0; for (int index = 0; index < openImplementationType.RuntimeInterfaces.Length; index++) { if (openImplementationType.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<byte>(), Array.Empty<Relocation>(), 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 interestingEntry.ScanForInterfaceGenericVirtualMethodEntries()) { AddGenericVirtualMethodImplementation(typeGVMEntryInfo.CallingMethod, typeGVMEntryInfo.ImplementationType, typeGVMEntryInfo.ImplementationMethod, typeGVMEntryInfo.DefaultResolution); } } // 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)); int callingMethodToken = factory.MetadataManager.GetMetadataHandleForMethod(factory, callingMethod); Vertex vertex = nativeFormatWriter.GetTuple( nativeFormatWriter.GetUnsignedConstant(typeId), nativeFormatWriter.GetUnsignedConstant((uint)callingMethodToken)); // 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 (object impl in gvmEntry.Value) { if (impl is MethodDesc implementationMethod) { int implementationMethodToken = factory.MetadataManager.GetMetadataHandleForMethod(factory, implementationMethod); typeId = _externalReferences.GetIndex(factory.NecessaryTypeSymbol(implementationMethod.OwningType)); vertex = nativeFormatWriter.GetTuple( vertex, nativeFormatWriter.GetUnsignedConstant((uint)implementationMethodToken), nativeFormatWriter.GetUnsignedConstant(typeId)); } else { Debug.Assert(impl is DefaultInterfaceMethodResolution); uint constant = (DefaultInterfaceMethodResolution)impl switch { DefaultInterfaceMethodResolution.Diamond => SpecialGVMInterfaceEntry.Diamond, DefaultInterfaceMethodResolution.Reabstraction => SpecialGVMInterfaceEntry.Reabstraction, _ => throw new NotImplementedException(), }; vertex = nativeFormatWriter.GetTuple( vertex, nativeFormatWriter.GetUnsignedConstant(constant)); } // Emit the interface GVM slot details for each type that implements the interface methods { Debug.Assert(_interfaceImpls.ContainsKey(impl)); var ifaceImpls = _interfaceImpls[impl]; // 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; byte[] streamBytes = nativeFormatWriter.Save(); return new ObjectData(streamBytes, Array.Empty<Relocation>(), 1, new ISymbolDefinitionNode[] { this }); } protected internal override int Phase => (int)ObjectNodePhase.Ordered; public override int ClassCode => (int)ObjectNodeOrder.InterfaceGenericVirtualMethodTableNode; } } |