-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[semantic-arc] When computing Lifetimes include all consuming uses, not just the final destroying use. #33351
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
[semantic-arc] When computing Lifetimes include all consuming uses, not just the final destroying use. #33351
Conversation
@swift-ci test |
@swift-ci benchmark |
I don't expect this to change anything, but just being careful. |
|
||
/// Critical edges that couldn't be split to compute the frontier. This could | ||
/// be non-empty when the analysis is invoked with DontModifyCFG mode. | ||
llvm::SmallVector<std::pair<TermInst *, unsigned>, 16> criticalEdges; |
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 just moved this up partly to clean things up a little but also so I could use a decltype in the constructor so I don't have a long PointerUnion in that declaration.
Build failed |
Performance: -OCode size: -OPerformance: -Osize
Code size: -OsizePerformance: -Onone
Code size: -swiftlibsHow to read the dataThe tables contain differences in performance which are larger than 8% and differences in code size which are larger than 1%.If you see any unexpected regressions, you should consider fixing the Noise: Sometimes the performance results (not code size!) contain false Hardware Overview
|
@swift-ci test windows platform |
@swift-ci test OS X platform |
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.
Great!
…ot just the final destroying use. TLDR: This fixes an ownership verifier assert caused by not placing end_borrows along paths where an enum is provable to have a trivial case. It only happens if all non-trivial cases in a switch_enum are "dead end blocks" where the program will end and we leak objects. The Problem ----------- The actual bug here only occurs in cases where we have a switch_enum on an enum with mixed trivial, non-trivial cases and all of the non-trivial payloaded cases are "dead end blocks". As an example, lets look at a simple switch_enum over an optional where the .some case is a dead end block and we leak the Klass object into program termination: ``` %0 = load [copy] %mem : $Klass switch_enum %0 : $Optional<Klass>, case #Optional.some: bbDeadEnd, case #Optional.none: bbContinue bbDeadEnd(%0a : @owned $Klass): // %0 is leaked into program end! unreachable bbContinue: ... // program continue. ``` In this case, if we were only looking at final destroying uses, we would pass a def without any uses to the ValueLifetimeChecker causing us to not have a frontier at all causing us to not insert any end_borrows, yielding: ``` %0 = load_borrow %mem : $Klass switch_enum %0 : $Optional<Klass>, case #Optional.some: bbDeadEnd, case #Optional.none: bbContinue bbDeadEnd(%0a : @guaranteed $Klass): // %0 is leaked into program end and // doesnt need an end_borrow! unreachable bbContinue: ... // program continue... we need an end_borrow here though! ``` This then trips the ownership verifier since switch_enum is a transforming terminator that acts like a forwarding instruction implying we need an end_borrow on the base value along all non-dead end paths through the program. Importantly this is not actually a leak of a value or unsafe behavior since the only time that we enter into unsafe territory is along paths where the enum was actually trivial. So the load_borrow is actually just loaded the trivial enum value. The Fix ------- In order to work around this, I realized that the right solution is to also include the forwarding consuming uses (in this case the switch_enum use) when determining the lifetime and that this solves the problem. That being said, after I made that change, I noticed that I needed to remove my previous manner of computing the insertion point to use for arguments when finding the lifetime using ValueLifetimeAnalysis. Previously since I was using only the destroying uses I knew that the destroy_value could not be the first instruction in the block of my argument since I handled that case individually before using the ValueLifetimeAnalysis. That invariant is no longer true as can be seen in the case above if %0 was from a SILArgument itself instead of a load [copy] and we were converting that argument to be a guaranteed argument. To fix this, I taught ValueLifetimeAnalysis how to handle defs from Arguments. The key thing is I noticed while reading the code that the analysis only generally cared about the instruction's parent block. Beyond that, the def being from an instruction was only needed to determine if a user is earlier in the same block as the def instruction. Those concerns not apply to SILArgument which dominate all instructions in the same block, so in this patch, we just skip those conditional checks when we have a SILArgument. The rest of the code that uses the parent block is the same for both SILArgument/SILInstructions. rdar://65244617
1086aff
to
962106f
Compare
@swift-ci test |
Build failed |
TLDR: This fixes an ownership verifier assert caused by not placing end_borrows
along paths where an enum is provable to have a trivial case. It only happens if
all non-trivial cases in a switch_enum are "dead end blocks" where the program
will end and we leak objects.
The Problem
The actual bug here only occurs in cases where we have a switch_enum on an enum
with mixed trivial, non-trivial cases and all of the non-trivial payloaded cases
are "dead end blocks". As an example, lets look at a simple switch_enum over an
optional where the .some case is a dead end block and we leak the Klass object
into program termination:
In this case, if we were only looking at final destroying uses, we would pass a
def without any uses to the ValueLifetimeChecker causing us to not have a
frontier at all causing us to not insert any end_borrows, yielding:
This then trips the ownership verifier since switch_enum is a transforming
terminator that acts like a forwarding instruction implying we need an
end_borrow on the base value along all non-dead end paths through the program.
Importantly this is not actually a leak of a value or unsafe behavior since the
only time that we enter into unsafe territory is along paths where the enum was
actually trivial. So the load_borrow is actually just loaded the trivial enum
value.
The Fix
In order to work around this, I realized that the right solution is to also
include the forwarding consuming uses (in this case the switch_enum use) when
determining the lifetime and that this solves the problem.
That being said, after I made that change, I noticed that I needed to remove my
previous manner of computing the insertion point to use for arguments when
finding the lifetime using ValueLifetimeAnalysis. Previously since I was using
only the destroying uses I knew that the destroy_value could not be the first
instruction in the block of my argument since I handled that case individually
before using the ValueLifetimeAnalysis. That invariant is no longer true as can
be seen in the case above if %0 was from a SILArgument itself instead of a load
[copy] and we were converting that argument to be a guaranteed argument.
To fix this, I taught ValueLifetimeAnalysis how to handle defs from
Arguments. The key thing is I noticed while reading the code that the analysis
only generally cared about the instruction's parent block. Beyond that, the def
being from an instruction was only needed to determine if a user is earlier in
the same block as the def instruction. Those concerns not apply to SILArgument
which dominate all instructions in the same block, so in this patch, we just
skip those conditional checks when we have a SILArgument. The rest of the code
that uses the parent block is the same for both SILArgument/SILInstructions.
rdar://65244617
Replace this paragraph with a description of your changes and rationale. Provide links to external references/discussions if appropriate.
Resolves SR-NNNN.