diff --git a/src/ILToNative.DependencyAnalysisFramework/ILToNative.DependencyAnalysisFramework.sln b/src/ILToNative.DependencyAnalysisFramework/ILToNative.DependencyAnalysisFramework.sln new file mode 100644 index 00000000000..db1bd4c9650 --- /dev/null +++ b/src/ILToNative.DependencyAnalysisFramework/ILToNative.DependencyAnalysisFramework.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.23023.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ILToNative.DependencyAnalysisFramework.Tests", "tests\ILToNative.DependencyAnalysisFramework.Tests.csproj", "{90076B9B-918B-49DD-8ADE-E76426D60B4D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ILToNative.DependencyAnalysisFramework", "src\ILToNative.DependencyAnalysisFramework.csproj", "{DAC23E9F-F826-4577-AE7A-0849FF83280C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {90076B9B-918B-49DD-8ADE-E76426D60B4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90076B9B-918B-49DD-8ADE-E76426D60B4D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90076B9B-918B-49DD-8ADE-E76426D60B4D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90076B9B-918B-49DD-8ADE-E76426D60B4D}.Release|Any CPU.Build.0 = Release|Any CPU + {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/ILToNative.DependencyAnalysisFramework/src/ComputedStaticDependencyNode.cs b/src/ILToNative.DependencyAnalysisFramework/src/ComputedStaticDependencyNode.cs new file mode 100644 index 00000000000..1b94e9d6c8e --- /dev/null +++ b/src/ILToNative.DependencyAnalysisFramework/src/ComputedStaticDependencyNode.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Diagnostics; + +namespace ILToNative.DependencyAnalysisFramework +{ + public abstract class ComputedStaticDependencyNode : DependencyNodeCore + { + private IEnumerable _dependencies; + private IEnumerable _conditionalDependencies; + private static CombinedDependencyListEntry[] s_emptyDynamicList = new CombinedDependencyListEntry[0]; + + public void SetStaticDependencies(IEnumerable dependencies, + IEnumerable conditionalDependencies) + { + Debug.Assert(_dependencies == null); + Debug.Assert(_conditionalDependencies == null); + Debug.Assert(dependencies != null); + + _dependencies = dependencies; + _conditionalDependencies = conditionalDependencies; + } + + public override bool HasConditionalStaticDependencies + { + get + { + return _conditionalDependencies != null; + } + } + + public override bool HasDynamicDependencies + { + get + { + return false; + } + } + + public override bool InterestingForDynamicDependencyAnalysis + { + get + { + return true; + } + } + + public override bool StaticDependenciesAreComputed + { + get + { + return _dependencies != null; + } + } + + public override IEnumerable GetConditionalStaticDependencies(DependencyContextType context) + { + return _conditionalDependencies; + } + + public override IEnumerable GetStaticDependencies(DependencyContextType context) + { + return _dependencies; + } + + public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, DependencyContextType context) + { + return s_emptyDynamicList; + } + } +} diff --git a/src/ILToNative.DependencyAnalysisFramework/src/DependencyAnalyzer.cs b/src/ILToNative.DependencyAnalysisFramework/src/DependencyAnalyzer.cs new file mode 100644 index 00000000000..ba6a30f0c62 --- /dev/null +++ b/src/ILToNative.DependencyAnalysisFramework/src/DependencyAnalyzer.cs @@ -0,0 +1,280 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; + +namespace ILToNative.DependencyAnalysisFramework +{ + /// + /// Implement a dependency analysis framework. This works much like a Garbage Collector's mark algorithm + /// in that it finds a set of nodes from an initial root set. + /// + /// However, in contrast to a typical GC in addition to simple edges from a node, there may also + /// be conditional edges where a node has a dependency if some other specific node exists in the + /// graph, and dynamic edges in which a node has a dependency if some other node exists in the graph, + /// but what that other node might be is not known until it may exist in the graph. + /// + /// This analyzer also attempts to maintain a serialized state of why nodes are in the graph + /// with strings describing the reason a given node was added to the graph. The degree of logging + /// is configurable via the MarkStrategy + /// + /// + public sealed class DependencyAnalyzer : DependencyAnalyzerBase where MarkStrategy: struct, IDependencyAnalysisMarkStrategy + { + private MarkStrategy _marker = new MarkStrategy(); + private DependencyContextType _dependencyContext; + private IComparer> _resultSorter = null; + + private Stack> _markStack = new Stack>(); + private List> _markedNodes = new List>(); + private ImmutableArray> _markedNodesFinal; + private List> _rootNodes = new List>(); + private List> _deferredStaticDependencies = new List>(); + private List> _dynamicDependencyInterestingList = new List>(); + private List _markedNodesWithDynamicDependencies = new List(); + private bool _newDynamicDependenciesMayHaveAppeared = false; + + private Dictionary, HashSet.CombinedDependencyListEntry>> _conditional_dependency_store = new Dictionary, HashSet.CombinedDependencyListEntry>>(); + private bool _markingCompleted = false; + + private struct DynamicDependencyNode + { + private DependencyNodeCore _node; + private int _next; + + public DynamicDependencyNode(DependencyNodeCore node) + { + _node = node; + _next = 0; + } + + public void MarkNewDynamicDependencies(DependencyAnalyzer analyzer) + { + foreach (DependencyNodeCore.CombinedDependencyListEntry dependency in + _node.SearchDynamicDependencies(analyzer._dynamicDependencyInterestingList, _next, analyzer._dependencyContext)) + { + analyzer.AddToMarkStack(dependency.Node, dependency.Reason, _node, dependency.OtherReasonNode); + } + _next = analyzer._dynamicDependencyInterestingList.Count; + } + } + + // Api surface + public DependencyAnalyzer(DependencyContextType dependencyContext, IComparer> resultSorter) + { + _dependencyContext = dependencyContext; + _resultSorter = resultSorter; + } + + /// + /// Add a root node + /// + public override sealed void AddRoot(DependencyNodeCore rootNode, string reason) + { + if (AddToMarkStack(rootNode, reason, null, null)) + { + _rootNodes.Add(rootNode); + } + } + + public override sealed ImmutableArray> MarkedNodeList + { + get + { + if (!_markingCompleted) + { + _markingCompleted = true; + ComputeMarkedNodes(); + } + + return _markedNodesFinal; + } + } + + public override sealed event Action> NewMarkedNode; + + public override sealed event Action>> ComputeDependencyRoutine; + + + private IEnumerable> MarkedNodesEnumerable() + { + if (_markedNodesFinal != null) + return _markedNodesFinal; + else + return _markedNodes; + } + + public override sealed void VisitLogNodes(IDependencyAnalyzerLogNodeVisitor logNodeVisitor) + { + foreach (DependencyNode node in MarkedNodesEnumerable()) + { + logNodeVisitor.VisitNode(node); + } + _marker.VisitLogNodes(MarkedNodesEnumerable(), logNodeVisitor); + } + + public override sealed void VisitLogEdges(IDependencyAnalyzerLogEdgeVisitor logEdgeVisitor) + { + _marker.VisitLogEdges(MarkedNodesEnumerable(), logEdgeVisitor); + } + + + /// + /// Called by the algorithm to ensure that this set of nodes is processed such that static dependencies are computed. + /// + /// List of nodes which must have static dependencies computed + private void ComputeDependencies(List> deferredStaticDependencies) + { + if (ComputeDependencyRoutine != null) + ComputeDependencyRoutine(deferredStaticDependencies); + } + + // Internal details + void GetStaticDependenciesImpl(DependencyNodeCore node) + { + foreach (DependencyNodeCore.DependencyListEntry dependency in node.GetStaticDependencies(_dependencyContext)) + { + AddToMarkStack(dependency.Node, dependency.Reason, node, null); + } + + if (node.HasConditionalStaticDependencies) + { + foreach (DependencyNodeCore.CombinedDependencyListEntry dependency in node.GetConditionalStaticDependencies(_dependencyContext)) + { + if (dependency.OtherReasonNode.Marked) + { + AddToMarkStack(dependency.Node, dependency.Reason, node, dependency.OtherReasonNode); + } + else + { + HashSet.CombinedDependencyListEntry> storedDependencySet = null; + 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(); + conditionalDependencyStoreEntry.Node = dependency.Node; + conditionalDependencyStoreEntry.Reason = dependency.Reason; + conditionalDependencyStoreEntry.OtherReasonNode = node; + + storedDependencySet.Add(conditionalDependencyStoreEntry); + } + } + } + } + + private void GetStaticDependencies(DependencyNodeCore node) + { + if (node.StaticDependenciesAreComputed) + { + GetStaticDependenciesImpl(node); + } + else + { + _deferredStaticDependencies.Add(node); + } + } + + private void ProcessMarkStack() + { + do + { + while (_markStack.Count > 0) + { + // Pop the top node of the mark stack + DependencyNodeCore currentNode = _markStack.Pop(); + + Debug.Assert(currentNode.Marked); + + // Only some marked objects are interesting for dynamic dependencies + // store those in a seperate list to avoid excess scanning over non-interesting + // nodes during dynamic dependency discovery + if (currentNode.InterestingForDynamicDependencyAnalysis) + { + _dynamicDependencyInterestingList.Add(currentNode); + _newDynamicDependenciesMayHaveAppeared = true; + } + + // Add all static dependencies to the mark stack + GetStaticDependencies(currentNode); + + // If there are dynamic dependencies, note for later + if (currentNode.HasDynamicDependencies) + { + _newDynamicDependenciesMayHaveAppeared = true; + _markedNodesWithDynamicDependencies.Add(new DynamicDependencyNode(currentNode)); + } + + // If this new node satisfies any stored conditional dependencies, + // add them to the mark stack + HashSet.CombinedDependencyListEntry> storedDependencySet = null; + if (_conditional_dependency_store.TryGetValue(currentNode, out storedDependencySet)) + { + foreach (DependencyNodeCore.CombinedDependencyListEntry newlySatisfiedDependency in storedDependencySet) + { + AddToMarkStack(newlySatisfiedDependency.Node, newlySatisfiedDependency.Reason, newlySatisfiedDependency.OtherReasonNode, currentNode); + } + + _conditional_dependency_store.Remove(currentNode); + } + + if (NewMarkedNode != null) + NewMarkedNode(currentNode); + } + + // Find new dependencies introduced by dynamic depedencies + if (_newDynamicDependenciesMayHaveAppeared) + { + _newDynamicDependenciesMayHaveAppeared = false; + foreach (DynamicDependencyNode dynamicNode in _markedNodesWithDynamicDependencies) + { + dynamicNode.MarkNewDynamicDependencies(this); + } + } + } while (_markStack.Count != 0); + } + + void ComputeMarkedNodes() + { + do + { + // Run mark stack algorithm as much as possible + ProcessMarkStack(); + + // Compute all dependencies which were not ready during the ProcessMarkStack step + ComputeDependencies(_deferredStaticDependencies); + foreach (DependencyNodeCore node in _deferredStaticDependencies) + { + Debug.Assert(node.StaticDependenciesAreComputed); + GetStaticDependenciesImpl(node); + } + + _deferredStaticDependencies.Clear(); + } while (_markStack.Count != 0); + + if (_resultSorter != null) + _markedNodes.Sort(_resultSorter); + + _markedNodesFinal = _markedNodes.ToImmutableArray(); + _markedNodes = null; + } + + private bool AddToMarkStack(DependencyNodeCore node, string reason, DependencyNodeCore reason1, DependencyNodeCore reason2) + { + if (_marker.MarkNode(node, reason1, reason2, reason)) + { + _markStack.Push(node); + _markedNodes.Add(node); + + return true; + } + + return false; + } + } +} diff --git a/src/ILToNative.DependencyAnalysisFramework/src/DependencyAnalyzerBase.cs b/src/ILToNative.DependencyAnalysisFramework/src/DependencyAnalyzerBase.cs new file mode 100644 index 00000000000..855d6d3d36f --- /dev/null +++ b/src/ILToNative.DependencyAnalysisFramework/src/DependencyAnalyzerBase.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; + +namespace ILToNative.DependencyAnalysisFramework +{ + /// + /// Api for dependency analyzer. The expected use pattern is that a set of nodes will be added to the + /// graph as roots. These nodes will internally implement the various dependency details + /// so that if MarkedNodeList is called, it can produce the complete graph. For the case + /// where nodes have deferred computation the ComputeDependencyRoutine even will be triggered + /// to fill in data. + /// + /// The Log visitor logic can be called at any time, and should log the current set of marked + /// nodes and edges in the analysis. (Notably, if its called before MarkedNodeList is evaluated + /// it will contain only roots, if its called during, the edges/nodes may be incomplete, and + /// if called after MarkedNodeList is computed it will be a complete graph. + /// + /// + /// + public abstract class DependencyAnalyzerBase + { + /// + /// Add a root node + /// + public abstract void AddRoot(DependencyNodeCore rootNode, string reason); + + /// + /// Return the marked node list. Do not modify this list, as it will cause unexpected behavior. + /// + public abstract ImmutableArray> MarkedNodeList + { + get; + } + + /// + /// This event is triggered when a node is added to the graph. + /// + public abstract event Action> NewMarkedNode; + + /// + /// This event is triggered when the algorithm requires that dependencies of some set of + /// nodes be computed. + /// + + public abstract event Action>> ComputeDependencyRoutine; + + /// + /// Used to walk all nodes that should be emitted to a log. Not intended for other purposes. + /// + /// + public abstract void VisitLogNodes(IDependencyAnalyzerLogNodeVisitor logNodeVisitor); + + /// + /// Used to walk the logical edges in the graph as part of log building. + /// + /// + public abstract void VisitLogEdges(IDependencyAnalyzerLogEdgeVisitor logEdgeVisitor); + } +} diff --git a/src/ILToNative.DependencyAnalysisFramework/src/DependencyNode.cs b/src/ILToNative.DependencyAnalysisFramework/src/DependencyNode.cs new file mode 100644 index 00000000000..96ffec8e5af --- /dev/null +++ b/src/ILToNative.DependencyAnalysisFramework/src/DependencyNode.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ILToNative.DependencyAnalysisFramework +{ + public class DependencyNode + { + private object _mark; + + // Only DependencyNodeCore is allowed to derive from this + internal DependencyNode() + { } + + internal void SetMark(object mark) + { + _mark = mark; + } + internal object GetMark() + { + return _mark; + } + + public bool Marked + { + get + { + return _mark != null; + } + } + } +} diff --git a/src/ILToNative.DependencyAnalysisFramework/src/DependencyNodeCore.cs b/src/ILToNative.DependencyAnalysisFramework/src/DependencyNodeCore.cs new file mode 100644 index 00000000000..f4989d2bffe --- /dev/null +++ b/src/ILToNative.DependencyAnalysisFramework/src/DependencyNodeCore.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ILToNative.DependencyAnalysisFramework +{ + public abstract class DependencyNodeCore : DependencyNode + { + public struct DependencyListEntry + { + public DependencyListEntry(DependencyNodeCore node, + string reason) + { + Node = node; + Reason = reason; + } + + public DependencyNodeCore Node; + public string Reason; + } + + public struct CombinedDependencyListEntry + { + public CombinedDependencyListEntry(DependencyNodeCore node, + DependencyNodeCore otherReasonNode, + string reason) + { + Node = node; + OtherReasonNode = otherReasonNode; + Reason = reason; + } + + // Used by HashSet, so must have good Equals/GetHashCode + public DependencyNodeCore Node; + public DependencyNodeCore OtherReasonNode; + public string Reason; + } + + public abstract bool InterestingForDynamicDependencyAnalysis + { + get; + } + + public abstract bool HasDynamicDependencies + { + get; + } + + public abstract bool HasConditionalStaticDependencies + { + get; + } + + public abstract bool StaticDependenciesAreComputed + { + get; + } + + public abstract IEnumerable GetStaticDependencies(DependencyContextType context); + + public abstract IEnumerable GetConditionalStaticDependencies(DependencyContextType context); + + public abstract IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, DependencyContextType context); + } +} diff --git a/src/ILToNative.DependencyAnalysisFramework/src/DgmlWriter.cs b/src/ILToNative.DependencyAnalysisFramework/src/DgmlWriter.cs new file mode 100644 index 00000000000..e6dcbc9abc6 --- /dev/null +++ b/src/ILToNative.DependencyAnalysisFramework/src/DgmlWriter.cs @@ -0,0 +1,183 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using System.IO; +using System.Diagnostics; + +namespace ILToNative.DependencyAnalysisFramework +{ + public class DgmlWriter : IDisposable, IDependencyAnalyzerLogEdgeVisitor, IDependencyAnalyzerLogNodeVisitor + { + XmlWriter _xmlWrite; + bool _done = false; + public DgmlWriter(XmlWriter xmlWrite) + { + _xmlWrite = xmlWrite; + _xmlWrite.WriteStartDocument(); + _xmlWrite.WriteStartElement("DirectedGraph", "http://schemas.microsoft.com/vs/2009/dgml"); + } + + public void WriteNodesAndEdges(Action> nodeWriter, Action> edgeWriter) + { + + _xmlWrite.WriteStartElement("Nodes"); + { + nodeWriter(AddNode); + } + _xmlWrite.WriteEndElement(); + + _xmlWrite.WriteStartElement("Links"); + { + edgeWriter(AddReason); + } + _xmlWrite.WriteEndElement(); + } + + public static void WriteDependencyGraphToStream(Stream stream, DependencyAnalyzerBase analysis) + { + XmlWriterSettings writerSettings = new XmlWriterSettings(); + writerSettings.Indent = true; + writerSettings.IndentChars = " "; + + using (XmlWriter xmlWriter = XmlWriter.Create(stream, writerSettings)) + { + using (DgmlWriter dgmlWriter = new DgmlWriter(xmlWriter)) + { + dgmlWriter.WriteNodesAndEdges((Action writeNode) => + { + analysis.VisitLogNodes(dgmlWriter); + }, + (Action writeEdge) => + { + analysis.VisitLogEdges(dgmlWriter); + } + ); + } + } + } + + public void Close() + { + if (!_done) + { + _done = true; + _xmlWrite.WriteStartElement("Properties"); + { + _xmlWrite.WriteStartElement("Property"); + _xmlWrite.WriteAttributeString("Id", "Label"); + _xmlWrite.WriteAttributeString("Label", "Label"); + _xmlWrite.WriteAttributeString("DataType", "String"); + _xmlWrite.WriteEndElement(); + + _xmlWrite.WriteStartElement("Property"); + _xmlWrite.WriteAttributeString("Id", "Reason"); + _xmlWrite.WriteAttributeString("Label", "Reason"); + _xmlWrite.WriteAttributeString("DataType", "String"); + _xmlWrite.WriteEndElement(); + } + _xmlWrite.WriteEndElement(); + + _xmlWrite.WriteEndElement(); + _xmlWrite.WriteEndDocument(); + } + } + + void IDisposable.Dispose() + { + Close(); + } + + Dictionary _nodeMappings = new Dictionary(); + int _nodeNextId = 0; + + void AddNode(object node) + { + int nodeId = _nodeNextId++; + Debug.Assert(!_nodeMappings.ContainsKey(node)); + + _nodeMappings.Add(node, nodeId); + + _xmlWrite.WriteStartElement("Node"); + _xmlWrite.WriteAttributeString("Id", nodeId.ToString()); + _xmlWrite.WriteAttributeString("Label", node.ToString()); + _xmlWrite.WriteEndElement(); + } + + void AddReason(object nodeA, object nodeB, string reason) + { + _xmlWrite.WriteStartElement("Link"); + _xmlWrite.WriteAttributeString("Source", _nodeMappings[nodeA].ToString()); + _xmlWrite.WriteAttributeString("Target", _nodeMappings[nodeB].ToString()); + _xmlWrite.WriteAttributeString("Reason", reason); + _xmlWrite.WriteEndElement(); + } + + void IDependencyAnalyzerLogEdgeVisitor.VisitEdge(DependencyNode nodeDepender, DependencyNode nodeDependedOn, string reason) + { + _xmlWrite.WriteStartElement("Link"); + _xmlWrite.WriteAttributeString("Source", _nodeMappings[nodeDepender].ToString()); + _xmlWrite.WriteAttributeString("Target", _nodeMappings[nodeDependedOn].ToString()); + _xmlWrite.WriteAttributeString("Reason", reason); + _xmlWrite.WriteAttributeString("Stroke", "#FF0000"); + _xmlWrite.WriteEndElement(); + } + + void IDependencyAnalyzerLogEdgeVisitor.VisitEdge(string root, DependencyNode dependedOn) + { + AddReason(root, dependedOn, null); + } + + void IDependencyAnalyzerLogNodeVisitor.VisitCombinedNode(Tuple node) + { + AddNode(node); + } + + HashSet> _combinedNodesEdgeVisited = new HashSet>(); + + void IDependencyAnalyzerLogEdgeVisitor.VisitEdge(DependencyNode nodeDepender, DependencyNode nodeDependerOther, DependencyNode nodeDependedOn, string reason) + { + Tuple combinedNode = new Tuple(nodeDepender, nodeDependerOther); + if (!_combinedNodesEdgeVisited.Contains(combinedNode)) + { + _combinedNodesEdgeVisited.Add(combinedNode); + + _xmlWrite.WriteStartElement("Link"); + _xmlWrite.WriteAttributeString("Source", _nodeMappings[nodeDepender].ToString()); + _xmlWrite.WriteAttributeString("Target", _nodeMappings[combinedNode].ToString()); + _xmlWrite.WriteAttributeString("Reason", "Primary"); + _xmlWrite.WriteAttributeString("Stroke", "#00FF00"); + _xmlWrite.WriteEndElement(); + + _xmlWrite.WriteStartElement("Link"); + _xmlWrite.WriteAttributeString("Source", _nodeMappings[nodeDependerOther].ToString()); + _xmlWrite.WriteAttributeString("Target", _nodeMappings[combinedNode].ToString()); + _xmlWrite.WriteAttributeString("Reason", "Secondary"); + _xmlWrite.WriteAttributeString("Stroke", "#00FF00"); + _xmlWrite.WriteEndElement(); + } + + _xmlWrite.WriteStartElement("Link"); + _xmlWrite.WriteAttributeString("Source", _nodeMappings[combinedNode].ToString()); + _xmlWrite.WriteAttributeString("Target", _nodeMappings[nodeDependedOn].ToString()); + _xmlWrite.WriteAttributeString("Reason", reason); + _xmlWrite.WriteAttributeString("Stroke", "#0000FF"); + _xmlWrite.WriteEndElement(); + } + + void IDependencyAnalyzerLogNodeVisitor.VisitNode(DependencyNode node) + { + AddNode(node); + } + + void IDependencyAnalyzerLogNodeVisitor.VisitRootNode(string rootName) + { + AddNode(rootName); + } + } +} diff --git a/src/ILToNative.DependencyAnalysisFramework/src/FirstMarkLogStrategy.cs b/src/ILToNative.DependencyAnalysisFramework/src/FirstMarkLogStrategy.cs new file mode 100644 index 00000000000..f51fbf82f20 --- /dev/null +++ b/src/ILToNative.DependencyAnalysisFramework/src/FirstMarkLogStrategy.cs @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using System.IO; +using System.Diagnostics; + +namespace ILToNative.DependencyAnalysisFramework +{ + public struct FirstMarkLogStrategy : IDependencyAnalysisMarkStrategy + { + private class MarkData + { + public MarkData(string reason, DependencyNodeCore reason1, DependencyNodeCore reason2) + { + Reason = reason; + Reason1 = reason1; + Reason2 = reason2; + } + + public string Reason + { + get; + private set; + } + + public DependencyNodeCore Reason1 + { + get; + private set; + } + + public DependencyNodeCore Reason2 + { + get; + private set; + } + } + + private HashSet _reasonStringOnlyNodes; + + bool IDependencyAnalysisMarkStrategy.MarkNode( + DependencyNodeCore node, + DependencyNodeCore reasonNode, + DependencyNodeCore reasonNode2, + string reason) + { + if (node.Marked) + return false; + + if ((reasonNode == null) && (reasonNode2 == null)) + { + Debug.Assert(reason != null); + if (_reasonStringOnlyNodes == null) + _reasonStringOnlyNodes = new HashSet(); + + _reasonStringOnlyNodes.Add(reason); + } + + node.SetMark(new MarkData(reason, reasonNode, reasonNode2)); + return true; + } + + void IDependencyAnalysisMarkStrategy.VisitLogNodes(IEnumerable> nodeList, IDependencyAnalyzerLogNodeVisitor logNodeVisitor) + { + HashSet> combinedNodesReported = new HashSet>(); + + if (_reasonStringOnlyNodes != null) + { + foreach (string reasonOnly in _reasonStringOnlyNodes) + { + logNodeVisitor.VisitRootNode(reasonOnly); + } + } + + foreach (DependencyNodeCore node in nodeList) + { + if (node.Marked) + { + MarkData markData = (MarkData)node.GetMark(); + + if (markData.Reason2 != null) + { + Tuple combinedNode = new Tuple(markData.Reason1, markData.Reason2); + + if (!combinedNodesReported.Contains(combinedNode)) + { + logNodeVisitor.VisitCombinedNode(combinedNode); + } + } + } + } + } + + void IDependencyAnalysisMarkStrategy.VisitLogEdges(IEnumerable> nodeList, IDependencyAnalyzerLogEdgeVisitor logEdgeVisitor) + { + foreach (DependencyNodeCore node in nodeList) + { + if (node.Marked) + { + MarkData markData = (MarkData)node.GetMark(); + + if (markData.Reason2 != null) + { + Debug.Assert(markData.Reason1 != null); + logEdgeVisitor.VisitEdge(markData.Reason1, markData.Reason2, node, markData.Reason); + } + else if (markData.Reason1 != null) + { + logEdgeVisitor.VisitEdge(markData.Reason1, node, markData.Reason); + } + else + { + Debug.Assert(markData.Reason != null); + logEdgeVisitor.VisitEdge(markData.Reason, node); + } + } + } + } + } +} diff --git a/src/ILToNative.DependencyAnalysisFramework/src/FullGraphLogStrategy.cs b/src/ILToNative.DependencyAnalysisFramework/src/FullGraphLogStrategy.cs new file mode 100644 index 00000000000..4754faba59f --- /dev/null +++ b/src/ILToNative.DependencyAnalysisFramework/src/FullGraphLogStrategy.cs @@ -0,0 +1,201 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using System.Diagnostics; + +namespace ILToNative.DependencyAnalysisFramework +{ + public struct FullGraphLogStrategy : IDependencyAnalysisMarkStrategy + { + private sealed class MarkData : IEquatable + { + public MarkData(string reason, DependencyNodeCore reason1, DependencyNodeCore reason2) + { + Reason = reason; + Reason1 = reason1; + Reason2 = reason2; + } + + public string Reason + { + get; + private set; + } + + public DependencyNodeCore Reason1 + { + get; + private set; + } + + public DependencyNodeCore Reason2 + { + get; + private set; + } + + private static int CombineHashCodes(int h1, int h2) + { + return (((h1 << 5) + h1) ^ h2); + } + + private static int CombineHashCodes(int h1, int h2, int h3) + { + return CombineHashCodes(CombineHashCodes(h1, h2), h3); + } + + public override int GetHashCode() + { + int reasonHashCode = Reason != null ? Reason.GetHashCode() : 0; + int reason1HashCode = Reason1 != null ? Reason1.GetHashCode() : 0; + int reason2HashCode = Reason2 != null ? Reason2.GetHashCode() : 0; + + return CombineHashCodes(reasonHashCode, reason1HashCode, reason2HashCode); + } + + public override bool Equals(object obj) + { + MarkData other = obj as MarkData; + if (other == null) + return false; + + return Equals(other); + } + + public bool Equals(MarkData other) + { + if (Reason1 != other.Reason1) + return false; + + if (Reason2 != other.Reason2) + return false; + + if (Reason == other.Reason) + return true; + + if (Reason != null) + { + return Reason.Equals(other.Reason); + } + return false; + } + } + + private sealed class MarkDataEqualityComparer : IEqualityComparer + { + public bool Equals(MarkData x, MarkData y) + { + return x.Equals(y); + } + + public int GetHashCode(MarkData obj) + { + return obj.GetHashCode(); + } + + public static IEqualityComparer Default = new MarkDataEqualityComparer(); + } + + private HashSet _reasonStringOnlyNodes; + + bool IDependencyAnalysisMarkStrategy.MarkNode( + DependencyNodeCore node, + DependencyNodeCore reasonNode, + DependencyNodeCore reasonNode2, + string reason) + { + bool newlyMarked = !node.Marked; + + HashSet associatedNodes; + if (newlyMarked) + { + associatedNodes = new HashSet(MarkDataEqualityComparer.Default); + node.SetMark(associatedNodes); + } + else + { + associatedNodes = (HashSet)node.GetMark(); + } + + if ((reasonNode == null) && (reasonNode2 == null)) + { + Debug.Assert(reason != null); + if (_reasonStringOnlyNodes == null) + _reasonStringOnlyNodes = new HashSet(); + + _reasonStringOnlyNodes.Add(reason); + } + + associatedNodes.Add(new MarkData(reason, reasonNode, reasonNode2)); + return newlyMarked; + } + + void IDependencyAnalysisMarkStrategy.VisitLogNodes(IEnumerable> nodeList, IDependencyAnalyzerLogNodeVisitor logNodeVisitor) + { + HashSet> combinedNodesReported = new HashSet>(); + + if (_reasonStringOnlyNodes != null) + { + foreach (string reasonOnly in _reasonStringOnlyNodes) + { + logNodeVisitor.VisitRootNode(reasonOnly); + } + } + + foreach (DependencyNodeCore node in nodeList) + { + if (node.Marked) + { + HashSet nodeReasons = (HashSet)node.GetMark(); + foreach (MarkData markData in nodeReasons) + { + if (markData.Reason2 != null) + { + Tuple combinedNode = new Tuple(markData.Reason1, markData.Reason2); + + if (!combinedNodesReported.Contains(combinedNode)) + { + logNodeVisitor.VisitCombinedNode(combinedNode); + } + } + } + } + } + } + + void IDependencyAnalysisMarkStrategy.VisitLogEdges(IEnumerable> nodeList, IDependencyAnalyzerLogEdgeVisitor logEdgeVisitor) + { + foreach (DependencyNodeCore node in nodeList) + { + if (node.Marked) + { + HashSet nodeReasons = (HashSet)node.GetMark(); + foreach (MarkData markData in nodeReasons) + { + if (markData.Reason2 != null) + { + Debug.Assert(markData.Reason1 != null); + logEdgeVisitor.VisitEdge(markData.Reason1, markData.Reason2, node, markData.Reason); + } + else if (markData.Reason1 != null) + { + logEdgeVisitor.VisitEdge(markData.Reason1, node, markData.Reason); + } + else + { + Debug.Assert(markData.Reason != null); + logEdgeVisitor.VisitEdge(markData.Reason, node); + } + } + } + } + } + } +} diff --git a/src/ILToNative.DependencyAnalysisFramework/src/IDependencyAnalysisMarkStrategy.cs b/src/ILToNative.DependencyAnalysisFramework/src/IDependencyAnalysisMarkStrategy.cs new file mode 100644 index 00000000000..67d9337c79f --- /dev/null +++ b/src/ILToNative.DependencyAnalysisFramework/src/IDependencyAnalysisMarkStrategy.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; + +namespace ILToNative.DependencyAnalysisFramework +{ + public interface IDependencyAnalysisMarkStrategy + { + /// + /// Use the provided node, reasonNode, reasonNode2, and reason to mark a node + /// + /// + /// + /// + /// + /// true if the node is newly marked + bool MarkNode(DependencyNodeCore node, DependencyNodeCore reasonNode, DependencyNodeCore reasonNode2, string reason); + + void VisitLogNodes(IEnumerable> nodeList, IDependencyAnalyzerLogNodeVisitor logNodeVisitor); + + void VisitLogEdges(IEnumerable> nodeList, IDependencyAnalyzerLogEdgeVisitor logEdgeVisitor); + } +} diff --git a/src/ILToNative.DependencyAnalysisFramework/src/IDependencyAnalyzerLogEdgeVisitor.cs b/src/ILToNative.DependencyAnalysisFramework/src/IDependencyAnalyzerLogEdgeVisitor.cs new file mode 100644 index 00000000000..ba919a8254b --- /dev/null +++ b/src/ILToNative.DependencyAnalysisFramework/src/IDependencyAnalyzerLogEdgeVisitor.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ILToNative.DependencyAnalysisFramework +{ + public interface IDependencyAnalyzerLogEdgeVisitor + { + void VisitEdge(DependencyNode nodeDepender, DependencyNode nodeDependedOn, string reason); + void VisitEdge(string root, DependencyNode dependedOn); + void VisitEdge(DependencyNode nodeDepender, DependencyNode nodeDependerOther, DependencyNode nodeDependedOn, string reason); + } +} diff --git a/src/ILToNative.DependencyAnalysisFramework/src/IDependencyAnalyzerLogNodeVisitor.cs b/src/ILToNative.DependencyAnalysisFramework/src/IDependencyAnalyzerLogNodeVisitor.cs new file mode 100644 index 00000000000..b7360854106 --- /dev/null +++ b/src/ILToNative.DependencyAnalysisFramework/src/IDependencyAnalyzerLogNodeVisitor.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace ILToNative.DependencyAnalysisFramework +{ + public interface IDependencyAnalyzerLogNodeVisitor + { + void VisitCombinedNode(Tuple node); + void VisitNode(DependencyNode node); + void VisitRootNode(string rootName); + } +} diff --git a/src/ILToNative.DependencyAnalysisFramework/src/ILToNative.DependencyAnalysisFramework.csproj b/src/ILToNative.DependencyAnalysisFramework/src/ILToNative.DependencyAnalysisFramework.csproj new file mode 100644 index 00000000000..00acee36395 --- /dev/null +++ b/src/ILToNative.DependencyAnalysisFramework/src/ILToNative.DependencyAnalysisFramework.csproj @@ -0,0 +1,37 @@ + + + + + Debug + AnyCPU + {DAC23E9F-F826-4577-AE7A-0849FF83280C} + Library + Properties + ILToNative.DependencyAnalysisFramework + ILToNative.DependencyAnalysisFramework + 512 + true + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ILToNative.DependencyAnalysisFramework/src/NoLogStrategy.cs b/src/ILToNative.DependencyAnalysisFramework/src/NoLogStrategy.cs new file mode 100644 index 00000000000..c2d92640756 --- /dev/null +++ b/src/ILToNative.DependencyAnalysisFramework/src/NoLogStrategy.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ILToNative.DependencyAnalysisFramework +{ + /// + /// Very memory efficient, and potentially faster mark strategy that eschews keeping track of what caused what to exist + /// + /// + public struct NoLogStrategy : IDependencyAnalysisMarkStrategy + { + private static object s_singleton = new object(); + + bool IDependencyAnalysisMarkStrategy.MarkNode( + DependencyNodeCore node, + DependencyNodeCore reasonNode, + DependencyNodeCore reasonNode2, + string reason) + { + if (node.Marked) + return false; + + node.SetMark(s_singleton); + return true; + } + + void IDependencyAnalysisMarkStrategy.VisitLogEdges(IEnumerable> nodeList, IDependencyAnalyzerLogEdgeVisitor logEdgeVisitor) + { + // This marker does not permit logging. + return; + } + + void IDependencyAnalysisMarkStrategy.VisitLogNodes(IEnumerable> nodeList, IDependencyAnalyzerLogNodeVisitor logNodeVisitor) + { + // This marker does not permit logging. + return; + } + } +} diff --git a/src/ILToNative.DependencyAnalysisFramework/src/project.json b/src/ILToNative.DependencyAnalysisFramework/src/project.json new file mode 100644 index 00000000000..72e995da3a4 --- /dev/null +++ b/src/ILToNative.DependencyAnalysisFramework/src/project.json @@ -0,0 +1,22 @@ +{ + "dependencies": { + "System.Runtime": "4.0.0", + "System.Resources.ResourceManager": "4.0.0", + "System.Reflection.Primitives": "4.0.0", + "System.Diagnostics.Debug": "4.0.0", + "System.IO": "4.0.0", + "System.Collections.Immutable": "1.1.37", + "System.Collections": "4.0.0", + "System.Text.Encoding": "4.0.0", + "System.Runtime.InteropServices": "4.0.0", + "System.Reflection": "4.0.0", + "System.Runtime.Extensions": "4.0.0", + "System.Threading": "4.0.0", + "System.Text.Encoding.Extensions": "4.0.0", + "System.Reflection.Extensions": "4.0.0", + "System.Xml.ReaderWriter": "4.0.0" + }, + "frameworks": { + "dotnet": {} + } +} \ No newline at end of file diff --git a/src/ILToNative.DependencyAnalysisFramework/tests/DependencyAnalysisFrameworkTests.cs b/src/ILToNative.DependencyAnalysisFramework/tests/DependencyAnalysisFrameworkTests.cs new file mode 100644 index 00000000000..70c379cca61 --- /dev/null +++ b/src/ILToNative.DependencyAnalysisFramework/tests/DependencyAnalysisFrameworkTests.cs @@ -0,0 +1,202 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; + +using ILToNative.DependencyAnalysisFramework; + +using Xunit; + +namespace ILToNative.DependencyAnalysisFramework.Tests +{ + public class DependencyTests + { + public DependencyTests() + { + } + + /// + /// Test on every graph type. Used to ensure that the behavior of the various markers is consistent + /// + /// + private void TestOnGraphTypes(Action> testGraph) + { + // Test using the full logging strategy + TestGraph testGraphFull = new TestGraph(); + DependencyAnalyzerBase analyzerFull = new DependencyAnalyzer, TestGraph>(testGraphFull, null); + testGraphFull.AttachToDependencyAnalyzer(analyzerFull); + testGraph(testGraphFull, analyzerFull); + + TestGraph testGraphFirstMark = new TestGraph(); + DependencyAnalyzerBase analyzerFirstMark = new DependencyAnalyzer, TestGraph>(testGraphFirstMark, null); + testGraphFirstMark.AttachToDependencyAnalyzer(analyzerFirstMark); + testGraph(testGraphFirstMark, analyzerFirstMark); + + TestGraph testGraphNoLog = new TestGraph(); + DependencyAnalyzerBase analyzerNoLog = new DependencyAnalyzer, TestGraph>(testGraphNoLog, null); + testGraphNoLog.AttachToDependencyAnalyzer(analyzerNoLog); + testGraph(testGraphNoLog, analyzerNoLog); + } + + [Fact] + public void TestADependsOnB() + { + TestOnGraphTypes((TestGraph testGraph, DependencyAnalyzerBase analyzer) => + { + testGraph.AddStaticRule("A", "B", "A depends on B"); + testGraph.AddRoot("A", "A is root"); + List results = testGraph.AnalysisResults; + + Assert.True(results.Contains("A")); + Assert.True(results.Contains("B")); + }); + } + + [Fact] + public void TestADependsOnBIfC_NoC() + { + TestOnGraphTypes((TestGraph testGraph, DependencyAnalyzerBase analyzer) => + { + testGraph.AddConditionalRule("A", "C", "B", "A depends on B if C"); + testGraph.AddRoot("A", "A is root"); + List results = testGraph.AnalysisResults; + + Assert.True(results.Contains("A")); + Assert.False(results.Contains("B")); + Assert.False(results.Contains("C")); + Assert.True(results.Count == 1); + }); + } + + [Fact] + public void TestADependsOnBIfC_HasC() + { + TestOnGraphTypes((TestGraph testGraph, DependencyAnalyzerBase analyzer) => + { + testGraph.AddConditionalRule("A", "C", "B", "A depends on B if C"); + testGraph.AddRoot("A", "A is root"); + testGraph.AddRoot("C", "C is root"); + List results = testGraph.AnalysisResults; + + Assert.True(results.Contains("A")); + Assert.True(results.Contains("B")); + Assert.True(results.Contains("C")); + Assert.True(results.Count == 3); + }); + } + + [Fact] + public void TestSimpleDynamicRule() + { + TestOnGraphTypes((TestGraph testGraph, DependencyAnalyzerBase analyzer) => + { + testGraph.SetDynamicDependencyRule((string nodeA, string nodeB) => + { + if (nodeA.EndsWith("*") && nodeB.StartsWith("*")) + { + return new Tuple(nodeA + nodeB, "DynamicRule"); + } + return null; + }); + + testGraph.AddRoot("A*", "A* is root"); + testGraph.AddRoot("B*", "B* is root"); + testGraph.AddRoot("*C", "*C is root"); + testGraph.AddRoot("*D", "*D is root"); + testGraph.AddRoot("A*B", "A*B is root"); + List results = testGraph.AnalysisResults; + + Assert.Contains("A*", results); + Assert.Contains("B*", results); + Assert.Contains("*C", results); + Assert.Contains("*D", results); + Assert.Contains("A*B", results); + Assert.Contains("A**C", results); + Assert.Contains("A**D", results); + Assert.Contains("B**C", results); + Assert.Contains("B**D", results); + Assert.True(results.Count == 9); + }); + } + + private void BuildGraphUsingAllTypesOfRules(TestGraph testGraph, DependencyAnalyzerBase analyzer) + { + testGraph.SetDynamicDependencyRule((string nodeA, string nodeB) => + { + if (nodeA.EndsWith("*") && nodeB.StartsWith("*")) + { + return new Tuple(nodeA + nodeB, "DynamicRule"); + } + return null; + }); + + testGraph.AddConditionalRule("A**C", "B**D", "D", "A**C depends on D if B**D"); + testGraph.AddStaticRule("D", "E", "D depends on E"); + + // Rules to ensure that there are some nodes that have multiple reasons to exist + testGraph.AddStaticRule("A*", "E", "A* depends on E"); + testGraph.AddStaticRule("*C", "E", "*C depends on E"); + + testGraph.AddRoot("A*", "A* is root"); + testGraph.AddRoot("B*", "B* is root"); + testGraph.AddRoot("*C", "*C is root"); + testGraph.AddRoot("*D", "*D is root"); + testGraph.AddRoot("A*B", "A*B is root"); + + List results = testGraph.AnalysisResults; + Assert.Contains("A*", results); + Assert.Contains("B*", results); + Assert.Contains("*C", results); + Assert.Contains("*D", results); + Assert.Contains("A*B", results); + Assert.Contains("A**C", results); + Assert.Contains("A**D", results); + Assert.Contains("B**C", results); + Assert.Contains("B**D", results); + Assert.Contains("D", results); + Assert.Contains("E", results); + Assert.True(results.Count == 11); + } + + [Fact] + public void TestDGMLOutput() + { + Dictionary dgmlOutputs = new Dictionary(); + TestOnGraphTypes((TestGraph testGraph, DependencyAnalyzerBase analyzer) => + { + BuildGraphUsingAllTypesOfRules(testGraph, analyzer); + MemoryStream dgmlOutput = new MemoryStream(); + DgmlWriter.WriteDependencyGraphToStream(dgmlOutput, analyzer); + dgmlOutput.Seek(0, SeekOrigin.Begin); + TextReader tr = new StreamReader(dgmlOutput); + dgmlOutputs[analyzer.GetType().FullName] = tr.ReadToEnd(); + }); + + foreach (var pair in dgmlOutputs) + { + int nodeCount = pair.Value.Split(new string[] { " + + + + Debug + AnyCPU + {90076B9B-918B-49DD-8ADE-E76426D60B4D} + Library + ILToNative.DependencyAnalysisFramework.Tests + ILToNative.DependencyAnalysisFramework.Tests + + + + + + + + + {DAC23E9F-F826-4577-AE7A-0849FF83280C} + ILToNative.DependencyAnalysisFramework + + + + + + + + + + + \ No newline at end of file diff --git a/src/ILToNative.DependencyAnalysisFramework/tests/TestGraph.cs b/src/ILToNative.DependencyAnalysisFramework/tests/TestGraph.cs new file mode 100644 index 00000000000..c6adac1bc43 --- /dev/null +++ b/src/ILToNative.DependencyAnalysisFramework/tests/TestGraph.cs @@ -0,0 +1,174 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ILToNative.DependencyAnalysisFramework; + +namespace ILToNative.DependencyAnalysisFramework.Tests +{ + class TestGraph + { + public class TestNode : ComputedStaticDependencyNode + { + private readonly string _data; + private readonly static CombinedDependencyListEntry[] s_emptyDynamicList = new CombinedDependencyListEntry[0]; + + public TestNode(string data) + { + _data = data; + } + + public string Data + { + get + { + return _data; + } + } + + public override string ToString() + { + return _data; + } + + public override bool HasDynamicDependencies + { + get + { + return true; + } + } + + public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, TestGraph context) + { + if (context._dynamicDependencyComputer == null) + return s_emptyDynamicList; + + IEnumerable returnValue = s_emptyDynamicList; + List returnValueWithData = null; + for (int i = firstNode; i < markedNodes.Count; i++) + { + Tuple nextResult = context._dynamicDependencyComputer(this.Data, ((TestNode)markedNodes[i]).Data); + + if (nextResult != null) + { + if (returnValueWithData == null) + { + returnValueWithData = new List.CombinedDependencyListEntry>(); + returnValue = returnValueWithData; + } + + returnValueWithData.Add(new CombinedDependencyListEntry(context.GetNode(nextResult.Item1), markedNodes[i], nextResult.Item2)); + } + } + + return returnValue; + } + } + + Dictionary>> _staticNonConditionalRules = new Dictionary>>(); + Dictionary>>> _staticConditionalRules = new Dictionary>>>(); + + Func> _dynamicDependencyComputer; + Dictionary _nodes = new Dictionary(); + DependencyAnalyzerBase _analyzer; + + public void AddStaticRule(string depender, string dependedOn, string reason) + { + HashSet> knownEdges = null; + if (!_staticNonConditionalRules.TryGetValue(depender, out knownEdges)) + { + knownEdges = new HashSet>(); + _staticNonConditionalRules[depender] = knownEdges; + } + knownEdges.Add(new Tuple(dependedOn, reason)); + } + + public void AddConditionalRule(string depender, string otherdepender, string dependedOn, string reason) + { + HashSet>> knownEdges = null; + if (!_staticConditionalRules.TryGetValue(depender, out knownEdges)) + { + knownEdges = new HashSet>>(); + _staticConditionalRules[depender] = knownEdges; + } + + knownEdges.Add(new Tuple>(dependedOn, new Tuple(otherdepender, reason))); + } + + public void SetDynamicDependencyRule(Func> dynamicDependencyComputer) + { + _dynamicDependencyComputer = dynamicDependencyComputer; + } + + public void AddRoot(string nodeNode, string reason) + { + _analyzer.AddRoot(this.GetNode(nodeNode), reason); + } + + public TestNode GetNode(string nodeName) + { + TestNode node; + if (!_nodes.TryGetValue(nodeName, out node)) + { + node = new TestNode(nodeName); + _nodes.Add(nodeName, node); + } + + return node; + } + + public void AttachToDependencyAnalyzer(DependencyAnalyzerBase analyzer) + { + analyzer.ComputeDependencyRoutine += Analyzer_ComputeDependencyRoutine; + _analyzer = analyzer; + } + + private void Analyzer_ComputeDependencyRoutine(List> obj) + { + foreach (TestNode node in obj) + { + List staticList = new List.DependencyListEntry>(); + List conditionalStaticList = new List.CombinedDependencyListEntry>(); + + HashSet> nonConditionalRules; + if (_staticNonConditionalRules.TryGetValue(node.Data, out nonConditionalRules)) + { + foreach (Tuple dependedOn in nonConditionalRules) + { + staticList.Add(new TestNode.DependencyListEntry(GetNode(dependedOn.Item1), dependedOn.Item2)); + } + } + + HashSet>> conditionalRules; + if (_staticConditionalRules.TryGetValue(node.Data, out conditionalRules)) + { + foreach (Tuple> dependedOn in conditionalRules) + { + conditionalStaticList.Add(new TestNode.CombinedDependencyListEntry(GetNode(dependedOn.Item1), GetNode(dependedOn.Item2.Item1), dependedOn.Item2.Item2)); + } + } + + node.SetStaticDependencies(staticList, conditionalStaticList); + } + } + + public List AnalysisResults + { + get + { + List liveNodes = new List(); + foreach (var node in _analyzer.MarkedNodeList) + { + liveNodes.Add(((TestNode)node).Data); + } + + return liveNodes; + } + } + } +} diff --git a/src/ILToNative.DependencyAnalysisFramework/tests/project.json b/src/ILToNative.DependencyAnalysisFramework/tests/project.json new file mode 100644 index 00000000000..0bb9854484e --- /dev/null +++ b/src/ILToNative.DependencyAnalysisFramework/tests/project.json @@ -0,0 +1,21 @@ +{ + "dependencies": { + "System.Collections": "4.0.10", + "System.Collections.Immutable": "1.1.37", + "System.Console": "4.0.0-beta-*", + "System.Diagnostics.Debug": "4.0.10", + "System.Diagnostics.Tracing": "4.0.20", + "System.Linq": "4.0.0", + "System.IO.FileSystem": "4.0.0", + "System.Reflection": "4.0.10", + "System.Runtime": "4.0.20", + "System.Runtime.Extensions": "4.0.10", + "System.Threading": "4.0.10", + "System.Threading.Tasks": "4.0.10", + "xunit": "2.1.0-beta3-*", + "xunit.netcore.extensions": "1.0.0-prerelease-*", + }, + "frameworks": { + "dotnet": {} + } +} \ No newline at end of file diff --git a/src/ILToNative/ILToNative.sln b/src/ILToNative/ILToNative.sln index 9311f75e3e6..c8c35092f1b 100644 --- a/src/ILToNative/ILToNative.sln +++ b/src/ILToNative/ILToNative.sln @@ -1,13 +1,14 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.23107.0 +VisualStudioVersion = 14.0.23023.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ILToNative", "src\ILToNative.csproj", "{DD5B6BAA-D41A-4A6E-9E7D-83060F394B10}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "repro", "repro\repro.csproj", "{FBA029C3-B184-4457-AEEC-38D0C2523067}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ILToNative.TypeSystem", "..\ILToNative.TypeSystem\src\ILToNative.TypeSystem.csproj", "{1A9DF196-43A9-44BB-B2C6-D62AA56B0E49}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ILToNative.DependencyAnalysisFramework", "..\ILToNative.DependencyAnalysisFramework\src\ILToNative.DependencyAnalysisFramework.csproj", "{DAC23E9F-F826-4577-AE7A-0849FF83280C}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ILToNative.Compiler", "..\ILToNative.Compiler\src\ILToNative.Compiler.csproj", "{13BB3788-C3EB-4046-8105-A95F8AE49404}" EndProject @@ -39,6 +40,10 @@ Global {B2C35178-5E42-4010-A72A-938711D7F8F0}.Debug|x64.Build.0 = Debug|x64 {B2C35178-5E42-4010-A72A-938711D7F8F0}.Release|x64.ActiveCfg = Release|x64 {B2C35178-5E42-4010-A72A-938711D7F8F0}.Release|x64.Build.0 = Release|x64 + {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Debug|x64.ActiveCfg = Debug|Any CPU + {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Debug|x64.Build.0 = Debug|Any CPU + {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Release|x64.ActiveCfg = Release|Any CPU + {DAC23E9F-F826-4577-AE7A-0849FF83280C}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE