Skip to content
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

Fix feature switch IL when feature attributes are removed #104995

Merged
merged 3 commits into from
Jul 18, 2024

Conversation

sbomer
Copy link
Member

@sbomer sbomer commented Jul 16, 2024

When removing FeatureSwitchDefinition and FeatureGuard attributes, we need to ensure that the body rewriting logic still respects these attributes even though the rewriting happens after attribute removal. Caching the substituted constant value fixes this.

More detail:

When feature switches or feature guards are substituted with a constant, the IL rewriting of the feature property happens after SweepStep, during CodeRewriterStep. At this point, any attributes marked for removal by RemoveAttributeInstancesAttribute have already been swept. When removing FeatureSwitchDefinitionAttribute and FeatureGuardAttribute, as we do with AggressiveAttributeTrimming, this means that CodeRewriterStep fails to substitute a constant for attributed feature check or feature guard properties.

Normally, the attributed feature properties would be removed entirely when substituted, so CodeRewriterStep wouldn't even visit the property. However, it's possible that the caller of the property was a copy assembly where the callsite can't be rewritten to return a constant.

When this happens, we can end up with a property that's not stubbed out during CodeRewriterStep, even though we didn't mark a callee of the property (because MarkStep did treat the property as a constant and didn't mark its instructions). When trying to write out the property's IL this results in a failure because we see a reference to a method that was removed.

Fixes #104945

@sbomer sbomer requested a review from vitek-karas July 16, 2024 21:34
@sbomer sbomer requested a review from marek-safar as a code owner July 16, 2024 21:34
@dotnet-issue-labeler dotnet-issue-labeler bot added the area-Tools-ILLink .NET linker development as well as trimming analyzers label Jul 16, 2024
@dotnet-policy-service dotnet-policy-service bot added the linkable-framework Issues associated with delivering a linker friendly framework label Jul 16, 2024
Copy link
Contributor

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

@@ -68,6 +70,9 @@ public bool TryGetMethodStubValue (MethodDefinition method, out object? value)

internal bool TryGetFeatureCheckValue (MethodDefinition method, out bool value)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we guarantee this will be called at least once on a given method before the CodeRewriterStep?
If I understand the repro for this problem, the callsites in trimmed assemblies are all removed due to branch removal, so we won't mark them and thus won't call this either. This leaves copy assemblies and their references. I don't remember exactly what type of processing we do there - if we end up calling MarkMethod then this should be fine.

I guess the reason why this works is:
If the CoreRewriterStep gets to process a method, that method had to be marked (otherwise it would have been removed) and thus MarkMethod ended up calling this TryGetFeatureCheckValue and thus populate the cache within.

I must admit this makes me a bit nervous - it feels pretty fragile. Maybe we should make the link from MarkMethod to this a bit more obvious - currently it goes through GetMethodAction which doesn't "seem" like being related to this that much.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the CoreRewriterStep gets to process a method, that method had to be marked (otherwise it would have been removed) and thus MarkMethod ended up calling this TryGetFeatureCheckValue and thus populate the cache within.

Yes, exactly.

It is a bit fragile. I thought of a way to get to to TryGetFeatureCheck in CodeRewriterStep that's not cached - it's possible to stub a body (with XML or SetAction) without also setting a stubbed value (even though we usually do both). If that's done for a method that also has a feature attribute, the stub value might not have been precomputed.

I added a test for this scenario and added a call to TryGetMethodStubValue in MarkMethod. I think that addresses your concern.

Copy link
Member

@vitek-karas vitek-karas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@sbomer sbomer merged commit 9cbd699 into dotnet:main Jul 18, 2024
76 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Aug 18, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-Tools-ILLink .NET linker development as well as trimming analyzers linkable-framework Issues associated with delivering a linker friendly framework
Projects
None yet
Development

Successfully merging this pull request may close these issues.

"Optimizing assemblies for size failed" - Core 9 Preview 6 Maui Mac Catalyst Build
2 participants