calling SML from C

Stephen Weeks sweeks@intertrust.com
Mon, 13 Aug 2001 22:38:57 -0700


> coming from university I was quite happy, that my colleagues have a serious
> interest in using SML within our big industrial C++ project. The integration
> of SML programs would be very easy, if one could call SML from C. Will this
> ever be possible?

Here is a sketch of one way to do it with a few modifications the current
MLton (but almost no compiler mods).

First, MLton is currently hardwired to be responsible for program startup and
shutdown.  Both of those must be changed.

The startup code is in the main function at the bottom of include/x86-codegen.h.
This code does two distinct things: initialize the MLton world (which is
contained in the variable gcState), and jump to the instruction at the beginning
of the SML program.  This code could be changed so that it isn't called "main"
and so that the world initialization is split from the code that enters the SML
program.  Hence, these could be called separately, the idea being that you could
call the initialization code once from your C program, before making any SML
calls.  Then you could call the other chunk of code as many times as you need to
call SML functions.

The shutdown code is in basis-library/misc/suffix.sml, which calls
OS.Process.exit, which is defined in basis-library/system/process.sml, which in
turn calls Primitive.halt, which calls the C function MLton_exit, which is
defined in runtime/basis/MLton/exit.c, which calls GC_done, defined in
runtime/gc.c.  Whew.  The upshot is that suffix.sml gets tacked on to the end of
every program so that it will exit through OS.Process.exit.  This could be
changed to be some other code that nicely returns to the C caller.  

Then, we could export a new function from the MLton runtime that lets you shut
down the MLton world whenever you are done calling SML.

Next, we have to pass arguments to SML and return results to C.  The easiest way
to do that is to use globals, which can be accessed and set from the SML side
via MLton's FFI mechanism.

Finally, we have to tell MLton which function to run, and to make sure that
it's whole-program optimization (and dead-code elimination) is aware of the
external calls.  The easiest way to do this is to make your SML code do a
dispatch on some C global, and then call the appropriate SML function.

So, the SML code would look like the following.

------------------------------------------------------------
fun foo (n: int): int = n + 1

fun bar (y: real, z: int): real = y + Real.fromInt z

local
   val functionTag = _ffi "ML_functionTag": unit -> int;
   fun int0 () = _ffi "ML_int0": int;
   val setInt0 = _ffi "ML_setInt0": int -> unit;
   fun real0 () = _ffi "ML_real0": real;
   val setReal0 = _ffi "ML_setReal0": real -> unit;
in
   val _ =
      case functionTag () of
	 0 => setInt0 (foo (int0 ()))
       | 1 => setReal0 (bar (real0 (), int0 ()))
       | _ => raise Fail "invalid functionTag"
end
------------------------------------------------------------

And the C code would look like the following.

------------------------------------------------------------
#include <stdio.h>

typedef int Int;
typedef double Real;

Int ML_functionTag;

void ML_call ();
void ML_initialize ();
void ML_shutdown ();

Int ML_int0;

void ML_setInt0 (Int i) {
	ML_int0 = i;
}

Real ML_real0;

void ML_setReal0 (Real x) {
	ML_real0 = x;
}

Int foo (Int n) {
	ML_int0 = n;
	ML_functionTag = 0;
	ML_call();
	return ML_int0;
}

Real bar (Real y, Int z) {
	ML_real0 = y;
	ML_int0 = z;
	ML_functionTag = 1;
	ML_call();
	return ML_real0;
}

int main () {
	ML_initialize();
	/* do some C stuff */
	fprintf (stderr, "%d\n", foo(13));
	/* do some C stuff */
	fprintf (stderr, "%f\n", bar(13.0, 14));
	ML_shutdown();
	/* do some C stuff */
}
------------------------------------------------------------

Of course, you could automatically generate the C stubs and the SML dispatcher
from some simple description language (or even from SML val specs, if you wrote
them in a stylized format).

Comments invited.