未验证 提交 ee5246dd 编写于 作者: D David Wrighton 提交者: GitHub

Add infinite codegen stress feature to crossgen2 and fix memory usage issues...

Add infinite codegen stress feature to crossgen2 and fix memory usage issues found while doing so (#74956)

- Add Inifinite codegen stress mode which runs the compilation portion of crossgen2 in an infinite loop
- Fix a couple of memory leaks around PInvokeTargetNativeMethod and UnboxingMethodDesc
  - These were being stored into long-lived data structures, and I was able to make at least UnboxingMethodDesc have a feature which prevents them from being stored for a long period of time
- Address a number of scenarios where we allocate tremendous amounts of useless garbage
  - Our pattern for using ConcurrentDictionary was allocating a delegate on every lookup
  - Tweak ModuleTokenResolver to not need to allocate new copies of the `TokenResolverProvider` and the various `ImmutableArray` objects in the signature decoders as we don't actually need the return value from those apis.
 - Tweak parallelization strategy to use raw threads instead of `Parallel.ForEach`
   - This allows us to easily control thread lifetime, as well as providing a means to eliminate old and no-longer used `CorInfoImpl` instances.
   - This should address to some extent high levels of memory needed for compilation seen by some customers

# Inifite codegen stress mode 
Enable by passing --codegenopt:InfiniteCompileStress=1 on the command line

A simple way to use this would be to run stress it on system.private.corelib.dll.

Assuming that a release clr+libs has been built and your enlistment is in `c:\gitdir\runtime`, run something like the following
```
pushd c:\gitdir\runtime
src\tests\build generatelayoutonly
set CORE_ROOT=C:\gitdir\runtime\artifacts\tests\coreclr\windows.x64.Release\tests\core_root
SET DOTNET_gcServer=1
SET DOTNET_GCHeapCount=6
%CORE_ROOT%\corerun C:\gitdir\runtime\artifacts\bin\coreclr\windows.x64.Release\crossgen2\crossgen2.dll -o:C:\temp\System.Private.CoreLib.dll -r:C:\gitdir\runtime\artifacts\bin\coreclr\windows.x64.Release\IL\*.dll --targetarch:x64 -m:C:\gitdir\runtime\artifacts\bin\coreclr\windows.x64.Release\StandardOptimizationData.mibc --embed-pgo-data -O C:\gitdir\runtime\artifacts\bin\coreclr\windows.x64.Release\IL\System.Private.CoreLib.dll   --pdb --pdb-path:C:\temp --codegenopt:InfiniteCompileStress=1

```

That will run for an infinite period of time, and stress the GC and various other aspects of the runtime.
上级 55060ebf
......@@ -221,7 +221,9 @@ public static IEnumerable<PgoSchemaElem> ConvertTypeHandleHistogramsToCompactTyp
Dictionary<object, IntPtr> objectToHandle = new Dictionary<object, IntPtr>();
Dictionary<IntPtr, object> handleToObject = new Dictionary<IntPtr, object>();
ComputeJitPgoInstrumentationSchema(LocalObjectToHandle, pgoData, out var nativeSchema, out var instrumentationData);
MemoryStream memoryStreamInstrumentationData = new MemoryStream();
ComputeJitPgoInstrumentationSchema(LocalObjectToHandle, pgoData, out var nativeSchema, memoryStreamInstrumentationData);
var instrumentationData = memoryStreamInstrumentationData.ToArray();
for (int i = 0; i < pgoData.Length; i++)
{
......@@ -653,7 +655,7 @@ private void CompileMethodCleanup()
_pgoResults.Clear();
}
private Dictionary<object, IntPtr> _objectToHandle = new Dictionary<object, IntPtr>();
private Dictionary<object, IntPtr> _objectToHandle = new Dictionary<object, IntPtr>(new JitObjectComparer());
private List<object> _handleToObject = new List<object>();
private const int handleMultiplier = 8;
......@@ -989,7 +991,24 @@ private uint getMethodAttribsInternal(MethodDesc method)
if (method.IsIntrinsic)
result |= CorInfoFlag.CORINFO_FLG_INTRINSIC;
if (method.IsVirtual)
{
result |= CorInfoFlag.CORINFO_FLG_VIRTUAL;
// The JIT only cares about the sealed flag if the method is virtual, or if
// it is a delegate.
// method or class might have the final bit
if (method.IsUnboxingThunk())
{
if (_compilation.IsEffectivelySealed(method.GetUnboxedMethod()))
result |= CorInfoFlag.CORINFO_FLG_FINAL;
}
else
{
if (_compilation.IsEffectivelySealed(method))
result |= CorInfoFlag.CORINFO_FLG_FINAL;
}
}
if (method.IsAbstract)
result |= CorInfoFlag.CORINFO_FLG_ABSTRACT;
if (method.IsConstructor || method.IsStaticConstructor)
......@@ -1000,10 +1019,6 @@ private uint getMethodAttribsInternal(MethodDesc method)
// method body.
//
// method or class might have the final bit
if (_compilation.IsEffectivelySealed(method))
result |= CorInfoFlag.CORINFO_FLG_FINAL;
if (method.IsSharedByGenericInstantiations)
result |= CorInfoFlag.CORINFO_FLG_SHAREDINST;
......@@ -2237,18 +2252,26 @@ private uint getClassGClayout(CORINFO_CLASS_STRUCT_* cls, byte* gcPtrs)
return result;
}
private Dictionary<TypeDesc, uint> _classNumInstanceFields = new();
private uint getClassNumInstanceFields(CORINFO_CLASS_STRUCT_* cls)
{
TypeDesc type = HandleToObject(cls);
uint result = 0;
var lookupType = type.GetTypeDefinition(); // The number of fields on an instantiation is the same as on the generic definition
if (_classNumInstanceFields.TryGetValue(lookupType, out uint numInstanceFields))
return numInstanceFields;
numInstanceFields = 0;
foreach (var field in type.GetFields())
{
if (!field.IsStatic)
result++;
numInstanceFields++;
}
return result;
_classNumInstanceFields.Add(lookupType, numInstanceFields);
return numInstanceFields;
}
private CORINFO_FIELD_STRUCT_* getFieldInClass(CORINFO_CLASS_STRUCT_* clsHnd, int num)
......@@ -3799,11 +3822,13 @@ private PgoSchemaElem[] getPgoInstrumentationResults(MethodDesc method)
return _compilation.ProfileData[method]?.SchemaData;
}
public static void ComputeJitPgoInstrumentationSchema(Func<object, IntPtr> objectToHandle, PgoSchemaElem[] pgoResultsSchemas, out PgoInstrumentationSchema[] nativeSchemas, out byte[] instrumentationData, Func<TypeDesc, bool> typeFilter = null)
private MemoryStream _cachedMemoryStream = new MemoryStream();
public static void ComputeJitPgoInstrumentationSchema(Func<object, IntPtr> objectToHandle, PgoSchemaElem[] pgoResultsSchemas, out PgoInstrumentationSchema[] nativeSchemas, MemoryStream instrumentationData, Func<TypeDesc, bool> typeFilter = null)
{
nativeSchemas = new PgoInstrumentationSchema[pgoResultsSchemas.Length];
MemoryStream msInstrumentationData = new MemoryStream();
BinaryWriter bwInstrumentationData = new BinaryWriter(msInstrumentationData);
instrumentationData.SetLength(0);
BinaryWriter bwInstrumentationData = new BinaryWriter(instrumentationData);
for (int i = 0; i < nativeSchemas.Length; i++)
{
if ((bwInstrumentationData.BaseStream.Position % 8) == 4)
......@@ -3865,8 +3890,6 @@ public static void ComputeJitPgoInstrumentationSchema(Func<object, IntPtr> objec
}
bwInstrumentationData.Flush();
instrumentationData = msInstrumentationData.ToArray();
}
private HRESULT getPgoInstrumentationResults(CORINFO_METHOD_STRUCT_* ftnHnd, ref PgoInstrumentationSchema* pSchema, ref uint countSchemaItems, byte** pInstrumentationData,
......@@ -3884,13 +3907,14 @@ public static void ComputeJitPgoInstrumentationSchema(Func<object, IntPtr> objec
else
{
#pragma warning disable SA1001, SA1113, SA1115 // Commas should be spaced correctly
ComputeJitPgoInstrumentationSchema(ObjectToHandle, pgoResultsSchemas, out var nativeSchemas, out var instrumentationData
ComputeJitPgoInstrumentationSchema(ObjectToHandle, pgoResultsSchemas, out var nativeSchemas, _cachedMemoryStream
#if !READYTORUN
, _compilation.CanConstructType
#endif
);
#pragma warning restore SA1001, SA1113, SA1115 // Commas should be spaced correctly
var instrumentationData = _cachedMemoryStream.ToArray();
pgoResults.pInstrumentationData = (byte*)GetPin(instrumentationData);
pgoResults.countSchemaItems = (uint)nativeSchemas.Length;
pgoResults.pSchema = (PgoInstrumentationSchema*)GetPin(nativeSchemas);
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using Internal.TypeSystem;
namespace Internal.JitInterface
{
public class JitObjectComparer : IEqualityComparer<object>
{
public new bool Equals(object x, object y) => x == y;
public int GetHashCode(object obj)
{
if (obj is IJitHashableOnly jitHashable)
return jitHashable.GetJitVisibleHashCode();
return obj.GetHashCode();
}
}
public class JitObjectComparer<T> : IEqualityComparer<T> where T:class
{
public bool Equals(T x, T y) => x == y;
public int GetHashCode(T obj)
{
if (obj is IJitHashableOnly jitHashable)
return jitHashable.GetJitVisibleHashCode();
return obj.GetHashCode();
}
}
// Mark a type system object with this interface to indicate that it
// can be hashed, but only by using the IJitHashableOnly interface.
// Implementors of this should throw an exception in their implementation of
// ComputeHashCode so that the normal GetHashCode function does not work.
// This is used to prevent putting these objects into long lived storage.
//
// The goal here is to make it difficult to accidentally store a type into
// another hashtable that isn't associated with the JIT itself, but still
// allow the jit side code use standard collections.
public interface IJitHashableOnly
{
int GetJitVisibleHashCode();
}
}
......@@ -12,9 +12,10 @@ namespace Internal.JitInterface
/// This class is for internal purposes within the JitInterface. It's not expected
/// for it to escape the JitInterface.
/// </summary>
internal sealed class UnboxingMethodDesc : MethodDelegator
internal sealed class UnboxingMethodDesc : MethodDelegator, IJitHashableOnly
{
private readonly UnboxingMethodDescFactory _factory;
private readonly int _jitVisibleHashCode;
public MethodDesc Target => _wrappedMethod;
......@@ -24,6 +25,7 @@ public UnboxingMethodDesc(MethodDesc wrappedMethod, UnboxingMethodDescFactory fa
Debug.Assert(wrappedMethod.OwningType.IsValueType);
Debug.Assert(!wrappedMethod.Signature.IsStatic);
_factory = factory;
_jitVisibleHashCode = HashCode.Combine(wrappedMethod.GetHashCode(), 401752602);
}
public override MethodDesc GetCanonMethodTarget(CanonicalFormKind kind)
......@@ -74,6 +76,13 @@ protected override int CompareToImpl(MethodDesc other, TypeSystemComparer compar
{
throw new NotImplementedException();
}
protected override int ComputeHashCode()
{
throw new NotSupportedException("This method may not be stored as it is expected to only be used transiently in the JIT");
}
int IJitHashableOnly.GetJitVisibleHashCode() => _jitVisibleHashCode;
#endif
}
......
......@@ -368,5 +368,16 @@ public override IEnumerable<MetadataType> GetNestedTypes()
// Return the result from the typical type definition.
return _typeDef.GetNestedTypes();
}
public override TypeDesc UnderlyingType
{
get
{
if (!IsEnum)
return this;
else
return _typeDef.UnderlyingType;
}
}
}
}
......@@ -472,7 +472,6 @@ public virtual TypeDesc UnderlyingType
if (!this.IsEnum)
return this;
// TODO: Cache the result?
foreach (var field in this.GetFields())
{
if (!field.IsStatic)
......
......@@ -429,6 +429,24 @@ public override IEnumerable<FieldDesc> GetFields()
}
}
public override TypeDesc UnderlyingType
{
get
{
if (!IsEnum)
return this;
foreach (var handle in _typeDefinition.GetFields())
{
var field = (EcmaField)_module.GetObject(handle);
if (!field.IsStatic)
return field.FieldType;
}
return base.UnderlyingType; // Use the base implementation to get consistent error behavior
}
}
public override FieldDesc GetField(string name)
{
var metadataReader = this.MetadataReader;
......
......@@ -130,22 +130,108 @@ public void AddModuleTokenForMethod(MethodDesc method, ModuleToken token)
if (token.TokenType == CorTokenType.mdtMethodSpec)
{
MethodSpecification methodSpec = token.MetadataReader.GetMethodSpecification((MethodSpecificationHandle)token.Handle);
methodSpec.DecodeSignature<DummyTypeInfo, ModuleTokenResolver>(new TokenResolverProvider(this, token.Module), this);
DecodeMethodSpecificationSignatureToDiscoverUsedTypeTokens(methodSpec.Signature, token);
token = new ModuleToken(token.Module, methodSpec.Method);
}
if (token.TokenType == CorTokenType.mdtMemberRef)
{
MemberReference memberRef = token.MetadataReader.GetMemberReference((MemberReferenceHandle)token.Handle);
EntityHandle owningTypeHandle = memberRef.Parent;
TypeDesc owningType = (TypeDesc)token.Module.GetObject(owningTypeHandle, NotFoundBehavior.Throw);
AddModuleTokenForType(owningType, new ModuleToken(token.Module, owningTypeHandle));
memberRef.DecodeMethodSignature<DummyTypeInfo, ModuleTokenResolver>(new TokenResolverProvider(this, token.Module), this);
DecodeMethodSignatureToDiscoverUsedTypeTokens(memberRef.Signature, token);
}
if (token.TokenType == CorTokenType.mdtMethodDef)
{
MethodDefinition methodDef = token.MetadataReader.GetMethodDefinition((MethodDefinitionHandle)token.Handle);
methodDef.DecodeSignature<DummyTypeInfo, ModuleTokenResolver>(new TokenResolverProvider(this, token.Module), this);
TokenResolverProvider rentedProvider = TokenResolverProvider.Rent(this, token.Module);
DecodeMethodSignatureToDiscoverUsedTypeTokens(methodDef.Signature, token);
}
}
private void DecodeMethodSpecificationSignatureToDiscoverUsedTypeTokens(BlobHandle signatureHandle, ModuleToken token)
{
MetadataReader metadataReader = token.MetadataReader;
TokenResolverProvider rentedProvider = TokenResolverProvider.Rent(this, token.Module);
SignatureDecoder<DummyTypeInfo, ModuleTokenResolver> sigDecoder = new(rentedProvider, metadataReader, this);
BlobReader signature = metadataReader.GetBlobReader(signatureHandle);
SignatureHeader header = signature.ReadSignatureHeader();
SignatureKind kind = header.Kind;
if (kind != SignatureKind.MethodSpecification)
{
throw new BadImageFormatException();
}
int count = signature.ReadCompressedInteger();
for (int i = 0; i < count; i++)
{
sigDecoder.DecodeType(ref signature);
}
TokenResolverProvider.ReturnRental(rentedProvider);
}
private void DecodeMethodSignatureToDiscoverUsedTypeTokens(BlobHandle signatureHandle, ModuleToken token)
{
MetadataReader metadataReader = token.MetadataReader;
TokenResolverProvider rentedProvider = TokenResolverProvider.Rent(this, token.Module);
SignatureDecoder<DummyTypeInfo, ModuleTokenResolver> sigDecoder = new (rentedProvider, metadataReader, this);
BlobReader signature = metadataReader.GetBlobReader(signatureHandle);
SignatureHeader header = signature.ReadSignatureHeader();
SignatureKind kind = header.Kind;
if (kind != SignatureKind.Method && kind != SignatureKind.Property)
{
throw new BadImageFormatException();
}
int genericParameterCount = 0;
if (header.IsGeneric)
{
genericParameterCount = signature.ReadCompressedInteger();
}
int parameterCount = signature.ReadCompressedInteger();
sigDecoder.DecodeType(ref signature);
if (parameterCount != 0)
{
int parameterIndex;
for (parameterIndex = 0; parameterIndex < parameterCount; parameterIndex++)
{
BlobReader sentinelReader = signature;
int typeCode = sentinelReader.ReadCompressedInteger();
if (typeCode == (int)SignatureTypeCode.Sentinel)
{
signature = sentinelReader;
}
sigDecoder.DecodeType(ref signature, allowTypeSpecifications: false);
}
}
TokenResolverProvider.ReturnRental(rentedProvider);
}
private void DecodeFieldSignatureToDiscoverUsedTypeTokens(BlobHandle signatureHandle, ModuleToken token)
{
MetadataReader metadataReader = token.MetadataReader;
TokenResolverProvider rentedProvider = TokenResolverProvider.Rent(this, token.Module);
SignatureDecoder<DummyTypeInfo, ModuleTokenResolver> sigDecoder = new(rentedProvider, metadataReader, this);
BlobReader signature = metadataReader.GetBlobReader(signatureHandle);
SignatureHeader header = signature.ReadSignatureHeader();
SignatureKind kind = header.Kind;
if (kind != SignatureKind.Field)
{
throw new BadImageFormatException();
}
sigDecoder.DecodeType(ref signature);
TokenResolverProvider.ReturnRental(rentedProvider);
}
private void AddModuleTokenForFieldReference(TypeDesc owningType, ModuleToken token)
......@@ -153,7 +239,7 @@ private void AddModuleTokenForFieldReference(TypeDesc owningType, ModuleToken to
MemberReference memberRef = token.MetadataReader.GetMemberReference((MemberReferenceHandle)token.Handle);
EntityHandle owningTypeHandle = memberRef.Parent;
AddModuleTokenForType(owningType, new ModuleToken(token.Module, owningTypeHandle));
memberRef.DecodeFieldSignature<DummyTypeInfo, ModuleTokenResolver>(new TokenResolverProvider(this, token.Module), this);
DecodeFieldSignatureToDiscoverUsedTypeTokens(memberRef.Signature, token);
}
// Add TypeSystemEntity -> ModuleToken mapping to a ConcurrentDictionary. Using CompareTo sort the token used, so it will
......@@ -182,7 +268,9 @@ public void AddModuleTokenForType(TypeDesc type, ModuleToken token)
if (token.TokenType == CorTokenType.mdtTypeSpec)
{
TypeSpecification typeSpec = token.MetadataReader.GetTypeSpecification((TypeSpecificationHandle)token.Handle);
typeSpec.DecodeSignature(new TokenResolverProvider(this, token.Module), this);
TokenResolverProvider rentedProvider = TokenResolverProvider.Rent(this, token.Module);
typeSpec.DecodeSignature(rentedProvider, this);
TokenResolverProvider.ReturnRental(rentedProvider);
specialTypeFound = true;
}
......@@ -229,12 +317,35 @@ private class TokenResolverProvider : ISignatureTypeProvider<DummyTypeInfo, Modu
IEcmaModule _contextModule;
[ThreadStatic]
private static TokenResolverProvider _rentalObject;
public TokenResolverProvider(ModuleTokenResolver resolver, IEcmaModule contextModule)
{
_resolver = resolver;
_contextModule = contextModule;
}
public static TokenResolverProvider Rent(ModuleTokenResolver resolver, IEcmaModule contextModule)
{
if (_rentalObject != null)
{
TokenResolverProvider result = _rentalObject;
_rentalObject = null;
result._resolver = resolver;
result._contextModule = contextModule;
return result;
}
return new TokenResolverProvider(resolver, contextModule);
}
public static void ReturnRental(TokenResolverProvider rental)
{
rental._resolver = null;
rental._contextModule = null;
_rentalObject = rental;
}
public DummyTypeInfo GetArrayType(DummyTypeInfo elementType, ArrayShape shape)
{
return DummyTypeInfo.Instance;
......
......@@ -227,12 +227,6 @@ public interface ICompilation
public sealed class ReadyToRunCodegenCompilation : Compilation
{
/// <summary>
/// We only need one CorInfoImpl per thread, and we don't want to unnecessarily construct them
/// because their construction takes a significant amount of time.
/// </summary>
private readonly ConditionalWeakTable<Thread, CorInfoImpl> _corInfoImpls;
/// <summary>
/// Input MSIL file names.
/// </summary>
......@@ -243,6 +237,7 @@ public sealed class ReadyToRunCodegenCompilation : Compilation
private readonly bool _resilient;
private readonly int _parallelism;
private readonly CorInfoImpl[] _corInfoImpls;
private readonly bool _generateMapFile;
private readonly bool _generateMapCsvFile;
......@@ -269,6 +264,7 @@ public sealed class ReadyToRunCodegenCompilation : Compilation
/// 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;
internal ReadyToRunCodegenCompilation(
DependencyAnalyzerBase<NodeFactory> dependencyGraph,
......@@ -306,8 +302,10 @@ public sealed class ReadyToRunCodegenCompilation : Compilation
logger,
instructionSetSupport)
{
_computedFixedLayoutTypesUncached = IsLayoutFixedInCurrentVersionBubbleInternal;
_resilient = resilient;
_parallelism = parallelism;
_corInfoImpls = new CorInfoImpl[_parallelism];
_generateMapFile = generateMapFile;
_generateMapCsvFile = generateMapCsvFile;
_generatePdbFile = generatePdbFile;
......@@ -322,7 +320,6 @@ public sealed class ReadyToRunCodegenCompilation : Compilation
nodeFactory.InstrumentationDataTable.Initialize(SymbolNodeFactory);
if (nodeFactory.CrossModuleInlningInfo != null)
nodeFactory.CrossModuleInlningInfo.Initialize(SymbolNodeFactory);
_corInfoImpls = new ConditionalWeakTable<Thread, CorInfoImpl>();
_inputFiles = inputFiles;
_compositeRootPath = compositeRootPath;
_printReproInstructions = printReproInstructions;
......@@ -344,6 +341,11 @@ public sealed class ReadyToRunCodegenCompilation : Compilation
public override void Compile(string outputFile)
{
_dependencyGraph.ComputeMarkedNodes();
_doneAllCompiling = true;
Array.Clear(_corInfoImpls);
_compilationThreadSemaphore.Release(_parallelism);
var nodes = _dependencyGraph.MarkedNodeList;
nodes = _fileLayoutOptimizer.ApplyProfilerGuidedMethodSort(nodes);
......@@ -507,7 +509,7 @@ private bool IsLayoutFixedInCurrentVersionBubbleInternal(TypeDesc type)
}
public bool IsLayoutFixedInCurrentVersionBubble(TypeDesc type) =>
_computedFixedLayoutTypes.GetOrAdd(type, (t) => IsLayoutFixedInCurrentVersionBubbleInternal(t));
_computedFixedLayoutTypes.GetOrAdd(type, _computedFixedLayoutTypesUncached);
public bool IsInheritanceChainLayoutFixedInCurrentVersionBubble(TypeDesc type)
{
......@@ -556,11 +558,22 @@ public void PrepareForCompilationRetry(MethodWithGCInfo methodToBeRecompiled, IE
lock (_methodsToRecompile)
{
_methodsToRecompile.Add(methodToBeRecompiled);
foreach (var method in methodsThatNeedILBodies)
_methodsWhichNeedMutableILBodies.Add(method);
if (methodsThatNeedILBodies != null)
foreach (var method in methodsThatNeedILBodies)
_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;
protected override void ComputeDependencyNodeDependencies(List<DependencyNodeCore<NodeFactory>> obj)
{
......@@ -695,23 +708,69 @@ void CompileMethodList(IEnumerable<DependencyNodeCore<NodeFactory>> methodList)
if (_parallelism == 1)
{
foreach (var dependency in methodList)
CompileOneMethod(dependency);
CompileOneMethod(dependency, 0);
}
else
{
ParallelOptions options = new ParallelOptions
_currentCompilationMethodList = methodList.GetEnumerator();
_finishedThreadCount = 0;
_compilationSessionComplete.Reset();
if (!_hasCreatedCompilationThreads)
{
MaxDegreeOfParallelism = _parallelism
};
for (int compilationThreadId = 1; compilationThreadId < _parallelism; compilationThreadId++)
{
new Thread(CompilationThread).Start((object)compilationThreadId);
}
_hasCreatedCompilationThreads = true;
}
Parallel.ForEach(methodList, options, CompileOneMethod);
_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 CompileOneMethod(DependencyNodeCore<NodeFactory> dependency)
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)
......@@ -744,9 +803,35 @@ void CompileOneMethod(DependencyNodeCore<NodeFactory> dependency)
{
using (PerfEventSource.StartStopEvents.JitMethodEvents())
{
// Create only 1 CorInfoImpl per thread.
// This allows SuperPMI to rely on non-reuse of handles in ObjectToHandle
CorInfoImpl corInfoImpl = _corInfoImpls.GetValue(Thread.CurrentThread, thread => new CorInfoImpl(this));
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);
}
}
......@@ -782,7 +867,7 @@ public ISymbolNode GetFieldRvaData(FieldDesc field)
public override void Dispose()
{
_corInfoImpls?.Clear();
Array.Clear(_corInfoImpls);
}
}
}
......@@ -44,17 +44,25 @@ public abstract class ReadyToRunCompilationModuleGroupBase : CompilationModuleGr
private readonly bool _crossModuleInlining;
private readonly bool _crossModuleGenericCompilation;
private readonly ConcurrentDictionary<TypeDesc, CompilationUnitSet> _layoutCompilationUnits = new ConcurrentDictionary<TypeDesc, CompilationUnitSet>();
private readonly Func<TypeDesc, CompilationUnitSet> _layoutCompilationUnitsUncached;
private readonly ConcurrentDictionary<TypeDesc, bool> _versionsWithTypeCache = new ConcurrentDictionary<TypeDesc, bool>();
private readonly Func<TypeDesc, bool> _versionsWithTypeUncached;
private readonly ConcurrentDictionary<TypeDesc, bool> _versionsWithTypeReferenceCache = new ConcurrentDictionary<TypeDesc, bool>();
private readonly Func<TypeDesc, bool> _versionsWithTypeReferenceUncached;
private readonly ConcurrentDictionary<MethodDesc, bool> _versionsWithMethodCache = new ConcurrentDictionary<MethodDesc, bool>();
private readonly Func<MethodDesc, bool> _versionsWithMethodUncached;
private readonly ConcurrentDictionary<MethodDesc, bool> _crossModuleInlineableCache = new ConcurrentDictionary<MethodDesc, bool>();
private readonly Func<MethodDesc, bool> _crossModuleInlineableCacheUncached;
private readonly ConcurrentDictionary<TypeDesc, bool> _crossModuleInlineableTypeCache = new ConcurrentDictionary<TypeDesc, bool>();
private readonly Func<TypeDesc, bool> _crossModuleInlineableTypeCacheUncached;
private readonly ConcurrentDictionary<MethodDesc, bool> _crossModuleCompilableCache = new ConcurrentDictionary<MethodDesc, bool>();
private readonly Func<MethodDesc, bool> _crossModuleCompilableCacheUncached;
private readonly Dictionary<ModuleDesc, CompilationUnitIndex> _moduleCompilationUnits = new Dictionary<ModuleDesc, CompilationUnitIndex>();
private ProfileDataManager _profileData;
private CompilationUnitIndex _nextCompilationUnit = CompilationUnitIndex.FirstDynamicallyAssigned;
private ModuleTokenResolver _tokenResolver = null;
private ConcurrentDictionary<EcmaMethod, bool> _tokenTranslationFreeNonVersionable = new ConcurrentDictionary<EcmaMethod, bool>();
private readonly Func<EcmaMethod, bool> _tokenTranslationFreeNonVersionableUncached;
private bool CompileAllPossibleCrossModuleCode = false;
public ReadyToRunCompilationModuleGroupBase(ReadyToRunCompilationModuleGroupConfig config)
......@@ -77,6 +85,15 @@ public ReadyToRunCompilationModuleGroupBase(ReadyToRunCompilationModuleGroupConf
_compileGenericDependenciesFromVersionBubbleModuleSet = config.CompileGenericDependenciesFromVersionBubbleModuleSet;
_tokenResolver = new ModuleTokenResolver(this, config.Context);
_layoutCompilationUnitsUncached = TypeLayoutCompilationUnitsUncached;
_versionsWithTypeUncached = ComputeTypeVersionsWithCode;
_versionsWithTypeReferenceUncached = ComputeTypeReferenceVersionsWithCode;
_versionsWithMethodUncached = VersionsWithMethodUncached;
_crossModuleInlineableCacheUncached = CrossModuleInlineableUncached;
_crossModuleInlineableTypeCacheUncached = ComputeCrossModuleInlineableType;
_crossModuleCompilableCacheUncached = CrossModuleCompileableUncached;
_tokenTranslationFreeNonVersionableUncached = IsNonVersionableWithILTokensThatDoNotNeedTranslationUncached;
}
public ModuleTokenResolver Resolver => _tokenResolver;
......@@ -113,7 +130,7 @@ protected bool CompileVersionBubbleGenericsIntoCurrentModule(MethodDesc method)
public CompilationUnitSet TypeLayoutCompilationUnits(TypeDesc type)
{
return _layoutCompilationUnits.GetOrAdd(type, TypeLayoutCompilationUnitsUncached);
return _layoutCompilationUnits.GetOrAdd(type, _layoutCompilationUnitsUncached);
}
public override ReadyToRunFlags GetReadyToRunFlags()
......@@ -343,7 +360,7 @@ public sealed override bool VersionsWithModule(ModuleDesc module)
public sealed override bool VersionsWithType(TypeDesc typeDesc)
{
return typeDesc.GetTypeDefinition() is EcmaType ecmaType &&
_versionsWithTypeCache.GetOrAdd(typeDesc, ComputeTypeVersionsWithCode);
_versionsWithTypeCache.GetOrAdd(typeDesc, _versionsWithTypeUncached);
}
public bool CrossModuleInlineableModule(ModuleDesc module)
......@@ -354,18 +371,18 @@ public bool CrossModuleInlineableModule(ModuleDesc module)
public bool CrossModuleInlineableType(TypeDesc typeDesc)
{
return typeDesc.GetTypeDefinition() is EcmaType ecmaType &&
_crossModuleInlineableTypeCache.GetOrAdd(typeDesc, ComputeCrossModuleInlineableType);
_crossModuleInlineableTypeCache.GetOrAdd(typeDesc, _crossModuleInlineableTypeCacheUncached);
}
public sealed override bool VersionsWithTypeReference(TypeDesc typeDesc)
{
return _versionsWithTypeReferenceCache.GetOrAdd(typeDesc, ComputeTypeReferenceVersionsWithCode);
return _versionsWithTypeReferenceCache.GetOrAdd(typeDesc, _versionsWithTypeReferenceUncached);
}
public sealed override bool VersionsWithMethodBody(MethodDesc method)
{
return _versionsWithMethodCache.GetOrAdd(method, VersionsWithMethodUncached);
return _versionsWithMethodCache.GetOrAdd(method, _versionsWithMethodUncached);
}
private bool VersionsWithMethodUncached(MethodDesc method)
......@@ -400,7 +417,7 @@ public bool IsNonVersionableWithILTokensThatDoNotNeedTranslation(MethodDesc meth
if (!method.IsNonVersionable())
return false;
return _tokenTranslationFreeNonVersionable.GetOrAdd((EcmaMethod)method.GetTypicalMethodDefinition(), IsNonVersionableWithILTokensThatDoNotNeedTranslationUncached);
return _tokenTranslationFreeNonVersionable.GetOrAdd((EcmaMethod)method.GetTypicalMethodDefinition(), _tokenTranslationFreeNonVersionableUncached);
}
public override bool CrossModuleCompileable(MethodDesc method)
......@@ -408,7 +425,7 @@ public override bool CrossModuleCompileable(MethodDesc method)
if (!_crossModuleGenericCompilation)
return false;
return _crossModuleCompilableCache.GetOrAdd(method, CrossModuleCompileableUncached);
return _crossModuleCompilableCache.GetOrAdd(method, _crossModuleCompilableCacheUncached);
}
private bool CrossModuleCompileableUncached(MethodDesc method)
......@@ -489,7 +506,7 @@ public override bool CrossModuleInlineable(MethodDesc method)
// Internal predicate so that the switches controlling cross module inlining and compilation are independent
private bool CrossModuleInlineableInternal(MethodDesc method)
{
return _crossModuleInlineableCache.GetOrAdd(method, CrossModuleInlineableUncached);
return _crossModuleInlineableCache.GetOrAdd(method, _crossModuleInlineableCacheUncached);
}
private bool CrossModuleInlineableUncached(MethodDesc method)
......
......@@ -5,6 +5,7 @@
using System.Net;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using ILCompiler;
using Internal.TypeSystem;
......@@ -23,6 +24,7 @@ public struct PInvokeILEmitter
private readonly MethodDesc _targetMethod;
private readonly Marshaller[] _marshallers;
private readonly PInvokeMetadata _importMetadata;
private static readonly ConditionalWeakTable<TypeSystemContext, ConcurrentDictionary<MethodDesc, PInvokeTargetNativeMethod>> s_contexts = new ();
private PInvokeILEmitter(MethodDesc targetMethod)
{
......@@ -50,9 +52,19 @@ private void EmitPInvokeCall(PInvokeILCodeStreams ilCodeStreams)
_targetMethod.Signature.Flags, 0, nativeReturnType,
nativeParameterTypes);
var rawTargetMethod = new PInvokeTargetNativeMethod(_targetMethod, nativeSig);
var rawTargetMethod = AllocateTargetNativeMethod(_targetMethod, nativeSig);
callsiteSetupCodeStream.Emit(ILOpcode.call, emitter.NewToken(rawTargetMethod));
static PInvokeTargetNativeMethod AllocateTargetNativeMethod(MethodDesc targetMethod, MethodSignature nativeSigArg)
{
var contextMethods = s_contexts.GetOrCreateValue(targetMethod.Context);
if (contextMethods.TryGetValue(targetMethod, out var pinvokeTargetMethod))
{
return pinvokeTargetMethod;
}
return contextMethods.GetOrAdd(targetMethod, new PInvokeTargetNativeMethod(targetMethod, nativeSigArg));
}
}
private MethodIL EmitIL()
......
......@@ -280,6 +280,9 @@
<Compile Include="..\..\Common\JitInterface\CorInfoInstructionSet.cs">
<Link>JitInterface\CorInfoInstructionSet.cs</Link>
</Compile>
<Compile Include="..\..\Common\JitInterface\IJitHashableOnly.cs">
<Link>JitInterface\IJitHashableOnly.cs</Link>
</Compile>
<Compile Include="..\..\Common\JitInterface\JitConfigProvider.cs">
<Link>JitInterface\JitConfigProvider.cs</Link>
</Compile>
......
......@@ -27,6 +27,15 @@
namespace Internal.JitInterface
{
class InfiniteCompileStress
{
public static bool Enabled;
static InfiniteCompileStress()
{
Enabled = JitConfigProvider.Instance.GetIntConfigValue("InfiniteCompileStress", 0) != 0;
}
}
internal class RequiresRuntimeJitIfUsedSymbol
{
public RequiresRuntimeJitIfUsedSymbol(string message)
......@@ -441,6 +450,7 @@ unsafe partial class CorInfoImpl
private UnboxingMethodDescFactory _unboxingThunkFactory = new UnboxingMethodDescFactory();
private List<ISymbolNode> _precodeFixups;
private List<EcmaMethod> _ilBodiesNeeded;
private Dictionary<TypeDesc, bool> _preInitedTypes = new Dictionary<TypeDesc, bool>();
public CorInfoImpl(ReadyToRunCodegenCompilation compilation)
: this()
......@@ -543,7 +553,7 @@ private static bool FunctionJustThrows(MethodIL ilBody)
partial void DetermineIfCompilationShouldBeRetried(ref CompilationResult result)
{
// If any il bodies need to be recomputed, force recompilation
if (_ilBodiesNeeded != null)
if ((_ilBodiesNeeded != null) || InfiniteCompileStress.Enabled)
{
_compilation.PrepareForCompilationRetry(_methodCodeNode, _ilBodiesNeeded);
result = CompilationResult.CompilationRetryRequested;
......@@ -1357,57 +1367,64 @@ private CorInfoHelpFunc getNewArrHelper(CORINFO_CLASS_STRUCT_* arrayCls)
return CorInfoHelpFunc.CORINFO_HELP_NEWARR_1_DIRECT;
}
private static bool IsClassPreInited(TypeDesc type)
private bool IsClassPreInited(TypeDesc type)
{
if (type.IsGenericDefinition)
{
return true;
}
if (type.HasStaticConstructor)
{
return false;
}
if (HasBoxedRegularStatics(type))
{
return false;
}
if (IsDynamicStatics(type))
var uninstantiatedType = type.GetTypeDefinition();
if (_preInitedTypes.TryGetValue(uninstantiatedType, out bool preInited))
{
return false;
return preInited;
}
return true;
}
private static bool HasBoxedRegularStatics(TypeDesc type)
{
foreach (FieldDesc field in type.GetFields())
preInited = ComputeIsClassPreInited(type);
_preInitedTypes.Add(uninstantiatedType, preInited);
return preInited;
static bool ComputeIsClassPreInited(TypeDesc type)
{
if (field.IsStatic &&
!field.IsLiteral &&
!field.HasRva &&
!field.IsThreadStatic &&
field.FieldType.IsValueType &&
!field.FieldType.UnderlyingType.IsPrimitive)
if (type.IsGenericDefinition)
{
return true;
}
if (type.HasStaticConstructor)
{
return false;
}
if (IsDynamicStaticsOrHasBoxedRegularStatics(type))
{
return false;
}
return true;
}
return false;
}
private static bool IsDynamicStatics(TypeDesc type)
{
if (type.HasInstantiation)
static bool IsDynamicStaticsOrHasBoxedRegularStatics(TypeDesc type)
{
bool typeHasInstantiation = type.HasInstantiation;
foreach (FieldDesc field in type.GetFields())
{
if (field.IsStatic && !field.IsLiteral)
if (!field.IsStatic || field.IsLiteral)
continue;
if (typeHasInstantiation)
return true; // Dynamic statics
if (!field.HasRva &&
!field.IsThreadStatic &&
field.FieldType.IsValueType &&
!field.FieldType.UnderlyingType.IsPrimitive)
{
return true;
return true; // HasBoxedRegularStatics
}
}
return false;
}
return false;
}
private bool IsGenericTooDeeplyNested(Instantiation instantiation, int nestingLevel)
......@@ -2729,9 +2746,9 @@ private unsafe HRESULT allocPgoInstrumentationBySchema(CORINFO_METHOD_STRUCT_* f
private void getAddressOfPInvokeTarget(CORINFO_METHOD_STRUCT_* method, ref CORINFO_CONST_LOOKUP pLookup)
{
MethodDesc methodDesc = HandleToObject(method);
Debug.Assert(_compilation.CompilationModuleGroup.VersionsWithMethodBody(methodDesc));
if (methodDesc is IL.Stubs.PInvokeTargetNativeMethod rawPInvoke)
methodDesc = rawPInvoke.Target;
Debug.Assert(_compilation.CompilationModuleGroup.VersionsWithMethodBody(methodDesc));
EcmaMethod ecmaMethod = (EcmaMethod)methodDesc;
ModuleToken moduleToken = new ModuleToken(ecmaMethod.Module, ecmaMethod.Handle);
MethodWithToken methodWithToken = new MethodWithToken(ecmaMethod, moduleToken, constrainedType: null, unboxing: false, context: null);
......
......@@ -59,6 +59,9 @@
<Compile Include="..\..\Common\TypeSystem\IL\Stubs\TypeSystemThrowingILEmitter.cs">
<Link>IL\Stubs\TypeSystemThrowingILEmitter.cs</Link>
</Compile>
<Compile Include="..\..\Common\JitInterface\IJitHashableOnly.cs">
<Link>JitInterface\IJitHashableOnly.cs</Link>
</Compile>
<Compile Include="..\..\Common\JitInterface\JitConfigProvider.cs">
<Link>JitInterface\JitConfigProvider.cs</Link>
</Compile>
......
......@@ -93,7 +93,14 @@ public CommandLineOptions(string[] args)
NonLocalGenericsModule = "";
PerfMapFormatVersion = DefaultPerfMapFormatVersion;
Parallelism = Environment.ProcessorCount;
// Limit parallelism to 24 wide at most by default, more parallelism is unlikely to improve compilation speed
// as many portions of the process are single threaded, and is known to use excessive memory.
Parallelism = Math.Min(24, Environment.ProcessorCount);
// On 32bit platforms restrict it more, as virtual address space is quite limited
if (!Environment.Is64BitProcess)
Parallelism = Math.Min(4, Parallelism);
SingleMethodGenericArg = null;
// These behaviors default to enabled
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册