[MLton] Callback functions: how?
Wesley W. Terpstra
wesley at terpstra.ca
Wed Feb 14 12:14:56 PST 2007
On Feb 14, 2007, at 8:36 PM, Matthew Fluet wrote:
> Wesley W. Terpstra wrote:
>> As I understand it, the code
>>> val fns : (int -> unit) list ref = ref [ fn _ => () ]
>>> val register f = fns := f :: !fns
>>> val runAll x = List.app (fn f => f x) (!fns)
>> will prevent flow-analysis and the runAll method will have a loop
>> over a giant switch statement that could call all possible
>> functions of type 'int -> unit'. Is this correct? Or can MLton
>> recognize that only methods passed to 'register' need to be in the
>> switch statement?
>
> I believe that MLton does something in between the two. That is,
> flow-analysis is (purposefully) conservative on functions that
> escape into mutable objects, but it does distinguish between
> escaping and non-escaping functions. So, you won't get a dispatch
> among all possible functions of type 'int -> unit'. On the other
> hand, if you had 'register1' and 'register2' that added to
> different refs, then you would get a dispatch among the set of
> functions passed to either 'register1' or 'register2'.
To help MLton out, I could create a locally defined type and add it
to the callbacks input. eg:
> local
> datatype secret = SECRET
> in
> fun register f = fns := (fn (SECRET, x) => f x) :: !fns
> end
Would this be enough to ensure that it caught exactly the right methods?
> There are ways of improving the precision, but they add analysis
> time and didn't demonstrate a lot of improvement; see the Cejtin/
> Jagannathan/Weeks; ESOP 2000 paper.
That's fine. This is by nature specific to MLton, so if I can make
the precision exact by using a compiler-specific trick, that's ok.
>> This idiom appears in enough C libraries that we should really
>> have a good solution for this in the FFI section of the wiki. If a
>> good solution is relatively complex, perhaps we should offer a
>> small library (I'l volunteer to write it). For my scenario, it's
>> quite important that this be as fast as possible---callbacks are
>> invoked inside a tight loop, and the callbacks themselves are very
>> simple.
>
> It's hard to come up with a general library, since the type of the
> callback functions often change from C library to C library.
I know there are other callback techniques. However, all C libraries
developed in the last while use this idiom. A good solution where we
can provide it seems fine to me.
> To complicate matters, some C callbacks don't give you a void* to
> hang callback specific data; I think John Reppy has mentioned that
> some OpenGL bindings are of that form. For that, we'd like to
> eventually support an "indirect export" mechanism:
> _export * : (int -> unit) -> MLton.Pointer.t;
I remember we talked about this before. However, I wonder if it's
truly necessary. If there is no user argument, the library probably
intends for you to hook exactly one function. In which case, you can
use the _export*_address trick explicitly for the one function. A
single ref cell will hold the SML method to call. Easy.
>> Also, what's the best known way to implement GrowingVector? I've
>> been using
>>> datatype 'a used = FREE of int | USED of 'a
>>> type 'a t = { free: int, buf: 'a used array }
>> where buf doubles in size when free = ~1. This isn't such a big
>> deal, since 'registercb' is rarely invoked compared to 'runOne'.
>> However, I'd like to know a better solution.
>
> That's pretty close to the resizeable-array.fun implementation in
> the MLton sources.
I imagine you don't have a free-list, though. ;-)
Vesa Karvonen wrote:
> Basically, there is a single exported ML callback function and a
> callback cache for each imported C function that takes callbacks.
> The key generated by the cache is given to the C side as the
> context pointer (uarg in your snippet).
This sounds pretty much the same as my solution, except that my hash
function is (fn x => x) and I don't need a collision policy.
More information about the MLton
mailing list