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

11
ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs

@ -24,6 +24,7 @@ using System.Reflection.Metadata;
using System.Reflection.PortableExecutable; using System.Reflection.PortableExecutable;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace ICSharpCode.Decompiler.Metadata 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); 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 #endif
public override bool IsSharedAssembly(IAssemblyReference reference, out string runtimePack) public override bool IsSharedAssembly(IAssemblyReference reference, out string runtimePack)

50
ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs

@ -19,6 +19,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.TypeSystem.Implementation; using ICSharpCode.Decompiler.TypeSystem.Implementation;
@ -181,7 +182,11 @@ namespace ICSharpCode.Decompiler.TypeSystem
// Load referenced assemblies and type-forwarder references. // Load referenced assemblies and type-forwarder references.
// This is necessary to make .NET Core/PCL binaries work better. // This is necessary to make .NET Core/PCL binaries work better.
var referencedAssemblies = new List<PEFile>(); 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; var mainMetadata = mainModule.Metadata;
foreach (var h in mainMetadata.GetModuleReferences()) foreach (var h in mainMetadata.GetModuleReferences())
{ {
@ -194,7 +199,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
var file = mainMetadata.GetAssemblyFile(fileHandle); var file = mainMetadata.GetAssemblyFile(fileHandle);
if (mainMetadata.StringComparer.Equals(file.Name, moduleName) && file.ContainsMetadata) if (mainMetadata.StringComparer.Equals(file.Name, moduleName) && file.ContainsMetadata)
{ {
assemblyReferenceQueue.Enqueue((false, mainModule, moduleName)); AddToQueue(false, mainModule, moduleName);
break; break;
} }
} }
@ -205,26 +210,12 @@ namespace ICSharpCode.Decompiler.TypeSystem
} }
foreach (var refs in mainModule.AssemblyReferences) 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) while (assemblyReferenceQueue.Count > 0)
{ {
var asmRef = assemblyReferenceQueue.Dequeue(); var asmRef = assemblyReferenceQueue.Dequeue();
if (!processedAssemblyReferences.Add(asmRef)) var asm = asmRef.ResolveTask.GetAwaiter().GetResult();
continue;
PEFile asm;
if (asmRef.IsAssembly)
{
asm = assemblyResolver.Resolve((AssemblyReference)asmRef.Reference);
}
else
{
asm = assemblyResolver.ResolveModule(asmRef.MainModule, (string)asmRef.Reference);
}
if (asm != null) if (asm != null)
{ {
referencedAssemblies.Add(asm); referencedAssemblies.Add(asm);
@ -235,11 +226,11 @@ namespace ICSharpCode.Decompiler.TypeSystem
switch (exportedType.Implementation.Kind) switch (exportedType.Implementation.Kind)
{ {
case SRM.HandleKind.AssemblyReference: 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; break;
case SRM.HandleKind.AssemblyFile: case SRM.HandleKind.AssemblyFile:
var file = metadata.GetAssemblyFile((SRM.AssemblyFileHandle)exportedType.Implementation); var file = metadata.GetAssemblyFile((SRM.AssemblyFileHandle)exportedType.Implementation);
assemblyReferenceQueue.Enqueue((false, asm, metadata.GetString(file.Name))); AddToQueue(false, asm, metadata.GetString(file.Name));
break; break;
} }
} }
@ -261,6 +252,25 @@ namespace ICSharpCode.Decompiler.TypeSystem
} }
this.MainModule = (MetadataModule)base.MainModule; 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) bool IsMissing(KnownTypeReference knownType)
{ {
var name = knownType.TypeName; var name = knownType.TypeName;

50
ILSpy/LoadedAssembly.cs

@ -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; ICompilation typeSystem;
/// <summary> /// <summary>
@ -429,6 +447,38 @@ namespace ICSharpCode.ILSpy
return module; return module;
return parent.LookupReferencedModule(mainModule, moduleName)?.GetPEFileOrNull(); 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; readonly MyAssemblyResolver resolver;

28
ILSpy/LoadedPackage.cs

@ -240,6 +240,20 @@ namespace ICSharpCode.ILSpy
return parent?.Resolve(reference); 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) public PEFile ResolveModule(PEFile mainModule, string moduleName)
{ {
var asm = ResolveFileName(moduleName + ".dll"); var asm = ResolveFileName(moduleName + ".dll");
@ -250,6 +264,20 @@ namespace ICSharpCode.ILSpy
return parent?.ResolveModule(mainModule, moduleName); 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); readonly Dictionary<string, LoadedAssembly> assemblies = new Dictionary<string, LoadedAssembly>(StringComparer.OrdinalIgnoreCase);
internal LoadedAssembly ResolveFileName(string name) internal LoadedAssembly ResolveFileName(string name)

Loading…
Cancel
Save