[MLton] Support for link options in ML Basis files

Matthew Fluet fluet@cs.cornell.edu
Tue, 18 Jan 2005 13:39:35 -0500 (EST)


> > > Can we avoid portability problems by automatically generating a
> > > wrapper to translate between the calling convention we understand
> > > (n-ary C functions with basic types) and the calling convention we
> > > don't (passing C structs)?
> >
> > That is an option.
> ...
> > The problem is that on the ML side, ML-NLFFI always passes structs/unions
> > around as pointers.
>
> The approach I proposed seems easier, although both work.  I think the
> difference is in who allocates space for the struct.  In my approach,
> the struct is allocated on the stack on the C side and then
> immediately flattened to pass to the funtion.  In their approach, if I
> understand correctly, the struct is allocated on the SML side (using
> malloc? or SML/NJ alloc?) and then flattened on the C side to pass to
> the function.

I think you understand correctly, but you are also missing the bigger
scenario.  Suppose I have a c file like:

structs.c:

#include <stdlib.h>
#include <stdio.h>

struct foo {
  int i;
  double j;
};

typedef struct foo foo_str;

void print_foo_str (foo_str foo) {
  printf ("  C::print_foo_str:  foo.i = %d  foo.j = %f\n",
          foo.i, foo.j);
}

foo_str make_foo_str (int i, double j) {
  foo_str f;
  printf ("  C::make_foo_str:       i = %d      j = %f\n", i, j);
  f.i = i;
  f.j = j;
  return f;
}

Then, I will get, among other things, the following files/structures:

f-print_foo_str.sml:
structure F_print_foo_str : sig
    val typ :
        (((ST_foo.tag, ro) su_obj' -> unit) fptr,
         (ST_foo.tag, ro) su_obj' -> unit) T.typ
    val fptr : unit -> ((ST_foo.tag, ro) su_obj' -> unit) fptr
    val f : (ST_foo.tag, 'c) su_obj -> unit
    val f' : (ST_foo.tag, 'c) su_obj' -> unit
end = struct
    ...
end

f-make_foo_str.sml:
structure F_make_foo_str : sig
    val typ :
        (((ST_foo.tag, rw) su_obj' * sint * double ->
              (ST_foo.tag, rw) su_obj') fptr,
         (ST_foo.tag, rw) su_obj' * sint * double ->
             (ST_foo.tag, rw) su_obj') T.typ
    val fptr :
        unit ->
            ((ST_foo.tag, rw) su_obj' * sint * double ->
                 (ST_foo.tag, rw) su_obj') fptr
    val f :
        (ST_foo.tag, rw) su_obj * MLRep.Int.Signed.int * MLRep.Double.real ->
            (ST_foo.tag, rw) su_obj
    val f' :
        (ST_foo.tag, rw) su_obj' *
        MLRep.Int.Signed.int *
        MLRep.Double.real -> (ST_foo.tag, rw) su_obj'
end = struct
    ...
end

Note that the function I get to call, which had C-prototype:
  void print_foo_str (foo_str foo);
is available with ML-type:
  val f : (ST_foo.tag, 'c) su_obj -> unit
not with ML-type:
  val f : {i : Int32.int, d : Real64.real} -> unit

So, to call the ML function, you have to have a (ST_foo.tag, 'c) su_obj,
which you might get from the translation of make_foo_str.

The ML-NLFFI Library represents (ST_foo.tag, 'c) su_obj as the address of
the allocated structure (the two type arguments are phantom).

The point being that when I call print_foo_str, the structure is already
allocated (in a malloc-ed area of memory) and I only have the pointer to
the structure.

> Hopefully my approach will be faster because gcc can
> optimize away the struct entirely, and just shuffle the args to
> translate between the n-ary calling convention and the struct calling
> convention.

I don't know if it will be necessarily faster, because you will first need
to extract each field from the (pointer to the) struct, which are then
shuffled to translate between the n-ary calling convention and the struct
calling convention.

> I guess the problem is that you will have to modify ML-NLFFI to do
> this.

To accomplish what you have in mind, on the SML side, you want to
represent structures as SML records.  I'm not sure that you can preserve
the ML-NLFFI interface to support that representation.