Generic Virtual Methods Hashtables#2521
Conversation
| get | ||
| { | ||
| if(_type.IsInterface || _type.IsValueType || _type.Category == TypeFlags.Class) | ||
| return _type.HasGenericVirtualMethod(); |
There was a problem hiding this comment.
Is this check really necessary? I guessed that we probably use the EETypeNode for array EETypes too, that's why I added the check for interface/class/valuetype #Closed
There was a problem hiding this comment.
The if check looks like a perf optimization and not much else. I don't mind either way, but consider making the check just _type.IsDefType if you want to keep it. I don't think there's value in doing 3 checks just to avoid nullables and enums. #Resolved
| /// This method node is used for dependency tracking, like in the case of GVMs for example. | ||
| /// </summary> | ||
| internal class MethodLdtokenNode : DependencyNodeCore<NodeFactory> | ||
| { |
There was a problem hiding this comment.
The naming "MethodLdTokenNode" is just to match NUTC's nodes in PN. I don't know if this node will be used for other non-GVM dependency tracking in the future, but if not, i'm leaning towards renaming this to something a bit different to reflect what this node does. Opinions?
There was a problem hiding this comment.
My vote is to rename it. Even if we add more stuff to it, this will probably not have much in common with LDTOKEN.
|
Constraint validation should at minimum be a separate commit, but I would prefer a separate pull request. I can see a number of issues with it and I would prefer we don't lose focus due to the GVM changes:
Was this code ported from NUTC/reflection (so that we can compare it against that), or written from scratch? #Resolved |
|
I'll separate the constraints validation to a separate PR. You can ignore it from here. In reply to: 273295123 [](ancestors = 273295123) |
47c4488 to
90cbcc4
Compare
MichalStrehovsky
left a comment
There was a problem hiding this comment.
I didn't review the actual dependency tracking part because it's confusing and I'll need a few rounds of meditation before I have anything constructive.
| /// <summary> | ||
| /// Gets a value indicating whether this type has any generic virtual methods. | ||
| /// </summary> | ||
| public static bool HasGenericVirtualMethod(this TypeDesc type) |
There was a problem hiding this comment.
This file has extension methods that the type system itself uses.
Since the type system doesn't use this one, it would be more fitting to put it in src\ILCompiler.Compiler\src\Compiler\TypeExtensions.cs (that has extension methods related to codegen and such). #Resolved
| /// </summary> | ||
| public static bool HasGenericVirtualMethod(this TypeDesc type) | ||
| { | ||
| foreach (var method in type.GetMethods()) |
There was a problem hiding this comment.
This should be GetAllMethods().
(If we ever have typesystem context-injected generic virtual methods, this method should still report true.) #Resolved
| get | ||
| { | ||
| if(_type.IsInterface || _type.IsValueType || _type.Category == TypeFlags.Class) | ||
| return _type.HasGenericVirtualMethod(); |
There was a problem hiding this comment.
The if check looks like a perf optimization and not much else. I don't mind either way, but consider making the check just _type.IsDefType if you want to keep it. I don't think there's value in doing 3 checks just to avoid nullables and enums. #Resolved
| return null; | ||
| } | ||
|
|
||
| public override bool InterestingForDynamicDependencyAnalysis |
There was a problem hiding this comment.
Should this be on the ConstructedEETypeNode? If a type wasn't allocated with newobj, we don't care about any of it's virtual methods because they can't be called. #Resolved
There was a problem hiding this comment.
Based on your offline explanation to me of what a ConstructedEETypeNode is used for, yes this should go there :)
In reply to: 96538247 [](ancestors = 96538247)
| // Generated type contains generic virtual methods that will get added to the GVM tables. | ||
| // The entries in the GVM tables are expressed in open generic definitions, so any generic | ||
| // definition type that has generic virtual methods will need some dependency tracking. | ||
| if (TypeGVMEntriesNode.TypeNeedsGVMTableEntries(_type)) |
There was a problem hiding this comment.
There's no guarantee that a GenericDefinitionEETypeNode will be generated in the current compilation:
Consider multifile mode where assembly A contains the type definition for Foo<T> (but doesn't use it in any way). The EEType node that represents the generic definition is generated into A.obj though, because that's the home module of the definition. Now assembly B allocates Foo<object> and calls a GVM method on it. EETypeNode for Foo<object> is generated in B.obj, but not the generic definition. The generic definition will be just an external symbol reference.
If analysis assumes we will be generating the generic definition EEType, these dependencies have to come in different way. #Resolved
There was a problem hiding this comment.
I see. I'll remove this from here. This will definitely need testing when multimodule mode is enabled
In reply to: 96538983 [](ancestors = 96538983)
| /// This method node is used for dependency tracking, like in the case of GVMs for example. | ||
| /// </summary> | ||
| internal class MethodLdtokenNode : DependencyNodeCore<NodeFactory> | ||
| { |
There was a problem hiding this comment.
My vote is to rename it. Even if we add more stuff to it, this will probably not have much in common with LDTOKEN.
| } | ||
|
|
||
| // Open generic types are not interesting. | ||
| if (potentialOverrideType.ContainsGenericVariables) |
There was a problem hiding this comment.
This should be IsGenericDefinition. Checking ContainsGenericVariables requires a walk of the composition to compute. I'm always suspicious when I see ContainsGenericVariables getting used. In fact, the existing code doesn't use it for anything, ever.
We never have EEType nodes for things where IsGenericDefinition and ContainsGenericVariables would give different answers. #Resolved
There was a problem hiding this comment.
There is no need to check for this... There should never be any open generics at this level
| public override bool HasDynamicDependencies => false; | ||
| public override bool InterestingForDynamicDependencyAnalysis => false; | ||
| public override bool StaticDependenciesAreComputed => true; | ||
| protected override string GetName() => null; |
There was a problem hiding this comment.
This needs a name for the log. #Resolved
|
|
||
| public static bool TypeNeedsGVMTableEntries(TypeDesc type) | ||
| { | ||
| if (type.Category != TypeFlags.Class && !type.IsValueType) |
There was a problem hiding this comment.
This is rather hard to read. What is the intent? Is it:
if (!type.IsDefType || type.IsInterface)
return false;Or something else? #Resolved
There was a problem hiding this comment.
Using .IsDefType is more readable I agree :). I just didn't know about it before
In reply to: 96550314 [](ancestors = 96550314)
| if (_method.IsCanonicalMethod(CanonicalFormKind.Specific)) | ||
| return false; | ||
|
|
||
| if (_method.OwningType.ContainsGenericVariables || _method.ContainsGenericVariables) |
There was a problem hiding this comment.
Can we make this a check for generic definition instead? Or does the code in this pull request actually generate things such as a Foo instantiated over IFoo's T and the like that would warrant this expensive check?
| { | ||
| foreach (var method in iface.GetMethods()) | ||
| { | ||
| if (!method.HasInstantiation) |
There was a problem hiding this comment.
This should also filter out static methods on interfaces. #Resolved
There was a problem hiding this comment.
Good point! I keep forgetting about static methods on interfaces
In reply to: 96720291 [](ancestors = 96720291)
|
|
||
| // If the target method is an exact (non-canonical) instantiation, we'll need an entry for it in the exact method instantiations | ||
| // hashtable, so we append the entry's dependencies to the method here. | ||
| var exactMethodInstantiationDependencies = ExactMethodInstantiationsNode.GetExactMethodInstantiationDependenciesForMethod(factory, method); |
There was a problem hiding this comment.
How does an entry for this end up in the ExactMethodInstantiations hashtable though? It seems like we generate dependencies for a thing that never actually lands in the hashtable. #Resolved
There was a problem hiding this comment.
It should have been the other way around (this was some very old code I added to my branch). We should track the method entry point here, not the hashtable dependencies.
Hashtable entries will be created from the MethodCodeNodes that we would create from here
In reply to: 96722057 [](ancestors = 96722057)
| return type.HasGenericVirtualMethod(); | ||
| } | ||
|
|
||
| public void ScanForGenericVirtualMethodEntries(NodeFactory factory, Action<NodeFactory, MethodDesc, MethodDesc> gvmEntryFoundCallback) |
There was a problem hiding this comment.
Did you consider making this method return an IEnumerable (of a custom helper struct that has two fields for the declaring and implementing method in it)?
The callback delegate makes the code rather hard to follow because it requires the reader to inspect bodies of 3 methods to find out what's going on. #Resolved
There was a problem hiding this comment.
I hadn't considered making it return an IEnumerable. I like that better than the callback :)
In reply to: 96753917 [](ancestors = 96753917)
4eee69e to
362bf7c
Compare
|
cc @yizhang82 |
| { | ||
| get | ||
| { | ||
| return _type.IsDefType && _type.HasGenericVirtualMethod(); |
There was a problem hiding this comment.
Do we need the check for if this type is a RuntimeDeterminedSubtype here? I'm not familiar if RuntimeDetermined things can appear in ConstructedEETypeNodes #Closed
There was a problem hiding this comment.
ConstructedEETypeNode is an ObjectNode and represents an EEType. It needs to be concrete, and we assert that. EEType for a non-exact type wouldn't make sense. #Closed
There was a problem hiding this comment.
The base type, EETypeNode, explicitly has Debug.Assert(!type.IsRuntimeDeterminedSubtype); #Closed
| derivedMethodInstantiation = derivedMethodInstantiation.GetCanonMethodTarget(CanonicalFormKind.Universal); | ||
| } | ||
|
|
||
| // TODO: verify for invalid instantiations, like List<void>? |
There was a problem hiding this comment.
// TODO: verify for invalid instantiations, like List? [](start = 12, length = 60)
Is this TODO not handled by the constraints check below? #WontFix
There was a problem hiding this comment.
No... Michal was suggesting we have the constraints validation just do constraints validations, and not check for invalid instantiations. If we need invalid instantiations validation, let it be separate from constraints validation. #WontFix
| slotDecl = currentType.ResolveInterfaceMethodToVirtualMethodOnType(genericDefinition); | ||
| currentType = currentType.BaseType; | ||
| } | ||
| while (slotDecl == null && currentType != null); |
There was a problem hiding this comment.
This is a very odd algorithm. As such we should make it testable. Could you put it into the typesystem dll as an extension method or something, and then write some tests? #Resolved
There was a problem hiding this comment.
Ok. I'll do it as a cleanup thing before merging the PR #Resolved
There was a problem hiding this comment.
I'd like to see the tests before you checkin. Please ping me again when you're done writing up tests.
In reply to: 97399311 [](ancestors = 97399311)
| } | ||
| while (slotDecl == null && currentType != null); | ||
|
|
||
| Debug.Assert(slotDecl != null); |
There was a problem hiding this comment.
I will remove this assert. I think it is wrong (ex: abstract type that doesn't implement all interface methods of all implemented interfaces). I'll also add a test for it in my GVM unit test (which i'll send later in another PR) #Resolved
| protected override string GetName() => this.GetMangledName(); | ||
|
|
||
| public static DependencyList GetGenericVirtualMethodImplementationDependencies(NodeFactory factory, MethodDesc callingMethod, MethodDesc implementationMethod) | ||
| { |
There was a problem hiding this comment.
Please comment as to when and why this method is to be used. #Resolved
| 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")); |
There was a problem hiding this comment.
gvm table needed signature [](start = 137, length = 26)
Please use different strings for dependency node additions wherever possible. That assists with debugging this later. #Resolved
| var openCallingMethodNameAndSig = factory.NativeLayout.MethodNameAndSignatureVertex(openCallingMethod); | ||
| var openImplementationMethodNameAndSig = factory.NativeLayout.MethodNameAndSignatureVertex(openImplementationMethod); | ||
|
|
||
| dependencyNodes.Add(new DependencyListEntry(factory.NativeLayout.PlacedSignatureVertex(openCallingMethodNameAndSig), "gvm table needed signature")); |
There was a problem hiding this comment.
gvm table needed signature [](start = 130, length = 26)
Please make all of these strings unique #Resolved
|
|
1) GenericVirtualMethodTableNode: Node that builds the GVM hashtable blob for GVMs on classes
2) InterfaceGenericVirtualMethodTableNode: Node that builds the GVM hashtable blob for GVMs on interfaces
3) MethodLdTokenNode: Analysis node that tracks dependencies of GVMs for derived types and variant interfaces
4) TypeGVMEntriesNode: Analysis node that scans an input type and computes the list of GVM entries that would be added to the hashtables. Also used to track dependencies of GVM table entries (ex: native layout signatures of methods).
Other
1) New helper method to compute whether a type has generic virtual methods
11d175d to
3d79516
Compare
GVM dependency tracking analysis and GVM table building logic:
1) GenericVirtualMethodTableNode: Node that builds the GVM hashtable blob for GVMs on classes
2) InterfaceGenericVirtualMethodTableNode: Node that builds the GVM hashtable blob for GVMs on interfaces
3) MethodLdTokenNode: Analysis node that tracks dependencies of GVMs for derived types and variant interfaces
4) TypeGVMEntriesNode: Analysis node that scans an input type and computes the list of GVM entries that would be added to the hashtables. Also used to track dependencies of GVM table entries (ex: native layout signatures of methods).
Couple of additions to the typesystem:
1) New helper method to compute whether a type has generic virtual methods
2) Concrete constraints validation for types and methods.