Skip to content
This repository has been archived by the owner on Nov 1, 2020. It is now read-only.

Commit

Permalink
Merge pull request #2488 from nattress/cctorrunner
Browse files Browse the repository at this point in the history
Run ordered class constructors explicitly
  • Loading branch information
nattress authored Jan 13, 2017
2 parents e48efff + 8089be9 commit 358c945
Show file tree
Hide file tree
Showing 23 changed files with 312 additions and 112 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,56 +41,8 @@ public bool HasEagerStaticConstructor(TypeDesc type)
private static bool HasEagerConstructorAttribute(TypeDesc type)
{
MetadataType mdType = type as MetadataType;
return mdType != null && (
mdType.HasCustomAttribute("System.Runtime.CompilerServices", "EagerOrderedStaticConstructorAttribute")
|| mdType.HasCustomAttribute("System.Runtime.CompilerServices", "EagerStaticClassConstructionAttribute"));
}
}

public class EagerConstructorComparer : IComparer<DependencyAnalysis.IMethodNode>
{
private int GetConstructionOrder(MetadataType type)
{
// For EagerOrderedStaticConstructorAttribute, order is defined by an integer.
// For the other case (EagerStaticClassConstructionAttribute), order is defined
// implicitly.

var decoded = ((EcmaType)type.GetTypeDefinition()).GetDecodedCustomAttribute(
"System.Runtime.CompilerServices", "EagerOrderedStaticConstructorAttribute");

if (decoded != null)
return (int)decoded.Value.FixedArguments[0].Value;

Debug.Assert(type.HasCustomAttribute("System.Runtime.CompilerServices", "EagerStaticClassConstructionAttribute"));
// RhBind on .NET Native for UWP will sort these based on static dependencies of the .cctors.
// We could probably do the same, but this attribute is pretty much deprecated in favor of
// EagerOrderedStaticConstructorAttribute that has explicit order. The remaining uses of
// the unordered one don't appear to have dependencies, so sorting them all before the
// ordered ones should do.
return -1;
}

public int Compare(DependencyAnalysis.IMethodNode x, DependencyAnalysis.IMethodNode y)
{
var typeX = (MetadataType)x.Method.OwningType;
var typeY = (MetadataType)y.Method.OwningType;

int orderX = GetConstructionOrder(typeX);
int orderY = GetConstructionOrder(typeY);

int result;
if (orderX != orderY)
{
result = Comparer<int>.Default.Compare(orderX, orderY);
}
else
{
// Use type name as a tie breaker. We need this algorithm to produce stable
// ordering so that the sequence of eager cctors is deterministic.
result = String.Compare(typeX.GetFullName(), typeY.GetFullName(), StringComparison.Ordinal);
}

return result;
return mdType != null &&
mdType.HasCustomAttribute("System.Runtime.CompilerServices", "EagerStaticClassConstructionAttribute");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,7 @@ public string GetSymbolAlternateName(ISymbolNode node)
public ArrayOfEmbeddedPointersNode<IMethodNode> EagerCctorTable = new ArrayOfEmbeddedPointersNode<IMethodNode>(
"__EagerCctorStart",
"__EagerCctorEnd",
new EagerConstructorComparer());
null);

public ArrayOfEmbeddedPointersNode<InterfaceDispatchMapNode> DispatchMapTable = new ArrayOfEmbeddedPointersNode<InterfaceDispatchMapNode>(
"__DispatchMapTableStart",
Expand Down
103 changes: 103 additions & 0 deletions src/ILCompiler.Compiler/src/Compiler/LibraryInitializers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// 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;

using Internal.TypeSystem;

using AssemblyName = System.Reflection.AssemblyName;
using Debug = System.Diagnostics.Debug;

namespace ILCompiler
{
/// <summary>
/// Encapsulates a list of class constructors that must be run in a prescribed order during start-up
/// </summary>
public sealed class LibraryInitializers
{
private const string ClassLibraryPlaceHolderString = "*ClassLibrary*";
private const string LibraryInitializerContainerNamespaceName = "Internal.Runtime.CompilerHelpers";
private const string LibraryInitializerContainerTypeName = "LibraryInitializer";
private const string LibraryInitializerMethodName = "InitializeLibrary";

private static readonly LibraryInitializerInfo[] s_assembliesWithLibraryInitializers =
{
new LibraryInitializerInfo(ClassLibraryPlaceHolderString),
new LibraryInitializerInfo("System.Private.TypeLoader", false),
new LibraryInitializerInfo("System.Private.Reflection.Execution", false),
new LibraryInitializerInfo("System.Private.DeveloperExperience.Console"),
};

private List<MethodDesc> _libraryInitializerMethods;

private readonly TypeSystemContext _context;
private readonly bool _isCppCodeGen;

public LibraryInitializers(TypeSystemContext context, bool isCppCodeGen)
{
_context = context;
//
// We should not care which code-gen is being used, however currently CppCodeGen cannot
// handle code pulled in by all explicit cctors.
//
// See https://github.com/dotnet/corert/issues/2486
//
_isCppCodeGen = isCppCodeGen;
}

public IList<MethodDesc> LibraryInitializerMethods
{
get
{
if (_libraryInitializerMethods == null)
InitLibraryInitializers();

return _libraryInitializerMethods;
}
}

private void InitLibraryInitializers()
{
Debug.Assert(_libraryInitializerMethods == null);

_libraryInitializerMethods = new List<MethodDesc>();

foreach (var entry in s_assembliesWithLibraryInitializers)
{
if (_isCppCodeGen && !entry.UseWithCppCodeGen)
continue;

ModuleDesc assembly = entry.Assembly == ClassLibraryPlaceHolderString
? _context.SystemModule
: _context.ResolveAssembly(new AssemblyName(entry.Assembly), false);

if (assembly == null)
continue;

TypeDesc containingType = assembly.GetType(LibraryInitializerContainerNamespaceName, LibraryInitializerContainerTypeName, false);
if (containingType == null)
continue;

MethodDesc initializerMethod = containingType.GetMethod(LibraryInitializerMethodName, null);
if (initializerMethod == null)
continue;

_libraryInitializerMethods.Add(initializerMethod);
}
}

private sealed class LibraryInitializerInfo
{
public string Assembly { get; }
public bool UseWithCppCodeGen { get; }

public LibraryInitializerInfo(string assembly, bool useWithCppCodeGen = true)
{
Assembly = assembly;
UseWithCppCodeGen = useWithCppCodeGen;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;

using Internal.TypeSystem;
using Internal.TypeSystem.Ecma;
Expand All @@ -21,10 +22,12 @@ public class MainMethodRootProvider : ICompilationRootProvider
public const string ManagedEntryPointMethodName = "__managed__Main";

private EcmaModule _module;
private IList<MethodDesc> _libraryInitializers;

public MainMethodRootProvider(EcmaModule module)
public MainMethodRootProvider(EcmaModule module, IList<MethodDesc> libraryInitializers)
{
_module = module;
_libraryInitializers = libraryInitializers;
}

public void AddCompilationRoots(IRootingServiceProvider rootProvider)
Expand All @@ -34,7 +37,7 @@ public void AddCompilationRoots(IRootingServiceProvider rootProvider)
throw new Exception("No managed entrypoint defined for executable module");

TypeDesc owningType = _module.GetGlobalModuleType();
var startupCodeMain = new StartupCodeMainMethod(owningType, mainMethod);
var startupCodeMain = new StartupCodeMainMethod(owningType, mainMethod, _libraryInitializers);

rootProvider.AddCompilationRoot(startupCodeMain, "Startup Code Main Method", ManagedEntryPointMethodName);
}
Expand Down
14 changes: 0 additions & 14 deletions src/ILCompiler.Compiler/src/CppCodeGen/CppWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -378,20 +378,6 @@ public void CompileMethod(CppMethodCodeNode methodCodeNodeNeedingCode)
if (methodIL == null)
return;

// TODO: Remove this code once CppCodegen is able to generate code for the reflection startup path.
// The startup path runs before any user code is executed.
// For now we replace the startup path with a simple "ret". Reflection won't work, but
// programs not using reflection will.
if (method.Name == ".cctor")
{
MetadataType owningType = method.OwningType as MetadataType;
if (owningType != null &&
owningType.Name == "ReflectionExecution" && owningType.Namespace == "Internal.Reflection.Execution")
{
methodIL = new Internal.IL.Stubs.ILStubMethodIL(method, new byte[] { (byte)ILOpcode.ret }, Array.Empty<LocalVariableDefinition>(), null);
}
}

try
{
// TODO: hacky special-case
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;

using Internal.TypeSystem;

Expand All @@ -20,11 +21,13 @@ public sealed class StartupCodeMainMethod : ILStubMethod
private TypeDesc _owningType;
private MainMethodWrapper _mainMethod;
private MethodSignature _signature;
private IList<MethodDesc> _libraryInitializers;

public StartupCodeMainMethod(TypeDesc owningType, MethodDesc mainMethod)
public StartupCodeMainMethod(TypeDesc owningType, MethodDesc mainMethod, IList<MethodDesc> libraryInitializers)
{
_owningType = owningType;
_mainMethod = new MainMethodWrapper(owningType, mainMethod);
_libraryInitializers = libraryInitializers;
}

public override TypeSystemContext Context
Expand Down Expand Up @@ -56,14 +59,15 @@ public override MethodIL EmitIL()
ILEmitter emitter = new ILEmitter();
ILCodeStream codeStream = emitter.NewCodeStream();

ModuleDesc developerExperience = Context.ResolveAssembly(new AssemblyName("System.Private.DeveloperExperience.Console"), false);
if (developerExperience != null)
// Allow the class library to run explicitly ordered class constructors first thing in start-up.
if (_libraryInitializers != null)
{
TypeDesc connectorType = developerExperience.GetKnownType("Internal.DeveloperExperience", "DeveloperExperienceConnectorConsole");
MethodDesc initializeMethod = connectorType.GetKnownMethod("Initialize", null);
codeStream.Emit(ILOpcode.call, emitter.NewToken(initializeMethod));
foreach (MethodDesc method in _libraryInitializers)
{
codeStream.Emit(ILOpcode.call, emitter.NewToken(method));
}
}

MetadataType startup = Context.GetHelperType("StartupCodeHelpers");

// Initialize command line args if the class library supports this
Expand Down
1 change: 1 addition & 0 deletions src/ILCompiler.Compiler/src/ILCompiler.Compiler.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
<Link>JitInterface\JitConfigProvider.cs</Link>
</Compile>
<Compile Include="Compiler\CompilerGeneratedType.cs" />
<Compile Include="Compiler\LibraryInitializers.cs" />
<Compile Include="Compiler\ICompilationRootProvider.cs" />
<Compile Include="Compiler\Compilation.cs" />
<Compile Include="Compiler\CompilationBuilder.cs" />
Expand Down
18 changes: 3 additions & 15 deletions src/ILCompiler/src/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,9 @@ private int Run(string[] args)

if (entrypointModule != null)
{
compilationRoots.Add(new MainMethodRootProvider(entrypointModule));
LibraryInitializers libraryInitializers =
new LibraryInitializers(typeSystemContext, _isCppCodegen);
compilationRoots.Add(new MainMethodRootProvider(entrypointModule, libraryInitializers.LibraryInitializerMethods));
}

if (_multiFile)
Expand Down Expand Up @@ -240,20 +242,6 @@ private int Run(string[] args)

compilationRoots.Add(new ExportedMethodsRootProvider((EcmaModule)typeSystemContext.SystemModule));

// System.Private.Reflection.Execution needs to establish a communication channel with System.Private.CoreLib
// at process startup. This is done through an eager constructor that calls into CoreLib and passes it
// a callback object.
//
// Since CoreLib cannot reference anything, the type and it's eager constructor won't be added to the compilation
// unless we explictly add it.

var refExec = typeSystemContext.GetModuleForSimpleName("System.Private.Reflection.Execution", false);
if (refExec != null)
{
var exec = refExec.GetType("Internal.Reflection.Execution", "ReflectionExecution");
compilationRoots.Add(new SingleMethodRootProvider(exec.GetStaticConstructor()));
}

compilationGroup = new SingleFileCompilationModuleGroup(typeSystemContext);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 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.Runtime;
using System.Runtime.CompilerServices;

using Debug = System.Diagnostics.Debug;

namespace Internal.Runtime.CompilerHelpers
{
/// <summary>
/// Container class to run specific class constructors in a defined order. Since we can't
/// directly invoke class constructors in C#, they're renamed Initialize.
/// </summary>
internal class LibraryInitializer
{
public static void InitializeLibrary()
{
PreallocatedOutOfMemoryException.Initialize();
ClassConstructorRunner.Initialize();
TypeLoaderExports.Initialize();
}
}
}
3 changes: 2 additions & 1 deletion src/System.Private.CoreLib/src/System.Private.CoreLib.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
<ItemGroup>
<Compile Include="Internal\Diagnostics\ExceptionExtensions.cs" />
<Compile Include="Internal\Diagnostics\StackTraceHelper.cs" />
<Compile Include="Internal\Runtime\CompilerHelpers\LibraryInitializer.cs" />
<Compile Include="Internal\Runtime\CompilerHelpers\ReflectionHelpers.cs" />
<Compile Include="Internal\Runtime\CompilerHelpers\StartupCode\ThreadingHelpers.cs" />
</ItemGroup>
Expand Down Expand Up @@ -1183,4 +1184,4 @@
</EmbeddedResource>
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
</Project>
</Project>
Loading

0 comments on commit 358c945

Please sign in to comment.