Thread overview
extern(C) and name mangling
Dec 15, 2020
Dave P.
Dec 16, 2020
Mike Parker
Dec 16, 2020
Dave P.
Dec 16, 2020
Mike Parker
Dec 16, 2020
Jacob Carlborg
Dec 16, 2020
Dave P.
Dec 16, 2020
Jacob Carlborg
Dec 16, 2020
Jacob Carlborg
Dec 16, 2020
Jacob Carlborg
Dec 16, 2020
Ali Çehreli
December 15, 2020
I can’t find this in the spec, but from experimentation it seems like extern(C) only affects name mangling of functions at the top level scope. Thus extern(C) function templates would be mangled differently, but still use the C calling convention. Is this right?  I want to pass some templated functions as function pointers to some C code and wanted to confirm that would work (and that different versions would be mangled differently).

Also, is there any way to say you want the C calling convention, but don’t want C name mangling for top level functions?
December 16, 2020
On Tuesday, 15 December 2020 at 22:04:12 UTC, Dave P. wrote:
> I can’t find this in the spec, but from experimentation it seems like extern(C) only affects name mangling of functions at the top level scope. Thus extern(C) function templates would be mangled differently, but still use the C calling convention. Is this right?  I want to pass some templated functions as function pointers to some C code and wanted to confirm that would work (and that different versions would be mangled differently).

Mangling does not play any role in passing and calling function pointers between D and C. It only plays a role in linking and loading. You can declare function pointers in D and C without any name whatsoever.

What matters is that both sides agree on the number and type of parameters accepted by the function that's pointed to, and that they both agree on the calling convention. I don't believe extern(C) has any effect on templated functions. However, the D calling convention is defined to be identical to the C calling convention on the host system for everything except Windows x86.

So theoretically, you should be able to pass a pointer to a templated free function to C as long as the types in the functions parameter list match those the C function expects. I don't know if I'd do that myself, though.

>
> Also, is there any way to say you want the C calling convention, but don’t want C name mangling for top level functions?

There's no such thing as C name mangling. C functions are *not* mangled (though some platforms do prepend an underscore to symbol names). What extern(C) does is to turn off D name mangling.

So what you're asking for is a way to retain the D name mangling on an extern C function. The way to do that is with `pragma(mangle, "new_name")`. To match the original D function mangling, declare the function first without extern(C) and print `func.mangleof`. Use that as the parameter to pragma(mangle).

I can't imagine any benefit you'd get from doing that, though.
December 15, 2020
On 12/15/20 2:04 PM, Dave P. wrote:

> I want to pass
> some templated functions as function pointers to some C code

As Mike Parker said, it works:

// The same thing as a C function pointer:
alias Func = long function(int);

long bar(T)(int) {
  return 0;
}

Func f0 = &(bar!float);
Func d1 = &(bar!double);

> (and that different versions would be mangled
> differently).

Yes:

auto foo(T)() {
}

void main() {
  pragma(msg, foo!int.mangleof);
  pragma(msg, foo!double.mangleof);
}

Output:

_D6deneme__T3fooTiZQhFNaNbNiNfZv
_D6deneme__T3fooTdZQhFNaNbNiNfZv

Ali

December 16, 2020
On Wednesday, 16 December 2020 at 04:17:13 UTC, Mike Parker wrote:
> On Tuesday, 15 December 2020 at 22:04:12 UTC, Dave P. wrote:
>> [...]
>
> Mangling does not play any role in passing and calling function pointers between D and C. It only plays a role in linking and loading. You can declare function pointers in D and C without any name whatsoever.
>
> What matters is that both sides agree on the number and type of parameters accepted by the function that's pointed to, and that they both agree on the calling convention. I don't believe extern(C) has any effect on templated functions. However, the D calling convention is defined to be identical to the C calling convention on the host system for everything except Windows x86.

Oh interesting, so I only need extern(C) for declaring symbols I’m linking to and
for symbols I want to export to C. I had sort of assumed that D might have
different calling conventions for different things, but that makes things a lot easier.

> So theoretically, you should be able to pass a pointer to a templated free function to C as long as the types in the functions parameter list match those the C function expects. I don't know if I'd do that myself, though.
>
>> [...]
>
> There's no such thing as C name mangling. C functions are *not* mangled (though some platforms do prepend an underscore to symbol names). What extern(C) does is to turn off D name mangling.
>

Yeah, I was playing a bit loose with the terminology and referring to the
leading underscore.

> So what you're asking for is a way to retain the D name mangling on an extern C function. The way to do that is with `pragma(mangle, "new_name")`. To match the original D function mangling, declare the function first without extern(C) and print `func.mangleof`. Use that as the parameter to pragma(mangle).
>
> I can't imagine any benefit you'd get from doing that, though.

Agreed!
December 16, 2020
On Wednesday, 16 December 2020 at 04:45:34 UTC, Dave P. wrote:

>
> Oh interesting, so I only need extern(C) for declaring symbols I’m linking to and
> for symbols I want to export to C. I had sort of assumed that D might have
> different calling conventions for different things, but that makes things a lot easier.
>

Not so fast! :-)

extern(C) does affect the calling convention on Windows x86. There's also extern(Windows), which changes a function to the stdcall calling convention used by the Win32 API (and OpenGL implementations on Windows, and a handful of other libraries). And there's no guarantee that as D moves to new platforms that there won't be other exceptions joining x86 Windows.

That's why I said I'm not sure I'd ever pass a templated function pointer to C. It isn't going to work on 32-bit Windows, or with any stdcall C function, right now, and possibly other platforms in the future.

So as a default, you should always be explicit with your extern(x) linkage attributes on functions even when you aren't actually linking with C, and only break that rule when it's necessary.
December 16, 2020
On Wednesday, 16 December 2020 at 04:17:13 UTC, Mike Parker wrote:

> However, the D calling convention is defined to be identical to the C calling convention on the host system for everything except Windows x86.

That's what's specified, but that's not how DMD actually behaves. DMD passes the arguments in reverse order. That's easily observable by calling a C function with D linkage:

$ cat foo.c
#include <stdio.h>

void foo(int a, int b)
{
    printf("a=%d b=%d\n", a, b);
}

$ cat main.d
module main;

import std;

pragma(mangle, "foo") // override D mangling to get the same name as the C function
void foo(int a, int b); // D calling convention

void main()
{
    foo(1, 2);
}

$ clang -o foo.o foo.c -c
$ dmd main.d foo.o
$ ./main
a=2 b=1

LDC behaves the same way as DMD and, IIRC, GDC follows how GCC passes the arguments.

--
/Jacob Carlborg


December 16, 2020
On Wednesday, 16 December 2020 at 04:17:13 UTC, Mike Parker wrote:

> However, the D calling convention is defined to be identical to the C calling convention on the host system for everything except Windows x86.

Also keep in mind that D supports other types than C does, like D arrays and delegates. In those cases the D calling convention needs to be extended since there's no way to just relay on the C calling convention. One could think it would be possible to passes D arrays and delegates as structs, but that's not how DMD passes them. On some platforms it matches how a struct is passed, on some it doesn't.

--
/Jacob Carlborg
December 16, 2020
On Wednesday, 16 December 2020 at 04:17:13 UTC, Mike Parker wrote:

> So what you're asking for is a way to retain the D name mangling on an extern C function. The way to do that is with `pragma(mangle, "new_name")`. To match the original D function mangling, declare the function first without extern(C) and print `func.mangleof`. Use that as the parameter to pragma(mangle).
>
> I can't imagine any benefit you'd get from doing that, though.

I actually had a use case for this. An initialization function that is called by the C runtime, i.e. `pragma(crt_constructor)`. That function needs to have C calling convention. But to avoid any conflicts with other `extern(C)` functions I wanted to keep the D mangling.

https://github.com/dlang/druntime/blob/master/src/core/memory.d#L212-L232

--
/Jacob Carlborg
December 16, 2020
On Wednesday, 16 December 2020 at 06:46:42 UTC, Jacob Carlborg wrote:
> On Wednesday, 16 December 2020 at 04:17:13 UTC, Mike Parker wrote:
>
>> However, the D calling convention is defined to be identical to the C calling convention on the host system for everything except Windows x86.
>
> That's what's specified, but that's not how DMD actually behaves. DMD passes the arguments in reverse order. That's easily observable by calling a C function with D linkage:

Is this a bug in the spec or in the implementation? How do we get this fixed?

December 16, 2020
On 2020-12-16 16:18, Dave P. wrote:

> Is this a bug in the spec or in the implementation?

Yeah, that's a good question. That's always problematic with D. The spec is incomplete.

> How do we get this fixed?

The simplest would be to change the spec.

-- 
/Jacob Carlborg