Remix.run Logo
ok123456 a day ago

Doesn't x86 actually support nested call pointers using enter to natively support this in Pascal?

klodolph a day ago | parent [-]

It’s not a question of ISA support. If you call via a function pointer, how do you supply the pointer to the data? (It has to either be in a separate place from the function code, or the same place. One requires an ABI change, the other an executable, writable section of memory.)

WalterBright a day ago | parent | next [-]

> If you call via a function pointer, how do you supply the pointer to the data?

D has the notion of a "delegate", which is a (function pointer) and (context pointer) pair. This is incredibly useful, because delegates can:

1. call nested functions that need a pointer to the stack frame of the nestee function

2. call member functions that need `this` pointer

3. call lambdas

4. call COM member functions

The neato thing about this is the ABI for delegates is all the same, so a function that gets a delegate parameter will work with any of 1..4. It's one of the most used features of D.

klodolph a day ago | parent [-]

Yes, that’s the “ABI” alternative that I was referring to.

uecker a day ago | parent [-]

This is why I want to have such a feature in C. It would be extremely useful for language interoperability.

But because ISA was mentioned, x86 does indeed even have native support for this: https://devblogs.microsoft.com/oldnewthing/20231211-00/?p=10... These instructions are not too useful though and I do not think anybody uses them.

8 hours ago | parent | next [-]
[deleted]
klodolph a day ago | parent | prev [-]

This feature would IMO violate the contract that C allows you to specify memory layout of objects, to some level of detail (I am being a little vague about the “level of detail”).

Supporting closures, more or less, requires design decisions that are equivalent to choosing a specific layout for objects in an object-oriented language. C, as it is, makes none of these assumptions and you can translate a lot of different language ABIs into some C code (that may be clumsy). Keeping the abstraction that function pointers = pointers to entry points for functions, well, that’s frustrating for C programmers writing C programs, but extremely useful for interoperability.

uecker a day ago | parent [-]

At the moment we can not call nested functions or other things from other language from C, which is a pretty big hole in our interoperability story. I do not see why we need to make any design decision to specify object layout, we simply need a code pointer and static chain pair, which would then be sufficient to call arbitrary entities from other languages.

klodolph 19 hours ago | parent [-]

> I do not see why we need to make any design decision to specify object layout

> we simply need a code pointer and static chain pair

The code pointer and chain pair needs an object layout. You would have to pick a specific layout, and it would not be compatible with other languages that have a different layout.

Right now I can do this:

    struct a {
      void (*fun)(void *ctx);
      int data1;
      int data2;
    };
    struct b {
      void (*fun)(void *ctx);
      void *ctx;
    };
And I could call them:

     struct a *aptr;
     a->fun(aptr);

     struct b *bptr;
     b->fun(bptr->ctx);
There is only one neutral, maximally compatible option here—which is to have the function pointer separate from the arguments you want to pass in, and pass them in explicitly.

If you add closures to C you are making compatibility worse, not better.

uecker 17 hours ago | parent [-]

Except doing this manually does not allow me to directly call a C++ lambda, a Go closure, an Ada closure etc which I can not even express in C. So compatibility can not become worse, it is already maximally bad. And even where you can build a compatible solution in C, there are now different choices. Your examples already directly shows this contradicting your claim that here is only one option.

Adding such a type as a vocabulary type would fix all this. You can argue that we fix an object layout for a pointer pair, but this seems an acceptable trade-off to me. This seems far from your previous claim that this "requires design decisions that are equivalent to choosing a specific layout for objects in an object-oriented language." Note also that such a type can always adapt to different calling conventions of other languages by using the address of a static thunk as code pointer so it is very generic.

klodolph an hour ago | parent | next [-]

C++ lambdas and Go closures are incompatible with each other. If you pick something that lets C call Go closures (which are fat pointers passed around), it is likely to be incompatible with whatever your C++ library is doing.

jcranmer 5 hours ago | parent | prev | next [-]

A C++ lambda (as essentially is the case for a Rust lambda as well) is just an unnameable class object with an overloaded call operator, which makes the closure function body itself just a regular member function ABI-wise. You're not calling these things outside of the language, because they're only really callable via the language-specific static dispatch mechanism of templates. If you need to be able to dynamically call a lambda, you're going to be stuffing them in an object that contains a pointer to the lambda object and a pointer to the function of the actual body, something that looks essentially like what GP's assertion of what a lambda ABI looks like.

uecker 4 hours ago | parent [-]

You can stuff it into something such as std::function_ref which erases the Voldemort type and then it can be dynamically called without needing templates. And this could be from C or other languages if only there was a common type in C we could use.

inigyou 8 hours ago | parent | prev [-]

C++ lambdas have different sizes, so you can't embed an arbitrary one into another structure.

uecker 6 hours ago | parent [-]

I does not need to be embedded. I just need a pointer to it.

QuadmasterXLII a day ago | parent | prev [-]

but that can just be executable heap?

klodolph a day ago | parent [-]

Yes, but the advice was W^X. Either a page of memory is executable or writable but not both at the same time.