Support alternative calling conventions in Swift

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

In particular, using “stdcall” for a non-default (in Swift) convention used on only one platform seems questionable. “Windows.stdcall” maybe.

1 Like

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!

@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.

Does anyone actually use stdcall on non-Windows outside of EFI?

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

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

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.

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?

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.

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.

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

Windows uses a combination of stdcall, fastcall, thiscall, and vectorcall, so how do you disambiguate which one it is?

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.

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.

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.

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

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