-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Inline resource strings in the compiler #80896
Conversation
Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas Issue DetailsContributes to #80165. Allows getting rid of resource manager. Cc @dotnet/ilc-contrib
|
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.
Pointing out the hacks, but they're quite obvious.
src/coreclr/nativeaot/System.Private.CoreLib/src/System/Diagnostics/StackFrame.NativeAot.cs
Outdated
Show resolved
Hide resolved
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/FeatureSwitchManager.cs
Show resolved
Hide resolved
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/FeatureSwitchManager.cs
Outdated
Show resolved
Hide resolved
string resourceName1 = $"{module.Assembly.GetName().Name}.Strings.resources"; | ||
string resourceName2 = $"FxResources.{module.Assembly.GetName().Name}.SR.resources"; |
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.
We now need to inline knowledge about these names. Don't like this one either. Don't see way out.
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.
How does resource manager find it? In theory this has to be written somewhere in the compiled code, so the compiler could get it from there. But it's probably VERY complicated to do so.
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.
Resource manager grabs it from a piece of (generated) code. It looks like this:
namespace FxResources.System.Private.DisabledReflection
{
internal static class SR { }
}
namespace System
{
internal static partial class SR
{
private static global::System.Resources.ResourceManager s_resourceManager;
internal static global::System.Resources.ResourceManager ResourceManager => s_resourceManager ?? (s_resourceManager = new global::System.Resources.ResourceManager(typeof(FxResources.System.Private.DisabledReflection.SR)));
}
}
So we'd need to crack open the cctor on the System.SR class and find the type that is passed to resource manager's constructor. That's the name of the resource.
It's not impossible. But also not completely straightforward.
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.
Hm, but this is all generated by Arcade logic, so we can just change it: https://github.com/dotnet/arcade/blob/083fc0406279952794c853630a08a141eef7244f/src/Microsoft.DotNet.Arcade.Sdk/src/GenerateResxSource.cs
Looks like the extra type is only done to comfort .NET Native (likely also some old version of .NET Native that still did ILMerging base on the comment).
So we can remove the useless type and instead call into the ResourceManager ctor that takes the resource name.
That will be a net improvement everywhere. I like those kinds of changes.
namespace System
{
internal static partial class SR
{
private const string ResourceName = "FxResources.System.Private.DisabledReflection.SR";
private static global::System.Resources.ResourceManager s_resourceManager;
internal static global::System.Resources.ResourceManager ResourceManager => s_resourceManager ?? (s_resourceManager = new global::System.Resources.ResourceManager(typeof(SR).Assembly), ResourceName);
}
}
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.
Nice - although in probably won't help that much to detect it from the compiler. We would still have to understand the .cctor and alike. I'd say keep the current "hardcoded" version and file a bug that this could be improved by analyzing the static .cctor. Even that would still rely on just specific patterns, but it would more robust.
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.
We'd be looking for the const string - that one is easy to find and can be just part of the protocol.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/FeatureSwitchManager.cs
Outdated
Show resolved
Hide resolved
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ManifestResourceBlockingPolicy.cs
Outdated
Show resolved
Hide resolved
case ParseFailureKind.FormatWithOriginalDateTimeAndParameter: | ||
return new FormatException(SR.Format(SR.GetResourceString(result.failureMessageID)!, new string(result.originalDateTimeString), result.failureMessageFormatArgument)); | ||
//case ParseFailureKind.ArgumentNull: | ||
// return new ArgumentNullException(result.failureArgumentName, SR.GetResourceString(result.failureMessageID)); |
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.
Would need to stop delaying grabbing the resource string. I'm not sure this one is on the hello world path, but we'd need to audit everything.
Does it make sense to optimize via "hacks" for hello world? |
Looking for ways to make it non-hacky! That's why this is a draft. In .NET 7 we shipped NativeAOT for command line apps scenarios - lots of command line apps can go by without ever touching resource manager. For a small command line utility, this can be a 40% saving (this is bringing hello world to 1.6 MB by default without any other feature switches or optimization, just |
Can linker help with inlining of string resources more broadly? Perhaps with some existing criteria, e.g. |
Linker could in theory do this as well - as a middle ground between full resources and resource keys only. The priority in linker is lower though because the size saving is relatively smaller (the constant 10MB of the native runtime outweighs any managed code in the app for small apps). That said we should design this such that the same technique could be used in the linker as well. |
2b51b7b
to
22d8052
Compare
Pushed out a new iteration. This addresses the caching issue. The optimization now also gracefully falls back if someone used |
{ | ||
string? displayName = SR.GetResourceString("Globalization_cp_" + codePage.ToString()); | ||
if (string.IsNullOrEmpty(displayName)) |
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.
I think this was dead code. The only situation when GetResourceString could return a null is if we failed to find the manifest resource blob.
I traced this back to dotnet/corert@db4c4e1 "this implementation does not try to find resource strings for EncodingName, because ProjectN does not support this yet". 6 years passed. We added support at some point.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/FeatureSwitchManager.cs
Outdated
Show resolved
Hide resolved
Goldilocks before 16,486,400 bytes. Goldilocks after 16,224,768 bytes (1.6% savings). Hello world before ~2.5 MB. Hello world after 1,857,536 bytes (massive% savings). |
The change in HelloWorld size is quite impressive |
We can now optimize away the assembly even when resource strings are in use.
/azp run runtime-extra-platforms |
Azure Pipelines successfully started running 1 pipeline(s). |
@dotnet/ilc-contrib could someone have a look? On a high level:
|
...r/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InlineableStringsResourceNode.cs
Outdated
Show resolved
Hide resolved
...r/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InlineableStringsResourceNode.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
// Do not attempt to inline resource strings if we only want to use resource keys. | ||
// The optimizations are not compatible. | ||
bool shouldInlineResourceStrings = | ||
!_hashtable._switchValues.TryGetValue("System.Resources.UseSystemResourceKeys", out bool useResourceKeys) || !useResourceKeys; |
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.
Any estimate on additional saving we would get if this feature also worked with UseSystemResourceKeys
feature switch (some of the message are very long)?
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.
Any estimate on additional saving we would get if this feature also worked with
UseSystemResourceKeys
feature switch (some of the message are very long)?
This will always be inlined - for the below app:
Console.WriteLine(new NullReferenceException().Message);
throw null;
If I compile this with .NET 7 as dotnet publish -c Release /p:PublishAot=true /p:UseSystemResourceKeys=true
and step into the ctor, I see this (the first lea
is loading the string literal of the resource key - inlined from the accessor):
00007FF787207950 56 push rsi
00007FF787207951 48 83 EC 20 sub rsp,20h
00007FF787207955 48 8B F1 mov rsi,rcx
00007FF787207958 48 8D 0D 11 7B 17 00 lea rcx,[__Str_Arg_NullReferenceException (07FF78737F470h)]
00007FF78720795F E8 0C C2 00 00 call S_P_CoreLib_System_SR__GetResourceString (07FF787213B70h)
00007FF787207964 C7 46 48 00 15 13 80 mov dword ptr [rsi+48h],80131500h
00007FF78720796B 48 8D 4E 08 lea rcx,[rsi+8]
00007FF78720796F 48 8B D0 mov rdx,rax
00007FF787207972 E8 89 BE F4 FF call RhpAssignRefAVLocation (07FF787153800h)
00007FF787207977 C7 46 48 01 15 13 80 mov dword ptr [rsi+48h],80131501h
00007FF78720797E C7 46 48 03 40 00 80 mov dword ptr [rsi+48h],80004003h
00007FF787207985 48 83 C4 20 add rsp,20h
00007FF787207989 5E pop rsi
00007FF78720798A C3 ret
SR__GetResourceString
didn't get inlined probably because the IL is messed up with too many nops and looks large even thought it isn't. It looks like this and there's only one of it, so not a huge deal size-wise, but we could leave fewer nops when substitutions happen to not to throw off inlining heuristics:
00007FF787213B70 48 8B C1 mov rax,rcx
00007FF787213B73 C3 ret
It might be worth enabling BannedAPIAnalyzet for this. |
Yes, we definitely have a couple calls to these for no good reason! |
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.
Looks good
...r/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InlineableStringsResourceNode.cs
Outdated
Show resolved
Hide resolved
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/FeatureSwitchManager.cs
Show resolved
Hide resolved
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/FeatureSwitchManager.cs
Show resolved
Hide resolved
This reverts commit 759fecb.
Contributes to #80165.
Allows getting rid of resource manager.
Cc @dotnet/ilc-contrib