-
Notifications
You must be signed in to change notification settings - Fork 508
CppCodeGen: implement shared generics and support generic virtual methods #6467
Conversation
@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 |
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.
Why do all these things types need to be public now?
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 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]; |
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 may be nice to submit the supporting self-contained fixes like this one as separate PRs.`
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 you think it's better to submit changes in ExternalReferencesTable
and ExternalReferencesTableNode
as separate PR?
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.
Up to you ... splitting large change into multiple smaller PRs that make sense on its own makes it easier to push the changes through.
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'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; |
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 looks unnecessary
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.
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 |
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 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) |
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.
@MichalStrehovsky , should this be based off of TargetDetails rather than Cpp?
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.
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.
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.
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); |
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.
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?
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'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);
}
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.
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.
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.
Done. Thank you!
} | ||
else | ||
{ | ||
Debug.Assert(_method.RequiresInstArg() || _method.AcquiresInstMethodTableFromThis()); |
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.
RequiresInstArg [](start = 37, length = 15)
RequiresInstMethodTableArg
matches more closely what we need to assert than RequiresInstArg
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.
Thank you!
2e149a4
to
24442be
Compare
I've checked Generics tests. All tests are passed except following:
|
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.
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) |
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 we override the Duplicate
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.
Yes, thank you for the note!
// TODO: indirection through IAT | ||
int* pRelPtr32 = &((int*)_elements)[index]; | ||
return (IntPtr)((byte*)pRelPtr32 + *pRelPtr32); | ||
return GetFieldAddressFromIndex(index); |
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.
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); |
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.
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) {"); |
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 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) |
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 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.
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 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.
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. CppCodegen requires different canonical forms than what I'm used to. This looks good.
That's great to hear! Could you please put the things that don't work under Could you also move the definition of CODEGEN_CPP to SimpleTest.targets while you're at it so that we don't duplicate it? |
Thank you for the review! I've added patch that enables generics test for CppCodeGen. |
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.
Thank you!
This patch fixes #6147