Hello Swift community,
Over the past few weeks I have been looking into what it would take to bring cross-platform Objective-C interop using the GNUStep runtime to Swift.
Although the GNUStep community is small, there is interest in having cross-platform Swift Objective-C interop. Another developer and I would be willing to work on it, so I’d like to get some feedback on my findings and discuss potential blockers.
I have written down some of my notes on what I think it would take to port different ways the Swift and Apple’s objc4 runtime interact to libobjc2 (the GNUStep runtime). Ideally, our goal would be to get as close to existing interop as possible, though I understand if we have to make some concessions.
As a first step, I would like to focus on the “@implementation extension interop” from SE-0436, since it does not require the Objective-C runtime to handle Swift objects and will likely be easier to implement. I’ll refer to this as “extension-interop” (as opposed to “header-interop”, where the bridging header is compiler generated).
Swift-GNUStep Feasibility
1. Metadata
Swift’s IRGen would have to be able to emit libobjc2 compatible metadata for classes, methods, protocols, etc. We might be able to make use of Clang’s CodeGen here to emit these for us to reduce the burden on Swift.
As stated in the original SE-0436 proposal, with extension-interop all bridged types are pure Objective-C types, and therefore Swift does not need to emit Swift class metadata for interfaces which have been implemented via Swift extension. This makes many things easier, since the Objective-C runtime does not need to be aware of Swift objects. With header-interop it gets trickier:
Swift’s class metadata was designed to be compatible with objc4’s metadata structure. When interop is enabled, Swift classes are slightly larger so the first 5 pointer-sized fields match objc4’s layout and contain objc interop reserved fields. Since this is not the case on non-Apple platforms where interop is disabled, we would need to find a way to add objc metadata to Swift classes without an ABI break. I don’t know how to solve this problem. Would it be possible to add a pointer “Immediate Member” (Which would be non ABI and could be added only to bridged types), which could then point to an additional record that stores the libobjc2 metadata?
2. Type Bridging
As I understand, there are three different ways objects can be bridged. Verbatim: Reference types are just forwarded across the language boundary. Bridged: Swift types that conform to _ObjectiveCBridgeable define a conversion method, which the runtime detects and calls. Many Foundation types do this. Boxed: Some value types are wrapped in __SwiftValues to make them usable from Objective-C. This generally seems to be portable aside from missing SPIs.
Here, my major blocker is bridging. GNUStep has its own Foundation with a different ABI, therefore the Swift Foundation bridging won’t work. The simplest approach (And frankly the only approach I can think of at the moment) to solve this problem would be to not bridge GNUStep types at all, and require explicit conversions.
3. Object Model differences (objc4 / libobjc2)
Objc4 supports three different ISA modes: Raw pointer, tagged and indexed/Nonpointer. Libobjc2 only knows raw pointers, so we would need to disable nonpointer ISAs. For header-interop, libobjc2 will have to support the “Swift bits” that indicate a Swift type in the ISA pointer.
Both objc4 and libobjc2 support tagged object pointers. This is an optimization, which allows embedding small objects (such as small NSNumber or very short strings) into the object pointer. Initially, we should be able to just disable these features for now, but this is a notable example where Swift directly accesses objc4 internals during bridging of built-in types. Eventually we might want an optimized codepath in the Swift runtime that supports libobjc2 “small objects”.
4. Memory Management
Objc4 uses side tables to map objects to their reference counts / weak tables, while libobjc2 has a slightly larger heap object, that stores the refcount after the ISA pointer. During interop, Swift and objc4 forward retain / release calls to the other runtime respectively, if they detect a foreign object. If libobjc2 is to support Swift objects, this mechanism will need to be implemented there as well.
5. Missing SPIs
Generally, I think the preferred approach to handle missing SPIs is to implement them in libobjc2. Most notably we are missing:
-
objc_constructInstance/objc_destructInstanceshould be easy to shim -
_objc_empty_cache, this is just a constant for initializing the objc4 cache
Various SPIs related to runtime initialization (See section 6)
_objc_realizeClassFromSwift,_objc_setClassCopyFixupHandler,objc_readClassPair
6. Loading and Initialization
This is probably going to be tricky to get right. Swift’s metadata initialization is very complex and I don’t fully understand how it works and integrates with the Objective-C runtime yet.
There are a few SPIs missing that Swift uses to initialize the Objective-C side of a class, or to insert fixup callbacks. Most of the complexity seems to stem from the fact that Swift builds Metadata dependency graphs and may defer Metadata initialization using stub classes. Objc4 is aware of Swift’s model, and we would have to replicate the same behavior in libobjc2. Again, I believe most of these problems only affect header-interop, since the Objective-C runtime does not need Swift object awareness for extension-interop.
Conclusion
The question of GNUStep interop has been raised several times on the Swift Forums, but previous discussions have been inconclusive about whether there is enough community interest to justify the added complexity in Swift.
It seems to me that most of the effort in adding GNUStep interop would be confined to Swift’s IRGen and a few additional branches and ifdefs in the runtime, though I would need a prototype to say this with more confidence. I also want to clarify that I am not asking for the Swift maintainers to work on this, but rather I hope to answer the following questions:
-
Would you (the Swift maintainers) be willing to review and accept patches to implement GNUStep interop support?
-
If so, do you expect a feature-complete implementation before any upstreaming, or can we work on this gradually, for example by working on the extension-interop first?
-
Would be possible to add a pointer to the metadata of bridged classes on non-Apple platforms without an ABI break?
-
Do you have any additional concerns or ideas, for example regarding the type bridging problem I mentioned?
Thanks.
- Hendrik