www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - simple ABI change to enable implicit conversion of functions to

reply ag0aep6g <anonymous example.com> writes:
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
May 15
parent reply kinke <kinke gmx.net> writes:
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
May 15
next sibling parent reply Jonathan Marler <johnnymarler gmail.com> writes:
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.
May 15
parent ag0aep6g <anonymous example.com> writes:
On 05/15/2017 05:44 PM, Jonathan Marler wrote:
 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.
I was aware of your DIP. I think they're related, but don't really overlap that much. You want to add a special kind of parameter that is taken as the context pointer when making a delegate of the function. You also add syntax to combine such a function with a matching `this` into a delegate. I'd like the context pointer to be passed differently, so that a normal function doesn't mistake it for one of its parameters. I don't think my thing would make your thing obsolete.
 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.
If I'm reading it right, with that rule this would be valid: ---- struct Foo { int x; } void baz(Foo* foo, int y) { import std.stdio; writeln(foo.x, " ", y); } void main() { void delegate(int y) dg; dg.funcptr = &baz; /* parameter `foo` is taken as context pointer */ dg.ptr = new Foo(1); dg(2); /* would print "1 2" */ } ---- That's not what I'm after. With my (crude) idea, that code would be just as invalid as it is now. A function's parameters would have to match the (visible) parameters of the delegate type. Any context pointer would be passed in a spot where the function doesn't look. The point is to allow using functions as delegates that don't use the context pointer.
May 15
prev sibling parent ag0aep6g <anonymous example.com> writes:
On 05/15/2017 02:27 PM, kinke wrote:
 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).
As far as I see, it doesn't matter if the arguments are reversed or not. The idea is to pass the context pointer in a way that doesn't affect the other arguments. Put it in a spot that would otherwise be unused. I.e., if the arguments are not reversed, add the context pointer as a new last parameter, and if the arguments are reversed, make it a new first parameter.
 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.
This __thiscall thing, right? https://msdn.microsoft.com/en-us/library/ek8tkfbw.aspx So, `this` always goes in ECX in that case. Yeah, that's a problem, I guess. But __thiscall is for C++, and we don't have to adhere to it in D, do we? I mean, I know that D is supposed to (mostly) follow the C calling convention of the system, but matching C++ is not a goal, or is it? `extern(C++)` functions/delegates have to follow it, obviously. But then we can just say that implicit conversion doesn't work with those.
 Some additional context: https://github.com/dlang/dmd/pull/5232
What I take from that is that changing the way arguments are passed (particularly if they're reversed or not) is going to break a ton of stuff.
May 15