Skip to content

Commit afffdc6

Browse files
authored
Support for specifying max depth of generic cycle (#63435)
1 parent ef49cd0 commit afffdc6

File tree

3 files changed

+82
-72
lines changed

3 files changed

+82
-72
lines changed

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.Aot.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ namespace ILCompiler
1414
{
1515
partial class CompilerTypeSystemContext
1616
{
17+
// Chosen rather arbitrarily. For the app that I was looking at, cutoff point of 7 compiled
18+
// more than 10 minutes on a release build of the compiler, and I lost patience.
19+
// Cutoff point of 5 produced an 1.7 GB object file.
20+
// Cutoff point of 4 produced an 830 MB object file.
21+
// Cutoff point of 3 produced an 470 MB object file.
22+
// We want this to be high enough so that it doesn't cut off too early. But also not too
23+
// high because things that are recursive often end up expanding laterally as well
24+
// through various other generic code the deep code calls into.
25+
public const int DefaultGenericCycleCutoffPoint = 4;
26+
1727
public SharedGenericsConfiguration GenericsConfig
1828
{
1929
get;
@@ -28,7 +38,7 @@ public SharedGenericsConfiguration GenericsConfig
2838
private ArrayOfTRuntimeInterfacesAlgorithm _arrayOfTRuntimeInterfacesAlgorithm;
2939
private MetadataType _arrayOfTType;
3040

31-
public CompilerTypeSystemContext(TargetDetails details, SharedGenericsMode genericsMode, DelegateFeature delegateFeatures)
41+
public CompilerTypeSystemContext(TargetDetails details, SharedGenericsMode genericsMode, DelegateFeature delegateFeatures, int genericCycleCutoffPoint = DefaultGenericCycleCutoffPoint)
3242
: base(details)
3343
{
3444
_genericsMode = genericsMode;
@@ -38,6 +48,8 @@ public CompilerTypeSystemContext(TargetDetails details, SharedGenericsMode gener
3848

3949
_delegateInfoHashtable = new DelegateInfoHashtable(delegateFeatures);
4050

51+
_genericCycleDetector = new LazyGenericsSupport.GenericCycleDetector(genericCycleCutoffPoint);
52+
4153
GenericsConfig = new SharedGenericsConfiguration();
4254
}
4355

@@ -181,7 +193,7 @@ internal DefType GetClosestDefType(TypeDesc type)
181193
return (DefType)type;
182194
}
183195

184-
private readonly LazyGenericsSupport.GenericCycleDetector _genericCycleDetector = new LazyGenericsSupport.GenericCycleDetector();
196+
private readonly LazyGenericsSupport.GenericCycleDetector _genericCycleDetector;
185197

186198
public void DetectGenericCycles(TypeSystemEntity owner, TypeSystemEntity referent)
187199
{

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/LazyGenerics/ModuleCycleInfo.cs

Lines changed: 65 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,69 @@ public bool FormsCycle(TypeSystemEntity owner)
3434
TypeDesc ownerType = (owner as EcmaMethod)?.OwningType;
3535
return _entitiesInCycles.Contains(owner) || (ownerType != null && _entitiesInCycles.Contains(ownerType));
3636
}
37+
}
38+
39+
private class CycleInfoHashtable : LockFreeReaderHashtable<EcmaModule, ModuleCycleInfo>
40+
{
41+
protected override bool CompareKeyToValue(EcmaModule key, ModuleCycleInfo value) => key == value.Module;
42+
protected override bool CompareValueToValue(ModuleCycleInfo value1, ModuleCycleInfo value2) => value1.Module == value2.Module;
43+
protected override int GetKeyHashCode(EcmaModule key) => key.GetHashCode();
44+
protected override int GetValueHashCode(ModuleCycleInfo value) => value.Module.GetHashCode();
45+
46+
protected override ModuleCycleInfo CreateValueFromKey(EcmaModule key)
47+
{
48+
GraphBuilder gb = new GraphBuilder(key);
49+
Graph<EcmaGenericParameter> graph = gb.Graph;
50+
51+
var formalsNeedingLazyGenerics = graph.ComputeVerticesInvolvedInAFlaggedCycle();
52+
var entitiesNeedingLazyGenerics = new HashSet<TypeSystemEntity>();
53+
54+
foreach (EcmaGenericParameter formal in formalsNeedingLazyGenerics)
55+
{
56+
var formalDefinition = key.MetadataReader.GetGenericParameter(formal.Handle);
57+
if (formal.Kind == GenericParameterKind.Type)
58+
{
59+
entitiesNeedingLazyGenerics.Add(key.GetType(formalDefinition.Parent));
60+
}
61+
else
62+
{
63+
entitiesNeedingLazyGenerics.Add(key.GetMethod(formalDefinition.Parent));
64+
}
65+
}
66+
67+
return new ModuleCycleInfo(key, entitiesNeedingLazyGenerics);
68+
}
69+
}
70+
71+
internal class GenericCycleDetector
72+
{
73+
private readonly CycleInfoHashtable _hashtable = new CycleInfoHashtable();
74+
75+
private readonly struct EntityPair : IEquatable<EntityPair>
76+
{
77+
public readonly TypeSystemEntity Owner;
78+
public readonly TypeSystemEntity Referent;
79+
public EntityPair(TypeSystemEntity owner, TypeSystemEntity referent)
80+
=> (Owner, Referent) = (owner, referent);
81+
public bool Equals(EntityPair other) => Owner == other.Owner && Referent == other.Referent;
82+
public override bool Equals(object obj) => obj is EntityPair p && Equals(p);
83+
public override int GetHashCode() => HashCode.Combine(Owner.GetHashCode(), Referent.GetHashCode());
84+
}
85+
86+
// This is a set of entities that had actual problems that caused us to abort compilation
87+
// somewhere.
88+
// Would prefer this to be a ConcurrentHashSet but there isn't any. ModuleCycleInfo can be looked up
89+
// from the key, but since this is a key/value pair, might as well use the value too...
90+
private readonly ConcurrentDictionary<EntityPair, ModuleCycleInfo> _actualProblems = new ConcurrentDictionary<EntityPair, ModuleCycleInfo>();
91+
92+
private readonly int _cutoffPoint;
93+
94+
public GenericCycleDetector(int cutoffPoint)
95+
{
96+
_cutoffPoint = cutoffPoint;
97+
}
3798

38-
// Chosen rather arbitrarily. For the app that I was looking at, cutoff point of 7 compiled
39-
// more than 10 minutes on a release build of the compiler, and I lost patience.
40-
// Cutoff point of 5 produced an 1.7 GB object file.
41-
// Cutoff point of 4 produced an 830 MB object file.
42-
// Cutoff point of 3 produced an 470 MB object file.
43-
// We want this to be high enough so that it doesn't cut off too early. But also not too
44-
// high because things that are recursive often end up expanding laterally as well
45-
// through various other generic code the deep code calls into.
46-
private const int CutoffPoint = 4;
47-
48-
public bool IsDeepPossiblyCyclicInstantiation(TypeSystemEntity entity)
99+
private bool IsDeepPossiblyCyclicInstantiation(TypeSystemEntity entity)
49100
{
50101
if (entity is TypeDesc type)
51102
{
@@ -57,7 +108,7 @@ public bool IsDeepPossiblyCyclicInstantiation(TypeSystemEntity entity)
57108
}
58109
}
59110

60-
public bool IsDeepPossiblyCyclicInstantiation(TypeDesc type, List<TypeDesc> seenTypes = null)
111+
private bool IsDeepPossiblyCyclicInstantiation(TypeDesc type, List<TypeDesc> seenTypes = null)
61112
{
62113
switch (type.Category)
63114
{
@@ -80,7 +131,7 @@ public bool IsDeepPossiblyCyclicInstantiation(TypeDesc type, List<TypeDesc> seen
80131
count++;
81132
}
82133

83-
if (count > CutoffPoint)
134+
if (count > _cutoffPoint)
84135
{
85136
return true;
86137
}
@@ -112,60 +163,6 @@ public bool IsDeepPossiblyCyclicInstantiation(MethodDesc method)
112163
{
113164
return IsDeepPossiblyCyclicInstantiation(method.Instantiation) || IsDeepPossiblyCyclicInstantiation(method.OwningType);
114165
}
115-
}
116-
117-
private class CycleInfoHashtable : LockFreeReaderHashtable<EcmaModule, ModuleCycleInfo>
118-
{
119-
protected override bool CompareKeyToValue(EcmaModule key, ModuleCycleInfo value) => key == value.Module;
120-
protected override bool CompareValueToValue(ModuleCycleInfo value1, ModuleCycleInfo value2) => value1.Module == value2.Module;
121-
protected override int GetKeyHashCode(EcmaModule key) => key.GetHashCode();
122-
protected override int GetValueHashCode(ModuleCycleInfo value) => value.Module.GetHashCode();
123-
124-
protected override ModuleCycleInfo CreateValueFromKey(EcmaModule key)
125-
{
126-
GraphBuilder gb = new GraphBuilder(key);
127-
Graph<EcmaGenericParameter> graph = gb.Graph;
128-
129-
var formalsNeedingLazyGenerics = graph.ComputeVerticesInvolvedInAFlaggedCycle();
130-
var entitiesNeedingLazyGenerics = new HashSet<TypeSystemEntity>();
131-
132-
foreach (EcmaGenericParameter formal in formalsNeedingLazyGenerics)
133-
{
134-
var formalDefinition = key.MetadataReader.GetGenericParameter(formal.Handle);
135-
if (formal.Kind == GenericParameterKind.Type)
136-
{
137-
entitiesNeedingLazyGenerics.Add(key.GetType(formalDefinition.Parent));
138-
}
139-
else
140-
{
141-
entitiesNeedingLazyGenerics.Add(key.GetMethod(formalDefinition.Parent));
142-
}
143-
}
144-
145-
return new ModuleCycleInfo(key, entitiesNeedingLazyGenerics);
146-
}
147-
}
148-
149-
internal class GenericCycleDetector
150-
{
151-
private readonly CycleInfoHashtable _hashtable = new CycleInfoHashtable();
152-
153-
private readonly struct EntityPair : IEquatable<EntityPair>
154-
{
155-
public readonly TypeSystemEntity Owner;
156-
public readonly TypeSystemEntity Referent;
157-
public EntityPair(TypeSystemEntity owner, TypeSystemEntity referent)
158-
=> (Owner, Referent) = (owner, referent);
159-
public bool Equals(EntityPair other) => Owner == other.Owner && Referent == other.Referent;
160-
public override bool Equals(object obj) => obj is EntityPair p && Equals(p);
161-
public override int GetHashCode() => HashCode.Combine(Owner.GetHashCode(), Referent.GetHashCode());
162-
}
163-
164-
// This is a set of entities that had actual problems that caused us to abort compilation
165-
// somewhere.
166-
// Would prefer this to be a ConcurrentHashSet but there isn't any. ModuleCycleInfo can be looked up
167-
// from the key, but since this is a key/value pair, might as well use the value too...
168-
private readonly ConcurrentDictionary<EntityPair, ModuleCycleInfo> _actualProblems = new ConcurrentDictionary<EntityPair, ModuleCycleInfo>();
169166

170167
public void DetectCycle(TypeSystemEntity owner, TypeSystemEntity referent)
171168
{
@@ -196,7 +193,7 @@ public void DetectCycle(TypeSystemEntity owner, TypeSystemEntity referent)
196193
{
197194
// Just the presence of a cycle is not a problem, but once we start getting too deep,
198195
// we need to cut our losses.
199-
if (cycleInfo.IsDeepPossiblyCyclicInstantiation(referent))
196+
if (IsDeepPossiblyCyclicInstantiation(referent))
200197
{
201198
_actualProblems.TryAdd(new EntityPair(owner, referent), cycleInfo);
202199

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ internal class Program
6060
private int _parallelism = Environment.ProcessorCount;
6161
private string _instructionSet;
6262
private string _guard;
63+
private int _maxGenericCycle = CompilerTypeSystemContext.DefaultGenericCycleCutoffPoint;
6364

6465
private string _singleMethodTypeName;
6566
private string _singleMethodName;
@@ -216,7 +217,7 @@ private ArgumentSyntax ParseCommandLine(string[] args)
216217
syntax.DefineOptionList("nosinglewarnassembly", ref _singleWarnDisabledAssemblies, "Expand AOT/trimming warnings for given assembly");
217218
syntax.DefineOptionList("directpinvoke", ref _directPInvokes, "PInvoke to call directly");
218219
syntax.DefineOptionList("directpinvokelist", ref _directPInvokeLists, "File with list of PInvokes to call directly");
219-
220+
syntax.DefineOption("maxgenericcycle", ref _maxGenericCycle, "Max depth of generic cycle");
220221
syntax.DefineOptionList("root", ref _rootedAssemblies, "Fully generate given assembly");
221222
syntax.DefineOptionList("conditionalroot", ref _conditionallyRootedAssemblies, "Fully generate given assembly if it's used");
222223
syntax.DefineOptionList("trim", ref _trimmedAssemblies, "Trim the specified assembly");
@@ -460,7 +461,7 @@ private int Run(string[] args)
460461
var targetAbi = TargetAbi.CoreRT;
461462
var targetDetails = new TargetDetails(_targetArchitecture, _targetOS, targetAbi, simdVectorLength);
462463
CompilerTypeSystemContext typeSystemContext =
463-
new CompilerTypeSystemContext(targetDetails, genericsMode, supportsReflection ? DelegateFeature.All : 0);
464+
new CompilerTypeSystemContext(targetDetails, genericsMode, supportsReflection ? DelegateFeature.All : 0, _maxGenericCycle);
464465

465466
//
466467
// TODO: To support our pre-compiled test tree, allow input files that aren't managed assemblies since

0 commit comments

Comments
 (0)