This repository has been archived by the owner on Nov 1, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 509
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2521 from fadimounir/gvm_tables
Generic Virtual Methods Hashtables
- Loading branch information
Showing
17 changed files
with
863 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
182 changes: 182 additions & 0 deletions
182
src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/GVMDependenciesNode.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
// 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 | ||
{ | ||
/// <summary> | ||
/// 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. | ||
/// </summary> | ||
internal class GVMDependenciesNode : DependencyNodeCore<NodeFactory> | ||
{ | ||
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<DependencyListEntry> GetStaticDependencies(NodeFactory context) | ||
{ | ||
return Array.Empty<DependencyListEntry>(); | ||
} | ||
public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory context) | ||
{ | ||
return Array.Empty<CombinedDependencyListEntry>(); | ||
} | ||
|
||
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<CombinedDependencyListEntry> SearchDynamicDependencies(List<DependencyNodeCore<NodeFactory>> markedNodes, int firstNode, NodeFactory factory) | ||
{ | ||
Debug.Assert(_method.IsVirtual && _method.HasInstantiation); | ||
|
||
List<CombinedDependencyListEntry> dynamicDependencies = new List<CombinedDependencyListEntry>(); | ||
|
||
for (int i = firstNode; i < markedNodes.Count; i++) | ||
{ | ||
DependencyNodeCore<NodeFactory> 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 = potentialOverrideType.ResolveInterfaceMethodTarget(genericDefinition); | ||
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<CombinedDependencyListEntry> dynamicDependencies, NodeFactory factory) | ||
{ | ||
Debug.Assert(methodDef != null); | ||
Debug.Assert(!methodDef.Signature.IsStatic); | ||
|
||
if (methodDef.IsAbstract) | ||
return; | ||
|
||
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<void>? | ||
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) | ||
{ | ||
MethodDesc canonMethodTarget = derivedMethodInstantiation.GetCanonMethodTarget(CanonicalFormKind.Specific); | ||
|
||
bool getUnboxingStub = (derivedMethodInstantiation.OwningType.IsValueType || derivedMethodInstantiation.OwningType.IsEnum); | ||
dynamicDependencies.Add(new CombinedDependencyListEntry(factory.MethodEntrypoint(canonMethodTarget, getUnboxingStub), null, "DerivedMethodInstantiation")); | ||
|
||
if (canonMethodTarget != derivedMethodInstantiation) | ||
{ | ||
// Dependency includes the generic method dictionary of the instantiation | ||
// TODO: detect large recursive generics and fallback to USG templates | ||
Debug.Assert(!derivedMethodInstantiation.IsCanonicalMethod(CanonicalFormKind.Any)); | ||
dynamicDependencies.Add(new CombinedDependencyListEntry(factory.MethodGenericDictionary(derivedMethodInstantiation), null, "DerivedMethodInstantiation dictionary")); | ||
} | ||
} | ||
else | ||
{ | ||
// TODO: universal generics | ||
} | ||
} | ||
} | ||
} |
151 changes: 151 additions & 0 deletions
151
src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/GenericVirtualMethodTableNode.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
// 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 | ||
{ | ||
/// <summary> | ||
/// Represents a map between reflection metadata and generated method bodies. | ||
/// </summary> | ||
internal sealed class GenericVirtualMethodTableNode : ObjectNode, ISymbolNode | ||
{ | ||
private ObjectAndOffsetSymbolNode _endSymbol; | ||
private ExternalReferencesTableNode _externalReferences; | ||
private Dictionary<MethodDesc, Dictionary<TypeDesc, MethodDesc>> _gvmImplemenations; | ||
|
||
public GenericVirtualMethodTableNode(ExternalReferencesTableNode externalReferences) | ||
{ | ||
_endSymbol = new ObjectAndOffsetSymbolNode(this, 0, "__gvm_table_End", true); | ||
_externalReferences = externalReferences; | ||
_gvmImplemenations = new Dictionary<MethodDesc, Dictionary<TypeDesc, MethodDesc>>(); | ||
} | ||
|
||
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(); | ||
|
||
/// <summary> | ||
/// Helper method to compute the dependencies that would be needed by a hashtable entry for a 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 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 calling method signature")); | ||
dependencyNodes.Add(new DependencyListEntry(factory.NativeLayout.PlacedSignatureVertex(openImplementationMethodNameAndSig), "gvm table implementation method 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<TypeDesc, MethodDesc>(); | ||
|
||
_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<byte>(), Array.Empty<Relocation>(), 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<Relocation>(), 1, new ISymbolNode[] { this, _endSymbol }); | ||
} | ||
} | ||
} |
Oops, something went wrong.