Skip to content

Commit 9ad3026

Browse files
Allow IL scanner's dependency graph be GC'd (#72430)
We don't need to keep the graph in the memory once we're done scanning. Verified it gets collected now by adding a finalizer to `ILScanNodeFactory` and having it print something. It didn't get collected previously. If I'm measuring it right, it was rooting about 17 MB of objects in memory on a hello world.
1 parent e81c02d commit 9ad3026

File tree

1 file changed

+54
-38
lines changed

1 file changed

+54
-38
lines changed

src/coreclr/tools/aot/ILCompiler/Program.cs

Lines changed: 54 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.IO;
77
using System.Reflection;
8+
using System.Runtime.CompilerServices;
89
using System.Runtime.InteropServices;
910
using System.Text;
1011

@@ -827,8 +828,20 @@ static string ILLinkify(string rootedAssembly)
827828
.UseILProvider(ilProvider)
828829
.UsePreinitializationManager(preinitManager);
829830

830-
ILScanResults scanResults = null;
831+
#if DEBUG
832+
List<TypeDesc> scannerConstructedTypes = null;
833+
List<MethodDesc> scannerCompiledMethods = null;
834+
#endif
835+
831836
if (useScanner)
837+
{
838+
// Run the scanner in a separate stack frame so that there's no dangling references to
839+
// it once we're done with it and it can be garbage collected.
840+
RunScanner();
841+
}
842+
843+
[MethodImpl(MethodImplOptions.NoInlining)]
844+
void RunScanner()
832845
{
833846
ILScannerBuilder scannerBuilder = builder.GetILScannerBuilder()
834847
.UseCompilationRoots(compilationRoots)
@@ -842,11 +855,43 @@ static string ILLinkify(string rootedAssembly)
842855

843856
IILScanner scanner = scannerBuilder.ToILScanner();
844857

845-
scanResults = scanner.Scan();
858+
ILScanResults scanResults = scanner.Scan();
859+
860+
#if DEBUG
861+
scannerCompiledMethods = new List<MethodDesc>(scanResults.CompiledMethodBodies);
862+
scannerConstructedTypes = new List<TypeDesc>(scanResults.ConstructedEETypes);
863+
#endif
864+
865+
if (_scanDgmlLogFileName != null)
866+
scanResults.WriteDependencyLog(_scanDgmlLogFileName);
846867

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

849870
interopStubManager = scanResults.GetInteropStubManager(interopStateManager, pinvokePolicy);
871+
872+
// If we have a scanner, feed the vtable analysis results to the compilation.
873+
// This could be a command line switch if we really wanted to.
874+
builder.UseVTableSliceProvider(scanResults.GetVTableLayoutInfo());
875+
876+
// If we have a scanner, feed the generic dictionary results to the compilation.
877+
// This could be a command line switch if we really wanted to.
878+
builder.UseGenericDictionaryLayoutProvider(scanResults.GetDictionaryLayoutInfo());
879+
880+
// If we have a scanner, we can drive devirtualization using the information
881+
// we collected at scanning time (effectively sealing unsealed types if possible).
882+
// This could be a command line switch if we really wanted to.
883+
builder.UseDevirtualizationManager(scanResults.GetDevirtualizationManager());
884+
885+
// If we use the scanner's result, we need to consult it to drive inlining.
886+
// This prevents e.g. devirtualizing and inlining methods on types that were
887+
// never actually allocated.
888+
builder.UseInliningPolicy(scanResults.GetInliningPolicy());
889+
890+
// Use an error provider that prevents us from re-importing methods that failed
891+
// to import with an exception during scanning phase. We would see the same failure during
892+
// compilation, but before RyuJIT gets there, it might ask questions that we don't
893+
// have answers for because we didn't scan the entire method.
894+
builder.UseMethodImportationErrorProvider(scanResults.GetMethodImportationErrorProvider());
850895
}
851896

852897
DebugInformationProvider debugInfoProvider = _enableDebugInfo ?
@@ -874,33 +919,6 @@ static string ILLinkify(string rootedAssembly)
874919
.UseDebugInfoProvider(debugInfoProvider)
875920
.UseDwarf5(_useDwarf5);
876921

877-
if (scanResults != null)
878-
{
879-
// If we have a scanner, feed the vtable analysis results to the compilation.
880-
// This could be a command line switch if we really wanted to.
881-
builder.UseVTableSliceProvider(scanResults.GetVTableLayoutInfo());
882-
883-
// If we have a scanner, feed the generic dictionary results to the compilation.
884-
// This could be a command line switch if we really wanted to.
885-
builder.UseGenericDictionaryLayoutProvider(scanResults.GetDictionaryLayoutInfo());
886-
887-
// If we have a scanner, we can drive devirtualization using the information
888-
// we collected at scanning time (effectively sealing unsealed types if possible).
889-
// This could be a command line switch if we really wanted to.
890-
builder.UseDevirtualizationManager(scanResults.GetDevirtualizationManager());
891-
892-
// If we use the scanner's result, we need to consult it to drive inlining.
893-
// This prevents e.g. devirtualizing and inlining methods on types that were
894-
// never actually allocated.
895-
builder.UseInliningPolicy(scanResults.GetInliningPolicy());
896-
897-
// Use an error provider that prevents us from re-importing methods that failed
898-
// to import with an exception during scanning phase. We would see the same failure during
899-
// compilation, but before RyuJIT gets there, it might ask questions that we don't
900-
// have answers for because we didn't scan the entire method.
901-
builder.UseMethodImportationErrorProvider(scanResults.GetMethodImportationErrorProvider());
902-
}
903-
904922
builder.UseResilience(_resilient);
905923

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

928-
if (scanResults != null)
946+
#if DEBUG
947+
if (scannerConstructedTypes != null)
929948
{
930-
if (_scanDgmlLogFileName != null)
931-
scanResults.WriteDependencyLog(_scanDgmlLogFileName);
932-
933949
// If the scanner and compiler don't agree on what to compile, the outputs of the scanner might not actually be usable.
934950
// We are going to check this two ways:
935951
// 1. The methods and types generated during compilation are a subset of method and types scanned
936952
// 2. The methods and types scanned are a subset of methods and types compiled (this has a chance to hold for unoptimized builds only).
937953

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

945961
static bool IsRelatedToInvalidInput(MethodDesc method)
@@ -963,15 +979,16 @@ static bool IsRelatedToInvalidInput(MethodDesc method)
963979

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

972988
if (scanningFail)
973989
throw new Exception("Scanning failure");
974990
}
991+
#endif
975992

976993
if (debugInfoProvider is IDisposable)
977994
((IDisposable)debugInfoProvider).Dispose();
@@ -981,7 +998,6 @@ static bool IsRelatedToInvalidInput(MethodDesc method)
981998
return 0;
982999
}
9831000

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

0 commit comments

Comments
 (0)