Skip to content

[sil.rst] Add a discussion of "Dead End Blocks" and its implications upon OSSA. #35233

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

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 81 additions & 5 deletions docs/SIL.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1681,11 +1681,12 @@ that use a SIL value are required to be able to be semantically partitioned in
between "non-lifetime ending uses" that just require the value to be live and
"lifetime ending uses" that end the lifetime of the value and after which the
value can no longer be used. Since by definition operands that are lifetime
ending uses end their associated value's lifetime, we must have that the
lifetime ending use points jointly post-dominate all non-lifetime ending use
points and that a value must have exactly one lifetime ending use along all
reachable program paths, preventing leaks and use-after-frees. As an example,
consider the following SIL example with partitioned defs/uses annotated inline::
ending uses end their associated value's lifetime, we must have that, ignoring
program ending `Dead End Blocks`_, the lifetime ending use points jointly
post-dominate all non-lifetime ending use points and that a value must have
exactly one lifetime ending use along all reachable program paths, preventing
leaks and use-after-frees. As an example, consider the following SIL example
with partitioned defs/uses annotated inline::

sil @stash_and_cast : $@convention(thin) (@owned Klass) -> @owned SuperKlass {
bb0(%kls1 : @owned $Klass): // Definition of %kls1
Expand Down Expand Up @@ -2316,6 +2317,81 @@ The current list of interior pointer SIL instructions are:
(*) We still need to finish adding support for project_box, but all other
interior pointers are guarded already.

Dead End Blocks
~~~~~~~~~~~~~~~

In SIL, one can express that a program is semantically expected to exit at the
end of a block by terminating the block with an `unreachable`_. Such a block is
called a *program terminating block* and all blocks that are post-dominated by
blocks of the aforementioned kind are called *dead end blocks*. Intuitively, any
path through a dead end block is known to result in program termination, so
resources that normally would need to be released back to the system will
instead be returned to the system by process tear down.

Since we rely on the system at these points to perform resource cleanup, we are
able to loosen our lifetime requirements by allowing for values to not have
their lifetimes ended along paths that end in program terminating
blocks. Operationally, this implies that:

* All SIL values must have exactly one lifetime ending use on all paths that
terminate in a `return`_ or `throw`_. In contrast, a SIL value does not need to
have a lifetime ending use along paths that end in an `unreachable`_.

* `end_borrow`_ and `destroy_value`_ are redundent, albeit legal, in blocks
where all paths through the block end in an `unreachable`_.

Consider the following legal SIL where we leak ``%0`` in blocks prefixed with
``bbDeadEndBlock`` and consume it in ``bb2``::

sil @user : $@convention(thin) (@owned Klass) -> @owned Klass {
bb0(%0 : @owned $Klass):
cond_br ..., bb1, bb2

bb1:
// This is a dead end block since it is post-dominated by two dead end
// blocks. It is not a program terminating block though since the program
// does not end in this block.
cond_br ..., bbDeadEndBlock1, bbDeadEndBlock2

bbDeadEndBlock1:
// This is a dead end block and a program terminating block.
//
// We are exiting the program here causing the operating system to clean up
// all resources associated with our process, so there is no need for a
// destroy_value. That memory will be cleaned up anyways.
unreachable

bbDeadEndBlock2:
// This is a dead end block and a program terminating block.
//
// Even though we do not need to insert destroy_value along these paths, we
// can if we want to. It is just necessary and the optimizer can eliminate
// such a destroy_value if it wishes.
//
// NOTE: The author arbitrarily chose just to destroy %0: we could legally
// destroy either value (or both!).
destroy_value %0 : $Klass
unreachable

bb2:
cond_br ..., bb3, bb4

bb3:
// This block is live, so we need to ensure that %0 is consumed within the
// block. In this case, %0 is consumed by returning %0 to our caller.
return %0 : $Klass

bb4:
// This block is also live, but since we do not return %0, we must insert a
// destroy_value to cleanup %0.
//
// NOTE: The copy_value/destroy_value here is redundent and can be removed by
// the optimizer. The author left it in for illustrative purposes.
%1 = copy_value %0 : $Klass
destroy_value %0 : $Klass
return %1 : $Klass
}

Runtime Failure
---------------

Expand Down