Skip to content
This repository has been archived by the owner on Nov 1, 2020. It is now read-only.

Generic Virtual Methods Hashtables #2521

Merged
merged 1 commit into from
Jan 24, 2017
Merged

Conversation

fadimounir
Copy link

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.

get
{
if(_type.IsInterface || _type.IsValueType || _type.Category == TypeFlags.Class)
return _type.HasGenericVirtualMethod();
Copy link
Author

@fadimounir fadimounir Jan 17, 2017

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

Copy link
Member

@MichalStrehovsky MichalStrehovsky Jan 17, 2017

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>
{
Copy link
Author

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?

Copy link
Member

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.

@fadimounir
Copy link
Author

fadimounir commented Jan 17, 2017

@MichalStrehovsky @davidwrighton PTAL

@MichalStrehovsky
Copy link
Member

MichalStrehovsky commented Jan 17, 2017

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:

  1. ComputeTypeFlags on SignatureVariable doesn't look right. We need a better understanding about what's going on. ContainsGenericVariables is not used for anything right now and I've been meaning to delete it for a long time.
  2. Special constraints are flags: you can have ReferenceTypeConstraint and DefaultConstructorConstraint at the same time. The code as proposed checks nothing in that case.
  3. Nullable does not meet reference type constraints
  4. We shouldn't intertwine the concept of constraint validation and general validation. E.g. List<void> is an invalid instantiation - not a failure to meet constraints of generic parameter 0.
  5. Non-trivial typesystem code needs type system unit tests. At minimum, the constraint tests from the DynamicGenerics test should be ported to the type system unit test suite.
  6. General purpose typesystem code should not have references to Canonical or RuntimeDetermined stuff. I would want to delete the constraint checking in src\System.Private.Reflection.Execution\src\Internal\Reflection\Execution\TypeLoader\ConstraintValidator.cs when this is checked in (and validate in the type loader), but the runtime type system doesn't include any of the .RuntimeDetermined.cs partial class files (because we don't need them). Constraint validation shouldn't rely on anything that's not in the Common directory of the TypeSystem.

Was this code ported from NUTC/reflection (so that we can compare it against that), or written from scratch? #Resolved

@fadimounir
Copy link
Author

I'll separate the constraints validation to a separate PR. You can ignore it from here.
This code is ported from NUTC.


In reply to: 273295123 [](ancestors = 273295123)

Copy link
Member

@MichalStrehovsky MichalStrehovsky left a 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)
Copy link
Member

@MichalStrehovsky MichalStrehovsky Jan 17, 2017

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())
Copy link
Member

@MichalStrehovsky MichalStrehovsky Jan 17, 2017

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();
Copy link
Member

@MichalStrehovsky MichalStrehovsky Jan 17, 2017

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
Copy link
Member

@MichalStrehovsky MichalStrehovsky Jan 17, 2017

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

Copy link
Author

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))
Copy link
Member

@MichalStrehovsky MichalStrehovsky Jan 17, 2017

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

Copy link
Author

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>
{
Copy link
Member

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)
Copy link
Member

@MichalStrehovsky MichalStrehovsky Jan 18, 2017

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

Copy link
Author

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;
Copy link
Member

@MichalStrehovsky MichalStrehovsky Jan 18, 2017

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)
Copy link
Member

@MichalStrehovsky MichalStrehovsky Jan 18, 2017

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

Copy link
Author

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)
Copy link
Member

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)
Copy link
Member

@MichalStrehovsky MichalStrehovsky Jan 18, 2017

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

Copy link
Author

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);
Copy link
Member

@MichalStrehovsky MichalStrehovsky Jan 18, 2017

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

Copy link
Author

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)
Copy link
Member

@MichalStrehovsky MichalStrehovsky Jan 18, 2017

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

Copy link
Author

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)

@fadimounir fadimounir force-pushed the gvm_tables branch 4 times, most recently from 4eee69e to 362bf7c Compare January 19, 2017 01:09
@fadimounir
Copy link
Author

cc @yizhang82

{
get
{
return _type.IsDefType && _type.HasGenericVirtualMethod();
Copy link
Member

@davidwrighton davidwrighton Jan 23, 2017

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

Copy link
Member

@MichalStrehovsky MichalStrehovsky Jan 23, 2017

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

Copy link
Author

@fadimounir fadimounir Jan 23, 2017

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>?
Copy link
Member

@davidwrighton davidwrighton Jan 23, 2017

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

Copy link
Author

@fadimounir fadimounir Jan 23, 2017

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);
Copy link
Member

@davidwrighton davidwrighton Jan 23, 2017

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

Copy link
Author

@fadimounir fadimounir Jan 23, 2017

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

Copy link
Member

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);
Copy link
Author

@fadimounir fadimounir Jan 23, 2017

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)
{
Copy link
Member

@davidwrighton davidwrighton Jan 23, 2017

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"));
Copy link
Member

@davidwrighton davidwrighton Jan 23, 2017

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"));
Copy link
Member

@davidwrighton davidwrighton Jan 23, 2017

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

@davidwrighton
Copy link
Member

:shipit:

    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
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants