File: CPointerArgumentInterop.rst

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (343 lines) | stat: -rw-r--r-- 13,840 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
:orphan:

Summary
=======

Pointer arguments are a fact of life in C and Cocoa, and there's no way we're
going to realistically annotate or wrap every API safely. However, there are
plenty of well-behaved APIs that use pointer arguments in well-behaved ways
that map naturally to Swift argument conventions, and we should interact with
those APIs in a natural, Swift-ish way. To do so, I propose adding language
and library facilities that enable the following uses of pointer
arguments:

- Const pointer arguments ``const int *``, including const pointers to ObjC
  classes ``NSFoo * const *``, can be used as "in" array arguments,
  as ``inout`` scalar arguments, or as ``UnsafeMutablePointer`` arguments.
- Non-const pointer arguments to C types, ``int *``, can be used as ``inout``
  array or scalar arguments, or as ``UnsafeMutablePointer`` arguments.
- Non-const pointer arguments to ObjC class types, ``NSFoo **``, can be used as
  ``inout`` scalar arguments or passed ``nil``. (They cannot be used as
  array arguments or as ``UnsafeMutablePointer`` arguments.)
- ``const void *`` and ``void *`` pointers can be used in the same ways as
  pointers to any C type (but not ObjC types).

This model intentionally doesn't try to provide a mapping to every possible
use case for pointers in C.  It also intentionally avoids addressing special
cases we could potentially provide higher-level support for. Some particular
issues this proposal specifically does not address:

- Pointer return values
- Special handling of ``char*`` and/or ``const char*`` arguments
- Special handling of Core Foundation types
- Special handling of ``NSError**`` arguments
- Precise lifetime of values (beyond the minimal lifetime extension to the
  duration of a call)
- Overriding of ObjC methods that take pointer arguments with subclass methods
  that take non-pointer arguments

Design Considerations
=====================

Const Pointer Arguments
-----------------------

Arrays
~~~~~~

Const pointer arguments are frequently used in both C and Objective-C to take
an array of arguments effectively by value. To support this use case, we should
support passing a Swift ``Array`` value to a const pointer argument. An
example from Core Foundation is ``CGColorCreate``, which takes a
``CGFloat`` array of color space-specific components::

  let rgb = CGColorSpaceCreateCalibratedRGB()
  let cyan = CGColorCreate(rgb, [0, 1, 1])

We are willing to assume that the API is well-behaved and does not mutate the
pointed-to array, so we can safely pass an interior pointer to the array storage
without worrying about mutation. We only guarantee the lifetime of the
array for the duration of the call, so it could potentially be promoted to a
stack allocation.

"In" Arguments
~~~~~~~~~~~~~~

Const pointer arguments are also used in many cases where a value is unmodified,
but its identity is important. Somewhat more rarely, const pointer arguments
are used as pure "in" value arguments with no regard for identity; this is
particularly prevalent on platforms like Win32 where there has historically
been no standard ABI for passing structs by value, but pure "in" pointer
parameters are rare on our platforms.  The potential consequences of
disregarding value identity with C APIs are too high to allow scalar arguments
to implicitly be used as pointer arguments::

  // From C: void foo(const pthread_mutex_t *);
  import Foo

  let mutex = pthread_mutex_create()
  // This would pass a different temporary buffer on each call--not what you
  // want for a mutex!
  foo(mutex)
  foo(mutex)
  foo(mutex)

Although const pointers should never be used for actual mutation, we propose
that only ``inout`` scalar arguments be accepted for const pointer parameters.
Although our semantics normally do not guarantee value identity, ``inout``
parameters that refer to stored variables or stored properties of C-derived
types are in practice never subjected to implicit writebacks except in limited
circumstances such as capture of ``inout`` references in closures that could be
diagnosed. Requiring ``inout`` also prevents the use of rvalues or ``let``
bindings that never have well-defined addresses as pointer arguments. This
more clearly communicates the intent for the callee to receive the same
variable on every call::

  // From C: void foo(const pthread_mutex_t *);
  import Foo

  var mutex = pthread_mutex_create()
  foo(&mutex)
  foo(&mutex)
  foo(&mutex)

If using an rvalue as a pointer argument is desired, it can easily be wrapped
in an array. This communicates that the value *is* being copied into the
temporary array, so it's more obvious that identity would not be maintained::

  // an immutable scalar we might want to pass into a "const double*".
  let grayLevel = 0.5
  let monochrome = CGColorSpaceCreateGrayscale()

  // error, can't pass Double into second argument.
  let c1 = CGColorCreate(monochrome, grayval)
  // error, can't take the address of a 'let' (would be ok for a 'var')
  let c2 = CGColorCreate(monochrome, &grayval)
  // OK, we're explicitly forming an array
  let c3 = CGColorCreate(monochrome, [grayval])

Non-Const Pointer Arguments
---------------------------

C Types
~~~~~~~

Non-const arguments of C type can be used as "out" or "inout" parameters,
either of scalars or of arrays, and so should accept ``inout`` parameters of
array or scalar type. Although a C API may expect a pure "out" parameter and
not require initialization of its arguments, it is safer to assume the argument
is ``inout`` and always require initialization::

  var s, c: Double
  // error, 's' and 'c' aren't initialized
  sincos(0.5, &s, &c)

  var s1 = 0.0, c1 = 0.0
  // OK
  sincos(0.5, &s1, &c1)

For array parameters, the exact point of mutation inside the callee cannot be
known, so a copy-on-write array buffer must be eagerly uniqued prior to the
address of the array being taken::

  func loadFloatsFromData(_ data: NSData) {
    var a: [Float] = [0.0, 0.0, 0.0, 0.0]
    var b = a

    // Should only mutate 'b' without affecting 'a', so its backing store
    // must be uniqued
    data.getBytes(&b, sizeof(Float.self) * b.count)
  }

ObjC Types
~~~~~~~~~~

ARC semantics treat an ``NSFoo**`` type as a pointer to an ``__autoreleasing``
``NSFoo*``. Although in theory these interfaces could receive arrays of object
pointers in Objective-C, that use case doesn't come up in Cocoa, and we can't
reliably bridge such APIs into Swift. We only need to bridge ObjC mutable pointer
types to accept a scalar ``inout`` object reference or ``nil``.

Pointer Return Values
---------------------

This proposal does not address the handling of return values, which should still
be imported into Swift as ``UnsafeMutablePointer`` values.


Library Features
================

The necessary conversions can be represented entirely in the standard library
with the help of some new language features, inout address conversion, inout
writeback conversion, and interior pointer conversion, described below. There
are three categories of argument behavior needed, and thus three new types.
These types should have no user-accessible operations of their own other than
their implicit conversions. The necessary types are as follows:

- ``CConstPointer<T>`` is the imported representation of a ``const T *``
  argument. It is implicitly convertible from ``inout T`` by inout address
  conversion and from ``Array<T>`` by immutable interior pointer
  conversion. It is also implicitly convertible to and from ``UnsafeMutablePointer<T>``
  by normal conversion.
- ``CMutablePointer<T>`` is the imported representation of a ``T *``
  argument for a POD C type ``T``. It is implicitly convertible from
  ``inout T`` by inout address conversion and from ``inout Array<T>`` by mutating
  interior pointer conversion. It is also implicitly convertible to and from
  ``UnsafeMutablePointer<T>`` by normal conversion.
- ``ObjCInOut<T>`` is the imported representation of a ``T **``
  argument for an ObjC class type ``T``. It is implicitly convertible from
  ``inout T`` by inout writeback conversion and is implicitly convertible
  from ``nil``. It cannot be converted from an array or to ``UnsafeMutablePointer``.

New Language Features
=====================

To support the necessary semantics for argument passing, some new conversion
forms need to be supported by the language with special-cased lifetime behavior.

Interior Pointer Conversions
----------------------------

To be able to pass a pointer to array data as an argument, we need to be able
to guarantee the lifetime of the array buffer for the duration of the call.
If mutation can potentially occur through the pointer, then copy-on-write
buffers must also be uniqued prior to taking the address. A new form of
conversion, ``@unsafe_interior_pointer_conversion``, can be applied to an
instance method of a type, to allow that type to return both a converted
pointer and an owning reference that guarantees the validity of the pointer.
Such methods can be either ``mutating`` or non-mutating; only non-mutating
conversions are considered for non- ``inout`` parameters, and only ``mutating``
conversions are considered for ``inout`` parameters::

  extension Array {
    @unsafe_interior_pointer_conversion
    func convertToConstPointer()
    -> (CConstPointer<T>, ArrayBuffer<T>) {
      return (CConstPointer(self.base), self.owner)
    }

    @unsafe_interior_pointer_conversion
    mutating func convertToMutablePointer()
    -> (CMutablePointer<T>, ArrayBuffer<T>) {
      // Make the backing buffer unique before handing out a mutable pointer.
      self.makeUnique()
      return (CMutablePointer(self.base), self.owner)
    }
  }

``@unsafe_interior_pointer_conversion`` conversions are only considered in
argument contexts. If such a conversion is found, the first element of the
return tuple is used as the argument, and a strong reference to the second
element is held for the duration of the callee that receives the converted
argument.

Inout Address Conversion
------------------------

To pass an ``inout`` as a pointer argument, we need to be able to lock an
address for the ``inout`` for the duration of the call, which is not normally
possible. This functionality only needs to be available to the standard library,
so can be expressed in terms of builtins. A type can conform to the
``_BuiltinInOutAddressConvertible`` protocol to be convertible from an
inout reference. The protocol is defined as follows::

  protocol _BuiltinInOutAddressConvertible {
    /// The type from which inout conversions are allowed to the conforming
    /// type.
    typealias InOutType

    /// Create a value of the conforming type using the address of an inout
    /// argument.
    class func _convertFromInOutAddress(_ p: Builtin.RawPointer) -> Self
  }

An example of a conformance for ``CMutablePointer``::

  struct CMutablePointer<T>: _BuiltinInOutAddressConvertible {
    let ptr: Builtin.RawPointer

    typealias InOutType = T

    @_transparent
    static func _convertFromInOutAddress(_ p: Builtin.RawPointer)
    -> CMutablePointer {
      return CMutablePointer(p)
    }
  }

  func foo(_ p: CMutablePointer<Int>) { }

  var i = 0
  foo(&i)

The lifetime of the variable, stored property owning object, or writeback
buffer backing the inout is guaranteed for the lifetime of the callee that
receives the converted parameter, as if the callee had received the inout
parameter directly.

Inout Writeback Conversion
--------------------------

Inout address conversion alone is not enough for ``ObjCInOut`` to work as
intended, because the change to the ``__autoreleasing`` convention for the
pointed-to object reference requires a writeback temporary. The
``_BuiltinInOutWritebackConvertible`` protocol allows for an additional
writeback to be introduced before and after the address of the ``inout`` is
taken::

  protocol _BuiltinInOutWritebackConvertible {
    /// The original type from which inout conversions are allowed to the
    /// conforming type.
    typealias InOutType

    /// The type of the temporary writeback whose address is used to construct
    /// the converted value.
    typealias WritebackType

    /// Get the initial value the writeback temporary should have on entry to
    /// the call.
    class func _createWriteback(inout InOutType) -> WritebackType

    /// Create a value of the conforming type using the address of the writeback
    /// temporary.
    class func _convertFromWritebackAddress(_ p: Builtin.RawPointer) -> Self

    /// Write the writeback temporary back to the original value.
    class func _commitWriteback(inout InOutType, WritebackType)
  }

An example of a conformance for ``ObjCInOut``::

  struct ObjCInOut<T: class>: _BuiltinInOutWritebackConvertible {
    let ptr: Builtin.RawPointer

    typealias InOutType = T!
    typealias WritebackType = Builtin.RawPointer

    @_transparent
    static func _createWriteback(ref: inout T!)
    -> Builtin.RawPointer {
      // The initial object reference is passed into the callee effectively
      // __unsafe_unretained, so pass it as a RawPointer.
      return unsafeBitCast(ref, Builtin.RawPointer.self)
    }

    @_transparent
    static func _commitWriteback(ref: inout T!,
                                 value: Builtin.RawPointer) {
      // The reference is autoreleased on return from the caller, so retain it
      // by loading it back as a T?.
      ref = unsafeBitCast(value, T!.self)
    }

    @_transparent
    static func _convertFromWritebackAddress(_ value: Builtin.RawPointer) {
      return ObjCInOut(value)
    }
  }

The lifetime of the writeback is guaranteed for the lifetime of the callee that
receives the converted parameter, as if the callee had received the writeback
temporary as a mutable logical property of the original inout parameter.