[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