Browse Source

Add ResolveAsync() method to IAssemblyResolver interface.

DecompilerTypeSystem uses this to resolve/load multiple assemblies in parallel.

Unfortunately this doesn't gain us any performance yet in ILSpy because there we have a global assembly-loader-lock :(
pull/2301/head
Daniel Grunwald 5 years ago
parent
commit
d70bfe80d5
  1. 3
      ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs
  2. 11
      ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs
  3. 50
      ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs
  4. 50
      ILSpy/LoadedAssembly.cs
  5. 28
      ILSpy/LoadedPackage.cs

3
ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs

@ -23,6 +23,7 @@ using System.Reflection; @@ -23,6 +23,7 @@ using System.Reflection;
using System.Reflection.Metadata;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace ICSharpCode.Decompiler.Metadata
{
@ -47,6 +48,8 @@ namespace ICSharpCode.Decompiler.Metadata @@ -47,6 +48,8 @@ namespace ICSharpCode.Decompiler.Metadata
#if !VSADDIN
PEFile Resolve(IAssemblyReference reference);
PEFile ResolveModule(PEFile mainModule, string moduleName);
Task<PEFile> ResolveAsync(IAssemblyReference reference);
Task<PEFile> ResolveModuleAsync(PEFile mainModule, string moduleName);
#endif
}

11
ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs

@ -24,6 +24,7 @@ using System.Reflection.Metadata; @@ -24,6 +24,7 @@ using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace ICSharpCode.Decompiler.Metadata
{
@ -203,6 +204,16 @@ namespace ICSharpCode.Decompiler.Metadata @@ -203,6 +204,16 @@ namespace ICSharpCode.Decompiler.Metadata
}
return new PEFile(moduleFileName, new FileStream(moduleFileName, FileMode.Open, FileAccess.Read), streamOptions, metadataOptions);
}
public Task<PEFile> ResolveAsync(IAssemblyReference name)
{
return Task.Run(() => Resolve(name));
}
public Task<PEFile> ResolveModuleAsync(PEFile mainModule, string moduleName)
{
return Task.Run(() => ResolveModule(mainModule, moduleName));
}
#endif
public override bool IsSharedAssembly(IAssemblyReference reference, out string runtimePack)

50
ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs

@ -19,6 +19,7 @@ @@ -19,6 +19,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
@ -181,7 +182,11 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -181,7 +182,11 @@ namespace ICSharpCode.Decompiler.TypeSystem
// Load referenced assemblies and type-forwarder references.
// This is necessary to make .NET Core/PCL binaries work better.
var referencedAssemblies = new List<PEFile>();
var assemblyReferenceQueue = new Queue<(bool IsAssembly, PEFile MainModule, object Reference)>();
var assemblyReferenceQueue = new Queue<(bool IsAssembly, PEFile MainModule, object Reference, Task<PEFile> ResolveTask)>();
var comparer = KeyComparer.Create(((bool IsAssembly, PEFile MainModule, object Reference) reference) =>
reference.IsAssembly ? "A:" + ((AssemblyReference)reference.Reference).FullName :
"M:" + reference.Reference);
var assemblyReferencesInQueue = new HashSet<(bool IsAssembly, PEFile Parent, object Reference)>(comparer);
var mainMetadata = mainModule.Metadata;
foreach (var h in mainMetadata.GetModuleReferences())
{
@ -194,7 +199,7 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -194,7 +199,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
var file = mainMetadata.GetAssemblyFile(fileHandle);
if (mainMetadata.StringComparer.Equals(file.Name, moduleName) && file.ContainsMetadata)
{
assemblyReferenceQueue.Enqueue((false, mainModule, moduleName));
AddToQueue(false, mainModule, moduleName);
break;
}
}
@ -205,26 +210,12 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -205,26 +210,12 @@ namespace ICSharpCode.Decompiler.TypeSystem
}
foreach (var refs in mainModule.AssemblyReferences)
{
assemblyReferenceQueue.Enqueue((true, mainModule, refs));
AddToQueue(true, mainModule, refs);
}
var comparer = KeyComparer.Create(((bool IsAssembly, PEFile MainModule, object Reference) reference) =>
reference.IsAssembly ? "A:" + ((AssemblyReference)reference.Reference).FullName :
"M:" + reference.Reference);
var processedAssemblyReferences = new HashSet<(bool IsAssembly, PEFile Parent, object Reference)>(comparer);
while (assemblyReferenceQueue.Count > 0)
{
var asmRef = assemblyReferenceQueue.Dequeue();
if (!processedAssemblyReferences.Add(asmRef))
continue;
PEFile asm;
if (asmRef.IsAssembly)
{
asm = assemblyResolver.Resolve((AssemblyReference)asmRef.Reference);
}
else
{
asm = assemblyResolver.ResolveModule(asmRef.MainModule, (string)asmRef.Reference);
}
var asm = asmRef.ResolveTask.GetAwaiter().GetResult();
if (asm != null)
{
referencedAssemblies.Add(asm);
@ -235,11 +226,11 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -235,11 +226,11 @@ namespace ICSharpCode.Decompiler.TypeSystem
switch (exportedType.Implementation.Kind)
{
case SRM.HandleKind.AssemblyReference:
assemblyReferenceQueue.Enqueue((true, asm, new AssemblyReference(asm, (SRM.AssemblyReferenceHandle)exportedType.Implementation)));
AddToQueue(true, asm, new AssemblyReference(asm, (SRM.AssemblyReferenceHandle)exportedType.Implementation));
break;
case SRM.HandleKind.AssemblyFile:
var file = metadata.GetAssemblyFile((SRM.AssemblyFileHandle)exportedType.Implementation);
assemblyReferenceQueue.Enqueue((false, asm, metadata.GetString(file.Name)));
AddToQueue(false, asm, metadata.GetString(file.Name));
break;
}
}
@ -261,6 +252,25 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -261,6 +252,25 @@ namespace ICSharpCode.Decompiler.TypeSystem
}
this.MainModule = (MetadataModule)base.MainModule;
void AddToQueue(bool isAssembly, PEFile mainModule, object reference)
{
if (assemblyReferencesInQueue.Add((isAssembly, mainModule, reference)))
{
// Immediately start loading the referenced module as we add the entry to the queue.
// This allows loading multiple modules in parallel.
Task<PEFile> asm;
if (isAssembly)
{
asm = assemblyResolver.ResolveAsync((AssemblyReference)reference);
}
else
{
asm = assemblyResolver.ResolveModuleAsync(mainModule, (string)reference);
}
assemblyReferenceQueue.Enqueue((isAssembly, mainModule, reference, asm));
}
}
bool IsMissing(KnownTypeReference knownType)
{
var name = knownType.TypeName;

50
ILSpy/LoadedAssembly.cs

@ -158,6 +158,24 @@ namespace ICSharpCode.ILSpy @@ -158,6 +158,24 @@ namespace ICSharpCode.ILSpy
}
}
/// <summary>
/// Gets the <see cref="PEFile"/>.
/// Returns null in case of load errors.
/// </summary>
public async Task<PEFile> GetPEFileOrNullAsync()
{
try
{
var loadResult = await loadingTask.ConfigureAwait(false);
return loadResult.PEFile;
}
catch (Exception ex)
{
System.Diagnostics.Trace.TraceError(ex.ToString());
return null;
}
}
ICompilation typeSystem;
/// <summary>
@ -429,6 +447,38 @@ namespace ICSharpCode.ILSpy @@ -429,6 +447,38 @@ namespace ICSharpCode.ILSpy
return module;
return parent.LookupReferencedModule(mainModule, moduleName)?.GetPEFileOrNull();
}
public async Task<PEFile> ResolveAsync(IAssemblyReference reference)
{
if (parent.providedAssemblyResolver != null)
{
var module = await parent.providedAssemblyResolver.ResolveAsync(reference).ConfigureAwait(false);
if (module != null)
return module;
}
var asm = parent.LookupReferencedAssembly(reference);
if (asm != null)
{
return await asm.GetPEFileOrNullAsync().ConfigureAwait(false);
}
return null;
}
public async Task<PEFile> ResolveModuleAsync(PEFile mainModule, string moduleName)
{
if (parent.providedAssemblyResolver != null)
{
var module = await parent.providedAssemblyResolver.ResolveModuleAsync(mainModule, moduleName).ConfigureAwait(false);
if (module != null)
return module;
}
var asm = parent.LookupReferencedModule(mainModule, moduleName);
if (asm != null)
{
return await asm.GetPEFileOrNullAsync().ConfigureAwait(false);
}
return null;
}
}
readonly MyAssemblyResolver resolver;

28
ILSpy/LoadedPackage.cs

@ -240,6 +240,20 @@ namespace ICSharpCode.ILSpy @@ -240,6 +240,20 @@ namespace ICSharpCode.ILSpy
return parent?.Resolve(reference);
}
public Task<PEFile> ResolveAsync(IAssemblyReference reference)
{
var asm = ResolveFileName(reference.Name + ".dll");
if (asm != null)
{
return asm.GetPEFileOrNullAsync();
}
if (parent != null)
{
return parent.ResolveAsync(reference);
}
return null;
}
public PEFile ResolveModule(PEFile mainModule, string moduleName)
{
var asm = ResolveFileName(moduleName + ".dll");
@ -250,6 +264,20 @@ namespace ICSharpCode.ILSpy @@ -250,6 +264,20 @@ namespace ICSharpCode.ILSpy
return parent?.ResolveModule(mainModule, moduleName);
}
public Task<PEFile> ResolveModuleAsync(PEFile mainModule, string moduleName)
{
var asm = ResolveFileName(moduleName + ".dll");
if (asm != null)
{
return asm.GetPEFileOrNullAsync();
}
if (parent != null)
{
return parent.ResolveModuleAsync(mainModule, moduleName);
}
return null;
}
readonly Dictionary<string, LoadedAssembly> assemblies = new Dictionary<string, LoadedAssembly>(StringComparer.OrdinalIgnoreCase);
internal LoadedAssembly ResolveFileName(string name)

Loading…
Cancel
Save