From bcc30a083c8f7a2df504cf37d2079bec049c36de Mon Sep 17 00:00:00 2001 From: Steven He Date: Thu, 19 Feb 2026 04:15:37 +0900 Subject: [PATCH 01/11] Bring up type preinitialization for R2R --- .../readytorun-preinitialized-statics.md | 200 ++++++ src/coreclr/inc/readytorun.h | 41 +- src/coreclr/jit/helperexpansion.cpp | 14 +- .../nativeaot/Runtime/inc/ModuleHeaders.h | 2 +- .../Compiler/ObjectWriter/ObjectWriter.cs | 4 +- .../Common/Internal/Runtime/ModuleHeaders.cs | 3 +- .../Internal/Runtime/ReadyToRunConstants.cs | 7 + .../Compiler/PreinitializationManager.cs | 4 + .../Compiler/TypePreinit.cs | 61 +- .../Compiler/Dataflow/FlowAnnotations.cs | 14 + .../ReadyToRun/MethodFixupSignature.cs | 2 +- ...rializedPreinitializationObjectDataNode.cs | 80 +++ .../TypePreinitializationMapNode.cs | 178 +++++ .../TypePreinitializedStaticsDataNode.cs | 295 +++++++++ .../ReadyToRunCodegenNodeFactory.cs | 30 + .../ReadyToRunSymbolNodeFactory.cs | 10 + .../DelegateCreationInfo.ReadyToRun.cs | 92 +++ .../PreinitializationExtensions.cs | 61 ++ .../ReadyToRunPreinitializationManager.cs | 423 ++++++++++++ .../Compiler/ReadyToRunCodegenCompilation.cs | 1 + .../ReadyToRunCodegenCompilationBuilder.cs | 17 + .../ILCompiler.ReadyToRun.csproj | 11 + .../JitInterface/CorInfoImpl.ReadyToRun.cs | 33 +- .../aot/crossgen2/Crossgen2RootCommand.cs | 6 + src/coreclr/tools/aot/crossgen2/Program.cs | 14 + .../aot/crossgen2/Properties/Resources.resx | 8 +- src/coreclr/tools/r2rdump/TextDumper.cs | 501 +++++++++++++++ src/coreclr/vm/ceeload.cpp | 152 +++++ src/coreclr/vm/ceeload.h | 6 +- src/coreclr/vm/methodtable.cpp | 606 +++++++++++++++++- src/coreclr/vm/methodtable.h | 14 + src/coreclr/vm/readytoruninfo.cpp | 178 +++++ src/coreclr/vm/readytoruninfo.h | 16 + 33 files changed, 3064 insertions(+), 20 deletions(-) create mode 100644 docs/design/features/readytorun-preinitialized-statics.md create mode 100644 src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Dataflow/FlowAnnotations.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SerializedPreinitializationObjectDataNode.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypePreinitializationMapNode.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypePreinitializedStaticsDataNode.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/DelegateCreationInfo.ReadyToRun.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/PreinitializationExtensions.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/ReadyToRunPreinitializationManager.cs diff --git a/docs/design/features/readytorun-preinitialized-statics.md b/docs/design/features/readytorun-preinitialized-statics.md new file mode 100644 index 00000000000000..938a19417846c9 --- /dev/null +++ b/docs/design/features/readytorun-preinitialized-statics.md @@ -0,0 +1,200 @@ +# ReadyToRun Preinitialized Statics + +Author: Enze He ([@hez2010](https://github.com/hez2010)) - 2026 + +## Instruction + +Preinitialized statics feature was added in R2R format version **18.2**. + +ReadyToRun now can interpret eligible `.cctor` bodies at compile time, serialize the resulting static state into the R2R image, and mark types as preinitialized so runtime class-init can be skipped. + +The interpreter reuses the one in ilc so that any shape of `.cctor` that ilc can preinitialize is also supported in R2R, with some additional constraints. There're various limitations on what can be preinitialized, and the supported scenarios are listed as follows. + +| Scenario | Support | +| --- | --- | +| Non-GC statics | Supported | +| GC statics | Supported, including object-graph materialization from serialized templates | +| Generic instantiations | Concrete, non-canonical instantiations that can be statically resolved | +| Delegates | Supported for closed delegates | + +## Enabling + +In crossgen2, preinitialized statics can be controlled with: + +- `--preinitstatics`: enable compile-time interpretation of eligible static constructors. +- `--nopreinitstatics`: disable preinitialized statics even when optimization would otherwise enable it. + +When optimization is enabled, preinitialized statics is enabled by default unless explicitly disabled via `--nopreinitstatics`. + +## Native format + +A new optional section `TypePreinitializationMap` has been added to the R2R image: + +```cpp +enum class ReadyToRunTypePreinitializationFlags : uint32_t +{ + None = 0x0, + TypeIsPreinitialized = 0x1, +}; + +struct READYTORUN_TYPE_PREINITIALIZATION_MAP_ENTRY +{ + DWORD TypeDefRid; + + union + { + struct + { + DWORD Index; + DWORD Count; + } Instantiation; + + struct + { + DWORD Rva; + DWORD Size; + } NonGCData; + }; + + ReadyToRunTypePreinitializationFlags Flags; +}; + +struct READYTORUN_TYPE_PREINITIALIZATION_MAP_INSTANTIATION_ENTRY +{ + DWORD TypeSignatureOffset; + DWORD TypeSignatureLength; + + DWORD NonGCDataRva; + DWORD NonGCDataSize; + ReadyToRunTypePreinitializationFlags Flags; +}; +``` + +Section payload emitted by `TypePreinitializationMapNode`: + +1. `uint32 TypeCount` +2. `TypeCount * READYTORUN_TYPE_PREINITIALIZATION_MAP_ENTRY` +3. `uint32 InstantiationEntryCount` +4. `InstantiationEntryCount * READYTORUN_TYPE_PREINITIALIZATION_MAP_INSTANTIATION_ENTRY` in TypeDef-order +5. Concatenated instantiation type-signature blob bytes + +The section contains two tables: a TypeDef table and an instantiation table. For each TypeDef row, payload fields are interpreted as either `NonGCData.Rva/Size` (non-generic definition) or `Instantiation.Index/Count` (generic definition). Generic definitions do not have their own statics storage. + +TypeDef rows are sorted by `TypeDefRid`. Instantiation rows are sorted first by owner `TypeDefRid`, then by lexicographic signature bytes. + +Runtime binary-searches TypeDef rows by `TypeDefRid`, then uses `Instantiation.Index`/`Instantiation.Count` to linearly compare signatures in that range. + +## Preinitialized static payload format + +Per-type static payload is emitted by `TypePreinitializedStaticsDataNode`. + +Payload layout: + +```text +[ Non-GC static bytes ][ padding to pointer alignment ][ GC static handle slots ] +``` + +The RVA and size of the non-GC static region is recorded in the map, and the GC static region immediately follows the non-GC region with padding to ensure pointer alignment. + +Runtime derives GC payload size from metadata (`GetNumHandleRegularStatics() * sizeof(TADDR)`), not from the map. + +For GC statics whose field type is a value type (boxed GC statics), the serialized payload stores a pointer to a serialized boxed object template. + +## Fixup encoding format + +### Fixup signature blob header + +For each import signature (`SignatureBuilder.EmitFixup`): + +1. Emit 1-byte `kind`. +2. If the target module is not the local context, set `kind |= READYTORUN_FIXUP_ModuleOverride (0x80)` and emit compressed `uint moduleIndex`. + +At runtime, the resolver can determine the fixup kind and target module from the signature blob header before decoding the rest of the signature. + +### Fixup payloads used in preinitialization + +| Fixup kind | Payload | Purpose | +| --- | --- | --- | +| `READYTORUN_FIXUP_TypeHandle` | Encoded type signature | Method table / runtime type references in serialized templates | +| `READYTORUN_FIXUP_StringHandle` | Compressed string token RID | Preinitialized string references | +| `READYTORUN_FIXUP_MethodEntry` plus optimized forms | Method-signature encoding (`ReadyToRunMethodSigFlags`, token RID, optional instantiation) | Delegate targets and function-pointer-like values | + +For method entry imports, compact optimized encodings are only used when unboxing/constrained metadata is not required. + +### Import cell addressing and addend encoding + +Serialized payload pointers can represent either a template-object address in R2R data or an import-cell address with an addend. + +Runtime resolver (in `TryResolveReadyToRunImportCellAddress`): + +1. Validate that the encoded address is inside an R2R import section. +2. Compute `importIndex` and `importDelta` from section `EntrySize`. +3. Ensure the cell is fixed up. +4. Return `resolved = importCellValue + importDelta`. + +This allows payload fields to encode "base + offset" against import cells. + +## Implementation details + +### Record creation and dependency rooting + +`ReadyToRunPreinitializationManager.GetTypeRecord(type)` computes and caches whether a type is preinitialized, the non-GC payload size, an optional statics payload node, and an optional failure reason. If a record has emitted static payload data, that payload node is rooted in the dependency graph. + +This is best-effort: if serialization/validation of the preinitialized graph fails (for example, unsupported layout or invalid serialized shape), the type is downgraded to non-preinitialized and the failure reason is recorded for diagnostics/statistics. + +### Generic instantiation coverage + +Generic instantiations are supported on a best-effort basis. Only concrete, non-canonical, non-runtime-determined instantiations that are statically referenced from the code are recorded in the map and have preinitialization support. + +To ensure the map covers needed generic instantiations, static-base helper paths trigger record materialization in `GenericLookupResult`, `ReadyToRunGenericHelperNode`, and `ReadyToRunSymbolNodeFactory`. + +### Serialized object templates + +Object graphs for GC statics are emitted as templates where the first pointer is the object type handle/method table fixup and subsequent bytes encode fields/elements. Reference fields are emitted via relocations to other serialized templates, string imports, or runtime-type imports. Pointer-like non-reference fields can carry encoded import pointers. + +Delegate serialization in R2R mode supports both closed static and closed instance delegates. Open static delegates are rejected due to no available token, as emitted IL stub is not an EcmaMethod; and open instance delegates are currently not implemented. + +### Loader and map attachment + +The runtime locates TypePreinitializationMap (124) section and loads it to `Module`. + +`Module` exposes lookup helpers: + +| Helper | Purpose | +| --- | --- | +| `IsReadyToRunTypePreinitialized` | Query preinitialized flag for a type | +| `TryGetReadyToRunPreinitializedNonGCStaticsData` | Resolve non-GC payload pointer/size | +| `TryGetReadyToRunPreinitializedGCStaticsData` | Resolve GC payload pointer/size | + +These runtime lookups apply to ReadyToRun method tables owned by the same module and not shared canonical generic instantiations. + +### Static allocation + +`MethodTable::EnsureStaticDataAllocated` keeps existing allocation behavior and then conditionally applies preinitialized data: + +1. Non-GC bytes are copied when available and size-compatible. Pointers within the non-GC region (including those nested in value types) are fixed up by resolving R2R import cells. +2. GC static handles are materialized from the preinitialized GC region when available and size-compatible. + +Before applying data, runtime validates map lookups, expected payload sizes, and image-bounds safety checks. + +### Class-init skipping + +`MethodTable::IsInitedIfStaticDataAllocated` can now return true even for types that have a cctor when the map marks the type as preinitialized and both non-GC and GC payload sizes match runtime layout expectations. + +This allows class-init checks to be skipped for eligible types. Compilation will also be skipped for preinitialized types, and call sites will directly reference the target addresses without cctor triggers. + +### Materialization + +The materialization is done in several passes: + +1. Non-GC static bytes are copied and fixed up. +2. GC static objects are allocated and initialized from the preinitialized GC region. +3. Nested value-type fields are handled recursively. + +A cache of materialized objects is used to ensure object identity is preserved for reference fields that point to the same template object. + +When decoding encoded references in GC payloads, runtime currently accepts import-based references for strings and runtime type objects (`READYTORUN_FIXUP_StringHandle`, `READYTORUN_FIXUP_TypeHandle`, and `READYTORUN_FIXUP_TypeDictionary`), otherwise the payload is rejected as invalid. + +### GC object allocation strategy + +We may be able to allocate GC objects in Frozen Object Heap, but it comes with the complication of collectible vs non-collectible types, as well as the fact that frozen objects cannot reference non-frozen objects. For simplicity, the initial implementation allocates GC statics in regular GC heap, and we can explore frozen heap support in the future if needed. diff --git a/src/coreclr/inc/readytorun.h b/src/coreclr/inc/readytorun.h index 7956fa7b77d0cb..23815c896f7cee 100644 --- a/src/coreclr/inc/readytorun.h +++ b/src/coreclr/inc/readytorun.h @@ -20,7 +20,7 @@ // If you update this, ensure you run `git grep MINIMUM_READYTORUN_MAJOR_VERSION` // and handle pending work. #define READYTORUN_MAJOR_VERSION 18 -#define READYTORUN_MINOR_VERSION 0x0001 +#define READYTORUN_MINOR_VERSION 0x0002 #define MINIMUM_READYTORUN_MAJOR_VERSION 18 @@ -52,6 +52,7 @@ // R2R 17 is not backward compatible with 16.x or earlier. // R2R Version 17.1 adds the READYTORUN_FLAG_PLATFORM_NATIVE_IMAGE flag to specify that the R2R image pointed to by OwnerCompositeExecutable is in the platform native format. // R2R Version 18 updates fields layout algorithm +// R2R Version 18.2 adds preinitialized statics map support struct READYTORUN_CORE_HEADER { @@ -117,6 +118,7 @@ enum class ReadyToRunSectionType : uint32_t MethodIsGenericMap = 121, // Added in V9.0 EnclosingTypeMap = 122, // Added in V9.0 TypeGenericInfoMap = 123, // Added in V9.0 + TypePreinitializationMap = 124, // Added in V18.2 // If you add a new section consider whether it is a breaking or non-breaking change. // Usually it is non-breaking, but if it is preferable to have older runtimes fail @@ -168,6 +170,43 @@ enum class ReadyToRunEnclosingTypeMap MaxTypeCount = 0xFFFE }; +enum class ReadyToRunTypePreinitializationFlags : uint32_t +{ + None = 0x0, + TypeIsPreinitialized = 0x1, +}; + +struct READYTORUN_TYPE_PREINITIALIZATION_MAP_ENTRY +{ + DWORD TypeDefRid; + union + { + struct + { + DWORD Index; + DWORD Count; + } Instantiation; + + struct + { + DWORD Rva; + DWORD Size; + } NonGCData; + }; + + ReadyToRunTypePreinitializationFlags Flags; +}; + +struct READYTORUN_TYPE_PREINITIALIZATION_MAP_INSTANTIATION_ENTRY +{ + DWORD TypeSignatureOffset; + DWORD TypeSignatureLength; + DWORD NonGCDataRva; + DWORD NonGCDataSize; + + ReadyToRunTypePreinitializationFlags Flags; +}; + // // READYTORUN_IMPORT_SECTION describes image range with references to code or runtime data structures // diff --git a/src/coreclr/jit/helperexpansion.cpp b/src/coreclr/jit/helperexpansion.cpp index 8bb9f8feb2a147..f290f9ca9fe4b8 100644 --- a/src/coreclr/jit/helperexpansion.cpp +++ b/src/coreclr/jit/helperexpansion.cpp @@ -1436,7 +1436,7 @@ bool Compiler::fgExpandStaticInitForCall(BasicBlock** pBlock, Statement* stmt, G // | \--* CNS_INT int -8 (offset) // \--* CNS_INT int 0 // - assert(flagAddr.accessType == IAT_VALUE); + assert((flagAddr.accessType == IAT_VALUE) || (flagAddr.accessType == IAT_PVALUE)); GenTree* cachedStaticBase = nullptr; GenTree* isInitedActualValueNode; @@ -1463,7 +1463,17 @@ bool Compiler::fgExpandStaticInitForCall(BasicBlock** pBlock, Statement* stmt, G { assert(isInitOffset == 0); - isInitedActualValueNode = gtNewIndOfIconHandleNode(TYP_INT, (size_t)flagAddr.addr, GTF_ICON_GLOBAL_PTR); + if (flagAddr.accessType == IAT_VALUE) + { + isInitedActualValueNode = gtNewIndOfIconHandleNode(TYP_INT, (size_t)flagAddr.addr, GTF_ICON_GLOBAL_PTR); + } + else + { + assert(flagAddr.accessType == IAT_PVALUE); + GenTree* flagAddrNode = gtNewIndOfIconHandleNode(TYP_I_IMPL, (size_t)flagAddr.addr, GTF_ICON_GLOBAL_PTR); + isInitedActualValueNode = gtNewIndir(TYP_INT, flagAddrNode, GTF_IND_NONFAULTING); + } + isInitedActualValueNode->gtFlags |= GTF_IND_VOLATILE; isInitedActualValueNode->SetHasOrderingSideEffect(); diff --git a/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h b/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h index 13eeb69f898ec5..804c5080e50f0e 100644 --- a/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h +++ b/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h @@ -12,7 +12,7 @@ struct ReadyToRunHeaderConstants static const uint32_t Signature = 0x00525452; // 'RTR' static const uint32_t CurrentMajorVersion = 18; - static const uint32_t CurrentMinorVersion = 1; + static const uint32_t CurrentMinorVersion = 2; }; struct ReadyToRunHeader diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index a82fe2cf094886..2db215fff1c2f9 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -375,7 +375,7 @@ public virtual void EmitObject(Stream outputFileStream, IReadOnlyCollection symbolRangeNodes = []; @@ -768,7 +768,7 @@ private struct ProgressReporter public ProgressReporter(Logger logger, int total) { _logger = logger; - _increment = total / Steps; + _increment = int.Max(1, total / Steps); _current = 0; } diff --git a/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs b/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs index 0e849fb950ae3e..95daa7e30d7117 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs @@ -16,7 +16,7 @@ internal struct ReadyToRunHeaderConstants public const uint Signature = 0x00525452; // 'RTR' public const ushort CurrentMajorVersion = 18; - public const ushort CurrentMinorVersion = 1; + public const ushort CurrentMinorVersion = 2; } #if READYTORUN #pragma warning disable 0169 @@ -79,6 +79,7 @@ enum ReadyToRunSectionType MethodIsGenericMap = 121, // Added in V9.0 EnclosingTypeMap = 122, // Added in V9.0 TypeGenericInfoMap = 123, // Added in V9.0 + TypePreinitializationMap = 124, // Added in V18.2 // // NativeAOT ReadyToRun sections diff --git a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs index f678e38bb1cf0b..bca995c812abe3 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs @@ -196,6 +196,13 @@ public enum ReadyToRunFixupKind // (used in cases inlining brings in references to assemblies not seen in the MSIL). } + [Flags] + public enum ReadyToRunTypePreinitializationFlags : uint + { + None = 0x0, + TypeIsPreinitialized = 0x1, + } + // // Intrinsics and helpers // Keep in sync with https://github.com/dotnet/runtime/blob/main/src/coreclr/inc/readytorun.h diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/PreinitializationManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/PreinitializationManager.cs index f885f77bc60894..14d525d53935c8 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/PreinitializationManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/PreinitializationManager.cs @@ -6,7 +6,11 @@ using Internal.IL; using Internal.TypeSystem; +#if READYTORUN +using FlowAnnotations = ILCompiler.FlowAnnotations; +#else using FlowAnnotations = ILLink.Shared.TrimAnalysis.FlowAnnotations; +#endif namespace ILCompiler { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs index d2f63bdc3c0f0b..3b6dade84fdeb4 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs @@ -13,7 +13,11 @@ using Internal.TypeSystem.Ecma; using CombinedDependencyList = System.Collections.Generic.List.CombinedDependencyListEntry>; +#if READYTORUN +using FlowAnnotations = ILCompiler.FlowAnnotations; +#else using FlowAnnotations = ILLink.Shared.TrimAnalysis.FlowAnnotations; +#endif namespace ILCompiler { @@ -3247,7 +3251,7 @@ public override void GetConditionalDependencies(ref CombinedDependencyList depen public void WriteContent(ref ObjectDataBuilder builder, ISymbolNode thisNode, NodeFactory factory) { - Debug.Assert(_methodPointed.Signature.IsStatic == (_firstParameter == null)); + Debug.Assert(_methodPointed.Signature.IsStatic || _firstParameter != null); DelegateCreationInfo creationInfo = GetDelegateCreationInfo(factory); @@ -3255,9 +3259,47 @@ public void WriteContent(ref ObjectDataBuilder builder, ISymbolNode thisNode, No // MethodTable var node = factory.ConstructedTypeSymbol(Type); +#if !READYTORUN Debug.Assert(!node.RepresentsIndirectionCell); // Shouldn't have allowed this +#endif builder.EmitPointerReloc(node); +#if READYTORUN + // CoreCLR delegate layout: + // Delegate: _target, _methodBase, _methodPtr, _methodPtrAux + // MulticastDelegate: _invocationList, _invocationCount + if (_methodPointed.Signature.IsStatic) + { + if (_firstParameter == null) + { + // Open static delegate. + builder.EmitPointerReloc(thisNode); // _target + builder.EmitZeroPointer(); // _methodBase + Debug.Assert(creationInfo.Thunk != null); + builder.EmitPointerReloc(creationInfo.Thunk); // _methodPtr + builder.EmitPointerReloc(creationInfo.GetTargetNode(factory)); // _methodPtrAux + } + else + { + // Closed static delegate. + _firstParameter.WriteFieldData(ref builder, factory); // _target + builder.EmitZeroPointer(); // _methodBase + builder.EmitPointerReloc(creationInfo.GetTargetNode(factory)); // _methodPtr + builder.EmitZeroPointer(); // _methodPtrAux + } + } + else + { + // Closed instance delegate. + _firstParameter.WriteFieldData(ref builder, factory); // _target + builder.EmitZeroPointer(); // _methodBase + builder.EmitPointerReloc(creationInfo.GetTargetNode(factory)); // _methodPtr + builder.EmitZeroPointer(); // _methodPtrAux + } + + builder.EmitZeroPointer(); // _invocationList + builder.EmitZeroPointer(); // _invocationCount +#else if (_methodPointed.Signature.IsStatic) { Debug.Assert(creationInfo.Constructor.Method.Name.SequenceEqual("InitializeOpenStaticThunk"u8)); @@ -3291,6 +3333,7 @@ public void WriteContent(ref ObjectDataBuilder builder, ISymbolNode thisNode, No // _functionPointer builder.EmitPointerReloc(creationInfo.GetTargetNode(factory)); } +#endif } public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory) @@ -3370,7 +3413,9 @@ public void WriteContent(ref ObjectDataBuilder builder, ISymbolNode thisNode, No { // MethodTable var node = factory.ConstructedTypeSymbol(Type); +#if !READYTORUN Debug.Assert(!node.RepresentsIndirectionCell); // Arrays are always local +#endif builder.EmitPointerReloc(node); // numComponents @@ -3551,7 +3596,9 @@ public void WriteContent(ref ObjectDataBuilder builder, ISymbolNode thisNode, No { // MethodTable var node = factory.ConstructedTypeSymbol(Type); +#if !READYTORUN Debug.Assert(!node.RepresentsIndirectionCell); // Shouldn't have allowed preinitializing this +#endif builder.EmitPointerReloc(node); // We skip the first pointer because that's the MethodTable pointer @@ -3708,11 +3755,11 @@ public bool TryGetFieldValue(TypePreinit context, FieldDesc field, out Value val public class PreinitializationInfo { - private readonly Dictionary _fieldValues; + private Dictionary _fieldValues; public MetadataType Type { get; } - public string FailureReason { get; } + public string FailureReason { get; private set; } public bool IsPreinitialized => _fieldValues != null; @@ -3737,6 +3784,14 @@ public ISerializableValue GetFieldValue(FieldDesc field) Debug.Assert(field.IsStatic && !field.HasRva && !field.IsThreadStatic && !field.IsLiteral); return _fieldValues[field]; } + + public void SetPostScanFailure(string failureReason) + { + if (!IsPreinitialized) return; + + FailureReason = failureReason; + _fieldValues = null; + } } public abstract class TypePreinitializationPolicy diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Dataflow/FlowAnnotations.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Dataflow/FlowAnnotations.cs new file mode 100644 index 00000000000000..8cfe3639741140 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Dataflow/FlowAnnotations.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Internal.TypeSystem; + +namespace ILCompiler +{ + public sealed class FlowAnnotations + { + public bool RequiresDataflowAnalysisDueToSignature(FieldDesc field) => false; + + public bool RequiresDataflowAnalysisDueToSignature(MethodDesc method) => false; + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs index e117bb2fb3be87..111f13c36dc3cc 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs @@ -90,7 +90,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) // Optimize some of the fixups into a more compact form ReadyToRunFixupKind fixupKind = _fixupKind; bool optimized = false; - if (_method.Method.IsPrimaryMethodDesc() && !IsInstantiatingStub + if (_method.Method.IsPrimaryMethodDesc() && !IsInstantiatingStub && !_method.Unboxing && _method.ConstrainedType == null && fixupKind == ReadyToRunFixupKind.MethodEntry) { if (!_method.Method.HasInstantiation && !_method.Method.OwningType.HasInstantiation && !_method.Method.OwningType.IsArray) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SerializedPreinitializationObjectDataNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SerializedPreinitializationObjectDataNode.cs new file mode 100644 index 00000000000000..8c75033ce89245 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SerializedPreinitializationObjectDataNode.cs @@ -0,0 +1,80 @@ +// 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 Internal.Text; +using Internal.TypeSystem; + +using CombinedDependencyList = System.Collections.Generic.List.CombinedDependencyListEntry>; + +namespace ILCompiler.DependencyAnalysis.ReadyToRun +{ + internal sealed class SerializedPreinitializationObjectDataNode : ObjectNode, ISymbolDefinitionNode, ISortableSymbolNode + { + private readonly MetadataType _owningType; + private readonly TypePreinit.ISerializableReference _data; + private readonly int _allocationSiteId; + + public SerializedPreinitializationObjectDataNode(MetadataType owningType, int allocationSiteId, TypePreinit.ISerializableReference data) + { + _owningType = owningType; + _allocationSiteId = allocationSiteId; + _data = data; + } + + public int Offset => 0; + + public override bool StaticDependenciesAreComputed => true; + + public override bool HasConditionalStaticDependencies => _data.HasConditionalDependencies; + + public override IEnumerable GetConditionalStaticDependencies(NodeFactory factory) + { + CombinedDependencyList result = null; + _data.GetConditionalDependencies(ref result, factory); + return result; + } + + protected override string GetName(NodeFactory factory) + { + Utf8StringBuilder sb = new Utf8StringBuilder(); + AppendMangledName(factory.NameMangler, sb); + return sb.ToString(); + } + + public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) + { + sb.Append(nameMangler.CompilationUnitPrefix).Append("__PreInitObj_"u8) + .Append(nameMangler.GetMangledTypeName(_owningType)) + .Append(_allocationSiteId.ToString()); + } + + public override ObjectNodeSection GetSection(NodeFactory factory) + => factory.Target.IsWindows ? ObjectNodeSection.ReadOnlyDataSection : ObjectNodeSection.DataSection; + + public override bool IsShareable => false; + + public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) + { + ObjectDataBuilder builder = new ObjectDataBuilder(factory, relocsOnly); + builder.RequireInitialPointerAlignment(); + builder.AddSymbol(this); + _data.WriteContent(ref builder, this, factory); + return builder.ToObjectData(); + } + + public override int ClassCode => 214568742; + + public override int CompareToImpl(ISortableNode other, CompilerComparer comparer) + { + SerializedPreinitializationObjectDataNode otherNode = (SerializedPreinitializationObjectDataNode)other; + + int result = comparer.Compare(_owningType, otherNode._owningType); + if (result != 0) + return result; + + return _allocationSiteId.CompareTo(otherNode._allocationSiteId); + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypePreinitializationMapNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypePreinitializationMapNode.cs new file mode 100644 index 00000000000000..0492c08fb2a183 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypePreinitializationMapNode.cs @@ -0,0 +1,178 @@ +// 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.Diagnostics; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +using Internal.ReadyToRunConstants; +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; + +namespace ILCompiler.DependencyAnalysis.ReadyToRun +{ + internal sealed class TypePreinitializationMapNode : ModuleSpecificHeaderTableNode + { + private readonly MetadataReader _metadata; + + public TypePreinitializationMapNode(EcmaModule module) + : base(module) + { + _metadata = module.MetadataReader; + } + + public override int ClassCode => -1815494040; + + protected override string ModuleSpecificName => "__TypePreinitializationMap__"; + + public override bool ShouldSkipEmittingObjectNode(NodeFactory factory) + => !factory.ReadyToRunPreinitializationManager.HasAnyPreinitializedTypesInModule(_module); + + private readonly struct InstantiatedTypeEntry + { + public InstantiatedTypeEntry( + uint typeDefRid, + byte[] typeSignature, + ReadyToRunPreinitializationManager.TypePreinitializationRecord record) + { + TypeDefRid = typeDefRid; + TypeSignature = typeSignature; + Record = record; + } + + public uint TypeDefRid { get; } + public byte[] TypeSignature { get; } + public ReadyToRunPreinitializationManager.TypePreinitializationRecord Record { get; } + } + + private static int CompareInstantiatedTypeEntries(InstantiatedTypeEntry x, InstantiatedTypeEntry y) + { + int ridCompare = x.TypeDefRid.CompareTo(y.TypeDefRid); + if (ridCompare != 0) + return ridCompare; + + int minLength = x.TypeSignature.Length < y.TypeSignature.Length ? x.TypeSignature.Length : y.TypeSignature.Length; + for (int i = 0; i < minLength; i++) + { + int byteCompare = x.TypeSignature[i].CompareTo(y.TypeSignature[i]); + if (byteCompare != 0) + return byteCompare; + } + + return x.TypeSignature.Length.CompareTo(y.TypeSignature.Length); + } + + public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) + { + ObjectDataBuilder builder = new ObjectDataBuilder(factory, relocsOnly); + builder.AddSymbol(this); + + uint typeCount = (uint)_metadata.TypeDefinitions.Count; + + List instantiatedTypeEntries = new(); + foreach (KeyValuePair knownEntry in + factory.ReadyToRunPreinitializationManager.GetKnownInstantiatedTypeRecordsForModule(_module)) + { + MetadataType instantiatedType = knownEntry.Key; + ReadyToRunPreinitializationManager.TypePreinitializationRecord record = knownEntry.Value; + + if (instantiatedType.GetTypeDefinition() is not EcmaType ecmaTypeDefinition) + continue; + + uint typeDefRid = (uint)MetadataTokens.GetRowNumber(ecmaTypeDefinition.Handle); + + ArraySignatureBuilder signatureBuilder = new ArraySignatureBuilder(); + signatureBuilder.EmitTypeSignature(instantiatedType, factory.SignatureContext); + + instantiatedTypeEntries.Add(new InstantiatedTypeEntry( + typeDefRid, + signatureBuilder.ToArray(), + record)); + } + + instantiatedTypeEntries.Sort(CompareInstantiatedTypeEntries); + + uint[] instantiationCountsByTypeDefRid = new uint[typeCount + 1]; + foreach (InstantiatedTypeEntry entry in instantiatedTypeEntries) + { + if (entry.TypeDefRid <= typeCount) + instantiationCountsByTypeDefRid[(int)entry.TypeDefRid]++; + } + + uint[] instantiationOffsetsByTypeDefRid = new uint[typeCount + 1]; + uint runningInstantiationOffset = 0; + for (uint rid = 1; rid <= typeCount; rid++) + { + instantiationOffsetsByTypeDefRid[(int)rid] = runningInstantiationOffset; + runningInstantiationOffset += instantiationCountsByTypeDefRid[(int)rid]; + } + + builder.EmitUInt(typeCount); + + foreach (TypeDefinitionHandle typeHandle in _metadata.TypeDefinitions) + { + MetadataType type = (MetadataType)_module.GetType(typeHandle); + var record = factory.ReadyToRunPreinitializationManager.GetTypeRecord(type); + if (factory.PreinitializationManager.IsPreinitialized(type) && !record.IsPreinitialized) + { + factory.PreinitializationManager.GetPreinitializationInfo(type).SetPostScanFailure(record.FailureReason); + } + + uint typeDefRid = (uint)MetadataTokens.GetRowNumber(typeHandle); + + // TypeDef row: READYTORUN_TYPE_PREINITIALIZATION_MAP_ENTRY + builder.EmitUInt(typeDefRid); + if (type.HasInstantiation) + { + Debug.Assert(record.StaticsDataNode == null); + builder.EmitUInt(instantiationOffsetsByTypeDefRid[(int)typeDefRid]); + builder.EmitUInt(instantiationCountsByTypeDefRid[(int)typeDefRid]); + } + else + { + if (record.StaticsDataNode != null) + builder.EmitReloc(record.StaticsDataNode, RelocType.IMAGE_REL_BASED_ADDR32NB); + else + builder.EmitUInt(0); + + builder.EmitUInt((uint)record.NonGCDataSize); + } + + uint flags = record.IsPreinitialized ? (uint)ReadyToRunTypePreinitializationFlags.TypeIsPreinitialized : 0; + builder.EmitUInt(flags); + } + + builder.EmitUInt((uint)instantiatedTypeEntries.Count); + + uint currentSignatureOffset = 0; + foreach (InstantiatedTypeEntry entry in instantiatedTypeEntries) + { + // Instantiation row: READYTORUN_TYPE_PREINITIALIZATION_MAP_INSTANTIATION_ENTRY + builder.EmitUInt(currentSignatureOffset); + builder.EmitUInt((uint)entry.TypeSignature.Length); + + if (entry.Record.StaticsDataNode != null) + builder.EmitReloc(entry.Record.StaticsDataNode, RelocType.IMAGE_REL_BASED_ADDR32NB); + else + builder.EmitUInt(0); + + builder.EmitUInt((uint)entry.Record.NonGCDataSize); + + uint flags = entry.Record.IsPreinitialized ? (uint)ReadyToRunTypePreinitializationFlags.TypeIsPreinitialized : 0; + builder.EmitUInt(flags); + + currentSignatureOffset += (uint)entry.TypeSignature.Length; + } + + // Type signatures + foreach (InstantiatedTypeEntry entry in instantiatedTypeEntries) + { + foreach (byte b in entry.TypeSignature) + builder.EmitByte(b); + } + + return builder.ToObjectData(); + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypePreinitializedStaticsDataNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypePreinitializedStaticsDataNode.cs new file mode 100644 index 00000000000000..88200b24aa371f --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypePreinitializedStaticsDataNode.cs @@ -0,0 +1,295 @@ +// 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.Text; +using Internal.TypeSystem; + +using Debug = System.Diagnostics.Debug; +using CombinedDependencyList = System.Collections.Generic.List.CombinedDependencyListEntry>; + +namespace ILCompiler.DependencyAnalysis.ReadyToRun +{ + // Emits the preinitialized static payload for a single type: + // - non-GC static bytes + // - pointer-aligned GC static reference slots + internal sealed class TypePreinitializedStaticsDataNode : ObjectNode, ISymbolDefinitionNode, ISortableSymbolNode + { + private readonly TypePreinit.PreinitializationInfo _preinitializationInfo; + + public TypePreinitializedStaticsDataNode(TypePreinit.PreinitializationInfo preinitializationInfo) + { + Debug.Assert(preinitializationInfo.IsPreinitialized); + Debug.Assert(!preinitializationInfo.Type.IsCanonicalSubtype(CanonicalFormKind.Specific)); + _preinitializationInfo = preinitializationInfo; + } + + public MetadataType Type => _preinitializationInfo.Type; + + private static int ComputeStaticDataSize( + MetadataType type, + Predicate predicate, + Func getSerializedFieldSize, + int baseOffset) + { + int size = 0; + foreach (FieldDesc field in type.GetFields()) + { + if (!predicate(field)) + continue; + + int fieldOffset = field.Offset.AsInt - baseOffset; + if (fieldOffset < 0) + throw new NotSupportedException($"Negative static field offset is not supported for preinitialized type '{type}'."); + + int fieldSize = getSerializedFieldSize(field); + int fieldEnd = checked(fieldOffset + fieldSize); + if (fieldEnd > size) + size = fieldEnd; + } + + return size; + } + + public static int ComputeNonGCStaticsDataSize(MetadataType type) + => ComputeStaticDataSize( + type, + IsNonGCStaticField, + static field => field.FieldType.GetElementSize().AsInt, + baseOffset: 0); + + public int NonGCStaticsDataSize => ComputeNonGCStaticsDataSize(Type); + + public static int ComputeGCStaticsDataSize(MetadataType type) + => ComputeStaticDataSize( + type, + IsGCStaticField, + field => GetGCStaticFieldSerializedSize(field, type.Context), + baseOffset: 0); + + public int GCStaticsDataSize(TypeSystemContext context) + => ComputeGCStaticsDataSize(Type); + + public int AlignedNonGCStaticsDataSize(TargetDetails target) + => AlignmentHelper.AlignUp(NonGCStaticsDataSize, target.PointerSize); + + public static bool IsNonGCStaticField(FieldDesc field) + => field.IsStatic && !field.HasRva && !field.IsLiteral && !field.IsThreadStatic && !field.HasGCStaticBase; + + public static bool IsGCStaticField(FieldDesc field) + => field.IsStatic && !field.HasRva && !field.IsLiteral && !field.IsThreadStatic && field.HasGCStaticBase; + + private static bool IsBoxedGCStaticField(FieldDesc field) + => IsGCStaticField(field) && field.FieldType.IsValueType; + + private static int GetGCStaticFieldSerializedSize(FieldDesc field, TypeSystemContext context) + => IsBoxedGCStaticField(field) ? context.Target.PointerSize : field.FieldType.GetElementSize().AsInt; + + // Synthesize a unique allocation site ID for each boxed static field based on its ordinal + // Starting from int.MinValue to avoid collisions with allocation sites from instruction pointer + private static int GetBoxedStaticFieldAllocationSiteId(int boxedStaticFieldOrdinal) + => unchecked(int.MinValue + boxedStaticFieldOrdinal); + + protected override string GetName(NodeFactory factory) + { + Utf8StringBuilder sb = new Utf8StringBuilder(); + AppendMangledName(factory.NameMangler, sb); + return sb.ToString(); + } + + public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) + { + sb.Append(nameMangler.CompilationUnitPrefix); + sb.Append("__PreInitStaticsData_"u8); + sb.Append(nameMangler.GetMangledTypeName(Type)); + } + + public int Offset => 0; + + public override bool StaticDependenciesAreComputed => true; + + public override ObjectNodeSection GetSection(NodeFactory factory) + { + if (factory.Target.IsWindows) + return ObjectNodeSection.ReadOnlyDataSection; + + return ObjectNodeSection.DataSection; + } + + public override bool IsShareable => false; + + private static List GetOrderedStaticFields(MetadataType type, Predicate predicate) + { + List fields = new(); + foreach (FieldDesc field in type.GetFields()) + { + if (predicate(field)) + fields.Add(field); + } + + fields.Sort(static (a, b) => a.Offset.AsInt.CompareTo(b.Offset.AsInt)); + return fields; + } + + public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) + { + ObjectDataBuilder builder = new ObjectDataBuilder(factory, relocsOnly); + int requiredAlignment = Type.NonGCStaticFieldAlignment.AsInt; + if (requiredAlignment < 1) + requiredAlignment = 1; + if (GCStaticsDataSize(factory.TypeSystemContext) > 0 && requiredAlignment < factory.Target.PointerSize) + requiredAlignment = factory.Target.PointerSize; + builder.RequireInitialAlignment(requiredAlignment); + builder.AddSymbol(this); + + int initialOffset = builder.CountBytes; + foreach (FieldDesc field in GetOrderedStaticFields(Type, IsNonGCStaticField)) + { + int padding = field.Offset.AsInt - builder.CountBytes + initialOffset; + if (padding < 0) + throw new NotSupportedException($"Overlapping non-GC static layout is not supported for preinitialized type '{Type}'."); + builder.EmitZeros(padding); + + TypePreinit.ISerializableValue value = _preinitializationInfo.GetFieldValue(field); + int currentOffset = builder.CountBytes; + value.WriteFieldData(ref builder, factory); + Debug.Assert(builder.CountBytes - currentOffset == field.FieldType.GetElementSize().AsInt); + } + + int nonGCPad = NonGCStaticsDataSize - builder.CountBytes + initialOffset; + Debug.Assert(nonGCPad >= 0); + builder.EmitZeros(nonGCPad); + + int gcDataSize = GCStaticsDataSize(factory.TypeSystemContext); + if (gcDataSize > 0) + { + // GC static payload is read as pointer-sized slots at runtime. + int alignedNonGCDataSize = AlignedNonGCStaticsDataSize(factory.Target); + int currentNonGCDataSize = builder.CountBytes - initialOffset; + if (currentNonGCDataSize < alignedNonGCDataSize) + builder.EmitZeros(alignedNonGCDataSize - currentNonGCDataSize); + + int gcInitialOffset = 0; + int gcStartOffset = builder.CountBytes; + int boxedStaticFieldOrdinal = 0; + + foreach (FieldDesc field in GetOrderedStaticFields(Type, IsGCStaticField)) + { + int padding = field.Offset.AsInt - gcInitialOffset - (builder.CountBytes - gcStartOffset); + if (padding < 0) + throw new NotSupportedException($"Overlapping GC static layout is not supported for preinitialized type '{Type}'."); + builder.EmitZeros(padding); + + TypePreinit.ISerializableValue value = _preinitializationInfo.GetFieldValue(field); + int currentOffset = builder.CountBytes; + EmitGCStaticFieldData(ref builder, factory, field, value, ref boxedStaticFieldOrdinal); + Debug.Assert(builder.CountBytes - currentOffset == GetGCStaticFieldSerializedSize(field, factory.TypeSystemContext)); + } + + int gcPad = gcDataSize - (builder.CountBytes - gcStartOffset); + Debug.Assert(gcPad >= 0); + builder.EmitZeros(gcPad); + } + + int totalSize = builder.CountBytes - initialOffset; + Debug.Assert(totalSize >= NonGCStaticsDataSize); + Debug.Assert(gcDataSize == 0 || totalSize >= AlignedNonGCStaticsDataSize(factory.Target) + gcDataSize); + Debug.Assert(totalSize >= 0); + return builder.ToObjectData(); + } + + private void EmitGCStaticFieldData( + ref ObjectDataBuilder builder, + NodeFactory factory, + FieldDesc field, + TypePreinit.ISerializableValue value, + ref int boxedStaticFieldOrdinal) + { + if (IsBoxedGCStaticField(field)) + { + if (value == null) + throw new NotSupportedException($"ReadyToRun preinitialized boxed static field '{field}' cannot be null."); + + if (field.FieldType is not DefType boxedValueType || !boxedValueType.IsValueType) + throw new NotSupportedException($"ReadyToRun preinitialized boxed static field '{field}' has unsupported type '{field.FieldType}'."); + + int allocationSiteId = GetBoxedStaticFieldAllocationSiteId(boxedStaticFieldOrdinal++); + var boxedValue = new BoxedStaticValueTypeReference(Type, allocationSiteId, boxedValueType, value); + boxedValue.WriteFieldData(ref builder, factory); + return; + } + + if (value != null) + value.WriteFieldData(ref builder, factory); + else + builder.EmitZeroPointer(); + } + + private sealed class BoxedStaticValueTypeReference : TypePreinit.ISerializableReference + { + private readonly MetadataType _owningType; + private readonly int _allocationSiteId; + private readonly DefType _boxedValueType; + private readonly TypePreinit.ISerializableValue _value; + + public BoxedStaticValueTypeReference( + MetadataType owningType, + int allocationSiteId, + DefType boxedValueType, + TypePreinit.ISerializableValue value) + { + Debug.Assert(boxedValueType.IsValueType); + _owningType = owningType; + _allocationSiteId = allocationSiteId; + _boxedValueType = boxedValueType; + _value = value; + } + + public TypeDesc Type => _boxedValueType; + + public bool HasConditionalDependencies => false; + + public bool IsKnownImmutable => false; + + public int ArrayLength => throw new NotSupportedException(); + + public void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory) + { + builder.EmitPointerReloc(factory.SerializedFrozenObject(_owningType, _allocationSiteId, this)); + } + + public bool GetRawData(NodeFactory factory, out object data) + { + data = null; + return false; + } + + public void GetConditionalDependencies(ref CombinedDependencyList dependencies, NodeFactory factory) + { + } + + public void WriteContent(ref ObjectDataBuilder builder, ISymbolNode thisNode, NodeFactory factory) + { + builder.EmitPointerReloc(factory.ConstructedTypeSymbol(_boxedValueType)); + + int dataStart = builder.CountBytes; + _value.WriteFieldData(ref builder, factory); + + int serializedDataSize = builder.CountBytes - dataStart; + int expectedDataSize = _boxedValueType.GetElementSize().AsInt; + if (serializedDataSize != expectedDataSize) + { + throw new NotSupportedException( + $"ReadyToRun preinitialized boxed static value type '{_boxedValueType}' has unsupported serialized size '{serializedDataSize}' (expected '{expectedDataSize}')."); + } + } + } + + public override int ClassCode => 2084515482; + + public override int CompareToImpl(ISortableNode other, CompilerComparer comparer) + => comparer.Compare(Type, ((TypePreinitializedStaticsDataNode)other).Type); + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs index a2a846481e3091..35b3b383a37ab9 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs @@ -69,6 +69,7 @@ public struct NodeFactoryOptimizationFlags public sealed class NodeFactory { private bool _markingComplete; + private DependencyAnalyzerBase _graph; public CompilerTypeSystemContext TypeSystemContext { get; } @@ -84,6 +85,10 @@ public sealed class NodeFactory public MetadataManager MetadataManager { get; } + public ILCompiler.PreinitializationManager PreinitializationManager { get; } + + internal ReadyToRunPreinitializationManager ReadyToRunPreinitializationManager { get; } + public CompositeImageSettings CompositeImageSettings { get; set; } public readonly NodeFactoryOptimizationFlags OptimizationFlags; @@ -94,6 +99,11 @@ public sealed class NodeFactory public bool MarkingComplete => _markingComplete; + internal void AddCompilationRoot(DependencyNodeCore node, string reason) + { + _graph?.AddRoot(node, reason); + } + public void GenerateHotColdMap(DependencyAnalyzerBase dependencyGraph) { if (HotColdMap == null) @@ -206,6 +216,7 @@ public NodeFactory( ReadyToRunContainerFormat format, ulong imageBase, EcmaModule associatedModule, + ILCompiler.PreinitializationManager preinitializationManager, int genericCycleDepthCutoff, int genericCycleBreadthCutoff) { OptimizationFlags = nodeFactoryOptimizationFlags; @@ -215,6 +226,8 @@ public NodeFactory( Target = context.Target; NameMangler = nameMangler; MetadataManager = new ReadyToRunTableManager(context); + PreinitializationManager = preinitializationManager; + ReadyToRunPreinitializationManager = new ReadyToRunPreinitializationManager(this); CopiedCorHeaderNode = corHeaderNode; DebugDirectoryNode = debugDirectoryNode; Resolver = compilationModuleGroup.Resolver; @@ -413,6 +426,8 @@ private void CreateNodeCaches() public ImportSectionNode StringImports; + public ImportSectionNode PreinitializationImports; + public ImportSectionNode HelperImports; public ImportSectionNode PrecodeImports; @@ -698,6 +713,8 @@ public ImportThunk ImportThunk(ReadyToRunHelper helper, ImportSectionNode contai public void AttachToDependencyGraph(DependencyAnalyzerBase graph, ILProvider ilProvider) { + _graph = graph; + graph.ComputingDependencyPhaseChange += Graph_ComputingDependencyPhaseChange; var compilerIdentifierNode = new CompilerIdentifierNode(Target); @@ -786,6 +803,9 @@ public void AttachToDependencyGraph(DependencyAnalyzerBase graph, I var node = new MethodIsGenericMapNode(inputModule); tableHeader.Add(Internal.Runtime.ReadyToRunSectionType.MethodIsGenericMap, node, node); } + + TypePreinitializationMapNode typePreinitializationMap = new TypePreinitializationMapNode(inputModule); + tableHeader.Add(Internal.Runtime.ReadyToRunSectionType.TypePreinitializationMap, typePreinitializationMap, typePreinitializationMap); } InliningInfoNode crossModuleInliningInfoTable = new InliningInfoNode(null, @@ -912,11 +932,21 @@ bool HasAnyProfileDataForInput() emitGCRefMap: false); ImportSectionsTable.AddEmbeddedObject(StringImports); + PreinitializationImports = new ImportSectionNode( + "PreinitializationImports", + ReadyToRunImportSectionType.Unknown, + ReadyToRunImportSectionFlags.None, + (byte)Target.PointerSize, + emitPrecode: false, + emitGCRefMap: false); + ImportSectionsTable.AddEmbeddedObject(PreinitializationImports); + graph.AddRoot(ImportSectionsTable, "Import sections table is always generated"); graph.AddRoot(ModuleImport, "Module import is always generated"); graph.AddRoot(EagerImports, "Eager imports are always generated"); graph.AddRoot(MethodImports, "Method imports are always generated"); graph.AddRoot(DispatchImports, "Dispatch imports are always generated"); + graph.AddRoot(PreinitializationImports, "Preinitialization imports are always generated"); graph.AddRoot(HelperImports, "Helper imports are always generated"); graph.AddRoot(PrecodeImports, "Precode helper imports are always generated"); graph.AddRoot(ILBodyPrecodeImports, "IL body precode imports are always generated"); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunSymbolNodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunSymbolNodeFactory.cs index 981ae21d19507e..7804455fe0f540 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunSymbolNodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunSymbolNodeFactory.cs @@ -313,6 +313,11 @@ private ISymbolNode CreateNewArrayHelper(ArrayType type) private ISymbolNode CreateGCStaticBaseHelper(TypeDesc type) { + if (type is MetadataType metadataType) + { + _codegenNodeFactory.ReadyToRunPreinitializationManager.GetTypeRecord(metadataType); + } + return new DelayLoadHelperImport( _codegenNodeFactory, _codegenNodeFactory.HelperImports, @@ -322,6 +327,11 @@ private ISymbolNode CreateGCStaticBaseHelper(TypeDesc type) private ISymbolNode CreateNonGCStaticBaseHelper(TypeDesc type) { + if (type is MetadataType metadataType) + { + _codegenNodeFactory.ReadyToRunPreinitializationManager.GetTypeRecord(metadataType); + } + return new DelayLoadHelperImport( _codegenNodeFactory, _codegenNodeFactory.HelperImports, diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/DelegateCreationInfo.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/DelegateCreationInfo.ReadyToRun.cs new file mode 100644 index 00000000000000..51bf09d29336da --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/DelegateCreationInfo.ReadyToRun.cs @@ -0,0 +1,92 @@ +// 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; + +using ILCompiler.DependencyAnalysis; +using ILCompiler.DependencyAnalysis.ReadyToRun; + +using Internal.IL; +using Internal.JitInterface; +using Internal.TypeSystem; + +namespace ILCompiler +{ + public sealed class DelegateCreationInfo + { + public IMethodNode Constructor { get; } + + public MethodDesc PossiblyUnresolvedTargetMethod { get; } + + private bool TargetMethodIsUnboxingThunk + => PossiblyUnresolvedTargetMethod.OwningType.IsValueType && !PossiblyUnresolvedTargetMethod.Signature.IsStatic; + + public bool TargetNeedsVTableLookup => false; + + public IMethodNode Thunk { get; } + + public TypeDesc DelegateType { get; } + + private DelegateCreationInfo(TypeDesc delegateType, IMethodNode constructor, MethodDesc targetMethod, IMethodNode thunk) + { + DelegateType = delegateType; + Constructor = constructor; + PossiblyUnresolvedTargetMethod = targetMethod; + Thunk = thunk; + } + + public static DelegateCreationInfo Create(TypeDesc delegateType, MethodDesc targetMethod, TypeDesc constrainedType, NodeFactory factory, bool followVirtualDispatch) + { + int paramCountTargetMethod = targetMethod.Signature.Length; + if (!targetMethod.Signature.IsStatic) + { + paramCountTargetMethod++; + } + + MethodSignature delegateSignature = delegateType.GetTypeDefinition().GetKnownMethod("Invoke"u8, null).Signature; + int paramCountDelegateClosed = delegateSignature.Length + 1; + bool closed = paramCountDelegateClosed == paramCountTargetMethod; + + if (targetMethod.Signature.IsStatic) + { + if (!closed) + { + throw new NotSupportedException("Open static delegates"); + } + } + else + { + if (!closed) + throw new NotImplementedException("Open instance delegates"); + } + + IMethodNode constructorNode = CreateMethodEntrypoint(factory, targetMethod, constrainedType); + return new DelegateCreationInfo(delegateType, constructorNode, targetMethod, null); + } + + public ISymbolNode GetTargetNode(NodeFactory factory) + => factory.ExactCallableAddressTakenAddress( + PossiblyUnresolvedTargetMethod, + TargetMethodIsUnboxingThunk); + + private static IMethodNode CreateMethodEntrypoint(NodeFactory factory, MethodDesc method, TypeDesc constrainedType) + { + bool unboxingStub = method.OwningType.IsValueType && !method.Signature.IsStatic; + + ModuleToken methodToken = factory.Resolver.GetModuleTokenForMethod( + method, + allowDynamicallyCreatedReference: true, + throwIfNotFound: true); + + IMethodNode methodNode = factory.MethodEntrypoint( + new MethodWithToken(method, methodToken, constrainedType, unboxing: unboxingStub, context: null), + isInstantiatingStub: false, + isPrecodeImportRequired: false, + isJumpableImportRequired: false); + + Debug.Assert(methodNode != null); + return methodNode; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/PreinitializationExtensions.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/PreinitializationExtensions.cs new file mode 100644 index 00000000000000..83445df5360314 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/PreinitializationExtensions.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using ILCompiler.DependencyAnalysis.ReadyToRun; + +using Internal.JitInterface; +using Internal.TypeSystem; + +using CombinedDependencyList = System.Collections.Generic.List.CombinedDependencyListEntry>; + +namespace ILCompiler.DependencyAnalysis +{ + internal static class PreinitializationExtensions + { + extension(NodeFactory factory) + { + public ISymbolNode TypeNonGCStaticsSymbol(MetadataType type) + => factory.ReadyToRunPreinitializationManager.GetOrCreateTypeNonGCStaticsImport(type); + + public ISymbolNode ConstructedTypeSymbol(TypeDesc type) + => factory.ReadyToRunPreinitializationManager.GetOrCreateConstructedTypeImport(type); + + public ISymbolNode SerializedStringObject(string data) + => factory.ReadyToRunPreinitializationManager.GetOrCreateSerializedStringImport(data); + + public ISymbolNode SerializedFrozenObject(MetadataType owningType, int allocationSiteId, TypePreinit.ISerializableReference data) + => factory.ReadyToRunPreinitializationManager.GetOrCreateSerializedFrozenObjectDataNode(owningType, allocationSiteId, data); + + public ISymbolNode SerializedMetadataRuntimeTypeObject(TypeDesc type) + => factory.ReadyToRunPreinitializationManager.GetOrCreateSerializedRuntimeTypeImport(type); + + public ISymbolNode ExactCallableAddressTakenAddress(MethodDesc method, bool isUnboxingStub = false) + { + ModuleToken token = factory.Resolver.GetModuleTokenForMethod( + method, + allowDynamicallyCreatedReference: true, + throwIfNotFound: true); + + MethodWithToken methodWithToken = new MethodWithToken( + method, + token, + constrainedType: null, + unboxing: isUnboxingStub, + context: null); + + return factory.ReadyToRunPreinitializationManager.GetOrCreateExactCallableAddressImport( + methodWithToken, + isInstantiatingStub: false); + } + } + + extension(MetadataManager metadataManager) + { + public void GetDependenciesDueToDelegateCreation(ref CombinedDependencyList dependencies, DependencyAnalysis.NodeFactory factory, TypeDesc delegateType, MethodDesc target) + { + // In R2R for preinitialized delegates, codegen dependencies are carried by relocations emitted + // into the serialized object payload, so no additional conditional dependencies are required. + } + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/ReadyToRunPreinitializationManager.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/ReadyToRunPreinitializationManager.cs new file mode 100644 index 00000000000000..ecc6e7a168e9bb --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/ReadyToRunPreinitializationManager.cs @@ -0,0 +1,423 @@ +// 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 System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +using ILCompiler.DependencyAnalysis.ReadyToRun; + +using Internal.JitInterface; +using Internal.ReadyToRunConstants; +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; + +namespace ILCompiler.DependencyAnalysis +{ + internal sealed class ReadyToRunPreinitializationManager + { + private readonly NodeFactory _factory; + private readonly Dictionary _typeRecords = new(); + private readonly Dictionary _moduleHasPreinitializedTypes = new(); + private readonly Dictionary _serializedStringImports = new(StringComparer.Ordinal); + private readonly Dictionary _constructedTypeImports = new(); + private readonly Dictionary _typeNonGCStaticsImports = new(); + private readonly Dictionary _serializedFrozenObjects = new(); + private readonly Dictionary _exactCallableAddressImports = new(); + + public ReadyToRunPreinitializationManager(NodeFactory factory) + { + _factory = factory; + } + + public bool IsTypePreinitialized(MetadataType type) + => GetTypeRecord(type).IsPreinitialized; + + public bool HasAnyPreinitializedTypesInModule(EcmaModule module) + { + lock (_typeRecords) + { + if (_moduleHasPreinitializedTypes.TryGetValue(module, out bool hasAny) && hasAny) + return hasAny; + } + + bool found = false; + foreach (TypeDefinitionHandle typeHandle in module.MetadataReader.TypeDefinitions) + { + if (GetTypeRecord((MetadataType)module.GetType(typeHandle)).IsPreinitialized) + { + found = true; + break; + } + } + + if (!found) + { + lock (_typeRecords) + { + foreach (KeyValuePair known in _typeRecords) + { + if (known.Value.IsPreinitialized && IsTypeInModule(known.Key, module)) + { + found = true; + break; + } + } + } + } + + lock (_typeRecords) + { + if (found) + _moduleHasPreinitializedTypes[module] = true; + } + + return found; + } + + public IReadOnlyList> GetKnownInstantiatedTypeRecordsForModule(EcmaModule module) + { + List> result = new(); + + lock (_typeRecords) + { + foreach (KeyValuePair known in _typeRecords) + { + MetadataType type = known.Key; + if (!known.Value.IsPreinitialized) + continue; + + if (!type.HasInstantiation || type.IsGenericDefinition || type.IsCanonicalSubtype(CanonicalFormKind.Any) || type.IsRuntimeDeterminedSubtype) + continue; + + if (!IsTypeInModule(type, module)) + continue; + + result.Add(known); + } + } + + return result; + } + + public TypePreinitializationRecord GetTypeRecord(MetadataType type) + { + lock (_typeRecords) + { + if (_typeRecords.TryGetValue(type, out TypePreinitializationRecord record)) + { + return record; + } + } + + TypePreinitializationRecord computed = ComputeTypeRecord(type); + lock (_typeRecords) + { + // It's possible another thread computed the same type record while we were computing, so check again before adding and rooting. + if (_typeRecords.TryGetValue(type, out TypePreinitializationRecord existing)) + { + return existing; + } + + _typeRecords[type] = computed; + RootStaticsDataNode(computed); + return computed; + } + } + + private void RootStaticsDataNode(TypePreinitializationRecord record) + { + if (record?.StaticsDataNode != null) + { + _factory.AddCompilationRoot(record.StaticsDataNode, "ReadyToRun preinitialized statics data"); + } + } + + public Import GetOrCreateSerializedStringImport(string data) + { + lock (_typeRecords) + { + if (_serializedStringImports.TryGetValue(data, out Import existingImport)) + return existingImport; + } + + if (_factory.ManifestMetadataTable == null) + throw new NotSupportedException("Manifest metadata table is not available for serialized string import emission."); + + int? tokenValue = _factory.ManifestMetadataTable._mutableModule.TryGetStringHandle(data); + if (!tokenValue.HasValue) + throw new NotSupportedException($"Unable to emit ReadyToRun string import for preinitialized value \"{data}\"."); + + ModuleToken moduleToken = new ModuleToken( + _factory.ManifestMetadataTable._mutableModule, + MetadataTokens.Handle(tokenValue.Value)); + + Import createdImport = new StringImport(_factory.StringImports, moduleToken); + + lock (_typeRecords) + { + if (_serializedStringImports.TryGetValue(data, out Import existingImport)) + return existingImport; + + _serializedStringImports[data] = createdImport; + return createdImport; + } + } + + public Import GetOrCreateConstructedTypeImport(TypeDesc type) + { + lock (_typeRecords) + { + if (_constructedTypeImports.TryGetValue(type, out Import existingImport)) + return existingImport; + } + + Import createdImport = new Import( + _factory.PreinitializationImports, + _factory.TypeSignature(ReadyToRunFixupKind.TypeHandle, type)); + + lock (_typeRecords) + { + if (_constructedTypeImports.TryGetValue(type, out Import existingImport)) + return existingImport; + + _constructedTypeImports[type] = createdImport; + return createdImport; + } + } + + public Import GetOrCreateTypeNonGCStaticsImport(MetadataType type) + { + lock (_typeRecords) + { + if (_typeNonGCStaticsImports.TryGetValue(type, out Import existingImport)) + return existingImport; + } + + Import createdImport = new Import( + _factory.PreinitializationImports, + _factory.TypeSignature(ReadyToRunFixupKind.StaticBaseNonGC, type)); + + lock (_typeRecords) + { + if (_typeNonGCStaticsImports.TryGetValue(type, out Import existingImport)) + return existingImport; + + _typeNonGCStaticsImports[type] = createdImport; + return createdImport; + } + } + + public SerializedPreinitializationObjectDataNode GetOrCreateSerializedFrozenObjectDataNode(MetadataType owningType, int allocationSiteId, TypePreinit.ISerializableReference data) + { + SerializedFrozenObjectKey key = new SerializedFrozenObjectKey(owningType, allocationSiteId); + + lock (_typeRecords) + { + if (_serializedFrozenObjects.TryGetValue(key, out SerializedPreinitializationObjectDataNode existingNode)) + return existingNode; + } + + SerializedPreinitializationObjectDataNode createdNode = new SerializedPreinitializationObjectDataNode(owningType, allocationSiteId, data); + + lock (_typeRecords) + { + if (_serializedFrozenObjects.TryGetValue(key, out SerializedPreinitializationObjectDataNode existingNode)) + return existingNode; + + _serializedFrozenObjects[key] = createdNode; + return createdNode; + } + } + + public Import GetOrCreateSerializedRuntimeTypeImport(TypeDesc type) + => GetOrCreateConstructedTypeImport(type); + + public Import GetOrCreateExactCallableAddressImport(MethodWithToken method, bool isInstantiatingStub) + { + MethodImportKey key = new MethodImportKey( + method, + isInstantiatingStub); + + lock (_typeRecords) + { + if (_exactCallableAddressImports.TryGetValue(key, out Import existingImport)) + return existingImport; + } + + Import createdImport = new Import( + _factory.PreinitializationImports, + _factory.MethodSignature(ReadyToRunFixupKind.MethodEntry, key.Method, key.IsInstantiatingStub)); + + lock (_typeRecords) + { + if (_exactCallableAddressImports.TryGetValue(key, out Import existingImport)) + return existingImport; + + _exactCallableAddressImports[key] = createdImport; + return createdImport; + } + } + + private TypePreinitializationRecord ComputeTypeRecord(MetadataType type) + { + if (!_factory.PreinitializationManager.IsPreinitialized(type)) + return TypePreinitializationRecord.NotPreinitialized; + + TypePreinit.PreinitializationInfo preinitInfo = _factory.PreinitializationManager.GetPreinitializationInfo(type); + if (!preinitInfo.IsPreinitialized) + return TypePreinitializationRecord.NotPreinitialized; + + bool hasNonNullGCData = false; + foreach (FieldDesc field in type.GetFields()) + { + if (!field.IsStatic || field.IsLiteral || field.HasRva || field.IsThreadStatic || !field.HasGCStaticBase) + continue; + + if (preinitInfo.GetFieldValue(field) != null) + hasNonNullGCData = true; + } + + TypePreinitializedStaticsDataNode staticsDataNode = null; + int nonGCDataSize; + int gcDataSize; + try + { + nonGCDataSize = TypePreinitializedStaticsDataNode.ComputeNonGCStaticsDataSize(type); + gcDataSize = TypePreinitializedStaticsDataNode.ComputeGCStaticsDataSize(type); + + if (nonGCDataSize > 0 || gcDataSize > 0) + { + staticsDataNode = new TypePreinitializedStaticsDataNode(preinitInfo); + ValidateSerializablePreinitializationGraph(staticsDataNode); + } + } + catch (NotSupportedException ex) + { + return TypePreinitializationRecord.Unsupported(ex.Message); + } + catch (InvalidProgramException ex) + { + return TypePreinitializationRecord.Unsupported(ex.Message); + } + + return new TypePreinitializationRecord( + isPreinitialized: true, + nonGCDataSize: nonGCDataSize, + staticsDataNode: staticsDataNode, + hasPreinitializedGCData: hasNonNullGCData, + failureReason: null); + } + + private void ValidateSerializablePreinitializationGraph(TypePreinitializedStaticsDataNode staticsDataNode) + { + Stack pending = new(); + HashSet visited = new(); + + AddSerializedObjectRelocs(staticsDataNode.GetData(_factory, relocsOnly: true).Relocs, pending, visited); + + while (pending.Count > 0) + { + SerializedPreinitializationObjectDataNode node = pending.Pop(); + + if (node.HasConditionalStaticDependencies) + _ = node.GetConditionalStaticDependencies(_factory); + + AddSerializedObjectRelocs(node.GetData(_factory, relocsOnly: true).Relocs, pending, visited); + } + } + + private static void AddSerializedObjectRelocs( + Relocation[] relocs, + Stack pending, + HashSet visited) + { + if (relocs == null) + return; + + foreach (Relocation reloc in relocs) + { + if (reloc.Target is SerializedPreinitializationObjectDataNode referencedObject + && visited.Add(referencedObject)) + { + pending.Push(referencedObject); + } + } + } + + private static bool IsTypeInModule(MetadataType type, EcmaModule module) + { + if (type.GetTypeDefinition() is not EcmaType ecmaTypeDefinition) + return false; + + return ecmaTypeDefinition.Module == module; + } + + private readonly struct SerializedFrozenObjectKey : IEquatable + { + public SerializedFrozenObjectKey(MetadataType owningType, int allocationSiteId) + { + OwningType = owningType; + AllocationSiteId = allocationSiteId; + } + + public MetadataType OwningType { get; } + public int AllocationSiteId { get; } + + public bool Equals(SerializedFrozenObjectKey other) + => OwningType == other.OwningType && AllocationSiteId == other.AllocationSiteId; + + public override bool Equals(object obj) + => obj is SerializedFrozenObjectKey other && Equals(other); + + public override int GetHashCode() + => HashCode.Combine(OwningType, AllocationSiteId); + } + + private readonly struct MethodImportKey : IEquatable + { + public MethodImportKey(MethodWithToken method, bool isInstantiatingStub) + { + Method = method; + IsInstantiatingStub = isInstantiatingStub; + } + + public MethodWithToken Method { get; } + public bool IsInstantiatingStub { get; } + + public bool Equals(MethodImportKey other) + => Method.Equals(other.Method) && IsInstantiatingStub == other.IsInstantiatingStub; + + public override bool Equals(object obj) + => obj is MethodImportKey other && Equals(other); + + public override int GetHashCode() + => HashCode.Combine(Method, IsInstantiatingStub); + } + + internal sealed class TypePreinitializationRecord + { + public static TypePreinitializationRecord NotPreinitialized { get; } = + new TypePreinitializationRecord(isPreinitialized: false, nonGCDataSize: 0, staticsDataNode: null, hasPreinitializedGCData: false, failureReason: null); + + public static TypePreinitializationRecord Unsupported(string reason) => + new TypePreinitializationRecord(isPreinitialized: false, nonGCDataSize: 0, staticsDataNode: null, hasPreinitializedGCData: false, failureReason: reason); + + public TypePreinitializationRecord(bool isPreinitialized, int nonGCDataSize, TypePreinitializedStaticsDataNode staticsDataNode, bool hasPreinitializedGCData, string failureReason) + { + IsPreinitialized = isPreinitialized; + NonGCDataSize = nonGCDataSize; + StaticsDataNode = staticsDataNode; + HasPreinitializedGCData = hasPreinitializedGCData; + FailureReason = failureReason; + } + + public bool IsPreinitialized { get; } + public int NonGCDataSize { get; } + public TypePreinitializedStaticsDataNode StaticsDataNode { get; } + public bool HasPreinitializedGCData { get; } + public string FailureReason { get; } + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs index 122682028f3353..9092f90921633d 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs @@ -515,6 +515,7 @@ private void RewriteComponentFile(string inputFile, string outputFile, string ow format: ReadyToRunContainerFormat.PE, imageBase: _nodeFactory.ImageBase, associatedModule: automaticTypeValidation ? inputModule : null, + preinitializationManager: _nodeFactory.PreinitializationManager, genericCycleDepthCutoff: -1, // We don't need generic cycle detection when rewriting component assemblies genericCycleBreadthCutoff: -1); // as we're not actually compiling anything diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs index 1501401effba15..c5bf9096a2fb06 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs @@ -54,6 +54,7 @@ public sealed class ReadyToRunCodegenCompilationBuilder : CompilationBuilder // calling the Use/Configure methods and still get something reasonable back. private KeyValuePair[] _ryujitOptions = Array.Empty>(); private ILProvider _ilProvider; + private PreinitializationManager _preinitializationManager; public ReadyToRunCodegenCompilationBuilder( CompilerTypeSystemContext context, @@ -103,6 +104,12 @@ public override CompilationBuilder UseILProvider(ILProvider ilProvider) return this; } + public ReadyToRunCodegenCompilationBuilder UsePreinitializationManager(PreinitializationManager preinitializationManager) + { + _preinitializationManager = preinitializationManager; + return this; + } + protected override ILProvider GetILProvider() { return _ilProvider; @@ -266,6 +273,15 @@ public override ICompilation ToCompilation() } flags |= _compilationGroup.GetReadyToRunFlags(); + PreinitializationManager preinitializationManager = _preinitializationManager ?? + new PreinitializationManager( + _context, + _compilationGroup, + _ilProvider, + new TypePreinit.DisabledPreinitializationPolicy(), + new StaticReadOnlyFieldPolicy(), + flowAnnotations: null); + NodeFactory factory = new NodeFactory( _context, (ReadyToRunCompilationModuleGroupBase)_compilationGroup, @@ -279,6 +295,7 @@ public override ICompilation ToCompilation() _format, _imageBase, automaticTypeValidation ? singleModule : null, + preinitializationManager, genericCycleDepthCutoff: _genericCycleDetectionDepthCutoff, genericCycleBreadthCutoff: _genericCycleDetectionBreadthCutoff ); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index cb7b001082722b..f409aa7b41f0c5 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -46,6 +46,7 @@ + @@ -190,12 +191,22 @@ + + + + + + + + + + diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index 42fb1148659daf..b4b36f09f393ee 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -521,6 +521,17 @@ private static mdToken FindGenericMethodArgTypeSpec(EcmaModule module) throw new NotSupportedException(); } + private static bool ShouldSkipPreinitializedTypeCctorCompilation(ReadyToRunPreinitializationManager preinitializationManager, MethodDesc method) + { + if (!method.IsStaticConstructor) + return false; + + if (method.OwningType is MetadataType metadataType) + return preinitializationManager.IsTypePreinitialized(metadataType); + + return false; + } + public static bool ShouldSkipCompilation(InstructionSetSupport instructionSetSupport, MethodDesc methodNeedingCode) { if (methodNeedingCode.IsAggressiveOptimization) @@ -764,6 +775,13 @@ public void CompileMethod(MethodWithGCInfo methodCodeNodeNeedingCode, Logger log return; } + if (ShouldSkipPreinitializedTypeCctorCompilation(_compilation.NodeFactory.ReadyToRunPreinitializationManager, MethodBeingCompiled)) + { + if (logger.IsVerbose) + logger.Writer.WriteLine($"Info: Method `{MethodBeingCompiled}` was not compiled because it is a static constructor for a preinitialized type."); + return; + } + if (MethodSignatureIsUnstable(MethodBeingCompiled.Signature, out var _)) { if (logger.IsVerbose) @@ -1625,24 +1643,29 @@ private bool IsClassPreInited(TypeDesc type) return true; } - var uninstantiatedType = type.GetTypeDefinition(); - if (_preInitedTypes.TryGetValue(uninstantiatedType, out bool preInited)) + if (_preInitedTypes.TryGetValue(type, out bool preInited)) { return preInited; } - preInited = ComputeIsClassPreInited(type); - _preInitedTypes.Add(uninstantiatedType, preInited); + preInited = ComputeIsClassPreInited(type, _compilation.NodeFactory); + _preInitedTypes.Add(type, preInited); return preInited; - static bool ComputeIsClassPreInited(TypeDesc type) + static bool ComputeIsClassPreInited(TypeDesc type, NodeFactory factory) { if (type.IsGenericDefinition) { return true; } + if (type is MetadataType metadataType && + factory.ReadyToRunPreinitializationManager.IsTypePreinitialized(metadataType)) + { + return true; + } + if (type.HasStaticConstructor) { return false; diff --git a/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs b/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs index c31f7175e0b7c8..044bb296ee7cb4 100644 --- a/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs +++ b/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs @@ -42,6 +42,10 @@ internal class Crossgen2RootCommand : RootCommand new("--optimize-space", "--Os") { Description = SR.OptimizeSpaceOption }; public Option OptimizeTime { get; } = new("--optimize-time", "--Ot") { Description = SR.OptimizeSpeedOption }; + public Option PreinitStatics { get; } = + new("--preinitstatics") { Description = SR.PreinitStaticsOption }; + public Option NoPreinitStatics { get; } = + new("--nopreinitstatics") { Description = SR.NoPreinitStaticsOption }; public Option EnableCachedInterfaceDispatchSupport { get; } = new("--enable-cached-interface-dispatch-support", "--CID") { Description = SR.EnableCachedInterfaceDispatchSupport }; public Option TypeValidation { get; } = @@ -169,6 +173,8 @@ public Crossgen2RootCommand(string[] args) : base(SR.Crossgen2BannerText) Options.Add(OptimizeDisabled); Options.Add(OptimizeSpace); Options.Add(OptimizeTime); + Options.Add(PreinitStatics); + Options.Add(NoPreinitStatics); Options.Add(EnableCachedInterfaceDispatchSupport); Options.Add(TypeValidation); Options.Add(InputBubble); diff --git a/src/coreclr/tools/aot/crossgen2/Program.cs b/src/coreclr/tools/aot/crossgen2/Program.cs index 2ac14e471e8448..8c24e1c345169b 100644 --- a/src/coreclr/tools/aot/crossgen2/Program.cs +++ b/src/coreclr/tools/aot/crossgen2/Program.cs @@ -345,6 +345,7 @@ private void RunSingleCompilation(Dictionary inFilePaths, Instru using (PerfEventSource.StartStopEvents.CompilationEvents()) { ICompilation compilation; + PreinitializationManager preinitManager = null; using (PerfEventSource.StartStopEvents.LoadingEvents()) { List inputModules = new List(); @@ -615,6 +616,15 @@ private void RunSingleCompilation(Dictionary inFilePaths, Instru ILProvider ilProvider = new ReadyToRunILProvider(compilationGroup); + bool preinitStatics = Get(_command.PreinitStatics) || optimizationMode != OptimizationMode.None; + preinitStatics &= !Get(_command.NoPreinitStatics); + + TypePreinit.TypePreinitializationPolicy preinitPolicy = preinitStatics ? + new TypePreinit.TypeLoaderAwarePreinitializationPolicy() : new TypePreinit.DisabledPreinitializationPolicy(); + + FlowAnnotations flowAnnotations = preinitStatics ? new FlowAnnotations() : null; + preinitManager = new PreinitializationManager(typeSystemContext, compilationGroup, ilProvider, preinitPolicy, new StaticReadOnlyFieldPolicy(), flowAnnotations); + DependencyTrackingLevel trackingLevel = dgmlLogFileName == null ? DependencyTrackingLevel.None : (Get(_command.GenerateFullDgmlLog) ? DependencyTrackingLevel.All : DependencyTrackingLevel.First); @@ -652,6 +662,8 @@ private void RunSingleCompilation(Dictionary inFilePaths, Instru .UseCompilationRoots(compilationRoots) .UseOptimizationMode(optimizationMode); + builder.UsePreinitializationManager(preinitManager); + if (Get(_command.EnableGenericCycleDetection)) { builder.UseGenericCycleDetection( @@ -673,6 +685,8 @@ private void RunSingleCompilation(Dictionary inFilePaths, Instru if (((ReadyToRunCodegenCompilation)compilation).DeterminismCheckFailed) throw new Exception("Determinism Check Failed"); + + preinitManager?.LogStatistics(logger); } } diff --git a/src/coreclr/tools/aot/crossgen2/Properties/Resources.resx b/src/coreclr/tools/aot/crossgen2/Properties/Resources.resx index ca30cc9a5e31d0..4fd2c904736294 100644 --- a/src/coreclr/tools/aot/crossgen2/Properties/Resources.resx +++ b/src/coreclr/tools/aot/crossgen2/Properties/Resources.resx @@ -216,6 +216,12 @@ Enable optimizations, favor code speed + + Interpret static constructors at compile time if possible (implied by -O) + + + Do not interpret static constructors at compile time + Create output files near input files with ni.dll suffix @@ -435,4 +441,4 @@ Error: Producing ReadyToRun output in the {0} format is only supported for Composite ReadyToRun images. - \ No newline at end of file + diff --git a/src/coreclr/tools/r2rdump/TextDumper.cs b/src/coreclr/tools/r2rdump/TextDumper.cs index 60cd0fb09ba35d..9a15d6550a471e 100644 --- a/src/coreclr/tools/r2rdump/TextDumper.cs +++ b/src/coreclr/tools/r2rdump/TextDumper.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.IO; using System.Linq; using System.Reflection.Metadata; @@ -598,6 +599,489 @@ public override void DumpSectionContents(ReadyToRunSection section) } break; } + case ReadyToRunSectionType.TypePreinitializationMap: + { + int mapStart = _r2r.GetOffset(section.RelativeVirtualAddress); + int mapOffset = mapStart; + int mapEnd = mapStart + section.Size; + + MetadataReader metadataReader = GetMetadataReaderForSection(section); + List<(string Name, int Start, int End)> r2rSectionRanges = BuildR2RSectionRanges(); + + List<(string Name, int Start, int End)> BuildR2RSectionRanges() + { + List<(string Name, int Start, int End)> ranges = new(); + + foreach (ReadyToRunSection headerSection in _r2r.ReadyToRunHeader.Sections.Values) + { + int start = headerSection.RelativeVirtualAddress; + int end = start + headerSection.Size; + ranges.Add(($"R2R.{headerSection.Type}", start, end)); + } + + if (_r2r.ReadyToRunAssemblyHeaders != null) + { + for (int assemblyIndex = 0; assemblyIndex < _r2r.ReadyToRunAssemblyHeaders.Count; assemblyIndex++) + { + foreach (ReadyToRunSection assemblySection in _r2r.ReadyToRunAssemblyHeaders[assemblyIndex].Sections.Values) + { + int start = assemblySection.RelativeVirtualAddress; + int end = start + assemblySection.Size; + ranges.Add(($"Component[{assemblyIndex}].{assemblySection.Type}", start, end)); + } + } + } + + return ranges; + } + + string ResolveRvaSymbol(uint rva) + { + if (rva == 0 || rva > int.MaxValue) + { + return String.Empty; + } + + int rva32 = (int)rva; + List symbols = new(); + + if (_r2r.ImportSignatures.TryGetValue(rva32, out ReadyToRunSignature signature)) + { + symbols.Add(signature.ToString(_model.SignatureFormattingOptions)); + } + + foreach ((string Name, int Start, int End) range in r2rSectionRanges) + { + if (rva32 >= range.Start && rva32 < range.End) + { + symbols.Add($"{range.Name}+0x{(rva32 - range.Start):X}"); + break; + } + } + + try + { + int fileOffset = _r2r.GetOffset(rva32); + symbols.Add($"image+0x{fileOffset:X8}"); + } + catch (IndexOutOfRangeException) + { + } + + return String.Join("; ", symbols); + } + + string FormatResolvedRva(uint rva) + { + string symbol = ResolveRvaSymbol(rva); + return symbol.Length == 0 ? $"0x{rva:X8}" : $"0x{rva:X8} ({symbol})"; + } + + bool TryReadUInt32(ref int offset, out uint value) + { + value = 0; + if (offset > mapEnd || mapEnd - offset < sizeof(uint)) + { + return false; + } + + value = _r2r.ImageReader.ReadUInt32(ref offset); + return true; + } + + bool TryGetTypeDefinitionInfo(uint rid, out string typeName, out bool isGenericDefinition) + { + typeName = null; + isGenericDefinition = false; + + if (metadataReader == null || rid == 0) + { + return false; + } + + if (rid > metadataReader.TypeDefinitions.Count) + { + return false; + } + + TypeDefinitionHandle handle = MetadataTokens.TypeDefinitionHandle((int)rid); + TypeDefinition definition = metadataReader.GetTypeDefinition(handle); + typeName = MetadataNameFormatter.FormatHandle(metadataReader, handle); + isGenericDefinition = definition.GetGenericParameters().Count > 0; + return true; + } + + string BuildDataPreview(uint rva, uint size, int maxBytes = 32) + { + if (rva == 0 || size == 0) + { + return ""; + } + + if (rva > int.MaxValue) + { + return ""; + } + + int dataOffset; + try + { + dataOffset = _r2r.GetOffset((int)rva); + } + catch (IndexOutOfRangeException) + { + return ""; + } + + if (dataOffset < 0 || dataOffset >= _r2r.Image.Length) + { + return ""; + } + + int byteCount = (int)uint.Min(size, (uint)maxBytes); + int available = Math.Min(byteCount, _r2r.Image.Length - dataOffset); + if (available <= 0) + { + return ""; + } + + StringBuilder sb = new StringBuilder(); + sb.Append($"Size={size}, Preview[{available}]="); + for (int i = 0; i < available; i++) + { + if (i != 0) + { + sb.Append(' '); + } + sb.Append(_r2r.Image[dataOffset + i].ToString("X2")); + } + + if (size > (uint)available) + { + sb.Append(" ..."); + } + + return sb.ToString(); + } + + uint AlignUp(uint value, uint alignment) + { + if (alignment == 0) + { + return value; + } + + uint mask = alignment - 1; + return (value + mask) & ~mask; + } + + void DumpNonGCData(uint rva, uint size) + { + if (rva == 0 || size == 0) + { + return; + } + + if (_model.Raw) + { + if (rva > int.MaxValue) + { + Program.WriteWarning($"Preinitialized data RVA 0x{rva:X8} is out of range"); + return; + } + + try + { + DumpBytes((int)rva, size); + } + catch (IndexOutOfRangeException) + { + Program.WriteWarning($"Preinitialized data range [0x{rva:X8}, size {size}] is out of image bounds"); + } + } + + _writer.WriteLine($" NonGCData: {BuildDataPreview(rva, size)}"); + } + + if (!TryReadUInt32(ref mapOffset, out uint typeCount)) + { + Program.WriteWarning("TypePreinitializationMap malformed: missing TypeCount"); + _writer.WriteLine("TypePreinitializationMap malformed: missing TypeCount"); + break; + } + + List<(uint TypeDefRid, uint Payload0, uint Payload1, ReadyToRunTypePreinitializationFlags Flags)> typeDefEntries = new(); + for (uint index = 0; index < typeCount; index++) + { + if (!TryReadUInt32(ref mapOffset, out uint typeDefRid) || + !TryReadUInt32(ref mapOffset, out uint payload0) || + !TryReadUInt32(ref mapOffset, out uint payload1) || + !TryReadUInt32(ref mapOffset, out uint flagsRaw)) + { + Program.WriteWarning("TypePreinitializationMap malformed: truncated typedef entries"); + break; + } + + typeDefEntries.Add((typeDefRid, payload0, payload1, (ReadyToRunTypePreinitializationFlags)flagsRaw)); + } + + if (typeDefEntries.Count != typeCount) + { + Program.WriteWarning($"TypePreinitializationMap malformed: expected {typeCount} typedef entries, parsed {typeDefEntries.Count}"); + } + + if (!TryReadUInt32(ref mapOffset, out uint instantiationEntryCount)) + { + Program.WriteWarning("TypePreinitializationMap malformed: missing InstantiationEntryCount"); + _writer.WriteLine($"TypeCount: {typeCount}"); + _writer.WriteLine("TypeDef entries:"); + for (int i = 0; i < typeDefEntries.Count; i++) + { + var entry = typeDefEntries[i]; + _writer.WriteLine($" [{i}] 0x{(0x02000000u | entry.TypeDefRid):X8}: Payload0=0x{entry.Payload0:X8}, Payload1=0x{entry.Payload1:X8}, Flags={entry.Flags} (0x{(uint)entry.Flags:X8})"); + } + break; + } + + List<(uint SignatureOffset, uint SignatureLength, uint NonGCDataRva, uint NonGCDataSize, ReadyToRunTypePreinitializationFlags Flags)> instantiationEntries = new(); + for (uint index = 0; index < instantiationEntryCount; index++) + { + if (!TryReadUInt32(ref mapOffset, out uint signatureOffset) || + !TryReadUInt32(ref mapOffset, out uint signatureLength) || + !TryReadUInt32(ref mapOffset, out uint nonGCDataRva) || + !TryReadUInt32(ref mapOffset, out uint nonGCDataSize) || + !TryReadUInt32(ref mapOffset, out uint flagsRaw)) + { + Program.WriteWarning("TypePreinitializationMap malformed: truncated instantiation entries"); + break; + } + + instantiationEntries.Add((signatureOffset, signatureLength, nonGCDataRva, nonGCDataSize, (ReadyToRunTypePreinitializationFlags)flagsRaw)); + } + + if (instantiationEntries.Count != instantiationEntryCount) + { + Program.WriteWarning($"TypePreinitializationMap malformed: expected {instantiationEntryCount} instantiation entries, parsed {instantiationEntries.Count}"); + } + + List<(bool HasTypeMetadata, string TypeName, bool IsGenericDefinition)> typeDefDetails = new(typeDefEntries.Count); + SortedSet payloadStartRvas = new(); + for (int i = 0; i < typeDefEntries.Count; i++) + { + var entry = typeDefEntries[i]; + bool hasTypeMetadata = TryGetTypeDefinitionInfo(entry.TypeDefRid, out string typeName, out bool isGenericDefinition); + typeDefDetails.Add((hasTypeMetadata, typeName, isGenericDefinition)); + + if (hasTypeMetadata && !isGenericDefinition && entry.Payload0 != 0) + { + payloadStartRvas.Add(entry.Payload0); + } + } + for (int i = 0; i < instantiationEntries.Count; i++) + { + uint rva = instantiationEntries[i].NonGCDataRva; + if (rva != 0) + { + payloadStartRvas.Add(rva); + } + } + List sortedPayloadStarts = payloadStartRvas.ToList(); + + bool TryComputeGCDataRange(uint nonGCRva, uint nonGCSize, out uint gcRva, out uint gcSize, out string reason) + { + gcRva = 0; + gcSize = 0; + reason = String.Empty; + + if (nonGCRva == 0) + { + return true; + } + + uint pointerSize = checked((uint)_r2r.TargetPointerSize); + uint alignedNonGCSize = AlignUp(nonGCSize, pointerSize); + if (alignedNonGCSize < nonGCSize) + { + reason = "aligned non-GC size overflow"; + return false; + } + + if (nonGCRva > uint.MaxValue - alignedNonGCSize) + { + reason = "start RVA overflow"; + return false; + } + + uint computedGCStart = nonGCRva + alignedNonGCSize; + uint nextPayloadStart = 0; + for (int i = 0; i < sortedPayloadStarts.Count; i++) + { + uint payloadStart = sortedPayloadStarts[i]; + if (payloadStart > nonGCRva) + { + nextPayloadStart = payloadStart; + break; + } + } + + uint size = nextPayloadStart == 0 ? (uint)_r2r.TargetPointerSize : nextPayloadStart - computedGCStart; + if ((size % pointerSize) != 0) + { + reason = $"size {size} is not pointer-size aligned"; + return false; + } + + gcRva = computedGCStart; + gcSize = size; + return true; + } + + void DumpGCData(uint nonGCRva, uint nonGCSize) + { + if (!TryComputeGCDataRange(nonGCRva, nonGCSize, out uint gcRva, out uint gcSize, out string reason)) + { + _writer.WriteLine($" GCData: <{reason}>"); + return; + } + + if (gcSize == 0) + { + return; + } + + _writer.WriteLine($" GCDataRva={FormatResolvedRva(gcRva)}, GCDataSize={gcSize}"); + + if (_model.Raw) + { + if (gcRva > int.MaxValue) + { + Program.WriteWarning($"GC data RVA 0x{gcRva:X8} is out of range"); + } + else + { + try + { + DumpBytes((int)gcRva, gcSize); + } + catch (IndexOutOfRangeException) + { + Program.WriteWarning($"GC data range [0x{gcRva:X8}, size {gcSize}] is out of image bounds"); + } + } + } + + _writer.WriteLine($" GCData: {BuildDataPreview(gcRva, gcSize)}"); + for (int i = 0; i < gcSize / _r2r.TargetPointerSize; i++) + { + int offset = checked((int)(gcRva + i * _r2r.TargetPointerSize)); + if (_r2r.TargetPointerSize == 4) + { + int value = _r2r.ImageReader.ReadInt32(ref offset); + _writer.WriteLine($" [{i}] 0x{value:X8}"); + } + else + { + long value = _r2r.ImageReader.ReadInt64(ref offset); + _writer.WriteLine($" [{i}] 0x{value:X16}"); + } + } + } + + int signatureBlobOffset = mapOffset; + int signatureBlobSize = mapEnd - signatureBlobOffset; + + _writer.WriteLine($"TypeCount: {typeCount}"); + _writer.WriteLine("TypeDef entries:"); + for (int i = 0; i < typeDefEntries.Count; i++) + { + var entry = typeDefEntries[i]; + var details = typeDefDetails[i]; + bool hasTypeMetadata = details.HasTypeMetadata; + bool isGenericDefinition = details.IsGenericDefinition; + string typeNameSuffix = hasTypeMetadata ? $" ({details.TypeName})" : String.Empty; + string payload; + + if (hasTypeMetadata && isGenericDefinition) + { + uint offset = entry.Payload0; + uint instantiationCountForType = entry.Payload1; + bool validRange = offset <= instantiationEntryCount && instantiationCountForType <= (instantiationEntryCount - offset); + payload = $"InstantiationOffset={offset}, InstantiationCount={instantiationCountForType}"; + if (!validRange) + { + payload += " [invalid range]"; + } + } + else if (hasTypeMetadata) + { + payload = $"NonGCDataRva={FormatResolvedRva(entry.Payload0)}, NonGCDataSize={entry.Payload1}"; + } + else + { + payload = $"Payload0=0x{entry.Payload0:X8}, Payload1=0x{entry.Payload1:X8}"; + } + + _writer.WriteLine($" [{i}] 0x{(0x02000000u | entry.TypeDefRid):X8}{typeNameSuffix}: {entry.Flags} (0x{(uint)entry.Flags:X8})"); + _writer.WriteLine($" {payload}"); + + if (hasTypeMetadata && !isGenericDefinition) + { + DumpNonGCData(entry.Payload0, entry.Payload1); + DumpGCData(entry.Payload0, entry.Payload1); + } + } + + _writer.WriteLine($"InstantiationEntryCount: {instantiationEntryCount}"); + _writer.WriteLine("Instantiation entries:"); + for (int i = 0; i < instantiationEntries.Count; i++) + { + var entry = instantiationEntries[i]; + string signature; + if (entry.SignatureLength == 0) + { + signature = ""; + } + else + { + long absoluteSignatureOffset = (long)signatureBlobOffset + entry.SignatureOffset; + long absoluteSignatureEnd = absoluteSignatureOffset + entry.SignatureLength; + if (absoluteSignatureOffset < signatureBlobOffset || absoluteSignatureEnd > mapEnd || absoluteSignatureOffset > int.MaxValue) + { + signature = ""; + } + else + { + int signatureOffset = (int)absoluteSignatureOffset; + SignatureDecoder decoder = new SignatureDecoder(_model, _model.SignatureFormattingOptions, metadataReader, _r2r, signatureOffset); + signature = decoder.ReadTypeSignatureNoEmit(); + int consumedLength = decoder.Offset - signatureOffset; + if (consumedLength != entry.SignatureLength) + { + signature += $" [decoded {consumedLength} bytes, expected {entry.SignatureLength}]"; + } + } + } + + _writer.WriteLine( + $" [{i}] 0x{entry.SignatureOffset:X8} ({signature}): {entry.Flags} (0x{(uint)entry.Flags:X8})"); + _writer.WriteLine($" NonGCDataRva={FormatResolvedRva(entry.NonGCDataRva)}, NonGCDataSize={entry.NonGCDataSize}"); + + DumpNonGCData(entry.NonGCDataRva, entry.NonGCDataSize); + DumpGCData(entry.NonGCDataRva, entry.NonGCDataSize); + } + + int signatureBlobRva = section.RelativeVirtualAddress + (signatureBlobOffset - mapStart); + _writer.WriteLine($"SignatureBlobRva: 0x{signatureBlobRva:X8}"); + _writer.WriteLine($"SignatureBlobSize: {signatureBlobSize} bytes"); + + mapOffset += signatureBlobSize; + + if (mapOffset != mapEnd) + { + Program.WriteWarning($"TypePreinitializationMap contains trailing bytes: {mapEnd - mapOffset}"); + } + break; + } default: _writer.WriteLine("Unsupported section type {0}", section.Type); @@ -605,6 +1089,23 @@ public override void DumpSectionContents(ReadyToRunSection section) } } + private MetadataReader GetMetadataReaderForSection(ReadyToRunSection section) + { + if (!_r2r.Composite) + { + return _r2r.GetGlobalMetadata()?.MetadataReader; + } + + int assemblyIndex = _r2r.GetAssemblyIndex(section); + if (assemblyIndex < 0 || assemblyIndex >= _r2r.ReadyToRunAssemblies.Count) + { + return _r2r.GetGlobalMetadata()?.MetadataReader; + } + + ReadyToRunMethod firstMethod = _r2r.ReadyToRunAssemblies[assemblyIndex].Methods.FirstOrDefault(); + return firstMethod?.ComponentReader?.MetadataReader; + } + public override void DumpQueryCount(string q, string title, int count) { _writer.WriteLine(count + " result(s) for \"" + q + "\""); diff --git a/src/coreclr/vm/ceeload.cpp b/src/coreclr/vm/ceeload.cpp index 1860164c62e19a..a8a3aebf39dbfd 100644 --- a/src/coreclr/vm/ceeload.cpp +++ b/src/coreclr/vm/ceeload.cpp @@ -3513,6 +3513,158 @@ BYTE* Module::GetNativeFixupBlobData(RVA rva) RETURN(BYTE*) GetReadyToRunImage()->GetRvaData(rva); } + +static bool TryGetReadyToRunPreinitializedStaticsDataInfo( + Module *pModule, + MethodTable *pMT, + TADDR *pDataAddress, + DWORD *pNonGCDataSize) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(CheckPointer(pModule)); + PRECONDITION(CheckPointer(pMT)); + PRECONDITION(CheckPointer(pDataAddress)); + PRECONDITION(CheckPointer(pNonGCDataSize)); + } + CONTRACTL_END; + + *pDataAddress = 0; + *pNonGCDataSize = 0; + + if (!pModule->IsReadyToRun() || pMT->IsSharedByGenericInstantiations() || (pMT->GetModule() != pModule)) + return false; + + if (!pModule->IsReadyToRunTypePreinitialized(pMT)) + return false; + + bool foundResult; + uint32_t dataRva; + uint32_t dataSize; + if (!pModule->m_pTypePreinitializationMap->TryGetNonGCData(pModule, pMT, &dataRva, &dataSize, &foundResult) || !foundResult) + return false; + + DWORD expectedNonGCDataSize = pMT->GetClass()->GetNonGCRegularStaticFieldBytes(); + if (dataSize != expectedNonGCDataSize) + return false; + + if (dataRva == 0) + return false; + + ReadyToRunLoadedImage *pImage = pModule->GetReadyToRunImage(); + TADDR imageBase = pImage->GetBase(); + uint32_t imageSize = pImage->GetVirtualSize(); + + TADDR imageEnd = imageBase + imageSize; + TADDR dataAddress = pImage->GetRvaData(dataRva); + if ((dataAddress < imageBase) || (dataAddress > imageEnd)) + return false; + + if (static_cast(imageEnd - dataAddress) < dataSize) + return false; + + *pDataAddress = dataAddress; + *pNonGCDataSize = dataSize; + return true; +} + +bool Module::IsReadyToRunTypePreinitialized(MethodTable * pMT) +{ + CONTRACTL + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(CheckPointer(pMT)); + } + CONTRACTL_END; + + if (!IsReadyToRun() || pMT->IsSharedByGenericInstantiations() || (pMT->GetModule() != this)) + return false; + + bool foundResult; + bool isPreinitialized = m_pTypePreinitializationMap->IsTypePreinitialized(this, pMT, &foundResult); + return foundResult && isPreinitialized; +} + +bool Module::TryGetReadyToRunPreinitializedNonGCStaticsData(MethodTable * pMT, PTR_BYTE * ppData, DWORD * pcbData) +{ + CONTRACTL + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(CheckPointer(pMT)); + PRECONDITION(CheckPointer(ppData)); + PRECONDITION(CheckPointer(pcbData)); + } + CONTRACTL_END; + + *ppData = NULL; + *pcbData = 0; + + TADDR dataAddress; + DWORD nonGCDataSize; + if (!TryGetReadyToRunPreinitializedStaticsDataInfo(this, pMT, &dataAddress, &nonGCDataSize)) + return false; + + if (nonGCDataSize == 0) + return false; + + *ppData = dac_cast(dataAddress); + *pcbData = nonGCDataSize; + return true; +} + +bool Module::TryGetReadyToRunPreinitializedGCStaticsData(MethodTable * pMT, PTR_BYTE * ppData, DWORD * pcbData) +{ + CONTRACTL + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(CheckPointer(pMT)); + PRECONDITION(CheckPointer(ppData)); + PRECONDITION(CheckPointer(pcbData)); + } + CONTRACTL_END; + + *ppData = NULL; + *pcbData = 0; + + DWORD cbGCStatics = pMT->GetClass()->GetNumHandleRegularStatics() * sizeof(TADDR); + if (cbGCStatics == 0) + return false; + + TADDR dataAddress; + DWORD nonGCDataSize; + if (!TryGetReadyToRunPreinitializedStaticsDataInfo(this, pMT, &dataAddress, &nonGCDataSize)) + return false; + + DWORD alignedNonGCDataSize = (DWORD)ALIGN_UP(nonGCDataSize, TARGET_POINTER_SIZE); + + TADDR gcDataAddress = dataAddress + alignedNonGCDataSize; + ReadyToRunLoadedImage *pImage = GetReadyToRunImage(); + TADDR imageBase = pImage->GetBase(); + uint32_t imageSize = pImage->GetVirtualSize(); + + TADDR imageEnd = imageBase + imageSize; + if ((gcDataAddress < imageBase) || (gcDataAddress > imageEnd)) + return false; + + if (static_cast(imageEnd - gcDataAddress) < cbGCStatics) + return false; + + *ppData = dac_cast(gcDataAddress); + *pcbData = cbGCStatics; + return true; +} #endif // DACCESS_COMPILE #ifndef DACCESS_COMPILE diff --git a/src/coreclr/vm/ceeload.h b/src/coreclr/vm/ceeload.h index 73f3a13adbfc52..67041738e6c356 100644 --- a/src/coreclr/vm/ceeload.h +++ b/src/coreclr/vm/ceeload.h @@ -726,7 +726,7 @@ class Module : public ModuleBase #define FIELD_DEF_MAP_ALL_FLAGS NO_MAP_FLAGS #define MEMBER_REF_MAP_ALL_FLAGS ((TADDR)0x00000003) - // For member ref hash table, 0x1 is reserved for IsHot bit + // For member ref hash table, 0x1 is reserved for IsHot bit #define IS_FIELD_MEMBER_REF ((TADDR)0x00000002) // denotes that target is a FieldDesc #define GENERIC_PARAM_MAP_ALL_FLAGS NO_MAP_FLAGS @@ -1476,6 +1476,9 @@ class Module : public ModuleBase BYTE *GetProfilerBase(); BYTE* GetNativeFixupBlobData(RVA fixup); + bool IsReadyToRunTypePreinitialized(MethodTable * pMT); + bool TryGetReadyToRunPreinitializedNonGCStaticsData(MethodTable * pMT, PTR_BYTE * ppData, DWORD * pcbData); + bool TryGetReadyToRunPreinitializedGCStaticsData(MethodTable * pMT, PTR_BYTE * ppData, DWORD * pcbData); IMDInternalImport *GetNativeAssemblyImport(BOOL loadAllowed = TRUE); IMDInternalImport *GetNativeAssemblyImportIfLoaded(); @@ -1566,6 +1569,7 @@ class Module : public ModuleBase public: const ReadyToRun_MethodIsGenericMap *m_pMethodIsGenericMap = &ReadyToRun_MethodIsGenericMap::EmptyInstance; const ReadyToRun_TypeGenericInfoMap *m_pTypeGenericInfoMap = &ReadyToRun_TypeGenericInfoMap::EmptyInstance; + const ReadyToRun_TypePreinitializationMap *m_pTypePreinitializationMap = &ReadyToRun_TypePreinitializationMap::EmptyInstance; protected: diff --git a/src/coreclr/vm/methodtable.cpp b/src/coreclr/vm/methodtable.cpp index 470d35b23cbbfc..7cdb63ca1a39c1 100644 --- a/src/coreclr/vm/methodtable.cpp +++ b/src/coreclr/vm/methodtable.cpp @@ -3901,6 +3901,582 @@ void MethodTable::CheckRunClassInitThrowing() DoRunClassInitThrowing(); } +// Materialized preinitialized objects cache to avoid multiple materializations +// of the same preinitialized object in a graph in case of circular references. +// +class MethodTable::MaterializedPreinitializedObjectCache +{ + struct MaterializedPreinitializedObjectCacheEntry + { + TADDR TemplateDataAddress; + OBJECTHANDLE Handle; + }; + +public: + ~MaterializedPreinitializedObjectCache() + { + LIMITED_METHOD_CONTRACT; + + for (SIZE_T i = 0; i < _entries.Size(); i++) + DestroyHandle(_entries[i].Handle); + } + + bool TryGet(TADDR templateDataAddress, OBJECTREF *pObjectRef) const + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(CheckPointer(pObjectRef)); + } + CONTRACTL_END; + + for (SIZE_T i = 0; i < _entries.Size(); i++) + { + if (_entries[i].TemplateDataAddress == templateDataAddress) + { + *pObjectRef = ObjectFromHandle(_entries[i].Handle); + return true; + } + } + + return false; + } + + void Add(TADDR templateDataAddress, OBJECTREF objectRef) + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + MaterializedPreinitializedObjectCacheEntry entry{ + templateDataAddress, + AppDomain::GetCurrentDomain()->CreateHandle(objectRef) + }; + _entries.Push(entry); + } + +private: + CQuickArrayList _entries; +}; + +static bool IsPointerLikeCorElementType(CorElementType corType) +{ + LIMITED_METHOD_DAC_CONTRACT; + + return (corType == ELEMENT_TYPE_I) || + (corType == ELEMENT_TYPE_U) || + (corType == ELEMENT_TYPE_PTR) || + (corType == ELEMENT_TYPE_FNPTR); +} + +static bool IsAddressInReadyToRunImage(Module *pModule, TADDR address) +{ + LIMITED_METHOD_CONTRACT; + + if (!pModule->IsReadyToRun()) + return false; + + ReadyToRunLoadedImage *pImage = pModule->GetReadyToRunImage(); + TADDR imageBase = pImage->GetBase(); + return (address >= imageBase) && (address < (imageBase + pImage->GetVirtualSize())); +} + +bool MethodTable::TryResolveReadyToRunImportCellAddress(TADDR encodedAddress, TADDR *pResolvedAddress, BYTE *pFixupKind) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + PRECONDITION(CheckPointer(pResolvedAddress)); + } + CONTRACTL_END; + + if (pFixupKind != nullptr) + *pFixupKind = 0; + + if (!IsAddressInReadyToRunImage(GetModule(), encodedAddress)) + return false; + + ReadyToRunLoadedImage *pImage = GetModule()->GetReadyToRunImage(); + RVA encodedRva = pImage->GetDataRva(encodedAddress); + PTR_READYTORUN_IMPORT_SECTION pSection = GetModule()->GetImportSectionForRVA(encodedRva); + if (pSection == NULL) + return false; + + DWORD sectionRva = VAL32(pSection->Section.VirtualAddress); + DWORD sectionSize = VAL32(pSection->Section.Size); + BYTE entrySize = pSection->EntrySize; + + if ((entrySize == 0) || (encodedRva < sectionRva) || (encodedRva >= (sectionRva + sectionSize))) + return false; + + DWORD offsetInSection = encodedRva - sectionRva; + DWORD importIndex = offsetInSection / entrySize; + DWORD importDelta = offsetInSection - (importIndex * entrySize); + DWORD importCount = sectionSize / entrySize; + if (importIndex >= importCount) + return false; + + TADDR importCellAddress = pImage->GetRvaData(sectionRva + (importIndex * entrySize)); + PTR_SIZE_T pImportCell = dac_cast(importCellAddress); + TADDR importCellValue = VolatileLoadWithoutBarrier(pImportCell); + + if (importCellValue == 0) + { + GCX_PREEMP(); + if (!GetModule()->FixupNativeEntry(pSection, importIndex, pImportCell)) + COMPlusThrow(kInvalidProgramException); + + importCellValue = VolatileLoadWithoutBarrier(pImportCell); + } + + *pResolvedAddress = importCellValue + importDelta; + + if ((pFixupKind != nullptr) && (pSection->Signatures != 0)) + { + PTR_DWORD pSignatures = dac_cast(pImage->GetRvaData(pSection->Signatures)); + RVA signatureRva = pSignatures[importIndex]; + if (signatureRva != 0) + { + PCCOR_SIGNATURE pBlob = GetModule()->GetNativeFixupBlobData(signatureRva); + BYTE kind = *pBlob++; + if ((kind & READYTORUN_FIXUP_ModuleOverride) != 0) + { + CorSigUncompressData(pBlob); + kind &= ~READYTORUN_FIXUP_ModuleOverride; + } + + *pFixupKind = kind; + } + } + + return true; +} + +TADDR MethodTable::ResolveReadyToRunEncodedPointer(TADDR encodedAddress, BYTE *pFixupKind) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + TADDR resolvedAddress; + if (TryResolveReadyToRunImportCellAddress(encodedAddress, &resolvedAddress, pFixupKind)) + return resolvedAddress; + + if (pFixupKind != nullptr) + *pFixupKind = 0; + + return encodedAddress; +} + +void MethodTable::ResolveReadyToRunEncodedPointersInValueTypeData(PTR_BYTE pValueData, MethodTable *pValueType) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(CheckPointer(pValueData)); + PRECONDITION(CheckPointer(pValueType)); + PRECONDITION(pValueType->IsValueType()); + } + CONTRACTL_END; + + DeepFieldDescIterator fieldIterator(pValueType, ApproxFieldDescIterator::INSTANCE_FIELDS); + while (FieldDesc *pField = fieldIterator.Next()) + { + _ASSERTE(!pField->IsStatic()); + SIZE_T fieldOffset = pField->GetOffset(); + PTR_BYTE pFieldAddress = pValueData + fieldOffset; + + if (IsPointerLikeCorElementType(pField->GetFieldType())) + { + PTR_TADDR pPointerField = dac_cast(pFieldAddress); + *pPointerField = ResolveReadyToRunEncodedPointer(*pPointerField); + continue; + } + + if (pField->IsObjRef()) + continue; + + if (pField->GetFieldType() != ELEMENT_TYPE_VALUETYPE) + continue; + + TypeHandle fieldTypeHandle = pField->GetApproxFieldTypeHandleThrowing(); + if (!fieldTypeHandle.IsNull() && !fieldTypeHandle.IsTypeDesc()) + { + MethodTable *pFieldType = fieldTypeHandle.AsMethodTable(); + if (pFieldType->IsValueType()) + ResolveReadyToRunEncodedPointersInValueTypeData(pFieldAddress, pFieldType); + } + } +} + +void MethodTable::MaterializeReadyToRunPreinitializedValueTypeData(PTR_BYTE pValueData, PTR_BYTE pTemplateData, MethodTable *pValueType, MaterializedPreinitializedObjectCache* pCache) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(CheckPointer(pValueData)); + PRECONDITION(CheckPointer(pTemplateData)); + PRECONDITION(CheckPointer(pValueType)); + PRECONDITION(pValueType->IsValueType()); + PRECONDITION(CheckPointer(pCache)); + } + CONTRACTL_END; + + DeepFieldDescIterator fieldIterator(pValueType, ApproxFieldDescIterator::INSTANCE_FIELDS); + while (FieldDesc *pField = fieldIterator.Next()) + { + _ASSERTE(!pField->IsStatic()); + SIZE_T fieldOffset = pField->GetOffset(); + PTR_BYTE pFieldAddress = pValueData + fieldOffset; + PTR_BYTE pTemplateFieldAddress = pTemplateData + fieldOffset; + + if (pField->IsObjRef()) + { + TADDR encodedFieldReference = VolatileLoadWithoutBarrier(dac_cast(pTemplateFieldAddress)); + SetObjectReference(dac_cast(pFieldAddress), ResolveReadyToRunPreinitializedObjectReference(encodedFieldReference, pCache)); + continue; + } + + CorElementType fieldType = pField->GetFieldType(); + if (fieldType == ELEMENT_TYPE_VALUETYPE) + { + TypeHandle fieldTypeHandle = pField->GetApproxFieldTypeHandleThrowing(); + if (!fieldTypeHandle.IsNull() && !fieldTypeHandle.IsTypeDesc()) + { + MethodTable *pFieldType = fieldTypeHandle.AsMethodTable(); + if (pFieldType->IsValueType() && pFieldType->ContainsGCPointers()) + { + MaterializeReadyToRunPreinitializedValueTypeData(pFieldAddress, pTemplateFieldAddress, pFieldType, pCache); + continue; + } + } + } + + memcpyNoGCRefs((void *)pFieldAddress, (void *)pTemplateFieldAddress, pField->LoadSize()); + + if (IsPointerLikeCorElementType(pField->GetFieldType())) + { + PTR_TADDR pPointerField = dac_cast(pFieldAddress); + *pPointerField = ResolveReadyToRunEncodedPointer(*pPointerField); + continue; + } + + if (fieldType != ELEMENT_TYPE_VALUETYPE) + continue; + + TypeHandle fieldTypeHandle = pField->GetApproxFieldTypeHandleThrowing(); + if (!fieldTypeHandle.IsNull() && !fieldTypeHandle.IsTypeDesc()) + { + MethodTable *pFieldType = fieldTypeHandle.AsMethodTable(); + if (pFieldType->IsValueType()) + ResolveReadyToRunEncodedPointersInValueTypeData(pFieldAddress, pFieldType); + } + } +} + +OBJECTREF MethodTable::MaterializeReadyToRunPreinitializedClassData(TADDR templateDataAddress, MaterializedPreinitializedObjectCache* pCache) +{ + CONTRACT(OBJECTREF) + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(templateDataAddress != 0); + PRECONDITION(CheckPointer(pCache)); + } + CONTRACT_END; + + OBJECTREF cachedObject; + if (pCache->TryGet(templateDataAddress, &cachedObject)) + RETURN(cachedObject); + + if (!IsAddressInReadyToRunImage(GetModule(), templateDataAddress)) + COMPlusThrow(kInvalidProgramException); + + TADDR encodedMethodTableAddress = VolatileLoadWithoutBarrier(dac_cast(templateDataAddress)); + BYTE methodTableFixupKind = 0; + TADDR methodTableAddress = ResolveReadyToRunEncodedPointer(encodedMethodTableAddress, &methodTableFixupKind); + if (methodTableFixupKind != READYTORUN_FIXUP_TypeHandle) + COMPlusThrow(kInvalidProgramException); + + TypeHandle th = TypeHandle::FromTAddr(methodTableAddress); + if (th.IsNull() || th.IsTypeDesc()) + COMPlusThrow(kInvalidProgramException); + + MethodTable *pObjectType = th.AsMethodTable(); + pObjectType->EnsureInstanceActive(); + + if (pObjectType->IsArray()) + { + _ASSERTE(!pObjectType->IsMultiDimArray()); + + INT32 arrayLength = GET_UNALIGNED_VAL32((PTR_BYTE)(templateDataAddress + TARGET_POINTER_SIZE)); + OBJECTREF arrayRef = AllocateSzArray(pObjectType, arrayLength); + + GCPROTECT_BEGIN(arrayRef); + { + pCache->Add(templateDataAddress, arrayRef); + + SIZE_T dataOffset = ArrayBase::GetDataPtrOffset(pObjectType); + SIZE_T componentSize = pObjectType->RawGetComponentSize(); + SIZE_T cbData = (SIZE_T)arrayLength * componentSize; + + if (cbData > 0) + { + TypeHandle elementTypeHandle = pObjectType->GetArrayElementTypeHandle(); + PTR_BYTE pArrayData = ((ArrayBase *)OBJECTREFToObject(arrayRef))->GetDataPtr(); + if (!elementTypeHandle.IsTypeDesc() && !elementTypeHandle.AsMethodTable()->IsValueType()) + { + PTR_OBJECTREF pArrayRefs = dac_cast(pArrayData); + PTR_TADDR pTemplateRefs = dac_cast(templateDataAddress + dataOffset); + + for (INT32 i = 0; i < arrayLength; i++) + { + TADDR encodedElementReference = VolatileLoadWithoutBarrier(pTemplateRefs + i); + SetObjectReference(pArrayRefs + i, ResolveReadyToRunPreinitializedObjectReference(encodedElementReference, pCache)); + } + } + else + { + if (!elementTypeHandle.IsTypeDesc() && elementTypeHandle.AsMethodTable()->ContainsGCPointers()) + { + MethodTable *pElementType = elementTypeHandle.AsMethodTable(); + PTR_BYTE pTemplateArrayData = dac_cast(templateDataAddress + dataOffset); + + for (INT32 i = 0; i < arrayLength; i++) + { + PTR_BYTE pElementData = pArrayData + ((SIZE_T)i * componentSize); + PTR_BYTE pTemplateElementData = pTemplateArrayData + ((SIZE_T)i * componentSize); + MaterializeReadyToRunPreinitializedValueTypeData(pElementData, pTemplateElementData, pElementType, pCache); + } + } + else + { + memcpyNoGCRefs((void *)pArrayData, (void *)(templateDataAddress + dataOffset), cbData); + + SIZE_T componentSize = pObjectType->RawGetComponentSize(); + TypeHandle elementTypeHandle = pObjectType->GetArrayElementTypeHandle(); + CorElementType elementCorType = elementTypeHandle.GetSignatureCorElementType(); + + if (IsPointerLikeCorElementType(elementCorType)) + { + for (INT32 i = 0; i < arrayLength; i++) + { + PTR_TADDR pElement = dac_cast(pArrayData + ((SIZE_T)i * componentSize)); + *pElement = ResolveReadyToRunEncodedPointer(*pElement); + } + } + else if (!elementTypeHandle.IsTypeDesc() && elementTypeHandle.AsMethodTable()->IsValueType()) + { + for (INT32 i = 0; i < arrayLength; i++) + { + PTR_BYTE pElementData = pArrayData + ((SIZE_T)i * componentSize); + ResolveReadyToRunEncodedPointersInValueTypeData(pElementData, elementTypeHandle.AsMethodTable()); + } + } + } + } + } + } + GCPROTECT_END(); + RETURN(arrayRef); + } + + OBJECTREF objectRef = AllocateObject(pObjectType); + + GCPROTECT_BEGIN(objectRef); + { + pCache->Add(templateDataAddress, objectRef); + + PTR_BYTE pObjectData = OBJECTREFToObject(objectRef)->GetData(); + PTR_BYTE pTemplateData = dac_cast(templateDataAddress + TARGET_POINTER_SIZE); + + DeepFieldDescIterator fieldIterator(pObjectType, ApproxFieldDescIterator::INSTANCE_FIELDS); + while (FieldDesc *pField = fieldIterator.Next()) + { + _ASSERTE(!pField->IsStatic()); + SIZE_T fieldOffset = pField->GetOffset(); + PTR_BYTE pFieldAddress = pObjectData + fieldOffset; + PTR_BYTE pTemplateFieldAddress = pTemplateData + fieldOffset; + + if (pField->IsObjRef()) + { + TADDR encodedFieldReference = VolatileLoadWithoutBarrier(dac_cast(pTemplateFieldAddress)); + SetObjectReference(dac_cast(pFieldAddress), ResolveReadyToRunPreinitializedObjectReference(encodedFieldReference, pCache)); + } + else + { + CorElementType fieldType = pField->GetFieldType(); + if (fieldType == ELEMENT_TYPE_VALUETYPE) + { + TypeHandle fieldTypeHandle = pField->GetApproxFieldTypeHandleThrowing(); + if (!fieldTypeHandle.IsNull() && !fieldTypeHandle.IsTypeDesc()) + { + MethodTable *pFieldType = fieldTypeHandle.AsMethodTable(); + if (pFieldType->IsValueType() && pFieldType->ContainsGCPointers()) + { + MaterializeReadyToRunPreinitializedValueTypeData(pFieldAddress, pTemplateFieldAddress, pFieldType, pCache); + continue; + } + } + } + + memcpyNoGCRefs((void *)pFieldAddress, (void *)pTemplateFieldAddress, pField->LoadSize()); + + if (IsPointerLikeCorElementType(pField->GetFieldType())) + { + PTR_TADDR pPointerField = dac_cast(pFieldAddress); + *pPointerField = ResolveReadyToRunEncodedPointer(*pPointerField); + continue; + } + + if (fieldType != ELEMENT_TYPE_VALUETYPE) + continue; + + TypeHandle fieldTypeHandle = pField->GetApproxFieldTypeHandleThrowing(); + if (!fieldTypeHandle.IsNull() && !fieldTypeHandle.IsTypeDesc()) + { + MethodTable *pFieldType = fieldTypeHandle.AsMethodTable(); + if (pFieldType->IsValueType()) + ResolveReadyToRunEncodedPointersInValueTypeData(pFieldAddress, pFieldType); + } + } + } + } + GCPROTECT_END(); + + RETURN(objectRef); +} + +OBJECTREF MethodTable::ResolveReadyToRunPreinitializedObjectReference(TADDR encodedAddress, MaterializedPreinitializedObjectCache* pCache) +{ + CONTRACT(OBJECTREF) + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(CheckPointer(pCache)); + } + CONTRACT_END; + + if (encodedAddress == 0) + RETURN(NULL); + + TADDR resolvedAddress = 0; + BYTE fixupKind = 0; + if (TryResolveReadyToRunImportCellAddress(encodedAddress, &resolvedAddress, &fixupKind)) + { + switch (fixupKind) + { + case READYTORUN_FIXUP_StringHandle: + { + TADDR objectAddress = VolatileLoadWithoutBarrier(dac_cast(resolvedAddress)); + RETURN(objectAddress != 0 ? ObjectToOBJECTREF(dac_cast(objectAddress)) : NULL); + } + case READYTORUN_FIXUP_TypeHandle: + case READYTORUN_FIXUP_TypeDictionary: + { + TypeHandle th = TypeHandle::FromTAddr(resolvedAddress); + if (th.IsNull()) + COMPlusThrow(kInvalidProgramException); + RETURN(th.GetManagedClassObject()); + } + default: + COMPlusThrow(kInvalidProgramException); + } + } + + OBJECTREF materialized = MaterializeReadyToRunPreinitializedClassData(encodedAddress, pCache); + RETURN(materialized); +} + +void MethodTable::ApplyReadyToRunPreinitializedData(DynamicStaticsInfo* pDynamicStaticsInfo) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + PRECONDITION(CheckPointer(pDynamicStaticsInfo)); + } + CONTRACTL_END; + + PTR_BYTE pPreinitializedData = NULL; + DWORD cbPreinitializedData = 0; + + if (pDynamicStaticsInfo->GetNonGCStaticsPointer() != NULL && + GetModule()->TryGetReadyToRunPreinitializedNonGCStaticsData(this, &pPreinitializedData, &cbPreinitializedData)) + { + memcpy(pDynamicStaticsInfo->GetNonGCStaticsPointer(), pPreinitializedData, cbPreinitializedData); + + ApproxFieldDescIterator fdIterator(this, ApproxFieldDescIterator::IteratorType::STATIC_FIELDS); + PTR_FieldDesc pField; + while ((pField = fdIterator.Next()) != NULL) + { + CorElementType fieldType = pField->GetFieldType(); + + if (pField->IsSpecialStatic() || + fieldType == ELEMENT_TYPE_CLASS) + { + continue; + } + + PTR_BYTE pFieldAddress = pDynamicStaticsInfo->GetNonGCStaticsPointer() + pField->GetOffset(); + + if (IsPointerLikeCorElementType(fieldType)) + { + PTR_TADDR pPointerFieldAddress = dac_cast(pFieldAddress); + *pPointerFieldAddress = ResolveReadyToRunEncodedPointer(*pPointerFieldAddress); + continue; + } + + if (fieldType == ELEMENT_TYPE_VALUETYPE) + { + TypeHandle fieldTypeHandle = pField->GetApproxFieldTypeHandleThrowing(); + if (!fieldTypeHandle.IsNull() && !fieldTypeHandle.IsTypeDesc()) + { + MethodTable *pFieldType = fieldTypeHandle.AsMethodTable(); + if (pFieldType->IsValueType()) + ResolveReadyToRunEncodedPointersInValueTypeData(pFieldAddress, pFieldType); + } + } + } + } + + if (pDynamicStaticsInfo->GetGCStaticsPointer() != NULL && + GetModule()->TryGetReadyToRunPreinitializedGCStaticsData(this, &pPreinitializedData, &cbPreinitializedData)) + { + DWORD cRegularGCStatics = GetClass()->GetNumHandleRegularStatics(); + + GCX_COOP(); + + PTR_OBJECTREF pGCStatics = pDynamicStaticsInfo->GetGCStaticsPointer(); + PTR_TADDR pGCPreinitializedData = dac_cast(pPreinitializedData); + + MaterializedPreinitializedObjectCache cache{}; + + for (DWORD i = 0; i < cRegularGCStatics; i++) + { + OBJECTREF value = ResolveReadyToRunPreinitializedObjectReference(pGCPreinitializedData[i], &cache); + SetObjectReference(pGCStatics + i, value); + } + } +} + void MethodTable::EnsureStaticDataAllocated() { CONTRACTL @@ -3924,6 +4500,8 @@ void MethodTable::EnsureStaticDataAllocated() if (pDynamicStaticsInfo->GetGCStaticsPointer() == NULL) GetLoaderAllocator()->AllocateGCHandlesBytesForStaticVariables(pDynamicStaticsInfo, GetClass()->GetNumHandleRegularStatics(), this->HasBoxedRegularStatics() ? this : NULL, isInitedIfStaticDataAllocated); + + ApplyReadyToRunPreinitializedData(pDynamicStaticsInfo); } pAuxiliaryData->SetIsStaticDataAllocated(isInitedIfStaticDataAllocated); } @@ -3964,8 +4542,32 @@ bool MethodTable::IsInitedIfStaticDataAllocated() if (HasClassConstructor()) { - // If there is a class constructor, then the class cannot be preinitted. - return false; + if (!GetModule()->IsReadyToRunTypePreinitialized(this)) + return false; + + PTR_BYTE pPreinitializedData = NULL; + DWORD cbPreinitializedData = 0; + + DWORD cbExpectedNonGCStatics = GetClass()->GetNonGCRegularStaticFieldBytes(); + if (cbExpectedNonGCStatics != 0 && + (!GetModule()->TryGetReadyToRunPreinitializedNonGCStaticsData(this, &pPreinitializedData, &cbPreinitializedData) || + (cbPreinitializedData != cbExpectedNonGCStatics))) + { + return false; + } + + DWORD cExpectedGCStatics = GetClass()->GetNumHandleRegularStatics(); + if (cExpectedGCStatics != 0) + { + DWORD cbExpectedGCStatics = cExpectedGCStatics * sizeof(TADDR); + if (!GetModule()->TryGetReadyToRunPreinitializedGCStaticsData(this, &pPreinitializedData, &cbPreinitializedData) || + (cbPreinitializedData != cbExpectedGCStatics)) + { + return false; + } + } + + return true; } if (GetClass()->GetNonGCRegularStaticFieldBytes() == 0 && GetClass()->GetNumHandleRegularStatics() == 0) diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 5bccec784652dc..b3ad02bf3292ed 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -1224,6 +1224,20 @@ class MethodTable private: bool IsInitedIfStaticDataAllocated(); + + // ReadyToRun statics pre-initialization + + class MaterializedPreinitializedObjectCache; + + bool TryResolveReadyToRunImportCellAddress(TADDR encodedAddress, TADDR *pResolvedAddress, BYTE *pFixupKind); + TADDR ResolveReadyToRunEncodedPointer(TADDR encodedAddress, BYTE *pFixupKind = nullptr); + void ResolveReadyToRunEncodedPointersInValueTypeData(PTR_BYTE pValueData, MethodTable *pValueType); + OBJECTREF ResolveReadyToRunPreinitializedObjectReference(TADDR encodedAddress, MaterializedPreinitializedObjectCache* pCache); + OBJECTREF MaterializeReadyToRunPreinitializedClassData(TADDR templateDataAddress, MaterializedPreinitializedObjectCache* pCache); + void MaterializeReadyToRunPreinitializedValueTypeData(PTR_BYTE pValueData, PTR_BYTE pTemplateData, MethodTable *pValueType, MaterializedPreinitializedObjectCache* pCache); + + void ApplyReadyToRunPreinitializedData(DynamicStaticsInfo* pDynamicStaticsInfo); + public: // Is the MethodTable current initialized, and/or can the runtime initialize the MethodTable // without running any user code. (This function may allocate memory, and may throw OutOfMemory) diff --git a/src/coreclr/vm/readytoruninfo.cpp b/src/coreclr/vm/readytoruninfo.cpp index 3cd42e18b8ff5a..5fd94c07d25e40 100644 --- a/src/coreclr/vm/readytoruninfo.cpp +++ b/src/coreclr/vm/readytoruninfo.cpp @@ -1004,6 +1004,12 @@ ReadyToRunInfo::ReadyToRunInfo(Module * pModule, LoaderAllocator* pLoaderAllocat { pModule->m_pMethodIsGenericMap = (ReadyToRun_MethodIsGenericMap*)m_pComposite->GetImage()->GetDirectoryData(methodIsGenericMap); } + + IMAGE_DATA_DIRECTORY *typePreinitializationMap = m_component.FindSection(ReadyToRunSectionType::TypePreinitializationMap); + if (typePreinitializationMap != NULL) + { + pModule->m_pTypePreinitializationMap = (ReadyToRun_TypePreinitializationMap*)m_pComposite->GetImage()->GetDirectoryData(typePreinitializationMap); + } } static bool SigMatchesMethodDesc(MethodDesc* pMD, SigPointer &sig, ModuleBase * pModule) @@ -1870,6 +1876,7 @@ ModuleBase* CreateNativeManifestModule(LoaderAllocator* pLoaderAllocator, IMDInt const ReadyToRun_EnclosingTypeMap ReadyToRun_EnclosingTypeMap::EmptyInstance; const ReadyToRun_TypeGenericInfoMap ReadyToRun_TypeGenericInfoMap::EmptyInstance; const ReadyToRun_MethodIsGenericMap ReadyToRun_MethodIsGenericMap::EmptyInstance; +const ReadyToRun_TypePreinitializationMap ReadyToRun_TypePreinitializationMap::EmptyInstance; mdTypeDef ReadyToRun_EnclosingTypeMap::GetEnclosingType(mdTypeDef input, IMDInternalImport* pImport) const { @@ -2039,6 +2046,177 @@ bool ReadyToRun_MethodIsGenericMap::IsGeneric(mdMethodDef input, bool *foundResu return false; } +bool ReadyToRun_TypePreinitializationMap::TryGetTypeDefEntry( + mdTypeDef input, + const READYTORUN_TYPE_PREINITIALIZATION_MAP_ENTRY **ppEntry, + bool *foundResult) const +{ +#ifdef DACCESS_COMPILE + *ppEntry = nullptr; + *foundResult = false; + return false; +#else + uint32_t rid = RidFromToken(input); + if (rid == 0) + { + *ppEntry = nullptr; + *foundResult = false; + return false; + } + + const uint8_t *pStart = reinterpret_cast(&TypeCount) + sizeof(uint32_t); + const READYTORUN_TYPE_PREINITIALIZATION_MAP_ENTRY *pEntries = + reinterpret_cast(pStart); + + uint32_t low = 0; + uint32_t high = TypeCount; + while (low < high) + { + uint32_t mid = low + ((high - low) / 2); + uint32_t currentRid = pEntries[mid].TypeDefRid; + if (currentRid < rid) + low = mid + 1; + else + high = mid; + } + + if ((low >= TypeCount) || (pEntries[low].TypeDefRid != rid)) + { + *ppEntry = nullptr; + *foundResult = false; + return false; + } + + *ppEntry = &pEntries[low]; + *foundResult = true; + return true; +#endif +} + +bool ReadyToRun_TypePreinitializationMap::TryGetInstantiationEntry( + Module *pModule, + MethodTable *pMT, + const READYTORUN_TYPE_PREINITIALIZATION_MAP_INSTANTIATION_ENTRY **ppEntry, + bool *foundResult) const +{ +#ifdef DACCESS_COMPILE + *ppEntry = nullptr; + *foundResult = false; + return false; +#else + _ASSERTE(CheckPointer(pModule)); + _ASSERTE(CheckPointer(pMT)); + + *ppEntry = nullptr; + *foundResult = false; + + if (this == &ReadyToRun_TypePreinitializationMap::EmptyInstance) + return false; + + uint32_t rid = RidFromToken(pMT->GetCl()); + if (rid == 0) + return false; + + const READYTORUN_TYPE_PREINITIALIZATION_MAP_ENTRY *pTypeDefEntry = nullptr; + bool typeDefFound = false; + if (!TryGetTypeDefEntry(pMT->GetCl(), &pTypeDefEntry, &typeDefFound) || !typeDefFound) + return false; + + if (pTypeDefEntry->TypeDefRid != rid) + return false; + + uint32_t instantiationStartIndex = pTypeDefEntry->Instantiation.Index; + uint32_t instantiationCountForType = pTypeDefEntry->Instantiation.Count; + if (instantiationCountForType == 0) + return false; + + const uint8_t *pStart = reinterpret_cast(&TypeCount) + sizeof(uint32_t); + const READYTORUN_TYPE_PREINITIALIZATION_MAP_ENTRY *pTypeDefEntries = + reinterpret_cast(pStart); + const uint8_t *pAfterTypeDefEntries = reinterpret_cast(pTypeDefEntries + TypeCount); + + uint32_t instantiationEntryCount = *reinterpret_cast(pAfterTypeDefEntries); + const READYTORUN_TYPE_PREINITIALIZATION_MAP_INSTANTIATION_ENTRY *pInstantiationEntries = + reinterpret_cast(pAfterTypeDefEntries + sizeof(uint32_t)); + + const uint8_t *pSignatureBlob = reinterpret_cast(pInstantiationEntries + instantiationEntryCount); + + if (instantiationStartIndex > instantiationEntryCount) + return false; + + if (instantiationCountForType > (instantiationEntryCount - instantiationStartIndex)) + return false; + + ZapSig::Context zapSigContext(pModule, pModule); + for (uint32_t index = 0; index < instantiationCountForType; index++) + { + const READYTORUN_TYPE_PREINITIALIZATION_MAP_INSTANTIATION_ENTRY *pCurrentEntry = &pInstantiationEntries[instantiationStartIndex + index]; + + if (pCurrentEntry->TypeSignatureLength == 0) + continue; + + PCCOR_SIGNATURE pSignature = reinterpret_cast(pSignatureBlob + pCurrentEntry->TypeSignatureOffset); + SigPointer sp(pSignature, pCurrentEntry->TypeSignatureLength); + if (FAILED(sp.SkipExactlyOne()) || (sp.GetPtr() != (pSignature + pCurrentEntry->TypeSignatureLength))) + continue; + + if (!ZapSig::CompareSignatureToTypeHandle(pSignature, pModule, TypeHandle(pMT), &zapSigContext)) + continue; + + *ppEntry = pCurrentEntry; + *foundResult = true; + return true; + } + + return false; +#endif +} + +bool ReadyToRun_TypePreinitializationMap::IsTypePreinitialized(Module *pModule, MethodTable *pMT, bool *foundResult) const +{ + if (pMT->HasInstantiation()) + { + const READYTORUN_TYPE_PREINITIALIZATION_MAP_INSTANTIATION_ENTRY *pEntry; + if (!TryGetInstantiationEntry(pModule, pMT, &pEntry, foundResult)) + return false; + + return (static_cast(pEntry->Flags) & static_cast(ReadyToRunTypePreinitializationFlags::TypeIsPreinitialized)) != 0; + } + + const READYTORUN_TYPE_PREINITIALIZATION_MAP_ENTRY *pEntry; + if (!TryGetTypeDefEntry(pMT->GetCl(), &pEntry, foundResult)) + return false; + + return (static_cast(pEntry->Flags) & static_cast(ReadyToRunTypePreinitializationFlags::TypeIsPreinitialized)) != 0; +} + +bool ReadyToRun_TypePreinitializationMap::TryGetNonGCData( + Module *pModule, + MethodTable *pMT, + uint32_t *pDataRva, + uint32_t *pDataSize, + bool *foundResult) const +{ + if (pMT->HasInstantiation()) + { + const READYTORUN_TYPE_PREINITIALIZATION_MAP_INSTANTIATION_ENTRY *pEntry; + if (!TryGetInstantiationEntry(pModule, pMT, &pEntry, foundResult)) + return false; + + *pDataRva = pEntry->NonGCDataRva; + *pDataSize = pEntry->NonGCDataSize; + return true; + } + + const READYTORUN_TYPE_PREINITIALIZATION_MAP_ENTRY *pEntry; + if (!TryGetTypeDefEntry(pMT->GetCl(), &pEntry, foundResult)) + return false; + + *pDataRva = pEntry->NonGCData.Rva; + *pDataSize = pEntry->NonGCData.Size; + return true; +} + #ifndef DACCESS_COMPILE #ifdef FEATURE_STUBPRECODE_DYNAMIC_HELPERS diff --git a/src/coreclr/vm/readytoruninfo.h b/src/coreclr/vm/readytoruninfo.h index 9f58e81c736c93..0b3b36ba31ed22 100644 --- a/src/coreclr/vm/readytoruninfo.h +++ b/src/coreclr/vm/readytoruninfo.h @@ -21,6 +21,7 @@ typedef DPTR(struct READYTORUN_SECTION) PTR_READYTORUN_SECTION; class NativeImage; class PrepareCodeConfig; class ReadyToRunLoadedImage; +class MethodTable; typedef DPTR(class ReadyToRunCoreInfo) PTR_ReadyToRunCoreInfo; typedef DPTR(class ReadyToRunLoadedImage) PTR_ReadyToRunLoadedImage; @@ -97,6 +98,21 @@ class ReadyToRun_MethodIsGenericMap bool IsGeneric(mdMethodDef input, bool *foundResult) const; }; +class ReadyToRun_TypePreinitializationMap +{ + uint32_t TypeCount = 0; + ReadyToRun_TypePreinitializationMap() = default; + bool TryGetTypeDefEntry(mdTypeDef input, const READYTORUN_TYPE_PREINITIALIZATION_MAP_ENTRY **ppEntry, bool *foundResult) const; + bool TryGetInstantiationEntry(Module *pModule, MethodTable *pMT, const READYTORUN_TYPE_PREINITIALIZATION_MAP_INSTANTIATION_ENTRY **ppEntry, bool *foundResult) const; +public: + ReadyToRun_TypePreinitializationMap& operator=(const ReadyToRun_TypePreinitializationMap& other) = delete; + ReadyToRun_TypePreinitializationMap(const ReadyToRun_TypePreinitializationMap& other) = delete; + + const static ReadyToRun_TypePreinitializationMap EmptyInstance; + bool IsTypePreinitialized(Module *pModule, MethodTable *pMT, bool *foundResult) const; + bool TryGetNonGCData(Module *pModule, MethodTable *pMT, uint32_t *pDataRva, uint32_t *pDataSize, bool *foundResult) const; +}; + class ReadyToRunInfo { friend class ReadyToRunJitManager; From b3ebd6a05e0e88ccade5d3d190cfe3b204a33334 Mon Sep 17 00:00:00 2001 From: Steve Date: Thu, 19 Feb 2026 04:46:33 +0900 Subject: [PATCH 02/11] Revert unnecessary helper expansion --- src/coreclr/jit/helperexpansion.cpp | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/coreclr/jit/helperexpansion.cpp b/src/coreclr/jit/helperexpansion.cpp index f290f9ca9fe4b8..8bb9f8feb2a147 100644 --- a/src/coreclr/jit/helperexpansion.cpp +++ b/src/coreclr/jit/helperexpansion.cpp @@ -1436,7 +1436,7 @@ bool Compiler::fgExpandStaticInitForCall(BasicBlock** pBlock, Statement* stmt, G // | \--* CNS_INT int -8 (offset) // \--* CNS_INT int 0 // - assert((flagAddr.accessType == IAT_VALUE) || (flagAddr.accessType == IAT_PVALUE)); + assert(flagAddr.accessType == IAT_VALUE); GenTree* cachedStaticBase = nullptr; GenTree* isInitedActualValueNode; @@ -1463,17 +1463,7 @@ bool Compiler::fgExpandStaticInitForCall(BasicBlock** pBlock, Statement* stmt, G { assert(isInitOffset == 0); - if (flagAddr.accessType == IAT_VALUE) - { - isInitedActualValueNode = gtNewIndOfIconHandleNode(TYP_INT, (size_t)flagAddr.addr, GTF_ICON_GLOBAL_PTR); - } - else - { - assert(flagAddr.accessType == IAT_PVALUE); - GenTree* flagAddrNode = gtNewIndOfIconHandleNode(TYP_I_IMPL, (size_t)flagAddr.addr, GTF_ICON_GLOBAL_PTR); - isInitedActualValueNode = gtNewIndir(TYP_INT, flagAddrNode, GTF_IND_NONFAULTING); - } - + isInitedActualValueNode = gtNewIndOfIconHandleNode(TYP_INT, (size_t)flagAddr.addr, GTF_ICON_GLOBAL_PTR); isInitedActualValueNode->gtFlags |= GTF_IND_VOLATILE; isInitedActualValueNode->SetHasOrderingSideEffect(); From 8611d11aed931d5a6e5414359fca1c8dcc102f32 Mon Sep 17 00:00:00 2001 From: Steve Date: Thu, 19 Feb 2026 20:08:50 +0900 Subject: [PATCH 03/11] Apply suggestions from code review Co-authored-by: Jeremy Koritzinsky --- docs/design/features/readytorun-preinitialized-statics.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/design/features/readytorun-preinitialized-statics.md b/docs/design/features/readytorun-preinitialized-statics.md index 938a19417846c9..446bbf6c6578ea 100644 --- a/docs/design/features/readytorun-preinitialized-statics.md +++ b/docs/design/features/readytorun-preinitialized-statics.md @@ -8,7 +8,7 @@ Preinitialized statics feature was added in R2R format version **18.2**. ReadyToRun now can interpret eligible `.cctor` bodies at compile time, serialize the resulting static state into the R2R image, and mark types as preinitialized so runtime class-init can be skipped. -The interpreter reuses the one in ilc so that any shape of `.cctor` that ilc can preinitialize is also supported in R2R, with some additional constraints. There're various limitations on what can be preinitialized, and the supported scenarios are listed as follows. +The preintialization interpreter is shared with the NativeAOT ILCompiler, so class constructors that can be preinitialized for NativeAOT are also supported in R2R, with some additional constraints. There're various limitations on what can be preinitialized, and the supported scenarios are listed as follows. | Scenario | Support | | --- | --- | @@ -78,7 +78,7 @@ Section payload emitted by `TypePreinitializationMapNode`: 4. `InstantiationEntryCount * READYTORUN_TYPE_PREINITIALIZATION_MAP_INSTANTIATION_ENTRY` in TypeDef-order 5. Concatenated instantiation type-signature blob bytes -The section contains two tables: a TypeDef table and an instantiation table. For each TypeDef row, payload fields are interpreted as either `NonGCData.Rva/Size` (non-generic definition) or `Instantiation.Index/Count` (generic definition). Generic definitions do not have their own statics storage. +The section contains two tables: a TypeDef table and an instantiation table. For each TypeDef row, payload fields are interpreted as either `NonGCData.Rva/Size` (when the type is not a generic definition) or `Instantiation.Index/Count` (generic definition type). Generic definitions do not have their own statics storage. TypeDef rows are sorted by `TypeDefRid`. Instantiation rows are sorted first by owner `TypeDefRid`, then by lexicographic signature bytes. @@ -152,7 +152,7 @@ To ensure the map covers needed generic instantiations, static-base helper paths Object graphs for GC statics are emitted as templates where the first pointer is the object type handle/method table fixup and subsequent bytes encode fields/elements. Reference fields are emitted via relocations to other serialized templates, string imports, or runtime-type imports. Pointer-like non-reference fields can carry encoded import pointers. -Delegate serialization in R2R mode supports both closed static and closed instance delegates. Open static delegates are rejected due to no available token, as emitted IL stub is not an EcmaMethod; and open instance delegates are currently not implemented. +Delegate serialization in R2R mode supports both closed static and closed instance delegates. Open static delegates are rejected due to no available token, as the emitted IL stub is not an `EcmaMethod`; and open instance delegates are currently not implemented. ### Loader and map attachment From e7c04fcd00d430b599d8a38456fd6c0379048543 Mon Sep 17 00:00:00 2001 From: Steven He Date: Thu, 19 Feb 2026 20:18:34 +0900 Subject: [PATCH 04/11] Allocate GC statics in frozen object heap --- docs/design/features/readytorun-preinitialized-statics.md | 6 ++++-- src/coreclr/vm/methodtable.cpp | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/design/features/readytorun-preinitialized-statics.md b/docs/design/features/readytorun-preinitialized-statics.md index 446bbf6c6578ea..613892c6ac0659 100644 --- a/docs/design/features/readytorun-preinitialized-statics.md +++ b/docs/design/features/readytorun-preinitialized-statics.md @@ -17,6 +17,8 @@ The preintialization interpreter is shared with the NativeAOT ILCompiler, so cla | Generic instantiations | Concrete, non-canonical instantiations that can be statically resolved | | Delegates | Supported for closed delegates | +A restriction for R2R is that the preinitializer cannot inline any methods that cross the version bubble. + ## Enabling In crossgen2, preinitialized statics can be controlled with: @@ -146,7 +148,7 @@ This is best-effort: if serialization/validation of the preinitialized graph fai Generic instantiations are supported on a best-effort basis. Only concrete, non-canonical, non-runtime-determined instantiations that are statically referenced from the code are recorded in the map and have preinitialization support. -To ensure the map covers needed generic instantiations, static-base helper paths trigger record materialization in `GenericLookupResult`, `ReadyToRunGenericHelperNode`, and `ReadyToRunSymbolNodeFactory`. +To ensure the map covers needed generic instantiations, static-base helper paths trigger record materialization in `ReadyToRunSymbolNodeFactory`. ### Serialized object templates @@ -197,4 +199,4 @@ When decoding encoded references in GC payloads, runtime currently accepts impor ### GC object allocation strategy -We may be able to allocate GC objects in Frozen Object Heap, but it comes with the complication of collectible vs non-collectible types, as well as the fact that frozen objects cannot reference non-frozen objects. For simplicity, the initial implementation allocates GC statics in regular GC heap, and we can explore frozen heap support in the future if needed. +The preinitializer will reject cases where a GC static field contains a reference to non-frozen objects. As a result we can safely allocate GC static objects in the frozen object heap, and allow them to be accessed directly. diff --git a/src/coreclr/vm/methodtable.cpp b/src/coreclr/vm/methodtable.cpp index 7cdb63ca1a39c1..4111e193672a46 100644 --- a/src/coreclr/vm/methodtable.cpp +++ b/src/coreclr/vm/methodtable.cpp @@ -4226,7 +4226,8 @@ OBJECTREF MethodTable::MaterializeReadyToRunPreinitializedClassData(TADDR templa _ASSERTE(!pObjectType->IsMultiDimArray()); INT32 arrayLength = GET_UNALIGNED_VAL32((PTR_BYTE)(templateDataAddress + TARGET_POINTER_SIZE)); - OBJECTREF arrayRef = AllocateSzArray(pObjectType, arrayLength); + OBJECTREF arrayRef = TryAllocateFrozenSzArray(pObjectType, arrayLength); + _ASSERTE(arrayRef != NULL); GCPROTECT_BEGIN(arrayRef); { @@ -4297,7 +4298,8 @@ OBJECTREF MethodTable::MaterializeReadyToRunPreinitializedClassData(TADDR templa RETURN(arrayRef); } - OBJECTREF objectRef = AllocateObject(pObjectType); + OBJECTREF objectRef = TryAllocateFrozenObject(pObjectType); + _ASSERTE(objectRef != NULL); GCPROTECT_BEGIN(objectRef); { From c703338ce2c235e452d6368b96f5d5c103a00c2d Mon Sep 17 00:00:00 2001 From: Steven He Date: Thu, 19 Feb 2026 20:19:16 +0900 Subject: [PATCH 05/11] Fix a type --- docs/design/features/readytorun-preinitialized-statics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/features/readytorun-preinitialized-statics.md b/docs/design/features/readytorun-preinitialized-statics.md index 613892c6ac0659..28b2afbe783862 100644 --- a/docs/design/features/readytorun-preinitialized-statics.md +++ b/docs/design/features/readytorun-preinitialized-statics.md @@ -8,7 +8,7 @@ Preinitialized statics feature was added in R2R format version **18.2**. ReadyToRun now can interpret eligible `.cctor` bodies at compile time, serialize the resulting static state into the R2R image, and mark types as preinitialized so runtime class-init can be skipped. -The preintialization interpreter is shared with the NativeAOT ILCompiler, so class constructors that can be preinitialized for NativeAOT are also supported in R2R, with some additional constraints. There're various limitations on what can be preinitialized, and the supported scenarios are listed as follows. +The preinitialization interpreter is shared with the NativeAOT ILCompiler, so class constructors that can be preinitialized for NativeAOT are also supported in R2R, with some additional constraints. There're various limitations on what can be preinitialized, and the supported scenarios are listed as follows. | Scenario | Support | | --- | --- | From 5ec045551393c6b25aa7fc2dd3a6d17650a9b253 Mon Sep 17 00:00:00 2001 From: Steven He Date: Thu, 19 Feb 2026 21:35:54 +0900 Subject: [PATCH 06/11] Move some files --- .../Compiler/PreinitializationManager.cs | 0 .../Compiler/ReadOnlyFieldPolicy.cs | 0 .../Compiler/TypePreinit.cs | 11 +++++++---- .../ILCompiler.Compiler/ILCompiler.Compiler.csproj | 6 +++--- .../ReadyToRun/TypePreinitializedStaticsDataNode.cs | 10 ++-------- .../ReadyToRunPreinitializationManager.cs | 2 +- .../ILCompiler.ReadyToRun.csproj | 6 +++--- 7 files changed, 16 insertions(+), 19 deletions(-) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/PreinitializationManager.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ReadOnlyFieldPolicy.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/TypePreinit.cs (99%) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/PreinitializationManager.cs b/src/coreclr/tools/Common/Compiler/PreinitializationManager.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/PreinitializationManager.cs rename to src/coreclr/tools/Common/Compiler/PreinitializationManager.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ReadOnlyFieldPolicy.cs b/src/coreclr/tools/Common/Compiler/ReadOnlyFieldPolicy.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ReadOnlyFieldPolicy.cs rename to src/coreclr/tools/Common/Compiler/ReadOnlyFieldPolicy.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs b/src/coreclr/tools/Common/Compiler/TypePreinit.cs similarity index 99% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs rename to src/coreclr/tools/Common/Compiler/TypePreinit.cs index 3b6dade84fdeb4..1f7a3b8612d709 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs +++ b/src/coreclr/tools/Common/Compiler/TypePreinit.cs @@ -3251,7 +3251,7 @@ public override void GetConditionalDependencies(ref CombinedDependencyList depen public void WriteContent(ref ObjectDataBuilder builder, ISymbolNode thisNode, NodeFactory factory) { - Debug.Assert(_methodPointed.Signature.IsStatic || _firstParameter != null); + Debug.Assert(_methodPointed.Signature.IsStatic == (_firstParameter == null)); DelegateCreationInfo creationInfo = GetDelegateCreationInfo(factory); @@ -3755,17 +3755,18 @@ public bool TryGetFieldValue(TypePreinit context, FieldDesc field, out Value val public class PreinitializationInfo { - private Dictionary _fieldValues; + private readonly Dictionary _fieldValues; public MetadataType Type { get; } public string FailureReason { get; private set; } - public bool IsPreinitialized => _fieldValues != null; + public bool IsPreinitialized { get; private set; } public PreinitializationInfo(MetadataType type, IEnumerable> fieldValues) { Type = type; + IsPreinitialized = true; _fieldValues = new Dictionary(); foreach (var field in fieldValues) _fieldValues.Add(field.Key, field.Value); @@ -3775,6 +3776,7 @@ public PreinitializationInfo(MetadataType type, string failureReason) { Type = type; FailureReason = failureReason; + IsPreinitialized = false; } public ISerializableValue GetFieldValue(FieldDesc field) @@ -3789,8 +3791,9 @@ public void SetPostScanFailure(string failureReason) { if (!IsPreinitialized) return; + _fieldValues.Clear(); FailureReason = failureReason; - _fieldValues = null; + IsPreinitialized = false; } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index 8d138b97ce980e..7a245391249e37 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -631,7 +631,7 @@ - + @@ -685,9 +685,9 @@ - + - + diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypePreinitializedStaticsDataNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypePreinitializedStaticsDataNode.cs index 88200b24aa371f..3bf0b1f943929d 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypePreinitializedStaticsDataNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypePreinitializedStaticsDataNode.cs @@ -72,9 +72,6 @@ public static int ComputeGCStaticsDataSize(MetadataType type) public int GCStaticsDataSize(TypeSystemContext context) => ComputeGCStaticsDataSize(Type); - public int AlignedNonGCStaticsDataSize(TargetDetails target) - => AlignmentHelper.AlignUp(NonGCStaticsDataSize, target.PointerSize); - public static bool IsNonGCStaticField(FieldDesc field) => field.IsStatic && !field.HasRva && !field.IsLiteral && !field.IsThreadStatic && !field.HasGCStaticBase; @@ -166,10 +163,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) if (gcDataSize > 0) { // GC static payload is read as pointer-sized slots at runtime. - int alignedNonGCDataSize = AlignedNonGCStaticsDataSize(factory.Target); - int currentNonGCDataSize = builder.CountBytes - initialOffset; - if (currentNonGCDataSize < alignedNonGCDataSize) - builder.EmitZeros(alignedNonGCDataSize - currentNonGCDataSize); + builder.PadAlignment(factory.Target.PointerSize); int gcInitialOffset = 0; int gcStartOffset = builder.CountBytes; @@ -195,7 +189,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) int totalSize = builder.CountBytes - initialOffset; Debug.Assert(totalSize >= NonGCStaticsDataSize); - Debug.Assert(gcDataSize == 0 || totalSize >= AlignedNonGCStaticsDataSize(factory.Target) + gcDataSize); + Debug.Assert(gcDataSize == 0 || totalSize >= AlignmentHelper.AlignUp(NonGCStaticsDataSize, factory.Target.PointerSize) + gcDataSize); Debug.Assert(totalSize >= 0); return builder.ToObjectData(); } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/ReadyToRunPreinitializationManager.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/ReadyToRunPreinitializationManager.cs index ecc6e7a168e9bb..d8d1a6506301c5 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/ReadyToRunPreinitializationManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/ReadyToRunPreinitializationManager.cs @@ -114,7 +114,7 @@ public TypePreinitializationRecord GetTypeRecord(MetadataType type) TypePreinitializationRecord computed = ComputeTypeRecord(type); lock (_typeRecords) { - // It's possible another thread computed the same type record while we were computing, so check again before adding and rooting. + // It's possible another thread computed the same type record while we were computing, so check again before adding. if (_typeRecords.TryGetValue(type, out TypePreinitializationRecord existing)) { return existing; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index f409aa7b41f0c5..27c4b89086fc88 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -173,6 +173,9 @@ + + + @@ -204,9 +207,6 @@ - - - From 261509692912f46fd4bfd593eddd8382c6b74e33 Mon Sep 17 00:00:00 2001 From: Steven He Date: Thu, 19 Feb 2026 22:27:30 +0900 Subject: [PATCH 07/11] Address more feedbacks --- .../Compiler/PreinitializationManager.cs | 4 --- .../tools/Common/Compiler/TypePreinit.cs | 28 ++----------------- .../Compiler/Dataflow/FlowAnnotations.cs | 2 +- .../TypePreinitializationMapNode.cs | 5 ---- src/coreclr/tools/aot/crossgen2/Program.cs | 2 ++ 5 files changed, 5 insertions(+), 36 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/PreinitializationManager.cs b/src/coreclr/tools/Common/Compiler/PreinitializationManager.cs index 14d525d53935c8..f885f77bc60894 100644 --- a/src/coreclr/tools/Common/Compiler/PreinitializationManager.cs +++ b/src/coreclr/tools/Common/Compiler/PreinitializationManager.cs @@ -6,11 +6,7 @@ using Internal.IL; using Internal.TypeSystem; -#if READYTORUN -using FlowAnnotations = ILCompiler.FlowAnnotations; -#else using FlowAnnotations = ILLink.Shared.TrimAnalysis.FlowAnnotations; -#endif namespace ILCompiler { diff --git a/src/coreclr/tools/Common/Compiler/TypePreinit.cs b/src/coreclr/tools/Common/Compiler/TypePreinit.cs index 1f7a3b8612d709..b2924f954809d6 100644 --- a/src/coreclr/tools/Common/Compiler/TypePreinit.cs +++ b/src/coreclr/tools/Common/Compiler/TypePreinit.cs @@ -13,11 +13,7 @@ using Internal.TypeSystem.Ecma; using CombinedDependencyList = System.Collections.Generic.List.CombinedDependencyListEntry>; -#if READYTORUN -using FlowAnnotations = ILCompiler.FlowAnnotations; -#else using FlowAnnotations = ILLink.Shared.TrimAnalysis.FlowAnnotations; -#endif namespace ILCompiler { @@ -3259,9 +3255,6 @@ public void WriteContent(ref ObjectDataBuilder builder, ISymbolNode thisNode, No // MethodTable var node = factory.ConstructedTypeSymbol(Type); -#if !READYTORUN - Debug.Assert(!node.RepresentsIndirectionCell); // Shouldn't have allowed this -#endif builder.EmitPointerReloc(node); #if READYTORUN @@ -3413,9 +3406,6 @@ public void WriteContent(ref ObjectDataBuilder builder, ISymbolNode thisNode, No { // MethodTable var node = factory.ConstructedTypeSymbol(Type); -#if !READYTORUN - Debug.Assert(!node.RepresentsIndirectionCell); // Arrays are always local -#endif builder.EmitPointerReloc(node); // numComponents @@ -3596,9 +3586,6 @@ public void WriteContent(ref ObjectDataBuilder builder, ISymbolNode thisNode, No { // MethodTable var node = factory.ConstructedTypeSymbol(Type); -#if !READYTORUN - Debug.Assert(!node.RepresentsIndirectionCell); // Shouldn't have allowed preinitializing this -#endif builder.EmitPointerReloc(node); // We skip the first pointer because that's the MethodTable pointer @@ -3759,14 +3746,13 @@ public class PreinitializationInfo public MetadataType Type { get; } - public string FailureReason { get; private set; } + public string FailureReason { get; } - public bool IsPreinitialized { get; private set; } + public bool IsPreinitialized => _fieldValues != null; public PreinitializationInfo(MetadataType type, IEnumerable> fieldValues) { Type = type; - IsPreinitialized = true; _fieldValues = new Dictionary(); foreach (var field in fieldValues) _fieldValues.Add(field.Key, field.Value); @@ -3776,7 +3762,6 @@ public PreinitializationInfo(MetadataType type, string failureReason) { Type = type; FailureReason = failureReason; - IsPreinitialized = false; } public ISerializableValue GetFieldValue(FieldDesc field) @@ -3786,15 +3771,6 @@ public ISerializableValue GetFieldValue(FieldDesc field) Debug.Assert(field.IsStatic && !field.HasRva && !field.IsThreadStatic && !field.IsLiteral); return _fieldValues[field]; } - - public void SetPostScanFailure(string failureReason) - { - if (!IsPreinitialized) return; - - _fieldValues.Clear(); - FailureReason = failureReason; - IsPreinitialized = false; - } } public abstract class TypePreinitializationPolicy diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Dataflow/FlowAnnotations.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Dataflow/FlowAnnotations.cs index 8cfe3639741140..fa49098686bd3c 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Dataflow/FlowAnnotations.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Dataflow/FlowAnnotations.cs @@ -3,7 +3,7 @@ using Internal.TypeSystem; -namespace ILCompiler +namespace ILLink.Shared.TrimAnalysis { public sealed class FlowAnnotations { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypePreinitializationMapNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypePreinitializationMapNode.cs index 0492c08fb2a183..ce69bbee22ef04 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypePreinitializationMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypePreinitializationMapNode.cs @@ -114,11 +114,6 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) { MetadataType type = (MetadataType)_module.GetType(typeHandle); var record = factory.ReadyToRunPreinitializationManager.GetTypeRecord(type); - if (factory.PreinitializationManager.IsPreinitialized(type) && !record.IsPreinitialized) - { - factory.PreinitializationManager.GetPreinitializationInfo(type).SetPostScanFailure(record.FailureReason); - } - uint typeDefRid = (uint)MetadataTokens.GetRowNumber(typeHandle); // TypeDef row: READYTORUN_TYPE_PREINITIALIZATION_MAP_ENTRY diff --git a/src/coreclr/tools/aot/crossgen2/Program.cs b/src/coreclr/tools/aot/crossgen2/Program.cs index 8c24e1c345169b..8bdf26439b9401 100644 --- a/src/coreclr/tools/aot/crossgen2/Program.cs +++ b/src/coreclr/tools/aot/crossgen2/Program.cs @@ -22,6 +22,8 @@ using ILCompiler.DependencyAnalysis; using ILCompiler.IBC; +using FlowAnnotations = ILLink.Shared.TrimAnalysis.FlowAnnotations; + namespace ILCompiler { internal sealed class Program From 129705048c8b4d907c08b9f5f023925c5307640f Mon Sep 17 00:00:00 2001 From: Steven He Date: Thu, 19 Feb 2026 22:28:27 +0900 Subject: [PATCH 08/11] Nit --- src/coreclr/tools/r2rdump/TextDumper.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/coreclr/tools/r2rdump/TextDumper.cs b/src/coreclr/tools/r2rdump/TextDumper.cs index 9a15d6550a471e..317938733bfbc4 100644 --- a/src/coreclr/tools/r2rdump/TextDumper.cs +++ b/src/coreclr/tools/r2rdump/TextDumper.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Drawing; using System.IO; using System.Linq; using System.Reflection.Metadata; From c34163bddf55b1d0643d3ebc4c3e84bfcd4d2d4a Mon Sep 17 00:00:00 2001 From: Steven He Date: Thu, 19 Feb 2026 23:27:05 +0900 Subject: [PATCH 09/11] Get rid of unnecessary binary search --- .../readytorun-preinitialized-statics.md | 4 +++- src/coreclr/vm/readytoruninfo.cpp | 20 +++++-------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/docs/design/features/readytorun-preinitialized-statics.md b/docs/design/features/readytorun-preinitialized-statics.md index 28b2afbe783862..430cf51cc14daa 100644 --- a/docs/design/features/readytorun-preinitialized-statics.md +++ b/docs/design/features/readytorun-preinitialized-statics.md @@ -84,7 +84,9 @@ The section contains two tables: a TypeDef table and an instantiation table. For TypeDef rows are sorted by `TypeDefRid`. Instantiation rows are sorted first by owner `TypeDefRid`, then by lexicographic signature bytes. -Runtime binary-searches TypeDef rows by `TypeDefRid`, then uses `Instantiation.Index`/`Instantiation.Count` to linearly compare signatures in that range. +Runtime locates TypeDef rows by `TypeDefRid`, then uses `Instantiation.Index`/`Instantiation.Count` to linearly compare signatures in that range. + +If a module has any preinitialized types, all TypeDef rows in the module are present in the map, even those that are not preinitialized. This allows the runtime to locate the map entry directly without searching by the rid. ## Preinitialized static payload format diff --git a/src/coreclr/vm/readytoruninfo.cpp b/src/coreclr/vm/readytoruninfo.cpp index 5fd94c07d25e40..0e8d80a829cee8 100644 --- a/src/coreclr/vm/readytoruninfo.cpp +++ b/src/coreclr/vm/readytoruninfo.cpp @@ -2057,7 +2057,7 @@ bool ReadyToRun_TypePreinitializationMap::TryGetTypeDefEntry( return false; #else uint32_t rid = RidFromToken(input); - if (rid == 0) + if ((rid == 0) || (rid > TypeCount)) { *ppEntry = nullptr; *foundResult = false; @@ -2068,26 +2068,16 @@ bool ReadyToRun_TypePreinitializationMap::TryGetTypeDefEntry( const READYTORUN_TYPE_PREINITIALIZATION_MAP_ENTRY *pEntries = reinterpret_cast(pStart); - uint32_t low = 0; - uint32_t high = TypeCount; - while (low < high) - { - uint32_t mid = low + ((high - low) / 2); - uint32_t currentRid = pEntries[mid].TypeDefRid; - if (currentRid < rid) - low = mid + 1; - else - high = mid; - } - - if ((low >= TypeCount) || (pEntries[low].TypeDefRid != rid)) + // Table is emitted in TypeDef RID order and contains one row per TypeDef. + const READYTORUN_TYPE_PREINITIALIZATION_MAP_ENTRY *pEntry = &pEntries[rid - 1]; + if (pEntry->TypeDefRid != rid) { *ppEntry = nullptr; *foundResult = false; return false; } - *ppEntry = &pEntries[low]; + *ppEntry = pEntry; *foundResult = true; return true; #endif From 79c7e83237bfbd45bdd51f2b55cf068e4a536856 Mon Sep 17 00:00:00 2001 From: Steven He Date: Thu, 19 Feb 2026 23:38:11 +0900 Subject: [PATCH 10/11] Frozen object fixes --- docs/design/features/readytorun-preinitialized-statics.md | 3 ++- src/coreclr/vm/methodtable.cpp | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/design/features/readytorun-preinitialized-statics.md b/docs/design/features/readytorun-preinitialized-statics.md index 430cf51cc14daa..b9d6db08a1af71 100644 --- a/docs/design/features/readytorun-preinitialized-statics.md +++ b/docs/design/features/readytorun-preinitialized-statics.md @@ -201,4 +201,5 @@ When decoding encoded references in GC payloads, runtime currently accepts impor ### GC object allocation strategy -The preinitializer will reject cases where a GC static field contains a reference to non-frozen objects. As a result we can safely allocate GC static objects in the frozen object heap, and allow them to be accessed directly. +The preinitializer will reject cases where a GC static field contains a reference to non-frozen objects that cannot be serialized. As a result we can safely allocate GC static objects in the frozen object heap, and allow them to be accessed directly. But it's possible that a GC type may contain GC pointers which can fail `TryAllocateFrozenObject`, in which case we will fall back to regular heap allocation. + diff --git a/src/coreclr/vm/methodtable.cpp b/src/coreclr/vm/methodtable.cpp index 4111e193672a46..fe028000f02673 100644 --- a/src/coreclr/vm/methodtable.cpp +++ b/src/coreclr/vm/methodtable.cpp @@ -4227,7 +4227,8 @@ OBJECTREF MethodTable::MaterializeReadyToRunPreinitializedClassData(TADDR templa INT32 arrayLength = GET_UNALIGNED_VAL32((PTR_BYTE)(templateDataAddress + TARGET_POINTER_SIZE)); OBJECTREF arrayRef = TryAllocateFrozenSzArray(pObjectType, arrayLength); - _ASSERTE(arrayRef != NULL); + if (arrayRef == NULL) + arrayRef = AllocateSzArray(pObjectType, arrayLength); GCPROTECT_BEGIN(arrayRef); { @@ -4299,7 +4300,8 @@ OBJECTREF MethodTable::MaterializeReadyToRunPreinitializedClassData(TADDR templa } OBJECTREF objectRef = TryAllocateFrozenObject(pObjectType); - _ASSERTE(objectRef != NULL); + if (objectRef == NULL) + objectRef = AllocateObject(pObjectType); GCPROTECT_BEGIN(objectRef); { From a71503747f9599e06ccab79af27cdbb1aff36053 Mon Sep 17 00:00:00 2001 From: Steven He Date: Sat, 21 Feb 2026 02:21:14 +0900 Subject: [PATCH 11/11] Expand preinitialized statics --- src/coreclr/inc/readytorun.h | 1 + src/coreclr/jit/helperexpansion.cpp | 18 +++- .../Internal/Runtime/ReadyToRunConstants.cs | 2 + .../tools/Common/JitInterface/CorInfoImpl.cs | 8 ++ .../ReadyToRunCodegenNodeFactory.cs | 2 +- .../PreinitializationExtensions.cs | 6 ++ .../ReadyToRunPreinitializationManager.cs | 46 ++++++++ .../JitInterface/CorInfoImpl.ReadyToRun.cs | 101 ++++++++++++++++-- .../ReadyToRunSignature.cs | 5 + src/coreclr/vm/jitinterface.cpp | 34 ++++++ src/coreclr/vm/prestub.cpp | 22 ++++ 11 files changed, 236 insertions(+), 9 deletions(-) diff --git a/src/coreclr/inc/readytorun.h b/src/coreclr/inc/readytorun.h index 23815c896f7cee..d3a88fc63f6737 100644 --- a/src/coreclr/inc/readytorun.h +++ b/src/coreclr/inc/readytorun.h @@ -336,6 +336,7 @@ enum ReadyToRunFixupKind READYTORUN_FIXUP_Check_IL_Body = 0x35, /* Check to see if an IL method is defined the same at runtime as at compile time. A failed match will cause code not to be used. */ READYTORUN_FIXUP_Verify_IL_Body = 0x36, /* Verify an IL body is defined the same at compile time and runtime. A failed match will cause a hard runtime failure. */ READYTORUN_FIXUP_Continuation_Layout = 0x37, /* Layout of an async method continuation type */ + READYTORUN_FIXUP_ClassInitFlags = 0x38, /* Address of class init flags */ READYTORUN_FIXUP_ModuleOverride = 0x80, /* followed by sig-encoded UInt with assemblyref index into either the assemblyref table of the MSIL metadata of the master context module for the signature or */ /* into the extra assemblyref table in the manifest metadata R2R header table (used in cases inlining brings in references to assemblies not seen in the MSIL). */ diff --git a/src/coreclr/jit/helperexpansion.cpp b/src/coreclr/jit/helperexpansion.cpp index 8bb9f8feb2a147..3907e961a9e828 100644 --- a/src/coreclr/jit/helperexpansion.cpp +++ b/src/coreclr/jit/helperexpansion.cpp @@ -1436,13 +1436,13 @@ bool Compiler::fgExpandStaticInitForCall(BasicBlock** pBlock, Statement* stmt, G // | \--* CNS_INT int -8 (offset) // \--* CNS_INT int 0 // - assert(flagAddr.accessType == IAT_VALUE); - GenTree* cachedStaticBase = nullptr; GenTree* isInitedActualValueNode; GenTree* isInitedExpectedValue; if (IsTargetAbi(CORINFO_NATIVEAOT_ABI)) { + assert(flagAddr.accessType == IAT_VALUE); + GenTree* baseAddr = gtNewIconHandleNode((size_t)flagAddr.addr, GTF_ICON_GLOBAL_PTR); // Save it to a temp - we'll be using its value for the replacementNode. @@ -1459,9 +1459,23 @@ bool Compiler::fgExpandStaticInitForCall(BasicBlock** pBlock, Statement* stmt, G // 0 means "initialized" on NativeAOT isInitedExpectedValue = gtNewIconNode(0, TYP_I_IMPL); } + else if (IsReadyToRun()) + { + assert(isInitOffset == 0); + assert(flagAddr.accessType == IAT_PVALUE); + + GenTree* flagAddrNode = gtNewIndOfIconHandleNode(TYP_I_IMPL, (size_t)flagAddr.addr, GTF_ICON_GLOBAL_PTR); + isInitedActualValueNode = gtNewIndir(TYP_INT, flagAddrNode, GTF_IND_NONFAULTING | GTF_IND_VOLATILE); + isInitedActualValueNode->SetHasOrderingSideEffect(); + + // Check ClassInitFlags::INITIALIZED_FLAG bit + isInitedActualValueNode = gtNewOperNode(GT_AND, TYP_INT, isInitedActualValueNode, gtNewIconNode(1)); + isInitedExpectedValue = gtNewIconNode(1); + } else { assert(isInitOffset == 0); + assert(flagAddr.accessType == IAT_VALUE); isInitedActualValueNode = gtNewIndOfIconHandleNode(TYP_INT, (size_t)flagAddr.addr, GTF_ICON_GLOBAL_PTR); isInitedActualValueNode->gtFlags |= GTF_IND_VOLATILE; diff --git a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs index bca995c812abe3..cb21c1914e30a4 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs @@ -189,6 +189,8 @@ public enum ReadyToRunFixupKind ContinuationLayout = 0x37, /* Layout of an async method continuation type */ + ClassInitFlags = 0x38, /* Address of class init flags */ + ModuleOverride = 0x80, // followed by sig-encoded UInt with assemblyref index into either the assemblyref // table of the MSIL metadata of the master context module for the signature or diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index af15d3c0d16169..75aadc457c7c36 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -2740,6 +2740,14 @@ private CorInfoInitClassResult initClass(CORINFO_FIELD_STRUCT_* field, CORINFO_M } #endif +#if READYTORUN + if (type is MetadataType metadataType && + _compilation.NodeFactory.ReadyToRunPreinitializationManager.IsTypePreinitialized(metadataType)) + { + return CorInfoInitClassResult.CORINFO_INITCLASS_NOT_REQUIRED; + } +#endif + MetadataType typeToInit = (MetadataType)type; if (fd == null) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs index 35b3b383a37ab9..d690369a5519c6 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs @@ -937,7 +937,7 @@ bool HasAnyProfileDataForInput() ReadyToRunImportSectionType.Unknown, ReadyToRunImportSectionFlags.None, (byte)Target.PointerSize, - emitPrecode: false, + emitPrecode: true, emitGCRefMap: false); ImportSectionsTable.AddEmbeddedObject(PreinitializationImports); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/PreinitializationExtensions.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/PreinitializationExtensions.cs index 83445df5360314..1dabd8af920a80 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/PreinitializationExtensions.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/PreinitializationExtensions.cs @@ -17,6 +17,12 @@ internal static class PreinitializationExtensions public ISymbolNode TypeNonGCStaticsSymbol(MetadataType type) => factory.ReadyToRunPreinitializationManager.GetOrCreateTypeNonGCStaticsImport(type); + public ISymbolNode TypeGCStaticsSymbol(MetadataType type) + => factory.ReadyToRunPreinitializationManager.GetOrCreateTypeGCStaticsImport(type); + + public ISymbolNode TypeClassInitFlagSymbol(MetadataType type) + => factory.ReadyToRunPreinitializationManager.GetOrCreateTypeClassInitFlagsImport(type); + public ISymbolNode ConstructedTypeSymbol(TypeDesc type) => factory.ReadyToRunPreinitializationManager.GetOrCreateConstructedTypeImport(type); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/ReadyToRunPreinitializationManager.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/ReadyToRunPreinitializationManager.cs index d8d1a6506301c5..491e42452c1064 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/ReadyToRunPreinitializationManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/Preinitialization/ReadyToRunPreinitializationManager.cs @@ -22,6 +22,8 @@ internal sealed class ReadyToRunPreinitializationManager private readonly Dictionary _moduleHasPreinitializedTypes = new(); private readonly Dictionary _serializedStringImports = new(StringComparer.Ordinal); private readonly Dictionary _constructedTypeImports = new(); + private readonly Dictionary _typeClassInitFlagsImports = new(); + private readonly Dictionary _typeGCStaticsImports = new(); private readonly Dictionary _typeNonGCStaticsImports = new(); private readonly Dictionary _serializedFrozenObjects = new(); private readonly Dictionary _exactCallableAddressImports = new(); @@ -209,6 +211,50 @@ public Import GetOrCreateTypeNonGCStaticsImport(MetadataType type) } } + public Import GetOrCreateTypeGCStaticsImport(MetadataType type) + { + lock (_typeRecords) + { + if (_typeGCStaticsImports.TryGetValue(type, out Import existingImport)) + return existingImport; + } + + Import createdImport = new Import( + _factory.PreinitializationImports, + _factory.TypeSignature(ReadyToRunFixupKind.StaticBaseGC, type)); + + lock (_typeRecords) + { + if (_typeGCStaticsImports.TryGetValue(type, out Import existingImport)) + return existingImport; + + _typeGCStaticsImports[type] = createdImport; + return createdImport; + } + } + + public Import GetOrCreateTypeClassInitFlagsImport(MetadataType type) + { + lock (_typeRecords) + { + if (_typeClassInitFlagsImports.TryGetValue(type, out Import existingImport)) + return existingImport; + } + + Import createdImport = new Import( + _factory.PreinitializationImports, + _factory.TypeSignature(ReadyToRunFixupKind.ClassInitFlags, type)); + + lock (_typeRecords) + { + if (_typeClassInitFlagsImports.TryGetValue(type, out Import existingImport)) + return existingImport; + + _typeClassInitFlagsImports[type] = createdImport; + return createdImport; + } + } + public SerializedPreinitializationObjectDataNode GetOrCreateSerializedFrozenObjectDataNode(MetadataType owningType, int allocationSiteId, TypePreinit.ISerializableReference data) { SerializedFrozenObjectKey key = new SerializedFrozenObjectKey(owningType, allocationSiteId); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index b4b36f09f393ee..a5f5cc6d5006f3 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -3371,13 +3371,69 @@ private int getExactClasses(CORINFO_CLASS_STRUCT_* baseType, int maxExactClasses private bool getStaticFieldContent(CORINFO_FIELD_STRUCT_* fieldHandle, byte* buffer, int bufferSize, int valueOffset, bool ignoreMovableObjects) { Debug.Assert(fieldHandle != null); + Debug.Assert(buffer != null); + Debug.Assert(bufferSize > 0); + Debug.Assert(valueOffset >= 0); + FieldDesc field = HandleToObject(fieldHandle); + Debug.Assert(field.IsStatic); - // For crossgen2 we only support RVA fields - if (_compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(field.OwningType) && field.HasRva) + if (!_compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(field.OwningType)) + { + return false; + } + + if (field.HasRva) { return TryReadRvaFieldData(field, buffer, bufferSize, valueOffset); } + + if (!field.IsThreadStatic && field.IsInitOnly && field.OwningType is MetadataType owningType) + { + if (_compilation.NodeFactory.ReadyToRunPreinitializationManager.IsTypePreinitialized(owningType)) + { + TypePreinit.ISerializableValue value = _compilation.NodeFactory.PreinitializationManager + .GetPreinitializationInfo(owningType).GetFieldValue(field); + + int targetPtrSize = _compilation.TypeSystemContext.Target.PointerSize; + + if (value == null) + { + if ((valueOffset == 0) && (bufferSize == targetPtrSize)) + { + // null + new Span(buffer, targetPtrSize).Clear(); + return true; + } + return false; + } + + if (value.GetRawData(_compilation.NodeFactory, out object data)) + { + switch (data) + { + case byte[] bytes: + if (bytes.Length >= bufferSize && valueOffset <= bytes.Length - bufferSize) + { + bytes.AsSpan(valueOffset, bufferSize).CopyTo(new Span(buffer, bufferSize)); + return true; + } + return false; + } + } + } + else if (!owningType.HasStaticConstructor) + { + // initonly without cctor, setting to default value + int size = field.FieldType.GetElementSize().AsInt; + if (size >= bufferSize && valueOffset <= size - bufferSize) + { + new Span(buffer, bufferSize).Clear(); + return true; + } + } + } + return false; } @@ -3413,14 +3469,47 @@ private int getArrayOrStringLength(CORINFO_OBJECT_STRUCT_* objHnd) private bool getIsClassInitedFlagAddress(CORINFO_CLASS_STRUCT_* cls, ref CORINFO_CONST_LOOKUP addr, ref int offset) { - // Implemented for JIT and NativeAOT only for now. - return false; + MetadataType type = HandleToObject(cls) as MetadataType; + if (type == null || !_compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(type)) + { + return false; + } + + if (type.IsCanonicalSubtype(CanonicalFormKind.Any)) + { + return false; + } + + if (!_compilation.NodeFactory.ReadyToRunPreinitializationManager.IsTypePreinitialized(type)) + { + return false; + } + + addr.addr = (void*)ObjectToHandle(_compilation.NodeFactory.TypeClassInitFlagSymbol(type)); + addr.accessType = InfoAccessType.IAT_PVALUE; + offset = 0; + return true; } private bool getStaticBaseAddress(CORINFO_CLASS_STRUCT_* cls, bool isGc, ref CORINFO_CONST_LOOKUP addr) { - // Implemented for JIT and NativeAOT only for now. - return false; + MetadataType type = HandleToObject(cls) as MetadataType; + if (type == null || !_compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(type)) + { + return false; + } + + if (isGc) + { + addr.accessType = InfoAccessType.IAT_PVALUE; + addr.addr = (void*)ObjectToHandle(_compilation.NodeFactory.TypeGCStaticsSymbol(type)); + return true; + } + + addr.accessType = InfoAccessType.IAT_PVALUE; + addr.addr = (void*)ObjectToHandle(_compilation.NodeFactory.TypeNonGCStaticsSymbol(type)); + + return true; } private void ValidateSafetyOfUsingTypeEquivalenceInSignature(MethodSignature signature) diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs index 0ac72684e5d4f4..65d15f779434fb 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs @@ -1307,6 +1307,11 @@ private ReadyToRunSignature ParseSignature(ReadyToRunFixupKind fixupType, String builder.Append(" (CCTOR_TRIGGER)"); break; + case ReadyToRunFixupKind.ClassInitFlags: + ParseType(builder); + builder.Append(" (CLASS_INIT_FLAGS)"); + break; + case ReadyToRunFixupKind.StaticBaseNonGC: ParseType(builder); diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 72ed6e806c614c..59438dc98a0d0f 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -14106,6 +14106,40 @@ BOOL LoadDynamicInfoEntry(Module *currentModule, } break; + case READYTORUN_FIXUP_StaticBaseNonGC: + case READYTORUN_FIXUP_StaticBaseGC: + { + TypeHandle th = ZapSig::DecodeType(currentModule, pInfoModule, pBlob); + + _ASSERTE(!th.IsTypeDesc()); + + MethodTable * pMT = th.AsMethodTable(); + pMT->EnsureInstanceActive(); + pMT->CheckRunClassInitThrowing(); + + if (kind == READYTORUN_FIXUP_StaticBaseNonGC) + { + result = (size_t)pMT->GetNonGCStaticsBasePointer(); + } + else + { + _ASSERTE(kind == READYTORUN_FIXUP_StaticBaseGC); + result = (size_t)pMT->GetGCStaticsBasePointer(); + } + } + break; + + case READYTORUN_FIXUP_ClassInitFlags: + { + TypeHandle th = ZapSig::DecodeType(currentModule, pInfoModule, pBlob); + + _ASSERTE(!th.IsTypeDesc()); + th.AsMethodTable()->EnsureInstanceActive(); + + result = (size_t)th.AsMethodTable()->getIsClassInitedFlagAddress(); + } + break; + case READYTORUN_FIXUP_Helper: { DWORD helperNum = CorSigUncompressData(pBlob); diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 9b4b886981a82c..2afc1c530637d9 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -2896,6 +2896,12 @@ static PCODE getHelperForInitializedStatic(Module * pModule, ReadyToRunFixupKind case READYTORUN_FIXUP_CctorTrigger: pHelper = DynamicHelpers::CreateReturn(pModule->GetLoaderAllocator()); break; + case READYTORUN_FIXUP_ClassInitFlags: + { + PVOID classInitFlags = pMT->getIsClassInitedFlagAddress(); + pHelper = DynamicHelpers::CreateReturnConst(pModule->GetLoaderAllocator(), (TADDR)classInitFlags); + } + break; case READYTORUN_FIXUP_FieldAddress: { _ASSERTE(pFD->IsStatic()); @@ -3262,6 +3268,12 @@ PCODE DynamicHelperFixup(TransitionBlock * pTransitionBlock, TADDR * pCell, DWOR fReliable = true; break; + case READYTORUN_FIXUP_ClassInitFlags: + th = ZapSig::DecodeType(pModule, pInfoModule, pBlob); + th.AsMethodTable()->EnsureInstanceActive(); + fReliable = true; + break; + case READYTORUN_FIXUP_FieldAddress: pFD = ZapSig::DecodeField(pModule, pInfoModule, pBlob, &th); _ASSERTE(pFD->IsStatic()); @@ -3367,6 +3379,13 @@ PCODE DynamicHelperFixup(TransitionBlock * pTransitionBlock, TADDR * pCell, DWOR } break; + case READYTORUN_FIXUP_ClassInitFlags: + { + MethodTable * pMT = th.AsMethodTable(); + pHelper = getHelperForInitializedStatic(pModule, (ReadyToRunFixupKind)kind, pMT, NULL); + } + break; + case READYTORUN_FIXUP_VirtualEntry: { if (!pMD->IsVtableMethod()) @@ -3602,6 +3621,9 @@ extern "C" SIZE_T STDCALL DynamicHelperWorker(TransitionBlock * pTransitionBlock break; case READYTORUN_FIXUP_CctorTrigger: break; + case READYTORUN_FIXUP_ClassInitFlags: + result = (SIZE_T)th.AsMethodTable()->getIsClassInitedFlagAddress(); + break; case READYTORUN_FIXUP_FieldAddress: result = (SIZE_T)pFD->GetCurrentStaticAddress(); break;