[erlang-questions] Type-tagged FFI interface

David Hopwood david.hopwood@REDACTED
Thu Sep 13 18:36:19 CEST 2007

Alceste Scalas wrote:
> Il giorno gio, 13/09/2007 alle 09.30 +0200, Raimo Niskanen ha scritto:
>> Oh, and I have a suggestion about the actual ffi:call/2,3 functions.
>> Personally I would prefer an interface with tagged values:
>>     ffi:call(Port, {ReturnType,Function}, [TaggedVal]) ->
>>             {ReturnType,term()}
>>         ReturnType = type_tag()
>>         TaggedVal  = {type_tag(),Val}
>>         Val        = term()
>>         type_tag() = uchar|schar|...|pointer|size_t|ssize_t

Tagging pointers makes sense, but the representation of a pointer
value need not be specified. Tagging integer types makes less sense,
I think; instead the FFI should check that the actual value of the
integer falls within the range of its C type (see below for an
interface that would support this).

>> e.g
>>     Pointer = {pointer,PointerVal} =
>>         ffi:call(Port, {pointer,malloc}, [{size_t,1024}]),
>>     void = ffi:call(Port, {void,free}, [Pointer]),
>> This would make the code more readable (resembling the C calls) IMHO,
>> and make the ffi values more self-contained; a return value can
>> directly be passed to another ffi function.
> A tagged interface would be *very* nice, and more type-safe --- but it
> would be a performance killer, too [1].
>   [1] The current FFI interface is tuple-based on purpose: it avoids
>       unrolling lists when counting function call parameters, and
>       Erlang terms are handled with very simple pointer arithmetics.

That problem can be solved by "compiling" each C function signature into
an Erlang function:

  ffi:c_function(port(), type_tag(), atom(), [type_tag()]) -> fun()
      type_tag() = uchar|schar|...|pointer|nonnull|size_t|ssize_t

  C_malloc = ffi:c_function(Port, nonnull, malloc, [size_t]),
  C_free   = ffi:c_function(Port, void, free, [pointer]),
  C_memset = ffi:c_function(Port, void, memset,
                                        [nonnull, int, size_t]),
  C_memcpy = ffi:c_function(Port, void, memcpy, [nonnull, nonnull, size_t]),

'nonnull' means a pointer that is checked by the FFI to be non-NULL,
i.e. an Erlang exception will be thrown if a NULL pointer is passed or

With the above definitions, the following code would be correct even though
the mallocs may fail:

  Src = C_malloc(1024),
  Dest = C_malloc(1024),
  void = C_memset(Src, 42, 1024),
  void = C_memcpy(Dest, Src, 1024),
  void = C_free(Src),
  void = C_free(Dest),

This could be implemented using one Erlang function definition for each
argument count (up to some maximum), closed over some private data
structure that holds the C type information.

> In general, every FFI function could have a "standard" and recommended
> version implemented in Erlang, that will support (and check) the type
> tags.  The "real FFI work" would be performed by the untagged_ BIFs.
> Developers may choose between the tagged and untagged interfaces,
> depending on the performance/safety/readability tradeoff they're looking
> for.

I don't think there's any need for a trade-off here. The cost of type
checking is negligable compared to the other overheads introduced by
this kind of interface (given that we need to do type conversions anyway).

David Hopwood <david.hopwood@REDACTED>

More information about the erlang-questions mailing list