-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
[ILLink analyzer] Add branch analysis #94123
Conversation
Tagging subscribers to this area: @agocke, @sbomer, @vitek-karas Issue DetailsThis adds an understanding of The When a branch operation is seen at the end of a basic block, the analysis examines it to see if it is a supported feature check. If so, it returns an abstract representation of the checked condition (which can be a boolean expression involving various features): The analysis then separates the output state for the basic block into two, one for the conditional successor, and one for the fall-through successor. It applies the To support this, the dataflow state tracking is now done per branch, instead of per basic block. Previously we tracked the state at the end of each block; now we track the state at the input to each edge in the control-flow graph. For now the only supported check is There are still some pieces of the Requires analyzer logic (generic instantiations and dynamic objects, for example) that need to be moved over to the dataflow analysis, so that the feature checks can act as guards for all of the related patterns. Until that is done, feature checks won't silence those particular warnings. I'll continue working on moving the remaining logic over, but I don't think it needs to block this change. Fixes dotnet/linker#2715
|
@@ -1194,32 +1194,32 @@ protected virtual void HandleStoreMethodReturnValue(MethodIL method, int offset, | |||
switch (value) | |||
{ | |||
case FieldReferenceValue fieldReferenceValue: | |||
dereferencedValue = MultiValue.Meet( | |||
dereferencedValue = MultiValue.Union( |
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 renamed Meet
on ValueSet to Union
since I also added an Intersection
method. I'm thinking it's most readable if we use set operation names for ValueSet, and the lattice operation names (Meet) when using ValueSetLattice. In the case of this method, I think the fact that DereferenceValue
computes a union of dereferenced values for the items in the ValueSet has more to do with the semantics of the dereference operation and ValueSet, than with ValueSetLattice, so I think it makes sense to use Union
here.
} | ||
|
||
// No Merge - there's nothing to merge since this pattern is uniquely identified by both the origin and the entity | ||
// and there's only one way to "access" a field. | ||
public TrimAnalysisFieldAccessPattern Merge ( |
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.
Merge is now necessary since this includes FeatureContext
as part of the pattern.
src/tools/illink/src/ILLink.Shared/DataFlow/ForwardDataFlowAnalysis.cs
Outdated
Show resolved
Hide resolved
// Duplicate the current state so that it's not shared with fall-through state. | ||
TValue conditionalCurrentState = lattice.Meet (lattice.Top, state.Current); | ||
|
||
if (conditionValue != null) { |
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.
Vast majority of cases will have conditionValue==null (or should, feature checks should be very rare). Why do we need to duplicate the state for conditions which are not about feature checks?
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.
Summarizing our discussion for posterity:
- I duplicated the states for all conditions as part of moving the state tracking to be per branch instead of per basic block
- We could still optimize this by only tracking a duplicate state for relevant conditions. It would mean defining a contract where you could request a state for a branch, but the implementation would be able to give you back the same copy for either outgoing branch, if the condition wasn't relevant.
- We agreed this optimization isn't worth doing at this point. In most cases the duplicated state will just be TopValue which is not expensive to copy. We also already track a dictionary entry of a state per basic block, and the extra overhead from one more state for the conditional branch exits is probably not high.
if (RuntimeFeature.IsDynamicCodeSupported && TestFeatures.IsUnreferencedCodeSupported) { | ||
RequiresDynamicCode (); | ||
RequiresUnreferencedCode (); | ||
} |
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 come NativeAOT produces 3050 here? I would expect the branch to be eliminated. I must be missing something here. Is it because of compound condition?
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 didn't debug it, but yes, I think it's because of the compound condition. Roslyn sees this as a CFG where the short-circuiting is a new edge in the CFG and it just works. But the IL looks like this:
IL_0001: call bool C::get_Prop1()
IL_0006: brfalse.s IL_000f
IL_0008: call bool C::get_Prop2()
IL_000d: br.s IL_0010
IL_000f: ldc.i4.0
IL_0010: stloc.0
// sequence point: hidden
IL_0011: ldloc.0
IL_0012: brfalse.s IL_0021
For NativeAot to understand this it would have to be able to propagate the constant ldc.i4.0 through the stloc/ldloc to the brfalse, and use that to eliminate the branch.
src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowAnalysis.cs
Outdated
Show resolved
Hide resolved
- Clarify that this doesn't model everything supported by feature switches - Add comment about ControlFlowBranch - Avoid using "feature switch" terminology - Avoid mentioning "feature context" in generic code
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.
Didn't review all of it - but enough comments to submit the review so that I don't lose them :-)
src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalContextStateLattice.cs
Outdated
Show resolved
Hide resolved
src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/FeatureCheckValue.cs
Outdated
Show resolved
Hide resolved
src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/FeatureCheckVisitor.cs
Outdated
Show resolved
Hide resolved
src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs
Outdated
Show resolved
Hide resolved
src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs
Outdated
Show resolved
Hide resolved
src/tools/illink/src/ILLink.Shared/DataFlow/IControlFlowGraph.cs
Outdated
Show resolved
Hide resolved
src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/ControlFlowGraphProxy.cs
Outdated
Show resolved
Hide resolved
src/tools/illink/src/ILLink.Shared/DataFlow/IControlFlowGraph.cs
Outdated
Show resolved
Hide resolved
- LocalContextState -> LoecalStateAndContext - FeatureCheckValue -> FeatureChecksValue - FeatureCheckVisitor -> FeatureChecksVisitor - Comments about state passed to FeatureChecksVisitor - TryGetConditionValue -> GetConditionValue - Use in parameter for GetFieldValue - Fix HandleMethodCallHelper to set entire state to Top, and fix comments to not mention feature context - Consistently pass in TContext to handlers - Comments about test-only feature checks - Move test-only feature checks to ILLink.RoslynAnalyzer namespace - Move INegate after ITransfer - Use 'Empty' in ValueSet Intersection for readability - Remove unnecessary DeepCopy - Remove unnecessary using - Replace throw with assert and continue - Remove left-over unused Successor type
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.
Reviewed all of product code, still have tests to do.
src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalStateAndContextLattice.cs
Outdated
Show resolved
Hide resolved
src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimDataFlowAnalysis.cs
Show resolved
Hide resolved
- Comments about FinallyRegions - LocalContextLattice -> LocalStateAndContextLattice - Comment about initial FeatureContext
src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/FeatureCheckDataFlow.cs
Show resolved
Hide resolved
src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/FeatureCheckDataFlow.cs
Show resolved
Hide resolved
src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/FeatureCheckDataFlow.cs
Outdated
Show resolved
Hide resolved
src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/FeatureCheckDataFlow.cs
Outdated
Show resolved
Hide resolved
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.
Some comments on the tests, but this looks good. Thanks a lot!
- SupportedFeatureChecks -> TestFeatureChecks - Add some calls to RequiresUnreferencedCode/RequiresAssemblyFiles
Change most of the tests to check IsUnreferencedCodeSupported and RequiresUnreferencedCode, with substitutions for ILLink/ILCompiler to make them treat IsUnreferencedCodeSupported as a constant. There are some differences in the current ILLink/ILC test infra: - ILC only allows embedded substitutions (not separate global substitutions) - ILLink doesn't allow modifying CoreLib from test embedded substitutions Because of this, we don't substitute IsDynamicCodeSupported for ILLink (it is already substituted by default for NativeAot). This matches the product behavior for ILLink, and leads to a small difference (extra warning) in the tests. We also substitute IsAssemblyFilesSupported so that NativeAot treats it as a constant, to keep it close to the product behavior and the analyzer behavior. For simplicity, this is done for both NativeAot and ILLink, even though it should really be treated the same as IsDynamicCodeCompiled for ILLink. This makes no difference in the test behavior because we don't test how ILLink specifically behaves with IsAssemblyFilesSupported.
...tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/FeatureCheckDataFlowTestSubstitutions.xml
Show resolved
Hide resolved
src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/FeatureCheckDataFlow.cs
Outdated
Show resolved
Hide resolved
Fix indentation Add a testcase showing ILLink branch removal behavior when both branches of an if throw after branch removal. This is to check that ILLink behaves like NativeAot despite showing an extra warning in the existing MeetFeaturesEmptyIntersection testcase, because IsDynamicCodeSupported isn't treated as a constant for ILLink.
if (runtimeFeaturesType == null) | ||
return false; | ||
|
||
var isDynamicCodeSupportedProperty = runtimeFeaturesType.GetMembers ("IsAssemblyFilesSupported").OfType<IPropertySymbol> ().FirstOrDefault (); |
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.
Did we add code/tests for IsDynamicCodeCompiled? See dotnet/linker#2715 (comment)
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.
Working on this: #94625. I'd appreciate your input on the design.
Can we also remove these suppressions now? Lines 28 to 33 in e61f45a
runtime/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs Lines 252 to 266 in e61f45a
runtime/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.cs Lines 86 to 87 in e61f45a
|
Yes, I have it on my list to remove these and other suppressions for various issues that have been fixed. |
This adds an understanding of
RuntimeFeature.IsDynamicCodeSupported
to the ILLink Roslyn analyzer. This is done by maintaining an additional state,FeatureContext
, alongside the tracking done for local variables.The
FeatureContext
maintains a set of features known to be enabled at a given point. The lattice meet operator is set intersection, so that we get features enabled along all paths, and the top value is the set of all features. At the beginning of the analysis, each basic block starts out in the "top" state, but the entry point gets initialized to the empty set of features. Feature checks (calls toRuntimeFeature.IsDynamicCodeSupported
) modify the state in the outgoing branch to track the fact that a given feature is enabled.When a branch operation is seen at the end of a basic block, the analysis examines it to see if it is a supported feature check. If so, it returns an abstract representation of the checked condition (which can be a boolean expression involving various features):
FeatureCheckValue
. Because this may be assumed to be true or false (depending on the outgoing branch), it has to track included and excluded features, unlikeFeatureContext
.The analysis then separates the output state for the basic block into two, one for the conditional successor, and one for the fall-through successor. It applies the
FeatureCheckValue
to each state separately, once assuming that the check is true, and once assuming that it's false, depending on the branch semantics, possibly modifying theFeatureContext
in each branch.To support this, the dataflow state tracking is now done per branch, instead of per basic block. Previously we tracked the state at the end of each block; now we track the state at the input to each edge in the control-flow graph.
The supported feature checks are hard-coded in the analyzer (this change isn't introducing any kind of attribute-based model to replace the feature xml files). For now the only supported check is
RuntimeFeature.IsDynamicCodeSupported
, but it should be easy to add new feature checks. This change includes testing of feature checks forRequiresUnreferencedCodeAttribute
andRequiresAssemblyFilesAttribute
, by including code in the analyzer that looks for specific feature guards in the test namespace. Happy to change to another approach if we don't like this.There are still some pieces of the Requires analyzer logic (generic instantiations and dynamic objects, for example) that need to be moved over to the dataflow analysis, so that the feature checks can act as guards for all of the related patterns. Until that is done, feature checks won't silence those particular warnings. I'll continue working on moving the remaining logic over, but I don't think it needs to block this change.
Fixes dotnet/linker#2715