www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - D 2.0 - dalegates to function

reply sergk <why.you.need my.email> writes:
Perhaps it's time to to bump an old request postponed to 2.0 times =)

So, how about to add some thunking magic - how  complex/complicated to
implement it?

-- 
serg.
Mar 01 2007
next sibling parent Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
sergk wrote:
 Perhaps it's time to to bump an old request postponed to 2.0 times =)
 
 So, how about to add some thunking magic - how  complex/complicated to
implement it?
(I'll be assuming an x86 for this post, and DMD. x86 because that's the assembler language I know (as well as the only architecture DMD runs on), and DMD because that's the calling convention documented at http://www.digitalmars.com/d/abi.html. Portability concerns are noted at the end) Did you mean converting delegates to function pointers (without an explicit this pointer) or creating delegates to functions? I think both can be implemented efficiently[1], perhaps even in library space. AFAIK it can't be done in an entirely generic fashion in the current though. The big problem currently is detection of out/inout parameters, and specifically whether the last parameter is one. [1]: Meaning: without re-pushing parameters. Pseudo-code for creating a delegate that calls a regular function: === dg.ptr = &func; static if (Parameters.length > 0 && "last parameter fits in EAX but is not 3 bytes large") { dg.funcptr = RetType function(Parameters) { asm { naked; pop ECX; // return address mov EDX, EAX; // move function pointer out of the way pop EAX; // last parameter must be in EAX push ECX; // restore return address jmp [EDX]; // jump directly to function entry point } } } else { dg.funcptr = RetType function(Parameters) { asm { naked; jmp [EAX]; } } } === We need to know whether the last parameter is out/inout to implement the static if(); if it wouldn't normally be passed in EAX but is out or inout it's still in there. This is a reduced variant of the "perfect forwarding" problem (which requires that knowledge for all parameters). IIRC Andrei made some posts about how they're going to try to fix that one. Once that's done the above should be feasible. Converting delegates to function pointers: This could be done by heap-allocating a stub that loads EAX with the correct value and then performs the right jump. This would have to be done such that both of those values (the EAX parameter and the function address) are correctly aligned in the code (so the gc can see them); nop instructions may be needed here. The heap-allocated code could look something like this (pseudo-asm): === asm { naked; static if(last param is in EAX) { pop ECX; // pop return address push EAX; // push last parameter on stack push ECX; // restore return address } // some NOPs for alignment perhaps mov EAX, dword 0x........ // some more NOPs? jmp 0x........ } === Where the "0x........" parts are filled at runtime. Notes and warnings: * First of all, above code is completely untested and written directly to this mail window. Use at own risk ;). * This code assumes both the delegate and the function pointer have the same calling convention, and that it's the one used by DMD on x86. Different code will have to be written for other architectures, calling conventions and (if it's even possible) cross-calling-convention conversion. * The second code sample assumes the heap is mapped with execute permission, which may be a non-portable assumption. For one thing, IIRC amd64 (aka x86-64) has an optional no-execute (NX) flag for memory pages that would break this; on OSs that use this for heap pages system calls would be required to disable this flag. * It also assumes the instructions are encodable such that the "0x........" parts look like regular pointers to the GC (i.e. absolute addresses). This may be a non-portable assumption as well, I'm not even sure x86 allows this (I think so though). An alternative would be for the GC to recognize such stubs and special-case them.
Mar 01 2007
prev sibling next sibling parent reply Reiner Pope <reIGNOREiner.poCAPSpe gmFOOail.cBARom> writes:
sergk wrote:
 Perhaps it's time to to bump an old request postponed to 2.0 times =)
 
 So, how about to add some thunking magic - how  complex/complicated to
implement it?
 
This works, except for the inout issue as Frits mentioned:
 T delegate(U) thunkify(T, U...)(T function(U) f)
 {
     struct Foo
     {
         T function(U) _f;
         T run(U u) { return _f(u); }
     }
     Foo* value = new Foo;
     value._f = f;
     return &value.run;
 }
Called like:
 int add2(int num)
 {
     return num + 2;
 }
 
 void main()
 {
     auto dg = thunkify(&add2);
 }
Cheers, Reiner
Mar 01 2007
parent reply Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Reiner Pope wrote:
 sergk wrote:
 Perhaps it's time to to bump an old request postponed to 2.0 times =)

 So, how about to add some thunking magic - how  complex/complicated to 
 implement it?
This works, except for the inout issue as Frits mentioned:
 T delegate(U) thunkify(T, U...)(T function(U) f)
 {
     struct Foo
     {
         T function(U) _f;
         T run(U u) { return _f(u); }
     }
     Foo* value = new Foo;
     value._f = f;
     return &value.run;
 }
Your version re-pushes arguments. Also, I'm pretty sure it doesn't handle varargs. I'd also like to note that my version for this (delegate that calls a function) should be implementable right now, without re-pushing arguments and with vararg support (I think). The only case it _doesn't_ handle is if the last parameter is out/inout, and would otherwise not be passed in EAX. So it only breaks if a 3-byte struct or anything larger than 4 bytes is passed as the last parameter and that parameter is out or inout. That is, aside from the fact it only supports DMD's extern(D) functions.
Mar 01 2007
parent reply Lionello Lunesu <lio lunesu.remove.com> writes:
 .... So it only breaks if a 3-byte struct or anything larger 
 than 4 bytes is passed as the last parameter and that parameter is out 
 or inout.
Do you happen to have any idea why 3-byte structs aren't passed in EAX (while 1, 2 and 4-byte structs are) ? I've been wondering about this. L.
Mar 01 2007
parent Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Lionello Lunesu wrote:
 .... So it only breaks if a 3-byte struct or anything larger than 4 
 bytes is passed as the last parameter and that parameter is out or inout.
Do you happen to have any idea why 3-byte structs aren't passed in EAX (while 1, 2 and 4-byte structs are) ? I've been wondering about this.
You'll have to ask Walter to be sure, but I guess so no distinction needs to be made based on what members they have. Let's consider the cases: A one byte struct most likely either has no members (IIRC 1 is the minimum size) or 1 char/byte/ubyte passed in AL. A 2-byte struct either has two such members (AL & AH) or a single (u)short/wchar (AX). A 4-byte struct most likely has one four-byte member (EAX) or two members of which at least one is 2 bytes (not as easily split, but the other case is probably more likely). Four one-byte members are also possible, but probably even less likely. A three-byte struct on the other hand, has almost by definition three one-byte members. (anything larger than one byte has natural .align > 1, though this can be overridden) So in this case the most likely case will be harder to split from the register. That's my guess, anyway.
Mar 01 2007
prev sibling parent reply Lionello Lunesu <lio lunesu.remove.com> writes:
sergk wrote:
 Perhaps it's time to to bump an old request postponed to 2.0 times =)
 
 So, how about to add some thunking magic - how  complex/complicated to
implement it?
 
There should be no reason for any thunking. The calling convention should be made compatible: the context-pointer of a delegate should be put in a register that's simply ignored by functions (without context). Or am I missing something? L.
Mar 01 2007
parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
Lionello Lunesu Wrote:

<snip>
 There should be no reason for any thunking.  The calling 
 convention should be made compatible: the context-pointer of a 
 delegate should be put in a register that's simply ignored by 
 functions (without context).
 
 Or am I missing something?
I think you're thinking of being able to convert functions to delegates, not vice versa which is what this thread is about. Stewart.
Mar 01 2007
parent reply Lionello Lunesu <lio lunesu.remove.com> writes:
Stewart Gordon wrote:
 Lionello Lunesu Wrote:
 
 <snip>
 There should be no reason for any thunking.  The calling 
 convention should be made compatible: the context-pointer of a 
 delegate should be put in a register that's simply ignored by 
 functions (without context).

 Or am I missing something?
I think you're thinking of being able to convert functions to delegates, not vice versa which is what this thread is about. Stewart.
Huh, I guess it is.. But it seems the others got it wrong, too :) But how can a delegate ever get "thunked" to behave like a function? R delegate(X...) => R function(C,X...), with the C the context ? I've had much need for passing function-pointers as delegates, but never the other way around. L.
Mar 01 2007
next sibling parent reply Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Lionello Lunesu wrote:
 But how can a delegate ever get "thunked" to behave like a function? R 
 delegate(X...) => R function(C,X...), with the C the context ?
Basically, a bit of code is generated at run-time (on the heap) that calls the delegate with the correct context pointer. Then that code is pointed to by the function pointer. The context pointer can be put into the code as an immediate value.
Mar 02 2007
parent reply =?ISO-8859-1?Q?Philipp_Sp=f6th?= <no spam.no> writes:
Just for my understanding:
If functionpointers could hold delegates would delegates become completly
redundant?


 Lionello Lunesu wrote:
 But how can a delegate ever get "thunked" to behave like a function? R 
 delegate(X...) => R function(C,X...), with the C the context ?
Basically, a bit of code is generated at run-time (on the heap) that calls the delegate with the correct context pointer. Then that code is pointed to by the function pointer. The context pointer can be put into the code as an immediate value.
Mar 03 2007
parent Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Philipp Spöth wrote:
 Just for my understanding:
 If functionpointers could hold delegates would delegates become completly
redundant?
They'd probably still be the most efficient method.
Mar 03 2007
prev sibling parent sergk <why.you.need my.email> writes:
Lionello Lunesu Wrote:
 I've had much need for passing function-pointers as delegates, but never 
 the other way around.
For example use object's methods as win32 window procedures, or as callbacks in C-libraries. -- serg.
Mar 02 2007