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

CppCodeGen: implement shared generics and support generic virtual methods #6467

Merged
merged 2 commits into from
Oct 29, 2018

Conversation

kbaladurin
Copy link
Member

This patch fixes #6147

@kbaladurin
Copy link
Member Author

@kbaladurin
Copy link
Member Author

@jkotas @MichalStrehovsky PTAL

@@ -13,7 +13,7 @@ namespace ILCompiler.DependencyAnalysis
/// <summary>
/// Represents a hashtable of all type blocked from reflection.
/// </summary>
internal sealed class BlockReflectionTypeMapNode : ObjectNode, ISymbolDefinitionNode
public sealed class BlockReflectionTypeMapNode : ObjectNode, ISymbolDefinitionNode
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do all these things types need to be public now?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is forced by CppCodegen living in a separate assembly and having to special cases these node types in the CppWriter. I never really liked that that's where we ended up with for CppWriter (the other object writers only deal with ObjectNode and don't have to be aware of the specific type).

It might be nice at some point to see if we can make CppWriter more general purpose so that it doesn't have to be aware of specific node types. But that's a future nice to have.

@@ -161,8 +151,13 @@ unsafe public IntPtr GetFieldAddressFromIndex(uint index)
throw new BadImageFormatException();

// TODO: indirection through IAT
int* pRelPtr32 = &((int*)_elements)[index];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be nice to submit the supporting self-contained fixes like this one as separate PRs.`

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it's better to submit changes in ExternalReferencesTable and ExternalReferencesTableNode as separate PR?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up to you ... splitting large change into multiple smaller PRs that make sense on its own makes it easier to push the changes through.

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'll look at the changes in CppWriter and importer later, but this looks awesome.

Would it be possible to start running the tests/src/Simple/Generics test now (by deleting the no_cpp file in the directory)? (Even if we have to put some parts under #if !CODEGEN_CPP).

@@ -26,6 +26,7 @@ internal class TypeMetadataNode : DependencyNodeCore<NodeFactory>

public TypeMetadataNode(MetadataType type)
{
EcmaType t = (EcmaType)type;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks unnecessary

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, thank you!

@@ -13,7 +13,7 @@ namespace ILCompiler.DependencyAnalysis
/// <summary>
/// Represents a hashtable of all type blocked from reflection.
/// </summary>
internal sealed class BlockReflectionTypeMapNode : ObjectNode, ISymbolDefinitionNode
public sealed class BlockReflectionTypeMapNode : ObjectNode, ISymbolDefinitionNode
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is forced by CppCodegen living in a separate assembly and having to special cases these node types in the CppWriter. I never really liked that that's where we ended up with for CppWriter (the other object writers only deal with ObjectNode and don't have to be aware of the specific type).

It might be nice at some point to see if we can make CppWriter more general purpose so that it doesn't have to be aware of specific node types. But that's a future nice to have.

@@ -102,6 +102,10 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
// TODO: set low bit if the linkage of the symbol is IAT_PVALUE.
builder.EmitReloc(symbolAndDelta.Symbol, RelocType.IMAGE_REL_BASED_RELPTR32, symbolAndDelta.Delta);
}
else if (factory.Target.Abi == TargetAbi.CppCodegen)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MichalStrehovsky , should this be based off of TargetDetails rather than Cpp?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good point. We should shuffle this a little and put the TargetAbi.ProjectN case first (that one will stay weird as long as we have the Binder), and then just use factory.Target.SupportsRelativePointers to decide whether the reloc is a full pointer, or just a relative address. The pointer reloc case is also relevant for webassembly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Thank you for suggestion!

{
public CanonicalEETypeNode(NodeFactory factory, TypeDesc type) : base(factory, type)
{
Debug.Assert(!type.IsCanonicalDefinitionType(CanonicalFormKind.Any));
Debug.Assert(type.IsCanonicalSubtype(CanonicalFormKind.Any));
Debug.Assert(type == type.ConvertToCanonForm(CanonicalFormKind.Specific));
Debug.Assert(!type.IsMdArray);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not be ending up with canonical EETypes for MdArrays - the template type loader doesn't need templates for these because there's nothing generic about them (they don't implement the generic interfaces that normal arrays do, such as IEnumerable<T>, etc.).

What stack are you hitting this assert from?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm hitting following stacktrace:

Compiling [Hello]Hello.Program+B.M<__Canon>(__Canon)
AddTypeReference owningType: [S.P.CoreLib]System.__Canon[,]
Assertion Failed

   at ILCompiler.DependencyAnalysis.CanonicalEETypeNode..ctor(NodeFactory factory, TypeDesc type) in /media/kbaladurin/data/dotnet/forked/corert/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/CanonicalEETypeNode.cs:line 29
   at ILCompiler.DependencyAnalysis.NodeFactory.<CreateNodeCaches>b__36_1(TypeDesc type) in /media/kbaladurin/data/dotnet/forked/corert/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs:line 194
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at ILCompiler.DependencyAnalysis.NodeFactory.NodeCache`2.GetOrAdd(TKey key) in /media/kbaladurin/data/dotnet/forked/corert/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs:line 150
   at ILCompiler.DependencyAnalysis.NodeFactory.ConstructedTypeSymbol(TypeDesc type) in /media/kbaladurin/data/dotnet/forked/corert/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs:line 527
   at Internal.IL.ILImporter.AddTypeDependency(TypeDesc type, Boolean constructed) in /media/kbaladurin/data/dotnet/forked/corert/src/ILCompiler.CppCodeGen/src/CppCodeGen/ILToCppImporter.cs:line 3233
   at Internal.IL.ILImporter.AddTypeReference(TypeDesc type, Boolean constructed) in /media/kbaladurin/data/dotnet/forked/corert/src/ILCompiler.CppCodeGen/src/CppCodeGen/ILToCppImporter.cs:line 3211
   at Internal.IL.ILImporter.ImportNewObjArray(TypeDesc owningType, MethodDesc method) in /media/kbaladurin/data/dotnet/forked/corert/src/ILCompiler.CppCodeGen/src/CppCodeGen/ILToCppImporter.cs:line 1174
   at Internal.IL.ILImporter.ImportCall(ILOpcode opcode, Int32 token) in /media/kbaladurin/data/dotnet/forked/corert/src/ILCompiler.CppCodeGen/src/CppCodeGen/ILToCppImporter.cs:line 1290
   at Internal.IL.ILImporter.ImportBasicBlock(BasicBlock basicBlock) in /media/kbaladurin/data/dotnet/forked/corert/src/Common/src/TypeSystem/IL/ILImporter.cs:line 616
   at Internal.IL.ILImporter.ImportBasicBlocks() in /media/kbaladurin/data/dotnet/forked/corert/src/Common/src/TypeSystem/IL/ILImporter.cs:line 327
   at Internal.IL.ILImporter.Compile(CppMethodCodeNode methodCodeNodeNeedingCode) in /media/kbaladurin/data/dotnet/forked/corert/src/ILCompiler.CppCodeGen/src/CppCodeGen/ILToCppImporter.cs:line 758
   at ILCompiler.CppCodeGen.CppWriter.CompileMethod(CppMethodCodeNode methodCodeNodeNeedingCode) in /media/kbaladurin/data/dotnet/forked/corert/src/ILCompiler.CppCodeGen/src/CppCodeGen/CppWriter.cs:line 553
   at ILCompiler.CppCodegenCompilation.ComputeDependencyNodeDependencies(List`1 obj) in /media/kbaladurin/data/dotnet/forked/corert/src/ILCompiler.CppCodeGen/src/Compiler/CppCodegenCompilation.cs:line 71
   at ILCompiler.DependencyAnalysisFramework.DependencyAnalyzer`2.ComputeDependencies(List`1 deferredStaticDependencies) in /media/kbaladurin/data/dotnet/forked/corert/src/ILCompiler.DependencyAnalysisFramework/src/DependencyAnalyzer.cs:line 139
   at ILCompiler.DependencyAnalysisFramework.DependencyAnalyzer`2.ComputeMarkedNodes() in /media/kbaladurin/data/dotnet/forked/corert/src/ILCompiler.DependencyAnalysisFramework/src/DependencyAnalyzer.cs:line 262
   at ILCompiler.CppCodegenCompilation.CompileInternal(String outputFile, ObjectDumper dumper) in /media/kbaladurin/data/dotnet/forked/corert/src/ILCompiler.CppCodeGen/src/Compiler/CppCodegenCompilation.cs:line 47
   at ILCompiler.Compilation.ILCompiler.ICompilation.Compile(String outputFile, ObjectDumper dumper) in /media/kbaladurin/data/dotnet/forked/corert/src/ILCompiler.Compiler/src/Compiler/Compilation.cs:line 333
   at ILCompiler.Program.Run(String[] args) in /media/kbaladurin/data/dotnet/forked/corert/src/ILCompiler/src/Program.cs:line 514
   at ILCompiler.Program.Main(String[] args) in /media/kbaladurin/data/dotnet/forked/corert/src/ILCompiler/src/Program.cs:line 670

when compiling this method:

public override void M<T>(T t)
{
    T[,] a = new T[2, 2];
    System.Console.WriteLine("Virtual B " + t + " " + a);
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's change the assert to !type.IsMdArray || factory.Target.Abi == TargetAbi.CppCodegen. Generating these for MdArrays still results in useless data structures, but due to how CppCodegen needs to operate, we'll have to live with it there.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Thank you!

}
else
{
Debug.Assert(_method.RequiresInstArg() || _method.AcquiresInstMethodTableFromThis());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RequiresInstArg [](start = 37, length = 15)

RequiresInstMethodTableArg matches more closely what we need to assert than RequiresInstArg

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@kbaladurin
Copy link
Member Author

I've checked Generics tests. All tests are passed except following:

  • TestReflectionInvoke (fails due to missing reflection support)
  • TestFieldAccess (fails due to missing reflection support)
  • TestMDArrayAddressMethod (fails due to missing exception support)
  • TestNativeLayoutGeneration (fails due to missing null check before call)

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.

Looks good to me otherwise.

{
public bool IsVirtual { get; }

public LdFtnTokenEntry(StackValueKind kind, string name, MethodDesc token, bool isVirtual, TypeDesc type = null) : base(kind, name, token, type)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we override the Duplicate method?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thank you for the note!

// TODO: indirection through IAT
int* pRelPtr32 = &((int*)_elements)[index];
return (IntPtr)((byte*)pRelPtr32 + *pRelPtr32);
return GetFieldAddressFromIndex(index);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should rename to GetAddressFromIndex since this is now more general-purpose.

{
public CanonicalEETypeNode(NodeFactory factory, TypeDesc type) : base(factory, type)
{
Debug.Assert(!type.IsCanonicalDefinitionType(CanonicalFormKind.Any));
Debug.Assert(type.IsCanonicalSubtype(CanonicalFormKind.Any));
Debug.Assert(type == type.ConvertToCanonForm(CanonicalFormKind.Specific));
Debug.Assert(!type.IsMdArray);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's change the assert to !type.IsMdArray || factory.Target.Abi == TargetAbi.CppCodegen. Generating these for MdArrays still results in useless data structures, but due to how CppCodegen needs to operate, we'll have to live with it there.


Append("if (");
Append(fatPtr);
Append(" & 0x2) {");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you change this and below to use FatFunctionPointerConstants.Offset instead? You might need to add RuntimeConstants.cs to the project, but that should be fine.

(Also below.)

Out.Write(sb.ToString());
}

public TypeDesc ConvertToCanonFormIfNecessary(TypeDesc type, CanonicalFormKind policy)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can just be:

public static TypeDesc CanonicallyNormalized(this TypeDesc type)
{
    if (type.IsCanonicalSubtype(CanonicalFormKind.Any))
        return type.ConvertToCanonForm(CanonicalFormKind.Specific);
    return type;
}

The pointers and byrefs shouldn't require special casing, and we don't need to specify canonical form as a parameter (if the type happens to have CanonicalFormKind.Universal types in it, the ConvertToCanonForm will to the necessary upgrade).

This method could also be static/extension method, and we could place it in TypeExtensions.cs. I know at least one more place that could use it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used special casing for pointers and byrefs to do conversions like this: Dictionary<String, Canon>& -> Dictionary<Canon, Canon>&. ConvertToCanonForm converts such types to Canon& that isn't convenient for cpp codegen.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. CppCodegen requires different canonical forms than what I'm used to. This looks good.

@MichalStrehovsky
Copy link
Member

I've checked Generics tests. All tests are passed except following

That's great to hear! Could you please put the things that don't work under #if !CODEGEN_CPP (same as we do in the PInvoke test) and delete the no_cpp file in the test directory so that we run it regularly in the CI?

Could you also move the definition of CODEGEN_CPP to SimpleTest.targets while you're at it so that we don't duplicate it?

@kbaladurin kbaladurin changed the title [WIP] CppCodeGen: implement shared generics and support generic virtual methods CppCodeGen: implement shared generics and support generic virtual methods Oct 26, 2018
@kbaladurin
Copy link
Member Author

Thank you for the review! I've added patch that enables generics test for CppCodeGen.

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.

Thank you!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[CppCodeGen] Assert during generic virtual method call compilation
4 participants