www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Question about x86 delegate calling convention

reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
I was stepping through some assembly when debugging one of my programs, and 
I noticed something a bit odd about how delegates were called.

Delegates consist basically of two things: a context pointer (either 'this' 
for class members or the outer function's frame pointer for nested 
functions), and the address of the actual code.  This is how D seems to call 
delegates:

EAX = context ptr;
EDX = code address;
EBX = context ptr;
EDX();

Inside the delegate, only the EAX context pointer is ever used.  'this' is 
also passed in EAX when calling class methods directly (a.method()).

My question is: why is EBX filled with the context pointer?  It's never used 
in the delegate; in fact, if the delegate has some complex code, and the 
codegen needs another register, it will preserve EBX (pushing it) before 
using it.

This happens even in release mode.

Is this a vestige of some older calling convention for delegates?

I also want to know this because I'm working on a (blatantly non-portable) 
method of dynamically calling functions at run-time (for fun). 
Jun 28 2006
next sibling parent James Dunne <james.jdunne gmail.com> writes:
Jarrett Billingsley wrote:
 I was stepping through some assembly when debugging one of my programs, and 
 I noticed something a bit odd about how delegates were called.
 
 Delegates consist basically of two things: a context pointer (either 'this' 
 for class members or the outer function's frame pointer for nested 
 functions), and the address of the actual code.  This is how D seems to call 
 delegates:
 
 EAX = context ptr;
 EDX = code address;
 EBX = context ptr;
 EDX();
 
 Inside the delegate, only the EAX context pointer is ever used.  'this' is 
 also passed in EAX when calling class methods directly (a.method()).
 
 My question is: why is EBX filled with the context pointer?  It's never used 
 in the delegate; in fact, if the delegate has some complex code, and the 
 codegen needs another register, it will preserve EBX (pushing it) before 
 using it.
 
 This happens even in release mode.
 
 Is this a vestige of some older calling convention for delegates?
 
 I also want to know this because I'm working on a (blatantly non-portable) 
 method of dynamically calling functions at run-time (for fun). 
 
 

A wild stab in the dark here, but perhaps the dup context ptr in EBX is coincidentally the same value used for a different feature implementation? Try all the cases of delegate calling, nested function calling, virtual method calling, variadic function calling, etc. and check the value of EBX vs. EAX. I have a suspicion your answer will lie in there somewhere. :) Then again, perhaps it's just bad codegen... but let's hope not. I don't think anyone could answer this 100% other than Walter. -- -----BEGIN GEEK CODE BLOCK----- Version: 3.1 GCS/MU/S d-pu s:+ a-->? C++++$ UL+++ P--- L+++ !E W-- N++ o? K? w--- O M-- V? PS PE Y+ PGP- t+ 5 X+ !R tv-->!tv b- DI++(+) D++ G e++>e h>--->++ r+++ y+++ ------END GEEK CODE BLOCK------ James Dunne
Jun 29 2006
prev sibling parent reply Walter Bright <newshound digitalmars.com> writes:
It's just an artifact of the register allocator. The context pointer is 
passed in EAX.
Jun 30 2006
next sibling parent "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Walter Bright" <newshound digitalmars.com> wrote in message 
news:e82j04$28fs$1 digitaldaemon.com...
 It's just an artifact of the register allocator. The context pointer is 
 passed in EAX.

OK, thanks :)
Jun 30 2006
prev sibling parent reply "Lionello Lunesu" <lionello lunesu.remove.com> writes:
"Walter Bright" <newshound digitalmars.com> wrote in message 
news:e82j04$28fs$1 digitaldaemon.com...
 It's just an artifact of the register allocator. The context pointer is 
 passed in EAX.

While on the subject, why can't a function pointer be (implicitly) converted to a delegate pointer? The context can just be ignored int that case so it'll probably be slightly less performant, but it's handy: a library could use delegates for all callbacks, without forcing the users to use either classes or nested functions. They can still use globals if they'd want. L.
Jul 01 2006
next sibling parent Walter Bright <newshound digitalmars.com> writes:
Lionello Lunesu wrote:
 "Walter Bright" <newshound digitalmars.com> wrote in message 
 news:e82j04$28fs$1 digitaldaemon.com...
 It's just an artifact of the register allocator. The context pointer is 
 passed in EAX.

While on the subject, why can't a function pointer be (implicitly) converted to a delegate pointer? The context can just be ignored int that case so it'll probably be slightly less performant, but it's handy: a library could use delegates for all callbacks, without forcing the users to use either classes or nested functions. They can still use globals if they'd want.

Some functions pass parameters in EAX (!)
Jul 01 2006
prev sibling parent reply Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Lionello Lunesu wrote:
 "Walter Bright" <newshound digitalmars.com> wrote in message 
 news:e82j04$28fs$1 digitaldaemon.com...
 It's just an artifact of the register allocator. The context pointer is 
 passed in EAX.

While on the subject, why can't a function pointer be (implicitly) converted to a delegate pointer? The context can just be ignored int that case so it'll probably be slightly less performant, but it's handy: a library could use delegates for all callbacks, without forcing the users to use either classes or nested functions. They can still use globals if they'd want.

Ok, so Walter beat me to posting the obvious reason. This was my reply, all typed up: The calling convention for extern(D) functions (the default) is to pass one of the arguments (the last one) in EAX[1] if it fits, so it's not ignored. It's used for something else. I believe Walter once said it was a feature he wanted to eventually implement though. I think he was even considering converting delegates to function pointers, by having it call dynamically generated code. If you don't mind using asm hacks, you could construct your delegates using an asm stub like below as the code to call. (the function pointer is stored as the context pointer) //------------------------------------------------------------ // The appropriate function to call for a delegate that receives a // void function(int) in EAX. // Note: It should also work for any other delegate that has the same // calling convention as the respective delegate, except that the last // (max 4 bytes) parameter is pushed to the stack instead of being put // in EAX. // That also means the return type shouldn't matter, as long as it's // stored in registers. // It just adjusts the stack to what such a function expects and jumps // into it. // This is of course specific to the calling convention and delegate // layout DMD uses // currently (does GDC the same ones?). // Also, this will obviously only work on x86 machines. void __function_delegate_simple() { asm { naked; pop EDX; // store return address mov ECX, EAX; // preserve function pointer pop EAX; // put last parameter in EAX push EDX; // put return address back in place jmp ECX; // jump directly to function } } /* Some test code: */ import std.stdio; void func(int i) { writefln("func(%d)", i); } // a convenient way to construct a delegate from scratch union dg { struct { void function(int) fn; void function() stub = &__function_delegate_simple; } void delegate(int) dg; } void main() { dg test; test.fn = &func; void delegate(int) my_dg = test.dg; // or use test.dg() directly my_dg(0); my_dg(1); my_dg(42); } //------------------------------------------------------------ [1]: At least if the return value is a primitive. When returning things like structs that are too big to fit in registers, I believe EAX is used as an indication where to put the return value. Don't feel like looking through a lot of disassembled code to figure out the specifics though. (Like what calling convention struct-returning delegates use)
Jul 01 2006
parent reply BCS <BCS pathlink.com> writes:
Frits van Bommel wrote:
 Lionello Lunesu wrote:
 
 "Walter Bright" <newshound digitalmars.com> wrote in message 
 news:e82j04$28fs$1 digitaldaemon.com...

 It's just an artifact of the register allocator. The context pointer 
 is passed in EAX.

While on the subject, why can't a function pointer be (implicitly) converted to a delegate pointer? The context can just be ignored int that case so it'll probably be slightly less performant, but it's handy: a library could use delegates for all callbacks, without forcing the users to use either classes or nested functions. They can still use globals if they'd want.

Ok, so Walter beat me to posting the obvious reason. This was my reply, all typed up: The calling convention for extern(D) functions (the default) is to pass one of the arguments (the last one) in EAX[1] if it fits, so it's not ignored. It's used for something else. I believe Walter once said it was a feature he wanted to eventually implement though. I think he was even considering converting delegates to function pointers, by having it call dynamically generated code. If you don't mind using asm hacks, you could construct your delegates using an asm stub like below as the code to call. (the function pointer is stored as the context pointer)

Couldn't the compiler generate the needed stub(s) for you? With that functionality, a function to delegate cast could be allowed.
Jul 01 2006
parent Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
BCS wrote:
 Frits van Bommel wrote:
 [a lot of cool code...]

Thanks :)
 Couldn't the compiler generate the needed stub(s) for you? With that 
 functionality, a function to delegate cast could be allowed.

Yes I would assume it could, and probably better than me as presumably those implementing the compiler have a better understanding of the calling conventions involved :). Might be a bit of work to make sure it handles all the corner cases though... Not sure how many of those would pop up, but there's bound to be some: Struct return values I mentioned in my post, and maybe array returns unless it uses EDX:EAX (or was it the other way around?) for that like it does for longs. Then there's associative arrays and last arguments that don't fit in EAX...
Jul 01 2006