Skip to content

Commit 7d42864

Browse files
[Java.Interop.Tools.Cecil] DirectoryAssemblyResolver+MemoryMappedFile (#1103)
Context: dotnet/linker#1130 Context: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1783420 In dotnet/linker#1130, their version of `DirectoryAssemblyResolver` was reworked to use [`MemoryMappedFile`][0] in the .NET 6 time frame. Port these changes to Java.Interop, as we have MSBuild tasks in xamarin/xamarin-android that use `DirectoryAssemblyResolver`. Primarily, the [`<GenerateJavaStubs/>` MSBuild task][1] uses `DirectoryAssemblyResolver` to iterate over types in assembly to emit Java source code and generate `AndroidManifest.xml`. Note: `MemoryMappedFile` can *only* be used when we are *not* opening the assembly as `.ReadWrite`. Trying to use `MemoryMappedFile` for read+write scenarios would result in an `InvalidOperationException`: error XAGJS7009: System.InvalidOperationException: Operation is not valid due to the current state of the object. error XAGJS7009: at Mono.Cecil.Cil.DefaultSymbolWriterProvider.GetSymbolWriter(ModuleDefinition module, String fileName) error XAGJS7009: at Mono.Cecil.ModuleWriter.GetSymbolWriter(ModuleDefinition module, String fq_name, ISymbolWriterProvider symbol_writer_provider, WriterParameters parameters) error XAGJS7009: at Mono.Cecil.ModuleWriter.Write(ModuleDefinition module, Disposable`1 stream, WriterParameters parameters) error XAGJS7009: at Mono.Cecil.ModuleWriter.WriteModule(ModuleDefinition module, Disposable`1 stream, WriterParameters parameters) error XAGJS7009: at Mono.Cecil.ModuleDefinition.Write(String fileName, WriterParameters parameters) error XAGJS7009: at Mono.Cecil.AssemblyDefinition.Write(String fileName, WriterParameters parameters) error XAGJS7009: at Xamarin.Android.Tasks.MarshalMethodsAssemblyRewriter.Rewrite(DirectoryAssemblyResolver resolver, List`1 targetAssemblyPaths, Boolean brokenExceptionTransitions) error XAGJS7009: at Xamarin.Android.Tasks.GenerateJavaStubs.Run(DirectoryAssemblyResolver res, Boolean useMarshalMethods) error XAGJS7009: at Xamarin.Android.Tasks.GenerateJavaStubs.RunTask() The results of these changes in a `dotnet new maui` project, an initial clean build: * Before MemoryMappedFile: GenerateJavaStubs = 1.318 s, 1 calls. * After MemoryMappedFile: GenerateJavaStubs = 1.254 s, 1 calls. Saving ~64ms or about ~5% in this example. Note: The current version of the linker's resolver in .NET 8+ has [moved to the dotnet/runtime repo][2]. [0]: https://learn.microsoft.com/dotnet/api/system.io.memorymappedfiles.memorymappedfile?view=net-8.0 [1]: https://github.com/xamarin/xamarin-android/blob/e694ba52a0cf5f45b213c8902bdbfb0ed348374a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs#L131 [2]: https://github.com/dotnet/runtime/blob/cd7d006030a7feace9076fa275fb5bffc1bf4a90/src/tools/illink/src/linker/Linker/AssemblyResolver.cs
1 parent 3c2a066 commit 7d42864

File tree

1 file changed

+35
-2
lines changed

1 file changed

+35
-2
lines changed

src/Java.Interop.Tools.Cecil/Java.Interop.Tools.Cecil/DirectoryAssemblyResolver.cs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
using System.Collections;
3333
using System.Collections.Generic;
3434
using System.IO;
35+
using System.IO.MemoryMappedFiles;
3536
using System.Linq;
3637
using System.Reflection;
3738

@@ -61,6 +62,7 @@ public class DirectoryAssemblyResolver : IAssemblyResolver {
6162

6263
public ICollection<string> SearchDirectories {get; private set;}
6364

65+
readonly List<MemoryMappedViewStream> viewStreams = new List<MemoryMappedViewStream> ();
6466
Dictionary<string, AssemblyDefinition?> cache;
6567
bool loadDebugSymbols;
6668
Action<TraceLevel, string> logger;
@@ -103,6 +105,10 @@ protected virtual void Dispose (bool disposing)
103105
e.Value?.Dispose ();
104106
}
105107
cache.Clear ();
108+
foreach (var viewStream in viewStreams) {
109+
viewStream.Dispose ();
110+
}
111+
viewStreams.Clear ();
106112
}
107113

108114
public Dictionary<string, AssemblyDefinition?> ToResolverCache ()
@@ -160,14 +166,41 @@ protected virtual AssemblyDefinition ReadAssembly (string file)
160166
SymbolStream = loadReaderParameters.SymbolStream,
161167
};
162168
try {
163-
return AssemblyDefinition.ReadAssembly (file, reader_parameters);
169+
return LoadFromMemoryMappedFile (file, reader_parameters);
164170
} catch (Exception ex) {
165171
logger (
166172
TraceLevel.Verbose,
167173
$"Failed to read '{file}' with debugging symbols. Retrying to load it without it. Error details are logged below.");
168174
logger (TraceLevel.Verbose, $"{ex.ToString ()}");
169175
reader_parameters.ReadSymbols = false;
170-
return AssemblyDefinition.ReadAssembly (file, reader_parameters);
176+
return LoadFromMemoryMappedFile (file, reader_parameters);
177+
}
178+
}
179+
180+
AssemblyDefinition LoadFromMemoryMappedFile (string file, ReaderParameters options)
181+
{
182+
// We can't use MemoryMappedFile when ReadWrite is true
183+
if (options.ReadWrite) {
184+
return AssemblyDefinition.ReadAssembly (file, options);
185+
}
186+
187+
MemoryMappedViewStream? viewStream = null;
188+
try {
189+
// Create stream because CreateFromFile(string, ...) uses FileShare.None which is too strict
190+
using var fileStream = new FileStream (file, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, false);
191+
using var mappedFile = MemoryMappedFile.CreateFromFile (
192+
fileStream, null, fileStream.Length, MemoryMappedFileAccess.Read, HandleInheritability.None, true);
193+
viewStream = mappedFile.CreateViewStream (0, 0, MemoryMappedFileAccess.Read);
194+
195+
AssemblyDefinition result = ModuleDefinition.ReadModule (viewStream, options).Assembly;
196+
viewStreams.Add (viewStream);
197+
198+
// We transferred the ownership of the viewStream to the collection.
199+
viewStream = null;
200+
201+
return result;
202+
} finally {
203+
viewStream?.Dispose ();
171204
}
172205
}
173206

0 commit comments

Comments
 (0)