--- /dev/null
+<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>
using Internal.TypeSystem;
using Internal.TypeSystem.Ecma;
+#if !READYTORUN
using ILLink.Shared;
+#endif
using Debug = System.Diagnostics.Debug;
// 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)
}
}
- 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++)
{
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;
}
}
}
- 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;
}
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
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.
}
}
+#if !READYTORUN
public void LogWarnings(Logger logger)
{
// Might need to sort these if we care about warning determinism, but we probably don't.
logger.LogWarning(actualProblem.Key.Owner, DiagnosticId.GenericRecursionCycle, actualProblem.Key.Referent.GetDisplayName(), message);
}
}
+#endif
}
}
}
{
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);
}
}
// 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
{
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;
_delegateInfoHashtable = new DelegateInfoHashtable(delegateFeatures);
- _genericCycleDetector = new LazyGenericsSupport.GenericCycleDetector(genericCycleCutoffPoint);
+ _genericCycleDetector = new LazyGenericsSupport.GenericCycleDetector(genericCycleDepthCutoff, genericCycleBreadthCutoff);
GenericsConfig = new SharedGenericsConfiguration();
}
<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" />
</ItemGroup>
<Import Project="..\..\..\..\tools\illink\src\ILLink.Shared\ILLink.Shared.projitems" Label="Shared" />
+ <Import Project="..\..\Common\Compiler\GenericCycleDetection\GenericCycleDetection.projitems" />
+
</Project>
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)
+ {
+ }
}
}
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");
+ }
}
}
}
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;
{
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;
}
}
ResourceData win32Resources,
ReadyToRunFlags flags,
NodeFactoryOptimizationFlags nodeFactoryOptimizationFlags,
- ulong imageBase)
+ ulong imageBase,
+ int genericCycleDepthCutoff,
+ int genericCycleBreadthCutoff)
{
OptimizationFlags = nodeFactoryOptimizationFlags;
TypeSystemContext = context;
}
CreateNodeCaches();
+
+ if (genericCycleBreadthCutoff >= 0 || genericCycleDepthCutoff >= 0)
+ {
+ _genericCycleDetector = new LazyGenericsSupport.GenericCycleDetector(
+ depthCutoff: genericCycleDepthCutoff,
+ breadthCutoff: genericCycleBreadthCutoff);
+ }
}
private void CreateNodeCaches()
private NodeCache<ReadyToRunHelper, Import> _constructedHelpers;
+ private LazyGenericsSupport.GenericCycleDetector _genericCycleDetector;
+
public Import GetReadyToRunHelperCell(ReadyToRunHelper helperId)
{
return _constructedHelpers.GetOrAdd(helperId);
{
return _copiedManagedResources.GetOrAdd(module);
}
+
+ public void DetectGenericCycles(TypeSystemEntity caller, TypeSystemEntity callee)
+ {
+ _genericCycleDetector?.DetectCycle(caller, callee);
+ }
}
}
}
}
+ _nodeFactory.DetectGenericCycles(caller, callee);
+
return NodeFactory.CompilationModuleGroup.CanInline(caller, callee);
}
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);
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;
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
win32Resources,
flags,
_nodeFactoryOptimizationFlags,
- _imageBase
+ _imageBase,
+ genericCycleDepthCutoff: _genericCycleDetectionDepthCutoff,
+ genericCycleBreadthCutoff: _genericCycleDetectionBreadthCutoff
);
factory.CompositeImageSettings = _compositeImageSettings;
if (CorInfoImpl.ShouldCodeNotBeCompiledIntoFinalImage(_instructionSetSupport, calleeMethod))
canInline = false;
}
+
return canInline;
}
using System;
using System.Collections.Generic;
-
using Internal.TypeSystem;
using Debug = System.Diagnostics.Debug;
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<T> marked as
+ // cyclic by the initial module analysis, for instance CyclicType`1<CyclicType`1<CyclicType`1<__Canon>>> 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<CyclicA`1<__Canon>, List`1<CyclicB`1<__Canon>>, IEnumerable`1<HashSet`1<CyclicC`1<__Canon>>>>
+ // 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;
</PackageReference>
</ItemGroup>
+ <Import Project="..\..\Common\Compiler\GenericCycleDetection\GenericCycleDetection.projitems" />
+
<ItemGroup>
<Compile Include="..\..\Common\Internal\Runtime\CorConstants.cs" Link="Common\CorConstants.cs" />
<Compile Include="..\..\Common\Internal\Runtime\ReadyToRunConstants.cs" Link="Common\ReadyToRunConstants.cs" />
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)
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
new(new[] { "--directpinvoke" }, Array.Empty<string>, "PInvoke to call directly");
public Option<string[]> DirectPInvokeLists { get; } =
new(new[] { "--directpinvokelist" }, Array.Empty<string>, "File with list of PInvokes to call directly");
- public Option<int> MaxGenericCycle { get; } =
- new(new[] { "--maxgenericcycle" }, () => CompilerTypeSystemContext.DefaultGenericCycleCutoffPoint, "Max depth of generic cycle");
+ public Option<int> MaxGenericCycleDepth { get; } =
+ new(new[] { "--maxgenericcycle" }, () => CompilerTypeSystemContext.DefaultGenericCycleDepthCutoff, "Max depth of generic cycle");
+ public Option<int> MaxGenericCycleBreadth { get; } =
+ new(new[] { "--maxgenericcyclebreadth" }, () => CompilerTypeSystemContext.DefaultGenericCycleBreadthCutoff, "Max breadth of generic cycle expansion");
public Option<string[]> RootedAssemblies { get; } =
new(new[] { "--root" }, Array.Empty<string>, "Fully generate given assembly");
public Option<IEnumerable<string>> ConditionallyRootedAssemblies { get; } =
AddOption(SingleWarnDisabledAssemblies);
AddOption(DirectPInvokes);
AddOption(DirectPInvokeLists);
- AddOption(MaxGenericCycle);
+ AddOption(MaxGenericCycleDepth);
+ AddOption(MaxGenericCycleBreadth);
AddOption(RootedAssemblies);
AddOption(ConditionallyRootedAssemblies);
AddOption(TrimmedAssemblies);
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
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;
{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
new(new[] { "--resilient" }, SR.ResilientOption);
public Option<string> ImageBase { get; } =
new(new[] { "--imagebase" }, SR.ImageBase);
+ public Option<bool> EnableGenericCycleDetection { get; } =
+ new(new[] { "--enable-generic-cycle-detection" }, SR.EnableGenericCycleDetection);
+ public Option<int> GenericCycleDepthCutoff { get; } =
+ new(new[] { "--maxgenericcycle" }, () => ReadyToRunCompilerContext.DefaultGenericCycleDepthCutoff, SR.GenericCycleDepthCutoff);
+ public Option<int> GenericCycleBreadthCutoff { get; } =
+ new(new[] { "--maxgenericcyclebreadth" }, () => ReadyToRunCompilerContext.DefaultGenericCycleBreadthCutoff, SR.GenericCycleBreadthCutoff);
public Option<TargetArchitecture> TargetArchitecture { get; } =
new(new[] { "--targetarch" }, result =>
{
AddOption(SupportIbc);
AddOption(Resilient);
AddOption(ImageBase);
+ AddOption(EnableGenericCycleDetection);
+ AddOption(GenericCycleDepthCutoff);
+ AddOption(GenericCycleBreadthCutoff);
AddOption(TargetArchitecture);
AddOption(TargetOS);
AddOption(JitPath);
//
// 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);
{
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));
.UseCompilationRoots(compilationRoots)
.UseOptimizationMode(optimizationMode);
+ if (Get(_command.EnableGenericCycleDetection))
+ {
+ builder.UseGenericCycleDetection(
+ depthCutoff: Get(_command.GenericCycleDepthCutoff),
+ breadthCutoff: Get(_command.GenericCycleBreadthCutoff));
+ }
+
builder.UsePrintReproInstructions(CreateReproArgumentString);
compilation = builder.ToCompilation();
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
<root>
- <!--
- Microsoft ResX Schema
-
+ <!--
+ Microsoft ResX Schema
+
Version 2.0
-
- The primary goals of this format is to allow a simple XML format
- that is mostly human readable. The generation and parsing of the
- various data types are done through the TypeConverter classes
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
associated with the data types.
-
+
Example:
-
+
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
-
- There are any number of "resheader" rows that contain simple
+
+ There are any number of "resheader" rows that contain simple
name/value pairs.
-
- Each data row contains a name, and value. The row also contains a
- type or mimetype. Type corresponds to a .NET class that support
- text/value conversion through the TypeConverter architecture.
- Classes that don't support this are serialized and stored with the
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
mimetype set.
-
- The mimetype is used for serialized objects, and tells the
- ResXResourceReader how to depersist the object. This is currently not
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
-
- Note - application/x-microsoft.net.object.binary.base64 is the format
- that the ResXResourceWriter will generate, however the reader can
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
-
+
mimetype: application/x-microsoft.net.object.binary.base64
- value : The object must be serialized with
+ value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
-
+
mimetype: application/x-microsoft.net.object.soap.base64
- value : The object must be serialized with
+ value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
- value : The object must be serialized into a byte array
+ value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<data name="Crossgen2BannerText" xml:space="preserve">
<value>.NET Crossgen2 Compiler</value>
</data>
-</root>
+ <data name="EnableGenericCycleDetection" xml:space="preserve">
+ <value>Perform generic cycle detection during compilation (incurs longer compilation time)</value>
+ </data>
+ <data name="GenericCycleBreadthCutoff" xml:space="preserve">
+ <value>Total number of occurrences of potentially cyclic generic types within a generic type to cut off</value>
+ </data>
+ <data name="GenericCycleDepthCutoff" xml:space="preserve">
+ <value>Number of nested occurrences of a potentially cyclic generic type to cut off</value>
+ </data>
+</root>
\ No newline at end of file
--- /dev/null
+// 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<T, U>
+ {
+ public static int Construct(int seed)
+ {
+ if (--seed <= 0) return 10;
+ return seed switch
+ {
+ 1 => Assignment<U, T>.Construct(seed),
+ 2 => Assignment<Expression<U, T>, T>.Construct(seed),
+ 3 => Assignment<U, Expression<U, T>>.Construct(seed),
+ _ => Assignment<Expression<U, T>, Expression<T, U>>.Construct(seed)
+ };
+ }
+ }
+
+ private struct Assignment<T, U>
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => Conditional<U, T>.Construct(seed),
+ 2 => Conditional<Assignment<U, T>, T>.Construct(seed),
+ 3 => Conditional<U, Assignment<U, T>>.Construct(seed),
+ _ => Conditional<Assignment<U, T>, Assignment<T, U>>.Construct(seed)
+ };
+ }
+ }
+
+ private struct Conditional<T, U>
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => LogicalOr<U, T>.Construct(seed),
+ 2 => LogicalOr<Conditional<U, T>, T>.Construct(seed),
+ 3 => LogicalOr<U, Conditional<U, T>>.Construct(seed),
+ _ => LogicalOr<Conditional<U, T>, Conditional<T, U>>.Construct(seed)
+ };
+ }
+ }
+
+ private struct LogicalOr<T, U>
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => LogicalAnd<U, T>.Construct(seed),
+ 2 => LogicalAnd<LogicalOr<U, T>, T>.Construct(seed),
+ 3 => LogicalAnd<U, LogicalOr<U, T>>.Construct(seed),
+ _ => LogicalAnd<LogicalOr<U, T>, LogicalOr<T, U>>.Construct(seed)
+ };
+ }
+ }
+
+ private struct LogicalAnd<T, U>
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => BitwiseOr<U, T>.Construct(seed),
+ 2 => BitwiseOr<LogicalAnd<U, T>, T>.Construct(seed),
+ 3 => BitwiseOr<U, LogicalAnd<U, T>>.Construct(seed),
+ _ => BitwiseOr<LogicalAnd<U, T>, LogicalAnd<T, U>>.Construct(seed),
+ };
+ }
+ }
+
+ private struct BitwiseOr<T, U>
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => BitwiseAnd<U, T>.Construct(seed),
+ 2 => BitwiseAnd<BitwiseOr<U, T>, T>.Construct(seed),
+ 3 => BitwiseAnd<U, BitwiseOr<U, T>>.Construct(seed),
+ _ => BitwiseAnd<BitwiseOr<U, T>, BitwiseOr<T, U>>.Construct(seed)
+ };
+ }
+ }
+
+ private struct BitwiseAnd<T, U>
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => Equality<U, T>.Construct(seed),
+ 2 => Equality<BitwiseAnd<U, T>, T>.Construct(seed),
+ 3 => Equality<U, BitwiseAnd<U, T>>.Construct(seed),
+ _ => Equality<BitwiseAnd<U, T>, BitwiseAnd<T, U>>.Construct(seed)
+ };
+ }
+ }
+
+ private struct Equality<T, U>
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => Comparison<U, T>.Construct(seed),
+ 2 => Comparison<Equality<U, T>, T>.Construct(seed),
+ 3 => Comparison<U, Equality<U, T>>.Construct(seed),
+ _ => Comparison<Equality<U, T>, Equality<T, U>>.Construct(seed)
+ };
+ }
+ }
+
+ private struct Comparison<T, U>
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => BitwiseShift<U, T>.Construct(seed),
+ 2 => BitwiseShift<Comparison<U, T>, T>.Construct(seed),
+ 3 => BitwiseShift<U, Comparison<U, T>>.Construct(seed),
+ _ => BitwiseShift<Comparison<U, T>, Comparison<T, U>>.Construct(seed)
+ };
+ }
+ }
+
+ private struct BitwiseShift<T, U>
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => Addition<U, T>.Construct(seed),
+ 2 => Addition<BitwiseShift<U, T>, T>.Construct(seed),
+ 3 => Addition<U, BitwiseShift<U, T>>.Construct(seed),
+ _ => Addition<BitwiseShift<U, T>, BitwiseShift<T, U>>.Construct(seed)
+ };
+ }
+ }
+
+ private struct Addition<T, U>
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => Multiplication<U, T>.Construct(seed),
+ 2 => Multiplication<Addition<U, T>, T>.Construct(seed),
+ 3 => Multiplication<U, Addition<U, T>>.Construct(seed),
+ _ => Multiplication<Addition<U, T>, Addition<T, U>>.Construct(seed)
+ };
+ }
+ }
+
+ private struct Multiplication<T, U>
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => Nested<U, T>.Construct(seed),
+ 2 => Nested<Multiplication<U, T>, T>.Construct(seed),
+ 3 => Nested<U, Multiplication<U, T>>.Construct(seed),
+ _ => Nested<Multiplication<U, T>, Multiplication<T, U>>.Construct(seed)
+ };
+ }
+ }
+
+ private struct Nested<T, U>
+ {
+ public static int Construct(int seed)
+ {
+ return seed switch
+ {
+ 1 => Expression<U, T>.Construct(seed),
+ 2 => Expression<Nested<U, T>, T>.Construct(seed),
+ 3 => Expression<U, Nested<U, T>>.Construct(seed),
+ _ => Expression<Nested<U, T>, Nested<T, U>>.Construct(seed)
+ };
+ }
+ }
+
+ [Fact]
+ public static int BreadthTest()
+ {
+ return Expression<long, int>.Construct(2) * Expression<float, double>.Construct(2);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static int ReturnTwoAndDontTellJIT() => 2;
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="Current">
+ <PropertyGroup>
+ <OutputType>exe</OutputType>
+ <CLRTestTargetUnsupported Condition="'$(RuntimeFlavor)' != 'coreclr'">true</CLRTestTargetUnsupported>
+ <!-- This is an explicit crossgen test -->
+ <AlwaysUseCrossGen2>true</AlwaysUseCrossGen2>
+ <!-- Without this flag Crossgen2 crashes after several minutes with arithmetic overflow -->
+ <CrossGen2TestExtraArguments>--enable-generic-cycle-detection --maxgenericcycle:1 --maxgenericcyclebreadth:1</CrossGen2TestExtraArguments>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="$(MSBuildProjectName).cs" />
+ </ItemGroup>
+</Project>
--- /dev/null
+// 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<T1, T2, T3, T4, T5, T6>
+ {
+ public static long TypeNestedFactorial(int count)
+ {
+ if (count <= 1)
+ {
+ return 1;
+ }
+ long result = 0;
+ if (result < count) result = Breadth<T2, T3, T4, T5, T6, Oper1<T1>>.TypeNestedFactorial(count - 1);
+ if (result < count) result = Breadth<T2, T3, T4, T5, T6, Oper2<T1>>.TypeNestedFactorial(count - 1);
+ if (result < count) result = Breadth<T2, T3, T4, T5, T6, Oper3<T1>>.TypeNestedFactorial(count - 1);
+ if (result < count) result = Breadth<T2, T3, T4, T5, T6, Oper4<T1>>.TypeNestedFactorial(count - 1);
+ if (result < count) result = Breadth<T2, T3, T4, T5, T6, Oper5<T1>>.TypeNestedFactorial(count - 1);
+ return count * result;
+ }
+ }
+
+ private struct Oper1<T> {}
+ private struct Oper2<T> {}
+ private struct Oper3<T> {}
+ private struct Oper4<T> {}
+ private struct Oper5<T> {}
+
+ [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<byte, char, int, long, float, double>.TypeNestedFactorial(ReturnTwentyAndDontTellJIT()) == Factorial20 ? 100 : 101;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static int ReturnTwentyAndDontTellJIT() => 20;
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="Current">
+ <PropertyGroup>
+ <OutputType>exe</OutputType>
+ <CLRTestTargetUnsupported Condition="'$(RuntimeFlavor)' != 'coreclr'">true</CLRTestTargetUnsupported>
+ <!-- This is an explicit crossgen test -->
+ <AlwaysUseCrossGen2>true</AlwaysUseCrossGen2>
+ <!-- Without this flag Crossgen2 crashes after several minutes with arithmetic overflow -->
+ <CrossGen2TestExtraArguments>--enable-generic-cycle-detection --maxgenericcycle:1 --maxgenericcyclebreadth:-1</CrossGen2TestExtraArguments>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="$(MSBuildProjectName).cs" />
+ </ItemGroup>
+</Project>
--- /dev/null
+// 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<Depth1`1<Depth1`1<Depth1`1<T>>>>
+ private struct Depth1<T>
+ {
+ public static long TypeNestedFactorial(int count)
+ {
+ if (count <= 1)
+ {
+ return 1;
+ }
+ long result = 0;
+ if (result < count) result = Depth1<Depth1<T>>.TypeNestedFactorial(count - 1);
+ if (result < count) result = Depth1<Depth2<T>>.TypeNestedFactorial(count - 1);
+ if (result < count) result = Depth1<Depth3<T>>.TypeNestedFactorial(count - 1);
+ if (result < count) result = Depth1<Depth4<T>>.TypeNestedFactorial(count - 1);
+ if (result < count) result = Depth1<Depth5<T>>.TypeNestedFactorial(count - 1);
+ return count * result;
+ }
+ }
+
+ private struct Depth2<T> {}
+ private struct Depth3<T> {}
+ private struct Depth4<T> {}
+ private struct Depth5<T> {}
+
+ [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<long>.TypeNestedFactorial(ReturnTwentyAndDontTellJIT()) == Factorial20 ? 100 : 101;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static int ReturnTwentyAndDontTellJIT() => 20;
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="Current">
+ <PropertyGroup>
+ <OutputType>exe</OutputType>
+ <CLRTestTargetUnsupported Condition="'$(RuntimeFlavor)' != 'coreclr'">true</CLRTestTargetUnsupported>
+ <!-- This is an explicit crossgen test -->
+ <AlwaysUseCrossGen2>true</AlwaysUseCrossGen2>
+ <!-- Without this flag Crossgen2 crashes after several minutes with arithmetic overflow -->
+ <CrossGen2TestExtraArguments>--enable-generic-cycle-detection --maxgenericcycle:3 --maxgenericcyclebreadth:-1</CrossGen2TestExtraArguments>
+ <!-- This test OOMs Crossgen2 when running in 32-bit address space -->
+ <CLRTestTargetUnsupported Condition="'$(TargetBits)' == '32'">true</CLRTestTargetUnsupported>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="$(MSBuildProjectName).cs" />
+ </ItemGroup>
+</Project>