|
// 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.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Internal.IL;
using Internal.IL.Stubs;
using Internal.JitInterface;
using Internal.ReadyToRunConstants;
using Internal.TypeSystem;
using ILCompiler.DependencyAnalysis;
using ILCompiler.DependencyAnalysis.ReadyToRun;
using ILCompiler.DependencyAnalysisFramework;
using ILCompiler.ReadyToRun;
using ILCompiler.Reflection.ReadyToRun;
using Internal.TypeSystem.Ecma;
using ILCompiler.ReadyToRun.TypeSystem;
namespace ILCompiler
{
public abstract class Compilation : ICompilation, IDisposable
{
protected readonly DependencyAnalyzerBase<NodeFactory> _dependencyGraph;
protected readonly NodeFactory _nodeFactory;
protected readonly Logger _logger;
private readonly DevirtualizationManager _devirtualizationManager;
protected ILCache _methodILCache;
private readonly HashSet<ModuleDesc> _modulesBeingInstrumented;
public NameMangler NameMangler => _nodeFactory.NameMangler;
public NodeFactory NodeFactory => _nodeFactory;
public CompilerTypeSystemContext TypeSystemContext => NodeFactory.TypeSystemContext;
public Logger Logger => _logger;
public InstructionSetSupport InstructionSetSupport { get; }
protected Compilation(
DependencyAnalyzerBase<NodeFactory> dependencyGraph,
NodeFactory nodeFactory,
IEnumerable<ICompilationRootProvider> compilationRoots,
ILProvider ilProvider,
DevirtualizationManager devirtualizationManager,
IEnumerable<ModuleDesc> modulesBeingInstrumented,
Logger logger,
InstructionSetSupport instructionSetSupport)
{
InstructionSetSupport = instructionSetSupport;
_dependencyGraph = dependencyGraph;
_nodeFactory = nodeFactory;
_logger = logger;
_devirtualizationManager = devirtualizationManager;
_modulesBeingInstrumented = new HashSet<ModuleDesc>(modulesBeingInstrumented);
_dependencyGraph.ComputeDependencyRoutine += ComputeDependencyNodeDependencies;
NodeFactory.AttachToDependencyGraph(_dependencyGraph, ilProvider);
var rootingService = new RootingServiceProvider(nodeFactory, _dependencyGraph.AddRoot);
foreach (var rootProvider in compilationRoots)
rootProvider.AddCompilationRoots(rootingService);
_methodILCache = new ILCache((ReadyToRunILProvider)ilProvider, NodeFactory.CompilationModuleGroup);
}
public abstract void Dispose();
public abstract void Compile(string outputFileName);
public abstract void WriteDependencyLog(string outputFileName);
protected abstract void ComputeDependencyNodeDependencies(List<DependencyNodeCore<NodeFactory>> obj);
public bool CanInline(MethodDesc caller, MethodDesc callee)
{
if (JitConfigProvider.Instance.HasFlag(CorJitFlag.CORJIT_FLAG_DEBUG_CODE))
{
// If the callee wants debuggable code, don't allow it to be inlined
return false;
}
if (callee.IsNoInlining || callee.IsNoOptimization)
{
// NoOptimization implies NoInlining
return false;
}
// Check to see if the method requires a security object. This means they call demand and
// shouldn't be inlined.
if (callee.RequireSecObject)
{
return false;
}
// If the method is MethodImpl'd by another method within the same type, then we have
// an issue that the importer will import the wrong body. In this case, we'll just
// disallow inlining because getFunctionEntryPoint will do the right thing.
if (callee.IsVirtual)
{
MethodDesc calleeMethodImpl = callee.OwningType.FindVirtualFunctionTargetMethodOnObjectType(callee);
if (calleeMethodImpl != callee)
{
return false;
}
}
_nodeFactory.DetectGenericCycles(caller, callee);
return NodeFactory.CompilationModuleGroup.CanInline(caller, callee);
}
public virtual MethodIL GetMethodIL(MethodDesc method)
{
return _methodILCache.GetOrCreateValue(method).MethodIL;
}
public bool IsEffectivelySealed(TypeDesc type)
{
return _devirtualizationManager.IsEffectivelySealed(type);
}
public bool IsEffectivelySealed(MethodDesc method)
{
return _devirtualizationManager.IsEffectivelySealed(method);
}
public MethodDesc ResolveVirtualMethod(MethodDesc declMethod, TypeDesc implType, out CORINFO_DEVIRTUALIZATION_DETAIL devirtualizationDetail)
{
if (declMethod.OwningType.IsInterface)
{
// The virtual method resolution algorithm in the managed type system is not implemented to work correctly
// in the presence of calling type equivalent interfaces.
// Notably:
// If the decl is to a interface equivalent to, but not equal to any interface implemented on the
// owning type, then the logic for matching up methods by method index is not present.
// AND
// If the owningType implements multiple different type equivalent interfaces that are all mutually
// equivalent, the implementation for finding the correct implementation method requires walking the
// type hierarchy and searching for exact and equivalent matches at each level (much like variance)
// This logic is also currently unimplemented.
// NOTE: We do not currently have tests in the runtime suite which cover these cases
if (declMethod.OwningType.HasTypeEquivalence)
{
// To protect against this, require that the implType implement exactly the right interface, and
// no additional interfaces that are equivalent
bool foundExactMatch = false;
bool foundEquivalentMatch = false;
foreach (var @interface in implType.RuntimeInterfaces)
{
if (@interface == declMethod.OwningType)
{
foundExactMatch = true;
continue;
}
if (@interface.IsEquivalentTo(declMethod.OwningType))
{
foundEquivalentMatch = true;
}
}
if (!foundExactMatch || foundEquivalentMatch)
{
devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_TYPE_EQUIVALENCE;
return null;
}
}
}
return _devirtualizationManager.ResolveVirtualMethod(declMethod, implType, out devirtualizationDetail);
}
public bool IsModuleInstrumented(ModuleDesc module)
{
return _modulesBeingInstrumented.Contains(module);
}
public sealed class ILCache : LockFreeReaderHashtable<MethodDesc, ILCache.MethodILData>
{
public ReadyToRunILProvider ILProvider { get; }
public int ExpectedILProviderVersion { get; }
private readonly CompilationModuleGroup _compilationModuleGroup;
public ILCache(ReadyToRunILProvider provider, CompilationModuleGroup compilationModuleGroup)
{
ILProvider = provider;
ExpectedILProviderVersion = provider.Version;
_compilationModuleGroup = compilationModuleGroup;
}
protected override int GetKeyHashCode(MethodDesc key)
{
return key.GetHashCode();
}
protected override int GetValueHashCode(MethodILData value)
{
return value.Method.GetHashCode();
}
protected override bool CompareKeyToValue(MethodDesc key, MethodILData value)
{
return Object.ReferenceEquals(key, value.Method);
}
protected override bool CompareValueToValue(MethodILData value1, MethodILData value2)
{
return Object.ReferenceEquals(value1.Method, value2.Method);
}
protected override MethodILData CreateValueFromKey(MethodDesc key)
{
MethodIL methodIL = ILProvider.GetMethodIL(key);
if (methodIL == null
&& key.IsPInvoke
&& _compilationModuleGroup.GeneratesPInvoke(key))
{
methodIL = PInvokeILEmitter.EmitIL(key);
}
return new MethodILData() { Method = key, MethodIL = methodIL };
}
public class MethodILData
{
public MethodDesc Method;
public MethodIL MethodIL;
}
}
private delegate void RootAdder(object o, string reason);
private class RootingServiceProvider : IRootingServiceProvider
{
private readonly NodeFactory _factory;
private readonly RootAdder _rootAdder;
private readonly DeferredTillPhaseNode _deferredPhaseNode = new DeferredTillPhaseNode(1);
public RootingServiceProvider(NodeFactory factory, RootAdder rootAdder)
{
_factory = factory;
_rootAdder = rootAdder;
_rootAdder(_deferredPhaseNode, "Deferred nodes");
}
public void AddCompilationRoot(MethodDesc method, bool rootMinimalDependencies, string reason)
{
MethodDesc canonMethod = method.GetCanonMethodTarget(CanonicalFormKind.Specific);
if (_factory.CompilationModuleGroup.ContainsMethodBody(canonMethod, false))
{
IMethodNode methodEntryPoint = _factory.CompiledMethodNode(canonMethod);
if (rootMinimalDependencies)
{
_deferredPhaseNode.AddDependency((DependencyNodeCore<NodeFactory>)methodEntryPoint);
}
else
{
_rootAdder(methodEntryPoint, reason);
}
}
}
}
}
public interface ICompilation
{
void Compile(string outputFileName);
void WriteDependencyLog(string outputFileName);
void Dispose();
}
public sealed class ReadyToRunCodegenCompilation : Compilation
{
/// <summary>
/// Input MSIL file names.
/// </summary>
private readonly IEnumerable<string> _inputFiles;
private readonly string _compositeRootPath;
private readonly bool _resilient;
private readonly int _parallelism;
private readonly CorInfoImpl[] _corInfoImpls;
private readonly bool _generateMapFile;
private readonly bool _generateMapCsvFile;
private readonly bool _generatePdbFile;
private readonly string _pdbPath;
private readonly bool _generatePerfMapFile;
private readonly string _perfMapPath;
private readonly int _perfMapFormatVersion;
private readonly bool _generateProfileFile;
private readonly Func<MethodDesc, string> _printReproInstructions;
private readonly ProfileDataManager _profileData;
private readonly FileLayoutOptimizer _fileLayoutOptimizer;
private readonly HashSet<MethodDesc> _methodsWhichNeedMutableILBodies = new HashSet<MethodDesc>();
private readonly HashSet<MethodWithGCInfo> _methodsToRecompile = new HashSet<MethodWithGCInfo>();
public ProfileDataManager ProfileData => _profileData;
public bool DeterminismCheckFailed { get; set; }
public ReadyToRunSymbolNodeFactory SymbolNodeFactory { get; }
public ReadyToRunCompilationModuleGroupBase CompilationModuleGroup { get; }
private readonly int _customPESectionAlignment;
private readonly ReadyToRunContainerFormat _format;
/// <summary>
/// Determining whether a type's layout is fixed is a little expensive and the question can be asked many times
/// for the same type during compilation so preserve the computed value.
/// </summary>
private ConcurrentDictionary<TypeDesc, bool> _computedFixedLayoutTypes = new ConcurrentDictionary<TypeDesc, bool>();
private Func<TypeDesc, bool> _computedFixedLayoutTypesUncached;
private readonly ExternalReferenceTokenManager _tokenManager;
internal ReadyToRunCodegenCompilation(
DependencyAnalyzerBase<NodeFactory> dependencyGraph,
NodeFactory nodeFactory,
IEnumerable<ICompilationRootProvider> roots,
ILProvider ilProvider,
Logger logger,
DevirtualizationManager devirtualizationManager,
IEnumerable<string> inputFiles,
string compositeRootPath,
InstructionSetSupport instructionSetSupport,
bool resilient,
bool generateMapFile,
bool generateMapCsvFile,
bool generatePdbFile,
Func<MethodDesc, string> printReproInstructions,
string pdbPath,
bool generatePerfMapFile,
string perfMapPath,
int perfMapFormatVersion,
bool generateProfileFile,
int parallelism,
ProfileDataManager profileData,
MethodLayoutAlgorithm methodLayoutAlgorithm,
FileLayoutAlgorithm fileLayoutAlgorithm,
int customPESectionAlignment,
bool verifyTypeAndFieldLayout,
ReadyToRunContainerFormat format)
: base(
dependencyGraph,
nodeFactory,
roots,
ilProvider,
devirtualizationManager,
modulesBeingInstrumented: nodeFactory.CompilationModuleGroup.CompilationModuleSet,
logger,
instructionSetSupport)
{
_computedFixedLayoutTypesUncached = IsLayoutFixedInCurrentVersionBubbleInternal;
_resilient = resilient;
_parallelism = parallelism;
_corInfoImpls = new CorInfoImpl[_parallelism];
_generateMapFile = generateMapFile;
_generateMapCsvFile = generateMapCsvFile;
_generatePdbFile = generatePdbFile;
_pdbPath = pdbPath;
_generatePerfMapFile = generatePerfMapFile;
_perfMapPath = perfMapPath;
_perfMapFormatVersion = perfMapFormatVersion;
_generateProfileFile = generateProfileFile;
_customPESectionAlignment = customPESectionAlignment;
_format = format;
SymbolNodeFactory = new ReadyToRunSymbolNodeFactory(nodeFactory, verifyTypeAndFieldLayout);
if (nodeFactory.InstrumentationDataTable != null)
nodeFactory.InstrumentationDataTable.Initialize(SymbolNodeFactory);
if (nodeFactory.CrossModuleInlningInfo != null)
nodeFactory.CrossModuleInlningInfo.Initialize(SymbolNodeFactory);
if (nodeFactory.ImportReferenceProvider != null)
nodeFactory.ImportReferenceProvider.Initialize(SymbolNodeFactory);
_inputFiles = inputFiles;
_compositeRootPath = compositeRootPath;
_printReproInstructions = printReproInstructions;
CompilationModuleGroup = (ReadyToRunCompilationModuleGroupBase)nodeFactory.CompilationModuleGroup;
// Generate baseline support specification for InstructionSetSupport. This will prevent usage of the generated
// code if the runtime environment doesn't support the specified instruction set
string instructionSetSupportString = ReadyToRunInstructionSetSupportSignature.ToInstructionSetSupportString(instructionSetSupport);
ReadyToRunInstructionSetSupportSignature instructionSetSupportSig = new ReadyToRunInstructionSetSupportSignature(instructionSetSupportString);
_dependencyGraph.AddRoot(new Import(NodeFactory.EagerImports, instructionSetSupportSig), "Baseline instruction set support");
_profileData = profileData;
_fileLayoutOptimizer = new FileLayoutOptimizer(logger, methodLayoutAlgorithm, fileLayoutAlgorithm, profileData, _nodeFactory);
_tokenManager = new ExternalReferenceTokenManager(_nodeFactory.ManifestMetadataTable._mutableModule, _nodeFactory.Resolver);
}
private readonly static string s_folderUpPrefix = ".." + Path.DirectorySeparatorChar;
public override void Compile(string outputFile)
{
_dependencyGraph.ComputeMarkedNodes();
_doneAllCompiling = true;
Array.Clear(_corInfoImpls);
_compilationThreadSemaphore.Release(_parallelism);
var nodes = _dependencyGraph.MarkedNodeList;
nodes = _fileLayoutOptimizer.ApplyProfilerGuidedMethodSort(nodes);
using (PerfEventSource.StartStopEvents.EmittingEvents())
{
NodeFactory.SetMarkingComplete();
ReadyToRunObjectWriter.EmitObject(
outputFile,
componentModule: null,
inputFiles: _inputFiles,
nodes,
NodeFactory,
generateMapFile: _generateMapFile,
generateMapCsvFile: _generateMapCsvFile,
generatePdbFile: _generatePdbFile,
pdbPath: _pdbPath,
generatePerfMapFile: _generatePerfMapFile,
perfMapPath: _perfMapPath,
perfMapFormatVersion: _perfMapFormatVersion,
generateProfileFile: _generateProfileFile,
callChainProfile: _profileData.CallChainProfile,
_format,
_customPESectionAlignment,
_logger);
CompilationModuleGroup moduleGroup = _nodeFactory.CompilationModuleGroup;
if (moduleGroup.IsCompositeBuildMode)
{
// In composite mode with standalone MSIL we rewrite all input MSIL assemblies to the
// output folder, adding a formal R2R header to them with forwarding information to
// the composite executable.
string outputDirectory = Path.GetDirectoryName(outputFile);
string ownerExecutableName = Path.GetFileName(outputFile);
if (_format == ReadyToRunContainerFormat.MachO)
{
// MachO composite images have the owner executable name stored with the dylib extension
ownerExecutableName = Path.ChangeExtension(ownerExecutableName, ".dylib");
}
HashSet<MethodDesc> compiledMethodDefs = null;
if (_nodeFactory.OptimizationFlags.StripILBodies)
{
compiledMethodDefs = _nodeFactory.BuildCompiledMethodDefsSet();
}
foreach (string inputFile in _inputFiles)
{
string relativeMsilPath = Path.GetRelativePath(_compositeRootPath, inputFile);
if (relativeMsilPath == inputFile || relativeMsilPath.StartsWith(s_folderUpPrefix, StringComparison.Ordinal))
{
// Input file not under the composite root, emit to root output folder
relativeMsilPath = Path.GetFileName(inputFile);
}
string standaloneMsilOutputFile = Path.Combine(outputDirectory, relativeMsilPath);
RewriteComponentFile(inputFile: inputFile, outputFile: standaloneMsilOutputFile, ownerExecutableName: ownerExecutableName, compiledMethodDefs: compiledMethodDefs);
}
}
}
}
private void RewriteComponentFile(string inputFile, string outputFile, string ownerExecutableName, HashSet<MethodDesc> compiledMethodDefs)
{
EcmaModule inputModule = NodeFactory.TypeSystemContext.GetModuleFromPath(inputFile);
Directory.CreateDirectory(Path.GetDirectoryName(outputFile));
ReadyToRunFlags flags =
ReadyToRunFlags.READYTORUN_FLAG_Component |
ReadyToRunFlags.READYTORUN_FLAG_NonSharedPInvokeStubs;
if (inputModule.IsPlatformNeutral || inputModule.PEReader.IsReadyToRunPlatformNeutralSource())
{
flags |= ReadyToRunFlags.READYTORUN_FLAG_PlatformNeutralSource;
}
bool automaticTypeValidation = _nodeFactory.OptimizationFlags.TypeValidation == TypeValidationRule.Automatic || _nodeFactory.OptimizationFlags.TypeValidation == TypeValidationRule.AutomaticWithLogging;
if (_nodeFactory.OptimizationFlags.TypeValidation == TypeValidationRule.SkipTypeValidation)
{
flags |= ReadyToRunFlags.READYTORUN_FLAG_SkipTypeValidation;
}
NodeFactoryOptimizationFlags optimizationFlags = _nodeFactory.OptimizationFlags with { IsComponentModule = true, CompiledMethodDefs = compiledMethodDefs };
if (optimizationFlags.StripILBodies)
{
flags |= ReadyToRunFlags.READYTORUN_FLAG_StrippedILBodies;
}
if (optimizationFlags.StripInliningInfo)
{
flags |= ReadyToRunFlags.READYTORUN_FLAG_StrippedInliningInfo;
}
if (optimizationFlags.StripDebugInfo)
{
flags |= ReadyToRunFlags.READYTORUN_FLAG_StrippedDebugInfo;
}
flags |= _nodeFactory.CompilationModuleGroup.GetReadyToRunFlags() & ReadyToRunFlags.READYTORUN_FLAG_MultiModuleVersionBubble;
bool isNativeCompositeImage = false;
if (NodeFactory.Target.IsWindows && NodeFactory.Format == ReadyToRunContainerFormat.PE)
{
isNativeCompositeImage = true;
}
else if (NodeFactory.Target.IsApplePlatform && NodeFactory.Format == ReadyToRunContainerFormat.MachO)
{
isNativeCompositeImage = true;
}
if (isNativeCompositeImage)
{
flags |= ReadyToRunFlags.READYTORUN_FLAG_PlatformNativeImage;
}
CopiedCorHeaderNode copiedCorHeader = new CopiedCorHeaderNode(inputModule);
// Re-written components shouldn't have any additional diagnostic information - only information about the forwards.
// Even with all of this, we might be modifying the image in a silly manner - adding a directory when if didn't have one.
DebugDirectoryNode debugDirectory = new DebugDirectoryNode(inputModule, outputFile, shouldAddNiPdb: false, shouldGeneratePerfmap: false, perfMapFormatVersion: 0);
NodeFactory componentFactory = new NodeFactory(
_nodeFactory.TypeSystemContext,
_nodeFactory.CompilationModuleGroup,
null,
_nodeFactory.NameMangler,
copiedCorHeader,
debugDirectory,
win32Resources: new Win32Resources.ResourceData(inputModule),
flags: flags,
nodeFactoryOptimizationFlags: optimizationFlags,
format: ReadyToRunContainerFormat.PE,
imageBase: _nodeFactory.ImageBase,
associatedModule: automaticTypeValidation ? inputModule : null,
genericCycleDepthCutoff: -1, // We don't need generic cycle detection when rewriting component assemblies
genericCycleBreadthCutoff: -1); // as we're not actually compiling anything
IComparer<DependencyNodeCore<NodeFactory>> comparer = new SortableDependencyNode.ObjectNodeComparer(CompilerComparer.Instance);
DependencyAnalyzerBase<NodeFactory> componentGraph = new DependencyAnalyzer<NoLogStrategy<NodeFactory>, NodeFactory>(componentFactory, comparer);
componentGraph.AddRoot(componentFactory.Header, "Component module R2R header");
OwnerCompositeExecutableNode ownerExecutableNode = new OwnerCompositeExecutableNode(ownerExecutableName);
componentGraph.AddRoot(ownerExecutableNode, "Owner composite executable name");
componentGraph.AddRoot(copiedCorHeader, "Copied COR header");
componentGraph.AddRoot(debugDirectory, "Debug directory");
if (componentFactory.Win32ResourcesNode != null)
{
componentGraph.AddRoot(componentFactory.Win32ResourcesNode, "Win32 resources");
}
componentGraph.ComputeMarkedNodes();
componentFactory.Header.Add(Internal.Runtime.ReadyToRunSectionType.OwnerCompositeExecutable, ownerExecutableNode);
componentFactory.SetMarkingComplete();
ReadyToRunObjectWriter.EmitObject(
outputFile,
componentModule: inputModule,
inputFiles: new string[] { inputFile },
componentGraph.MarkedNodeList,
componentFactory,
generateMapFile: false,
generateMapCsvFile: false,
generatePdbFile: false,
pdbPath: null,
generatePerfMapFile: false,
perfMapPath: null,
perfMapFormatVersion: _perfMapFormatVersion,
generateProfileFile: false,
_profileData.CallChainProfile,
ReadyToRunContainerFormat.PE,
customPESectionAlignment: 0,
_logger);
}
public override void WriteDependencyLog(string outputFileName)
{
using (FileStream dgmlOutput = new FileStream(outputFileName, FileMode.Create))
{
DgmlWriter.WriteDependencyGraphToStream(dgmlOutput, _dependencyGraph, _nodeFactory);
dgmlOutput.Flush();
}
}
private bool IsLayoutFixedInCurrentVersionBubbleInternal(TypeDesc type)
{
// Primitive types and enums have fixed layout
if (type.IsPrimitive || type.IsEnum)
{
return true;
}
if (type is not MetadataType defType)
{
// Non metadata backed types have layout defined in all version bubbles
return true;
}
if (VectorOfTFieldLayoutAlgorithm.IsVectorOfTType(defType))
{
// Vector<T> always needs a layout check
return false;
}
if (!NodeFactory.CompilationModuleGroup.VersionsWithModule(defType.Module))
{
// Valuetypes with non-versionable attribute are candidates for fixed layout. Reject the rest.
return type is MetadataType metadataType && metadataType.IsNonVersionable();
}
// If the above condition passed, check that all instance fields have fixed layout as well. In particular,
// it is important for generic types with non-versionable layout (e.g. Nullable<T>)
foreach (var field in type.GetFields())
{
// Only instance fields matter here
if (field.IsStatic)
continue;
var fieldType = field.FieldType;
if (!fieldType.IsValueType)
continue;
if (!IsLayoutFixedInCurrentVersionBubble(fieldType))
{
return false;
}
}
return true;
}
public bool IsLayoutFixedInCurrentVersionBubble(TypeDesc type) =>
_computedFixedLayoutTypes.GetOrAdd(type, _computedFixedLayoutTypesUncached);
public bool IsInheritanceChainLayoutFixedInCurrentVersionBubble(TypeDesc type)
{
// This method is not expected to be called for value types
Debug.Assert(!type.IsValueType);
if (type.IsObject)
return true;
if (!IsLayoutFixedInCurrentVersionBubble(type))
{
return false;
}
type = type.BaseType;
if (type != null)
{
// If there are multiple inexact compilation units in the layout of the type, then the exact offset
// of a derived given field is unknown as there may or may not be alignment inserted between a type and its base
if (CompilationModuleGroup.TypeLayoutCompilationUnits(type).HasMultipleInexactCompilationUnits)
return false;
while (!type.IsObject && type != null)
{
if (!IsLayoutFixedInCurrentVersionBubble(type))
{
return false;
}
type = type.BaseType;
}
}
return true;
}
// Compilation is broken into phases which interact with dependency analysis
// Phase 0: All compilations which are driven by our standard heuristics and dependency expansion model
// Phase 1: A helper phase which works in tandem with the DeferredTillPhaseNode to gather work to be done in phase 2
// Phase 2: A phase where all compilations are not allowed to add dependencies that can trigger further compilations.
// The _finishedFirstCompilationRunInPhase2 variable works in concert some checking to ensure that we don't violate any of this model
private bool _finishedFirstCompilationRunInPhase2 = false;
public void PrepareForCompilationRetry(MethodWithGCInfo methodToBeRecompiled, IEnumerable<MethodDesc> methodsThatNeedILBodies)
{
lock (_methodsToRecompile)
{
_methodsToRecompile.Add(methodToBeRecompiled);
if (methodsThatNeedILBodies != null)
{
foreach (var method in methodsThatNeedILBodies)
{
Debug.Assert(method.IsMethodDefinition);
_methodsWhichNeedMutableILBodies.Add(method);
}
}
}
}
[ThreadStatic]
private static int s_methodsCompiledPerThread = 0;
private SemaphoreSlim _compilationThreadSemaphore = new(0);
private volatile IEnumerator<DependencyNodeCore<NodeFactory>> _currentCompilationMethodList;
private volatile bool _doneAllCompiling;
private int _finishedThreadCount;
private ManualResetEventSlim _compilationSessionComplete = new ManualResetEventSlim();
private bool _hasCreatedCompilationThreads = false;
private bool _hasAddedAsyncReferences = false;
protected override void ComputeDependencyNodeDependencies(List<DependencyNodeCore<NodeFactory>> obj)
{
bool generatedColdCode = false;
using (PerfEventSource.StartStopEvents.JitEvents())
{
// Use only main thread to compile if parallelism is 1. This allows SuperPMI to rely on non-reuse of handles in ObjectToHandle
if (Logger.IsVerbose)
Logger.Writer.WriteLine($"Processing {obj.Count} dependencies");
// Ensure all methods being compiled have assigned tokens. This matters for code from modules from outside of the version bubble
// as those tokens are dynamically assigned, and for instantiation which depend on tokens outside of the module
var ilProvider = (ReadyToRunILProvider)_methodILCache.ILProvider;
obj.MergeSortAllowDuplicates(new SortableDependencyNode.ObjectNodeComparer(CompilerComparer.Instance));
foreach (var dependency in obj)
{
if (dependency is MethodWithGCInfo methodCodeNodeNeedingCode)
{
var method = methodCodeNodeNeedingCode.Method;
var typicalDef = method.GetTypicalMethodDefinition();
if (typicalDef is EcmaMethod or AsyncMethodVariant or AsyncResumptionStub)
{
if (ilProvider.NeedsCrossModuleInlineableTokens(typicalDef) &&
!_methodsWhichNeedMutableILBodies.Contains(typicalDef) &&
CorInfoImpl.IsMethodCompilable(this, method))
{
_methodsWhichNeedMutableILBodies.Add(typicalDef);
}
}
bool shouldBeCompiled = !CorInfoImpl.ShouldCodeNotBeCompiledIntoFinalImage(InstructionSetSupport, method);
if (method.IsAsyncCall() && shouldBeCompiled)
AddNecessaryAsyncReferences(method);
if (method.IsCompilerGeneratedILBodyForAsync() && shouldBeCompiled)
EnsureAsyncThunkTokensAreAvailable(method);
if (!_nodeFactory.CompilationModuleGroup.VersionsWithMethodBody(method))
EnsureInstantiationReferencesArePresentForExternalMethod(method);
}
}
ProcessMutableMethodBodiesList();
ResetILCache();
CompileMethodList(obj);
while (_methodsToRecompile.Count > 0)
{
ProcessMutableMethodBodiesList();
ResetILCache();
MethodWithGCInfo[] methodsToRecompile = new MethodWithGCInfo[_methodsToRecompile.Count];
_methodsToRecompile.CopyTo(methodsToRecompile);
_methodsToRecompile.Clear();
Array.Sort(methodsToRecompile, new SortableDependencyNode.ObjectNodeComparer(CompilerComparer.Instance));
if (Logger.IsVerbose)
Logger.Writer.WriteLine($"Processing {methodsToRecompile.Length} recompiles");
CompileMethodList(methodsToRecompile);
}
}
ResetILCache();
if (_nodeFactory.CompilationCurrentPhase == 2)
{
_finishedFirstCompilationRunInPhase2 = true;
}
if (generatedColdCode)
{
_nodeFactory.GenerateHotColdMap(_dependencyGraph);
}
void EnsureAsyncThunkTokensAreAvailable(MethodDesc method)
{
if (!method.IsCompilerGeneratedILBodyForAsync())
return;
MethodIL il = _methodILCache.ILProvider.GetMethodIL(method);
if (il is null)
return;
var bytes = il.GetILBytes();
// Use ILTokenReplacer to iterate over tokens, not actually replace them
ILTokenReplacer.Replace(bytes, tok =>
{
switch (il.GetObject(tok))
{
case TypeSystemEntity tse:
_tokenManager.EnsureDefTokensAreAvailable(tse, ((EcmaMethod)method.GetPrimaryMethodDesc().GetTypicalMethodDefinition()).Module, true);
break;
default:
// We don't need to worry about string handles
break;
}
return tok;
});
// ILTokenReplacer doesn't handle exception regions or local variable types, so handle those separately
var exceptionRegions = (ILExceptionRegion[])il.GetExceptionRegions();
for (int i = 0; i < exceptionRegions.Length; i++)
{
var region = exceptionRegions[i];
if (region.Kind == ILExceptionRegionKind.Catch)
{
TypeSystemEntity catchType = (TypeSystemEntity)il.GetObject(region.ClassToken);
_tokenManager.EnsureDefTokensAreAvailable(catchType, ((EcmaMethod)method.GetPrimaryMethodDesc().GetTypicalMethodDefinition()).Module, true);
}
}
foreach (var local in il.GetLocals())
{
_tokenManager.EnsureDefTokensAreAvailable(local.Type, ((EcmaMethod)method.GetPrimaryMethodDesc().GetTypicalMethodDefinition()).Module, true);
}
}
void ProcessMutableMethodBodiesList()
{
MethodDesc[] mutableMethodBodyNeedList = new MethodDesc[_methodsWhichNeedMutableILBodies.Count];
_methodsWhichNeedMutableILBodies.CopyTo(mutableMethodBodyNeedList);
_methodsWhichNeedMutableILBodies.Clear();
TypeSystemComparer comparer = TypeSystemComparer.Instance;
Comparison<MethodDesc> comparison = (MethodDesc a, MethodDesc b) => comparer.Compare(a, b);
Array.Sort(mutableMethodBodyNeedList, comparison);
var ilProvider = (ReadyToRunILProvider)_methodILCache.ILProvider;
foreach (var method in mutableMethodBodyNeedList)
ilProvider.CreateCrossModuleInlineableTokensForILBody(method);
}
void ResetILCache()
{
if (_methodILCache.Count > 1000 || _methodILCache.ILProvider.Version != _methodILCache.ExpectedILProviderVersion)
_methodILCache = new ILCache(_methodILCache.ILProvider, NodeFactory.CompilationModuleGroup);
}
void CompileMethodList(IEnumerable<DependencyNodeCore<NodeFactory>> methodList)
{
// Disable generation of new tokens across the multi-threaded compile
NodeFactory.ManifestMetadataTable._mutableModule.DisableNewTokens = true;
if (_parallelism == 1)
{
foreach (var dependency in methodList)
CompileOneMethod(dependency, 0);
}
else
{
_currentCompilationMethodList = methodList.GetEnumerator();
_finishedThreadCount = 0;
_compilationSessionComplete.Reset();
if (!_hasCreatedCompilationThreads)
{
for (int compilationThreadId = 1; compilationThreadId < _parallelism; compilationThreadId++)
{
new Thread(CompilationThread).Start((object)compilationThreadId);
}
_hasCreatedCompilationThreads = true;
}
_compilationThreadSemaphore.Release(_parallelism - 1);
CompileOnThread(0);
_compilationSessionComplete.Wait();
}
// Re-enable generation of new tokens after the multi-threaded compile
NodeFactory.ManifestMetadataTable._mutableModule.DisableNewTokens = false;
}
void CompilationThread(object objThreadId)
{
while (true)
{
_compilationThreadSemaphore.Wait();
lock (this)
{
if (_doneAllCompiling)
return;
}
CompileOnThread((int)objThreadId);
}
}
void CompileOnThread(int compilationThreadId)
{
var compilationMethodList = _currentCompilationMethodList;
while (true)
{
DependencyNodeCore<NodeFactory> dependency;
lock (compilationMethodList)
{
if (!compilationMethodList.MoveNext())
{
if (Interlocked.Increment(ref _finishedThreadCount) == _parallelism)
_compilationSessionComplete.Set();
return;
}
dependency = compilationMethodList.Current;
}
CompileOneMethod(dependency, compilationThreadId);
}
}
void CompileOneMethod(DependencyNodeCore<NodeFactory> dependency, int compileThreadId)
{
MethodWithGCInfo methodCodeNodeNeedingCode = dependency as MethodWithGCInfo;
if (methodCodeNodeNeedingCode == null)
{
if (dependency is DeferredTillPhaseNode deferredPhaseNode)
{
if (Logger.IsVerbose)
_logger.Writer.WriteLine($"Moved to phase {_nodeFactory.CompilationCurrentPhase}");
deferredPhaseNode.NotifyCurrentPhase(_nodeFactory.CompilationCurrentPhase);
return;
}
}
Debug.Assert((_nodeFactory.CompilationCurrentPhase == 0) || ((_nodeFactory.CompilationCurrentPhase == 2) && !_finishedFirstCompilationRunInPhase2));
MethodDesc method = methodCodeNodeNeedingCode.Method;
if (Logger.IsVerbose)
{
string methodName = method.ToString();
Logger.Writer.WriteLine("Compiling " + methodName);
}
if (_nodeFactory.OptimizationFlags.PrintReproArgs)
{
Logger.Writer.WriteLine($"Single method repro args:{GetReproInstructions(method)}");
}
try
{
using (PerfEventSource.StartStopEvents.JitMethodEvents())
{
s_methodsCompiledPerThread++;
bool createNewCorInfoImpl = false;
if (_corInfoImpls[compileThreadId] == null)
createNewCorInfoImpl = true;
else
{
if (_parallelism == 1)
{
// Create only 1 CorInfoImpl if not using parallelism
// This allows SuperPMI to rely on non-reuse of handles in ObjectToHandle
}
else
{
// Periodically create a new CorInfoImpl to clear out stale caches
// This is done as the CorInfoImpl holds a cache of data structures visible to the JIT
// Those data structures include both structures which will last for the lifetime of the compilation
// process, as well as various temporary structures that would really be better off with thread lifetime.
if ((s_methodsCompiledPerThread % 3000) == 0)
{
createNewCorInfoImpl = true;
}
}
}
if (createNewCorInfoImpl)
_corInfoImpls[compileThreadId] = new CorInfoImpl(this);
CorInfoImpl corInfoImpl = _corInfoImpls[compileThreadId];
corInfoImpl.CompileMethod(methodCodeNodeNeedingCode, Logger);
if (corInfoImpl.HasColdCode)
{
generatedColdCode = true;
}
}
}
catch (TypeSystemException ex)
{
// If compilation fails, don't emit code for this method. It will be Jitted at runtime
if (Logger.IsVerbose)
Logger.Writer.WriteLine($"Warning: Method `{method}` was not compiled because: {ex.Message}");
}
catch (RequiresRuntimeJitException ex)
{
if (Logger.IsVerbose)
Logger.Writer.WriteLine($"Info: Method `{method}` was not compiled because `{ex.Message}` requires runtime JIT");
}
catch (CodeGenerationFailedException ex) when (_resilient)
{
if (Logger.IsVerbose)
Logger.Writer.WriteLine($"Warning: Method `{method}` was not compiled because `{ex.Message}` requires runtime JIT");
}
}
}
private void EnsureInstantiationReferencesArePresentForExternalMethod(MethodDesc method)
{
// Validate that the typedef tokens for all of the instantiation parameters of the method
// have tokens.
var moduleForNewReferences = ((EcmaMethod)method.GetPrimaryMethodDesc().GetTypicalMethodDefinition()).Module;
foreach (var type in method.Instantiation)
_tokenManager.EnsureDefTokensAreAvailable(type, moduleForNewReferences, false);
foreach (var type in method.OwningType.Instantiation)
_tokenManager.EnsureDefTokensAreAvailable(type, moduleForNewReferences, false);
}
private void AddNecessaryAsyncReferences(MethodDesc method)
{
if (_hasAddedAsyncReferences)
return;
// Keep in sync with CorInfoImpl.getAsyncInfo()
DefType continuation = TypeSystemContext.ContinuationType;
TypeDesc asyncHelpers = TypeSystemContext.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "AsyncHelpers"u8);
TypeDesc[] requiredTypes = [asyncHelpers, continuation];
FieldDesc[] requiredFields =
[
// For CorInfoImpl.getAsyncInfo
continuation.GetKnownField("Next"u8),
continuation.GetKnownField("ResumeInfo"u8),
continuation.GetKnownField("State"u8),
continuation.GetKnownField("Flags"u8),
];
MethodDesc[] requiredMethods =
[
// For CorInfoImpl.getAsyncInfo
asyncHelpers.GetKnownMethod("CaptureExecutionContext"u8, null),
asyncHelpers.GetKnownMethod("CaptureContinuationContext"u8, null),
asyncHelpers.GetKnownMethod("CaptureContexts"u8, null),
asyncHelpers.GetKnownMethod("RestoreContexts"u8, null),
asyncHelpers.GetKnownMethod("RestoreContextsOnSuspension"u8, null),
asyncHelpers.GetKnownMethod("FinishSuspensionNoContinuationContext"u8, null),
asyncHelpers.GetKnownMethod("FinishSuspensionWithContinuationContext"u8, null),
// R2R Helpers
asyncHelpers.GetKnownMethod("AllocContinuation"u8, null),
asyncHelpers.GetKnownMethod("AllocContinuationClass"u8, null),
asyncHelpers.GetKnownMethod("AllocContinuationMethod"u8, null),
];
var moduleForNewReferences = ((EcmaMethod)method.GetPrimaryMethodDesc().GetTypicalMethodDefinition()).Module;
_tokenManager.EnsureDefTokensAreAvailable([..requiredMethods, ..requiredTypes, ..requiredFields], moduleForNewReferences, true);
_hasAddedAsyncReferences = true;
}
public ISymbolNode GetFieldRvaData(FieldDesc field)
{
if (!CompilationModuleGroup.ContainsType(field.OwningType.GetTypeDefinition()))
{
// TODO: cross-bubble RVA field
throw new RequiresRuntimeJitException($"GetFieldRvaData({field})");
}
return NodeFactory.CopiedFieldRva(field);
}
public override void Dispose()
{
// Workaround for https://github.com/dotnet/runtime/issues/23103.
// ManifestMetadataTable.Dispose() allows to break circular reference
// ConcurrentBag<EcmaModule> -> EcmaModule -> EcmaAssembly -> ReadyToRunCompilerContext -> ... -> ConcurrentBag<EcmaModule>.
// This circular reference along with #23103 prevents objects from being collected by GC.
_nodeFactory.ManifestMetadataTable.Dispose();
}
public string GetReproInstructions(MethodDesc method)
{
return _printReproInstructions(method);
}
}
}
|