-
Notifications
You must be signed in to change notification settings - Fork 127
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
Track pending marked members #1768
Conversation
{ | ||
processed.Add (provider); | ||
if (processed.Add (provider)) { |
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.
Can you think of better api? It seems that Mark+SetProccessed will do extra work 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.
I thought of adding something like MarkProcessed, but it would only be used in one place because MarkStep often does a small amount of extra work for items which are already processed and get marked again.
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 I counted more places than 1 and there is also the second combination doing a similar thing with Matk+CheckProcessed. I think it's worth looking if this can be written more effectively
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.
You're right - I was looking at CheckProcessed, but there are more for SetProcessed. I will add a helper for these cases.
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 added a helper for the Mark+SetProcessed case, but I left CheckProcessed alone since derived classes might override it.
if (reason.Kind == DependencyKind.AlreadyMarked) { | ||
Debug.Assert (Annotations.IsMarked (field)); | ||
} else { | ||
Annotations.Mark (field, 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.
Could this be moved after CheckProcessed?
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.
It needs to go before CheckProcessed because the tracing can happen for already-processed items.
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 guess I don't follow why we need to move this at all - previously it was the last thing the method did, now it's the very first thing - what has changed?
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.
Mark should happen before CheckProcessed, because multiple sources can mark the same field, and all of those dependencies should be traced (which happens whenever we Mark). It was incorrect before, and now the order is enforced by SetProcess which asserts that a processed item should have been marked (pending) first - that's how I caught this.
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.
So could we instead of double marking and adding AlreadyMarked special case not to mark it twice? It seems like this logic is needed only for preserved elements. Could we Mark it from preserve only?
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.
Not sure I understand the question - the AlreadyMarked check is there to avoid calling Annotations.Mark twice when a different step marks a member through Annotations (not just for typepreserve or preserved methods). But we still need to call Annotations.Mark if the MarkStep.Mark* method is called directly (for example when we mark declaring types).
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.
What I meant is that you can simplify this handling by not doing the double-checking with AlreadyMarked and marked_pending at one line and removing it at the next line.
I think replacing the introduced redundant logic should be fixed.
-
For AlreadyMarked logic you could split the methods and call them with no DependencyInfo from
ProcessMarkedPending
and skip all the checks because the flow went through them already. -
For Mark+CheckProcessed logic
You could replace
Annotations.Mark (assembly, reason);
if (CheckProcessed (assembly))
return;
code flow with simple insertion to processed
and not to touch marked_pending
at all.
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 for clarifying - I made this optimization for the Mark
+SetProcessed
cases, but like I mentioned in #1768 (comment), I'd rather not change the behavior of existing callsites that use CheckProcessed
here, same goes for the AlreadyMarked
pattern. I opened #1776 to track this.
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 understand the perf impact, but it feel fragile to have a "already marked" version and "non marked" version and try to make sure that all callsites are correct. The overall impact should be pretty small (assuming we're not marking 10000s of methods from custom steps).
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 agree about the "already marked"/"non marked" split - there might be other ways to address #1776 without a split. Also FWIW I did a rough perf measurement on helloworld before/after the change in this PR, and it's probably within noise, but if anything it was a slight improvement (maybe because we avoid walking types in Initialize, though I didn't dig into it).
- Call Annotations.Mark for preserved methods up-front, if the key was already preserved. This way we only track preserved methods for things that aren't marked already. - Track pending preserve info that needs to be applied - Clean up logic for pending marked members The Mark* methods in MarkStep "fully" mark members (base types, parameters, etc.), whereas the methods in Annotations only perform a "shallow" mark (they track the fact that this member is to be kept, but they don't process implications of this fact - for example, they may mark methods without marking the containing type). MarkStep fulfills "shallow" marks by keeping pieces of IL that are required. Each Mark* method in MarkStep now serves two purposes: - Recursive marking of required IL (for example base types) It calls Mark* on everything that is required, with a "reason" so that this gets traced. The base cases call Annotations.Mark to trace the reason and keep the required IL. After this is done, the original member passed to Mark* is considered "processed". We set the processed bit early enough to ensure that the recursive logic is only performed once. - Fulfilling "shallow" marks Similar to the above, but in this case, it does not need to trace the dependency again (Annotations.Mark has already been called). We need to ensure that it transitions the mark state from "pending" -> "processed" so that anything else which does a shallow mark of the member doesn't introduce new "pending" state.
Call MarkMethod instead of EnqueueMethod so that early-marked methods get an action assigned. This used to happen in ResolveFromAssemblyStep. Also fix up typepreserve to correctly track accessibility bits, and add a testcase for copyused marking behavior. Fix formatting
And remove currently failing testcase.
d1f8827
to
bd85953
Compare
if (reason.Kind == DependencyKind.AlreadyMarked) { | ||
Debug.Assert (Annotations.IsMarked (field)); | ||
} else { | ||
Annotations.Mark (field, 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.
I guess I don't follow why we need to move this at all - previously it was the last thing the method did, now it's the very first thing - what has changed?
if (!(bool) customData) { | ||
Annotations.AddPreservedMethod (type, method); | ||
} else { | ||
Annotations.Mark (method, new DependencyInfo (DependencyKind.XmlDescriptor, _xmlDocumentLocation)); | ||
} |
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.
This is potentially a change of behavior - unless MarkIndirectlyCalledMethod will end up marking the method anyway (which it might). If it is a change of behavior we should add tests for this.
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.
The behavior change is that Initialize used to only fully mark a method if it belonged to a marked type, so the method wouldn't actually be marked unless the type was - I will add a test for this. I think the call to Mark here has always been redundant.
The "pending" logic will fully mark any marked methods even if the declaring type wasn't marked, so to preserve the XML behavior we shouldn't mark the method. AddPreservedMethod already expresses the type -> method behavior that we had originally.
- Return arrays instead of IEnumerable for pending Annotations - Rename methods to Get* - Add comment for marked_pending - Don't make state changes inside a Debug assert - Change sourceLocationMember to null when marking pending types - Add exception messages
We used to ignore such methods in Initialize, but the new logic for pending marked items will keep the method (and its type) as long as it was marked before MarkStep.
To directly mark an item without adding it to the pending set first.
I've addressed all the feedback so far - PTAL |
Track pending marked members This enables tracking of pending marked items which need to be fully marked by MarkStep. Contributes to dotnet/linker#1735 - this change is in preparation for running custom steps during MarkStep, where they could change the Annotations state in ways that interact with other marking logic. The idea is that custom steps can call Annotations methods without triggering a lot of other processing, because the full logic is deferred. This way custom steps don't need to be re-entrant. This change causes Annotations.Mark to place members into a set of "pending" items which will get fully marked ("processed") later. "pending" items are already considered marked. AddPreservedMethod conceptually adds a conditional dependency from source -> destination - so if source gets marked, destination should get marked. This change will immediately call Annotations.Mark on the destination if the source is already marked, and otherwise track the condition to be applied if the source gets marked later. SetPreserve can change the TypePreserve of a type after it has been marked. This change will track any changes to TypePreserve and apply them later even for types which have already been marked. This works a little differently from AddPreservedMethod, to avoid traversing type members in Annotations. Note that once we allow custom steps to run during during MarkStep, Process will call ProcessMarkedPending - but this isn't required in this change. The intention here is just to add adding extra tracking (that will actually be used by dotnet/linker#1666), while mostly preserving existing behavior. Commit migrated from dotnet/linker@24ba73a
Separated out from #1666.
This enables tracking of pending marked items which need to be fully marked by MarkStep. Contributes to #1735 - this change is in preparation for running custom steps during MarkStep, where they could change the Annotations state in ways that interact with other marking logic. The idea is that custom steps can call Annotations methods without triggering a lot of other processing, because the full logic is deferred. This way custom steps don't need to be re-entrant.
This change causes
Annotations.Mark
to place members into a set of "pending" items which will get fully marked ("processed") later. "pending" items are already considered marked.AddPreservedMethod
conceptually adds a conditional dependency from source -> destination - so if source gets marked, destination should get marked. This change will immediately call Annotations.Mark on the destination if the source is already marked, and otherwise track the condition to be applied if the source gets marked later.SetPreserve
can change the TypePreserve of a type after it has been marked. This change will track any changes to TypePreserve and apply them later even for types which have already been marked. This works a little differently fromAddPreservedMethod
, to avoid traversing type members in Annotations.Note that once we allow custom steps to run during during MarkStep,
Process
will callProcessMarkedPending
- but this isn't required in this change. The intention here is just to add adding extra tracking (that will actually be used by #1666), while mostly preserving existing behavior.