Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9f91f88
Adapt Tarjan generic cycle detector for use in Crossgen2
trylek Jun 28, 2022
315ed70
Mop-up fixes after rebase to most recent main
trylek May 11, 2023
09d41fa
Implement breadth cutoff; remove temporary instrumentation
trylek May 19, 2023
3fba217
Remove remaining instrumentation and consolidate command-line args
trylek May 19, 2023
53db664
Fix incorrect rebase
trylek May 19, 2023
2215ff7
One more rebase fix
trylek May 20, 2023
f3d9a99
Fix NativeAOT compiler variant of the generic cutoff point
trylek May 20, 2023
9c34303
Address Michal's local PR feedback (simple functionality shuffles)
trylek May 23, 2023
2722de8
Revert change to pre-existing command-line option naming to reduce churn
trylek May 24, 2023
d3e50b5
Move cycle detector under GenericCycleDetection per Michal's PR feedback
trylek May 24, 2023
09ce974
Delete no longer needed logging changes
trylek May 24, 2023
ef8f33c
Revert incorrect addition of READYTORUN define from DependencyAnalysi…
trylek May 24, 2023
5f903fb
Add generic depth cutoff test
trylek May 25, 2023
f6dc9b6
Adjust DepthTest so that it only uses the depth cutoff
trylek May 25, 2023
f41ab02
Implement unit tests for generic cycle detection
trylek May 31, 2023
d3a44b3
Fix generic breadth test
trylek Jun 2, 2023
96fc08b
Address Michal's PR feedback, fix one unit test to avoid lab timeouts
trylek Jun 8, 2023
1ec93be
Make Depth3Test unsupported on 32-bit platforms where it OOMS Crossgen2
trylek Jun 9, 2023
dfd968b
Fix typo in MSBuild condition
trylek Jun 9, 2023
7b3dab7
Reorganize handling of method fixups per David's PR feedback
trylek Jun 9, 2023
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project>

<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Graph.cs" Link="Common\Compiler\GenericCycleDetection\Graph.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Graph.Cycles.cs" Link="Common\Compiler\GenericCycleDetection\Graph.Cycles.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Graph.Vertex.cs" Link="Common\Compiler\GenericCycleDetection\Graph.Vertex.cs" />
<Compile Include="$(MSBuildThisFileDirectory)GraphBuilder.cs" Link="Common\Compiler\GenericCycleDetection\GraphBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)GraphBuilder.ForEach.cs" Link="Common\Compiler\GenericCycleDetection\GraphBuilder.ForEach.cs" />
<Compile Include="$(MSBuildThisFileDirectory)GraphBuilder.MethodCall.cs" Link="Common\Compiler\GenericCycleDetection\GraphBuilder.MethodCall.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ModuleCycleInfo.cs" Link="Common\Compiler\GenericCycleDetection\ModuleCycleInfo.cs" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
using Internal.TypeSystem;
using Internal.TypeSystem.Ecma;

#if !READYTORUN
using ILLink.Shared;
#endif

using Debug = System.Diagnostics.Debug;

Expand Down Expand Up @@ -91,11 +93,13 @@ public EntityPair(TypeSystemEntity owner, TypeSystemEntity referent)
// from the key, but since this is a key/value pair, might as well use the value too...
private readonly ConcurrentDictionary<EntityPair, ModuleCycleInfo> _actualProblems = new ConcurrentDictionary<EntityPair, ModuleCycleInfo>();

private readonly int _cutoffPoint;
private readonly int _depthCutoff;
private readonly int _breadthCutoff;

public GenericCycleDetector(int cutoffPoint)
public GenericCycleDetector(int depthCutoff, int breadthCutoff)
{
_cutoffPoint = cutoffPoint;
_depthCutoff = depthCutoff;
_breadthCutoff = breadthCutoff;
}

private bool IsDeepPossiblyCyclicInstantiation(TypeSystemEntity entity)
Expand All @@ -110,17 +114,31 @@ private bool IsDeepPossiblyCyclicInstantiation(TypeSystemEntity entity)
}
}

private bool IsDeepPossiblyCyclicInstantiation(TypeDesc type, List<TypeDesc> seenTypes = null)
private bool IsDeepPossiblyCyclicInstantiation(TypeDesc type)
{
int breadthCounter = 0;
return IsDeepPossiblyCyclicInstantiation(type, ref breadthCounter, seenTypes: null);
}

private bool IsDeepPossiblyCyclicInstantiation(TypeDesc type, ref int breadthCounter, List<TypeDesc> seenTypes = null)
{
switch (type.Category)
{
case TypeFlags.Array:
case TypeFlags.SzArray:
return IsDeepPossiblyCyclicInstantiation(((ParameterizedType)type).ParameterType, seenTypes);
return IsDeepPossiblyCyclicInstantiation(((ParameterizedType)type).ParameterType, ref breadthCounter, seenTypes);
default:
TypeDesc typeDef = type.GetTypeDefinition();
if (type != typeDef)
{
if (FormsCycle(typeDef, out ModuleCycleInfo _))
{
if (_breadthCutoff >= 0 && ++breadthCounter >= _breadthCutoff)
{
return true;
}
}

(seenTypes ??= new List<TypeDesc>()).Add(typeDef);
for (int i = 0; i < seenTypes.Count; i++)
{
Expand All @@ -133,26 +151,26 @@ private bool IsDeepPossiblyCyclicInstantiation(TypeDesc type, List<TypeDesc> see
count++;
}

if (count > _cutoffPoint)
if (count > _depthCutoff)
{
return true;
}
}
}

bool result = IsDeepPossiblyCyclicInstantiation(type.Instantiation, seenTypes);
bool result = IsDeepPossiblyCyclicInstantiation(type.Instantiation, ref breadthCounter, seenTypes);
seenTypes.RemoveAt(seenTypes.Count - 1);
return result;
}
return false;
}
}

private bool IsDeepPossiblyCyclicInstantiation(Instantiation instantiation, List<TypeDesc> seenTypes = null)
private bool IsDeepPossiblyCyclicInstantiation(Instantiation instantiation, ref int breadthCounter, List<TypeDesc> seenTypes)
{
foreach (TypeDesc arg in instantiation)
{
if (IsDeepPossiblyCyclicInstantiation(arg, seenTypes))
if (IsDeepPossiblyCyclicInstantiation(arg, ref breadthCounter, seenTypes))
{
return true;
}
Expand All @@ -163,13 +181,30 @@ private bool IsDeepPossiblyCyclicInstantiation(Instantiation instantiation, List

public bool IsDeepPossiblyCyclicInstantiation(MethodDesc method)
{
return IsDeepPossiblyCyclicInstantiation(method.Instantiation) || IsDeepPossiblyCyclicInstantiation(method.OwningType);
int breadthCounter = 0;
return IsDeepPossiblyCyclicInstantiation(method.Instantiation, ref breadthCounter, seenTypes: null)
|| IsDeepPossiblyCyclicInstantiation(method.OwningType, ref breadthCounter, seenTypes: null);
}

private bool FormsCycle(TypeSystemEntity entity, out ModuleCycleInfo cycleInfo)
{
EcmaModule ownerModule = (entity as EcmaType)?.EcmaModule ?? (entity as EcmaMethod)?.Module;
if (ownerModule != null)
{
cycleInfo = _hashtable.GetOrCreateValue(ownerModule);
return cycleInfo.FormsCycle(entity);
}
else
{
cycleInfo = null;
return false;
}
}

public void DetectCycle(TypeSystemEntity owner, TypeSystemEntity referent)
{
// This allows to disable cycle detection completely (typically for perf reasons as the algorithm is pretty slow)
if (_cutoffPoint < 0)
if (_depthCutoff < 0)
return;

// Not clear if generic recursion through fields is a thing
Expand All @@ -192,10 +227,7 @@ public void DetectCycle(TypeSystemEntity owner, TypeSystemEntity referent)
return;
}

EcmaModule ownerModule = (ownerDefinition as EcmaType)?.EcmaModule ?? ((EcmaMethod)ownerDefinition).Module;

ModuleCycleInfo cycleInfo = _hashtable.GetOrCreateValue(ownerModule);
if (cycleInfo.FormsCycle(ownerDefinition))
if (FormsCycle(ownerDefinition, out ModuleCycleInfo cycleInfo))
{
// Just the presence of a cycle is not a problem, but once we start getting too deep,
// we need to cut our losses.
Expand All @@ -217,6 +249,7 @@ public void DetectCycle(TypeSystemEntity owner, TypeSystemEntity referent)
}
}

#if !READYTORUN
public void LogWarnings(Logger logger)
{
// Might need to sort these if we care about warning determinism, but we probably don't.
Expand Down Expand Up @@ -252,6 +285,7 @@ public void LogWarnings(Logger logger)
logger.LogWarning(actualProblem.Key.Owner, DiagnosticId.GenericRecursionCycle, actualProblem.Key.Referent.GetDisplayName(), message);
}
}
#endif
}
}
}
11 changes: 11 additions & 0 deletions src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,17 @@ private void PublishCode()
{
if (computedNodes.Add(fixup))
{
if (fixup is IMethodNode methodNode)
{
try
{
_compilation.NodeFactory.DetectGenericCycles(_methodCodeNode.Method, methodNode.Method);
}
catch (TypeLoadException)
{
throw new RequiresRuntimeJitException("Requires runtime JIT - potential generic cycle detected");
}
}
_methodCodeNode.Fixups.Add(fixup);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ public partial class CompilerTypeSystemContext
// We want this to be high enough so that it doesn't cut off too early. But also not too
// high because things that are recursive often end up expanding laterally as well
// through various other generic code the deep code calls into.
public const int DefaultGenericCycleCutoffPoint = 4;
public const int DefaultGenericCycleDepthCutoff = 4;

public const int DefaultGenericCycleBreadthCutoff = 10;

public SharedGenericsConfiguration GenericsConfig
{
Expand All @@ -40,7 +42,9 @@ public SharedGenericsConfiguration GenericsConfig
private MetadataType _arrayOfTType;
private MetadataType _attributeType;

public CompilerTypeSystemContext(TargetDetails details, SharedGenericsMode genericsMode, DelegateFeature delegateFeatures, int genericCycleCutoffPoint = DefaultGenericCycleCutoffPoint)
public CompilerTypeSystemContext(TargetDetails details, SharedGenericsMode genericsMode, DelegateFeature delegateFeatures,
int genericCycleDepthCutoff = DefaultGenericCycleDepthCutoff,
int genericCycleBreadthCutoff = DefaultGenericCycleBreadthCutoff)
: base(details)
{
_genericsMode = genericsMode;
Expand All @@ -51,7 +55,7 @@ public CompilerTypeSystemContext(TargetDetails details, SharedGenericsMode gener

_delegateInfoHashtable = new DelegateInfoHashtable(delegateFeatures);

_genericCycleDetector = new LazyGenericsSupport.GenericCycleDetector(genericCycleCutoffPoint);
_genericCycleDetector = new LazyGenericsSupport.GenericCycleDetector(genericCycleDepthCutoff, genericCycleBreadthCutoff);

GenericsConfig = new SharedGenericsConfiguration();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -438,13 +438,6 @@
<Compile Include="Compiler\FeatureSwitchManager.cs" />
<Compile Include="Compiler\GeneratingMetadataManager.cs" />
<Compile Include="Compiler\GenericRootProvider.cs" />
<Compile Include="Compiler\LazyGenerics\ModuleCycleInfo.cs" />
<Compile Include="Compiler\LazyGenerics\Graph.cs" />
<Compile Include="Compiler\LazyGenerics\Graph.Cycles.cs" />
<Compile Include="Compiler\LazyGenerics\Graph.Vertex.cs" />
<Compile Include="Compiler\LazyGenerics\GraphBuilder.cs" />
<Compile Include="Compiler\LazyGenerics\GraphBuilder.ForEach.cs" />
<Compile Include="Compiler\LazyGenerics\GraphBuilder.MethodCall.cs" />
<Compile Include="Compiler\HardwareIntrinsicHelpers.Aot.cs" />
<Compile Include="Compiler\IInliningPolicy.cs" />
<Compile Include="Compiler\Logging\NativeAotFatalErrorException.cs" />
Expand Down Expand Up @@ -711,4 +704,6 @@
</ItemGroup>

<Import Project="..\..\..\..\tools\illink\src\ILLink.Shared\ILLink.Shared.projitems" Label="Shared" />
<Import Project="..\..\Common\Compiler\GenericCycleDetection\GenericCycleDetection.projitems" />

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,14 @@ public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFacto
if (!method.IsGenericMethodDefinition &&
context.CompilationModuleGroup.ContainsMethodBody(method, false))
{
dependencies.Add(context.CompiledMethodNode(method), $"Method on type {Type.ToString()}");
try
{
context.DetectGenericCycles(Type, method);
dependencies.Add(context.CompiledMethodNode(method), $"Method on type {Type.ToString()}");
}
catch (TypeSystemException)
{
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,20 @@ public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFacto
MethodDesc canonMethod = _method.Method.GetCanonMethodTarget(CanonicalFormKind.Specific);
if (factory.CompilationModuleGroup.ContainsMethodBody(canonMethod, false))
{
ISymbolNode canonMethodNode = factory.CompiledMethodNode(canonMethod);
yield return new DependencyListEntry(canonMethodNode, "Canonical method for instantiating stub");
bool useDependency = true;
try
{
factory.DetectGenericCycles(_method.Method, canonMethod);
}
catch (TypeSystemException)
{
useDependency = false;
}
if (useDependency)
{
ISymbolNode canonMethodNode = factory.CompiledMethodNode(canonMethod);
yield return new DependencyListEntry(canonMethodNode, "Canonical method for instantiating stub");
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,14 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact
factory.CompilationModuleGroup.ContainsMethodBody(canonMethod, false))
{
list = list ?? new DependencyAnalysisFramework.DependencyNodeCore<NodeFactory>.DependencyList();
list.Add(factory.CompiledMethodNode(canonMethod), "Virtual function dependency on cross module inlineable method");
try
{
factory.DetectGenericCycles(_method.Method, canonMethod);
list.Add(factory.CompiledMethodNode(canonMethod), "Virtual function dependency on cross module inlineable method");
}
catch (TypeSystemException)
{
}
}

return list;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,14 @@ public static void AddDependenciesForAsyncStateMachineBox(ref DependencyList dep
{
case "MoveNext":
case ".cctor":
dependencies.Add(factory.CompiledMethodNode(method), $"AsyncStateMachineBox Method on type {type.ToString()}");
try
{
factory.DetectGenericCycles(type, method);
dependencies.Add(factory.CompiledMethodNode(method), $"AsyncStateMachineBox Method on type {type.ToString()}");
}
catch (TypeSystemException)
{
}
break;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,9 @@ public NodeFactory(
ResourceData win32Resources,
ReadyToRunFlags flags,
NodeFactoryOptimizationFlags nodeFactoryOptimizationFlags,
ulong imageBase)
ulong imageBase,
int genericCycleDepthCutoff,
int genericCycleBreadthCutoff)
{
OptimizationFlags = nodeFactoryOptimizationFlags;
TypeSystemContext = context;
Expand Down Expand Up @@ -216,6 +218,13 @@ public NodeFactory(
}

CreateNodeCaches();

if (genericCycleBreadthCutoff >= 0 || genericCycleDepthCutoff >= 0)
{
_genericCycleDetector = new LazyGenericsSupport.GenericCycleDetector(
depthCutoff: genericCycleDepthCutoff,
breadthCutoff: genericCycleBreadthCutoff);
}
}

private void CreateNodeCaches()
Expand Down Expand Up @@ -389,6 +398,8 @@ private void CreateNodeCaches()

private NodeCache<ReadyToRunHelper, Import> _constructedHelpers;

private LazyGenericsSupport.GenericCycleDetector _genericCycleDetector;

public Import GetReadyToRunHelperCell(ReadyToRunHelper helperId)
{
return _constructedHelpers.GetOrAdd(helperId);
Expand Down Expand Up @@ -1010,5 +1021,10 @@ public CopiedManagedResourcesNode CopiedManagedResources(EcmaModule module)
{
return _copiedManagedResources.GetOrAdd(module);
}

public void DetectGenericCycles(TypeSystemEntity caller, TypeSystemEntity callee)
{
_genericCycleDetector?.DetectCycle(caller, callee);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ public bool CanInline(MethodDesc caller, MethodDesc callee)
}
}

_nodeFactory.DetectGenericCycles(caller, callee);

return NodeFactory.CompilationModuleGroup.CanInline(caller, callee);
}

Expand Down Expand Up @@ -426,7 +428,9 @@ private void RewriteComponentFile(string inputFile, string outputFile, string ow
win32Resources: new Win32Resources.ResourceData(inputModule),
flags,
_nodeFactory.OptimizationFlags,
_nodeFactory.ImageBase);
_nodeFactory.ImageBase,
genericCycleDepthCutoff: -1, // We don't need generic cycle detection when rewriting component assemblies
genericCycleBreadthCutoff: -1); // as we're not actually compiling anything

IComparer<DependencyNodeCore<NodeFactory>> comparer = new SortableDependencyNode.ObjectNodeComparer(CompilerComparer.Instance);
DependencyAnalyzerBase<NodeFactory> componentGraph = new DependencyAnalyzer<NoLogStrategy<NodeFactory>, NodeFactory>(componentFactory, comparer);
Expand Down
Loading