-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Place frozen objects into dehydrated section #78688
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,34 +1,56 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Collections.Generic; | ||
using System; | ||
|
||
using Internal.Text; | ||
using Internal.TypeSystem; | ||
|
||
namespace ILCompiler.DependencyAnalysis | ||
{ | ||
public class ArrayOfFrozenObjectsNode<TEmbedded> : ArrayOfEmbeddedDataNode<TEmbedded> | ||
where TEmbedded : EmbeddedObjectNode | ||
public class ArrayOfFrozenObjectsNode : DehydratableObjectNode, ISymbolDefinitionNode | ||
{ | ||
public ArrayOfFrozenObjectsNode(string startSymbolMangledName, string endSymbolMangledName, IComparer<TEmbedded> nodeSorter) : base(startSymbolMangledName, endSymbolMangledName, nodeSorter) | ||
private readonly ObjectAndOffsetSymbolNode _endSymbol; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this get dehyrated as a pointer? Or written inline? If the former, is there a big benefit to the extra pointer? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This defines an additional symbol - it's up to whoever references it how they'll do it. ReadyToRun header currently references it as a full pointer. It doesn't have to. It's one of those things I'm planning to convert to 32bit relative relocs (although this one doesn't contribute much). |
||
public ISymbolNode EndSymbol => _endSymbol; | ||
|
||
public ArrayOfFrozenObjectsNode() | ||
{ | ||
_endSymbol = new ObjectAndOffsetSymbolNode(this, 0, "__FrozenSegmentEnd", true); | ||
} | ||
|
||
public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) | ||
=> sb.Append(nameMangler.CompilationUnitPrefix).Append("__FrozenSegmentStart"); | ||
|
||
public int Offset => 0; | ||
|
||
private static void AlignNextObject(ref ObjectDataBuilder builder, NodeFactory factory) | ||
{ | ||
builder.EmitZeros(AlignmentHelper.AlignUp(builder.CountBytes, factory.Target.PointerSize) - builder.CountBytes); | ||
} | ||
|
||
protected override void GetElementDataForNodes(ref ObjectDataBuilder builder, NodeFactory factory, bool relocsOnly) | ||
protected override ObjectData GetDehydratableData(NodeFactory factory, bool relocsOnly) | ||
{ | ||
foreach (EmbeddedObjectNode node in NodesList) | ||
// This is a summary node | ||
if (relocsOnly) | ||
return new ObjectData(Array.Empty<byte>(), Array.Empty<Relocation>(), 1, Array.Empty<ISymbolDefinitionNode>()); | ||
|
||
var builder = new ObjectDataBuilder(factory, relocsOnly); | ||
builder.AddSymbol(this); | ||
foreach (EmbeddedObjectNode node in factory.MetadataManager.GetFrozenObjects()) | ||
{ | ||
AlignNextObject(ref builder, factory); | ||
|
||
if (!relocsOnly) | ||
node.InitializeOffsetFromBeginningOfArray(builder.CountBytes); | ||
node.InitializeOffsetFromBeginningOfArray(builder.CountBytes); | ||
|
||
int initialOffset = builder.CountBytes; | ||
sbomer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
node.EncodeData(ref builder, factory, relocsOnly); | ||
int objectSize = builder.CountBytes - initialOffset; | ||
int minimumObjectSize = EETypeNode.GetMinimumObjectSize(factory.TypeSystemContext); | ||
if (objectSize < minimumObjectSize) | ||
{ | ||
builder.EmitZeros(minimumObjectSize - objectSize); | ||
} | ||
|
||
if (node is ISymbolDefinitionNode) | ||
{ | ||
builder.AddSymbol((ISymbolDefinitionNode)node); | ||
|
@@ -38,8 +60,20 @@ protected override void GetElementDataForNodes(ref ObjectDataBuilder builder, No | |
// Terminate with a null pointer as expected by the GC | ||
AlignNextObject(ref builder, factory); | ||
builder.EmitZeroPointer(); | ||
|
||
_endSymbol.SetSymbolOffset(builder.CountBytes); | ||
builder.AddSymbol(_endSymbol); | ||
|
||
return builder.ToObjectData(); | ||
} | ||
|
||
protected override ObjectNodeSection GetDehydratedSection(NodeFactory factory) => ObjectNodeSection.DataSection; | ||
protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler); | ||
|
||
public override int ClassCode => -1771336339; | ||
|
||
public override bool IsShareable => false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I probably missed it, but I couldn't find any references to this. What does it do? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have a (mostly test-only) mode where each managed assembly can be compiled into a separate object file, and then linked into an EXE by the linker. In this mode, some nodes can end up being duplicated (e.g. |
||
|
||
public override bool StaticDependenciesAreComputed => true; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1122,10 +1122,7 @@ public string GetSymbolAlternateName(ISymbolNode node) | |
"__DispatchMapTableEnd", | ||
new SortableDependencyNode.ObjectNodeComparer(CompilerComparer.Instance)); | ||
|
||
public ArrayOfEmbeddedDataNode<EmbeddedObjectNode> FrozenSegmentRegion = new ArrayOfFrozenObjectsNode<EmbeddedObjectNode>( | ||
"__FrozenSegmentRegionStart", | ||
"__FrozenSegmentRegionEnd", | ||
new SortableDependencyNode.EmbeddedObjectNodeComparer(CompilerComparer.Instance)); | ||
public ArrayOfFrozenObjectsNode FrozenSegmentRegion = new ArrayOfFrozenObjectsNode(); | ||
|
||
internal ModuleInitializerListNode ModuleInitializerList = new ModuleInitializerListNode(); | ||
|
||
|
@@ -1158,7 +1155,7 @@ public virtual void AttachToDependencyGraph(DependencyAnalyzerBase<NodeFactory> | |
ReadyToRunHeader.Add(ReadyToRunSectionType.EagerCctor, EagerCctorTable, EagerCctorTable.StartSymbol, EagerCctorTable.EndSymbol); | ||
ReadyToRunHeader.Add(ReadyToRunSectionType.TypeManagerIndirection, TypeManagerIndirection, TypeManagerIndirection); | ||
ReadyToRunHeader.Add(ReadyToRunSectionType.InterfaceDispatchTable, DispatchMapTable, DispatchMapTable.StartSymbol); | ||
ReadyToRunHeader.Add(ReadyToRunSectionType.FrozenObjectRegion, FrozenSegmentRegion, FrozenSegmentRegion.StartSymbol, FrozenSegmentRegion.EndSymbol); | ||
ReadyToRunHeader.Add(ReadyToRunSectionType.FrozenObjectRegion, FrozenSegmentRegion, FrozenSegmentRegion, FrozenSegmentRegion.EndSymbol); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's a "ReadyToRunHeader"? Is this documented anywhere? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a data structure similar to the ReadyToRun header in R2R images. There might be a plan to unify aspects of this format at some point. The things pointed from the header are currently pretty disjoint. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The structure of the header is doc'd at https://github.com/dotnet/runtime/blob/main/docs/design/coreclr/botr/readytorun-format.md#readytorun_header. The main difference starts at READYTORUN_SECTION where the section type IDs are different for NativeAOT. Also the way to get to the header from "outside the image" doesn't exist. The address is inlined in code and not pointed by PE headers. |
||
ReadyToRunHeader.Add(ReadyToRunSectionType.ModuleInitializerList, ModuleInitializerList, ModuleInitializerList, ModuleInitializerList.EndSymbol); | ||
|
||
var commonFixupsTableNode = new ExternalReferencesTableNode("CommonFixupsTable", this); | ||
|
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.
Dehydratable I assume means serializable. This is an interesting design. What happens if there are cycles in this graph? Are they correctly preserved?
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.
Dehydratable means we will run the code from #77884 on this and "dehydrate" pointers and excess zeros in the data blob this node generated into a more succinct representation in the executable image. It will get "rehydrated" at startup.
For cycles in the graph of frozen objects: those can't happen - we don't allow freezing objects with mutable reference-typed fields. The write barriers don't handle frozen segment and the GC wouldn't handle references from those either. The freezing/serialization algorithm wouldn't probably have problems with 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.
Not having writeable fields does not prevent cycles. The following will have a cycle:
Can we have this in frozen objects?
(I am mostly curious if there are other restrictions that will prevent cycles. I do not think dehydration will have problems)
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.
That one bails at the stfld:
runtime/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs
Lines 605 to 608 in 1076cf5
In theory the cycle should be fine because the reference would be just a reloc to another symbol and the data blob associated with that symbol has another reloc.
I think the only reference-typed instance fields we currently really allow are those of delegates to instance methods, and those are pretty special.