[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. ;-)