You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
DI: New analysis to replace the 'self consumed' analysis
In a throwing or failable initializer for a class, the typical pattern
is that an apply or try_apply consumes the self value, and returns
success or failure. On success, a new self value is produced.
On failure, there is no new self value. In both cases, the original
self value no longer exists.
We used to model this by attempting to look at the apply or try_apply
instruction, and figure out from subsequent control flow which
successor block was the success case and which was the error case.
The error blocks were marked as such, and a dataflow analysis was used
to compute whether 'self' had been consumed in each block reachable
from the entry block.
This analysis was used to prevent invalid use of 'self' in catch
blocks when the initializer delegation was wrapped in do/catch;
more importantly, it was also used to know when to release 'self'
on exit from the initializer.
For example, when we 'throw e' here, 'self' was already consumed
and does not need to be released -- doing so would cause a crash:
do {
try self.init(...)
} catch let e {
// do some other cleanup
throw e
}
On the other hand, here we do have to release 'self', otherwise we
will exit leaking memory:
do {
try someOtherThing()
self.init(...)
} catch let e {
// do some other cleanup
throw e
}
The problem with the old analysis is that it was too brittle and did
not recognize certain patterns generated by SILGen. For example, it
did not correctly detect the failure block of a delegation to a
foreign throwing initializer, because those are not modeled as a
try_apply; instead, they return an Optional value.
For similar reasons, we did not correctly failure blocks emitted
after calls to initializers which are both throwing and failable.
The new analysis is simpler and more robust. The idea is that in the
success block, SILGen emits a store of the new 'self' value into
the self box. So all we need to do is seed the dataflow analysis with
the set of blocks where the 'self' box is stored to, excluding the
initial entry block.
The new analysis is called 'self initialized' rather than 'self
consumed'. In blocks dominated by the self.init() delegation,
the result is the logical not of the old analysis:
- If the old analysis said self was consumed, the new one says self
is not initialized.
- If the old analysis said self was not consumed, the new analysis
says that self *is* initialized.
- If the old analysis returned a partial result, the new analysis
will also; it means the block in question can be reached from
blocks where the 'self' box is both initialized and not.
Note that any blocks that precede the self.init() delegation now
report self as uninitialized, because they are not dominated by
a store into the box. So any clients of the old analysis must first
check if self is "live", meaning we're past the point of the
self.init() call. Only if self is live do we then go on to check
the 'self initialized' analysis.
0 commit comments