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 + + + + +