diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs index 6295e9341f6bf3..1781f473371249 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Text; +using ILCompiler.DependencyAnalysisFramework; using ILCompiler.Dataflow; using Internal.IL; using Internal.TypeSystem; @@ -126,6 +127,455 @@ public void TestDependencyGraphInvariants(EcmaMethod method) Assert.True(foundSomethingToCheck, "No invariants to check?"); } + [Fact] + public void ConditionalDependencyRequiresCondition() + { + TestContext context = new TestContext(); + TestNode condition = new TestNode("condition"); + TestNode dependency = new TestNode("dependency"); + TestNode emptySource = new TestNode("empty source"); + TestNode source = new TestNode( + "source", + conditionalDependencies: new[] { ConditionalDependency(dependency, condition, "condition") }); + + DependencyAnalyzer analyzer = Analyze(context, emptySource, source); + + Assert.True(emptySource.Marked); + Assert.False(condition.Marked); + Assert.False(dependency.Marked); + Assert.DoesNotContain(dependency, analyzer.MarkedNodeList); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ConditionalDependencyMarksForConditionOrder(bool conditionAlreadyMarked) + { + TestContext context = new TestContext(); + TestNode condition = new TestNode("condition"); + TestNode dependency = new TestNode("dependency"); + TestNode trigger = new TestNode( + "trigger", + dependencies: new[] { StaticDependency(condition, "condition") }); + TestNode source = new TestNode( + "source", + dependencies: conditionAlreadyMarked ? null : new[] { StaticDependency(trigger, "trigger") }, + conditionalDependencies: new[] { ConditionalDependency(dependency, condition, "condition") }); + DependencyAnalyzer analyzer = + new DependencyAnalyzer(context, null); + + if (conditionAlreadyMarked) + { + analyzer.AddRoot(condition, "condition root"); + } + analyzer.AddRoot(source, "source root"); + analyzer.ComputeMarkedNodes(); + + Assert.True(condition.Marked); + Assert.True(dependency.Marked); + Assert.Equal(1, context.CountMarkAttempts(dependency)); + } + + [Fact] + public void ConditionalDependenciesDeduplicateAcrossPromotion() + { + TestContext context = new TestContext(); + TestNode condition = new TestNode("condition"); + TestNode firstDependency = new TestNode("first dependency"); + TestNode secondDependency = new TestNode("second dependency"); + TestNode thirdDependency = new TestNode("third dependency"); + TestNode trigger = new TestNode( + "trigger", + dependencies: new[] { StaticDependency(condition, "condition") }); + string duplicateFirstReason = new string("first".ToCharArray()); + string duplicateSecondReason = new string("second".ToCharArray()); + TestNode source = new TestNode( + "source", + dependencies: new[] { StaticDependency(trigger, "trigger") }, + conditionalDependencies: new[] + { + ConditionalDependency(firstDependency, condition, "first"), + ConditionalDependency(firstDependency, condition, duplicateFirstReason), + ConditionalDependency(secondDependency, condition, "second"), + ConditionalDependency(secondDependency, condition, duplicateSecondReason), + ConditionalDependency(thirdDependency, condition, "third"), + }); + + Analyze(context, source); + + Assert.Equal(1, context.CountMarkAttempts(firstDependency)); + Assert.Equal(1, context.CountMarkAttempts(secondDependency)); + Assert.Equal(1, context.CountMarkAttempts(thirdDependency)); + } + + [Fact] + public void ConditionalDependenciesPreserveDistinctOwners() + { + TestContext context = new TestContext(); + TestNode condition = new TestNode("condition"); + TestNode dependency = new TestNode("dependency"); + TestNode trigger = new TestNode( + "trigger", + dependencies: new[] { StaticDependency(condition, "condition") }); + TestNode firstSource = new TestNode( + "first source", + conditionalDependencies: new[] { ConditionalDependency(dependency, condition, "condition") }); + TestNode secondSource = new TestNode( + "second source", + conditionalDependencies: new[] { ConditionalDependency(dependency, condition, "condition") }); + TestNode root = new TestNode( + "root", + dependencies: new[] + { + StaticDependency(trigger, "trigger"), + StaticDependency(firstSource, "first source"), + StaticDependency(secondSource, "second source"), + }); + + Analyze(context, root); + + Assert.True(dependency.Marked); + Assert.Equal(2, context.CountMarkAttempts(dependency)); + } + + [Fact] + public void ConditionalDependenciesUseConditionIdentity() + { + TestContext context = new TestContext(); + TestNode firstCondition = new TestNode("condition"); + TestNode secondCondition = new TestNode("condition"); + TestNode firstDependency = new TestNode("first dependency"); + TestNode secondDependency = new TestNode("second dependency"); + TestNode trigger = new TestNode( + "trigger", + dependencies: new[] { StaticDependency(firstCondition, "first condition") }); + TestNode firstSource = new TestNode( + "first source", + conditionalDependencies: new[] { ConditionalDependency(firstDependency, firstCondition, "condition") }); + TestNode secondSource = new TestNode( + "second source", + conditionalDependencies: new[] { ConditionalDependency(secondDependency, secondCondition, "condition") }); + TestNode root = new TestNode( + "root", + dependencies: new[] + { + StaticDependency(trigger, "trigger"), + StaticDependency(firstSource, "first source"), + StaticDependency(secondSource, "second source"), + }); + + Analyze(context, root); + + Assert.True(firstCondition.Marked); + Assert.False(secondCondition.Marked); + Assert.True(firstDependency.Marked); + Assert.False(secondDependency.Marked); + } + + [Fact] + public void ConditionalDependencyDoesNotRemarkDependency() + { + TestContext context = new TestContext(); + TestNode condition = new TestNode("condition"); + TestNode dependency = new TestNode("dependency"); + TestNode trigger = new TestNode( + "trigger", + dependencies: new[] { StaticDependency(condition, "condition") }); + TestNode source = new TestNode( + "source", + dependencies: new[] { StaticDependency(trigger, "trigger") }, + conditionalDependencies: new[] { ConditionalDependency(dependency, condition, "condition") }); + DependencyAnalyzer analyzer = + new DependencyAnalyzer(context, null); + int dependencyMarkedCount = 0; + analyzer.NewMarkedNode += node => + { + if (ReferenceEquals(node, dependency)) + { + dependencyMarkedCount++; + } + }; + + analyzer.AddRoot(dependency, "dependency root"); + analyzer.AddRoot(source, "source root"); + analyzer.ComputeMarkedNodes(); + + Assert.Equal(2, context.CountMarkAttempts(dependency)); + Assert.Equal(1, dependencyMarkedCount); + } + + [Fact] + public void ConditionalDependencyAllowsNullCondition() + { + TestContext context = new TestContext(); + TestNode dependency = new TestNode("dependency"); + TestNode source = new TestNode( + "source", + conditionalDependencies: new[] { ConditionalDependency(dependency, null, "unconditional") }); + + Analyze(context, source); + + Assert.True(dependency.Marked); + Assert.Equal(1, context.CountMarkAttempts(dependency)); + } + + [Fact] + public void DeferredConditionalDependencyReplays() + { + TestContext context = new TestContext(); + TestNode condition = new TestNode("condition"); + TestNode dependency = new TestNode("dependency"); + TestNode trigger = new TestNode( + "trigger", + dependencies: new[] { StaticDependency(condition, "condition") }); + TestNode source = new TestNode("source", dependenciesComputed: false, dependencyPhase: 2); + DependencyAnalyzer analyzer = + new DependencyAnalyzer(context, null); + int computationCount = 0; + analyzer.ComputeDependencyRoutine += nodes => + { + if (nodes.Count == 0) + { + return; + } + + computationCount++; + Assert.Single(nodes); + Assert.Same(source, nodes[0]); + source.SetDependencies( + new[] { StaticDependency(trigger, "trigger") }, + new[] { ConditionalDependency(dependency, condition, "condition") }); + }; + + analyzer.AddRoot(source, "source root"); + analyzer.ComputeMarkedNodes(); + + Assert.True(condition.Marked); + Assert.True(dependency.Marked); + Assert.Equal(1, computationCount); + } + + [Fact] + public void SatisfiedConditionalDependencyDoesNotBlockLaterDependency() + { + TestContext context = new TestContext(); + TestNode condition = new TestNode("condition"); + TestNode laterDependency = new TestNode("later dependency"); + TestNode laterSource = new TestNode( + "later source", + conditionalDependencies: new[] { ConditionalDependency(laterDependency, condition, "later condition") }); + TestNode firstDependency = new TestNode( + "first dependency", + dependencies: new[] { StaticDependency(laterSource, "later source") }); + TestNode trigger = new TestNode( + "trigger", + dependencies: new[] { StaticDependency(condition, "condition") }); + TestNode source = new TestNode( + "source", + dependencies: new[] { StaticDependency(trigger, "trigger") }, + conditionalDependencies: new[] { ConditionalDependency(firstDependency, condition, "first condition") }); + + Analyze(context, source); + + Assert.True(firstDependency.Marked); + Assert.True(laterDependency.Marked); + Assert.Equal(1, context.CountMarkAttempts(laterDependency)); + } + + [Fact] + public void ConditionalDependenciesPreserveDistinctReasons() + { + TestContext context = new TestContext(); + TestNode condition = new TestNode("condition"); + TestNode dependency = new TestNode("dependency"); + TestNode trigger = new TestNode( + "trigger", + dependencies: new[] { StaticDependency(condition, "condition") }); + TestNode source = new TestNode( + "source", + dependencies: new[] { StaticDependency(trigger, "trigger") }, + conditionalDependencies: new[] + { + ConditionalDependency(dependency, condition, "first"), + ConditionalDependency(dependency, condition, "second"), + }); + + Analyze(context, source); + + string[] reasons = context.GetMarkReasons(dependency); + Array.Sort(reasons, StringComparer.Ordinal); + Assert.Equal(new[] { "first", "second" }, reasons); + } + + private static DependencyAnalyzer Analyze( + TestContext context, + params TestNode[] roots) + { + DependencyAnalyzer analyzer = + new DependencyAnalyzer(context, null); + foreach (TestNode root in roots) + { + analyzer.AddRoot(root, "root"); + } + analyzer.ComputeMarkedNodes(); + return analyzer; + } + + private static DependencyNodeCore.DependencyListEntry StaticDependency(TestNode node, string reason) + { + return new DependencyNodeCore.DependencyListEntry(node, reason); + } + + private static DependencyNodeCore.CombinedDependencyListEntry ConditionalDependency( + TestNode node, + TestNode condition, + string reason) + { + return new DependencyNodeCore.CombinedDependencyListEntry(node, condition, reason); + } + + private sealed class TestContext + { + private readonly List<(DependencyNodeCore Node, string Reason)> _markAttempts = + new List<(DependencyNodeCore, string)>(); + + public void RecordMarkAttempt(DependencyNodeCore node, string reason) + { + _markAttempts.Add((node, reason)); + } + + public int CountMarkAttempts(DependencyNodeCore node) + { + int count = 0; + foreach ((DependencyNodeCore attemptedNode, _) in _markAttempts) + { + if (ReferenceEquals(attemptedNode, node)) + { + count++; + } + } + return count; + } + + public string[] GetMarkReasons(DependencyNodeCore node) + { + List reasons = new List(); + foreach ((DependencyNodeCore attemptedNode, string reason) in _markAttempts) + { + if (ReferenceEquals(attemptedNode, node)) + { + reasons.Add(reason); + } + } + return reasons.ToArray(); + } + } + + private struct TrackingMarkStrategy : IDependencyAnalysisMarkStrategy + { + private TestContext _context; + private IDependencyAnalysisMarkStrategy _innerStrategy; + + public void AttachContext(TestContext context) + { + _context = context; + _innerStrategy = new NoLogStrategy(); + _innerStrategy.AttachContext(context); + } + + public bool MarkNode( + DependencyNodeCore node, + DependencyNodeCore reasonNode, + DependencyNodeCore reasonNode2, + string reason) + { + _context.RecordMarkAttempt(node, reason); + return _innerStrategy.MarkNode(node, reasonNode, reasonNode2, reason); + } + + public void VisitLogEdges( + IEnumerable> nodeList, + IDependencyAnalyzerLogEdgeVisitor logEdgeVisitor) + { + _innerStrategy.VisitLogEdges(nodeList, logEdgeVisitor); + } + + public void VisitLogNodes( + IEnumerable> nodeList, + IDependencyAnalyzerLogNodeVisitor logNodeVisitor) + { + _innerStrategy.VisitLogNodes(nodeList, logNodeVisitor); + } + } + + private sealed class TestNode : DependencyNodeCore + { + private readonly string _name; + private readonly int _dependencyPhase; + private IEnumerable _dependencies; + private IEnumerable _conditionalDependencies; + + public TestNode( + string name, + IEnumerable dependencies = null, + IEnumerable conditionalDependencies = null, + bool dependenciesComputed = true, + int dependencyPhase = 0) + { + _name = name; + _dependencyPhase = dependencyPhase; + if (dependenciesComputed) + { + _dependencies = dependencies ?? Array.Empty(); + _conditionalDependencies = conditionalDependencies; + } + } + + public string Name => _name; + + public override bool InterestingForDynamicDependencyAnalysis => false; + + public override bool HasDynamicDependencies => false; + + public override bool HasConditionalStaticDependencies => _conditionalDependencies is not null; + + public override bool StaticDependenciesAreComputed => _dependencies is not null; + + public override int DependencyPhaseForDeferredStaticComputation => _dependencyPhase; + + public void SetDependencies( + IEnumerable dependencies, + IEnumerable conditionalDependencies) + { + Assert.False(StaticDependenciesAreComputed); + _dependencies = dependencies; + _conditionalDependencies = conditionalDependencies; + } + + public override IEnumerable GetStaticDependencies(TestContext context) + { + return _dependencies; + } + + public override IEnumerable GetConditionalStaticDependencies(TestContext context) + { + return _conditionalDependencies; + } + + public override IEnumerable SearchDynamicDependencies( + List> markedNodes, + int firstNode, + TestContext context) + { + return Array.Empty(); + } + + protected override string GetName(TestContext context) + { + return _name; + } + } + private static MethodDesc GetMethodFromAttribute(CustomAttributeValue attr) { if (attr.NamedArguments.Length > 0) diff --git a/src/coreclr/tools/aot/ILCompiler.DependencyAnalysisFramework/DependencyAnalyzer.cs b/src/coreclr/tools/aot/ILCompiler.DependencyAnalysisFramework/DependencyAnalyzer.cs index f4adb44450ee85..d64cf3ebd2ccfa 100644 --- a/src/coreclr/tools/aot/ILCompiler.DependencyAnalysisFramework/DependencyAnalyzer.cs +++ b/src/coreclr/tools/aot/ILCompiler.DependencyAnalysisFramework/DependencyAnalyzer.cs @@ -39,9 +39,57 @@ public sealed class DependencyAnalyzer : De private List _markedNodesWithDynamicDependencies = new List(); private bool _newDynamicDependenciesMayHaveAppeared; - private Dictionary, HashSet.CombinedDependencyListEntry>> _conditional_dependency_store = new Dictionary, HashSet.CombinedDependencyListEntry>>(); + private Dictionary, ConditionalDependencyBucket> _conditionalDependencyStore = + new Dictionary, ConditionalDependencyBucket>(); private bool _markingCompleted; + private sealed class ConditionalDependencyBucket + { + private readonly DependencyNodeCore.CombinedDependencyListEntry _singleDependency; + private HashSet.CombinedDependencyListEntry> _dependencies; + + public ConditionalDependencyBucket(DependencyNodeCore.CombinedDependencyListEntry dependency) + { + _singleDependency = dependency; + } + + public void Add(DependencyNodeCore.CombinedDependencyListEntry dependency) + { + if (_dependencies is null) + { + if (_singleDependency.Equals(dependency)) + { + return; + } + + _dependencies = new HashSet.CombinedDependencyListEntry> + { + _singleDependency, + dependency, + }; + return; + } + + _dependencies.Add(dependency); + } + + public void MarkDependencies( + DependencyAnalyzer analyzer, + DependencyNodeCore condition) + { + if (_dependencies is null) + { + analyzer.AddToMarkStack(_singleDependency.Node, _singleDependency.Reason, _singleDependency.OtherReasonNode, condition); + return; + } + + foreach (DependencyNodeCore.CombinedDependencyListEntry dependency in _dependencies) + { + analyzer.AddToMarkStack(dependency.Node, dependency.Reason, dependency.OtherReasonNode, condition); + } + } + } + private sealed class RandomInsertStack { private List _nodes = new List(); @@ -199,16 +247,19 @@ private void GetStaticDependenciesImpl(DependencyNodeCore } else { - HashSet.CombinedDependencyListEntry> storedDependencySet; - if (!_conditional_dependency_store.TryGetValue(dependency.OtherReasonNode, out storedDependencySet)) - { - storedDependencySet = new HashSet.CombinedDependencyListEntry>(); - _conditional_dependency_store.Add(dependency.OtherReasonNode, storedDependencySet); - } // Swap out other reason node as we're storing that as the dictionary key DependencyNodeCore.CombinedDependencyListEntry conditionalDependencyStoreEntry = new DependencyNodeCore.CombinedDependencyListEntry(dependency.Node, node, dependency.Reason); - storedDependencySet.Add(conditionalDependencyStoreEntry); + ConditionalDependencyBucket storedDependencies; + if (!_conditionalDependencyStore.TryGetValue(dependency.OtherReasonNode, out storedDependencies)) + { + storedDependencies = new ConditionalDependencyBucket(conditionalDependencyStoreEntry); + _conditionalDependencyStore.Add(dependency.OtherReasonNode, storedDependencies); + } + else + { + storedDependencies.Add(conditionalDependencyStoreEntry); + } } } } @@ -266,15 +317,9 @@ private void ProcessMarkStack() // If this new node satisfies any stored conditional dependencies, // add them to the mark stack - HashSet.CombinedDependencyListEntry> storedDependencySet; - if (_conditional_dependency_store.TryGetValue(currentNode, out storedDependencySet)) + if (_conditionalDependencyStore.Remove(currentNode, out ConditionalDependencyBucket storedDependencies)) { - foreach (DependencyNodeCore.CombinedDependencyListEntry newlySatisfiedDependency in storedDependencySet) - { - AddToMarkStack(newlySatisfiedDependency.Node, newlySatisfiedDependency.Reason, newlySatisfiedDependency.OtherReasonNode, currentNode); - } - - _conditional_dependency_store.Remove(currentNode); + storedDependencies.MarkDependencies(this, currentNode); } }