[MLton-user] Calling C functions with a variable number of arguments

Matthew Fluet matthew.fluet at gmail.com
Wed Sep 1 06:35:29 PDT 2010


On Tue, Aug 31, 2010 at 9:21 AM, Phil Clayton <phil.clayton at lineone.net> wrote:
> Thanks for the link - I read with great interest.  I would like to explore
> NLFFI further but in the short term I am tied to using MLton's _import
> directly.
>
> To get a feel what direction I should be heading in, it would be useful to
> known whether there are any plans afoot to support varargs for _import -
> just variability up to compile-time, not at run-time.

There are no immediate plans to support varargs for _import.  I think
that It would require a fairly serious reworking of the type checker
in order to support varargs for _import.

> The capability appears almost there in MLton: we can already write e.g.
>
>  val fa = _import "f" : t -> unit;
>  val fb = _import "f" : t * b2 -> unit;
>  val fc = _import "f" : t * c2 * c1 -> unit;
>
> i.e. any number of different calling signatures to the C function f and use
> fa, fb, fc, etc. as required.

That's true.  And it is probably the best approach.  If you can
enumerate (or automatically generate) a suitably large number of
distinct _import expressions, then you could probably hide them in a
module, require the user to perform the injection into the 'arg' type
and then dispatch to the correct _import expression:

structure FVarArg :
sig
  datatype arg = SChar of Int8.int | UChar of Word8.word | ... |
Double of Real64.real
  val f : arg list -> unit
end =
struct
  datatype arg = SChar of Int8.int | UChar of Word8.word | ... |
Double of Real64.real
  fun f [] = (_import "f" : unit -> unit) ()
    | f [SChar a1] = (_import "f" : Int8.int -> unit) (a1)
    | f [UChar a1] = (_import "f" : Word8.word -> unit) (a1)
    ...
    | f [Double a1] = (_import "f" : Real64.real -> unit) (a1)
    | f [SChar a1, SChar a2] = (_import "f" : Int8.int * Int8.int ->
unit) (a1, a2)
    | f [SChar a1, UChar a2] = (_import "f" : Int8.int * Word8.word ->
unit) (a1, a2)
    ...
    | f [SChar a1, Double a2] = (_import "f" : Int8.int * Real64.real
-> unit) (a1, a2)
    ...
    | f _ => raise Fail "Unsupported argument list."
end

Of course, this suffers from a combinatorial explosion in code size,
and will always be limited to a finite number of possible argument
lists.

You could combine this with the Danvy-style typings technique
discussed in the ml-varargs paper to get a more palatable interface.

> In order to provide general purpose libraries that don't tie down the number
> of arguments (until compile-time), it would be nice to be able to write e.g.
>
>  val f = _import "f" : t -> ... -> unit;
>
> and just use f, letting type inference fill in the types of the missing
> curried arguments.  That could be awkward as '...' can't be represented by a
> type variable, so perhaps e.g.
>
>  val f = _import "f" : t -> 'a -> unit;
>
> where 'a must be a nested tuple, e.g. bool * (real * (int * unit)).

It's trickier than that.  The type checker requires _import have a
ground type --- no type variables, and, furthermore, that all of the
types are types compatible with the C interface (e.g., no tuple or
datatype types).  It is also trickier because for each distinct
instantiation of such a varargs _import, we would need to introduce a
new ground _import.  (That is very different than what happens when a
standard HM-polymorphic value is instantiated.)

> With an eye to constructing type safe interfaces (as described in John et
> al.'s paper, section 3), it is is worth noting that it is easy to convert
> such a function on nested pairs to have curried arguments:
>
>  fun curry f x y = f (x, y)
>  fun na x = x              (* no arg *)
>  fun va f g = f o curry g  (* var arg *)
>  fun va_call spec = spec (fn f => f ())
>
> Then we have
>
>  va_call na;  (* (unit -> 'a) -> 'a *)
>  va_call va;  (* ('b * unit -> 'a) -> 'b -> 'a  *)
>  va_call (va o va);
>               (* ('c * ('b * unit) -> 'a) -> 'c -> 'b -> 'a *)
>  va_call (va o va o va);
>               (* ... *)
>  etc.
>
> (Here the last argument has type unit as there could be no arguments.)
>
> Phil
>
>
> John Reppy wrote:
>>
>> We support varargs in SML/NJ using a technique described in a 2008 ML
>> Workshop
>> paper.  A key feature of our approach is that it should be possible to use
>> our
>> implementation in any ML system that supports C calls.  You can find a
>> copy of
>> the paper at
>>
>>        http://people.cs.uchicago.edu/~jhr/papers/2008/ml-varargs.pdf
>>
>> On Jul 26, 2010, at 2:00 PM, mlton-user-request at mlton.org wrote:
>>>
>>> Hi,
>>>
>>> I have been using the FFI and have found it very flexible but have run
>>> into a problem that I can't see how to solve.  I am trying to call a C
>>> function with a variable number of arguments but need the variability in the
>>> number of arguments available in SML too.
>>>
>>> The attached examples show that there is no problem importing a C
>>> function with a variable argument list for a fixed number of arguments: the
>>> files 'call_sum_<N>.sml' import the C function 'sum' with N arguments for
>>> the variable argument list.  I need to do something like
>>>
>>>  val sum = _import "sum" : int * int list -> int;
>>>
>>> (I am happy for the variable arguments to have the same type in SML.)
>>> Does anyone have any ideas about how to achieve a similar effect?
>>>
>>> Thanks,
>>> Phil
>>
>>
>
>
>
>
> _______________________________________________
> MLton-user mailing list
> MLton-user at mlton.org
> http://mlton.org/mailman/listinfo/mlton-user
>



More information about the MLton-user mailing list