simple ABI change to enable implicit conversion of functions to delegates?

Jonathan Marler via Digitalmars-d digitalmars-d at puremagic.com
Mon May 15 08:44:47 PDT 2017


On Monday, 15 May 2017 at 12:27:10 UTC, kinke wrote:
> On Monday, 15 May 2017 at 10:41:55 UTC, ag0aep6g wrote:
>> TL;DR: Changing the ABI of delegates so that the context 
>> pointer is passed last would make functions implicitly 
>> convertible to delegates, no?
>>
>> In the discussion of issue 17156 [1], Eyal asks why functions 
>> (function pointers?) don't convert implicitly to delegates. 
>> Walter's answer is that their ABIs differ and that a wrapper 
>> would have to be generated to treat a function transparently 
>> as a delegate.
>>
>> As far as I understand, the problem is that the hidden context 
>> pointer of a delegate takes the first register, pushing the 
>> other parameters back. That means the visible arguments are 
>> passed in different registers than when calling a function.
>>
>> Some code to show this:
>>
>> ----
>> void delegate(int a, int b) dg;
>> void f(int a, int b) { import std.stdio; writeln(a, " ", b); }
>>
>> void main()
>> {
>>     dg.funcptr = &f; /* This line should probably not compile, 
>> but that's
>>         another story. */
>>     dg.ptr = cast(void*) 13;
>>     f(1, 2); /* prints "1 2" - no surprise */
>>     dg(1, 2); /* prints "2 13" */
>> }
>> ----
>>
>> Arguments are put into registers in reverse order. I.e., in a 
>> sense, the call `f(1, 2)` passes (2, 1) to f. And the call 
>> `dg(1, 2)` passes (13, 2, 1), because a delegate has a hidden 
>> last parameter: the context pointer. But `f` isn't compiled 
>> with such a hidden parameter, so it sees 13 in `b` and 2 in 
>> `a`. The register that holds 1 is simply ignored because 
>> there's no corresponding parameter.
>>
>> Now, what if we changed the ABI of delegates so that the 
>> context pointer is passed after the explicit arguments? That 
>> is, `dg(1, 2)` would pass (2, 1, 13). Then `f` would see 2 in 
>> b and 1 in a. It would ignore 13. Seems everything would just 
>> work then.
>>
>> This seems quite simple. But I'm most probably just too 
>> ignorant to see the problems. Why wouldn't this work? Maybe 
>> there's a reason why the context pointer has to be passed 
>> first?
>>
>>
>>
>> [1] https://issues.dlang.org/show_bug.cgi?id=17156
>
> First of all, please don't forget that we're not only targeting 
> X86, and that the args, according to the docs, shouldn't 
> actually be reversed (incl. extern(D) - just on Win32, 
> everywhere else the C ABI is to be followed).
> Then some ABIs, like Microsoft's, treat ` this` in a special 
> way, not just like any other argument (in combination with 
> struct-return), which would apply to method calls via a 
> delegate with context = object reference.
>
> Some additional context: https://github.com/dlang/dmd/pull/5232

Not sure if members in this conversation were aware of my DIP to 
address this issue:

https://github.com/dlang/DIPs/pull/61

The DIP uses a different approach to solve this same problem. It 
adds new semantics to specify whether a function should use the 
same ABI as a delegate, called a "delegateable function".

The solution you have proposed would remove the need for my DIP 
by reconciling the difference between the function ABI and 
delegate ABI.  You have two ways to go about this, either modify 
the delegate ABI to match the function ABI, or the reverse.  The 
specific change could be stated as:

Modify the function/delegate ABI so that the first parameter of 
every function is passed in the same way as the context pointer 
of a delegate.

The questions is whether or not this restriction is reasonable.  
Currently I don't believe the language restricts any target 
machine to use a particular ABI. The only real restriction would 
be that functions with the same signature use the same ABI, but 
functions that add a parameter or use a different width on one of 
them can drastically change the ABI however they want.  This 
restriction could circumvent optimizations in the ABI since every 
function would HAVE TO be compatible with delegates. Take the 
following example.

Say you had a 32-bit machine with one 8-bit register and two 
32-bit registers.  Since it's a 32-bit machine, the obvious ABI 
for delegates would be to use one of the 32-bit registers for the 
context pointer.  Now say you had a function like this:

void foo(byte a, int b, int c)
{
}

The obviously optimized ABI would be to pass the three parameters 
in the 3 registers, however, if you require that the first 
parameter of every function must use the same ABI as delegates 
then you would have to pass the 8-bit argument "a" in the 32-bit 
register.  This means you could not longer pass all the 
parameters in the registers so either "b" or "c" would need to be 
passed in some other way, maybe on the stack.

If the application never intends on passing foo to a delegate, 
you've just removed an optimization for no benefit.




More information about the Digitalmars-d mailing list