Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow IL scanner's dependency graph be GC'd #72430

Merged
merged 1 commit into from
Jul 19, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 54 additions & 38 deletions src/coreclr/tools/aot/ILCompiler/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;

Expand Down Expand Up @@ -827,8 +828,20 @@ static string ILLinkify(string rootedAssembly)
.UseILProvider(ilProvider)
.UsePreinitializationManager(preinitManager);

ILScanResults scanResults = null;
#if DEBUG
List<TypeDesc> scannerConstructedTypes = null;
List<MethodDesc> scannerCompiledMethods = null;
#endif

if (useScanner)
{
// Run the scanner in a separate stack frame so that there's no dangling references to
// it once we're done with it and it can be garbage collected.
RunScanner();
}

[MethodImpl(MethodImplOptions.NoInlining)]
void RunScanner()
{
ILScannerBuilder scannerBuilder = builder.GetILScannerBuilder()
.UseCompilationRoots(compilationRoots)
Expand All @@ -842,11 +855,43 @@ static string ILLinkify(string rootedAssembly)

IILScanner scanner = scannerBuilder.ToILScanner();

scanResults = scanner.Scan();
ILScanResults scanResults = scanner.Scan();

#if DEBUG
scannerCompiledMethods = new List<MethodDesc>(scanResults.CompiledMethodBodies);
scannerConstructedTypes = new List<TypeDesc>(scanResults.ConstructedEETypes);
#endif

if (_scanDgmlLogFileName != null)
scanResults.WriteDependencyLog(_scanDgmlLogFileName);

metadataManager = ((UsageBasedMetadataManager)metadataManager).ToAnalysisBasedMetadataManager();

interopStubManager = scanResults.GetInteropStubManager(interopStateManager, pinvokePolicy);

// If we have a scanner, feed the vtable analysis results to the compilation.
// This could be a command line switch if we really wanted to.
builder.UseVTableSliceProvider(scanResults.GetVTableLayoutInfo());

// If we have a scanner, feed the generic dictionary results to the compilation.
// This could be a command line switch if we really wanted to.
builder.UseGenericDictionaryLayoutProvider(scanResults.GetDictionaryLayoutInfo());

// If we have a scanner, we can drive devirtualization using the information
// we collected at scanning time (effectively sealing unsealed types if possible).
// This could be a command line switch if we really wanted to.
builder.UseDevirtualizationManager(scanResults.GetDevirtualizationManager());

// If we use the scanner's result, we need to consult it to drive inlining.
// This prevents e.g. devirtualizing and inlining methods on types that were
// never actually allocated.
builder.UseInliningPolicy(scanResults.GetInliningPolicy());

// Use an error provider that prevents us from re-importing methods that failed
// to import with an exception during scanning phase. We would see the same failure during
// compilation, but before RyuJIT gets there, it might ask questions that we don't
// have answers for because we didn't scan the entire method.
builder.UseMethodImportationErrorProvider(scanResults.GetMethodImportationErrorProvider());
}

DebugInformationProvider debugInfoProvider = _enableDebugInfo ?
Expand Down Expand Up @@ -874,33 +919,6 @@ static string ILLinkify(string rootedAssembly)
.UseDebugInfoProvider(debugInfoProvider)
.UseDwarf5(_useDwarf5);

if (scanResults != null)
{
// If we have a scanner, feed the vtable analysis results to the compilation.
// This could be a command line switch if we really wanted to.
builder.UseVTableSliceProvider(scanResults.GetVTableLayoutInfo());

// If we have a scanner, feed the generic dictionary results to the compilation.
// This could be a command line switch if we really wanted to.
builder.UseGenericDictionaryLayoutProvider(scanResults.GetDictionaryLayoutInfo());

// If we have a scanner, we can drive devirtualization using the information
// we collected at scanning time (effectively sealing unsealed types if possible).
// This could be a command line switch if we really wanted to.
builder.UseDevirtualizationManager(scanResults.GetDevirtualizationManager());

// If we use the scanner's result, we need to consult it to drive inlining.
// This prevents e.g. devirtualizing and inlining methods on types that were
// never actually allocated.
builder.UseInliningPolicy(scanResults.GetInliningPolicy());

// Use an error provider that prevents us from re-importing methods that failed
// to import with an exception during scanning phase. We would see the same failure during
// compilation, but before RyuJIT gets there, it might ask questions that we don't
// have answers for because we didn't scan the entire method.
builder.UseMethodImportationErrorProvider(scanResults.GetMethodImportationErrorProvider());
}

builder.UseResilience(_resilient);

ICompilation compilation = builder.ToCompilation();
Expand All @@ -925,21 +943,19 @@ static string ILLinkify(string rootedAssembly)
if (_dgmlLogFileName != null)
compilationResults.WriteDependencyLog(_dgmlLogFileName);

if (scanResults != null)
#if DEBUG
if (scannerConstructedTypes != null)
{
if (_scanDgmlLogFileName != null)
scanResults.WriteDependencyLog(_scanDgmlLogFileName);

// If the scanner and compiler don't agree on what to compile, the outputs of the scanner might not actually be usable.
// We are going to check this two ways:
// 1. The methods and types generated during compilation are a subset of method and types scanned
// 2. The methods and types scanned are a subset of methods and types compiled (this has a chance to hold for unoptimized builds only).

// Check that methods and types generated during compilation are a subset of method and types scanned
bool scanningFail = false;
DiffCompilationResults(ref scanningFail, compilationResults.CompiledMethodBodies, scanResults.CompiledMethodBodies,
DiffCompilationResults(ref scanningFail, compilationResults.CompiledMethodBodies, scannerCompiledMethods,
"Methods", "compiled", "scanned", method => !(method.GetTypicalMethodDefinition() is EcmaMethod) || IsRelatedToInvalidInput(method));
DiffCompilationResults(ref scanningFail, compilationResults.ConstructedEETypes, scanResults.ConstructedEETypes,
DiffCompilationResults(ref scanningFail, compilationResults.ConstructedEETypes, scannerConstructedTypes,
"EETypes", "compiled", "scanned", type => !(type.GetTypeDefinition() is EcmaType));

static bool IsRelatedToInvalidInput(MethodDesc method)
Expand All @@ -963,15 +979,16 @@ static bool IsRelatedToInvalidInput(MethodDesc method)

// We additionally skip methods in SIMD module because there's just too many intrisics to handle and IL scanner
// doesn't expand them. They would show up as noisy diffs.
DiffCompilationResults(ref dummy, scanResults.CompiledMethodBodies, compilationResults.CompiledMethodBodies,
DiffCompilationResults(ref dummy, scannerCompiledMethods, compilationResults.CompiledMethodBodies,
"Methods", "scanned", "compiled", method => !(method.GetTypicalMethodDefinition() is EcmaMethod) || method.OwningType.IsIntrinsic);
DiffCompilationResults(ref dummy, scanResults.ConstructedEETypes, compilationResults.ConstructedEETypes,
DiffCompilationResults(ref dummy, scannerConstructedTypes, compilationResults.ConstructedEETypes,
"EETypes", "scanned", "compiled", type => !(type.GetTypeDefinition() is EcmaType));
}

if (scanningFail)
throw new Exception("Scanning failure");
}
#endif

if (debugInfoProvider is IDisposable)
((IDisposable)debugInfoProvider).Dispose();
Expand All @@ -981,7 +998,6 @@ static bool IsRelatedToInvalidInput(MethodDesc method)
return 0;
}

[System.Diagnostics.Conditional("DEBUG")]
private void DiffCompilationResults<T>(ref bool result, IEnumerable<T> set1, IEnumerable<T> set2, string prefix,
string set1name, string set2name, Predicate<T> filter)
{
Expand Down