-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Allow preinitializing AppContext.TryGetSwitch
#95948
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
Allow preinitializing AppContext.TryGetSwitch
#95948
Conversation
Interpret this in the static constructor interpreter. Allows compile-time initializing ~30 extra types in the Stage1 app. 99% of them are the SR class. To actually preinitialize the SR class I had to pass `--feature:System.Resources.UseSystemResourceKeys=false` on the ILC command line. This will get overwritten by `--feature:System.Resources.UseSystemResourceKeys=true` if it shows up on the command line after this. I considered doing this in the substitution IL rewriter (so that RyuJIT-produced code can also benefit), but the API shape (returning bool and taking ref to a bool) makes this really awkward to rewrite in IL. Much easier to interpret.
|
Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas Issue DetailsInterpret this in the static constructor interpreter. Allows compile-time initializing ~30 extra types in the Stage1 app. 99% of them are the SR class. To actually preinitialize the SR class I had to pass I considered doing this in the substitution IL rewriter (so that RyuJIT-produced code can also benefit), but the API shape (returning bool and taking ref to a bool) makes this really awkward to rewrite in IL. Much easier to interpret. Cc @dotnet/ilc-contrib
|
|
AppContext key/value pairs are mutable at runtime. Can this lead to odd behaviors when the AppContext is mutated at runtime? |
Both trimmer and AOT already "inline" some of the feature switches anyway. The fact that |
The existing inlining behavior has same observable effect as caching of feature switch values. Inlining of AppContext.TryGetSwitch calls brings it to a different level. For example, consider this program: It prints false when the first Should we mark |
FWIW I would find that useful to do that. |
I would think this is quiet dangerous. There are many places where similar situations can occur from We have a way already to force I'm not really convinced this is trim or AOT incompatible either, you can see many of the same behaviors at JIT time. I'd think it would only make sense to warn if the analyzer detects you're forcing the switch such that mutation is no longer valid. |
|
I look at this as the specific string. This will not kick in for anything but the feature switch strings (no other RuntimeHostConfiguration options get inlined). Like Vitek said we wanted to have a read-only mechanism to query these at runtime (not |
|
After sleeping on this, I see two options: 1. Go with what's hereConsider "What if someone calls 2. Reopen the conversation about a read-only appcontext.What we probably want here is: class AppContext
{
public static bool GetRuntimeConfigurationSwitch(string name, bool defaultValue) { }
}This would:
This would allow us to get rid of substitution XML garbage for most scenarios because the IL Linker could also see and optimize this without having to worry about being inconsistent if someone reverse engineers a feature switch name and tries to @vitek-karas @sbomer what do you think? |
|
@jkotas I'd like to clarify which specific aspect of the inlining behavior you find problematic.
The existing inlining behavior can show observable differences similar to your example: using System;
class Program
{
static bool V => AppContext.TryGetSwitch("System.Resources.UseSystemResourceKeys", out bool tmp) && tmp;
static void Main()
{
AppContext.SetSwitch("System.Resources.UseSystemResourceKeys", true);
Console.WriteLine(V);
}
}This prints false when there is an XML substitution ( Do you consider this similarly problematic, or is your point that inlining based on whether the string argument is a constant is less explainable than an explicit substitution? |
|
Personally I think a read-only AppContext would be nice, but it seems orthogonal to this change. It would still have the problem that I would like to establish a pattern for feature switches that reliably has the desired semantics and allowed optimizations. We should either decide that the supported pattern requires a constant string (for both preinit inlining and branch removal), or use the substitutions for both. |
I do not have a problem with Xml substitution. The Xml substitutions are not an optimization, they are guaranteed to happen. I have a problem with optimizations that are not guaranteed to happen and that change observable functional behavior when they do. For example, Roslyn can decide to store the constant string in a local variable or confuse the pattern matching in some other way - it should not result into an observable functional behavior difference. |
|
Thanks! I tend to agree. I would additionally like it if the inlining for preinit could rely on the substitutions, even if that's not an observable functional behavior. We rely on the substitutions for branch removal already. It would be nice to use this as the source of truth for both optimizations. Could we detect that since |
The point I'm trying to make is that this is only "observable functional behavior difference" if we consider someone doing Suppose someone puts |
We don't have capability to trim fields, but we can eliminate entire static bases and I think in this case it's going to be eliminated once the static constructor runs at compile time. |
Observable effects of this case are similar to observable effects of the cached appcontext value. I have tried to explain the distinction in #95948 (comment). |
|
We use IL pattern matching in number of places today. If the IL pattern matching changes observable behavior, we typically produce warnings when we fail to pattern match the IL. For example, failure to pattern match IL for reflection annotations will produce warning. Do we have any existing situations where failure to pattern match IL changes behavior without producing a warning? |
RuntimeHelpers.InitializeArray for example. But note that this PR is not a pattern match; this is running the cctor at compile time. If the cctor can be interpreted, the effect is that the cctor executed before someone managed to call SetData. If it cannot be interpreted because it's too complex, it will run at a later time, which may or may not be after someone called SetData.
To me it sounds like the same timing issue - the SetData might be called after someone already read the data and latched it and trimming makes it so it gets "latched" at trimming time. |
|
Ok, I have been thinking about what a more robust implementation of this optimization can look like: Scan the app for This leads to a problem with needing to restart the scanning, or at least invalidate and recompute some of the scanning results. Are there other situations where we can do an optimization once we know that certain rarely used API is not used anywhere in the app? Would it be worth it to generalize infrastructure for this pattern? |
I think the applicability of this would be fairly limited because we only do it in the cctor and doing this extra analysis will more than double the amount of code we need to write for this feature. Most other places within the framework don't actually read this in cctors but have their own lazy scheme. This has a really simple fix - add this to ILLink.Substitutions.Resources.template: <assembly fullname="{AssemblyName}" feature="System.Resources.UseSystemResourceKeys" featurevalue="false" featuredefault="true">
<type fullname="System.SR">
<method signature="System.Boolean UsingResourceKeys()" body="stub" value="false" />
<method signature="System.Boolean GetUsingResourceKeysSwitchValue()" body="stub" value="false" />
</type>And then fix up SR.cs to do this: - private static readonly bool s_usingResourceKeys = AppContext.TryGetSwitch("System.Resources.UseSystemResourceKeys", out bool usingResourceKeys) ? usingResourceKeys : false;
+ private static readonly bool s_usingResourceKeys = GetUsingResourceKeysSwitchValue();
+
+ // This method is a target of ILLink substitution.
+ private static bool GetUsingResourceKeysSwitchValue() => AppContext.TryGetSwitch("System.Resources.UseSystemResourceKeys", out bool usingResourceKeys) ? usingResourceKeys : false;Unfortunately that runs into #96539 - ILLink will do this substitution at the time we build the runtime repo. The refcount on that issue is starting to be pretty high. |
|
I moved the trimmer issue into the runtime repo - for better visibility. |
Interpret this in the static constructor interpreter. Allows compile-time initializing ~30 extra types in the Stage1 app. 99% of them are the SR class.
To actually preinitialize the SR class I had to pass
--feature:System.Resources.UseSystemResourceKeys=falseon the ILC command line. This will get overwritten by--feature:System.Resources.UseSystemResourceKeys=trueif it shows up on the command line after this.I considered doing this in the substitution IL rewriter (so that RyuJIT-produced code can also benefit), but the API shape (returning bool and taking ref to a bool) makes this really awkward to rewrite in IL. Much easier to interpret.
Cc @dotnet/ilc-contrib