www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Before it's too late: delegate calling convention

reply Lionello Lunesu <lio lunesu.remove.com> writes:
Just thought I'd post this before it's too late (some will argue it 
already is too late, but I don't think so):

Please make the calling convention for delegates compatible with the one 
for functions. I want to be able to write my API using only delegates, 
and allow users to pass also globals (function pointers) for callbacks.

The two workarounds currently are

* wrapping the function in a dummy delegate literal (I don't like having 
to do this; it's not about the work, but it looks clumsy)

* provide an API using delegates and one using function pointers (much 
like std.thread.Thread's constructor; needs extra code, for what?)

If I'd be asked to submit ONE issue for "to do before 1.0", this would 
be it.

L.
Nov 01 2006
parent reply Gregor Richards <Richards codu.org> writes:
Lionello Lunesu wrote:
 Just thought I'd post this before it's too late (some will argue it 
 already is too late, but I don't think so):
 
 Please make the calling convention for delegates compatible with the one 
 for functions. I want to be able to write my API using only delegates, 
 and allow users to pass also globals (function pointers) for callbacks.
 
 The two workarounds currently are
 
 * wrapping the function in a dummy delegate literal (I don't like having 
 to do this; it's not about the work, but it looks clumsy)
 
 * provide an API using delegates and one using function pointers (much 
 like std.thread.Thread's constructor; needs extra code, for what?)
 
 If I'd be asked to submit ONE issue for "to do before 1.0", this would 
 be it.
 
 L.

One major problem: This would make function pointers incompatible with C function pointers, making interfacing with a huge chunk of software impossible. One solution nobody will like (hell, I don't even like it): Have a special value for the scope part of the delegate that basically implies "this is not really a delegate", and will cause the call to the delegate to not pass it as the first argument. This would incur an if for delegate calls, but would make function pointers castable to delegates. - Gregor Richards
Nov 01 2006
parent reply Gregor Richards <Richards codu.org> writes:
Gregor Richards wrote:
 Lionello Lunesu wrote:
 
 Just thought I'd post this before it's too late (some will argue it 
 already is too late, but I don't think so):

 Please make the calling convention for delegates compatible with the 
 one for functions. I want to be able to write my API using only 
 delegates, and allow users to pass also globals (function pointers) 
 for callbacks.

 The two workarounds currently are

 * wrapping the function in a dummy delegate literal (I don't like 
 having to do this; it's not about the work, but it looks clumsy)

 * provide an API using delegates and one using function pointers (much 
 like std.thread.Thread's constructor; needs extra code, for what?)

 If I'd be asked to submit ONE issue for "to do before 1.0", this would 
 be it.

 L.

One major problem: This would make function pointers incompatible with C function pointers, making interfacing with a huge chunk of software impossible. One solution nobody will like (hell, I don't even like it): Have a special value for the scope part of the delegate that basically implies "this is not really a delegate", and will cause the call to the delegate to not pass it as the first argument. This would incur an if for delegate calls, but would make function pointers castable to delegates. - Gregor Richards

And a solution that's less nasty: Allow casting of function pointers to delegate pointers by automatic thunktion generation. - Gregor Richards
Nov 01 2006
next sibling parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Gregor Richards" <Richards codu.org> wrote in message 
news:4548CD75.80803 codu.org...

 And a solution that's less nasty: Allow casting of function pointers to 
 delegate pointers by automatic thunktion generation.

I like that a lot better. But if you're wrapping a function pointer in a delegate reference, is a thunk really needed? I mean, the function won't be accessing the context pointer, so why not just set the delegate's context pointer to null? i.e. int foo() { } int delegate() bar = cast(int delegate())&foo; // bar's function address is &foo, and bar.ptr == null
Nov 01 2006
parent reply Gregor Richards <Richards codu.org> writes:
Jarrett Billingsley wrote:
 "Gregor Richards" <Richards codu.org> wrote in message 
 news:4548CD75.80803 codu.org...
 
 
And a solution that's less nasty: Allow casting of function pointers to 
delegate pointers by automatic thunktion generation.

I like that a lot better. But if you're wrapping a function pointer in a delegate reference, is a thunk really needed? I mean, the function won't be accessing the context pointer, so why not just set the delegate's context pointer to null? i.e. int foo() { } int delegate() bar = cast(int delegate())&foo; // bar's function address is &foo, and bar.ptr == null

The context pointer isn't magic, it's passed as an argument to the function. So if you directly casted, you'd end up with an extra argument passed to the function, any argument processing would be off-by-one. - Gregor Richards
Nov 01 2006
parent reply Lionello Lunesu <lio lunesu.remove.com> writes:
Gregor Richards wrote:
 Jarrett Billingsley wrote:
 "Gregor Richards" <Richards codu.org> wrote in message 
 news:4548CD75.80803 codu.org...


 And a solution that's less nasty: Allow casting of function pointers 
 to delegate pointers by automatic thunktion generation.

I like that a lot better. But if you're wrapping a function pointer in a delegate reference, is a thunk really needed? I mean, the function won't be accessing the context pointer, so why not just set the delegate's context pointer to null? i.e. int foo() { } int delegate() bar = cast(int delegate())&foo; // bar's function address is &foo, and bar.ptr == null

The context pointer isn't magic, it's passed as an argument to the function. So if you directly casted, you'd end up with an extra argument passed to the function, any argument processing would be off-by-one. - Gregor Richards

Actually, it's passed in a register, so it could be made to work. L.
Nov 01 2006
parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Lionello Lunesu" <lio lunesu.remove.com> wrote in message 
news:eic2t2$2hjj$1 digitaldaemon.com...
 Actually, it's passed in a register, so it could be made to work.

Ahh, but so are parameters. Since the last parameter of a function is passed in EAX (as long as it fits), something like.. void fork(int x, int y) Will take y on the stack and x in EAX. But then converting it to a delegate, Y would end up past the end of the arguments, x in y's spot, and the context in x's spot. :S
Nov 01 2006
next sibling parent Gregor Richards <Richards codu.org> writes:
Jarrett Billingsley wrote:
 "Lionello Lunesu" <lio lunesu.remove.com> wrote in message 
 news:eic2t2$2hjj$1 digitaldaemon.com...
 
Actually, it's passed in a register, so it could be made to work.

Ahh, but so are parameters. Since the last parameter of a function is passed in EAX (as long as it fits), something like.. void fork(int x, int y) Will take y on the stack and x in EAX. But then converting it to a delegate, Y would end up past the end of the arguments, x in y's spot, and the context in x's spot. :S

Like I said, it's passed as an argument, it's not magic. - Gregor Richards
Nov 02 2006
prev sibling parent reply Lionello Lunesu <lio lunesu.remove.com> writes:
Jarrett Billingsley wrote:
 "Lionello Lunesu" <lio lunesu.remove.com> wrote in message 
 news:eic2t2$2hjj$1 digitaldaemon.com...
 Actually, it's passed in a register, so it could be made to work.

Ahh, but so are parameters. Since the last parameter of a function is passed in EAX (as long as it fits), something like.. void fork(int x, int y) Will take y on the stack and x in EAX. But then converting it to a delegate, Y would end up past the end of the arguments, x in y's spot, and the context in x's spot. :S

IIRC, Thomas has made a working example once, using inline assembly. I think it was indeed putting the context pointer of a delegate to null (ignoring it would work to) and calling the function-pointer as a normal function. To make this work without changing the ABI would mean an extra null-check before calling the delegate: if (dg.ptr !is null) // push/mov for delegate ABI else // push/mov for function-pointer ABI asm { call dg.func; } Making the two calling conventions compatible would mean that the context pointer would be stored somewhere where it doesn't interfere with C's function-pointer ABI. ... Right? L.
Nov 02 2006
parent "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Lionello Lunesu" <lio lunesu.remove.com> wrote in message 
news:eicths$9sn$1 digitaldaemon.com..

 It's not EAX, but a different register :)

It's EAX and EBX, but delegates only access EAX as the context, as it's treated as the first parameter. The EBX is superfluous. mov EAX, dg.ptr mov EBX, dg.ptr mov EDX, dg.function call [EDX] Is what DMD generates for a delegate call.
 IIRC, Thomas has made a working example once, using inline assembly. I 
 think it was indeed putting the context pointer of a delegate to null 
 (ignoring it would work to) and calling the function-pointer as a normal 
 function.

 To make this work without changing the ABI would mean an extra null-check 
 before calling the delegate:

 if (dg.ptr !is null)
   // push/mov for delegate ABI
 else
   // push/mov for function-pointer ABI
 asm { call dg.func; }

I guess that works :) Of course, it means having to do an extra comparison every time you call a function through a pointer or a delegate, but..
 Making the two calling conventions compatible would mean that the context 
 pointer would be stored somewhere where it doesn't interfere with C's 
 function-pointer ABI. ... Right?

Hmm.. if all function pointers became delegates technically, then.. I guess since C functions would be expecting extern(C) pointers, they wouldn't need the context at all. So it'd be extern(C) void function() f; if(dg.ptr is null) f = dg.function; // don't need context else // error, can't pass delegate with context to a C function CFunction(f); Maybe?
Nov 02 2006
prev sibling parent reply Burton Radons <burton-radons smocky.com> writes:
Gregor Richards wrote:
 And a solution that's less nasty: Allow casting of function pointers to 
 delegate pointers by automatic thunktion generation.

And vice versa, they'd actually be very simple with D. As a matter of fact... void thunk_delegate_to_function_args () { asm { naked; mov ECX, EAX; mov EAX, [ESP+4]; jmp ECX; } } void thunk_delegate_to_function_noargs () { asm { naked; jmp EAX; } } template ThunkBase (From, To, bool HasArgs) { To ThunkBase (From from) { size_t thunk_length; ubyte [] data; size_t pos; To to; void add (ubyte [] stuff...) { data [pos .. pos + stuff.length] = stuff; pos += stuff.length; } static if (from.sizeof == 4) { union Delegate { struct { void *data, func; } To result; } Delegate output; output.data = from; static if (HasArgs) output.func = &thunk_delegate_to_function_args; else output.func = &thunk_delegate_to_function_noargs; return output.result; } else { static assert (to.sizeof == 4); union Delegate { struct { void *self, func; } From all; } Delegate fromd; fromd.all = from; thunk_length = 10 + 13; data = new ubyte [thunk_length]; add (0x8B, 0x0C, 0x24, 0x83, 0xEC, 0x04, 0x89, 0x0C, 0x24, 0x89, 0x44, 0x24, 0x04); // mov ECX, [ESP+0]; sub ESP, 4; mov [ESP+0], ECX; mov [ESP+4], EAX; add (0xB8); add ((cast (ubyte *) &fromd.self) [0 .. 4]); // mov EAX, from.this; int offset = cast (int) (cast (void *) fromd.func - (cast (void *) data + pos + 5)); add (0xE9); add ((cast (ubyte *) &offset) [0 .. 4]); return cast (To) data.ptr; } } } /// Convert a delegate into a function or vice versa. struct Thunk (ReturnType, A = void, B = void, C = void, D = void, E = void, F = void, G = void, H = void, I = void, J = void, K = void, L = void, M = void, N = void, O = void, P = void, Q = void, R = void, S = void, T = void, U = void, V = void, W = void, X = void, Y = void, Z = void) { static if (is (A == void)) { alias ReturnType delegate () TD; alias ReturnType function () TF; } else static if (is (B == void)) { alias ReturnType delegate (A) TD; alias ReturnType function (A) TF; } else static if (is (C == void)) { alias ReturnType delegate (A, B) TD; alias ReturnType function (A, B) TF; } else static if (is (D == void)) { alias ReturnType delegate (A, B, C) TD; alias ReturnType function (A, B, C) TF; } else static if (is (E == void)) { alias ReturnType delegate (A, B, C, D) TD; alias ReturnType function (A, B, C, D) TF; } else static if (is (F == void)) { alias ReturnType delegate (A, B, C, D, E) TD; alias ReturnType function (A, B, C, D, E) TF; } else static if (is (G == void)) { alias ReturnType delegate (A, B, C, D, E, F) TD; alias ReturnType function (A, B, C, D, E, F) TF; } else static if (is (H == void)) { alias ReturnType delegate (A, B, C, D, E, F, G) TD; alias ReturnType function (A, B, C, D, E, F, G) TF; } else static if (is (I == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H) TD; alias ReturnType function (A, B, C, D, E, F, G, H) TF; } else static if (is (J == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I) TF; } else static if (is (K == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J) TF; } else static if (is (L == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K) TF; } else static if (is (M == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L) TF; } else static if (is (N == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M) TF; } else static if (is (O == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N) TF; } else static if (is (P == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O) TF; } else static if (is (Q == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P) TF; } else static if (is (R == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q) TF; } else static if (is (S == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R) TF; } else static if (is (T == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S) TF; } else static if (is (U == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T) TF; } else static if (is (V == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U) TF; } else static if (is (W == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V) TF; } else static if (is (X == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W) TF; } else static if (is (Y == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X) TF; } else static if (is (Z == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y) TF; } else { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z) TF; } static if (!is (A == void) && A.sizeof <= 4 && !is (A == float)) const bool hasargs = true; else const bool hasargs = false; static TD opCall (TF from) { return ThunkBase! (TF, TD, hasargs) (from); } static TF opCall (TD from) { return ThunkBase! (TD, TF, hasargs) (from); } } Use like: int zambo (float a, float b) { writef ("Hello %s, %s!\n", a, b); } alias Thunk! (int, float, float) thunker; auto rambo = thunker (zambo); rambo (16, 32); Note that casting a function to a delegate doesn't even require an allocation, it's just a matter of choosing the right wrapper. Not tested thoroughly since I don't care, it'll likely fail like crazy if you do bad things to it.
Nov 01 2006
parent reply Gregor Richards <Richards codu.org> writes:
Burton Radons wrote:
 Gregor Richards wrote:
 
 And a solution that's less nasty: Allow casting of function pointers 
 to delegate pointers by automatic thunktion generation.

And vice versa, they'd actually be very simple with D. As a matter of fact... void thunk_delegate_to_function_args () { asm { naked; mov ECX, EAX; mov EAX, [ESP+4]; jmp ECX; } } void thunk_delegate_to_function_noargs () { asm { naked; jmp EAX; } } template ThunkBase (From, To, bool HasArgs) { To ThunkBase (From from) { size_t thunk_length; ubyte [] data; size_t pos; To to; void add (ubyte [] stuff...) { data [pos .. pos + stuff.length] = stuff; pos += stuff.length; } static if (from.sizeof == 4) { union Delegate { struct { void *data, func; } To result; } Delegate output; output.data = from; static if (HasArgs) output.func = &thunk_delegate_to_function_args; else output.func = &thunk_delegate_to_function_noargs; return output.result; } else { static assert (to.sizeof == 4); union Delegate { struct { void *self, func; } From all; } Delegate fromd; fromd.all = from; thunk_length = 10 + 13; data = new ubyte [thunk_length]; add (0x8B, 0x0C, 0x24, 0x83, 0xEC, 0x04, 0x89, 0x0C, 0x24, 0x89, 0x44, 0x24, 0x04); // mov ECX, [ESP+0]; sub ESP, 4; mov [ESP+0], ECX; mov [ESP+4], EAX; add (0xB8); add ((cast (ubyte *) &fromd.self) [0 .. 4]); // mov EAX, from.this; int offset = cast (int) (cast (void *) fromd.func - (cast (void *) data + pos + 5)); add (0xE9); add ((cast (ubyte *) &offset) [0 .. 4]); return cast (To) data.ptr; } } } /// Convert a delegate into a function or vice versa. struct Thunk (ReturnType, A = void, B = void, C = void, D = void, E = void, F = void, G = void, H = void, I = void, J = void, K = void, L = void, M = void, N = void, O = void, P = void, Q = void, R = void, S = void, T = void, U = void, V = void, W = void, X = void, Y = void, Z = void) { static if (is (A == void)) { alias ReturnType delegate () TD; alias ReturnType function () TF; } else static if (is (B == void)) { alias ReturnType delegate (A) TD; alias ReturnType function (A) TF; } else static if (is (C == void)) { alias ReturnType delegate (A, B) TD; alias ReturnType function (A, B) TF; } else static if (is (D == void)) { alias ReturnType delegate (A, B, C) TD; alias ReturnType function (A, B, C) TF; } else static if (is (E == void)) { alias ReturnType delegate (A, B, C, D) TD; alias ReturnType function (A, B, C, D) TF; } else static if (is (F == void)) { alias ReturnType delegate (A, B, C, D, E) TD; alias ReturnType function (A, B, C, D, E) TF; } else static if (is (G == void)) { alias ReturnType delegate (A, B, C, D, E, F) TD; alias ReturnType function (A, B, C, D, E, F) TF; } else static if (is (H == void)) { alias ReturnType delegate (A, B, C, D, E, F, G) TD; alias ReturnType function (A, B, C, D, E, F, G) TF; } else static if (is (I == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H) TD; alias ReturnType function (A, B, C, D, E, F, G, H) TF; } else static if (is (J == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I) TF; } else static if (is (K == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J) TF; } else static if (is (L == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K) TF; } else static if (is (M == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L) TF; } else static if (is (N == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M) TF; } else static if (is (O == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N) TF; } else static if (is (P == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O) TF; } else static if (is (Q == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P) TF; } else static if (is (R == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q) TF; } else static if (is (S == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R) TF; } else static if (is (T == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S) TF; } else static if (is (U == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T) TF; } else static if (is (V == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U) TF; } else static if (is (W == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V) TF; } else static if (is (X == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W) TF; } else static if (is (Y == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X) TF; } else static if (is (Z == void)) { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y) TF; } else { alias ReturnType delegate (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z) TD; alias ReturnType function (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z) TF; } static if (!is (A == void) && A.sizeof <= 4 && !is (A == float)) const bool hasargs = true; else const bool hasargs = false; static TD opCall (TF from) { return ThunkBase! (TF, TD, hasargs) (from); } static TF opCall (TD from) { return ThunkBase! (TD, TF, hasargs) (from); } } Use like: int zambo (float a, float b) { writef ("Hello %s, %s!\n", a, b); } alias Thunk! (int, float, float) thunker; auto rambo = thunker (zambo); rambo (16, 32); Note that casting a function to a delegate doesn't even require an allocation, it's just a matter of choosing the right wrapper. Not tested thoroughly since I don't care, it'll likely fail like crazy if you do bad things to it.

It's also DMD-with-D-calling-convention-specific X_X (eg: won't work with GDC, won't work with C functions) - Gregor Richards
Nov 01 2006
parent reply Burton Radons <burton-radons smocky.com> writes:
Gregor Richards wrote:
 It's also DMD-with-D-calling-convention-specific X_X
 
 (eg: won't work with GDC, won't work with C functions)

It could easily be done, just have the thunks bounce into a function the Thunk template builds that reorder the arguments (this function can gratefully be written in D), and add a post-call patch to fix the stack. I'm not trying to argue that this is the way it should be, obviously the compiler should be handling this magic. But while I've never heard Walter say anything about thunking, I can predict with great accuracy that he'll be against it because it's a hidden allocation [of great utility]. It seems like it would be better to have SOMETHING to do the task than to keep crashing against the barricade and getting more and more frustrated. Anyway, thunking D to C would generally be pretty hazardous because as soon as you pass it to the C library it'll be collectable, and since the allocation IS hidden it would be easy to miss. Manually thunking C function pointers isn't difficult with any correctly-written C library and doesn't happen often enough in my experience (once per wrapper, to be exact) to bother with. What happens more often is the need to cast from function pointers to delegates (or SOME common function pointer), constrained to the D calling convention. Since that can be done without allocations, I think that and only that should be made part of the language. The rest can follow with time, or not.
Nov 01 2006
parent Gregor Richards <Richards codu.org> writes:
Burton Radons wrote:
 Gregor Richards wrote:
 
 It's also DMD-with-D-calling-convention-specific X_X

 (eg: won't work with GDC, won't work with C functions)

It could easily be done, just have the thunks bounce into a function the Thunk template builds that reorder the arguments (this function can gratefully be written in D), and add a post-call patch to fix the stack. I'm not trying to argue that this is the way it should be, obviously the compiler should be handling this magic. But while I've never heard Walter say anything about thunking, I can predict with great accuracy that he'll be against it because it's a hidden allocation [of great utility]. It seems like it would be better to have SOMETHING to do the task than to keep crashing against the barricade and getting more and more frustrated. Anyway, thunking D to C would generally be pretty hazardous because as soon as you pass it to the C library it'll be collectable, and since the allocation IS hidden it would be easy to miss. Manually thunking C function pointers isn't difficult with any correctly-written C library and doesn't happen often enough in my experience (once per wrapper, to be exact) to bother with. What happens more often is the need to cast from function pointers to delegates (or SOME common function pointer), constrained to the D calling convention. Since that can be done without allocations, I think that and only that should be made part of the language. The rest can follow with time, or not.

If I recall properly, DMC uses thunktions for C++'s lame pseudodelegates :) - Gregor Richards
Nov 01 2006