Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

lambda calling-conventions

262 views
Skip to first unread message

Bonita Montero

unread,
Feb 26, 2021, 9:17:21 AM2/26/21
to
Lambdas with no captures can be handled like ordinary functions and you
can get the address of the lambda as a normal function-pointer. AFAIK
there's no compiler which allows to specify a callig-convention on a
lambda. But with MSVC you can convert a lamda to a function-pointer of
any type, i.e. you can convert it to __cdecl, __fastcall or whatever
(they exist for x86 only, x64 has a consistent calling convention for
all calls). What MSVC does here is that it compiles the lambda multiple
times for each calling convention on demand.

Look here:

#include <iostream>
#include <string>
#include <iomanip>
#include <cstdint>

using namespace std;

void fastCall( void (__fastcall *pf)( string ) )
{
string str = "hello world";
cout << p << endl;
pf( str );
}

void cdeclCall( void (__cdecl *pf)( string ) )
{
string str = "hello world";
cout << p << endl;
pf( str );
}

int main()
{
auto lambda = []( string str )
{
cout << str << endl;
};
fastCall( lambda );
cdeclCall( lambda );
}

As you can see the addresses pf vary. You might think that the compiler
generates proxy-code and the string is copy-constructed with each proxy.
But I disassembled the lambda and found that the lambda is compiled
completely multiple times.

So are there any other compilers which have different calling conven-
tions and which do the same ?

David Brown

unread,
Feb 26, 2021, 11:08:32 AM2/26/21
to
On 26/02/2021 15:17, Bonita Montero wrote:
> Lambdas with no captures can be handled like ordinary functions and you
> can get the address of the lambda as a normal function-pointer. AFAIK
> there's no compiler which allows to specify a callig-convention on a
> lambda. But with MSVC you can convert a lamda to a function-pointer of
> any type, i.e. you can convert it to __cdecl, __fastcall or whatever
> (they exist for x86 only, x64 has a consistent calling convention for
> all calls). What MSVC does here is that it compiles the lambda multiple
> times for each calling convention on demand.
>

Calling conventions - especially the various ones supported by MSVC -
are very much implementation specific. On most platforms you have a
single ABI that all compilers adhere to, but not on 32-bit Windows. So
how different calling conventions are handled on Win32 will be entirely
up to the compiler.

Lambdas have internal linkage and are usually not "exported" in any way,
so compilers are free to use whatever calling conventions they want -
there is no need for any consistency, especially if the lambda is a leaf
function.

When you convert a lambda to a pointer-to-function, you are actually
using a conversion function template which is free to give different
results for different pointer types. (It can even give different
results for the same pointer types, although that would be less likely
in a real implementation.)

See the section on "ClosureType::operator ret(*)(params)()" :

<https://en.cppreference.com/w/cpp/language/lambda>


Of curiosity, what happens if you replace the lambda with a static function:

static auto lambda(string str) { cout << str << endl; }

?


Bonita Montero

unread,
Feb 26, 2021, 11:21:57 AM2/26/21
to
> Calling conventions - especially the various ones supported by MSVC -
> are very much implementation specific. On most platforms you have a
> single ABI that all compilers adhere to, ...

On Windows x64 there's no such thing as __cdecl, __fastcall, __stdcall
etc. anymore.

David Brown

unread,
Feb 26, 2021, 11:41:34 AM2/26/21
to
Well, MS didn't do nearly as badly for Win64 as they did for Win32
(where there are half a dozen conventions) or DOS (where there is so
little defined that it's a stretch to use the word "convention"). They
tried to have a single ABI - though in good old MS fashion, they saw
that everyone else had settled on a single consistent ABI and thus
decided they needed a completely different one instead. But there is
still the "normal" MS Win64 calling convention, and the "__vectorcall"
version.

However, I thought you were specifically talking about Win32 since you
were talking about __cdecl, __fastcall, etc., ?

Bonita Montero

unread,
Feb 26, 2021, 11:43:59 AM2/26/21
to
> Well, MS didn't do nearly as badly for Win64 as they did for Win32
> (where there are half a dozen conventions) or DOS (where there is so
> little defined that it's a stretch to use the word "convention"). They
> tried to have a single ABI - though in good old MS fashion, they saw
> that everyone else had settled on a single consistent ABI and thus
> decided they needed a completely different one instead. But there is
> still the "normal" MS Win64 calling convention, and the "__vectorcall"
> version.

With most other platforms there's usually a default calling convention
and mostly a cdecl calling convention which pushes everything on the
stack.

Bonita Montero

unread,
Feb 26, 2021, 11:48:38 AM2/26/21
to
Am 26.02.2021 um 15:17 schrieb Bonita Montero:
> Lambdas with no captures can be handled like ordinary functions and you
> can get the address of the lambda as a normal function-pointer. AFAIK
> there's no compiler which allows to specify a callig-convention on a
> lambda. But with MSVC you can convert a lamda to a function-pointer of
> any type, i.e. you can convert it to __cdecl, __fastcall or whatever
> (they exist for x86 only, x64 has a consistent calling convention for
> all calls). What MSVC does here is that it compiles the lambda multiple
> times for each calling convention on demand.
>
> Look here:
>
> #include <iostream>
> #include <string>
> #include <iomanip>
> #include <cstdint>
>
> using namespace std;
>
> void fastCall( void (__fastcall *pf)( string ) )
> {
>     string str = "hello world";
>     cout << p << endl;
pf, not p
>     pf( str );
> }
>
> void cdeclCall( void (__cdecl *pf)( string ) )
> {
>     string str = "hello world";
>     cout << p << endl;
pf, not p

Scott Lurndal

unread,
Feb 26, 2021, 12:26:41 PM2/26/21
to
Most platforms I've seen in the past two decades use calling conventions
that pass the first N parameters in registers, where N varies based
on the number of registers. E.g. x86_64 on unix/linux (N=6), Motorola 88100,
IA64, et alia.

Calling conventions also agree on how to handle varargs and how to pass
SIMD, vector (and for ARM, scalable vector) values.
0 new messages