Skip to content

Commit c270c9e

Browse files
authored
Add limit on trim analyzer CFG convergence iterations (#118760)
With large block counts, the CFG convergence takes a long time and performs a lot of Meets, allocating a new Dictionary for each Value in each block. This adds up to gigabytes of allocations in the process. Ideally we would fix the performance issue with some optimization, but I'm still working on a proper solution. To fix the issue for .NET 10 in the meantime, we can add a limit to the number of iterations to bail out if we reach an excessively large number of iterations. The largest number of iterations in the runtime build is 2600, so 10,000 seems fairly reasonable. The build time of the project in issue #118121 is around ~20 seconds with 10,000 iterations and ~70 seconds with 20,000.
1 parent fffd8c0 commit c270c9e

File tree

6 files changed

+10064
-11
lines changed

6 files changed

+10064
-11
lines changed

src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowAnalysis.cs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,9 @@ protected LocalDataFlowAnalysis(
6363
OperationBlock = operationBlock;
6464
}
6565

66-
public void InterproceduralAnalyze()
66+
public bool InterproceduralAnalyze()
6767
{
68+
bool succeeded = true;
6869
ValueSetLattice<MethodBodyValue> methodGroupLattice = default;
6970
DictionaryLattice<LocalKey, Maybe<TValue>, MaybeLattice<TValue, TLattice>> hoistedLocalLattice = default;
7071
var interproceduralStateLattice = new InterproceduralStateLattice<TValue, TLattice>(
@@ -75,8 +76,8 @@ public void InterproceduralAnalyze()
7576

7677
if (OperationBlock is IAttributeOperation attribute)
7778
{
78-
AnalyzeAttribute(Context.OwningSymbol, attribute);
79-
return;
79+
succeeded &= AnalyzeAttribute(Context.OwningSymbol, attribute);
80+
return succeeded;
8081
}
8182

8283
Debug.Assert(Context.OwningSymbol is not IMethodSymbol methodSymbol ||
@@ -91,29 +92,31 @@ public void InterproceduralAnalyze()
9192
Debug.Assert(!oldInterproceduralState.Methods.IsUnknown());
9293
foreach (var method in oldInterproceduralState.Methods.GetKnownValues())
9394
{
94-
AnalyzeMethod(method, ref interproceduralState);
95+
succeeded &= AnalyzeMethod(method, ref interproceduralState);
9596
}
9697
}
98+
return succeeded;
9799
}
98100

99-
private void AnalyzeAttribute(ISymbol owningSymbol, IAttributeOperation attribute)
101+
private bool AnalyzeAttribute(ISymbol owningSymbol, IAttributeOperation attribute)
100102
{
101103
var cfg = Context.GetControlFlowGraph(attribute);
102104
var lValueFlowCaptures = LValueFlowCapturesProvider.CreateLValueFlowCaptures(cfg);
103105
var visitor = GetVisitor(owningSymbol, cfg, lValueFlowCaptures, default);
104-
Fixpoint(new ControlFlowGraphProxy(cfg), visitor);
106+
return Fixpoint(new ControlFlowGraphProxy(cfg), visitor);
105107
}
106108

107-
private void AnalyzeMethod(MethodBodyValue method, ref InterproceduralState<TValue, TLattice> interproceduralState)
109+
private bool AnalyzeMethod(MethodBodyValue method, ref InterproceduralState<TValue, TLattice> interproceduralState)
108110
{
109111
var cfg = method.ControlFlowGraph;
110112
var lValueFlowCaptures = LValueFlowCapturesProvider.CreateLValueFlowCaptures(cfg);
111113
var visitor = GetVisitor(method.OwningSymbol, cfg, lValueFlowCaptures, interproceduralState);
112-
Fixpoint(new ControlFlowGraphProxy(cfg), visitor);
114+
bool succeeded = Fixpoint(new ControlFlowGraphProxy(cfg), visitor);
113115

114116
// The interprocedural state struct is stored as a field of the visitor and modified
115117
// in-place there, but we also need those modifications to be reflected here.
116118
interproceduralState = visitor.InterproceduralState;
119+
return succeeded;
117120
}
118121

119122
protected abstract TTransfer GetVisitor(

src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public static ImmutableArray<DiagnosticDescriptor> GetSupportedDiagnostics()
6161
diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.ReturnValueDoesNotMatchFeatureGuards));
6262
diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.InvalidFeatureGuard));
6363
diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.TypeMapGroupTypeCannotBeStaticallyDetermined));
64+
diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DataflowAnalysisDidNotConverge));
6465

6566
foreach (var requiresAnalyzer in RequiresAnalyzers.Value)
6667
{
@@ -110,8 +111,17 @@ public override void Initialize(AnalysisContext context)
110111
foreach (var operationBlock in context.OperationBlocks)
111112
{
112113
TrimDataFlowAnalysis trimDataFlowAnalysis = new(context, dataFlowAnalyzerContext, operationBlock);
113-
trimDataFlowAnalysis.InterproceduralAnalyze();
114+
bool success = trimDataFlowAnalysis.InterproceduralAnalyze();
114115
trimDataFlowAnalysis.ReportDiagnostics(context.ReportDiagnostic);
116+
if (!success)
117+
{
118+
context.ReportDiagnostic(
119+
Diagnostic.Create(DiagnosticDescriptors.GetDiagnosticDescriptor(
120+
DiagnosticId.DataflowAnalysisDidNotConverge,
121+
diagnosticSeverity: DiagnosticSeverity.Warning),
122+
operationBlock.Syntax.GetLocation(),
123+
operationBlock.FindContainingSymbol(context.OwningSymbol).GetDisplayName()));
124+
}
115125
}
116126
});
117127

src/tools/illink/src/ILLink.Shared/DataFlow/ForwardDataFlowAnalysis.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ successor.ConditionKind is ConditionKind.WhenTrue
214214

215215
// This just runs a dataflow algorithm until convergence. It doesn't cache any results,
216216
// allowing each particular kind of analysis to decide what is worth saving.
217-
public void Fixpoint(TControlFlowGraph cfg, TTransfer transfer)
217+
public bool Fixpoint(TControlFlowGraph cfg, TTransfer transfer)
218218
{
219219
TraceStart(cfg);
220220

@@ -244,11 +244,15 @@ public void Fixpoint(TControlFlowGraph cfg, TTransfer transfer)
244244
};
245245

246246
bool changed = true;
247-
while (changed)
247+
const int MaxIterations = 10_000;
248+
int iterations = 0;
249+
while (changed && iterations < MaxIterations)
248250
{
249251
changed = false;
250252
foreach (var block in cfg.Blocks)
251253
{
254+
if (++iterations >= MaxIterations)
255+
break;
252256

253257
TraceVisitBlock(block);
254258

@@ -417,6 +421,8 @@ public void Fixpoint(TControlFlowGraph cfg, TTransfer transfer)
417421
}
418422
}
419423

424+
return !changed || iterations >= MaxIterations;
425+
420426
void FlowStateThroughExitedFinallys(
421427
IControlFlowGraph<TBlock, TRegion>.ControlFlowBranch predecessor,
422428
ref TValue predecessorState)

src/tools/illink/src/ILLink.Shared/DiagnosticId.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ public enum DiagnosticId
190190
RequiresUnreferencedCodeOnEntryPoint = 2123,
191191
TypeMapGroupTypeCannotBeStaticallyDetermined = 2124,
192192
ReferenceNotMarkedIsTrimmable = 2125,
193+
DataflowAnalysisDidNotConverge = 2126,
193194
_EndTrimAnalysisWarningsSentinel,
194195

195196
// Single-file diagnostic ids.

src/tools/illink/src/ILLink.Shared/SharedStrings.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1251,4 +1251,10 @@
12511251
<data name="ReferenceNotMarkedIsAotCompatibleMessage" xml:space="preserve">
12521252
<value>Referenced assembly '{0}' is not built with `<IsAotCompatible>true</IsAotCompatible>` and may not be compatible with AOT.</value>
12531253
</data>
1254+
<data name="DataflowAnalysisDidNotConvergeTitle" xml:space="preserve">
1255+
<value>Trim dataflow analysis took too long to complete. Trim safety cannot be guaranteed.</value>
1256+
</data>
1257+
<data name="DataflowAnalysisDidNotConvergeMessage" xml:space="preserve">
1258+
<value>Trim dataflow analysis of member '{0}' took too long to complete. Trim safety cannot be guaranteed.</value>
1259+
</data>
12541260
</root>

0 commit comments

Comments
 (0)