C++ Interop

I've been looking into c++ interop. It is mostly just straightforward bug fixes right now as I'm following the plan laid out by @Douglas_Gregor and @John_McCall here C++ / Objective-C++ Interop but I foresee that there will be some design questions related to this space that will need discussion.

I have some meta-questions about proceeding with work on this feature:

  • Are there any similar features that I could draw inspiration on when engaging in design discussions?
  • How should I proceed towards writing a manifesto?
  • Should the individual features be discussed independently, or as part of one long design discussion?

Here is an example of the early stage design questions that will need discussion:

  • c++ types imported as swift type objects contain c++ namespaces as their decl context; how should I mangle the namespace into this generated __C.TypeName? These are accessible via using statements that import into the global namespace.

Some slightly later design questions:

  • Should names in namespaces be exposed as `namespace_name::name` or import a namespace object that you can then member lookup.
9 Likes

Hi Parker,

As you know, I'm super thrilled that you're pushing on this. I'd start with the general plan outlined by Doug in the other email: get the existing feature set to work in C++ mode, then start turning on the uncontroversial things (e.g. operator overloads in C++, methods on POD types, etc). At the same time, we can open specific design discussions on non-obvious things: e.g. how are namespaces imported (single member enums? ignored? mangled into the type names? something else?)

The thing to shoot for is to make sure the importer ignores anything that cannot be imported uncontroversially: it is perfectly fine to drop them on the floor, since they wouldn't have come in in c mode anyway.

I'm super thrilled to see this progressing!

-Chris

5 Likes

Thanks for pushing this forward @pschuh! I've been playing with this idea for the last few days without knowing there's anyone else driving it at this time.

My initial plan was to start with simple codegen tools that would be able to use ClangSwift to scan C++ declarations and generate C wrappers around that. These C wrappers could be imported into Swift code as proper types thanks to the implemented "Import as Member" proposal, although this would only work for simple classes without destructors. For destructors, this would probably need another class wrapper on Swift side that would call the destructor function from deinit. This would still not allow using C++ templates as generics, but it could be a start.

As far as I know, this is similar to what rust-bindgen is doing, although it's a bit easier for them thanks to availability of macros in the language.

Obviously, proper support in the Swift compiler itself would be great, but I imagine would require more expertise in the compiler itself. IIUC this would require extending the existing ClangImporter?

Another direction this could go is preparing a proposal for C and C++ calling convention attributes on Swift declarations (only non-generic Swift function to start with?). This would probably require such functions to be mangled by C++ rules when exposed to C++. I'd expect this to work similarly to the existing @objc and the unofficially supported @_cdecl attribute. Maybe we could have a single @ffi (name for bikeshedding) attribute that could be reused for interop with other languages, not just C and C++?

I'm really excited about the possibility of interfacing with C++ from Swift, as this would unlock building more powerful tools that interact with the Swift compiler directly. We have SwiftSyntax, which uses a mostly manually written C wrapper, but I can't wait to be able to use other compiler modules like AST and Sema directly for Swift. Who knows, maybe when that's all working, we could consider a possibility of a bootstrapped Swift compiler? :crossed_fingers:

Hi Max,
These all sound like interesting future directions. I have some local patches that convince me that it is relatively straightforward to support the basic importing in ClangImporter. This lets you write your "C wrapper" inline into the c++ header file using the c++ types directly in the signature of the wrapper. Things c++ does not understand are just ignored. Then, over time, the verbosity of this wrapper can be reduced. Most of the benefit will come from the first couple features. eg: not having to wrap basic methods.

Another example of a thing to discuss:
This is a general c-interop thing, implicit conversion for const char* happens through _convertConstStringToUTF8PointerArgument which gets emitted from StringToPointerExpr in silgen. This in turn produces a mark_dependence between the original string and the resulting const char*.

In the c world, if I wrap a c++ class in a c type as a pointer, and then wrap that pointer in a swift class that destroys the c++ class on deinit {}, if you convert that class back into a pointer via an computed property, and then pass it to a c-function, there is no guarantee that the class will stick around for the duration of the function call. Perhaps a @dependent attribute on a computed property that would enable providing this safety for "pointer-like" types in interop definitions (this would only hold true for the body of the returned function). C++ solves this with sequence points.

Another related project I've just discovered: cbindgen

cbindgen creates C/C++11 headers for Rust libraries which expose a public C API.
...
C++ headers are nice because we can use operator overloads, constructors, enum classes, and templates to make the API more ergonomic and Rust-like. C headers are nice because you can be more confident that whoever you're interoperating with can handle them. With cbindgen you don't need to choose! You can just tell it to emit both from the same Rust library.