-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Fix speculative devirtualization to correctly use casted value #29003
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 speculative devirtualization to correctly use casted value #29003
Conversation
…d value We used to emit this: checked_cast_br [exact] %0 : $Foo to $Bar, bb1, bb2 bb1(%1 : $Bar): %2 = unchecked_ref_cast %0 : $Foo to %1 : $Bar apply ...(..., %2) br ... bb2: ... This is not ownership SIL-safe, because we're re-using the original operand, which will have already been consumed at that point. The more immediate problem here is that this is actually not safe when combined with other optimizations. Suppose that after the speculative devirtualization, our function is inlined into another function where the operand was an upcast. Now we have this code: %0 = upcast ... checked_cast_br [exact] %0 : $Foo to $Bar, bb1, bb2 bb1(%1 : $Bar): %2 = unchecked_ref_cast %0 : $Foo to %1 : $Bar apply ...(..., %2) br ... bb2: ... The SILCombiner will simplify the unchecked_ref_cast of the upcast into an unchecked_ref_cast of the upcast's original operand. At this point, we have an unchecked_ref_cast between two unrelated class types. While this means the block bb1 is unreachable, we might perform further optimizations on it before we run the cast optimizer and delete it altogether. In particular, the devirtualizer follows chains of reference cast instructions, and it will get very confused if it finds this invalid cast between unrelated types. Fixes <rdar://problem/57712094>, <https://bugs.swift.org/browse/SR-11916>.
I wasn't able to come up with a reduced test case that triggers the original crash (the original bug report is while building SwiftNIO). The FileCheck tests for the output of speculative devirtualization are the best I could do. |
@swift-ci Please smoke test |
@swift-ci Please test source compatibility |
@gottesmm You might be interested in this from the ownership SIL angle. |
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's hard to follow the log comment for a couple reasons:
There's an extra %1
operand here:
%2 = unchecked_ref_cast %0 : $Foo to %1 : $Bar
I understand that checked_cast_br
supports any conversion, while unchecked_ref_cast
statically constrains the types, so I see how there could be a problem in theory. But it's not clear why the upcast
would expose a problem--that should also have the same constraints. And I don't see any example of the problem scenario in the .sil test. What type was the upcast
operating on and why couldn't that be unchecked_ref_cast
'd?
@atrick The checked_cast_br is emitted by the devirtualizer in this case so its always a simple class downcast. The problem is rather that we're using the operand to the checked_cast_br instruction inside the success block. The devirtualizer follows chains of cast instructions to find the 'original' type of the value. The problem is we had this:
It was trying to devirtualize SecondSubclass.someMethod on an instance of FirstSubclass, which tripped an assertion. The SIL tests were updated to CHECK for the pattern that is now generated, where instead of emitting an unchecked_ref_cast, we simply use the SILArgument in the success block of the checked_cast_br. Does that clarify things? |
@atrick Also I wasn't able to derive a stand-alone test case because in my small examples, the dead branches from the cast were deleted before we would do further optimization and devirtualization on them, so we never saw the invalid SIL. The SwiftNIO function that triggered this had something like 500 basic blocks. However I think even as a cleanup in preparation for eventually converting speculative devirt to ownership SIL, this change makes sense to land in its current form. You can't use the original operand to a checked_cast_br in the success block, because it's been consumed. |
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.
@slavapestov Totally makes sense. I see how two bugs are being fixed at once. I run into the issue all the time that there's no way unit test certain mistakes in the optimizer.
We used to emit this:
This is not ownership SIL-safe, because we're re-using the original
operand, which will have already been consumed at that point.
The more immediate problem here is that this is actually not safe
when combined with other optimizations. Suppose that after the
speculative devirtualization, our function is inlined into another
function where the operand was an upcast. Now we have this code:
The SILCombiner will simplify the unchecked_ref_cast of the upcast
into an unchecked_ref_cast of the upcast's original operand. At
this point, we have an unchecked_ref_cast between two unrelated
class types. While this means the block bb1 is unreachable, we
might perform further optimizations on it before we run the cast
optimizer and delete it altogether.
In particular, the devirtualizer follows chains of reference cast
instructions, and it will get very confused if it finds this invalid
cast between unrelated types.
Fixes rdar://problem/57712094, https://bugs.swift.org/browse/SR-11916.