diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs index 1bd82830585d51..48d66ff85b26b6 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs @@ -118,7 +118,7 @@ public CompilationBuilder UseDwarf5(bool value) protected PreinitializationManager GetPreinitializationManager() { if (_preinitializationManager == null) - return new PreinitializationManager(_context, _compilationGroup, GetILProvider(), enableInterpreter: false); + return new PreinitializationManager(_context, _compilationGroup, GetILProvider(), new TypePreinit.DisabledPreinitializationPolicy()); return _preinitializationManager; } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericTypesTemplateMap.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericTypesTemplateMap.cs index bb8435ab8c671a..9a329b0a74140d 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericTypesTemplateMap.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericTypesTemplateMap.cs @@ -50,17 +50,10 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) Section nativeSection = nativeWriter.NewSection(); nativeSection.Place(hashtable); - foreach (TypeDesc type in factory.MetadataManager.GetTypesWithConstructedEETypes()) + foreach (TypeDesc type in factory.MetadataManager.GetTypeTemplates()) { - if (!IsEligibleToHaveATemplate(type)) - continue; - // Type's native layout info NativeLayoutTemplateTypeLayoutVertexNode templateNode = factory.NativeLayout.TemplateTypeLayout(type); - - // If this template isn't considered necessary, don't emit it. - if (!templateNode.Marked) - continue; Vertex nativeLayout = templateNode.SavedVertex; // Hashtable Entry diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs index de53e842a9ad79..a366cff6c76e42 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs @@ -921,6 +921,8 @@ public sealed class NativeLayoutTemplateTypeLayoutVertexNode : NativeLayoutSaved private TypeDesc _type; private bool _isUniversalCanon; + public TypeDesc CanonType => _type.ConvertToCanonForm(CanonicalFormKind.Specific); + protected override string GetName(NodeFactory factory) => "NativeLayoutTemplateTypeLayoutVertexNode_" + factory.NameMangler.GetMangledTypeName(_type); public NativeLayoutTemplateTypeLayoutVertexNode(NodeFactory factory, TypeDesc type) @@ -989,7 +991,7 @@ public override IEnumerable GetStaticDependencies(NodeFacto } } - if (context.PreinitializationManager.HasLazyStaticConstructor(_type)) + if (context.PreinitializationManager.HasLazyStaticConstructor(_type.ConvertToCanonForm(CanonicalFormKind.Specific))) { yield return new DependencyListEntry(context.MethodEntrypoint(_type.GetStaticConstructor().GetCanonMethodTarget(CanonicalFormKind.Specific)), "cctor for template"); } @@ -1188,7 +1190,7 @@ public override Vertex WriteVertex(NodeFactory factory) layoutInfo.Append(BagElementKind.DictionaryLayout, dictionaryLayout.WriteVertex(factory)); } - if (factory.PreinitializationManager.HasLazyStaticConstructor(_type)) + if (factory.PreinitializationManager.HasLazyStaticConstructor(_type.ConvertToCanonForm(CanonicalFormKind.Specific))) { MethodDesc cctorMethod = _type.GetStaticConstructor(); MethodDesc canonCctorMethod = cctorMethod.GetCanonMethodTarget(CanonicalFormKind.Specific); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NonGCStaticsNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NonGCStaticsNode.cs index 08acf8265190a7..e29c8a1dca3dbf 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NonGCStaticsNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NonGCStaticsNode.cs @@ -33,7 +33,7 @@ public NonGCStaticsNode(MetadataType type, PreinitializationManager preinitializ protected override ObjectNodeSection GetDehydratedSection(NodeFactory factory) { - if (_preinitializationManager.HasLazyStaticConstructor(_type) + if (HasCCtorContext || _preinitializationManager.IsPreinitialized(_type)) { // We have data to be emitted so this needs to be in an initialized data section @@ -63,7 +63,7 @@ int ISymbolDefinitionNode.Offset get { // Make sure the NonGCStatics symbol always points to the beginning of the data. - if (_preinitializationManager.HasLazyStaticConstructor(_type)) + if (HasCCtorContext) { return GetClassConstructorContextStorageSize(_type.Context.Target, _type); } @@ -74,7 +74,25 @@ int ISymbolDefinitionNode.Offset } } - public bool HasCCtorContext => _preinitializationManager.HasLazyStaticConstructor(_type); + public static bool TypeHasCctorContext(PreinitializationManager preinitializationManager, MetadataType type) + { + // If the type has a lazy static constructor, we need the cctor context. + if (preinitializationManager.HasLazyStaticConstructor(type)) + return true; + + // If the type has a canonical form and accessing the base from a canonical + // context requires a cctor check, we need the cctor context. + TypeDesc canonType = type.ConvertToCanonForm(CanonicalFormKind.Specific); + if (canonType != type && preinitializationManager.HasLazyStaticConstructor(canonType)) + return true; + + // Otherwise no cctor context needed. + return false; + } + + public bool HasCCtorContext => TypeHasCctorContext(_preinitializationManager, _type); + + public bool HasLazyStaticConstructor => _preinitializationManager.HasLazyStaticConstructor(_type); public override bool IsShareable => EETypeNode.IsTypeNodeShareable(_type); @@ -124,7 +142,7 @@ protected override ObjectData GetDehydratableData(NodeFactory factory, bool relo // If the type has a class constructor, its non-GC statics section is prefixed // by System.Runtime.CompilerServices.StaticClassConstructionContext struct. - if (factory.PreinitializationManager.HasLazyStaticConstructor(_type)) + if (HasCCtorContext) { int alignmentRequired = Math.Max(_type.NonGCStaticFieldAlignment.AsInt, GetClassConstructorContextAlignment(_type.Context.Target)); int classConstructorContextStorageSize = GetClassConstructorContextStorageSize(factory.Target, _type); @@ -138,7 +156,24 @@ protected override ObjectData GetDehydratableData(NodeFactory factory, bool relo // Emit the actual StaticClassConstructionContext MethodDesc cctorMethod = _type.GetStaticConstructor(); builder.EmitPointerReloc(factory.ExactCallableAddress(cctorMethod)); - builder.EmitZeroPointer(); + + // If we're emitting the cctor context, but the type is actually preinitialized, emit the + // cctor context as already executed. + if (!HasLazyStaticConstructor) + { + // Constructor executed + // TODO-NICE: introduce a named constant and also use it in the runner in CoreLib + builder.EmitInt(1); + } + else + { + // Constructor didn't execute + builder.EmitInt(0); + } + + // Emit padding if needed + if (builder.TargetPointerSize == 8) + builder.EmitInt(0); } else { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs index e1bdd031296ee4..0cad5e99a02a3b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs @@ -107,7 +107,7 @@ protected sealed override void OnMarked(NodeFactory factory) layout.EnsureEntry(_lookupSignature); if ((_id == ReadyToRunHelperId.GetGCStaticBase || _id == ReadyToRunHelperId.GetThreadStaticBase) && - factory.PreinitializationManager.HasLazyStaticConstructor((TypeDesc)_target)) + TriggersLazyStaticConstructor(factory)) { // If the type has a lazy static constructor, we also need the non-GC static base // because that's where the class constructor context is. @@ -116,6 +116,12 @@ protected sealed override void OnMarked(NodeFactory factory) } } + private bool TriggersLazyStaticConstructor(NodeFactory factory) + { + TypeDesc type = (TypeDesc)_target; + return factory.PreinitializationManager.HasLazyStaticConstructor(type.ConvertToCanonForm(CanonicalFormKind.Specific)); + } + public IEnumerable InstantiateDependencies(NodeFactory factory, Instantiation typeInstantiation, Instantiation methodInstantiation) { DependencyList result = new DependencyList(); @@ -129,13 +135,11 @@ public IEnumerable InstantiateDependencies(NodeFactory fact { // If the type has a lazy static constructor, we also need the non-GC static base // because that's where the class constructor context is. - TypeDesc type = (TypeDesc)_target; - - if (factory.PreinitializationManager.HasLazyStaticConstructor(type)) + if (TriggersLazyStaticConstructor(factory)) { result.Add( new DependencyListEntry( - factory.GenericLookup.TypeNonGCStaticBase(type).GetTarget(factory, lookupContext), + factory.GenericLookup.TypeNonGCStaticBase((TypeDesc)_target).GetTarget(factory, lookupContext), "Dictionary dependency")); } } @@ -250,7 +254,7 @@ public override IEnumerable GetConditionalStaticDep // a template dictionary node. TypeDesc type = (TypeDesc)_target; Debug.Assert(templateLayout != null); - if (factory.PreinitializationManager.HasLazyStaticConstructor(type)) + if (TriggersLazyStaticConstructor(factory)) { GenericLookupResult nonGcRegionLookup = factory.GenericLookup.TypeNonGCStaticBase(type); conditionalDependencies.Add(new CombinedDependencyListEntry(nonGcRegionLookup.TemplateDictionaryNode(factory), diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/StaticsInfoHashtableNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/StaticsInfoHashtableNode.cs index 5f68ef8850dcf1..013837e322600e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/StaticsInfoHashtableNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/StaticsInfoHashtableNode.cs @@ -57,7 +57,7 @@ public static void AddStaticsInfoDependencies(ref DependencyList dependencies, N dependencies.Add(factory.TypeGCStaticsSymbol(metadataType), "GC statics indirection for StaticsInfoHashtable"); } - if (metadataType.NonGCStaticFieldSize.AsInt > 0 || factory.PreinitializationManager.HasLazyStaticConstructor(type)) + if (metadataType.NonGCStaticFieldSize.AsInt > 0 || NonGCStaticsNode.TypeHasCctorContext(factory.PreinitializationManager, metadataType)) { // The entry in the StaticsInfoHashtable points at the beginning of the static fields data, rather than the cctor // context offset. @@ -98,7 +98,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) { bag.AppendUnsigned(BagElementKind.GcStaticData, _nativeStaticsReferences.GetIndex(factory.TypeGCStaticsSymbol(metadataType))); } - if (metadataType.NonGCStaticFieldSize.AsInt > 0 || factory.PreinitializationManager.HasLazyStaticConstructor(type)) + if (metadataType.NonGCStaticFieldSize.AsInt > 0 || NonGCStaticsNode.TypeHasCctorContext(factory.PreinitializationManager, metadataType)) { bag.AppendUnsigned(BagElementKind.NonGcStaticData, _nativeStaticsReferences.GetIndex(factory.TypeNonGCStaticsSymbol(metadataType))); } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_ARM/ARMReadyToRunGenericHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_ARM/ARMReadyToRunGenericHelperNode.cs index aab05e9597e13b..87c3c69048aaba 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_ARM/ARMReadyToRunGenericHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_ARM/ARMReadyToRunGenericHelperNode.cs @@ -70,8 +70,7 @@ protected sealed override void EmitCode(NodeFactory factory, ref ARMEmitter enco EmitDictionaryLookup(factory, ref encoder, encoder.TargetRegister.Arg0, encoder.TargetRegister.Result, _lookupSignature, relocsOnly); - MetadataType target = (MetadataType)_target; - if (!factory.PreinitializationManager.HasLazyStaticConstructor(target)) + if (!TriggersLazyStaticConstructor(factory)) { encoder.EmitRET(); } @@ -99,7 +98,7 @@ protected sealed override void EmitCode(NodeFactory factory, ref ARMEmitter enco encoder.EmitLDR(encoder.TargetRegister.Result, encoder.TargetRegister.Result); MetadataType target = (MetadataType)_target; - if (!factory.PreinitializationManager.HasLazyStaticConstructor(target)) + if (!TriggersLazyStaticConstructor(factory)) { encoder.EmitRET(); } @@ -132,7 +131,7 @@ protected sealed override void EmitCode(NodeFactory factory, ref ARMEmitter enco EmitDictionaryLookup(factory, ref encoder, encoder.TargetRegister.Arg0, encoder.TargetRegister.Arg1, _lookupSignature, relocsOnly); ISymbolNode helperEntrypoint; - if (factory.PreinitializationManager.HasLazyStaticConstructor(target)) + if (TriggersLazyStaticConstructor(factory)) { // There is a lazy class constructor. We need the non-GC static base because that's where the // class constructor context lives. diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_ARM64/ARM64ReadyToRunGenericHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_ARM64/ARM64ReadyToRunGenericHelperNode.cs index f0e3edc070cc2a..d017da8ba492b5 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_ARM64/ARM64ReadyToRunGenericHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_ARM64/ARM64ReadyToRunGenericHelperNode.cs @@ -81,8 +81,7 @@ protected sealed override void EmitCode(NodeFactory factory, ref ARM64Emitter en EmitDictionaryLookup(factory, ref encoder, encoder.TargetRegister.Arg0, encoder.TargetRegister.Result, _lookupSignature, relocsOnly); - MetadataType target = (MetadataType)_target; - if (!factory.PreinitializationManager.HasLazyStaticConstructor(target)) + if (!TriggersLazyStaticConstructor(factory)) { encoder.EmitRET(); } @@ -111,7 +110,7 @@ protected sealed override void EmitCode(NodeFactory factory, ref ARM64Emitter en encoder.EmitLDR(encoder.TargetRegister.Result, encoder.TargetRegister.Result); MetadataType target = (MetadataType)_target; - if (!factory.PreinitializationManager.HasLazyStaticConstructor(target)) + if (!TriggersLazyStaticConstructor(factory)) { encoder.EmitRET(); } @@ -144,7 +143,7 @@ protected sealed override void EmitCode(NodeFactory factory, ref ARM64Emitter en EmitDictionaryLookup(factory, ref encoder, encoder.TargetRegister.Arg0, encoder.TargetRegister.Arg1, _lookupSignature, relocsOnly); ISymbolNode helperEntrypoint; - if (factory.PreinitializationManager.HasLazyStaticConstructor(target)) + if (TriggersLazyStaticConstructor(factory)) { // There is a lazy class constructor. We need the non-GC static base because that's where the // class constructor context lives. diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_LoongArch64/LoongArch64ReadyToRunGenericHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_LoongArch64/LoongArch64ReadyToRunGenericHelperNode.cs index 095056b587f65b..1b6f404ea7d693 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_LoongArch64/LoongArch64ReadyToRunGenericHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_LoongArch64/LoongArch64ReadyToRunGenericHelperNode.cs @@ -70,8 +70,7 @@ protected sealed override void EmitCode(NodeFactory factory, ref LoongArch64Emit EmitDictionaryLookup(factory, ref encoder, encoder.TargetRegister.Arg0, encoder.TargetRegister.Result, _lookupSignature, relocsOnly); - MetadataType target = (MetadataType)_target; - if (!factory.PreinitializationManager.HasLazyStaticConstructor(target)) + if (!TriggersLazyStaticConstructor(factory)) { encoder.EmitRET(); } @@ -100,7 +99,7 @@ protected sealed override void EmitCode(NodeFactory factory, ref LoongArch64Emit encoder.EmitLD(encoder.TargetRegister.Result, encoder.TargetRegister.Result, 0); MetadataType target = (MetadataType)_target; - if (!factory.PreinitializationManager.HasLazyStaticConstructor(target)) + if (!TriggersLazyStaticConstructor(factory)) { encoder.EmitRET(); } @@ -133,7 +132,7 @@ protected sealed override void EmitCode(NodeFactory factory, ref LoongArch64Emit EmitDictionaryLookup(factory, ref encoder, encoder.TargetRegister.Arg0, encoder.TargetRegister.Arg1, _lookupSignature, relocsOnly); ISymbolNode helperEntrypoint; - if (factory.PreinitializationManager.HasLazyStaticConstructor(target)) + if (TriggersLazyStaticConstructor(factory)) { // There is a lazy class constructor. We need the non-GC static base because that's where the // class constructor context lives. diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_X64/X64ReadyToRunGenericHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_X64/X64ReadyToRunGenericHelperNode.cs index 802d85ae18c232..447393d10d6ba4 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_X64/X64ReadyToRunGenericHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_X64/X64ReadyToRunGenericHelperNode.cs @@ -82,9 +82,7 @@ protected sealed override void EmitCode(NodeFactory factory, ref X64Emitter enco { Debug.Assert(contextRegister == encoder.TargetRegister.Arg0); - MetadataType target = (MetadataType)_target; - - if (!factory.PreinitializationManager.HasLazyStaticConstructor(target)) + if (!TriggersLazyStaticConstructor(factory)) { EmitDictionaryLookup(factory, ref encoder, encoder.TargetRegister.Arg0, encoder.TargetRegister.Result, _lookupSignature, relocsOnly); encoder.EmitRET(); @@ -119,7 +117,7 @@ protected sealed override void EmitCode(NodeFactory factory, ref X64Emitter enco AddrMode loadFromResult = new AddrMode(encoder.TargetRegister.Result, null, 0, 0, AddrModeSize.Int64); encoder.EmitMOV(encoder.TargetRegister.Result, ref loadFromResult); - if (!factory.PreinitializationManager.HasLazyStaticConstructor(target)) + if (!TriggersLazyStaticConstructor(factory)) { encoder.EmitRET(); } @@ -153,7 +151,7 @@ protected sealed override void EmitCode(NodeFactory factory, ref X64Emitter enco EmitDictionaryLookup(factory, ref encoder, encoder.TargetRegister.Arg0, encoder.TargetRegister.Arg1, _lookupSignature, relocsOnly); ISymbolNode helperEntrypoint; - if (factory.PreinitializationManager.HasLazyStaticConstructor(target)) + if (TriggersLazyStaticConstructor(factory)) { // There is a lazy class constructor. We need the non-GC static base because that's where the // class constructor context lives. diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs index ab6c2b0b99d32d..342a1609b6db90 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs @@ -247,6 +247,11 @@ public MethodImportationErrorProvider GetMethodImportationErrorProvider() return new ScannedMethodImportationErrorProvider(MarkedNodes); } + public TypePreinit.TypePreinitializationPolicy GetPreinitializationPolicy() + { + return new ScannedPreinitializationPolicy(MarkedNodes); + } + private sealed class ScannedVTableProvider : VTableSliceProvider { private Dictionary> _vtableSlices = new Dictionary>(); @@ -629,5 +634,47 @@ public ScannedMethodImportationErrorProvider(ImmutableArray _importationErrors.TryGetValue(method, out var exception) ? exception : null; } + + private sealed class ScannedPreinitializationPolicy : TypePreinit.TypePreinitializationPolicy + { + private readonly HashSet _canonFormsWithCctorChecks = new HashSet(); + + public ScannedPreinitializationPolicy(ImmutableArray> markedNodes) + { + foreach (var markedNode in markedNodes) + { + // If there's a type loader template for a type, we can create new instances + // at runtime that will not be preinitialized. + // This makes sure accessing static bases of template-constructed types + // goes through a cctor check. + if (markedNode is NativeLayoutTemplateTypeLayoutVertexNode typeTemplate) + { + _canonFormsWithCctorChecks.Add(typeTemplate.CanonType); + } + + // If there's a type for which we have a canonical form that requires + // a cctor check, make sure accessing the static base from a shared generic context + // will trigger the cctor. + // This makes sure that "static object Read() => SomeType.StaticField" will do + // a cctor check if any of the canonically-equivalent SomeType instantiations required + // a cctor check. + if (markedNode is NonGCStaticsNode nonGCStatics + && nonGCStatics.Type.ConvertToCanonForm(CanonicalFormKind.Specific) != nonGCStatics.Type + && nonGCStatics.HasLazyStaticConstructor) + { + _canonFormsWithCctorChecks.Add(nonGCStatics.Type.ConvertToCanonForm(CanonicalFormKind.Specific)); + } + } + } + + public override bool CanPreinitialize(DefType type) => true; + + public override bool CanPreinitializeAllConcreteFormsForCanonForm(DefType type) + { + // The form we're asking about should be canonical, but may not be normalized + Debug.Assert(type.IsCanonicalSubtype(CanonicalFormKind.Any)); + return !_canonFormsWithCctorChecks.Contains(type.NormalizeInstantiation()); + } + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs index f96b597a923e0b..ce195771f68594 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs @@ -63,6 +63,7 @@ private readonly SortedSet _typeGVMEntries private readonly SortedSet _typesWithDelegateMarshalling = new SortedSet(TypeSystemComparer.Instance); private readonly SortedSet _typesWithStructMarshalling = new SortedSet(TypeSystemComparer.Instance); private HashSet _templateMethodEntries = new HashSet(); + private readonly SortedSet _typeTemplates = new SortedSet(TypeSystemComparer.Instance); private List<(DehydratableObjectNode Node, ObjectNode.ObjectData Data)> _dehydratableData = new List<(DehydratableObjectNode Node, ObjectNode.ObjectData data)>(); @@ -240,7 +241,7 @@ protected virtual void Graph_NewMarkedNode(DependencyNodeCore obj) } var nonGcStaticSectionNode = obj as NonGCStaticsNode; - if (nonGcStaticSectionNode != null && nonGcStaticSectionNode.HasCCtorContext) + if (nonGcStaticSectionNode != null && nonGcStaticSectionNode.HasLazyStaticConstructor) { _cctorContextsGenerated.Add(nonGcStaticSectionNode); } @@ -280,6 +281,11 @@ protected virtual void Graph_NewMarkedNode(DependencyNodeCore obj) _templateMethodEntries.Add(templateMethodEntry); } + if (obj is NativeLayoutTemplateTypeLayoutVertexNode typeTemplate) + { + _typeTemplates.Add(typeTemplate.CanonType); + } + if (obj is FrozenObjectNode frozenObj) { _frozenObjects.Add(frozenObj); @@ -711,6 +717,11 @@ public IEnumerable GetReflectableMethods() return _reflectableMethods; } + public IEnumerable GetTypeTemplates() + { + return _typeTemplates; + } + public IEnumerable GetFrozenObjects() { return _frozenObjects; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/PreinitializationManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/PreinitializationManager.cs index c76c65e3a5ebbe..625821ac1d3cad 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/PreinitializationManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/PreinitializationManager.cs @@ -13,13 +13,11 @@ namespace ILCompiler public class PreinitializationManager { private readonly bool _supportsLazyCctors; - private readonly bool _enableInterpreter; - public PreinitializationManager(TypeSystemContext context, CompilationModuleGroup compilationGroup, ILProvider ilprovider, bool enableInterpreter) + public PreinitializationManager(TypeSystemContext context, CompilationModuleGroup compilationGroup, ILProvider ilprovider, TypePreinit.TypePreinitializationPolicy policy) { _supportsLazyCctors = context.SystemModule.GetType("System.Runtime.CompilerServices", "ClassConstructorRunner", throwIfNotFound: false) != null; - _preinitHashTable = new PreinitializationInfoHashtable(compilationGroup, ilprovider); - _enableInterpreter = enableInterpreter; + _preinitHashTable = new PreinitializationInfoHashtable(compilationGroup, ilprovider, policy); } /// @@ -82,10 +80,6 @@ private static bool HasEagerConstructorAttribute(TypeDesc type) public bool IsPreinitialized(MetadataType type) { - // If the cctor interpreter is not enabled, no type is preinitialized. - if (!_enableInterpreter) - return false; - if (!type.HasStaticConstructor) return false; @@ -102,7 +96,7 @@ public bool IsPreinitialized(MetadataType type) public void LogStatistics(Logger logger) { - if (!_enableInterpreter) + if (_preinitHashTable._policy is TypePreinit.DisabledPreinitializationPolicy) return; int totalEligibleTypes = 0; @@ -112,6 +106,10 @@ public void LogStatistics(Logger logger) { foreach (var item in LockFreeReaderHashtable.Enumerator.Get(_preinitHashTable)) { + // Canonical types are not actual types. They represent the pessimized version of all types that share the form. + if (item.Type.IsCanonicalSubtype(CanonicalFormKind.Any)) + continue; + totalEligibleTypes++; if (item.IsPreinitialized) { @@ -137,11 +135,13 @@ private sealed class PreinitializationInfoHashtable : LockFreeReaderHashtable key == value.Type; @@ -151,7 +151,15 @@ public PreinitializationInfoHashtable(CompilationModuleGroup compilationGroup, I protected override TypePreinit.PreinitializationInfo CreateValueFromKey(MetadataType key) { - return TypePreinit.ScanType(_compilationGroup, _ilProvider, key); + var info = TypePreinit.ScanType(_compilationGroup, _ilProvider, _policy, key); + + // We either successfully preinitialized or + // the type doesn't have a canonical form or + // the policy doesn't allow treating canonical forms of this type as preinitialized + Debug.Assert(info.IsPreinitialized || + (key.ConvertToCanonForm(CanonicalFormKind.Specific) is DefType canonType && (key == canonType || !_policy.CanPreinitializeAllConcreteFormsForCanonForm(canonType)))); + + return info; } } private PreinitializationInfoHashtable _preinitHashTable; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs index 5d02a477fcd646..72fd1d18fe5a60 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs @@ -32,14 +32,16 @@ public class TypePreinit private readonly MetadataType _type; private readonly CompilationModuleGroup _compilationGroup; private readonly ILProvider _ilProvider; + private readonly TypePreinitializationPolicy _policy; private readonly Dictionary _fieldValues = new Dictionary(); private readonly Dictionary _internedStrings = new Dictionary(); - private TypePreinit(MetadataType owningType, CompilationModuleGroup compilationGroup, ILProvider ilProvider) + private TypePreinit(MetadataType owningType, CompilationModuleGroup compilationGroup, ILProvider ilProvider, TypePreinitializationPolicy policy) { _type = owningType; _compilationGroup = compilationGroup; _ilProvider = ilProvider; + _policy = policy; // Zero initialize all fields we model. foreach (var field in owningType.GetFields()) @@ -51,38 +53,34 @@ private TypePreinit(MetadataType owningType, CompilationModuleGroup compilationG } } - // Could potentially expose this as a policy class. When type loader is not present or - // the given type can't be constructed by the type loader, preinitialization could still - // happen. - private static bool CanPreinitializeByPolicy(TypeDesc type) - { - // If the type has a canonical form the runtime type loader could create - // a new type sharing code with this one. They need to agree on how - // initialization happens. We can't preinitialize runtime-created - // generic types at compile time. - if (type.ConvertToCanonForm(CanonicalFormKind.Specific) - .IsCanonicalSubtype(CanonicalFormKind.Any)) - return false; - - return true; - } - - public static PreinitializationInfo ScanType(CompilationModuleGroup compilationGroup, ILProvider ilProvider, MetadataType type) + public static PreinitializationInfo ScanType(CompilationModuleGroup compilationGroup, ILProvider ilProvider, TypePreinitializationPolicy policy, MetadataType type) { Debug.Assert(type.HasStaticConstructor); Debug.Assert(!type.IsGenericDefinition); + Debug.Assert(!type.IsRuntimeDeterminedSubtype); + + if (type.IsCanonicalSubtype(CanonicalFormKind.Any)) + { + // It's an odd question to ask about canonical types. Defer to policy that might + // have more information. + // If the policy allows it, we allow it, but create invalid field values so that + // things still crash if someone wanted to do more with canonical types than just + // ask if a cctor check is necessary to access. + if (policy.CanPreinitializeAllConcreteFormsForCanonForm(type)) + return new PreinitializationInfo(type, Array.Empty>()); - if (!CanPreinitializeByPolicy(type)) return new PreinitializationInfo(type, "Disallowed by policy"); + } - Debug.Assert(!type.IsCanonicalSubtype(CanonicalFormKind.Any)); + if (!policy.CanPreinitialize(type)) + return new PreinitializationInfo(type, "Disallowed by policy"); TypePreinit preinit = null; Status status; try { - preinit = new TypePreinit(type, compilationGroup, ilProvider); + preinit = new TypePreinit(type, compilationGroup, ilProvider, policy); int instructions = 0; status = preinit.TryScanMethod(type.GetStaticConstructor(), null, null, ref instructions, out _); } @@ -337,9 +335,9 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack(); recursionProtect.Push(methodIL.OwningMethod); @@ -2545,5 +2543,39 @@ public ISerializableValue GetFieldValue(FieldDesc field) return _fieldValues[field]; } } + + public abstract class TypePreinitializationPolicy + { + /// + /// Returns true if the preinitialization system may attempt to preinitialize this type. + /// + public abstract bool CanPreinitialize(DefType type); + + /// + /// Returns true if all concrete forms of this canonical form will be preinitialized. + /// This can only be answered by a whole program view. + /// + public abstract bool CanPreinitializeAllConcreteFormsForCanonForm(DefType type); + } + + /// + /// Preinitialization policy that doesn't allow preinitialization. + /// + public sealed class DisabledPreinitializationPolicy : TypePreinitializationPolicy + { + public override bool CanPreinitialize(DefType type) => false; + public override bool CanPreinitializeAllConcreteFormsForCanonForm(DefType type) => false; + } + + /// + /// Preinitialization policy that assumes new canonical forms of types could be created + /// at runtime. + /// + public sealed class TypeLoaderAwarePreinitializationPolicy : TypePreinitializationPolicy + { + public override bool CanPreinitialize(DefType type) => true; + + public override bool CanPreinitializeAllConcreteFormsForCanonForm(DefType type) => false; + } } } diff --git a/src/coreclr/tools/aot/ILCompiler/Program.cs b/src/coreclr/tools/aot/ILCompiler/Program.cs index a1c7c860eb3c66..61bdaf1fb2406b 100644 --- a/src/coreclr/tools/aot/ILCompiler/Program.cs +++ b/src/coreclr/tools/aot/ILCompiler/Program.cs @@ -371,7 +371,10 @@ public int Run() (_command.OptimizationMode != OptimizationMode.None && !multiFile); preinitStatics &= !Get(_command.NoPreinitStatics); - var preinitManager = new PreinitializationManager(typeSystemContext, compilationGroup, ilProvider, preinitStatics); + TypePreinit.TypePreinitializationPolicy preinitPolicy = preinitStatics ? + new TypePreinit.TypeLoaderAwarePreinitializationPolicy() : new TypePreinit.DisabledPreinitializationPolicy(); + + var preinitManager = new PreinitializationManager(typeSystemContext, compilationGroup, ilProvider, preinitPolicy); builder .UseILProvider(ilProvider) .UsePreinitializationManager(preinitManager); @@ -443,6 +446,14 @@ void RunScanner() // compilation, but before RyuJIT gets there, it might ask questions that we don't // have answers for because we didn't scan the entire method. builder.UseMethodImportationErrorProvider(scanResults.GetMethodImportationErrorProvider()); + + // If we're doing preinitialization, use a new preinitialization manager that + // has the whole program view. + if (preinitStatics) + { + preinitManager = new PreinitializationManager(typeSystemContext, compilationGroup, ilProvider, scanResults.GetPreinitializationPolicy()); + builder.UsePreinitializationManager(preinitManager); + } } string ilDump = Get(_command.IlDump); diff --git a/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs b/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs index 6d6abed62ef9cb..803e6d179467ee 100644 --- a/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs +++ b/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using BindingFlags = System.Reflection.BindingFlags; @@ -46,6 +47,7 @@ private static int Main() TestDuplicatedFields.Run(); TestInstanceDelegate.Run(); TestStringFields.Run(); + TestSharedCode.Run(); #else Console.WriteLine("Preinitialization is disabled in multimodule builds for now. Skipping test."); #endif @@ -945,6 +947,59 @@ public static void Run() } } +class TestSharedCode +{ + class ClassWithTemplate + { + public static int Cookie = 42; + public static T[] Array = new T[0]; + } + + class C1 { } + class C2 { } + class C3 { } + class C4 { } + class C5 { } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static int AccessCookie() + => ClassWithTemplate.Cookie; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static object AccessArray() + => ClassWithTemplate.Array; + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL3050:MakeGeneric", + Justification = "MakeGeneric is over reference types")] + public static void Run() + { + { + int val = AccessCookie(); + Assert.AreEqual(42, val); + + val = (int)typeof(ClassWithTemplate<>).MakeGenericType(typeof(C2)).GetField("Cookie").GetValue(null); + Assert.AreEqual(42, val); + + val = (int)typeof(TestSharedCode).GetMethod(nameof(AccessCookie)).MakeGenericMethod(typeof(C3)).Invoke(null, Array.Empty()); + Assert.AreEqual(42, val); + } + + { + // Expecting this to be a frozen array, and reported as Gen2 by the GC + object val = AccessArray(); + Assert.AreEqual(2, GC.GetGeneration(val)); + + val = typeof(ClassWithTemplate<>).MakeGenericType(typeof(C4)).GetField("Array").GetValue(null); + Assert.AreEqual(0, GC.GetGeneration(val)); + Assert.AreEqual(nameof(C4), val.GetType().GetElementType().Name); + + val = typeof(TestSharedCode).GetMethod(nameof(AccessArray)).MakeGenericMethod(typeof(C5)).Invoke(null, Array.Empty()); + Assert.AreEqual(0, GC.GetGeneration(val)); + Assert.AreEqual(nameof(C5), val.GetType().GetElementType().Name); + } + } +} + static class Assert { [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern", @@ -984,6 +1039,12 @@ public static unsafe void AreEqual(int v1, int v2) throw new Exception(); } + public static void AreEqual(string v1, string v2) + { + if (v1 != v2) + throw new Exception(); + } + public static unsafe void AreEqual(long v1, long v2) { if (v1 != v2)