diff --git a/tools/linker/CoreTypeMapStep.cs b/tools/linker/CoreTypeMapStep.cs index 89c2fa35adda..272e1e3926e9 100644 --- a/tools/linker/CoreTypeMapStep.cs +++ b/tools/linker/CoreTypeMapStep.cs @@ -9,6 +9,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using Mono.Cecil; using Mono.Linker.Steps; @@ -34,30 +35,82 @@ public class CoreTypeMapStep : Profile Profile => new Profile (Configuration); + // Get the set of assemblies transitively referenced from the input assembly, + // along with the reversed references for each assembly that's part of the + // transitively referenced set. + Dictionary> GetReverseReferenceGraph (AssemblyDefinition start) + { + var references = new Dictionary> { + { start, new HashSet () } + }; + var toProcess = new Queue (); + toProcess.Enqueue (start); + while (toProcess.TryDequeue (out var assembly)) { + foreach (var reference in assembly.MainModule.AssemblyReferences) { + var resolvedReference = Configuration.Context.GetLoadedAssembly (reference.Name); + if (resolvedReference == null) + continue; + + if (!references.TryGetValue (resolvedReference, out var referrers)) { + referrers = new HashSet (); + references.Add (resolvedReference, referrers); + toProcess.Enqueue (resolvedReference); + } + + referrers.Add (assembly); + } + } + return references; + } + Dictionary _transitivelyReferencesProduct = new Dictionary (); - bool TransitivelyReferencesProduct (AssemblyDefinition assembly) + bool TransitivelyReferencesProduct (AssemblyDefinition start) { - if (_transitivelyReferencesProduct.TryGetValue (assembly, out bool result)) + if (_transitivelyReferencesProduct.TryGetValue (start, out bool result)) return result; - if (Profile.IsProductAssembly (assembly)) { - _transitivelyReferencesProduct.Add (assembly, true); - return true; - } - - foreach (var reference in assembly.MainModule.AssemblyReferences) { - var resolvedReference = Configuration.Context.GetLoadedAssembly (reference.Name); - if (resolvedReference == null) + // A depth-first search is insufficient because there are reference cycles, so we + // get the set of transitive references, and do a reverse BFS. + var references = GetReverseReferenceGraph (start); + var referencesProductToProcess = new Queue (); + + // We start the BFS from the product assembly or any references which are already known + // to reference the product assembly. + foreach (var reference in references.Keys) { + if (_transitivelyReferencesProduct.TryGetValue (reference, out bool referencesProduct)) { + if (referencesProduct) + referencesProductToProcess.Enqueue (reference); continue; + } + + if (Profile.IsProductAssembly (reference)) { + _transitivelyReferencesProduct.Add (reference, true); + referencesProductToProcess.Enqueue (reference); + } + } - if (TransitivelyReferencesProduct (resolvedReference)) { - _transitivelyReferencesProduct.Add (assembly, true); - return true; + // Scan the reverse references to find out which referencing assemblies + // are reachable from the product assembly (that is, transitively reference it). + while (referencesProductToProcess.TryDequeue (out var assembly)) { + foreach (var referrer in references[assembly]) { + if (_transitivelyReferencesProduct.TryGetValue (referrer, out bool referencesProduct)) { + Debug.Assert (referencesProduct); + // Any which were already determined to reference the product assembly + // don't need to be scanned again. + continue; + } + + _transitivelyReferencesProduct.Add (referrer, true); + referencesProductToProcess.Enqueue (referrer); } } - _transitivelyReferencesProduct.Add (assembly, false); - return false; + // Any remaining references that we didn't discover during the search + // don't reference the product. + foreach (var reference in references.Keys) + _transitivelyReferencesProduct.TryAdd (reference, false); + + return _transitivelyReferencesProduct[start]; } protected override void TryProcessAssembly (AssemblyDefinition assembly)