dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.16k stars 4.71k forks source link

Roslyn analyzer assembly loading with NativeAOT #83355

Open am11 opened 1 year ago

am11 commented 1 year ago

The NativeAOT-published csc works nicely as long as we are not using roslyn analyzers (/analyzer:...). The way to override is by placing native csc next to csc.dll under ~/.nuget directory; for this condition to kick in: https://github.com/dotnet/roslyn/blob/92a989539e51a89627a487a6a664202624efef83/src/Compilers/Shared/RuntimeHostInfo.cs#L36 and globally set UseSharedCompilation property to false.

However, trying to build some real-world project (e.g. runtime/build.sh lib.src -p:UseSharedCompilation=false) runs into assembly load issues. The internal exceptions caught by Roslyn under a debugger looks like:

Unable to load Microsoft.Interop.LibraryImportGenerator
System.IO.FileNotFoundException: Cannot load assembly 'Microsoft.Interop.LibraryImportGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. No metadata found for this assembly.
   at System.Reflection.Runtime.General.ReflectionCoreCallbacksImplementation.Load(AssemblyName, Boolean) + 0x5c
   at Microsoft.CodeAnalysis.AnalyzerAssemblyLoader.Load(AssemblyName, String) + 0xf8
   at Microsoft.CodeAnalysis.AnalyzerAssemblyLoader.LoadFromPath(String) + 0x58

Assuming this is related to "one of the dependency was not found" -- can we improve the assembly loader, e.g. by hardcoding probes for *CodeAnalysis.dll and friends when an analyzer assembly is requested?

ghost commented 1 year ago

Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas See info in area-owners.md if you want to be subscribed.

Issue Details
The NativeAOT-published `csc` works nicely as long as we are not using roslyn analyzers (`/analyzer:...`). The way to override is by placing native `csc` next to `csc.dll` under `~/.nuget` directory; for this condition to kick in: https://github.com/dotnet/roslyn/blob/92a989539e51a89627a487a6a664202624efef83/src/Compilers/Shared/RuntimeHostInfo.cs#L36 and globally set `UseSharedCompilation` property to `false`. However, trying to build some real-world project (e.g. `runtime/build.sh lib.src -p:UseSharedCompilation=false`) runs into assembly load issues. The internal exceptions caught by Roslyn under a debugger looks like: ``` Unable to load Microsoft.Interop.LibraryImportGenerator System.IO.FileNotFoundException: Cannot load assembly 'Microsoft.Interop.LibraryImportGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. No metadata found for this assembly. at System.Reflection.Runtime.General.ReflectionCoreCallbacksImplementation.Load(AssemblyName, Boolean) + 0x5c at Microsoft.CodeAnalysis.AnalyzerAssemblyLoader.Load(AssemblyName, String) + 0xf8 at Microsoft.CodeAnalysis.AnalyzerAssemblyLoader.LoadFromPath(String) + 0x58 ``` Assuming this is related to "one of the dependency was not found" -- can we improve the assembly loader, e.g. by hardcoding probes for `*CodeAnalysis.dll` and friends when an analyzer assembly is requested?
Author: am11
Assignees: -
Labels: `area-NativeAOT-coreclr`
Milestone: -
MichalStrehovsky commented 1 year ago

If you add the *CodeAnalysis.dll assemblies at native compile time as TrimmerRootAssembly the assembly load should "just work".

am11 commented 1 year ago

@MichalStrehovsky, I published csc.proj with TrimmerRootAssembly:

--- a/roslyn/src/Compilers/CSharp/csc/AnyCpu/csc.csproj
+++ b/roslyn/src/Compilers/CSharp/csc/AnyCpu/csc.csproj
@@ -3,11 +3,17 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <TargetFrameworks>net6.0;net472</TargetFrameworks>
-    <UseAppHost>false</UseAppHost>
+    <TargetFramework>net8.0</TargetFramework>
     <IsSymbolPublishingPackage>true</IsSymbolPublishingPackage>
+    <PublishAot>true</PublishAot>
+    <StripSymbols>true</StripSymbols>
+    <EnableTrimAnalyzer>false</EnableTrimAnalyzer>
   </PropertyGroup>
-  
+
+  <ItemGroup>
+    <TrimmerRootAssembly Include="Microsoft.CodeAnalysis;Microsoft.CodeAnalysis.CSharp" />
+  </ItemGroup>
+
   <!-- Can't put this in shared project due to https://github.com/dotnet/project-system/issues/8157 -->
   <ItemGroup>
     <None Include="$(MSBuildThisFileDirectory)..\csc.rsp" Condition="'$(TargetFramework)' == 'net472'">

then used this published csc to build runtime libs and it ran into same errors:

CSC : error CS8034: Unable to load Analyzer assembly /Users/am11/.nuget/packages/microsoft.net.illink.tasks/8.0.0-preview.3.23155.6/analyzers/dotnet/cs/ILLink.RoslynAnalyzer.dll : Unable to load ILLink.RoslynAnalyzer [/Users/am11/projects/runtime2/src/tasks/MonoTargetsTasks/ILStrip/AssemblyStripper/AssemblyStripper.csproj::TargetFramework=net472]
CSC : error CS8034: Unable to load Analyzer assembly /Users/am11/.nuget/packages/microsoft.net.illink.tasks/8.0.0-preview.3.23155.6/analyzers/dotnet/cs/ILLink.RoslynAnalyzer.dll : Unable to load ILLink.RoslynAnalyzer [/Users/am11/projects/runtime2/src/tasks/installer.tasks/installer.tasks.csproj::TargetFramework=net472]
CSC : error CS8034: Unable to load Analyzer assembly /Users/am11/.nuget/packages/system.text.json/6.0.0/analyzers/dotnet/roslyn4.0/cs/System.Text.Json.SourceGeneration.dll : Unable to load System.Text.Json.SourceGeneration [/Users/am11/projects/runtime2/src/tasks/installer.tasks/installer.tasks.csproj::TargetFramework=net472]
CSC : error CS8034: Unable to load Analyzer assembly /Users/am11/.nuget/packages/microsoft.codeanalysis.analyzers/3.3.5-beta1.23158.3/analyzers/dotnet/cs/Microsoft.CodeAnalysis.Analyzers.dll : Unable to load Microsoft.CodeAnalysis.Analyzers [/Users/am11/projects/runtime2/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Microsoft.Interop.SourceGeneration.csproj]
CSC : error CS8034: Unable to load Analyzer assembly /Users/am11/.nuget/packages/microsoft.codeanalysis.analyzers/3.3.5-beta1.23158.3/analyzers/dotnet/cs/Microsoft.CodeAnalysis.CSharp.Analyzers.dll : Unable to load Microsoft.CodeAnalysis.CSharp.Analyzers [/Users/am11/projects/runtime2/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Microsoft.Interop.SourceGeneration.csproj]
CSC : error CS8034: Unable to load Analyzer assembly /Users/am11/.nuget/packages/microsoft.codeanalysis.csharp.codestyle/4.5.0/analyzers/dotnet/cs/Microsoft.CodeAnalysis.CSharp.CodeStyle.dll : Unable to load Microsoft.CodeAnalysis.CSharp.CodeStyle [/Users/am11/projects/runtime2/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Microsoft.Interop.SourceGeneration.csproj]
CSC : error CS8034: Unable to load Analyzer assembly /Users/am11/.nuget/packages/microsoft.codeanalysis.csharp.codestyle/4.5.0/analyzers/dotnet/cs/Microsoft.CodeAnalysis.CodeStyle.dll : Unable to load Microsoft.CodeAnalysis.CodeStyle [/Users/am11/projects/runtime2/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Microsoft.Interop.SourceGeneration.csproj]
CSC : error CS8034: Unable to load Analyzer assembly /Users/am11/.nuget/packages/microsoft.codeanalysis.netanalyzers/8.0.0-preview1.23158.3/analyzers/dotnet/cs/Microsoft.CodeAnalysis.CSharp.NetAnalyzers.dll : Unable to load Microsoft.CodeAnalysis.CSharp.NetAnalyzers [/Users/am11/projects/runtime2/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Microsoft.Interop.SourceGeneration.csproj]
CSC : error CS8034: Unable to load Analyzer assembly /Users/am11/.nuget/packages/microsoft.codeanalysis.netanalyzers/8.0.0-preview1.23158.3/analyzers/dotnet/cs/Microsoft.CodeAnalysis.NetAnalyzers.dll : Unable to load Microsoft.CodeAnalysis.NetAnalyzers [/Users/am11/projects/runtime2/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Microsoft.Interop.SourceGeneration.csproj]
CSC : error CS8034: Unable to load Analyzer assembly /Users/am11/.nuget/packages/microsoft.dotnet.codeanalysis/8.0.0-beta.23159.1/analyzers/Microsoft.DotNet.CodeAnalysis.dll : Unable to load Microsoft.DotNet.CodeAnalysis [/Users/am11/projects/runtime2/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Microsoft.Interop.SourceGeneration.csproj]
CSC : error CS8034: Unable to load Analyzer assembly /Users/am11/.nuget/packages/microsoft.net.illink.tasks/8.0.0-preview.3.23155.6/analyzers/dotnet/cs/ILLink.RoslynAnalyzer.dll : Unable to load ILLink.RoslynAnalyzer [/Users/am11/projects/runtime2/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Microsoft.Interop.SourceGeneration.csproj]
CSC : error CS8034: Unable to load Analyzer assembly /Users/am11/.nuget/packages/stylecop.analyzers.unstable/1.2.0.406/analyzers/dotnet/cs/StyleCop.Analyzers.dll : Unable to load StyleCop.Analyzers [/Users/am11/projects/runtime2/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Microsoft.Interop.SourceGeneration.csproj]

(all these have the same call stack as shown above, and paths exist)

MichalStrehovsky commented 1 year ago

You need to TrimmerRootAssembly the assemblies it's trying to load, so Microsoft.Interop.LibraryImportGenerator and others. If they're not added at compile time, this needs a jit and a full runtime.

agocke commented 1 year ago

In my Roslyn PR I mark all the assembly load APIs as RUC https://github.com/dotnet/roslyn/pull/66519

So using analyzers or generators with AOT would be unsupported. Csc would probably need to be rewritten with a feature flag to remove support for the command line option.

agocke commented 1 year ago

I'm going to close this out as I don't think there's anything to do for Native AOT. Any improvements here would be in Roslyn, although I expect it is a non-goal for Roslyn to publish a Native AOT version of csc.

MichalStrehovsky commented 1 year ago

@am11 I don't see much that would be actionable here for the runtime repo.

can we improve the assembly loader, e.g. by hardcoding probes for *CodeAnalysis.dll and friends when an analyzer assembly is requested?

Native AOT doesn't have an assembly loader. It doesn't know how to parse PE files. Assembly.Load will work as long as the assembly was part of the native compilation. We emit enough metadata to be able to tell e.g. typeof(object).Assembly and from that it follows that we can also Assembly.Load("CoreLib").GetType("System.Object"). But there's nothing we can do for assemblies that were not part of the closure. Those are not compatible with AOT (or even trimming in general because we likely trimmed pieces of the framework that the loaded assembly might want).

am11 commented 1 year ago

@MichalStrehovsky, yes, they aren't available at compile time. Still, can we improve the loader? No metadata found for this assembly exception message is confusing.

MichalStrehovsky commented 1 year ago

@MichalStrehovsky, yes, they aren't available at compile time. Still, can we improve the loader? No metadata found for this assembly exception message is confusing.

What message do you suggest? CoreCLR will print "Could not load file or assembly 'Fhtagn, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.'" but that's not right either because we're not looking for files. We're only looking for metadata within the app.

am11 commented 1 year ago

What message do you suggest?

My understanding is that AOT'd app on LoadFrom(path) stats the file on filesystem, read its metadata, creates AssemblyName and then fails to load it because we don't have the IL interpreter. From that context, it can tell: "AOT app cannot load IL assembly because interpreter is not supported" or some such to reflect that there is nothing wrong with the path or the assembly, but it is the shortcoming of the loader. I believe it would make more sense than FileNotFoundException with No metadata found for this assembly.

On a related note, while looking around for clues, I noticed that https://github.com/dotnet/corert/blob/master/src/System.Private.TypeLoader/src/Internal/Reflection/Execution/AssemblyBinderImplementation.Ecma.cs was not ported to runtime repo. Not sure if it was intentional but it is a bit strange that we only have signatures (with partial) for BindEcmaAssemblyName and other BindXx methods; without the implementation.

MichalStrehovsky commented 1 year ago

On a related note, while looking around for clues, I noticed that

Yes, the .Ecma.cs files were not brought over intentionally. They were part of the JIT/interpreter prototypes that we didn't carry forward. There's still vestiges of it left in the system. The fact that we touch the file system at all for Load is one of those vestiges. IIRC this was just outright PlatformNotSupportedException thrown from the implementation of LoadFrom, no questions asked, before those prototypes. I didn't realize you got to this callstack with LoadFrom. We can probably excise this binder from the product. It's unlikely to come back.

Calling LoadFrom in a trimmed app doesn't result in happiness. The failure mode with AOT is actually still very nice compared to what one would get with just trimming (missing members, wrong optimizations applied, etc.). The API is annotated to generate a compile-time warning. Our recommendation is to fix AOT/trim/singlefile compatibility issues by looking at warnings. The runtime behaviors are not pretty. We strongly discourage troubleshooting these in a debugger.

am11 commented 1 year ago

So the plan is that we will throw PNSE straight up, instead of FNFE, correct?

MichalStrehovsky commented 1 year ago

So the plan is that we will throw PNSE straight up, instead of FNFE, correct?

We delete vestiges of .NET Native when they are in the way but we generally don't track them in issues because we already have enough issues in the backlog. Unless it's something you'd like to work on, I suggest we close this.

ghost commented 1 year ago

This issue has been marked needs-author-action and may be missing some important information.

am11 commented 1 year ago

because we already have enough issues in the backlog

We normally use milestones for that. Assigned future milestone. Will take a look when I get a chance.