wide function pointer type

Martin Uecker ma.uecker@gmail.com
Wed Oct 20 18:52:23 GMT 2021


Am Mittwoch, den 20.10.2021, 01:24 -0700 schrieb Kaz Kylheku (libffi):
> On 2021-10-19 05:13, Martin Uecker via Libffi-discuss wrote:
> > void foo(
> >   void (cb1)(void* data, int a), void* data1,
> >   void* other_data);
> > 
> > Here a human (and maybe also a machine) could guess
> > that data1 belongs to cb1 but not other_data.  But
> > it is not clear and in more complicated cases
> > even less so.
> 
> That's a property of almost every C API. You cannot
> guess the properties from the declaration alone.
> 
> If memcpy didn't have the const on the source
> argument, you couldn't guess which argument is
> the destination and which is the source.

The difference here is that something which is a fundamental
type in most other languages is split up into two things
in C.

>                                  y
> Argument names and analogy to x    tell you
> which argument of pow is the base and which the
> exponent.
> 
> That's the job of documentation.

Generators for language wrappers do
not read documentation ;-)

> A declarative mechanism which indicates that a given
> context parameter goes with a given function could
> exist instead of _Wide.

Yes, I had considered this too.

> Such a thing was introduced 22 years ago for VLA's:
> 
>    void fun(int m, double a[m]);

Sure, but VLA types are also underdeveloped
The limitations are:

- You can not store a pointer to a VLA in the struct.
- You can not put the size later.
- You can not
always automatically verify that
the size is correct at the caller side because
the semantic type of a
declaration is:

void fun(int m, double a[*]);

All this would be avoided with a wide
pointer type that integrates the size into the
pointer (as in FORTRAN). This was in fact proposed
by Dennis Ritchie himself a long time ago:

Ritchie DM. Variable-size arrays in C.
The Journal of C Language Translation 1990;2:81-86.

> An obvious convention is possible, and often occurs
> in practice: namely, the context parameter in the API
> function is given a name which exactly matches the
> one in the function pointer:
> 
>    void foo(void (cb1)(void *the_context, int a),
>             void *unrelated_data,
>             void *the_context);
> 
> Your proposal also assumes that closures must always
> be specified as two argument words, which is an
> inappropriate choice to foist onto programmers.

No it does not assume this. You can always keep
doing this as before.

> So that is to say, you're taking it for granted that
> you want to keep a distinct disadvantage of the
> current approach and just wrap it in some syntactic
> sugar machinery so that the two arguments appear
> encapsulated as some _Wide thing passed by value.
> 
> But callback interfaces can easily use just a single
> argument word to point to a object that is passed
> with reference semantics; that object has a function
> and context data.

I am not sure what you are talking about..

> In high level languages with closures, like Lisp
> dialects, closures are of the same "size" as other
> values; e.g. 32 or 64 bit pointer-sized word or
> what have you.
> 
> C++ callback objects are usually one pointer also.
> 
>     void f(CallBackBase *cb)
>     {
>        (*cb)(42); // via operator ()
>        // or
>        cb->callBack(42); // regular member function
>     }

Not at all. C++ has std::function for this purpose
which is usually even bigger than two words because
it does an optimization for small objects.

> C programs do this too:
> 
>     void f(struct obj *o)
>     {
>        o->ops->callback(o, 42);
>     }
> 
> Or with the ops table in the object instance:
> 
>     void f(struct obj *o)
>     {
>        o->ops.callback(o, 42);
>     }
> 
> The idea that all callback interfaces in C
> use a function and a void * argument is a false; you
> have to survey the entire field of practice in this general
> area.

I never claimed that "all callbacks use void*". So please
do not use strawman arguments.

> > void foo(void (_Wide cb1)(int a), void* other_data);
> 
> Without _Wide, we have
> 
>    void (cb1)(int a)
> 
> That (cb1)(int a) is a function declarator (with superfluous
> parentheses); but when that occurs as a parameter, it
> declares a function pointer: it is equivalent to:
> 
>    void (*cb1)(int a)
> 
> But it looks like
> 
>     void (_Wide cb1)(int)
> 
> might not be similarly equivalent to
> 
>     void (_Wide *cb1)(int)
> 
> and the difference could be useful.
>
> Furthermore, given that you can have a _Wide value W, or
> a pointer to a _Wide value PW, do you call them using different
> syntax?
> 
> Is it just W(arg, ...) and PW(arg, ....)?
> 
> Or do we require (*PW)(arg, ...)?
>
> If we don't require it, is it because of a decay rule, so that
> we can write PW(arg, ...) or (*PW)(arg, ...) or (**PW)(arg, ...)?
> and same with W? Or is it due to separately articulated semantics:
> that the function call operator works with a _Wide and a
> pointer-to_Wide, without any "decay" conversion going on?

If _Wide is a qualifier as in the proposal
Function types nothing changes.

Martin






More information about the Libffi-discuss mailing list