diff --git a/src/coreclr/nativeaot/Common/src/Internal/Runtime/CompilerHelpers/StartupCodeHelpers.cs b/src/coreclr/nativeaot/Common/src/Internal/Runtime/CompilerHelpers/StartupCodeHelpers.cs index afd0348c047b..eab095b2a119 100644 --- a/src/coreclr/nativeaot/Common/src/Internal/Runtime/CompilerHelpers/StartupCodeHelpers.cs +++ b/src/coreclr/nativeaot/Common/src/Internal/Runtime/CompilerHelpers/StartupCodeHelpers.cs @@ -85,17 +85,32 @@ private static unsafe TypeManagerHandle[] CreateTypeManagers(IntPtr osModule, In moduleCount++; } - TypeManagerHandle[] modules = new TypeManagerHandle[moduleCount]; + // We cannot use the new keyword just yet, so stackalloc the array first + TypeManagerHandle* pHandles = stackalloc TypeManagerHandle[moduleCount]; int moduleIndex = 0; for (int i = 0; i < count; i++) { if (pModuleHeaders[i] != IntPtr.Zero) { - modules[moduleIndex] = RuntimeImports.RhpCreateTypeManager(osModule, pModuleHeaders[i], pClasslibFunctions, nClasslibFunctions); - moduleIndex++; + TypeManagerHandle handle = RuntimeImports.RhpCreateTypeManager(osModule, pModuleHeaders[i], pClasslibFunctions, nClasslibFunctions); + + // Rehydrate any dehydrated data structures + IntPtr dehydratedDataSection = RuntimeImports.RhGetModuleSection( + handle, ReadyToRunSectionType.DehydratedData, out int dehydratedDataLength); + if (dehydratedDataSection != IntPtr.Zero) + { + RehydrateData(dehydratedDataSection, dehydratedDataLength); + } + + pHandles[moduleIndex++] = handle; } } + // Any potentially dehydrated MethodTables got rehydrated, we can safely use `new` now. + TypeManagerHandle[] modules = new TypeManagerHandle[moduleCount]; + for (int i = 0; i < moduleCount; i++) + modules[i] = pHandles[i]; + return modules; } @@ -217,6 +232,49 @@ private static unsafe object[] InitializeStatics(IntPtr gcStaticRegionStart, int return spine; } + + private static unsafe void RehydrateData(IntPtr dehydratedData, int length) + { + // Destination for the hydrated data is in the first 32-bit relative pointer + byte* pDest = (byte*)ReadRelPtr32((void*)dehydratedData); + + // The dehydrated data follows + byte* pCurrent = (byte*)dehydratedData + sizeof(int); + byte* pEnd = (byte*)dehydratedData + length; + + // Fixup table immediately follows the command stream + int* pFixups = (int*)pEnd; + + while (pCurrent < pEnd) + { + pCurrent = DehydratedDataCommand.Decode(pCurrent, out int command, out int payload); + switch (command) + { + case DehydratedDataCommand.Copy: + // TODO: can we do any kind of memcpy here? + for (; payload > 0; payload--) + *pDest++ = *pCurrent++; + break; + case DehydratedDataCommand.ZeroFill: + pDest += payload; + break; + case DehydratedDataCommand.PtrReloc: + *(void**)pDest = ReadRelPtr32(pFixups + payload); + pDest += sizeof(void*); + break; + case DehydratedDataCommand.RelPtr32Reloc: + WriteRelPtr32(pDest, ReadRelPtr32(pFixups + payload)); + pDest += sizeof(int); + break; + } + } + + static void* ReadRelPtr32(void* address) + => (byte*)address + *(int*)address; + + static void WriteRelPtr32(void* dest, void* value) + => *(int*)dest = (int)((byte*)value - (byte*)dest); + } } [StructLayout(LayoutKind.Sequential)] diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj index 0c0e84b3b7d7..271949b251b1 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -40,6 +40,9 @@ + + Internal\Runtime\DehydratedData.cs + Internal\Runtime\RuntimeConstants.cs diff --git a/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj b/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj index 9daa8e0ee004..da62a5daa830 100644 --- a/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj +++ b/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj @@ -103,6 +103,9 @@ Internal\Runtime\ModuleHeaders.cs + + Internal\Runtime\DehydratedData.cs + Internal\Runtime\RuntimeConstants.cs diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ObjectNodeSection.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ObjectNodeSection.cs index 89c4b895584a..de6ec995220e 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ObjectNodeSection.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ObjectNodeSection.cs @@ -7,7 +7,8 @@ public enum SectionType { ReadOnly, Writeable, - Executable + Executable, + Uninitialized, } /// @@ -47,6 +48,7 @@ public bool IsStandardSection public static readonly ObjectNodeSection TextSection = new ObjectNodeSection("text", SectionType.Executable); public static readonly ObjectNodeSection TLSSection = new ObjectNodeSection("TLS", SectionType.Writeable); public static readonly ObjectNodeSection BssSection = new ObjectNodeSection("bss", SectionType.Writeable); + public static readonly ObjectNodeSection HydrationTargetSection = new ObjectNodeSection("hydrated", SectionType.Uninitialized); public static readonly ObjectNodeSection ManagedCodeWindowsContentSection = new ObjectNodeSection(".managedcode$I", SectionType.Executable); public static readonly ObjectNodeSection FoldableManagedCodeWindowsContentSection = new ObjectNodeSection(".managedcode$I", SectionType.Executable); public static readonly ObjectNodeSection ManagedCodeUnixContentSection = new ObjectNodeSection("__managedcode", SectionType.Executable); diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs index 29186fd18ad4..25b94a376542 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Diagnostics; namespace ILCompiler.DependencyAnalysis @@ -438,6 +439,16 @@ public static unsafe void WriteValue(RelocType relocType, void* location, long v } } + public static int GetSize(RelocType relocType) + { + return relocType switch + { + RelocType.IMAGE_REL_BASED_DIR64 => 8, + RelocType.IMAGE_REL_BASED_RELPTR32 => 4, + _ => throw new NotSupportedException(), + }; + } + public static unsafe long ReadValue(RelocType relocType, void* location) { switch (relocType) diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/SortableDependencyNode.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/SortableDependencyNode.cs index a29c732e0814..c6059b5cd1f0 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/SortableDependencyNode.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/SortableDependencyNode.cs @@ -42,7 +42,8 @@ protected enum ObjectNodePhase /// affect compiler correctness. Today that includes native layout tables. /// Ordered, - Unordered + Unordered, + Late, } protected enum ObjectNodeOrder @@ -94,6 +95,7 @@ protected enum ObjectNodeOrder StackTraceEmbeddedMetadataNode, StackTraceMethodMappingNode, ArrayOfEmbeddedDataNode, + DehydratedDataNode, } public class EmbeddedObjectNodeComparer : IComparer diff --git a/src/coreclr/tools/Common/Internal/Runtime/DehydratedData.cs b/src/coreclr/tools/Common/Internal/Runtime/DehydratedData.cs new file mode 100644 index 000000000000..ca632e0c25c7 --- /dev/null +++ b/src/coreclr/tools/Common/Internal/Runtime/DehydratedData.cs @@ -0,0 +1,116 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Debug = System.Diagnostics.Debug; + +namespace Internal.Runtime +{ + /// + /// Provides functionality to encode/decode dehydrated data instruction stream. + /// + /// + /// The instructions use a variable length encoding and are split in two parts: + /// the instruction command kind and command data (payload). + /// The payload is an integer. Instruction kind and payload can fit into a single + /// byte, the encoding is one byte. Bigger payloads produce bigger instructions. + /// + internal static class DehydratedDataCommand + { + public const byte Copy = 0x00; + public const byte ZeroFill = 0x01; + public const byte RelPtr32Reloc = 0x02; + public const byte PtrReloc = 0x03; + + private const byte DehydratedDataCommandMask = 0x03; + private const int DehydratedDataCommandPayloadShift = 2; + + private const int MaxRawShortPayload = (1 << (8 - DehydratedDataCommandPayloadShift)) - 1; + private const int MaxExtraPayloadBytes = 3; + private const int MaxShortPayload = MaxRawShortPayload - MaxExtraPayloadBytes; + + public static byte EncodeShort(int command, int commandData) + { + Debug.Assert((command & DehydratedDataCommandMask) == command); + Debug.Assert(commandData <= MaxShortPayload); + return (byte)(command | (commandData << DehydratedDataCommandPayloadShift)); + } + + public static int Encode(int command, int commandData, byte[] buffer) + { + Debug.Assert((command & DehydratedDataCommandMask) == command); + int remainingData = commandData - MaxShortPayload; + if (remainingData <= 0) + { + buffer[0] = EncodeShort(command, commandData); + return 1; + } + + int numExtraBytes = 0; + for (; remainingData != 0; remainingData >>= 8) + buffer[++numExtraBytes] = (byte)remainingData; + Debug.Assert(numExtraBytes <= MaxExtraPayloadBytes, "Decoder assumes this"); + + buffer[0] = (byte)(command | ((MaxShortPayload + numExtraBytes) << DehydratedDataCommandPayloadShift)); + return 1 + numExtraBytes; + } + + public static unsafe byte* Decode(byte* pB, out int command, out int payload) + { + byte b = *pB; + command = b & DehydratedDataCommandMask; + payload = b >> DehydratedDataCommandPayloadShift; + int extraBytes = payload - MaxShortPayload; + if (extraBytes > 0) + { + payload = *++pB; + if (extraBytes > 1) + { + payload += *++pB << 8; + if (extraBytes > 2) + payload += *++pB << 16; + } + + payload += MaxShortPayload; + } + + return pB + 1; + } + +#if false + static void Main() + { + int command, payload; + + byte[] buf = new byte[5]; + Debug.Assert(Encode(1, 0, buf) == 1); + Debug.Assert(buf[0] == 1); + Debug.Assert(D(buf, out command, out payload) == 1 && command == 1 && payload == 0); + Debug.Assert(Encode(1, 1, buf) == 1); + Debug.Assert(buf[0] == (1 | (1 << DehydratedDataCommandPayloadShift))); + Debug.Assert(D(buf, out command, out payload) == 1 && command == 1 && payload == 1); + Debug.Assert(Encode(1, 60, buf) == 1); + Debug.Assert(buf[0] == (1 | (60 << DehydratedDataCommandPayloadShift))); + Debug.Assert(D(buf, out command, out payload) == 1 && command == 1 && payload == 60); + Debug.Assert(Encode(1, 61, buf) == 2); + Debug.Assert(buf[0] == (1 | ((MaxShortPayload + 1) << DehydratedDataCommandPayloadShift))); + Debug.Assert(buf[1] == 1); + Debug.Assert(D(buf, out command, out payload) == 2 && command == 1 && payload == 61); + + Debug.Assert(Encode(3, 256, buf) == 2); + Debug.Assert(D(buf, out command, out payload) == 2 && command == 3 && payload == 256); + Debug.Assert(Encode(3, 6500, buf) == 3); + Debug.Assert(D(buf, out command, out payload) == 3 && command == 3 && payload == 6500); + Debug.Assert(Encode(3, 65000, buf) == 3); + Debug.Assert(D(buf, out command, out payload) == 3 && command == 3 && payload == 65000); + Debug.Assert(Encode(3, 100000, buf) == 4); + Debug.Assert(D(buf, out command, out payload) == 4 && command == 3 && payload == 100000); + + static unsafe int D(byte[] bytes, out int command, out int payload) + { + fixed (byte* pBytes = bytes) + return (int)(Decode(pBytes, out command, out payload) - pBytes); + } + } +#endif + } +} diff --git a/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs b/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs index 9d081f719c97..144bd50f949d 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs @@ -81,7 +81,7 @@ public enum ReadyToRunSectionType TypeManagerIndirection = 204, EagerCctor = 205, FrozenObjectRegion = 206, - // 207 is unused - it was used by GCStaticDesc + DehydratedData = 207, ThreadStaticOffsetRegion = 208, // 209 is unused - it was used by ThreadStaticGCDescRegion // 210 is unused - it was used by ThreadStaticIndex diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs index bf1a053c0c61..1bd82830585d 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs @@ -22,6 +22,7 @@ public partial class CompilationBuilder protected bool _methodBodyFolding; protected InstructionSetSupport _instructionSetSupport; protected SecurityMitigationOptions _mitigationOptions; + protected bool _dehydrate; protected bool _useDwarf5; partial void InitializePartial() @@ -84,6 +85,12 @@ public CompilationBuilder UseSecurityMitigationOptions(SecurityMitigationOptions return this; } + public CompilationBuilder UseDehydration(bool dehydrate) + { + _dehydrate = dehydrate; + return this; + } + public CompilationBuilder UseMethodBodyFolding(bool enable) { _methodBodyFolding = enable; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DehydratableObjectNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DehydratableObjectNode.cs new file mode 100644 index 000000000000..daf034850e44 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DehydratableObjectNode.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace ILCompiler.DependencyAnalysis +{ + public abstract class DehydratableObjectNode : ObjectNode + { + public sealed override ObjectNodeSection GetSection(NodeFactory factory) + { + return factory.MetadataManager.IsDataDehydrated ? ObjectNodeSection.HydrationTargetSection : GetDehydratedSection(factory); + } + + public sealed override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) + { + ObjectData result = GetDehydratableData(factory, relocsOnly); + + // If we're not generating full data yet, or dehydration is not active, + // return the ObjectData as is. + if (relocsOnly || !factory.MetadataManager.IsDataDehydrated) + return result; + + // Otherwise return the dehydrated data + return factory.MetadataManager.PrepareForDehydration(this, result); + } + + protected abstract ObjectNodeSection GetDehydratedSection(NodeFactory factory); + protected abstract ObjectData GetDehydratableData(NodeFactory factory, bool relocsOnly = false); + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DehydratedDataNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DehydratedDataNode.cs new file mode 100644 index 000000000000..1e412e2056e8 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DehydratedDataNode.cs @@ -0,0 +1,242 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; + +using Internal.Runtime; +using Internal.Text; +using Internal.TypeSystem; + +using Debug = System.Diagnostics.Debug; + +namespace ILCompiler.DependencyAnalysis +{ + /// + /// Responsible for generating dehydrated stream of data structures that need to be rehydrated + /// at runtime before use. + /// + /// + /// The things we're dehydrating: + /// * Pointers. On 64bit targets, absolute pointers take up 8 bytes + reloc in the image. The + /// size of the reloc can be up to 24 bytes (ELF/Linux). We replace relocs with a more efficient + /// index into a lookup table. + /// * Runs of zeros. These can be result of alignment or simply a result of how things ended up naturally. + /// + /// The dehydrated stream can be though of as an instruction stream with 3 kinds of instructions: + /// * Copy N bytes of literal data following the instruction. + /// * Generate N bytes of zeros. + /// * Generate a relocation to Nth entry in the lookup table that supplements the dehydrated stream. + /// + internal sealed class DehydratedDataNode : ObjectNode, ISymbolDefinitionNode + { + private ObjectAndOffsetSymbolNode _endSymbol; + + public DehydratedDataNode() + { + _endSymbol = new ObjectAndOffsetSymbolNode(this, 0, "__dehydrated_data_End", true); + } + + public override bool IsShareable => false; + + protected internal override int Phase => (int)ObjectNodePhase.Late; + + public override int ClassCode => (int)ObjectNodeOrder.DehydratedDataNode; + + public override bool StaticDependenciesAreComputed => true; + + public int Offset => 0; + + public ISymbolNode EndSymbol => _endSymbol; + + public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) + { + sb.Append(nameMangler.CompilationUnitPrefix).Append("__dehydrated_data"); + } + + protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler); + + public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) + { + // This is a summary node that doesn't contribute to the dependency graph. + if (relocsOnly) + return new ObjectData(Array.Empty(), Array.Empty(), 1, Array.Empty()); + + ObjectDataBuilder builder = new ObjectDataBuilder(factory, relocsOnly); + builder.AddSymbol(this); + + // Count the number of occurences of reloc targets. We'll sort the reloc targets by the number of + // references to them so that we can assign the most efficient instruction encoding (1 byte) for + // the most popular targets. + ISymbolDefinitionNode firstSymbol = null; + var relocOccurences = new Dictionary(); + foreach (ObjectNode.ObjectData o in factory.MetadataManager.GetDehydratableData()) + { + firstSymbol ??= o.DefinedSymbols[0]; + + foreach (Relocation reloc in o.Relocs) + { + ISymbolNode target = reloc.Target; + if (target is ISymbolNodeWithLinkage withLinkage) + target = withLinkage.NodeForLinkage(factory); + relocOccurences.TryGetValue(target, out int num); + relocOccurences[target] = ++num; + } + } + + if (firstSymbol != null) + builder.EmitReloc(firstSymbol, RelocType.IMAGE_REL_BASED_RELPTR32, -firstSymbol.Offset); + + // Sort the reloc targets and create reloc lookup table. + KeyValuePair[] relocSort = new List>(relocOccurences).ToArray(); + Array.Sort(relocSort, (x, y) => y.Value.CompareTo(x.Value)); + for (int i = 0; i < relocSort.Length; i++) + relocSort[i] = new KeyValuePair(relocSort[i].Key, i); + var relocs = new Dictionary(relocSort); + + // Walk all the ObjectDatas and generate the dehydrated instruction stream. + byte[] buff = new byte[5]; + int dehydratedSegmentPosition = 0; + foreach (ObjectData o in factory.MetadataManager.GetDehydratableData()) + { + Debug.Assert(o.Data.Length != 0); + + if (o.Alignment > 1) + { + // Might need to emit a ZeroFill to align the data. + int oldPosition = dehydratedSegmentPosition; + dehydratedSegmentPosition = dehydratedSegmentPosition.AlignUp(o.Alignment); + if (dehydratedSegmentPosition > oldPosition) + builder.EmitByte(DehydratedDataCommand.EncodeShort(DehydratedDataCommand.ZeroFill, dehydratedSegmentPosition - oldPosition)); + } + + int currentReloc = 0; + int sourcePosition = 0; + + while (sourcePosition < o.Data.Length) + { + // The ObjectData can be though of as a run of bytes interrupted by relocations. + // We identify the next chunk of data by looking at the next relocation and + // emit the next chunk of data (if any) followed by a relocation (if any). + int bytesToCopy; + Relocation reloc; + if (currentReloc < o.Relocs.Length) + { + reloc = o.Relocs[currentReloc++]; + + // We assume relocations are sorted + Debug.Assert(reloc.Offset >= sourcePosition); + bytesToCopy = reloc.Offset - sourcePosition; + } + else + { + reloc = default; + bytesToCopy = o.Data.Length - sourcePosition; + } + + // Emit the run of bytes before the next relocation (or end of ObjectData, whichever + // comes first). + while (bytesToCopy > 0) + { + // Try to identify a continuos run of zeros. If we find one, split this run of + // bytes into chunks of data interleaved with zero fill instructions. + int chunkLength = 0; + int numZeros = 0; + const int MinProfitableZerofill = 4; + for (int i = sourcePosition; i < sourcePosition + bytesToCopy; i++) + { + if (o.Data[i] == 0) + { + numZeros++; + } + else if (numZeros >= MinProfitableZerofill) + { + break; + } + else + { + chunkLength += numZeros; + numZeros = 0; + chunkLength++; + } + } + + // If it wouldn't be profiable to emit zero fill, emit zeros at literal data. + if (numZeros < MinProfitableZerofill) + { + chunkLength += numZeros; + numZeros = 0; + } + + // Emit literal data if there's any. + if (chunkLength > 0) + { + int written = DehydratedDataCommand.Encode(DehydratedDataCommand.Copy, chunkLength, buff); + builder.EmitBytes(buff, 0, written); + builder.EmitBytes(o.Data, sourcePosition, chunkLength); + sourcePosition += chunkLength; + bytesToCopy -= chunkLength; + } + + // Emit a ZeroFill instruction if there's any zeros. + if (numZeros > 0) + { + int written = DehydratedDataCommand.Encode(DehydratedDataCommand.ZeroFill, numZeros, buff); + builder.EmitBytes(buff, 0, written); + sourcePosition += numZeros; + bytesToCopy -= numZeros; + } + } + + // Generate the next relocation if there's any. + if (reloc.Target != null) + { +#if DEBUG + unsafe + { + fixed (byte* pData = &o.Data[reloc.Offset]) + { + long delta = Relocation.ReadValue(reloc.RelocType, pData); + // Extra work needed to be able to encode/decode relocs with deltas + Debug.Assert(delta == 0); + } + } +#endif + + // The size of the relocation is included in the ObjectData bytes. Skip the literal bytes. + sourcePosition += Relocation.GetSize(reloc.RelocType); + + ISymbolNode target = reloc.Target; + if (target is ISymbolNodeWithLinkage withLinkage) + target = withLinkage.NodeForLinkage(factory); + + int targetIndex = relocs[target]; + + int relocCommand = reloc.RelocType switch + { + RelocType.IMAGE_REL_BASED_DIR64 => DehydratedDataCommand.PtrReloc, + RelocType.IMAGE_REL_BASED_RELPTR32 => DehydratedDataCommand.RelPtr32Reloc, + _ => throw new NotSupportedException(), + }; + + int written = DehydratedDataCommand.Encode(relocCommand, targetIndex, buff); + builder.EmitBytes(buff, 0, written); + } + } + + Debug.Assert(sourcePosition == o.Data.Length); + dehydratedSegmentPosition += o.Data.Length; + } + + _endSymbol.SetSymbolOffset(builder.CountBytes); + builder.AddSymbol(_endSymbol); + + for (int i = 0; i < relocSort.Length; i++) + builder.EmitReloc(relocSort[i].Key, RelocType.IMAGE_REL_BASED_RELPTR32); + + return builder.ToObjectData(); + } + + public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.ReadOnlyDataSection; + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs index 91eec5ffef39..04a5b32c9acb 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EETypeNode.cs @@ -60,7 +60,7 @@ namespace ILCompiler.DependencyAnalysis /// | /// [Relative ptr] | Pointer to the generic argument and variance info (optional) /// - public partial class EETypeNode : ObjectNode, IEETypeNode, ISymbolDefinitionNode, ISymbolNodeWithLinkage + public partial class EETypeNode : DehydratableObjectNode, IEETypeNode, ISymbolDefinitionNode, ISymbolNodeWithLinkage { protected readonly TypeDesc _type; internal readonly EETypeOptionalFieldsBuilder _optionalFieldsBuilder = new EETypeOptionalFieldsBuilder(); @@ -117,7 +117,7 @@ public virtual ISymbolNode NodeForLinkage(NodeFactory factory) public TypeDesc Type => _type; - public override ObjectNodeSection GetSection(NodeFactory factory) + protected override ObjectNodeSection GetDehydratedSection(NodeFactory factory) { if (factory.Target.IsWindows) return ObjectNodeSection.ReadOnlyDataSection; @@ -561,7 +561,7 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact return dependencies; } - public override ObjectData GetData(NodeFactory factory, bool relocsOnly) + protected override ObjectData GetDehydratableData(NodeFactory factory, bool relocsOnly) { ObjectDataBuilder objData = new ObjectDataBuilder(factory, relocsOnly); objData.RequireInitialPointerAlignment(); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericDefinitionEETypeNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericDefinitionEETypeNode.cs index 36fa4b640ab2..d69d87db2754 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericDefinitionEETypeNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericDefinitionEETypeNode.cs @@ -34,7 +34,7 @@ protected internal override void ComputeOptionalEETypeFields(NodeFactory factory { } - public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) + protected override ObjectData GetDehydratableData(NodeFactory factory, bool relocsOnly = false) { ObjectDataBuilder dataBuilder = new ObjectDataBuilder(factory, relocsOnly); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ObjectWriter.cs index b984cda14384..d6eb8ee23148 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ObjectWriter.cs @@ -131,6 +131,7 @@ public enum CustomSectionAttributes ReadOnly = 0x0000, Writeable = 0x0001, Executable = 0x0002, + Uninitialized = 0x0004, }; /// @@ -151,6 +152,9 @@ private static CustomSectionAttributes GetCustomSectionAttributes(ObjectNodeSect case SectionType.Writeable: attributes |= CustomSectionAttributes.Writeable; break; + case SectionType.Uninitialized: + attributes |= CustomSectionAttributes.Uninitialized | CustomSectionAttributes.Writeable; + break; } return attributes; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs index b63026dae37a..9e35b08545a2 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs @@ -60,6 +60,8 @@ public abstract class MetadataManager : ICompilationRootProvider private readonly SortedSet _typesWithStructMarshalling = new SortedSet(TypeSystemComparer.Instance); private HashSet _templateMethodEntries = new HashSet(); + private List<(DehydratableObjectNode Node, ObjectNode.ObjectData Data)> _dehydratableData = new List<(DehydratableObjectNode Node, ObjectNode.ObjectData data)>(); + internal NativeLayoutInfoNode NativeLayoutInfo { get; private set; } public MetadataManager(CompilerTypeSystemContext typeSystemContext, MetadataBlockingPolicy blockingPolicy, @@ -71,6 +73,32 @@ public abstract class MetadataManager : ICompilationRootProvider _dynamicInvokeThunkGenerationPolicy = dynamicInvokeThunkGenerationPolicy; } + public bool IsDataDehydrated => true; + + internal ObjectNode.ObjectData PrepareForDehydration(DehydratableObjectNode node, ObjectNode.ObjectData hydratedData) + { + _dehydratableData.Add((node, hydratedData)); + + return new ObjectNode.ObjectData(new byte[hydratedData.Data.Length], + Array.Empty(), + hydratedData.Alignment, + hydratedData.DefinedSymbols); + } + + public IEnumerable GetDehydratableData() + { +#if DEBUG + // We're making an assumption that PrepareForDehydration was called in the emission order. + // Double check that here. + var comparer = new CompilerComparer(); + for (int i = 1; i < _dehydratableData.Count; i++) + Debug.Assert(comparer.Compare(_dehydratableData[i - 1].Node, _dehydratableData[i].Node) < 0); +#endif + + foreach (var entry in _dehydratableData) + yield return entry.Data; + } + public void AttachToDependencyGraph(DependencyAnalyzerBase graph) { graph.NewMarkedNode += Graph_NewMarkedNode; @@ -155,6 +183,12 @@ public virtual void AddToReadyToRunHeader(ReadyToRunHeaderNode header, NodeFacto // The external references tables should go last header.Add(BlobIdToReadyToRunSection(ReflectionMapBlob.NativeReferences), nativeReferencesTableNode, nativeReferencesTableNode, nativeReferencesTableNode.EndSymbol); header.Add(BlobIdToReadyToRunSection(ReflectionMapBlob.NativeStatics), nativeStaticsTableNode, nativeStaticsTableNode, nativeStaticsTableNode.EndSymbol); + + if (IsDataDehydrated) + { + var dehydratedDataNode = new DehydratedDataNode(); + header.Add(ReadyToRunSectionType.DehydratedData, dehydratedDataNode, dehydratedDataNode, dehydratedDataNode.EndSymbol); + } } protected virtual void Graph_NewMarkedNode(DependencyNodeCore obj) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index 214570667ca9..fdee2473d392 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -180,6 +180,9 @@ TypeSystem\MetadataEmitter\TypeSystemMetadataEmitter.cs + + Common\DehydratedData.cs + Common\GCDescEncoder.cs @@ -319,6 +322,8 @@ + + diff --git a/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs b/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs index b7b11d8bcb1d..17b496f08482 100644 --- a/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs +++ b/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs @@ -113,6 +113,8 @@ internal sealed class ILCompilerRootCommand : RootCommand new(new[] { "--instruction-set" }, "Instruction set to allow or disallow"); public Option Guard { get; } = new(new[] { "--guard" }, "Enable mitigations. Options: 'cf': CFG (Control Flow Guard, Windows only)"); + public Option Dehydrate { get; } = + new(new[] { "--dehydrate" }, "Dehydrate runtime data structures"); public Option PreinitStatics { get; } = new(new[] { "--preinitstatics" }, "Interpret static constructors at compile time if possible (implied by -O)"); public Option NoPreinitStatics { get; } = @@ -205,6 +207,7 @@ public ILCompilerRootCommand(string[] args) : base(".NET Native IL Compiler") AddOption(Parallelism); AddOption(InstructionSet); AddOption(Guard); + AddOption(Dehydrate); AddOption(PreinitStatics); AddOption(NoPreinitStatics); AddOption(SuppressedWarnings);