[MLton-devel] Callbacks

Stephen Weeks MLton@mlton.org
Mon, 19 May 2003 17:59:32 -0700


> Here is a proposal for a MLton.Callback structure to replace
> MLton.FFI.
...
> Thoughts? Comments?
...
> The vararg solution was surprisingly simple.  Now the client code looks
> like:
...

It all makes sense.  My one minor complaint is that the functions are
curried (I understand that they have to be due to the printf-style
stuff).  My major complaint is that there is too much work and room
for error on the C side.  I would prefer if the exported function
appeared to be a normal C function.

How about the following approach, which admittedly requires compiler
support, but I think not too much.

Similar to _ffi, add a new core-level expression 

	_export "<name>": <ty>;

The type of the _export expression is (<ty> -> unit), and it exports a
C function named <name>.  The _export declaration evaluates to a
function that when called with a function f, sets the meaning of the C
call to be f.  For example

	_export "A": int * int -> int; (fn (x1, x2) => x1 + x2)

>From this _export, MLton will construct the necessary C routine and C
prototype so that C code "printf ("%d\n", A(17, 18));" will type
check and will print "35".

Here's how it's implemented.  Early in the front end, MLton counts the
number of _export declarations and makes that number available via

	_build_const "MLton_numExports": int;

MLton also assigns a unique integer in [0, numExports) to each _export
declaration.  That integer will be used to access into the array of
exported functions.  I will continue with 

	_export "A": int * int -> int;

as a running example, assuming it has been assigned slot 13.

As part of elaboration, MLton builds a file z-export.h that contains
the prototypes of all the exported functions.  In our example, it
would contain

	int A (int x1, int x2);

MLton also builds the appropriate C routine for each export, which is
easy to do from the type.  Our example would generate

	int A (int x1, int x1) {
	        MLton_FFI_setI (0, 13);
	        MLton_FFI_setI (1, x1);
	        MLton_FFI_setI (2, x2);
	        MLton_callFromC ();
	        return MLton_FFI_getI (0);
	}

Finally, during elaboration, MLton replaces the export declaration
with the function that will set the appropriate entry in the exports
array.

	(fn f: int * int -> int =>
	 MLton.FFI.register
	 (13, fn () =>
          let
	     val x1 = MLton.FFI.getI 1
	     val x2 = MLton.FFI.getI 2
	     val _ = MLton.Thread.atomicEnd ()
	     val res = f (x1, x2)
	     val _ = MLton.Thread.atomicBegin ()
	     val _ = MLton.FFI.setI (0, res)
	  in
	     ()
	  end))

MLton.FFI implements the register function, which simply updates the
array and sets the callFromC handler to dispatch to the appropriate
function.

signature MLTON_FFI =
   sig
      val getI: int -> int
      val register: int * (unit -> unit) -> unit
      val setI: int * int -> unit
   end

structure MLtonFFI =
    struct
       val getI = "MLton_FFI_getI": int -> int;
       val numExported = _build_const "MLton_numExports": int;
       val setI = "MLton_FFI_setI": int * int -> unit
	 
      val register =
	 let
	    val exports = Array.array (numExported, fn () =>
				       raise Fail "undefined export\n")
	    val _ = MLtonThread.handleCallFromC 
                    (fn () => Array.sub (exports, getI 0) ())
	 in
	    fn (i, f) => Array.update (exports, i, f)
	 end
   end

That's it.  I know it's more compiler support, but the gain in
simplicity and error checking on the C side seems worth it.


-------------------------------------------------------
This SF.net email is sponsored by: ObjectStore.
If flattening out C++ or Java code to make your application fit in a
relational database is painful, don't do it! Check out ObjectStore.
Now part of Progress Software. http://www.objectstore.net/sourceforge
_______________________________________________
MLton-devel mailing list
MLton-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/mlton-devel