-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[nativeaot] fix Exists()
checks
#112995
[nativeaot] fix Exists()
checks
#112995
Conversation
Context: dotnet/android#9846 Trying to build our `Mono.Android-Tests` suite for NativeAOT, it fails with: EXEC Failed to load assembly 'System.IO' Which is caused by passing: --root:System.IO I couldn't find anything in the Android workload that would cause this. It appears the `Microsoft.NETCore.Native.targets` have an issue caused by this specific project: * When adding to `@(_IlcRootedAssemblies)` it does an `Exists()` check * So `Exists('System.IO.Compression')` is called. This is *true* because there is a folder in the working directory named `System.IO.Compression`! https://github.com/dotnet/android/tree/main/tests/Mono.Android-Tests/Mono.Android-Tests/System.IO.Compression * `%(FileName)` is then `System.IO` as `.Compression` is stripped as the `%(Extension)` metadata. I believe the fix here is to use `System.IO.File.Exists()` instead of MSBuild's `Exists()` as it returns `true` for files *and* folders.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copilot reviewed 1 out of 1 changed files in this pull request and generated no comments.
Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas |
Does it apply to other usages of Exists in build integration as well? % git grep Exists src/coreclr/nativeaot/BuildIntegration
src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Publish.targets: <Warning Condition="Exists($(UserRuntimeConfig))" Text="The published project has a runtimeconfig.template.json that is not supported by PublishAot. Move the configuration to the project file using RuntimeHostConfigurationOption." />
src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Publish.targets: <Delete Files="$(_symbolTargetPath)" Condition="'$(_symbolIsFile)' == 'true' and Exists('$(_symbolTargetPath)')" />
src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Publish.targets: Condition="'$(_symbolIsFile)' == 'true' and Exists('$(_symbolSourcePath)')" />
src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Publish.targets: <RemoveDir Directories="$(_symbolTargetPath)" Condition="'$(_symbolIsFile)' == 'false' and Exists('$(_symbolTargetPath)')" />
src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Publish.targets: Condition="'$(_symbolIsFile)' == 'false' and Exists('$(_symbolSourcePath)')" />
src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets: <UseSystemZlib Condition="'$(UseSystemZlib)' == '' and !Exists('$(IlcFrameworkNativePath)libz.a')">true</UseSystemZlib>
src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets: <UseSystemBrotli Condition="'$(UseSystemBrotli)' == '' and !Exists('$(IlcFrameworkNativePath)libbrotlicommon.a')">true</UseSystemBrotli>
src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets: <Error Condition="!Exists('$(SysRoot)') and '$(_IsiOSLikePlatform)' == 'true'"
src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets: <ItemGroup Condition="'$(StaticOpenSslLinking)' != 'true' and Exists('$(IlcSdkPath)nonportable.txt')">
src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Windows.targets: <ItemGroup Condition="!Exists('$(IlcSdkPath)debugucrt.txt')"> |
I think it applies to the section below these comments: <!-- Now process the raw ItemGroup into the form that ILC accepts: assembly names only -->
<!-- Use the logic that ILLinker uses: if the file exists, this is a file name. Otherwise it's an assembly name --> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I still really wish we didn't have to do this, but this is how it was defined for ILLink, for better or worse.
Rooting assemblies has really bad impact on size/compile time/etc with native AOT, much worse than for ILLinker (we can optimize things quite well if they're not reflection visible; making them reflection visible disables optimizations and generates a lot of extra data and code). Having to root system assemblies is not very ideal. |
This is definitely just a step to get it working, but is the same as what we have on iOS right now:
If you have ideas to optimize this better, the two approaches are at:
Android is still experimental in .NET 10; iOS they support NativeAOT in .NET 9+. Ideally one day, we'd remove all the custom trimmer steps and just run ILC by itself. |
Yep, iOS runs ILLinker too, but iOS doesn't root the resulting assemblies for native AOT. It may look like the rooting shouldn't matter because we already trimmed, but native AOT does the trimming on top of native artifacts and that allows trimming a lot more. To give an idea of what that means: using System.Reflection;
class Program
{
public static void Main()
{
// Call something
GrabString();
// Generates trimming warnings, but we want to observe how trimming happened
foreach (var m in Type.GetType(string.Concat("Pro", "gram")).GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly))
Console.WriteLine(m);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static string GrabString() => "Hello";
[MethodImpl(MethodImplOptions.NoInlining)]
public static string UnusedMethod() => "Hola";
} This will print without trimming:
With PublishTrimmed it will print:
That matches expectations. But with native AOT it will print:
This is because native AOT optimized away the reflection metadata for You can run
Same as ILLink - if a method was called, we generate a method body and the body can be reflection accessed. If it wasn't called, it's completely removed. IlcTrimMetadata=false will let you approximate the cost of "run ILLink + root everything that survived IL trimming" - it's pretty much the same thing. As an example, The impact of reflection-rooting things that should not be roots is so massive that any collected perf numbers are meaningless (such size difference is going to affect startup, working set, everything, not just size). |
Refreshing my memory, the line such as: <TrimmerRootAssembly Include="@(_AndroidILLinkAssemblies->'%(Filename)')" Exclude="System.Private.CoreLib" TrimMode="All" /> Was mostly me trying to get Android to work at all for the first time. Let me see if we can remove it, for sure.
For what it's worth, the startup time we've seen is already better than Mono+Profiled AOT. So, if we're not even doing it correctly yet, things might improve even further. |
I found some apps can launch with only rooting a single assembly: <TrimmerRootAssembly Include="Mono.Android" TrimMode="All" /> But then a The problem is we need to "root" every It will take longer to solve this correctly, but I can imagine some combination of:
|
We should talk about the precise dependencies here. I believe the new type map proposal is aimed at avoiding this blanket preservation. |
It might be possible to do something short-term that would allow dropping the TrimmerRootAssembly - iOS had similar problems and also rooted things for ILC after ILLinker executed, but they were able to drop it. Worst case a custom step could generate I agree with Andy that medium-term we'd want the new type map proposal to solve this. |
Context: dotnet/android#9846
Trying to build our
Mono.Android-Tests
suite for NativeAOT, it fails with:Which is caused by passing:
I couldn't find anything in the Android workload that would cause this.
It appears the
Microsoft.NETCore.Native.targets
have an issue caused by this specific project:When adding to
@(_IlcRootedAssemblies)
it does anExists()
checkSo
Exists('System.IO.Compression')
is called. This is true because there is a folder in the working directory namedSystem.IO.Compression
!https://github.com/dotnet/android/tree/main/tests/Mono.Android-Tests/Mono.Android-Tests/System.IO.Compression
%(FileName)
is thenSystem.IO
as.Compression
is stripped as the%(Extension)
metadata.I believe the fix here is to use
System.IO.File.Exists()
instead of MSBuild'sExists()
as it returnstrue
for files and folders.