[MLton] Callback to function pointer?

Matthew Fluet fluet@cs.cornell.edu
Thu, 14 Jul 2005 08:43:14 -0400 (EDT)


> On Wed, Jul 13, 2005 at 09:08:03AM -0400, Matthew Fluet wrote:
> > For FFI _import, we currently have the following:
> > 
> >   (* A. fetch a simple FFI object (when evaluated) *)
> >   _import "name" : ffiTy;
> 
> Another dumb FFI question: why can't we export a constant?
> 
> Essentially, _export "foo": 'a; gives 'a -> unit for a type, right?

For an "argTy -> resTy" type.  You can only export functions.

> So, _export "foo": int -> unit; returns a setter function for the symbol
> "foo" with type (int -> unit) -> unit. Furthermore, MLton will emit the
> symbol "foo" so that C can link against it. 

Not just the symbol, but a bona-fide C function named "foo".  It is this C 
function that knows how to get into the MLton runtime.

> What I wonder is why doesn't
> _export "foo": int; return a setter for the symbol "foo" with type 
> int -> unit?

Ultimately, because simple C types are fundamentally different from C 
function types.  Also, you can get much the same effect using 
MLton.Pointer functions:

 (_export "foo": int; 5)

 (let val ptr = _import # "foo": MLton.Pointer.t;
  in MLton.Pointer.setInt32 (ptr, 0, 5)
  end)

The difference between them would be who is ultimately responsible for 
ensuring that the symbol "foo" appears in the final executable.

> I can think of a couple cases where being able to set a C global variable
> would be useful. The only question is whether or not MLton should create
> the symbol as well as set it. Perhaps 'weak' symbols are useful here.
> 
> foo.c:
> int myglobal = 4;
> ...
> foo.sml:
> fun dowork x = (_export "myglobal": int; 5; more_stuff...)
> 
> Here the global variable from C gets set.

So, this is exactly in line with the MLton.Pointer functions.  You would 
import the address and then set the value.  I note that this idiom 
strongly suggests changing  _import #  to  _address, since it makes it 
clear that it makes available the address, suitable for both getting and 
setting.

I will note that:

  _import "foo": ffiTy;

is elaborated into:

  (let ptr = _address "foo": MLton.Pointer.t
   in MLton.Pointer.get???(ptr, 0)
   end)

And it would be fairly easy to get 

  _export "foo": ffiTy;

to elaborate in a similar fashion:

  (let ptr = _address "foo": MLton.Pointer.t
   in fn x => MLton.Pointer.set???(ptr, 0, x)
   end)

> The alternative, perhaps more in line with the word 'export', is for 
> MLton to create it's own "myglobal" symbol and set it. (like for functions)
> 
> foo.c:
> extern int myglobal;
> int myfun() { printf("%d\n", myglobal); return 0; }
> foo.sml:
> fun dowork x = (_export "myglobal": int; x; _import "myfun": unit -> int; ())

I agree that would be a little bit more inline with the word 'export'.  I 
don't see that it would be terribly difficult -- we could associate a 
CType.t option with the FFI_Symbol prim and ensure that we create the 
symbol with the right C-type.

An alternative response would be to note that 'import' and 'export' for 
non-functional types are really the wrong words, and instead we should 
have:

  _fetch "foo" : int;  ==>  let val ptr = _address "foo" : MLton.Pointer.t
                            in MLton.Pointer.getInt32(ptr, 0)
                            end

  _store "foo" : int;  ==>  let val ptr = _address "foo" : MLton.Pointer.t
                            in fn x => MLton.Pointer.setInt32(ptr, 0, x)
                            end

And the responsibility of ensuring that those symbols exist does not fall 
on MLton.  Again, the only advantage of _fetch/_store over the desugared 
counter-parts is that one can use opaque (but FFI compatible) types with 
_fetch/_store, but not with MLton.Pointer.* functions.

Note that _store, unlike _export, has no static component, so it is 
perfectly valid to put _store in a loop.  Again, more evidence to the fact 
that functions are so not first class in C that it is questionably how 
much we should try to map them to first class ML functions.

> An amusing example of abuse of this syntax:
> 
> local
>   val setter = _export "faster_sneaky_hack": Posix.IO.file_desc;
> in
>   fun getFD fd = (setter fd; _import "faster_sneaky_hack": int;)
> end

Much more efficient. ;-)