Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix Issue 7925 - extern(C++) delegates #13217

Merged
merged 1 commit into from Nov 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/dmd/e2ir.d
Expand Up @@ -5106,7 +5106,15 @@ elem *callfunc(const ref Loc loc,
ec = el_same(&ethis);
ethis = el_una(target.is64bit ? OP128_64 : OP64_32, TYnptr, ethis); // get this
ec = array_toPtr(t, ec); // get funcptr
ec = el_una(OPind, totym(tf), ec);
tym_t tym;
/* Delegates use the same calling convention as member functions.
* For extern(C++) on Win32 this differs from other functions.
*/
if (tf.linkage == LINK.cpp && !target.is64bit && target.os == Target.OS.Windows)
tym = (tf.parameterList.varargs == VarArg.variadic) ? TYnfunc : TYmfunc;
else
tym = totym(tf);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks to me like totym() is in the wrong here, not the caller.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks to me like totym() is in the wrong here, not the caller.

totym() does not know, that the function is used in a delegate, because the argument tf contains the type of funcptr and not the whole delegate. TypeFunction does not seem to have information about the hidden this pointer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative implementation would be to add a flag to TypeFunction, which marks functions with hidden this pointer. Function tytym could then choose the calling convention based on that flag. The flag could also be used to fix https://issues.dlang.org/show_bug.cgi?id=3720 and https://issues.dlang.org/show_bug.cgi?id=17080, but it would be a bigger change and affect more code. You would probably also need a new syntax for function types with hidden this pointer and a trait for getting the flag.

ec = el_una(OPind, tym, ec);
}

const ty = fd ? toSymbol(fd).Stype.Tty : ec.Ety;
Expand Down
4 changes: 3 additions & 1 deletion src/dmd/tocsym.d
Expand Up @@ -387,7 +387,9 @@ Symbol *toSymbol(Dsymbol s)
break;
case LINK.cpp:
s.Sflags |= SFLpublic;
if (fd.isThis() && !target.is64bit && target.os == Target.OS.Windows)
/* Nested functions use the same calling convention as
* member functions, because both can be used as delegates. */
if ((fd.isThis() || fd.isNested()) && !target.is64bit && target.os == Target.OS.Windows)
{
if ((cast(TypeFunction)fd.type).parameterList.varargs == VarArg.variadic)
{
Expand Down
103 changes: 103 additions & 0 deletions test/runnable_cxx/extra-files/cpp7925.cpp
@@ -0,0 +1,103 @@
#include <stdarg.h>
#include <assert.h>

class C1
{
public:
virtual ~C1();

int i;

int f0();
int f1(int a);
int f2(int a, int b);
virtual int f3(int a, int b);
int f4(int a, ...);
};

C1::~C1()
{
}

int C1::f0()
{
return i;
}

int C1::f1(int a)
{
return i + a;
}

int C1::f2(int a, int b)
{
return i + a + b;
}

int C1::f3(int a, int b)
{
return i + a + b;
}

int C1::f4(int a, ...)
{
int r = i + a;
int last = a;

va_list argp;
va_start(argp, a);
while (last)
{
last = va_arg(argp, int);
r += last;
}
va_end(argp);
return r;
}

C1 *createC1()
{
return new C1();
}

class C2
{
public:
virtual ~C2();

int i;

int f0();
int f1(int a);
int f2(int a, int b);
virtual int f3(int a, int b);
int f4(int a, ...);
};

C2 *createC2();

void runCPPTests()
{
C2 *c2 = createC2();
c2->i = 100;
assert(c2->f0() == 100);
assert(c2->f1(1) == 101);
assert(c2->f2(20, 3) == 123);
assert(c2->f3(20, 3) == 123);
assert(c2->f4(20, 3, 0) == 123);

int (C2::*fp0)() = &C2::f0;
int (C2::*fp1)(int) = &C2::f1;
int (C2::*fp2)(int, int) = &C2::f2;
int (C2::*fp3)(int, int) = &C2::f3;
#ifndef __DMC__
int (C2::*fp4)(int, ...) = &C2::f4;
#endif
assert((c2->*(fp0))() == 100);
assert((c2->*(fp1))(1) == 101);
assert((c2->*(fp2))(20, 3) == 123);
assert((c2->*(fp3))(20, 3) == 123);
#ifndef __DMC__
assert((c2->*(fp4))(20, 3, 0) == 123);
#endif
}
143 changes: 143 additions & 0 deletions test/runnable_cxx/test7925.d
@@ -0,0 +1,143 @@
// EXTRA_CPP_SOURCES: cpp7925.cpp
import core.vararg;

extern(C++) class C1
{
public:
~this();

int i;

final int f0();
final int f1(int a);
final int f2(int a, int b);
int f3(int a, int b);
final int f4(int a, ...);
};

extern(C++) C1 createC1();

extern(C++) class C2
{
public:
~this()
{
}

int i;

final int f0()
{
return i;
}

final int f1(int a)
{
return i + a;
}

final int f2(int a, int b)
{
return i + a + b;
}

int f3(int a, int b)
{
return i + a + b;
}

final int f4(int a, ...)
{
int r = i + a;
int last = a;

va_list argp;
va_start(argp, a);
while (last)
{
last = va_arg!int(argp);
r += last;
}
va_end(argp);
return r;
}
};

extern(C++) C2 createC2()
{
return new C2;
}

auto callMember(alias F, Params...)(__traits(parent, F) obj, Params params)
{
static if(__traits(getFunctionVariadicStyle, F) == "stdarg")
enum varargSuffix = ", ...";
else
enum varargSuffix = "";

static if(is(typeof(&F) R == return) && is(typeof(F) P == __parameters))
mixin("extern(" ~ __traits(getLinkage, F) ~ ") R delegate(P" ~ varargSuffix ~ ") dg;");
dg.funcptr = &F;
dg.ptr = cast(void*)obj;
return dg(params);
}

extern(C++) void runCPPTests();

void main()
{
C1 c1 = createC1();
c1.i = 100;
assert(c1.f0() == 100);
assert(c1.f1(1) == 101);
assert(c1.f2(20, 3) == 123);
assert(c1.f3(20, 3) == 123);
assert(c1.f4(20, 3, 0) == 123);

auto dg0 = &c1.f0;
auto dg1 = &c1.f1;
auto dg2 = &c1.f2;
auto dg3 = &c1.f3;
auto dg4 = &c1.f4;
assert(dg0() == 100);
assert(dg1(1) == 101);
assert(dg2(20, 3) == 123);
assert(dg3(20, 3) == 123);
assert(dg4(20, 3, 0) == 123);

assert(callMember!(C1.f0)(c1) == 100);
assert(callMember!(C1.f1)(c1, 1) == 101);
assert(callMember!(C1.f2)(c1, 20, 3) == 123);
assert(callMember!(C1.f3)(c1, 20, 3) == 123);
assert(callMember!(C1.f4)(c1, 20, 3, 0) == 123);

int i;
extern(C++) void delegate() lamdba1 = () {
i = 5;
};
lamdba1();
assert(i == 5);

extern(C++) int function(int, int) lamdba2 = (int a, int b) {
return a + b;
};
assert(lamdba2(3, 4) == 7);

extern(C++) void delegate(int, ...) lamdba3 = (int a, ...) {
i = a;
int last = a;

va_list argp;
va_start(argp, a);
while (last)
{
last = va_arg!int(argp);
i += last;
}
va_end(argp);
};
lamdba3(1000, 200, 30, 4, 0);
assert(i == 1234);

runCPPTests();
}