Skip to content

[SR-5441] Closure Capture ABI #48015

@atrick

Description

@atrick
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:

  1. the convention for passing `@thick` function types as arguments.
  2. the convention for passing the closure context.
  3. 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`.

Metadata

Metadata

Assignees

Labels

affects ABIFlag: Affects ABIcompilerThe Swift compiler itself

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions