diff --git a/src/coreclr/tools/Common/Compiler/GenericCycleDetection/GenericCycleDetection.projitems b/src/coreclr/tools/Common/Compiler/GenericCycleDetection/GenericCycleDetection.projitems
new file mode 100644
index 00000000000000..e0cee3bca578ff
--- /dev/null
+++ b/src/coreclr/tools/Common/Compiler/GenericCycleDetection/GenericCycleDetection.projitems
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/LazyGenerics/Graph.Cycles.cs b/src/coreclr/tools/Common/Compiler/GenericCycleDetection/Graph.Cycles.cs
similarity index 100%
rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/LazyGenerics/Graph.Cycles.cs
rename to src/coreclr/tools/Common/Compiler/GenericCycleDetection/Graph.Cycles.cs
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/LazyGenerics/Graph.Vertex.cs b/src/coreclr/tools/Common/Compiler/GenericCycleDetection/Graph.Vertex.cs
similarity index 100%
rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/LazyGenerics/Graph.Vertex.cs
rename to src/coreclr/tools/Common/Compiler/GenericCycleDetection/Graph.Vertex.cs
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/LazyGenerics/Graph.cs b/src/coreclr/tools/Common/Compiler/GenericCycleDetection/Graph.cs
similarity index 100%
rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/LazyGenerics/Graph.cs
rename to src/coreclr/tools/Common/Compiler/GenericCycleDetection/Graph.cs
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/LazyGenerics/GraphBuilder.ForEach.cs b/src/coreclr/tools/Common/Compiler/GenericCycleDetection/GraphBuilder.ForEach.cs
similarity index 100%
rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/LazyGenerics/GraphBuilder.ForEach.cs
rename to src/coreclr/tools/Common/Compiler/GenericCycleDetection/GraphBuilder.ForEach.cs
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/LazyGenerics/GraphBuilder.MethodCall.cs b/src/coreclr/tools/Common/Compiler/GenericCycleDetection/GraphBuilder.MethodCall.cs
similarity index 100%
rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/LazyGenerics/GraphBuilder.MethodCall.cs
rename to src/coreclr/tools/Common/Compiler/GenericCycleDetection/GraphBuilder.MethodCall.cs
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/LazyGenerics/GraphBuilder.cs b/src/coreclr/tools/Common/Compiler/GenericCycleDetection/GraphBuilder.cs
similarity index 100%
rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/LazyGenerics/GraphBuilder.cs
rename to src/coreclr/tools/Common/Compiler/GenericCycleDetection/GraphBuilder.cs
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/LazyGenerics/ModuleCycleInfo.cs b/src/coreclr/tools/Common/Compiler/GenericCycleDetection/ModuleCycleInfo.cs
similarity index 82%
rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/LazyGenerics/ModuleCycleInfo.cs
rename to src/coreclr/tools/Common/Compiler/GenericCycleDetection/ModuleCycleInfo.cs
index 205578477032a4..b5a81581c58a05 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/LazyGenerics/ModuleCycleInfo.cs
+++ b/src/coreclr/tools/Common/Compiler/GenericCycleDetection/ModuleCycleInfo.cs
@@ -8,7 +8,9 @@
using Internal.TypeSystem;
using Internal.TypeSystem.Ecma;
+#if !READYTORUN
using ILLink.Shared;
+#endif
using Debug = System.Diagnostics.Debug;
@@ -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 _actualProblems = new ConcurrentDictionary();
- 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)
@@ -110,17 +114,31 @@ private bool IsDeepPossiblyCyclicInstantiation(TypeSystemEntity entity)
}
}
- private bool IsDeepPossiblyCyclicInstantiation(TypeDesc type, List 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 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()).Add(typeDef);
for (int i = 0; i < seenTypes.Count; i++)
{
@@ -133,14 +151,14 @@ private bool IsDeepPossiblyCyclicInstantiation(TypeDesc type, List 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;
}
@@ -148,11 +166,11 @@ private bool IsDeepPossiblyCyclicInstantiation(TypeDesc type, List see
}
}
- private bool IsDeepPossiblyCyclicInstantiation(Instantiation instantiation, List seenTypes = null)
+ private bool IsDeepPossiblyCyclicInstantiation(Instantiation instantiation, ref int breadthCounter, List seenTypes)
{
foreach (TypeDesc arg in instantiation)
{
- if (IsDeepPossiblyCyclicInstantiation(arg, seenTypes))
+ if (IsDeepPossiblyCyclicInstantiation(arg, ref breadthCounter, seenTypes))
{
return true;
}
@@ -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
@@ -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.
@@ -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.
@@ -252,6 +285,7 @@ public void LogWarnings(Logger logger)
logger.LogWarning(actualProblem.Key.Owner, DiagnosticId.GenericRecursionCycle, actualProblem.Key.Referent.GetDisplayName(), message);
}
}
+#endif
}
}
}
diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
index 30f0b145a18641..32a0b0c8c3db51 100644
--- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
+++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
@@ -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);
}
}
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.Aot.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.Aot.cs
index a022eb556e4d69..87ee25d892f0d9 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.Aot.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.Aot.cs
@@ -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
{
@@ -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;
@@ -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();
}
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
index e0bce1b1eb82c7..1822619c8a423a 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
@@ -438,13 +438,6 @@
-
-
-
-
-
-
-
@@ -711,4 +704,6 @@
+
+
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/AllMethodsOnTypeNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/AllMethodsOnTypeNode.cs
index 1585861242d2a4..a89a3115cb102a 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/AllMethodsOnTypeNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/AllMethodsOnTypeNode.cs
@@ -40,7 +40,14 @@ public override IEnumerable 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)
+ {
+ }
}
}
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelayLoadHelperMethodImport.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelayLoadHelperMethodImport.cs
index d034ba56d87b2f..eb996de2ac2b0a 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelayLoadHelperMethodImport.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelayLoadHelperMethodImport.cs
@@ -48,8 +48,20 @@ public override IEnumerable 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");
+ }
}
}
}
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs
index e3a3d3a0708118..792430ac675eb4 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs
@@ -58,7 +58,14 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact
factory.CompilationModuleGroup.ContainsMethodBody(canonMethod, false))
{
list = list ?? new DependencyAnalysisFramework.DependencyNodeCore.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;
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeFixupSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeFixupSignature.cs
index e379651ff5e2cb..a32a1a11f5c5f9 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeFixupSignature.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeFixupSignature.cs
@@ -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;
}
}
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs
index 3046ad95a29891..2206340494b51b 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs
@@ -188,7 +188,9 @@ public NodeFactory(
ResourceData win32Resources,
ReadyToRunFlags flags,
NodeFactoryOptimizationFlags nodeFactoryOptimizationFlags,
- ulong imageBase)
+ ulong imageBase,
+ int genericCycleDepthCutoff,
+ int genericCycleBreadthCutoff)
{
OptimizationFlags = nodeFactoryOptimizationFlags;
TypeSystemContext = context;
@@ -216,6 +218,13 @@ public NodeFactory(
}
CreateNodeCaches();
+
+ if (genericCycleBreadthCutoff >= 0 || genericCycleDepthCutoff >= 0)
+ {
+ _genericCycleDetector = new LazyGenericsSupport.GenericCycleDetector(
+ depthCutoff: genericCycleDepthCutoff,
+ breadthCutoff: genericCycleBreadthCutoff);
+ }
}
private void CreateNodeCaches()
@@ -389,6 +398,8 @@ private void CreateNodeCaches()
private NodeCache _constructedHelpers;
+ private LazyGenericsSupport.GenericCycleDetector _genericCycleDetector;
+
public Import GetReadyToRunHelperCell(ReadyToRunHelper helperId)
{
return _constructedHelpers.GetOrAdd(helperId);
@@ -1010,5 +1021,10 @@ public CopiedManagedResourcesNode CopiedManagedResources(EcmaModule module)
{
return _copiedManagedResources.GetOrAdd(module);
}
+
+ public void DetectGenericCycles(TypeSystemEntity caller, TypeSystemEntity callee)
+ {
+ _genericCycleDetector?.DetectCycle(caller, callee);
+ }
}
}
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs
index 09b786b1a7bf24..e975df9df63b3d 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs
@@ -106,6 +106,8 @@ public bool CanInline(MethodDesc caller, MethodDesc callee)
}
}
+ _nodeFactory.DetectGenericCycles(caller, callee);
+
return NodeFactory.CompilationModuleGroup.CanInline(caller, callee);
}
@@ -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> comparer = new SortableDependencyNode.ObjectNodeComparer(CompilerComparer.Instance);
DependencyAnalyzerBase componentGraph = new DependencyAnalyzer, NodeFactory>(componentFactory, comparer);
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs
index 573eacc1fcc4cf..ae40f3e19ae5cc 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs
@@ -42,6 +42,8 @@ public sealed class ReadyToRunCodegenCompilationBuilder : CompilationBuilder
private CompositeImageSettings _compositeImageSettings;
private ulong _imageBase;
private NodeFactoryOptimizationFlags _nodeFactoryOptimizationFlags = new NodeFactoryOptimizationFlags();
+ private int _genericCycleDetectionDepthCutoff = -1;
+ private int _genericCycleDetectionBreadthCutoff = -1;
private string _jitPath;
private string _outputFile;
@@ -210,6 +212,13 @@ public ReadyToRunCodegenCompilationBuilder UseNodeFactoryOptimizationFlags(NodeF
return this;
}
+ public ReadyToRunCodegenCompilationBuilder UseGenericCycleDetection(int depthCutoff, int breadthCutoff)
+ {
+ _genericCycleDetectionDepthCutoff = depthCutoff;
+ _genericCycleDetectionBreadthCutoff = breadthCutoff;
+ return this;
+ }
+
public override ICompilation ToCompilation()
{
// TODO: only copy COR headers for single-assembly build and for composite build with embedded MSIL
@@ -254,7 +263,9 @@ public override ICompilation ToCompilation()
win32Resources,
flags,
_nodeFactoryOptimizationFlags,
- _imageBase
+ _imageBase,
+ genericCycleDepthCutoff: _genericCycleDetectionDepthCutoff,
+ genericCycleBreadthCutoff: _genericCycleDetectionBreadthCutoff
);
factory.CompositeImageSettings = _compositeImageSettings;
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs
index 27d0a9798b0d61..265301f755d76f 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs
@@ -418,6 +418,7 @@ public sealed override bool CanInline(MethodDesc callerMethod, MethodDesc callee
if (CorInfoImpl.ShouldCodeNotBeCompiledIntoFinalImage(_instructionSetSupport, calleeMethod))
canInline = false;
}
+
return canInline;
}
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilerContext.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilerContext.cs
index c6d40d4ee7b42b..3fc37c63c8fa61 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilerContext.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilerContext.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
-
using Internal.TypeSystem;
using Debug = System.Diagnostics.Debug;
@@ -32,13 +31,31 @@ internal DefType GetClosestDefType(TypeDesc type)
public partial class ReadyToRunCompilerContext : CompilerTypeSystemContext
{
+ // Depth cutoff specifies the number of repetitions of a particular generic type within a type instantiation
+ // to trigger marking the type as potentially cyclic. Considering a generic type CyclicType`1 marked as
+ // cyclic by the initial module analysis, for instance CyclicType`1>> has "depth 3"
+ // so it will be cut off by specifying anything less than or equal to three.
+ public const int DefaultGenericCycleDepthCutoff = 4;
+
+ // Breadth cutoff specifies the minimum total number of generic types identified as potentially cyclic
+ // that must appear within a type instantiation to mark it as potentially cyclic. Considering generic types
+ // CyclicA`1, CyclicB`1 and CyclicC`1 marked as cyclic by the initial module analysis, a hypothetical type
+ // SomeType`3, List`1>, IEnumerable`1>>>
+ // will have "breadth 3" and will be cut off by specifying anything less than or equal to three.
+ public const int DefaultGenericCycleBreadthCutoff = 2;
+
private ReadyToRunMetadataFieldLayoutAlgorithm _r2rFieldLayoutAlgorithm;
private SystemObjectFieldLayoutAlgorithm _systemObjectFieldLayoutAlgorithm;
private VectorOfTFieldLayoutAlgorithm _vectorOfTFieldLayoutAlgorithm;
private VectorFieldLayoutAlgorithm _vectorFieldLayoutAlgorithm;
private Int128FieldLayoutAlgorithm _int128FieldLayoutAlgorithm;
- public ReadyToRunCompilerContext(TargetDetails details, SharedGenericsMode genericsMode, bool bubbleIncludesCorelib, InstructionSetSupport instructionSetSupport, CompilerTypeSystemContext oldTypeSystemContext = null)
+ public ReadyToRunCompilerContext(
+ TargetDetails details,
+ SharedGenericsMode genericsMode,
+ bool bubbleIncludesCorelib,
+ InstructionSetSupport instructionSetSupport,
+ CompilerTypeSystemContext oldTypeSystemContext)
: base(details, genericsMode)
{
InstructionSetSupport = instructionSetSupport;
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj
index 3bb44554265c6c..5ca06051a9eea7 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj
@@ -30,6 +30,8 @@
+
+
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs
index 1e6ed986c944b8..de3a6c1866e6cf 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs
@@ -865,12 +865,14 @@ private bool getReadyToRunHelper(ref CORINFO_RESOLVED_TOKEN pResolvedToken, ref
if (helperId == ReadyToRunHelperId.MethodEntry && pGenericLookupKind.runtimeLookupArgs != null)
{
constrainedType = (TypeDesc)GetRuntimeDeterminedObjectForToken(ref *(CORINFO_RESOLVED_TOKEN*)pGenericLookupKind.runtimeLookupArgs);
+ _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, constrainedType);
}
object helperArg = GetRuntimeDeterminedObjectForToken(ref pResolvedToken);
if (helperArg is MethodDesc methodDesc)
{
var methodIL = HandleToObject(pResolvedToken.tokenScope);
MethodDesc sharedMethod = methodIL.OwningMethod.GetSharedRuntimeFormMethodTarget();
+ _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, sharedMethod);
helperArg = new MethodWithToken(methodDesc, HandleToModuleToken(ref pResolvedToken), constrainedType, unboxing: false, context: sharedMethod);
}
else if (helperArg is FieldDesc fieldDesc)
@@ -2254,6 +2256,11 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO
out callerModule,
out useInstantiatingStub);
+ if (callerMethod.HasInstantiation || callerMethod.OwningType.HasInstantiation)
+ {
+ _compilation.NodeFactory.DetectGenericCycles(callerMethod, methodToCall);
+ }
+
if (pResult->thisTransform == CORINFO_THIS_TRANSFORM.CORINFO_BOX_THIS)
{
// READYTORUN: FUTURE: Optionally create boxing stub at runtime
diff --git a/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs b/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs
index 574bfb78c461ca..743192ffbeba0d 100644
--- a/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs
+++ b/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs
@@ -138,8 +138,10 @@ internal sealed class ILCompilerRootCommand : RootCommand
new(new[] { "--directpinvoke" }, Array.Empty, "PInvoke to call directly");
public Option DirectPInvokeLists { get; } =
new(new[] { "--directpinvokelist" }, Array.Empty, "File with list of PInvokes to call directly");
- public Option MaxGenericCycle { get; } =
- new(new[] { "--maxgenericcycle" }, () => CompilerTypeSystemContext.DefaultGenericCycleCutoffPoint, "Max depth of generic cycle");
+ public Option MaxGenericCycleDepth { get; } =
+ new(new[] { "--maxgenericcycle" }, () => CompilerTypeSystemContext.DefaultGenericCycleDepthCutoff, "Max depth of generic cycle");
+ public Option MaxGenericCycleBreadth { get; } =
+ new(new[] { "--maxgenericcyclebreadth" }, () => CompilerTypeSystemContext.DefaultGenericCycleBreadthCutoff, "Max breadth of generic cycle expansion");
public Option RootedAssemblies { get; } =
new(new[] { "--root" }, Array.Empty, "Fully generate given assembly");
public Option> ConditionallyRootedAssemblies { get; } =
@@ -225,7 +227,8 @@ public ILCompilerRootCommand(string[] args) : base(".NET Native IL Compiler")
AddOption(SingleWarnDisabledAssemblies);
AddOption(DirectPInvokes);
AddOption(DirectPInvokeLists);
- AddOption(MaxGenericCycle);
+ AddOption(MaxGenericCycleDepth);
+ AddOption(MaxGenericCycleBreadth);
AddOption(RootedAssemblies);
AddOption(ConditionallyRootedAssemblies);
AddOption(TrimmedAssemblies);
diff --git a/src/coreclr/tools/aot/ILCompiler/Program.cs b/src/coreclr/tools/aot/ILCompiler/Program.cs
index 9f0af5d90d3c12..5c54516e45dd0e 100644
--- a/src/coreclr/tools/aot/ILCompiler/Program.cs
+++ b/src/coreclr/tools/aot/ILCompiler/Program.cs
@@ -92,7 +92,9 @@ public int Run()
var targetAbi = TargetAbi.NativeAot;
var targetDetails = new TargetDetails(targetArchitecture, targetOS, targetAbi, simdVectorLength);
CompilerTypeSystemContext typeSystemContext =
- new CompilerTypeSystemContext(targetDetails, genericsMode, supportsReflection ? DelegateFeature.All : 0, Get(_command.MaxGenericCycle));
+ new CompilerTypeSystemContext(targetDetails, genericsMode, supportsReflection ? DelegateFeature.All : 0,
+ genericCycleDepthCutoff: Get(_command.MaxGenericCycleDepth),
+ genericCycleBreadthCutoff: Get(_command.MaxGenericCycleBreadth));
//
// TODO: To support our pre-compiled test tree, allow input files that aren't managed assemblies since
diff --git a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerDriver.cs b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerDriver.cs
index 16d2e42c1fe4f7..7542979d71cb02 100644
--- a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerDriver.cs
+++ b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerDriver.cs
@@ -25,7 +25,7 @@ public ILScanResults Trim (ILCompilerOptions options, ILogWriter logWriter)
ComputeDefaultOptions (out var targetOS, out var targetArchitecture);
var targetDetails = new TargetDetails (targetArchitecture, targetOS, TargetAbi.NativeAot);
CompilerTypeSystemContext typeSystemContext =
- new CompilerTypeSystemContext (targetDetails, SharedGenericsMode.CanonicalReferenceTypes, DelegateFeature.All, genericCycleCutoffPoint: -1);
+ new CompilerTypeSystemContext (targetDetails, SharedGenericsMode.CanonicalReferenceTypes, DelegateFeature.All, genericCycleDepthCutoff: -1);
typeSystemContext.InputFilePaths = options.InputFilePaths;
typeSystemContext.ReferenceFilePaths = options.ReferenceFilePaths;
diff --git a/src/coreclr/tools/aot/crossgen2.sln b/src/coreclr/tools/aot/crossgen2.sln
index ee4d3a0973f014..1f7ac88c4db6a6 100644
--- a/src/coreclr/tools/aot/crossgen2.sln
+++ b/src/coreclr/tools/aot/crossgen2.sln
@@ -87,10 +87,10 @@ Global
{3EACD929-4725-4173-A845-734936BBDF87}.Debug|x86.Build.0 = Debug|Any CPU
{3EACD929-4725-4173-A845-734936BBDF87}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3EACD929-4725-4173-A845-734936BBDF87}.Release|Any CPU.Build.0 = Release|Any CPU
- {3EACD929-4725-4173-A845-734936BBDF87}.Release|x64.ActiveCfg = Release|Any CPU
- {3EACD929-4725-4173-A845-734936BBDF87}.Release|x64.Build.0 = Release|Any CPU
- {3EACD929-4725-4173-A845-734936BBDF87}.Release|x86.ActiveCfg = Release|Any CPU
- {3EACD929-4725-4173-A845-734936BBDF87}.Release|x86.Build.0 = Release|Any CPU
+ {3EACD929-4725-4173-A845-734936BBDF87}.Release|x64.ActiveCfg = Release|x64
+ {3EACD929-4725-4173-A845-734936BBDF87}.Release|x64.Build.0 = Release|x64
+ {3EACD929-4725-4173-A845-734936BBDF87}.Release|x86.ActiveCfg = Release|x86
+ {3EACD929-4725-4173-A845-734936BBDF87}.Release|x86.Build.0 = Release|x86
{0BB34BA1-1B3A-445C-9C04-0D710D1983F0}.Checked|Any CPU.ActiveCfg = Debug|Any CPU
{0BB34BA1-1B3A-445C-9C04-0D710D1983F0}.Checked|Any CPU.Build.0 = Debug|Any CPU
{0BB34BA1-1B3A-445C-9C04-0D710D1983F0}.Checked|x64.ActiveCfg = Debug|x64
diff --git a/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs b/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs
index 85d6b163ef4e8e..259975bb89886b 100644
--- a/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs
+++ b/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs
@@ -77,6 +77,12 @@ internal class Crossgen2RootCommand : RootCommand
new(new[] { "--resilient" }, SR.ResilientOption);
public Option ImageBase { get; } =
new(new[] { "--imagebase" }, SR.ImageBase);
+ public Option EnableGenericCycleDetection { get; } =
+ new(new[] { "--enable-generic-cycle-detection" }, SR.EnableGenericCycleDetection);
+ public Option GenericCycleDepthCutoff { get; } =
+ new(new[] { "--maxgenericcycle" }, () => ReadyToRunCompilerContext.DefaultGenericCycleDepthCutoff, SR.GenericCycleDepthCutoff);
+ public Option GenericCycleBreadthCutoff { get; } =
+ new(new[] { "--maxgenericcyclebreadth" }, () => ReadyToRunCompilerContext.DefaultGenericCycleBreadthCutoff, SR.GenericCycleBreadthCutoff);
public Option TargetArchitecture { get; } =
new(new[] { "--targetarch" }, result =>
{
@@ -225,6 +231,9 @@ public Crossgen2RootCommand(string[] args) : base(SR.Crossgen2BannerText)
AddOption(SupportIbc);
AddOption(Resilient);
AddOption(ImageBase);
+ AddOption(EnableGenericCycleDetection);
+ AddOption(GenericCycleDepthCutoff);
+ AddOption(GenericCycleBreadthCutoff);
AddOption(TargetArchitecture);
AddOption(TargetOS);
AddOption(JitPath);
diff --git a/src/coreclr/tools/aot/crossgen2/Program.cs b/src/coreclr/tools/aot/crossgen2/Program.cs
index 1546def4619819..4b522a38688670 100644
--- a/src/coreclr/tools/aot/crossgen2/Program.cs
+++ b/src/coreclr/tools/aot/crossgen2/Program.cs
@@ -127,7 +127,9 @@ public int Run()
//
// Initialize type system context
//
- _typeSystemContext = new ReadyToRunCompilerContext(targetDetails, genericsMode, versionBubbleIncludesCoreLib, instructionSetSupport);
+ _typeSystemContext = new ReadyToRunCompilerContext(targetDetails, genericsMode, versionBubbleIncludesCoreLib,
+ instructionSetSupport,
+ oldTypeSystemContext: null);
string compositeRootPath = Get(_command.CompositeRootPath);
@@ -269,7 +271,9 @@ public int Run()
{
bool singleCompilationVersionBubbleIncludesCoreLib = versionBubbleIncludesCoreLib || (String.Compare(inputFile.Key, "System.Private.CoreLib", StringComparison.OrdinalIgnoreCase) == 0);
- typeSystemContext = new ReadyToRunCompilerContext(targetDetails, genericsMode, singleCompilationVersionBubbleIncludesCoreLib, _typeSystemContext.InstructionSetSupport, _typeSystemContext);
+ typeSystemContext = new ReadyToRunCompilerContext(targetDetails, genericsMode, singleCompilationVersionBubbleIncludesCoreLib,
+ _typeSystemContext.InstructionSetSupport,
+ _typeSystemContext);
typeSystemContext.InputFilePaths = singleCompilationInputFilePaths;
typeSystemContext.ReferenceFilePaths = referenceFilePaths;
typeSystemContext.SetSystemModule((EcmaModule)typeSystemContext.GetModuleForSimpleName(systemModuleName));
@@ -617,6 +621,13 @@ private void RunSingleCompilation(Dictionary inFilePaths, Instru
.UseCompilationRoots(compilationRoots)
.UseOptimizationMode(optimizationMode);
+ if (Get(_command.EnableGenericCycleDetection))
+ {
+ builder.UseGenericCycleDetection(
+ depthCutoff: Get(_command.GenericCycleDepthCutoff),
+ breadthCutoff: Get(_command.GenericCycleBreadthCutoff));
+ }
+
builder.UsePrintReproInstructions(CreateReproArgumentString);
compilation = builder.ToCompilation();
diff --git a/src/coreclr/tools/aot/crossgen2/Properties/Resources.resx b/src/coreclr/tools/aot/crossgen2/Properties/Resources.resx
index a737ea6aeb7706..584ae258c86b54 100644
--- a/src/coreclr/tools/aot/crossgen2/Properties/Resources.resx
+++ b/src/coreclr/tools/aot/crossgen2/Properties/Resources.resx
@@ -1,17 +1,17 @@
-
+
-
@@ -402,4 +402,13 @@
.NET Crossgen2 Compiler
-
+
+ Perform generic cycle detection during compilation (incurs longer compilation time)
+
+
+ Total number of occurrences of potentially cyclic generic types within a generic type to cut off
+
+
+ Number of nested occurrences of a potentially cyclic generic type to cut off
+
+
\ No newline at end of file
diff --git a/src/tests/readytorun/GenericCycleDetection/Breadth1Test.cs b/src/tests/readytorun/GenericCycleDetection/Breadth1Test.cs
new file mode 100644
index 00000000000000..08a776381fd226
--- /dev/null
+++ b/src/tests/readytorun/GenericCycleDetection/Breadth1Test.cs
@@ -0,0 +1,203 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Runtime.CompilerServices;
+using Xunit;
+
+internal class Program
+{
+ // This test exercises the "breadth cutoff" parameter of generic cycle detector.
+ // It mimics generic representation of expressions.
+ private struct Expression
+ {
+ public static int Construct(int seed)
+ {
+ if (--seed <= 0) return 10;
+ return seed switch
+ {
+ 1 => Assignment.Construct(seed),
+ 2 => Assignment, T>.Construct(seed),
+ 3 => Assignment>.Construct(seed),
+ _ => Assignment, Expression>.Construct(seed)
+ };
+ }
+ }
+
+ private struct Assignment
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => Conditional.Construct(seed),
+ 2 => Conditional, T>.Construct(seed),
+ 3 => Conditional>.Construct(seed),
+ _ => Conditional, Assignment>.Construct(seed)
+ };
+ }
+ }
+
+ private struct Conditional
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => LogicalOr.Construct(seed),
+ 2 => LogicalOr, T>.Construct(seed),
+ 3 => LogicalOr>.Construct(seed),
+ _ => LogicalOr, Conditional>.Construct(seed)
+ };
+ }
+ }
+
+ private struct LogicalOr
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => LogicalAnd.Construct(seed),
+ 2 => LogicalAnd, T>.Construct(seed),
+ 3 => LogicalAnd>.Construct(seed),
+ _ => LogicalAnd, LogicalOr>.Construct(seed)
+ };
+ }
+ }
+
+ private struct LogicalAnd
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => BitwiseOr.Construct(seed),
+ 2 => BitwiseOr, T>.Construct(seed),
+ 3 => BitwiseOr>.Construct(seed),
+ _ => BitwiseOr, LogicalAnd>.Construct(seed),
+ };
+ }
+ }
+
+ private struct BitwiseOr
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => BitwiseAnd.Construct(seed),
+ 2 => BitwiseAnd, T>.Construct(seed),
+ 3 => BitwiseAnd>.Construct(seed),
+ _ => BitwiseAnd, BitwiseOr>.Construct(seed)
+ };
+ }
+ }
+
+ private struct BitwiseAnd
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => Equality.Construct(seed),
+ 2 => Equality, T>.Construct(seed),
+ 3 => Equality>.Construct(seed),
+ _ => Equality, BitwiseAnd>.Construct(seed)
+ };
+ }
+ }
+
+ private struct Equality
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => Comparison.Construct(seed),
+ 2 => Comparison, T>.Construct(seed),
+ 3 => Comparison>.Construct(seed),
+ _ => Comparison, Equality>.Construct(seed)
+ };
+ }
+ }
+
+ private struct Comparison
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => BitwiseShift.Construct(seed),
+ 2 => BitwiseShift, T>.Construct(seed),
+ 3 => BitwiseShift>.Construct(seed),
+ _ => BitwiseShift, Comparison>.Construct(seed)
+ };
+ }
+ }
+
+ private struct BitwiseShift
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => Addition.Construct(seed),
+ 2 => Addition, T>.Construct(seed),
+ 3 => Addition>.Construct(seed),
+ _ => Addition, BitwiseShift>.Construct(seed)
+ };
+ }
+ }
+
+ private struct Addition
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => Multiplication.Construct(seed),
+ 2 => Multiplication, T>.Construct(seed),
+ 3 => Multiplication>.Construct(seed),
+ _ => Multiplication, Addition>.Construct(seed)
+ };
+ }
+ }
+
+ private struct Multiplication
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => Nested.Construct(seed),
+ 2 => Nested, T>.Construct(seed),
+ 3 => Nested>.Construct(seed),
+ _ => Nested, Multiplication>.Construct(seed)
+ };
+ }
+ }
+
+ private struct Nested
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => Expression.Construct(seed),
+ 2 => Expression, T>.Construct(seed),
+ 3 => Expression>.Construct(seed),
+ _ => Expression, Nested>.Construct(seed)
+ };
+ }
+ }
+
+ [Fact]
+ public static int BreadthTest()
+ {
+ return Expression.Construct(2) * Expression.Construct(2);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static int ReturnTwoAndDontTellJIT() => 2;
+}
diff --git a/src/tests/readytorun/GenericCycleDetection/Breadth1Test.csproj b/src/tests/readytorun/GenericCycleDetection/Breadth1Test.csproj
new file mode 100644
index 00000000000000..31b228022a9765
--- /dev/null
+++ b/src/tests/readytorun/GenericCycleDetection/Breadth1Test.csproj
@@ -0,0 +1,13 @@
+
+
+ exe
+ true
+
+ true
+
+ --enable-generic-cycle-detection --maxgenericcycle:1 --maxgenericcyclebreadth:1
+
+
+
+
+
diff --git a/src/tests/readytorun/GenericCycleDetection/Depth1Test.cs b/src/tests/readytorun/GenericCycleDetection/Depth1Test.cs
new file mode 100644
index 00000000000000..2e20735c53c1d1
--- /dev/null
+++ b/src/tests/readytorun/GenericCycleDetection/Depth1Test.cs
@@ -0,0 +1,45 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Runtime.CompilerServices;
+using Xunit;
+
+internal class Program
+{
+ // This test only works when the "depth cutoff" parameter of generic cycle detector
+ // is reduced to 1, otherwise the combinatorical explosion overflows the code generator.
+ private struct Breadth
+ {
+ public static long TypeNestedFactorial(int count)
+ {
+ if (count <= 1)
+ {
+ return 1;
+ }
+ long result = 0;
+ if (result < count) result = Breadth>.TypeNestedFactorial(count - 1);
+ if (result < count) result = Breadth>.TypeNestedFactorial(count - 1);
+ if (result < count) result = Breadth>.TypeNestedFactorial(count - 1);
+ if (result < count) result = Breadth>.TypeNestedFactorial(count - 1);
+ if (result < count) result = Breadth>.TypeNestedFactorial(count - 1);
+ return count * result;
+ }
+ }
+
+ private struct Oper1 {}
+ private struct Oper2 {}
+ private struct Oper3 {}
+ private struct Oper4 {}
+ private struct Oper5 {}
+
+ [Fact]
+ public static int BreadthTest()
+ {
+ const long Factorial20 = 20L * 19L * 18L * 17L * 16L * 15L * 14L * 13L * 12L * 11L * 10L * 9L * 8L * 7L * 6L * 5L * 4L * 3L * 2L;
+ return Breadth.TypeNestedFactorial(ReturnTwentyAndDontTellJIT()) == Factorial20 ? 100 : 101;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static int ReturnTwentyAndDontTellJIT() => 20;
+}
diff --git a/src/tests/readytorun/GenericCycleDetection/Depth1Test.csproj b/src/tests/readytorun/GenericCycleDetection/Depth1Test.csproj
new file mode 100644
index 00000000000000..2c0617a43c5d9c
--- /dev/null
+++ b/src/tests/readytorun/GenericCycleDetection/Depth1Test.csproj
@@ -0,0 +1,13 @@
+
+
+ exe
+ true
+
+ true
+
+ --enable-generic-cycle-detection --maxgenericcycle:1 --maxgenericcyclebreadth:-1
+
+
+
+
+
diff --git a/src/tests/readytorun/GenericCycleDetection/Depth3Test.cs b/src/tests/readytorun/GenericCycleDetection/Depth3Test.cs
new file mode 100644
index 00000000000000..2edaef57d2681c
--- /dev/null
+++ b/src/tests/readytorun/GenericCycleDetection/Depth3Test.cs
@@ -0,0 +1,45 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Runtime.CompilerServices;
+using Xunit;
+
+internal class Program
+{
+ // This test exercises the "depth cutoff" parameter of generic cycle detector.
+ // It is a simple algorithm generating deep generic types of the form
+ // Depth1`1>>>
+ private struct Depth1
+ {
+ public static long TypeNestedFactorial(int count)
+ {
+ if (count <= 1)
+ {
+ return 1;
+ }
+ long result = 0;
+ if (result < count) result = Depth1>.TypeNestedFactorial(count - 1);
+ if (result < count) result = Depth1>.TypeNestedFactorial(count - 1);
+ if (result < count) result = Depth1>.TypeNestedFactorial(count - 1);
+ if (result < count) result = Depth1>.TypeNestedFactorial(count - 1);
+ if (result < count) result = Depth1>.TypeNestedFactorial(count - 1);
+ return count * result;
+ }
+ }
+
+ private struct Depth2 {}
+ private struct Depth3 {}
+ private struct Depth4 {}
+ private struct Depth5 {}
+
+ [Fact]
+ public static int DepthTest()
+ {
+ const long Factorial20 = 20L * 19L * 18L * 17L * 16L * 15L * 14L * 13L * 12L * 11L * 10L * 9L * 8L * 7L * 6L * 5L * 4L * 3L * 2L;
+ return Depth1.TypeNestedFactorial(ReturnTwentyAndDontTellJIT()) == Factorial20 ? 100 : 101;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static int ReturnTwentyAndDontTellJIT() => 20;
+}
diff --git a/src/tests/readytorun/GenericCycleDetection/Depth3Test.csproj b/src/tests/readytorun/GenericCycleDetection/Depth3Test.csproj
new file mode 100644
index 00000000000000..9f6f94744e31e4
--- /dev/null
+++ b/src/tests/readytorun/GenericCycleDetection/Depth3Test.csproj
@@ -0,0 +1,15 @@
+
+
+ exe
+ true
+
+ true
+
+ --enable-generic-cycle-detection --maxgenericcycle:3 --maxgenericcyclebreadth:-1
+
+ true
+
+
+
+
+