This note outlines the steps required to add ML callable C code
to the run-time system.


THE ORGANIZATION OF C LIBRARIES
-------------------------------

The various C functions that are callable from ML are organized into
several independent libraries.  These can be found in the directory
src/runtime/c-libs.  Each library lives in its own sub-directory,
which contains a makefile, an include file that lists the C functions
in the library, a root ".c" file that defines the table of C functions
in the library, and files for each of the library functions.


WRITING ML CALLABLE C CODE
--------------------------

In the current system, a C function that is callable from ML must deal
with any conversions between ML and C representations.  These functions
have the standard C prototype:

  ml_val_t cfun (ml_state_t *msp, ml_val_t arg);

They take a pointer to the ML state vector (needed for doing allocation)
and a single argument, which is in ML representation, and return a result,
which is also in ML representation.  The runtime system provides include
files (ml-values.h and ml-objects.h) that provide operations for doing
these conversions.  To illustrate, consider a C function that will swap
the members of a pair; it has the ML type ('a * 'b) -> ('b * 'a).

    ml_val_t _ml_swap (ml_state_t *msp, ml_val_t arg)
    {
	ml_val_t	a = REC_SEL(arg, 0);
	ml_val_t	b = REC_SEL(arg, 1);
	ml_val_t	res;

	REC_ALLOC2 (msp, res, b, a);

	return res;
    }

The REC_SEL macro selects a field of a record, and the REC_ALLOC2 macro
allocates a pair.  When ML code calls a C function, the run-time system
first ensures that there is at least 8k bytes of free space in the
allocation arena.  If you C code needs to allocate more, then you may
need to invoke the garbage collector.  [This will be documented in a
later version of this note]

NOTE: these allocation operations are CPP macros, and thus, must be used with
care.


ADDING C CODE TO THE RUNTIME
----------------------------

To add ML callable C code to the SML/NJ run-time requires several steps:

  1) create a directory (say "mylib") for your library.  The easiest place
     to put this is in the runtime/c-libs directory.

  2) write the C functions that provide the C/ML interface, as described
     above.

  3) copy and modify the template files (makefile, cfun-proto-list.h,
     cfun-list.h, and library-template.c) into your directory.  The file
     runtime/c-libs/templates/README contains instructions.

  4) add your library to the list in the file runtime/c-libs/clib-list.h.

  5) add a make rule to runtime/objs/makefile (the library makerules are
     at the bottom of the file).  You can also add your library to the
     CLIBS target list in the makefile, or you can define XCLIBS in the
     root makefile for your version of the run-time system.

Now you should be able to recompile the runtime system to build a version
that includes your library.  When invoking SML, you can specify that you
want to use your own runtime as follows:

  sml @SMLrun=MyRuntime

This will allow you to use existing SML heap images with your new run-time
system.


BINDING C FUNCTIONS IN ML
-------------------------
Once you have added your own C library to the run-time system, you
need to define ML bindings for the C functions.  This is done using
the function System.Unsafe.CInterface.c_function, which has the type:

  val c_function : string -> string -> ('a -> 'b)

The first string argument is the Library name (e.g., "SMLNJ-IO"), and
the second argument is the function's symbolic name (e.g., "close").
The function that is returned has no type constraint, so you must
restrict its type in your binding.  For example:

    val swap : ('a * 'b) -> ('b * 'a) =
	  System.Unsafe.CInterface.c_function "MyLib" "swap"

At this point, you are ready to use your C library from ML.  Note that
if you export a heap image at this point, you will need to use your
version of the run-time system to load the image later.


