Formalizing @cdecl

I have been using this annotation for a couple of years.

@_cdecl("LIBHelloWorld")
func libHelloWorld() {
  World.hello()
}

private class World {
  static func hello() {
    print("Hello World!")
  }
}

Passing parameters also works well.

Following this forum thread, it's not clear to me the status of this proposal, or if I can help out.
In my daily workflow, this code pattern is seen again and again.

I am worried that @_cdecl could be removed from the language without warning. Is that possible, or would someone reply here before doing that?

I mean, that’s what the underscore name means, right?

3 Likes

Yes, the underscore means the annotation is unofficial.

I will rephrase the question.

What would give me a heads up that @_cdecl is being removed from the language? I assume that subscribing to this thread is the best thing, but maybe there is something else.

Hi Joe, a couple of times in this thread you speak about "importing a header". Is there a way to do this that doesn't involve creating a clang modulemap which imports the header? Because that works, but it always feels like jumping through hoops – creating multiple files, targets, build rules, to expose just one function, whose body should be defined in Swift anyway.

We are experiencing sporadic crashes calling functions exposed to the JNI via @_cdecl and we're looking for a "more official" way of doing it.

If you were to expose a function to C today in a non-broken way, how would you do it?

  1. Create thing.h which declares (in C syntax) that MyFunction exists
  2. Create a module map that imports thing.h
  3. Somehow import that module (SwiftPM, Xcode Build Settings, etc.)
  4. In the Swift code, something something (???) – how do we declare the function in Swift, and if we do, what was the point of importing the header?

I don't mean to derail the conversation with this. I am genuinely interested in finding out whether a use-case that I still somehow consider essential to this thread's (pre-)proposal can be achieved otherwise with reasonable effort.

2 Likes

If you're defining the functions in Swift, then @_cdecl should be fine. If it's crashing, then there's either a bug in the compiler, or a calling convention mismatch between what Swift and LLVM are doing and what the JNI expects. You would use headers and modulemaps to declare external C functions to be imported into Swift.

3 Likes

Just to clarify my use-case, this annotation is great for writing Unity plugins for iOS.

The Unity iOS space is full of plugins written in ObjC. By using @_cdecl, we can write 100% Swift, no ObjC bridges required.

For example: Swift "Hello World" Unity plugin ¡ GitHub

4 Likes

Bumping this thread and wondering if any new decisions have been made on the matter, as well as the viability of continuing to use @_cdecl still without fear of it not being supported in a future release. And if so, how much of an impact would that be (other than refactoring the symbol to @cdecl or something similar)

4 Likes

The implementation is, to the best of my knowledge, functionally complete, so the thing we'd need to advance @_cdecl to an official feature is an evolution proposal. We generally keep existing underscored attribute spellings around for a while for source compatibility, and I don't foresee any massively breaking changes we'd make to an official feature that'd make maintaining the existing behavior for compatibility difficult. The list of issues from Jordan's OP still looks like a good list of topics to hash out in a pitch/review cycle.

8 Likes

Yeah, I’m afraid after starting the topic and getting a lot of good discussion I developed RSI and couldn’t continue spearheading it myself.

8 Likes

I am interested in working on pitching an attribute like @expose that was proposed in this thread. We would like to adopt it for Swift and C++ interop, to control which Swift APIs get exposed to C++ (APIs would have to be explicitly annotated with @expose). I think it could potentially act as a replacement for @_cdecl as well.

So far I have thought about the following behavior for this attribute:

  • @expose - tells the compiler that this declaration is exposed to a foreign language interface (C/C++/Objective-C++, in one unified header). Most likely when applied to something like a struct, it will expose all its public members too. Note that in this case the Swift declaration will still use the underlying Swift ABI.

  • @expose(c++, name) - tells the compiler that this declaration is exposed to a foreign language interface. In the generated C++ interface, this declaration will be renamed with the specified name.

  • @expose(c, name, convention: cdecl) - equivalent to today's @_cdecl(name) essentially.

I am most likely going to work on a formal evolution pitch and proposal for @expose sometime this year, but I'm interested in getting feedback before then as well.

5 Likes

A thought:
Why not expand the existing @convention type attribute to function declarations for this purpose?

To me, that's a different thing—@convention specifies a concrete representation for a specific function value, but function declarations in Swift don't have any particular convention by themselves; we generate entry points for them guided by how they're used. The attribute we're talking about here would be saying that we want to export a symbol with the C calling convention and a particular name based on a Swift function, but that wouldn't mean that that is the only way to call the function.

7 Likes

Coming in quite late, but… I would think the following would be unambiguous?

@convention(​c)
public func allocate(_ length: Int) -> UnsafeRawPointer { ... }

We also already have @objc[(name)], could we say @c[(name)] or is @c too concise?

What, if anything, would we want to do for C++? Surely we'd want consistency between Objective-C, C, and C++ here?

Doesn’t Swift also have the unique situation of exporting functions with C mangling but Swift calling convention?

@convention could work. I steered away from it cause the existing conventions “c” and “block” don’t match up with the “c” and “objc” and maybe “c++” or “cxx” we’re going to want, but you could argue that that’s because one is the convention for a function value (closure) and the other is the convention for a declaration (not necessarily a function). I think we’d want to put more arguments in there, like @available, but the syntactic space is available for that. (I personally still like @expose best of the options in this thread.)

(Separately, I would love to have @convention(thin) formalized, for a function with the Swift calling convention but no closure context. I don’t know what to name it though. New thread!)

@c is a bit short, and doesn’t follow the Zero-One-Many rule, i.e. if we want to export to more languages in the future we shouldn’t go through all of this again.

I mean, not normally, but there are a few parts of the stdlib that do this. I do think it’s worth supporting: “C mangling” really means “no mangling” or “custom symbol name”, which in turn means “can be looked up in a dylib”. I don’t think that has to be supported in the same feature as exporting the C convention to C, though.

(Formalizing @cdecl - #31 by jrose covers my thoughts on both of these to some extent.)

What if we had both -emit-c-header and -emit-objc-header that took the same code path, except the former would produce an error if you actually had @objc classes/protocols in your module?

(I realise I'm very late to this, FWIW).

I'd rather have something like @export(c). cdecl is a hangover from the 1980s (the Internet suggests it's Microsoft's, but actually I think it comes from Lattice C). Also, if we have @cdecl, Windows folk are going to want @stdcall et al (which I'd probably spell @export(pascal) because that's what it really means).

-emit-header=c,objc,c++,objc++ would let you specify exactly which language(s) you expected your header to work with. You could even go further (potentially) and specify which version, e.g. -emit-header=c11 or -emit-header=c++23.

5 Likes

Everyone reading the first post: there was a lot of discussion the first time around and there are better names in the middle of the thread!

4 Likes

In the context of C++ interoperability we don't intend to let the user control which flavor of the C/C++ language they want to expose in the generated header. We would like to generate a single header file that exposes declarations that are defined in the #if defined(__cplusplus) or the #if defined(__OBJC__) blocks, so that the user will be able to use the interface that's right for their translation unit. They will also always have access to the underlying C declarations if they would like to just use plain C and interoperate with Swift manually without proper bridging support that's provided through C++ functions and classes. Furthermore, we don't intend to allow them to control which C++ standard the header should generate. The header will choose to provide certain declarations or utilize certain C++ clauses (e.g. C++ 20 requires clauses) based on the C++ standard specified by the translation unit that's importing the header.

I would like to use something like -emit-clang-header and alias the old -emit-objc-header flag to it. I actually just recently implemented something like that in the frontend for C++ interoperability (although it aliases in the opposite direction), but it's not yet something that can be adopted by the Driver as it will need to get community approval first.

stdcall and pascal are different—Pascal convention pushes the arguments on the stack in FIFO order so they appear "backwards" in memory, opposite from C, and reserves stack space for the return value before the arguments. stdcall was introduced with Win32 and is basically a C convention but with callee-pop arguments: arguments are pushed in C order, and returns are by-register like in C. Pascal is probably irrelevant today (unless someone wants to keep the Mac OS 9 port going), but stdcall would be interesting for Win32 interop to this day. (Although even there it's contained to Win32—on Win64 the conventions have all been combined into one C-style convention).

2 Likes