[MLton] Callback to function pointer?
Wesley W. Terpstra
wesley@terpstra.ca
Thu, 14 Jul 2005 16:46:48 +0200
On Thu, Jul 14, 2005 at 08:43:14AM -0400, Matthew Fluet wrote:
> > What I wonder is why doesn't _export "foo": int; return a setter for the
> > symbol "foo" with type int -> unit?
I was trying ask the abstract question, ignoring the fact that the
underlying implementation details for the two cases differ.
> Ultimately, because simple C types are fundamentally different from C
> function types. Also, you can get much the same effect using
> MLton.Pointer functions:
>
> (let val ptr = _import # "foo": MLton.Pointer.t;
> in MLton.Pointer.setInt32 (ptr, 0, 5)
> end)
However, as you mentioned, that doesn't have type transparency.
> 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 agree.
> _import "foo": ffiTy;
>
> is elaborated into:
>
> (let ptr = _address "foo": MLton.Pointer.t
> in MLton.Pointer.get???(ptr, 0)
> end)
But, that's after the type checking, right?
So, although that works inside MLton, that does not work for the user.
> _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)
I think this is the wrong name...
Perhaps _store, like you suggest later.
_export to me strongly suggests that the symbol name is created.
> > 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.
Then perhaps _export could just begin accepting non-arrow types?
> An alternative response would be to note that 'import' and 'export' for
> non-functional types are really the wrong words
At first I disagreed with you.
Then I realized that, as you said earlier, _import is different for
functions and for values. The key difference is that in C, a value is
really a 'ref value', so that we need to fetch it repeatedly. Functions
are just 'normal' from an SML point of view (ie: unchanging) and so only
need to be fetched once.
In fact, _import * has absolutely nothing to do with importing of anything.
What it really does is create bridge code to call via a function pointer.
Nevertheless, I'm not sure the distinction merits user-visibility.
> _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
These make sense and certainly convey no implication of creating the symbol.
However, I think it would be better if they operated on MLton.Pointer.t
instead of a given symbol. This is strictly more powerful.
> 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.
... which I think is fairly important for practical FFI use.
> Note that _store, unlike _export, has no static component, so it is
> perfectly valid to put _store in a loop.
It's also perfectly valid to put _export in a loop, as long as you are
prepared to have it overwrite the "symbol's value" each time.
> 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.
I think this comment is in error; functions are the only SML-like thing in
C. It's C values that are all screwy, being references as they are.
_address "symbol"; ==> MLton.Pointer.t
I didn't really understand why one needs multiple types?
Is always returning MLton.Pointer.t a bad thing?
_store *: 'a; ==> (MLton.Pointer.t * 'a) -> unit
_store "symbol": 'a; ==> 'a -> unit
'a must be of non-arrow FFI type
result is a normal first-class SML function
_import *: 'a; ==> MLton.Pointer.t -> 'a
_import "symbol": 'a; ==> 'a
reads the named object 'immediately'
if 'a is of non-arrow FFI type, grab result immediately
if 'a if of arrow FFI type, returns SML glue code that
calls the symbol as it points to a C function
_export "symbol": 'a; ==> 'a -> unit
creates the named symbol
for non-arrow types, otherwise identical to _store "symbol"
for arrow types, a gateway function is emitted, and a SML function
is returned which set's the SML function called by the gateway
What do you think?
An aside, I think _import * and _store * are vastly superior to the
MLton.Pointer.setWord* family of functions. In fact, MLton.Pointer.get*
is completely superfluous even as things stand now, AFAICS.
After writing that summary, I noticed something that surprised me.
MLton can already do the "faster_sneaky_hack" in the opposite direction:
sneaky.c:
int faster_sneaky_hack;
sneaky.sml:
fun coerce fd =
let
val ptr = _import # "faster_sneaky_hack"; (* or _address *)
in
MLton.Pointer.setInt32 (ptr, 0, Int32.fromInt fd);
_import "faster_sneaky_hack": Posix.IO.file_desc;
end
The only reason that one can't go the other way is that MLton.Pointer.set* is
insufficiently powerful... hence _store. The other problem, needing sneaky.c
just to create the symbol is fixed by extending _export.
--
Wesley W. Terpstra