From f97d248e53072a864df92f846529da558146af7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Sat, 13 Feb 2016 11:58:27 +0100 Subject: [PATCH] Add support for eager static constructors This looks like a lot of code just to support a rarely used feature, but a lot of this is plumbing that will let me delete some existing code in a subsequent commit. Some of this will also be needed to support the various preinitialized data attributes/general type preinitialization. * Redirecting uses of HasStaticConstructor to go through the TypeInitialization class (exposing higher level APIs "HasLazyCctor" and "HasEagerCctor") * Emitting a new table with an array of eager class constructors * Augmenting GC/nonGC/ThreadStatic and Method nodes to depend on an entry in the eager cctor table if the owning type has a cctor * Startup code addition to iterate over the list of eager cctors and call them one by one Reusable code: * ArrayOfEmbeddedPointers: we have a buch of places where we just want an array of pointers. This will let us e.g. delete StringIndirectionNode. Missing features: ordering the eager cctors. I want to think a bit about how we parse custom attribute blobs before I implement that. Fixes #471. --- .../src/Compiler/Compilation.cs | 10 +- .../ArrayOfEmbeddedDataNode.cs | 30 +++-- .../ArrayOfEmbeddedPointersNode.cs | 106 ++++++++++++++++++ .../DependencyAnalysis/CppMethodCodeNode.cs | 2 +- .../Compiler/DependencyAnalysis/EETypeNode.cs | 6 +- .../EmbeddedPointerIndirectionNode.cs | 52 +++++++++ .../DependencyAnalysis/GCStaticsNode.cs | 14 ++- .../DependencyAnalysis/IMethodNode.cs | 16 +++ .../DependencyAnalysis/MethodCodeNode.cs | 15 ++- .../DependencyAnalysis/NodeFactory.cs | 38 ++++++- .../DependencyAnalysis/NonGCStaticsNode.cs | 20 +++- .../Target_X64/X64ReadyToRunHelperNode.cs | 4 +- .../DependencyAnalysis/ThreadStaticsNode.cs | 14 ++- .../src/Compiler/FormattingHelpers.cs | 17 +++ .../src/Compiler/TypeInitialization.cs | 43 +++++++ .../src/ILCompiler.Compiler.csproj | 5 + src/JitInterface/src/CorInfoImpl.cs | 2 +- src/Native/Bootstrap/main.cpp | 4 + .../StartupCode/StartupCodeHelpers.cs | 20 +++- 19 files changed, 392 insertions(+), 26 deletions(-) create mode 100644 src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ArrayOfEmbeddedPointersNode.cs create mode 100644 src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EmbeddedPointerIndirectionNode.cs create mode 100644 src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/IMethodNode.cs create mode 100644 src/ILCompiler.Compiler/src/Compiler/FormattingHelpers.cs create mode 100644 src/ILCompiler.Compiler/src/Compiler/TypeInitialization.cs diff --git a/src/ILCompiler.Compiler/src/Compiler/Compilation.cs b/src/ILCompiler.Compiler/src/Compiler/Compilation.cs index 08190cad9d5..5999ed7b218 100644 --- a/src/ILCompiler.Compiler/src/Compiler/Compilation.cs +++ b/src/ILCompiler.Compiler/src/Compiler/Compilation.cs @@ -42,6 +42,7 @@ public partial class Compilation { private readonly CompilerTypeSystemContext _typeSystemContext; private readonly CompilationOptions _options; + private readonly TypeInitialization _typeInitManager; private NodeFactory _nodeFactory; private DependencyAnalyzerBase _dependencyGraph; @@ -61,6 +62,8 @@ public Compilation(CompilationOptions options) _typeSystemContext.SetSystemModule(_typeSystemContext.GetModuleForSimpleName(options.SystemModuleName)); _nameMangler = new NameMangler(this); + + _typeInitManager = new TypeInitialization(); } public CompilerTypeSystemContext TypeSystemContext @@ -132,7 +135,7 @@ public void CompileSingleFile() { NodeFactory.NameMangler = NameMangler; - _nodeFactory = new NodeFactory(_typeSystemContext, _options.IsCppCodeGen); + _nodeFactory = new NodeFactory(_typeSystemContext, _typeInitManager, _options.IsCppCodeGen); // Choose which dependency graph implementation to use based on the amount of logging requested. if (_options.DgmlLog == null) @@ -329,5 +332,10 @@ public object GetFieldRvaData(FieldDesc field) return _nodeFactory.ReadOnlyDataBlob(NameMangler.GetMangledFieldName(field), ((EcmaField)field).GetFieldRvaData(), _typeSystemContext.Target.PointerSize); } + + public bool HasLazyStaticConstructor(TypeDesc type) + { + return _typeInitManager.HasLazyStaticConstructor(type); + } } } diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ArrayOfEmbeddedDataNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ArrayOfEmbeddedDataNode.cs index 5a484a1820d..a8fd19844dc 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ArrayOfEmbeddedDataNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ArrayOfEmbeddedDataNode.cs @@ -11,22 +11,28 @@ namespace ILCompiler.DependencyAnalysis { - public class ArrayOfEmbeddedDataNode : ObjectNode + /// + /// Represents an array of nodes. The contents of this node will be emitted + /// by placing a starting symbol, followed by contents of nodes (optionally + /// sorted using provided comparer), followed by ending symbol. + /// + public class ArrayOfEmbeddedDataNode : ObjectNode + where TEmbedded : EmbeddedObjectNode { - private HashSet _nestedNodes = new HashSet(); - private List _nestedNodesList = new List(); + private HashSet _nestedNodes = new HashSet(); + private List _nestedNodesList = new List(); private ObjectAndOffsetSymbolNode _startSymbol; private ObjectAndOffsetSymbolNode _endSymbol; - private IComparer _sorter; + private IComparer _sorter; - public ArrayOfEmbeddedDataNode(string startSymbolMangledName, string endSymbolMangledName, IComparer nodeSorter) + public ArrayOfEmbeddedDataNode(string startSymbolMangledName, string endSymbolMangledName, IComparer nodeSorter) { _startSymbol = new ObjectAndOffsetSymbolNode(this, 0, startSymbolMangledName); _endSymbol = new ObjectAndOffsetSymbolNode(this, 0, endSymbolMangledName); _sorter = nodeSorter; } - public void AddEmbeddedObject(EmbeddedObjectNode symbol) + public void AddEmbeddedObject(TEmbedded symbol) { if (_nestedNodes.Add(symbol)) { @@ -64,7 +70,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly) _nestedNodesList.Sort(_sorter); builder.DefinedSymbols.Add(_startSymbol); - foreach (EmbeddedObjectNode node in _nestedNodesList) + foreach (TEmbedded node in _nestedNodesList) { if (!relocsOnly) node.Offset = builder.CountBytes; @@ -82,4 +88,14 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly) return objData; } } + + // TODO: delete this once we review each use of this and put it on the generic plan with the + // right element type + public class ArrayOfEmbeddedDataNode : ArrayOfEmbeddedDataNode + { + public ArrayOfEmbeddedDataNode(string startSymbolMangledName, string endSymbolMangledName, IComparer nodeSorter) + : base(startSymbolMangledName, endSymbolMangledName, nodeSorter) + { + } + } } diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ArrayOfEmbeddedPointersNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ArrayOfEmbeddedPointersNode.cs new file mode 100644 index 00000000000..d26ea748894 --- /dev/null +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ArrayOfEmbeddedPointersNode.cs @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; + +namespace ILCompiler.DependencyAnalysis +{ + /// + /// Represents an array of pointers to symbols. is the type + /// of node each pointer within the vector points to. + /// + public sealed class ArrayOfEmbeddedPointersNode : ArrayOfEmbeddedDataNode> + where TTarget : ISymbolNode + { + private int _nextId; + private string _startSymbolMangledName; + + public ArrayOfEmbeddedPointersNode(string startSymbolMangledName, string endSymbolMangledName, IComparer nodeSorter) + : base( + startSymbolMangledName, + endSymbolMangledName, + nodeSorter != null ? new PointerIndirectionNodeComparer(nodeSorter) : null) + { + _startSymbolMangledName = startSymbolMangledName; + } + + public EmbeddedObjectNode NewNode(TTarget target) + { + return new SimpleEmbeddedPointerIndirectionNode(this, target); + } + + public EmbeddedObjectNode NewNodeWithSymbol(TTarget target) + { + int id = System.Threading.Interlocked.Increment(ref _nextId); + return new EmbeddedPointerIndirectionWithSymbolNode(this, target, id); + } + + private class PointerIndirectionNodeComparer : IComparer> + { + private IComparer _innerComparer; + + public PointerIndirectionNodeComparer(IComparer innerComparer) + { + _innerComparer = innerComparer; + } + + public int Compare(EmbeddedPointerIndirectionNode x, EmbeddedPointerIndirectionNode y) + { + return _innerComparer.Compare(x.Target, y.Target); + } + } + + private class SimpleEmbeddedPointerIndirectionNode : EmbeddedPointerIndirectionNode + { + protected ArrayOfEmbeddedPointersNode _parentNode; + + public SimpleEmbeddedPointerIndirectionNode(ArrayOfEmbeddedPointersNode futureParent, TTarget target) + : base(target) + { + _parentNode = futureParent; + } + + public override string GetName() + { + return "Embedded pointer to " + Target.MangledName; + } + + protected override void OnMarked(NodeFactory context) + { + // We don't want the child in the parent collection unless it's necessary. + // Only when this node gets marked, the parent node becomes the actual parent. + _parentNode.AddEmbeddedObject(this); + } + + public override IEnumerable GetStaticDependencies(NodeFactory context) + { + return new[] + { + new DependencyListEntry(Target, "reloc"), + new DependencyListEntry(_parentNode, "Pointer region") + }; + } + } + + private class EmbeddedPointerIndirectionWithSymbolNode : SimpleEmbeddedPointerIndirectionNode, ISymbolNode + { + private int _id; + + public EmbeddedPointerIndirectionWithSymbolNode(ArrayOfEmbeddedPointersNode futureParent, TTarget target, int id) + : base(futureParent, target) + { + _id = id; + } + + public string MangledName + { + get + { + return String.Concat(_parentNode._startSymbolMangledName, "_", _id.ToStringInvariant()); + } + } + } + } +} diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/CppMethodCodeNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/CppMethodCodeNode.cs index 1f9fa74e7dd..40eb37f2588 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/CppMethodCodeNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/CppMethodCodeNode.cs @@ -11,7 +11,7 @@ namespace ILCompiler.DependencyAnalysis { - internal class CppMethodCodeNode : DependencyNodeCore, ISymbolNode + internal class CppMethodCodeNode : DependencyNodeCore, IMethodNode { private MethodDesc _method; private string _methodCode; diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EETypeNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EETypeNode.cs index 558f5868b6b..cff132c6d36 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EETypeNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EETypeNode.cs @@ -450,13 +450,13 @@ private void OutputNullableTypeParameter(NodeFactory factory, ref ObjectDataBuil /// private void ComputeOptionalEETypeFields(NodeFactory factory) { - ComputeRareFlags(); + ComputeRareFlags(factory); ComputeNullableValueOffset(); ComputeICastableVirtualMethodSlots(factory); ComputeValueTypeFieldPadding(); } - void ComputeRareFlags() + void ComputeRareFlags(NodeFactory factory) { uint flags = 0; @@ -465,7 +465,7 @@ void ComputeRareFlags() flags |= (uint)EETypeRareFlags.IsNullableFlag; } - if (_type.HasStaticConstructor) + if (factory.TypeInitializationManager.HasLazyStaticConstructor(_type)) { flags |= (uint)EETypeRareFlags.HasCctorFlag; } diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EmbeddedPointerIndirectionNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EmbeddedPointerIndirectionNode.cs new file mode 100644 index 00000000000..157e4d89202 --- /dev/null +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EmbeddedPointerIndirectionNode.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; + +namespace ILCompiler.DependencyAnalysis +{ + /// + /// An whose sole value is a pointer to a different . + /// represents the node type this pointer points to. + /// + public abstract class EmbeddedPointerIndirectionNode : EmbeddedObjectNode + where TTarget : ISymbolNode + { + private TTarget _targetNode; + + /// + /// Target symbol this node points to. + /// + public TTarget Target + { + get + { + return _targetNode; + } + } + + internal EmbeddedPointerIndirectionNode(TTarget target) + { + _targetNode = target; + } + + public override bool StaticDependenciesAreComputed + { + get + { + return true; + } + } + + public override void EncodeData(ref ObjectDataBuilder dataBuilder, NodeFactory factory, bool relocsOnly) + { + dataBuilder.RequirePointerAlignment(); + dataBuilder.EmitPointerReloc(Target); + } + + // At minimum, Target needs to be reported as a static dependency by inheritors. + public abstract override IEnumerable GetStaticDependencies(NodeFactory context); + } +} diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/GCStaticsNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/GCStaticsNode.cs index d4fb76e1421..b4a0860954d 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/GCStaticsNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/GCStaticsNode.cs @@ -48,8 +48,18 @@ public ISymbolNode GetGCStaticEETypeNode(NodeFactory context) public override IEnumerable GetStaticDependencies(NodeFactory context) { - return new DependencyListEntry[] { new DependencyListEntry(context.GCStaticsRegion, "GCStatics Region"), - new DependencyListEntry(GetGCStaticEETypeNode(context), "GCStatic EEType")}; + DependencyListEntry[] result; + if (context.TypeInitializationManager.HasEagerStaticConstructor(_type)) + { + result = new DependencyListEntry[3]; + result[2] = new DependencyListEntry(context.EagerCctorIndirection(_type.GetStaticConstructor()), "Eager .cctor"); + } + else + result = new DependencyListEntry[2]; + + result[0] = new DependencyListEntry(context.GCStaticsRegion, "GCStatics Region"); + result[1] = new DependencyListEntry(GetGCStaticEETypeNode(context), "GCStatic EEType"); + return result; } int ISymbolNode.Offset diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/IMethodNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/IMethodNode.cs new file mode 100644 index 00000000000..be4edd79380 --- /dev/null +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/IMethodNode.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Internal.TypeSystem; + +namespace ILCompiler.DependencyAnalysis +{ + /// + /// A dependency analysis node that represents a method. + /// + public interface IMethodNode : ISymbolNode + { + MethodDesc Method { get; } + } +} diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/MethodCodeNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/MethodCodeNode.cs index 68feb92fe0e..b303974bc07 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/MethodCodeNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/MethodCodeNode.cs @@ -8,7 +8,7 @@ namespace ILCompiler.DependencyAnalysis { - internal class MethodCodeNode : ObjectNode, INodeWithFrameInfo, INodeWithDebugInfo, ISymbolNode + internal class MethodCodeNode : ObjectNode, IMethodNode, INodeWithFrameInfo, INodeWithDebugInfo { private MethodDesc _method; private ObjectData _methodCode; @@ -72,6 +72,19 @@ int ISymbolNode.Offset } } + protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory context) + { + TypeDesc owningType = _method.OwningType; + if (context.TypeInitializationManager.HasEagerStaticConstructor(owningType)) + { + var result = new DependencyList(); + result.Add(context.EagerCctorIndirection(owningType.GetStaticConstructor()), "Eager .cctor"); + return result; + } + + return null; + } + public override ObjectData GetData(NodeFactory factory, bool relocsOnly) { return _methodCode; diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs index 1a91682e35f..493bc17d81c 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs @@ -19,11 +19,12 @@ public class NodeFactory private CompilerTypeSystemContext _context; private bool _cppCodeGen; - public NodeFactory(CompilerTypeSystemContext context, bool cppCodeGen) + public NodeFactory(CompilerTypeSystemContext context, TypeInitialization typeInitManager, bool cppCodeGen) { _target = context.Target; _context = context; _cppCodeGen = cppCodeGen; + TypeInitializationManager = typeInitManager; DispatchMapTable = new InterfaceDispatchMapTableNode(this.Target); CreateNodeCaches(); } @@ -36,6 +37,11 @@ public TargetDetails Target } } + public TypeInitialization TypeInitializationManager + { + get; private set; + } + private struct NodeCache { private Func _creator; @@ -80,7 +86,7 @@ private void CreateNodeCaches() _nonGCStatics = new NodeCache((MetadataType type) => { - return new NonGCStaticsNode(type); + return new NonGCStaticsNode(type, this); }); _GCStatics = new NodeCache((MetadataType type) => @@ -161,6 +167,20 @@ private void CreateNodeCaches() { return new InterfaceDispatchMapNode(type); }); + + _eagerCctorIndirectionNodes = new NodeCache((MethodDesc method) => + { + Debug.Assert(method.IsStaticConstructor); + Debug.Assert(TypeInitializationManager.HasEagerStaticConstructor((MetadataType)method.OwningType)); + + ISymbolNode entrypoint = MethodEntrypoint(method); + + // TODO: multifile: We will likely hit this assert with ExternSymbolNode. We probably need ExternMethodSymbolNode + // deriving from ExternSymbolNode that carries around the target method. + Debug.Assert(entrypoint is IMethodNode); + + return EagerCctorTable.NewNode((IMethodNode)entrypoint); + }); } private NodeCache _typeSymbols; @@ -287,6 +307,8 @@ public ISymbolNode ObjectAndOffset(ObjectNode obj, int offset, string name) public ISymbolNode MethodEntrypoint(MethodDesc method) { + // TODO: NICE: make this method always return IMethodNode. We will likely be able to get rid of the + // cppCodeGen special casing here that way, and other places won't need to cast this from ISymbolNode. if (!_cppCodeGen) { var kind = method.DetectSpecialMethodKind(); @@ -374,6 +396,13 @@ public StringIndirectionNode StringIndirection(string data) return _stringIndirectionNodes.GetOrAdd(data); } + private NodeCache _eagerCctorIndirectionNodes; + + public EmbeddedObjectNode EagerCctorIndirection(MethodDesc cctorMethod) + { + return _eagerCctorIndirectionNodes.GetOrAdd(cctorMethod); + } + /// /// Returns alternative symbol name that object writer should produce for given symbols /// in addition to the regular one. @@ -398,6 +427,10 @@ public string GetSymbolAlternateName(ISymbolNode node) NameMangler.CompilationUnitPrefix + "__StringTableStart", NameMangler.CompilationUnitPrefix + "__StringTableEnd", null); + public ArrayOfEmbeddedPointersNode EagerCctorTable = new ArrayOfEmbeddedPointersNode( + NameMangler.CompilationUnitPrefix + "__EagerCctorStart", + NameMangler.CompilationUnitPrefix + "__EagerCctorEnd", + /*TODO SORT */null); public InterfaceDispatchMapTableNode DispatchMapTable; @@ -413,6 +446,7 @@ public void AttachToDependencyGraph(DependencyAnalysisFramework.DependencyAnalyz graph.AddRoot(ThreadStaticsRegion, "ThreadStaticsRegion is always generated"); graph.AddRoot(StringTable, "StringTable is always generated"); graph.AddRoot(DispatchMapTable, "DispatchMapTable is always generated"); + graph.AddRoot(EagerCctorTable, "EagerCctorTable is always generated"); } } diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NonGCStaticsNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NonGCStaticsNode.cs index 631b1bf8e82..8030eb78638 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NonGCStaticsNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NonGCStaticsNode.cs @@ -20,12 +20,14 @@ internal class NonGCStaticsNode : ObjectNode, ISymbolNode private MetadataType _type; private ISymbolNode _classConstructorContext; - public NonGCStaticsNode(MetadataType type) + public NonGCStaticsNode(MetadataType type, NodeFactory factory) { _type = type; - if (HasClassConstructorContext) + if (factory.TypeInitializationManager.HasLazyStaticConstructor(type)) { + // Class constructor context is a small struct prepended to type's non-GC static data region + // that keeps track of whether the .cctor executed and holds the pointer to the .cctor method. _classConstructorContext = new ObjectAndOffsetSymbolNode(this, 0, "__CCtorContext_" + NodeFactory.NameMangler.GetMangledTypeName(_type)); } @@ -74,7 +76,7 @@ public bool HasClassConstructorContext { get { - return _type.HasStaticConstructor; + return _classConstructorContext != null; } } @@ -115,6 +117,18 @@ public override bool StaticDependenciesAreComputed } } + protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory context) + { + if (context.TypeInitializationManager.HasEagerStaticConstructor(_type)) + { + var result = new DependencyList(); + result.Add(context.EagerCctorIndirection(_type.GetStaticConstructor()), "Eager .cctor"); + return result; + } + + return null; + } + public override ObjectData GetData(NodeFactory factory, bool relocsOnly) { ObjectDataBuilder builder = new ObjectDataBuilder(factory); diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/Target_X64/X64ReadyToRunHelperNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/Target_X64/X64ReadyToRunHelperNode.cs index 7365797e34a..54f36b8bfda 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/Target_X64/X64ReadyToRunHelperNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/Target_X64/X64ReadyToRunHelperNode.cs @@ -77,7 +77,7 @@ protected override void EmitCode(NodeFactory factory, ref X64Emitter encoder, bo case ReadyToRunHelperId.GetNonGCStaticBase: { MetadataType target = (MetadataType)Target; - if (!target.HasStaticConstructor) + if (!factory.TypeInitializationManager.HasLazyStaticConstructor(target)) { Debug.Assert(Id == ReadyToRunHelperId.GetNonGCStaticBase); encoder.EmitLEAQ(encoder.TargetRegister.Result, factory.TypeNonGCStaticsSymbol(target)); @@ -100,7 +100,7 @@ protected override void EmitCode(NodeFactory factory, ref X64Emitter encoder, bo case ReadyToRunHelperId.GetGCStaticBase: { MetadataType target = (MetadataType)Target; - if (!target.HasStaticConstructor) + if (!factory.TypeInitializationManager.HasLazyStaticConstructor(target)) { encoder.EmitLEAQ(encoder.TargetRegister.Result, factory.TypeGCStaticsSymbol(target)); AddrMode loadFromRax = new AddrMode(encoder.TargetRegister.Result, null, 0, 0, AddrModeSize.Int64); diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ThreadStaticsNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ThreadStaticsNode.cs index e122e2dd7c8..5871ee0d249 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ThreadStaticsNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ThreadStaticsNode.cs @@ -48,8 +48,18 @@ public ISymbolNode GetGCStaticEETypeNode(NodeFactory context) public override IEnumerable GetStaticDependencies(NodeFactory context) { - return new DependencyListEntry[] { new DependencyListEntry(context.ThreadStaticsRegion, "ThreadStatics Region"), - new DependencyListEntry(GetGCStaticEETypeNode(context), "ThreadStatic EEType")}; + DependencyListEntry[] result; + if (context.TypeInitializationManager.HasEagerStaticConstructor(_type)) + { + result = new DependencyListEntry[3]; + result[2] = new DependencyListEntry(context.EagerCctorIndirection(_type.GetStaticConstructor()), "Eager .cctor"); + } + else + result = new DependencyListEntry[2]; + + result[0] = new DependencyListEntry(context.ThreadStaticsRegion, "ThreadStatics Region"); + result[1] = new DependencyListEntry(GetGCStaticEETypeNode(context), "ThreadStatic EEType"); + return result; } int ISymbolNode.Offset diff --git a/src/ILCompiler.Compiler/src/Compiler/FormattingHelpers.cs b/src/ILCompiler.Compiler/src/Compiler/FormattingHelpers.cs new file mode 100644 index 00000000000..e076e2c8c07 --- /dev/null +++ b/src/ILCompiler.Compiler/src/Compiler/FormattingHelpers.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Globalization; + +namespace ILCompiler +{ + static class FormattingHelpers + { + public static string ToStringInvariant(this int value) + { + return value.ToString(CultureInfo.InvariantCulture); + } + } +} diff --git a/src/ILCompiler.Compiler/src/Compiler/TypeInitialization.cs b/src/ILCompiler.Compiler/src/Compiler/TypeInitialization.cs new file mode 100644 index 00000000000..80dbfd7ae2d --- /dev/null +++ b/src/ILCompiler.Compiler/src/Compiler/TypeInitialization.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Internal.TypeSystem; + +namespace ILCompiler +{ + /// + /// Manages policies around static constructors (.cctors) and static data initialization. + /// + public class TypeInitialization + { + // Eventually, this class will also manage preinitialization (interpreting cctors at compile + // time and converting them to blobs of preinitialized data), and the various + // System.Runtime.CompilerServices.PreInitializedAttribute/InitDataBlobAttribute/etc. placed on + // types and their members by toolchain components. + + /// + /// Returns true if '' has a lazily executed static constructor. + /// A lazy static constructor gets executed on first access to type's members. + /// + public bool HasLazyStaticConstructor(TypeDesc type) + { + return type.HasStaticConstructor && !HasEagerConstructorAttribute(type); + } + + /// + /// Returns true if '' has a static constructor that is eagerly + /// executed at process startup time. + /// + public bool HasEagerStaticConstructor(TypeDesc type) + { + return type.HasStaticConstructor && HasEagerConstructorAttribute(type); + } + + private static bool HasEagerConstructorAttribute(TypeDesc type) + { + MetadataType mdType = type as MetadataType; + return mdType != null && mdType.HasCustomAttribute("System.Runtime.CompilerServices", "EagerOrderedStaticConstructorAttribute"); + } + } +} diff --git a/src/ILCompiler.Compiler/src/ILCompiler.Compiler.csproj b/src/ILCompiler.Compiler/src/ILCompiler.Compiler.csproj index bfeedc2bca4..a8f94a9208c 100644 --- a/src/ILCompiler.Compiler/src/ILCompiler.Compiler.csproj +++ b/src/ILCompiler.Compiler/src/ILCompiler.Compiler.csproj @@ -41,10 +41,13 @@ + + + @@ -73,6 +76,7 @@ + @@ -80,6 +84,7 @@ + diff --git a/src/JitInterface/src/CorInfoImpl.cs b/src/JitInterface/src/CorInfoImpl.cs index ed65c865c5e..a8d04e895de 100644 --- a/src/JitInterface/src/CorInfoImpl.cs +++ b/src/JitInterface/src/CorInfoImpl.cs @@ -1037,7 +1037,7 @@ private CorInfoInitClassResult initClass(CORINFO_FIELD_STRUCT_* field, CORINFO_M MethodDesc md = HandleToObject(method); TypeDesc type = fd != null ? fd.OwningType : typeFromContext(context); - if (!type.HasStaticConstructor) + if (!_compilation.HasLazyStaticConstructor(type)) { return CorInfoInitClassResult.CORINFO_INITCLASS_NOT_REQUIRED; } diff --git a/src/Native/Bootstrap/main.cpp b/src/Native/Bootstrap/main.cpp index ff59915531e..397cee96177 100644 --- a/src/Native/Bootstrap/main.cpp +++ b/src/Native/Bootstrap/main.cpp @@ -255,6 +255,8 @@ void * g_pDispatchMapTemporaryWorkaround; extern "C" void* __StringTableStart; extern "C" void* __StringTableEnd; +extern "C" void* __EagerCctorStart; +extern "C" void* __EagerCctorEnd; extern "C" void* GetModuleSection(int id, int* length) { struct ModuleSectionSymbol @@ -269,9 +271,11 @@ extern "C" void* GetModuleSection(int id, int* length) #ifdef CPPCODEGEN { System::String::__getMethodTable(), sizeof(void*) }, { nullptr, 0 }, + { nullptr, 0 }, #else { &__EEType_System_Private_CoreLib_System_String, sizeof(void*) }, { &__StringTableStart, (size_t)((uint8_t*)&__StringTableEnd - (uint8_t*)&__StringTableStart) }, + { &__EagerCctorStart, (size_t)((uint8_t*)&__EagerCctorEnd - (uint8_t*)&__EagerCctorStart) }, #endif }; diff --git a/src/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/StartupCode/StartupCodeHelpers.cs b/src/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/StartupCode/StartupCodeHelpers.cs index e745e9a6b8a..e58200a9a2d 100644 --- a/src/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/StartupCode/StartupCodeHelpers.cs +++ b/src/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/StartupCode/StartupCodeHelpers.cs @@ -11,6 +11,8 @@ using System; using System.Runtime; +using Debug = System.Diagnostics.Debug; + namespace Internal.Runtime.CompilerHelpers { internal static class StartupCodeHelpers @@ -18,6 +20,7 @@ internal static class StartupCodeHelpers internal static void Initialize() { InitializeStringTable(); + RunEagerClassConstructors(); RuntimeImports.RhEnableShutdownFinalization(0xffffffffu); } @@ -76,6 +79,20 @@ private static unsafe void InitializeStringTable() } } + private static unsafe void RunEagerClassConstructors() + { + int length = 0; + IntPtr cctorTableStart = GetModuleSection((int)ModuleSectionIds.EagerCctorStart, out length); + Debug.Assert(length % IntPtr.Size == 0); + + IntPtr cctorTableEnd = (IntPtr)((byte*)cctorTableStart + length); + + for (IntPtr* tab = (IntPtr*)cctorTableStart; tab < (IntPtr*)cctorTableEnd; tab++) + { + CalliIntrinsics.Call(*tab); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static unsafe int CStrLen(byte* str) { @@ -88,7 +105,8 @@ internal static unsafe int CStrLen(byte* str) internal enum ModuleSectionIds { StringEETypePtr, - StringFixupStart + StringFixupStart, + EagerCctorStart, }; [RuntimeImport(".", "GetModuleSection")]