[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
returned.

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