Skip to content

Commit f5dc18c

Browse files
elinor-fungSimaTian
authored andcommitted
Allow using flat layout for component assemblies of composite R2R (#115631)
Allow using flat layout for component assemblies of composite R2R. The component assemblies don't have native code, so we can just use their flat layout without loading / mapping them to virtual addresses. For scenarios where we can't just map from a file for the load - for example, external data (Android), this avoids copying all the data of component assemblies. - Skip emitting empty sections in R2R images - When we had no data for a section, we'd emit a single-byte section. Avoid including the section altogether - Use read-only section for R2R headers - This was using writeable data on non-Windows. We do the mapping ourselves and don't need this to be writeable on any platform. The way we emit R2R PE images, the read-only data goes into .text, so this should not incur the penalty of an extra section. - Relax checks for component assembly initialization to stop requiring a loaded layout - Handle getting the owner composite name for a flat layout of a component assembly of composite R2R
1 parent 854a119 commit f5dc18c

File tree

5 files changed

+161
-92
lines changed

5 files changed

+161
-92
lines changed

src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/HeaderNode.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,10 @@ public void Add(ReadyToRunSectionType id, DependencyNodeCore<NodeFactory> node,
129129

130130
public override bool StaticDependenciesAreComputed => true;
131131

132-
public override ObjectNodeSection GetSection(NodeFactory factory)
133-
{
134-
if (factory.Target.IsWindows)
135-
return ObjectNodeSection.ReadOnlyDataSection;
136-
else
137-
return ObjectNodeSection.DataSection;
138-
}
132+
// For R2R, we can put the header in the read-only section on non-Windows as well. Since we emit a PE image
133+
// and do our own mapping, we don't need it to be writeable for the OS loader to handle absolute pointer relocs.
134+
// Our R2R PE images group read-only data into the .text section, so this doesn't result in more work to map.
135+
public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.ReadOnlyDataSection;
139136

140137
public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
141138
{

src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs

Lines changed: 107 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ namespace ILCompiler.PEWriter
2121
/// metadata and IL and adding new code and data representing the R2R JITted code and
2222
/// additional runtime structures (R2R header and tables).
2323
/// </summary>
24-
public class R2RPEBuilder : PEBuilder
24+
public sealed class R2RPEBuilder : PEBuilder
2525
{
2626
/// <summary>
2727
/// Number of low-order RVA bits that must match file position on Linux.
@@ -73,7 +73,7 @@ public SectionRVADelta(int startRVA, int endRVA, int deltaRVA)
7373
/// Name of the initialized data section.
7474
/// </summary>
7575
public const string SDataSectionName = ".sdata";
76-
76+
7777
/// <summary>
7878
/// Name of the relocation section.
7979
/// </summary>
@@ -94,11 +94,6 @@ public SectionRVADelta(int startRVA, int endRVA, int deltaRVA)
9494
/// </summary>
9595
private TargetDetails _target;
9696

97-
/// <summary>
98-
/// Complete list of sections to emit into the output R2R executable.
99-
/// </summary>
100-
private ImmutableArray<Section> _sections;
101-
10297
/// <summary>
10398
/// Callback to retrieve the runtime function table which needs setting to the
10499
/// ExceptionTable PE directory entry.
@@ -112,28 +107,48 @@ public SectionRVADelta(int startRVA, int endRVA, int deltaRVA)
112107
/// </summary>
113108
private List<SectionRVADelta> _sectionRvaDeltas;
114109

115-
/// <summary>
116-
/// Logical section start RVAs. When emitting R2R PE executables for Linux, we must
117-
/// align RVA's so that their 'RVABitsToMatchFilePos' lowest-order bits match the
118-
/// file position (otherwise memory mapping of the file fails and CoreCLR silently
119-
/// switches over to runtime JIT). PEBuilder doesn't support this today so that we
120-
/// must store the RVA's and post-process the produced PE by patching the section
121-
/// headers in the PE header.
122-
/// </summary>
123-
private int[] _sectionRVAs;
110+
private class SerializedSectionData
111+
{
112+
/// <summary>
113+
/// Name of the section
114+
/// </summary>
115+
public string Name;
124116

125-
/// <summary>
126-
/// Pointers to the location of the raw data. Needed to allow phyical file alignment
127-
/// beyond 4KB. PEBuilder doesn't support this today so that we
128-
/// must store the RVA's and post-process the produced PE by patching the section
129-
/// headers in the PE header.
130-
/// </summary>
131-
private int[] _sectionPointerToRawData;
117+
/// <summary>
118+
/// Logical section start RVAs. When emitting R2R PE executables for Linux, we must
119+
/// align RVA's so that their 'RVABitsToMatchFilePos' lowest-order bits match the
120+
/// file position (otherwise memory mapping of the file fails and CoreCLR silently
121+
/// switches over to runtime JIT). PEBuilder doesn't support this today so that we
122+
/// must store the RVA's and post-process the produced PE by patching the section
123+
/// headers in the PE header.
124+
/// </summary>
125+
public int RVA;
126+
127+
/// <summary>
128+
/// Pointers to the location of the raw data. Needed to allow phyical file alignment
129+
/// beyond 4KB. PEBuilder doesn't support this today so that we
130+
/// must store the RVA's and post-process the produced PE by patching the section
131+
/// headers in the PE header.
132+
/// </summary>
133+
public int PointerToRawData;
134+
135+
/// <summary>
136+
/// Maximum of virtual and physical size for each section.
137+
/// </summary>
138+
public int RawSize;
139+
140+
/// <summary>
141+
/// Whether or not the section has been serialized - if the RVA, pointer to raw data,
142+
/// and size have been set.
143+
/// </summary>
144+
public bool IsSerialized;
145+
}
132146

133147
/// <summary>
134-
/// Maximum of virtual and physical size for each section.
148+
/// List of possible sections to emit into the output R2R executable in the order in which
149+
/// they are expected to be serialized. Data (aside from name) is set during serialization.
135150
/// </summary>
136-
private int[] _sectionRawSizes;
151+
private readonly SerializedSectionData[] _sectionData;
137152

138153
/// <summary>
139154
/// R2R PE section builder &amp; relocator.
@@ -206,18 +221,13 @@ public R2RPEBuilder(
206221
PEHeaderConstants.SectionAlignment);
207222
}
208223

209-
ImmutableArray<Section>.Builder sectionListBuilder = ImmutableArray.CreateBuilder<Section>();
224+
List<SerializedSectionData> sectionData = new List<SerializedSectionData>();
210225
foreach (SectionInfo sectionInfo in _sectionBuilder.GetSections())
211226
{
212-
ILCompiler.PEWriter.Section builderSection = _sectionBuilder.FindSection(sectionInfo.SectionName);
213-
Debug.Assert(builderSection != null);
214-
sectionListBuilder.Add(new Section(builderSection.Name, builderSection.Characteristics));
227+
sectionData.Add(new SerializedSectionData() { Name = sectionInfo.SectionName });
215228
}
216229

217-
_sections = sectionListBuilder.ToImmutableArray();
218-
_sectionRVAs = new int[_sections.Length];
219-
_sectionPointerToRawData = new int[_sections.Length];
220-
_sectionRawSizes = new int[_sections.Length];
230+
_sectionData = sectionData.ToArray();
221231
}
222232

223233
public void SetCorHeader(ISymbolNode symbol, int headerSize)
@@ -400,13 +410,17 @@ private void UpdateSectionRVAs(Stream outputStream)
400410
16 * sizeof(long); // directory entries
401411

402412
int sectionHeaderOffset = DosHeaderSize + PESignatureSize + COFFHeaderSize + peHeaderSize;
403-
int sectionCount = _sectionRVAs.Length;
413+
int sectionCount = _sectionData.Length;
404414
for (int sectionIndex = 0; sectionIndex < sectionCount; sectionIndex++)
405415
{
416+
SerializedSectionData section = _sectionData[sectionIndex];
417+
if (!section.IsSerialized)
418+
continue;
419+
406420
if (_customPESectionAlignment != 0)
407421
{
408422
// When _customPESectionAlignment is set, the physical and virtual sizes are the same
409-
byte[] sizeBytes = BitConverter.GetBytes(_sectionRawSizes[sectionIndex]);
423+
byte[] sizeBytes = BitConverter.GetBytes(section.RawSize);
410424
Debug.Assert(sizeBytes.Length == sizeof(int));
411425

412426
// Update VirtualSize
@@ -424,23 +438,33 @@ private void UpdateSectionRVAs(Stream outputStream)
424438
// Update RVAs
425439
{
426440
outputStream.Seek(sectionHeaderOffset + SectionHeaderSize * sectionIndex + SectionHeaderRVAOffset, SeekOrigin.Begin);
427-
byte[] rvaBytes = BitConverter.GetBytes(_sectionRVAs[sectionIndex]);
441+
byte[] rvaBytes = BitConverter.GetBytes(section.RVA);
428442
Debug.Assert(rvaBytes.Length == sizeof(int));
429443
outputStream.Write(rvaBytes, 0, rvaBytes.Length);
430444
}
431445

432446
// Update pointer to raw data
433447
{
434448
outputStream.Seek(sectionHeaderOffset + SectionHeaderSize * sectionIndex + SectionHeaderPointerToRawDataOffset, SeekOrigin.Begin);
435-
byte[] rawDataBytesBytes = BitConverter.GetBytes(_sectionPointerToRawData[sectionIndex]);
449+
byte[] rawDataBytesBytes = BitConverter.GetBytes(section.PointerToRawData);
436450
Debug.Assert(rawDataBytesBytes.Length == sizeof(int));
437451
outputStream.Write(rawDataBytesBytes, 0, rawDataBytesBytes.Length);
438452
}
439453
}
440454

441455
// Patch SizeOfImage to point past the end of the last section
456+
SerializedSectionData lastSection = null;
457+
for (int i = sectionCount - 1; i >= 0; i--)
458+
{
459+
if (_sectionData[i].IsSerialized)
460+
{
461+
lastSection = _sectionData[i];
462+
break;
463+
}
464+
}
465+
Debug.Assert(lastSection != null);
442466
outputStream.Seek(DosHeaderSize + PESignatureSize + COFFHeaderSize + OffsetOfSizeOfImage, SeekOrigin.Begin);
443-
int sizeOfImage = AlignmentHelper.AlignUp(_sectionRVAs[sectionCount - 1] + _sectionRawSizes[sectionCount - 1], Header.SectionAlignment);
467+
int sizeOfImage = AlignmentHelper.AlignUp(lastSection.RVA + lastSection.RawSize, Header.SectionAlignment);
444468
byte[] sizeOfImageBytes = BitConverter.GetBytes(sizeOfImage);
445469
Debug.Assert(sizeOfImageBytes.Length == sizeof(int));
446470
outputStream.Write(sizeOfImageBytes, 0, sizeOfImageBytes.Length);
@@ -557,14 +581,21 @@ private int RelocateRVA(int rva)
557581
/// </summary>
558582
protected override ImmutableArray<Section> CreateSections()
559583
{
560-
return _sections;
584+
ImmutableArray<Section>.Builder sectionListBuilder = ImmutableArray.CreateBuilder<Section>();
585+
foreach (SectionInfo sectionInfo in _sectionBuilder.GetSections())
586+
{
587+
// Only include sections that have content.
588+
if (!_sectionBuilder.HasContent(sectionInfo.SectionName))
589+
continue;
590+
591+
sectionListBuilder.Add(new Section(sectionInfo.SectionName, sectionInfo.Characteristics));
592+
}
593+
594+
return sectionListBuilder.ToImmutable();
561595
}
562596

563597
/// <summary>
564-
/// Output the section with a given name. For sections existent in the source MSIL PE file
565-
/// (.text, optionally .rsrc and .reloc), we first copy the content of the input MSIL PE file
566-
/// and then call the section serialization callback to emit the extra content after the input
567-
/// section content.
598+
/// Output the section with a given name.
568599
/// </summary>
569600
/// <param name="name">Section name</param>
570601
/// <param name="location">RVA and file location where the section will be put</param>
@@ -574,18 +605,33 @@ protected override BlobBuilder SerializeSection(string name, SectionLocation loc
574605
BlobBuilder sectionDataBuilder = null;
575606
int sectionStartRva = location.RelativeVirtualAddress;
576607

577-
int outputSectionIndex = _sections.Length - 1;
578-
while (outputSectionIndex >= 0 && _sections[outputSectionIndex].Name != name)
608+
int outputSectionIndex = _sectionData.Length - 1;
609+
while (outputSectionIndex >= 0 && _sectionData[outputSectionIndex].Name != name)
579610
{
580611
outputSectionIndex--;
581612
}
582613

614+
if (outputSectionIndex < 0)
615+
throw new ArgumentException($"Unknown section name: '{name}'", nameof(name));
616+
617+
Debug.Assert(_sectionBuilder.HasContent(name));
618+
SerializedSectionData outputSection = _sectionData[outputSectionIndex];
619+
SerializedSectionData previousSection = null;
620+
for (int i = outputSectionIndex - 1; i >= 0; i--)
621+
{
622+
if (_sectionData[i].IsSerialized)
623+
{
624+
previousSection = _sectionData[i];
625+
break;
626+
}
627+
}
628+
583629
int injectedPadding = 0;
584630
if (_customPESectionAlignment != 0)
585631
{
586-
if (outputSectionIndex > 0)
632+
if (previousSection is not null)
587633
{
588-
sectionStartRva = Math.Max(sectionStartRva, _sectionRVAs[outputSectionIndex - 1] + _sectionRawSizes[outputSectionIndex - 1]);
634+
sectionStartRva = Math.Max(sectionStartRva, previousSection.RVA + previousSection.RawSize);
589635
}
590636

591637
int newSectionStartRva = AlignmentHelper.AlignUp(sectionStartRva, _customPESectionAlignment);
@@ -603,13 +649,13 @@ protected override BlobBuilder SerializeSection(string name, SectionLocation loc
603649
if (!_target.IsWindows)
604650
{
605651
const int RVAAlign = 1 << RVABitsToMatchFilePos;
606-
if (outputSectionIndex > 0)
652+
if (previousSection is not null)
607653
{
608-
sectionStartRva = Math.Max(sectionStartRva, _sectionRVAs[outputSectionIndex - 1] + _sectionRawSizes[outputSectionIndex - 1]);
654+
sectionStartRva = Math.Max(sectionStartRva, previousSection.RVA + previousSection.RawSize);
609655

610656
// when assembly is stored in a singlefile bundle, an additional skew is introduced
611-
// as the streams inside the bundle are not necessarily page aligned as we do not
612-
// know the actual page size on the target system.
657+
// as the streams inside the bundle are not necessarily page aligned as we do not
658+
// know the actual page size on the target system.
613659
// We may need one page gap of unused VA space before the next section starts.
614660
// We will assume the page size is <= RVAAlign
615661
sectionStartRva += RVAAlign;
@@ -622,36 +668,19 @@ protected override BlobBuilder SerializeSection(string name, SectionLocation loc
622668
location = new SectionLocation(sectionStartRva, location.PointerToRawData);
623669
}
624670

625-
if (outputSectionIndex >= 0)
626-
{
627-
_sectionRVAs[outputSectionIndex] = sectionStartRva;
628-
_sectionPointerToRawData[outputSectionIndex] = location.PointerToRawData;
629-
}
671+
outputSection.RVA = sectionStartRva;
672+
outputSection.PointerToRawData = location.PointerToRawData;
630673

631674
BlobBuilder extraData = _sectionBuilder.SerializeSection(name, location);
632-
if (extraData != null)
633-
{
634-
if (sectionDataBuilder == null)
635-
{
636-
// See above - there's a bug due to which LinkSuffix to an empty BlobBuilder screws up the blob content.
637-
sectionDataBuilder = extraData;
638-
}
639-
else
640-
{
641-
sectionDataBuilder.LinkSuffix(extraData);
642-
}
643-
}
644-
645-
// Make sure the section has at least 1 byte, otherwise the PE emitter goes mad,
646-
// messes up the section map and corrups the output executable.
675+
Debug.Assert(extraData != null);
647676
if (sectionDataBuilder == null)
648677
{
649-
sectionDataBuilder = new BlobBuilder();
678+
// See above - there's a bug due to which LinkSuffix to an empty BlobBuilder screws up the blob content.
679+
sectionDataBuilder = extraData;
650680
}
651-
652-
if (sectionDataBuilder.Count == 0)
681+
else
653682
{
654-
sectionDataBuilder.WriteByte(0);
683+
sectionDataBuilder.LinkSuffix(extraData);
655684
}
656685

657686
int sectionRawSize = sectionDataBuilder.Count - injectedPadding;
@@ -664,15 +693,13 @@ protected override BlobBuilder SerializeSection(string name, SectionLocation loc
664693
sectionRawSize = count;
665694
}
666695

667-
if (outputSectionIndex >= 0)
668-
{
669-
_sectionRawSizes[outputSectionIndex] = sectionRawSize;
670-
}
696+
outputSection.RawSize = sectionRawSize;
697+
outputSection.IsSerialized = true;
671698

672699
return sectionDataBuilder;
673700
}
674701
}
675-
702+
676703
/// <summary>
677704
/// Simple helper for filling in PE header information.
678705
/// </summary>

src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,5 +944,22 @@ public void RelocateOutputFile(
944944
// Flush remaining PE file blocks after the last relocation
945945
relocationHelper.CopyRestOfFile();
946946
}
947+
948+
internal bool HasContent(string sectionName)
949+
{
950+
if (sectionName == R2RPEBuilder.ExportDataSectionName)
951+
return _exportSymbols.Count > 0 && _dllNameForExportDirectoryTable != null;
952+
953+
if (sectionName == R2RPEBuilder.RelocSectionName)
954+
{
955+
return _sections.Any(
956+
s => s.PlacedObjectDataToRelocate.Any(
957+
d => d.Relocs.Any(
958+
r => Relocation.GetFileRelocationType(r.RelocType) != RelocType.IMAGE_REL_BASED_ABSOLUTE)));
959+
}
960+
961+
Section section = FindSection(sectionName);
962+
return section != null && section.Content.Count > 0;
963+
}
947964
}
948965
}

src/coreclr/vm/peimagelayout.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@ PEImageLayout* PEImageLayout::LoadConverted(PEImage* pOwner, bool disableMapping
106106
_ASSERTE(!pOwner->IsFile() || !pFlat->HasReadyToRunHeader() || disableMapping);
107107
#endif
108108

109-
if ((pFlat->HasReadyToRunHeader() && AllowR2RForImage(pOwner))
109+
// If the image is R2R with native code (that is, not a component assembly of composite R2R) or has writeable sections,
110+
// we need to actually load/map it into virtual addresses
111+
if ((pFlat->HasReadyToRunHeader() && !pFlat->IsComponentAssembly() && AllowR2RForImage(pOwner))
110112
|| pFlat->HasWriteableSections())
111113
{
112114
return new ConvertedImageLayout(pFlat, disableMapping);

0 commit comments

Comments
 (0)