compnerd
(Saleem Abdulrasool)
1
Hello Swift developers,
I was wondering if it would be possible (read: patches welcome) to have support for alternative calling conventions on functions. Even better would be the ability to synthesize calling convention thunks for blocks. In particular, this would make writing UI applications on Windows (wait, what? ... yes, I do have Windows UI code working at this point) which require that the windows message dispatch callback be implemented with the stdcall calling convention. Are there better ways to do this without having to resort to a C function to thunk the parameters back to swift and back from Swift into C just to adjust the calling convention. It would be convenient to have the ability to do something like:
@convention(stdcall)
func WndProc(hWnd: HWND, uMsg: UINT, wParam: WPARAM, lParam: LPARAM) -> LRESULT {
switch uMsg {
case WM_DESTROY:
PostQuitMessage(0)
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam)
}
}
...
SetWindowLongPtrW(window.handle, GWLP_WNDPROC, WndProc)
...
This would allow fully implementing the application lifecycle in Swift.
CC: @John_McCall @Douglas_Gregor @Joe_Groff
13 Likes
I think this sort of thing was anticipated in the @convention design, yes.
I would like us to think at least a little about convention names instead of necessarily just adding everything under the inconsistent set of names used by C frontends.
5 Likes
scanon
(Steve Canon)
3
In particular, using “stdcall” for a non-default (in Swift) convention used on only one platform seems questionable. “Windows.stdcall” maybe.
1 Like
compnerd
(Saleem Abdulrasool)
4
Hmm, I do see your point about the naming being haphazard in the C frontends. However, there is something to be said for consistency with the naming as well.
While this is something that requires some knowledge about what is happening in terms of code generation, it is much more discoverable to have consistent names for the calling convention across the frontends, especially since this is something which facilitates interoperability between them.
I would agree that exposing all the calling conventions isn't the best thing to do either - e.g. PreserveAll and PreserveMost can probably stay out of the fray. But, I think that stdcall, fastcall, thiscall, vectorcall should be provided without something else adjusting that. While these are mostly useful for Windows, note that they can be used on any x86 target (__attribute__((__fastcall__)) and __attribute__((__stdcall__)) is available on all targets in GCC/clang IIRC). I don't think that an alternative name for these would really improve anything but would hinder development instead.
At least we don't have to deal with something like Watcom's #pragma aux which allows you to specify the calling convention per symbol!
compnerd
(Saleem Abdulrasool)
5
@scanon - yes, but that makes it weird when you actually have code on a non-Windows target that uses the stdcall or fastcall calling convention.
scanon
(Steve Canon)
6
Does anyone actually use stdcall on non-Windows outside of EFI?
compnerd
(Saleem Abdulrasool)
7
TBH, I don't have a very good way to give any concrete answer to that. A trivial search on GitHub seems to hit ~2M instances (in C/C++). But, I could see that UEFI and Windows are probably going to be the biggest consumers of this.
It's not actually a good calling convention; it's basically cdecl but callee-pop, which is pretty much strictly negative unless you care about tail calls, which C doesn't guarantee (and generally makes difficult). It's strictly useful for interoperation with existing code that requires stdcall functions for whatever reason.
1 Like
Joe_Groff
(Joe Groff)
9
There are a bunch of frequently-used synonym macros in windows.h too, like WINAPI. Using one of those names might give us a name that's still recognizable to Windows developers without sounding as "standard" as stdcall.
Do any Windows APIs use fastcall/thiscall/vectorcall? stdcall is by far the most important one, and IIRC, they're all no-ops on Win64, right?
3 Likes
scanon
(Steve Canon)
10
vectorcall is not a no-op on 64-bit (it uses up to 6 xmm or ymm regs instead of up to 4 xmm regs, IIRC), but I don't believe that any Windows API uses it.
Agreed that WINAPI or similar might be a good solution for naming.
compnerd
(Saleem Abdulrasool)
11
thiscall is used for C++, vectorcall is used as @scanon mentioned. I believe that fastcall is used in a few places. True, there are a bunch of synonyms. What about CALLBACK which is pretty commonly used to indicate stdcall?
Joe_Groff
(Joe Groff)
12
thiscall is only used by C++, though, right? I don't recall it ever used as a function pointer type. CALLBACK is also an extremely generic name.
compnerd
(Saleem Abdulrasool)
13
Yes, thiscall is for C++, but it can be used outside of that context and it is used in a couple of obscure corners as an equivalent to -mregparm=1.
michelf
(Michel Fortin)
14
I'll just point out that if we add calling conventions that are platform-specific, the next thing to be desired will be to a way to parametrize the calling convention depending on the platform.
In D they solved this with a linkage attribute named extern(System) meaning either extern(Windows) for stdcall on Windows or extern(C) everywhere else.
2 Likes
compnerd
(Saleem Abdulrasool)
15
Windows uses a combination of stdcall, fastcall, thiscall, and vectorcall, so how do you disambiguate which one it is?
Joe_Groff
(Joe Groff)
16
Well, you could use a conditional typealias if nothing else in Swift:
#if os(Windows)
typealias Callback = @convention(stdcall) (...) -> ...
#else
typealias Callback = @convention(c) (...) -> ...
#endif
since declarations in Swift adapt to the calling convention they're used as and don't normally need to be assigned specific conventions.
compnerd
(Saleem Abdulrasool)
17
Hmm, does the ... get translated by the compiler or do you need to fill them in? If its the latter, it is going to be horrendous as there are thousands of different callbacks.
michelf
(Michel Fortin)
18
In D there's no fastcall or vectorcall (that I know of), so I guess you're stuck if you need it. Presumably thiscall is supported by extern(Windows) delegates and member functions (a "delegate" being a function pointer paired with a this pointer, used for closures) but I'm not too sure as I never used D on Windows.
I was not necessarily promoting this as a solution for Swift, just pointing out some prior art. Another idea would be to simply enumerate the conventions by platform:
@convention(c, windows: stdcall)
which would mean "c" everywhere except "stdcall" on Windows. This syntax also nicely alleviate the concern about "stdcall" appearing to be a cross-platform thing. You could have:
@convention(windows: stdcall)
and it'd mean "swift" everywhere except "stdcall" on Windows. Trying to apply stdcall everywhere would be an error.
Joe_Groff
(Joe Groff)
19
I suppose you could use generic typealiases here. Until we get variadic generics, this would alas mean a different typealias for each function arity:
#if os(Windows)
typealias Callback1<A, R> = @convention(stdcall) (A) -> R
typealias Callback2<A1, A2, R> = @convention(stdcall) (A1, A2) -> R
...etc to some reasonable limit...
#else
typealias Callback1<A, R> = @convention(c) (A) -> R
typealias Callback2<A1, A2, R> = @convention(c) (A1, A2) -> R
#endif
I don't recall ever needing this personally in my own cross-platform development work, but that could be a way of achieving it.
1 Like
John_McCall
(John McCall)
20
Yeah, I really can't imagine what the point of extra target-conditionality for @convention would be. Presumably we'd be using @convention to match the signature of a specific function pointer used in the Windows API; what are the odds that there would be another function pointer used in, say, POSIX that uses a structurally-identical function type except for the calling convention?
I mean, in the long run we should allow #if in attribute lists as a general matter, but in the short term I don't think this creates a specific need for it.
3 Likes