Skip to content

Commit 40115fa

Browse files
xymusstephentyrone
andauthored
[Pitch] C compatible functions and enums with @c (#2813)
* [Pitch] Base proposal to formalize @cdecl * [Pitch] Update @cdecl proposal after pitch and implementation * Mark the @cdecl proposal as ready for review * cdecl: apply early LSG comments * Update and rename NNNN-cdecl.md to 0495-cdecl.md Assign SE-0495 --------- Co-authored-by: Stephen Canon <[email protected]>
1 parent 7daeeb0 commit 40115fa

File tree

1 file changed

+228
-0
lines changed

1 file changed

+228
-0
lines changed

proposals/0495-cdecl.md

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
# C compatible functions and enums
2+
3+
* Proposal: [SE-0495](0495-cdecl.md)
4+
* Author: [Alexis Laferrière](https://github.com/xymus)
5+
* Review Manager: [Steve Canon](https://github.com/stephentyrone)
6+
* Status: **Ready for Review**
7+
* Implementation: On `main` with the experimental feature flags `CDecl` for `@c`, and `CImplementation` for `@c @implementation`. With the exception of the `@objc` support for global functions which is available under the name `@_cdecl`.
8+
* Review: ([pitch](https://forums.swift.org/t/pitch-formalize-cdecl/79557))
9+
10+
## Introduction
11+
12+
Implementing a C function in Swift eases integration of Swift and C code. This proposal introduces `@c` to mark Swift functions as callable from C, and enums as representable in C. It provides the same behavior under the `@objc` attribute for Objective-C compatible global functions.
13+
14+
To expose the function to C clients, this proposal adds a new C block to the compatibility header where `@c` functions are printed. As an alternative, this proposal extends `@implementation` support to global functions, allowing users to declare the function in a hand-written C header.
15+
16+
> Note: This proposal aims to formalize and extend the long experimental `@_cdecl`. While experimental this attribute has been widely in use so we will refer to it as needed for clarity in this document.
17+
18+
## Motivation
19+
20+
Swift already offers some integration with C, notably it can import declarations from C headers and call C functions. Swift also already offers a wide integration with Objective-C: import headers, call methods, print the compatibility header, and implement Objective-C classes in Swift with `@implementation`. These language features have proven to be useful for integration with Objective-C. Offering a similar language support for C will further ease integrating Swift and C, and encourage incremental adoption of Swift in existing C code bases.
21+
22+
Offering a C compatibility type-checking ensures `@c` functions only reference types representable in C. This type-checking helps cross-platform development as one can define a `@c` while working from an Objective-C compatible environment and still see the restrictions from a C only environment.
23+
24+
Printing the C representation of `@c` functions in a C header will enable a mixed-source software to easily call the functions from C code. The current generated header is limited to Objective-C and C++ content. Adding a section for C compatible clients will extend its usefulness to this language.
25+
26+
Extending `@implementation` to support global C functions will provide support to developers through type-checking by ensuring the C declaration matches the corresponding definition in Swift.
27+
28+
## Proposed solution
29+
30+
We propose to introduce the new `@c` attribute for global functions and enums, extend `@objc` for global functions, and support `@c @implementation`.
31+
32+
### `@c` global functions
33+
34+
Introduce the `@c` attribute to mark a global function as a C function implemented in Swift. That function uses the C calling convention and its signature can only reference types representable in C. Its body is implemented in Swift as usual. The signature of that function is printed in the compatibility header using C corresponding types, allowing C source code to import the compatibility header and call the function.
35+
36+
A `@c` function is declared with an optional C function name, by default the Swift base name is used as C name:
37+
```swift
38+
@c func foo() {}
39+
40+
@c(mirrorCName)
41+
func mirror(value: CInt) -> CInt { return value }
42+
```
43+
44+
### `@objc` global functions
45+
46+
Extends the `@objc` attribute to be accepted on a global function. It offers the same behavior as `@c` while allowing the signature to reference types representable in Objective-C. The signature of a `@objc` function is printed in the compatibility header using corresponding Objective-C types.
47+
48+
A `@objc` function is declared with an optional C compatible name without parameter labels:
49+
50+
```swift
51+
@objc func bar() {}
52+
53+
@objc(mirrorObjCName)
54+
func objectMirror(value: NSObject) -> NSObject { return value }
55+
```
56+
57+
> Note: The attribute `@objc` can be used on a global function to replace `@_cdecl` as it preserves the behavior of the unofficial attribute.
58+
59+
### `@c` enums
60+
61+
Accept `@c` on enums to mark them as C compatible. These enums can be referenced from `@c` or `@objc` functions. They are printed in the compatibility header as a C enum or a similar type.
62+
63+
A `@c` enum may declare a custom C name, and must declare an integer raw type compatible with C:
64+
65+
```swift
66+
@c
67+
enum CEnum: CInt {
68+
case a
69+
case b
70+
}
71+
```
72+
73+
The attribute `@objc` is already accepted on enums. These enums qualify as an Objective-C representable type and are usable from `@objc` global function signatures but not from `@c` functions.
74+
75+
### `@c @implementation` global functions
76+
77+
Extend support for the `@implementation` attribute, introduced in [SE-0436](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0436-objc-implementation.md), to global functions marked with either `@c` or `@objc`. These functions are declared in an imported C or Objective-C header, while the Swift function provides their implementation. Type-checking ensures the declaration matches the implementation signature in Swift. Functions marked `@implementation` are not printed in the compatibility header.
78+
79+
The declaration and implementation are distinct across languages and must have matching names and types:
80+
81+
```c
82+
// C header
83+
int cImplMirror(int value);
84+
```
85+
86+
```swift
87+
// Swift sources
88+
@c @implementation
89+
func cImplMirror(_ value: CInt) -> CInt { return value }
90+
```
91+
92+
## Detailed design
93+
94+
This proposal extends the language syntax, type-checking for both global functions and enums, supporting logic for `@implementation`, and the content printed to the compatibility header.
95+
96+
### Syntax
97+
98+
Required syntax changes involve one new attribute and the reuse of two existing attributes.
99+
100+
* Introduce the attribute `@c` accepted on global functions and enums. It accepts one optional parameter specifying the corresponding C name of the declaration. The C name defaults to the Swift base identifier of the declaration, it doesn't consider parameter names.
101+
102+
* Extend `@objc` to be accepted on global functions, using the optional parameter to define the C function name instead of the Objective-C symbol. Again here, the C function name defaults to the base identifier of the Swift function.
103+
104+
* Extend `@implementation` to be accepted on global functions marked with either `@c` or `@objc`.
105+
106+
### Type-checking of global functions signatures
107+
108+
Global functions marked with `@c` or `@objc` need type-checking to ensure types used in their signature are representable in the target language.
109+
110+
The following types are accepted in the signature of `@c` global functions:
111+
112+
- Primitive types defined in the standard library: Int, UInt, Int8, Float, Double, Bool, etc.
113+
- Pointers defined in the standard library: OpaquePointer and the variants of Unsafe{Mutable}{Raw}Pointer.
114+
- C primitive types defined in the standard library: CChar, CInt, CUnsignedInt, CLong, CLongLong, etc.
115+
- Function references using the C calling convention, marked with `@convention(c)`.
116+
- SIMD types where the scalar is representable in C.
117+
- Enums marked with `@c`.
118+
- Imported C types.
119+
120+
In addition to the types above, the following types should be accepted in the signature of `@objc` global functions:
121+
122+
- `@objc` classes, enums and protocols.
123+
- Imported Objective-C types.
124+
125+
For both `@c` and `@objc` global functions, type-checking should reject:
126+
127+
- Optional non-pointer types.
128+
- Non-`@objc` classes.
129+
- Swift structs.
130+
- Non-`@c` enums.
131+
- Protocol existentials.
132+
133+
### Type-checking of `@c` enums
134+
135+
For `@c` enums to be representable in C, type-checking should ensure the raw type is defined to an integer value that is itself representable in C. This is the same check as already applied to `@objc` enums.
136+
137+
### `@c @implementation` and `@objc @implementation`
138+
139+
A global function marked with `@c @implementation` or `@objc @implementation` needs to be associated with the corresponding declaration from imported headers. The compiler should report uses without a corresponding C declaration or inconsistencies in the match.
140+
141+
### Compatibility header printing
142+
143+
The compiler should print a single compatibility header for all languages, adding a block specific to C as is currently done for Objective-C and C++. Printing the header is requested using the preexisting compiler flags: `-emit-objc-header`, `-emit-objc-header-path` or `-emit-clang-header-path`.
144+
145+
This C block declares the `@c` global functions and enums using C types, while `@objc` functions are printed in the Objective-C block with Objective-C types.
146+
147+
The C block should be printed in a way it's parseable by compilers targeting C, Objective-C and C++. To do so, ensure that only C types are printed, there is no unprotected use of non-standard C features, and the syntax is C compatible.
148+
149+
### Type mapping to C
150+
151+
When printing `@c` functions in the compatibility header, Swift types are mapped to their corresponding C representations. Here is a partial mapping:
152+
153+
- Swift `Bool` maps to C `bool` from `stdbool.h`.
154+
- Swift `Int` maps to `ptrdiff_t`, `UInt` to `size_t`, `Int8` to `int8_t`, `UInt32` to `uint32_t`, etc.
155+
- Swift floating-point types `Float` and `Double` map to C `float` and `double` respectively.
156+
- Swift's version of C primitive types map to their C equivalents: `CInt` to `int`, `CLong` to long, etc.
157+
- Swift SIMD types map to vector types printed as needed in the compatibility header.
158+
- Function references with `@convention(c)` map to C function pointers.
159+
160+
## Source compatibility
161+
162+
This proposal preserves all source compatibility as the new features are opt-in.
163+
164+
Existing adopters of `@_cdecl` can replace the attribute with `@objc` to preserve the same behavior. Alternatively, they can update it to `@c` to get the more restrictive C compatibility check. Using `@c` will however change how the corresponding C function is printed in the compatibility header so it may be necessary to update sources calling into the function.
165+
166+
## ABI compatibility
167+
168+
Marking a global function with `@c` or `@objc` makes it use the C calling convention. Adding or removing these attributes on a function is an ABI breaking change. Updating existing `@_cdecl` to `@objc` or `@c` is ABI stable.
169+
170+
Adding or removing the `@c` attribute on an enum is ABI stable, but changing its raw type is not.
171+
172+
Moving the implementation of an existing C function to Swift using `@c` or `@objc @implementation` within the same binary is ABI stable.
173+
174+
## Implications on adoption
175+
176+
The changes proposed here are backwards compatible with older runtimes.
177+
178+
## Future directions
179+
180+
This work opens the door to closer interoperability with the C language.
181+
182+
### `@c` struct support
183+
184+
A valuable addition would be supporting C compatible structs declared in Swift. We could consider exposing them to C as opaque data, or produce structs with a memory layout representable in C. Both have different use cases and advantages:
185+
186+
* Using an opaque data representation would hide Swift details from C. Hiding these details allows the Swift struct to reference any Swift types and language features, without the concern of finding an equivalent C representation. This approach should be enough for the standard references to user data in C APIs.
187+
188+
* Producing a Swift struct with a C memory layout would give the C code direct access to the data. This struct could be printed in the compatibility header as a normal C struct. This approach would need to be more restrictive on the Swift types and features used in the struct, starting with accepting only C representable types.
189+
190+
### Custom calling conventions
191+
192+
Defining a custom calling convention on a function may be a requirement by some API for callback functions and such.
193+
194+
With this proposal, it should be possible to declare a custom calling convention by using `@c @implementation`. This allows to apply any existing C attribute on the definition in the C header.
195+
196+
We could allow specifying the C calling conventions from Swift code with further work. Either by extending `@convention` to be accepted on `@c` and `@objc` global functions, and have it accept a wider set of conventions. Or by adding an optional named parameter to the `@c` attribute in the style of `@c(customCName, convention: stdcall)`.
197+
198+
## Alternatives considered
199+
200+
### `@c` attribute name
201+
202+
This proposal uses the `@c` attribute on functions to identify them as C functions implemented in Swift and on enums to identify them as C compatible. This concise attribute clearly references interoperability with the C language. Plus, having an attribute specific to this feature aligns with `@objc` which is already used on some functions, enums, and for `@objc @implementation`.
203+
204+
We considered some alternatives:
205+
206+
- An official `@cdecl` may be more practical for discoverability but the terms *cdecl* and *decl* are compiler implementation details we do not wish to surface in the language.
207+
208+
- An official `@expose(c)`, formalizing the experimental `@_expose(Cxx)`, would align the global function use case with what has been suggested for the C++ interop. However, sharing an attribute for the features described here may add complexity to both compiler implementation and user understanding of the language.
209+
210+
While `@_expose(Cxx)` supports enums, it doesn't have the same requirement as `@objc` and `@c` for the raw type. The generated representation in the compatibility header for the enums differs too. The attribute `@_expose(Cxx)` also supports structs, while we consider supporting `@c` structs in the future, we have yet to pick the best approach so it would likely differ from the C++ one.
211+
212+
Although sharing an attribute avoids adding a new one to the language, it also implies a similar behavior between the language interops. However, these behaviors already diverge and we may want to have each feature evolve differently in the future.
213+
214+
### `@objc` attribute on global functions
215+
216+
We use the `@objc` attribute on global functions to identify them as C functions implemented in Swift that are callable from Objective-C. This was more of a natural choice as `@objc` is already widely used for interoperability with Objective-C.
217+
218+
We considered using instead `@c @objc` to make it more explicit that the behavior is similar to `@c`, and extending it to Objective-C is additive. We went against this option as it doesn't add much useful information besides being closer to the compiler implementation.
219+
220+
### Compatibility header
221+
222+
We decided to extend the existing compatibility header instead of introducing a new one specific to C compatibility. This allows content printed for Objective-C to reference C types printed earlier in the same header. Plus this follows the current behavior of the C++ interop which prints its own block in the same compatibility header.
223+
224+
Since we use the same compatibility header, we also use the same compiler flags to request it being emitted. We considered adding a C specific flag as the main one, `-emit-objc-header`, is Objective-C specific. In practice build systems tend to use the `-path` variant, in that case we already have `-emit-clang-header-path` that applies well to the C language. We could add a `-emit-clang-header` flag but the practical use of such a flag would be limited.
225+
226+
## Acknowledgements
227+
228+
A special thank you goes to Becca Royal-Gordon, Joe Groff and many others for the past work on `@_cdecl` on which this proposal is built.

0 commit comments

Comments
 (0)