Open litherum opened 1 year ago
This is blocking my PR at https://github.com/WebKit/WebKit/pull/13849
@litherum Can you explain a bit more why this is needed? I don't know how this Swift mechanism works, maybe you can point to some documentation. If you're marking everything in webgpu.h as NS_REFINED_FOR_SWIFT, why can't you just not expose webgpu.h through the Swift bindings at all? For example in some hypothetical C++ bindings (this is different from Dawn's C++ bindings):
This seems like a much more standard way to writing bindings over a C API, though to be fair only a few languages interoperate with C the way C++ and Objective-C/++ and I guess Swift do.
The docs are at https://developer.apple.com/documentation/swift/improving-objective-c-api-declarations-for-swift
The goal is:
By default, a framework's C API is automatically "bridged" into Swift. So, if we do nothing, WebGPU.framework will expose a Swift function that's something like (please forgive the errors, written off the top of my head):
wgpuBufferMapAsync(buffer: WGPUBuffer, mode: WGPUMapModeFlags, offset: Int, size: Int, callback: @C_ABI ((status: WGPUBufferMapAsyncStatus, userdata: UnsafePointer<Void>) -> Void), userdata: UnsafePointer<Void>);
See all that junk at the end? That's just there because the C API doesn't translate well into Swift. In Swift, you would never write that by hand; instead, you'd write something like:
wgpuBufferMapAsync(buffer: WGPUBuffer, mode: WGPUMapModeFlags, offset: Int, size: Int) async -> WGPUBufferMapAsyncStatus
This is more expressive than C is capable of being. So, the only way to expose this kind of symbol from the framework is to actually write a Swift class/method that exposes this function signature. (The Swift class would just call the C function internally.)
But, now you've exposed 2 symbols which do the same thing!!
From a C client's perspective, there is no problem, since they can't see the Swift API in the first place. So they only see the C API, and they can call it no problem.
But, from a Swift client's perspective, both functions would exist, since the framework's C API automatically gets bridged.
NS_REFINED_FOR_SWIFT
is the solution for this. Putting this on the C API tells the Swift importer "don't bridge this symbol." It doesn't have any effect on C clients; those still just call the C API like they always have. But, from a Swift client's perspective, if the C API is all marked as NS_REFINED_FOR_SWIFT
, then they only see the nicer Swift API. This is the standard way to do this.
(Aside: The auto-bridging of C frameworks into Swift is a good thing in the general case; this is so that pure-C frameworks are all immediately accessible from Swift code, without the author of the framework having to do anything. We're only hitting the problem here because the author of WebGPU.framework (us) are trying to do better, and expose a more custom API that's more Swifty. An alternative is to make a second framework, WebGPUSwift.framework, and have that link WebGPU.framework, but it's generally an anti-pattern to do this, specifically because NS_REFINED_FOR_SWIFT
exists.)
Note that Swift doesn't have headers, so we can't just tell clients "please include the appropriate header for your language." Instead, Swift is based on modules, where you import a module as a whole, rather than textual includes.
An alternative is to make a second framework, WebGPUSwift.framework, and have that link WebGPU.framework, but it's generally an anti-pattern to do this, specifically because
NS_REFINED_FOR_SWIFT
exists.
I disagree with the characterization as an anti-pattern in this case. This is not Apple software, it's an open source header. We can't have every new bindings layer requiring changes to the entire header, even if they're non-breaking. I absolutely see the reasoning you would want to expose both a C API and a Swift API from the same framework, while not exposing both at the same time, but I just don't think it's appropriate to impose Swift's peculiar, single-platform-centric design choices, at least on the public version of this header.
Unless I'm misunderstanding, all that's needed/asked for is an overridable attribute as seen in #182
Yes, but it's still a 450 line patch. I would have no problem with it if it were generally useful in some way (like nullability attributes or C block support) or necessary (like the function pointer/proc table stuff). But it's an awful lot of overhead for one language binding when the language binding could just solve the problem in a less intrusive way.
I disagree with the characterization as an anti-pattern in this case.
It's definitely an anti-pattern in the Swift world. I expect the reasoning for this is that, at the top of you Swift file, you want to have something like:
import Foo
import Bar
import Baz
and not
import Foo_Swift
import Bar_Swift
import Baz_Swift
it's an awful lot of overhead for one language binding
I dunno. I'm not a maintainer of this project, but it seems to me that if someone came along and said "adding some attribute allows for better interop with Rust" or "adding some attribute allows for better interop with C#" then those seem like totally legit use cases to me.
like nullability attributes or C block support
Would very much be interested in seeing these things added <3
it's an awful lot of overhead for one language binding
I dunno. I'm not a maintainer of this project, but it seems to me that if someone came along and said "adding some attribute allows for better interop with Rust" or "adding some attribute allows for better interop with C#" then those seem like totally legit use cases to me.
I just have a hard time seeing this actually happen. Rust and most other languages in the world just deal with the fact that C is C without adding stuff to it. Swift seems fairly unique in this regard.
Rereading the documentation you linked, I realized NS_REFINED_FOR_SWIFT
is an Objective-C thing. Of course, doing things in this header to make it work better in Objective-C would be fine, but you don't actually need to do that because Objective-C (like C++) (mostly) just deals with C being C.
I'm just very surprised if there isn't any better solution that you could apply in the case of an upstream project where you didn't have the opportunity to add these. Like, an option just say "here's a list of symbols, don't expose any of them to Swift"?
Anyway, I'm not the arbiter of what goes into webgpu.h. It's not really that big of a deal, we can add these if other maintainers are fine with it (seems @Kangz implicitly is).
You’re right that ideally there would be a way to make this modal. The way to make it modal would be to do this:
_Pragma("clang attribute push(__attribute__((swift_private)))")
...
_Pragma("clang attribute pop")
However, that doesn’t actually work, because the compiler reports:
Attribute 'swift_private' is not supported by '#pragma clang attribute'
I filed rdar://109555754 to request such a thing. Until then, the only way to do this appears to be to use Corentin’s patch.
OK, let's just go with it then. Thank you for looking into the alternatives, I appreciate it!
Thank you very much!
@litherum Does
typedef enum WGPUBar {
WGPUBar_Undefined = 0x00000000
} WGPUBar NS_REFINED_FOR_SWIFT;
Need to actually be
typedef enum WGPUBar {
WGPUBar_Undefined = 0x00000000
} NS_REFINED_FOR_SWIFT WGPUBar NS_REFINED_FOR_SWIFT;
to avoid exporting the type enum WGPUBar
in addition to WGPUBar
?
And similarly
typedef struct WGPUQux {
} NS_REFINED_FOR_SWIFT WGPUQux NS_REFINED_FOR_SWIFT;
And are the implicit forward declarations like struct WGPUAdapterImpl
in typedef struct WGPUAdapterImpl* WGPUAdapter WGPU_OBJECT_ATTRIBUTE;
OK since they're not definitions?
Frameworks can expose C symbols (of course). However, when projected into Swift, those C symbols are usually not very ergonomic. One example is asynchronous functions: In C, they take a function pointer and a
void* userdata
. If the same function was to be exposed in Swift, it should be set up in such a way that it can actually be marked asasync
and use Swift's built in facilities for asynchronous programming.To handle this, frameworks have a mechanism for saying "expose this symbol to C clients, but not to Swift clients" so that the functionality can be wrapped and exposed in a more ergonomic Swift wrapper. That mechanism looks like this:
However, the declarations in WebGPU.h are just:
There is no way for us to stick the
NS_REFINED_FOR_SWIFT
attribute on these functions/types.It would be useful if WebGPU.h was modified, to do something like:
That way, we could pass the necessary
-D
flag to our compiler to specify this swift refinement stuff for all the functions / types.