-
Notifications
You must be signed in to change notification settings - Fork 10.6k
Description
Previous ID | SR-5441 |
Radar | rdar://problem/33255593 |
Original Reporter | @atrick |
Type | Task |
Status | Closed |
Resolution | Done |
Additional Detail from JIRA
Votes | 0 |
Component/s | Compiler |
Labels | Task, AffectsABI |
Assignee | @aschwaighofer |
Priority | Medium |
md5: 940b3fa562a38f5d53346475aff54fcc
relates to:
- SR-5699 Non escaping blocks should not need to malloc captured values (stack memory pointers are just fine)
- SR-6148 SIL Representation of Closures
Issue Description:
This is a task to formally specify and implement a stable ABI for closure captures.
Summary of proposed ABI changes from the current design
1. Move from @callee_owned to @callee_guaranteed as the default.
2. Add a non-escaping case to the ABI that treats the context as an
opaque word instead of a reference counted box.
Background
When we talk about closure ABI, we're talking about `@thick` function
types in SIL. There will be two conventions for passing these function
types: escaping and non-escaping. In both cases, they are passed as a
function pointer and a context.
We need to specify:
- the convention for passing `@thick` function types as arguments.
- the convention for passing the closure context.
- the convention for passing arguments to a closure given its context.
We do not need to specify the layout of "boxed" arguments since the
closure body is always emitted within the same module that captures
the arguments.
Proposal
An escaping function is passed as a function pointer along with a
reference-counted context (called BoxReference below):
func takesEscapingClosure(_ f: @escaping ()->())
-> sil takesEscapingClosure(@callee_guaranteed @escaping ()->()) -> ()
-> takesEscapingClosure(void (*)(), BoxReference)
A single box provides access to all captured variables. This is
consistent with the current implementation.
A non-escaping function is passed as a function pointer along with a
word-sized opaque value represnting the context. (In practice, the
context will likely be an address directly into the Boxed captures.)
func takesNonescapingClosure(_ f: ()->())
-> sil takesNonescapingClosure(@callee_guaranteed ()->()) -> ()
-> takesNonescapingClosure(void (*)(), OpaqueBox)
This differs from the current implementation by removing the guarantee
that the context can be reference counted. The callee is no longer
allowed to copy the box that represents the context.
To allow conversion between function types, we must change the
context's ownership convention from `@owned` to `@guaranteed`.
Non-ABI Implementation Details.
This ABI allows for easy conversion between function types. The layout
of the Box will be the same in both escaping and non-escaping
cases. The only difference in represention and semantics of the
context is that in the escaping case, the context may be retained by
the callee.
Converting from escaping to non-escaping:
The escaping function type must be retained for the duration of the
non-escaping variable. Within that scope, the address of the box can
simply be projected directly from the heap box and passed as the
context of a non-escaping closure.
Converting from non-escaping to escaping (withoutActuallyEscaping):
A heap box is allocated and the contents of the non-escaping box are
copied into the heap box and written back into the non-escaping box at
the end of scope. According to exclusivity rules, the closure can
neither escape nor be invoked nonreentrantly. The heap box will need
some additional state to enforce `withoutActuallyEscaping`.