-
Notifications
You must be signed in to change notification settings - Fork 509
Conversation
get | ||
{ | ||
if(_type.IsInterface || _type.IsValueType || _type.Category == TypeFlags.Class) | ||
return _type.HasGenericVirtualMethod(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
@@ -144,6 +144,17 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact | |||
return null; | |||
} | |||
|
|||
public override bool InterestingForDynamicDependencyAnalysis |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should also filter out static methods on interfaces. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. I'll do it as a cleanup thing before merging the PR #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.