Packages

Erlang CFFI — call C shared libraries from Erlang via libffi

Current section

Files

Jump to
cffi src cffi_type.erl
Raw

src/cffi_type.erl

%% cffi_type.erl — Type registry and C struct layout engine.
%%
%% Stores struct/union/enum/typedef definitions in a named ETS table.
%% Layout (field offsets, padding, total size) is computed once at
%% definition time using System V AMD64 ABI alignment rules.
%%
%% Supported type specs:
%%
%% Primitives : void | int8 | uint8 | int16 | uint16 | int32 | uint32
%% | int64 | uint64 | float | double | pointer | string | bool
%%
%% Composites : {array, ElemType, N} — fixed-size C array
%% | {ptr, PointeeType} — typed pointer (same as pointer)
%% | SomeName — named registered type
%%
%% The table is created lazily on first access; no OTP application needed.
-module(cffi_type).
-export([
%% Registration
defcstruct/2, %% (Name, [{FieldName, TypeSpec}])
defcunion/2, %% (Name, [{FieldName, TypeSpec}])
defcenum/2, %% (Name, [Atom] | [{Atom, Int}])
defctype/2, %% (Name, TypeSpec) — typedef alias
%% Query
lookup/1, %% Name -> TypeInfo | not_found
sizeof/1, %% TypeSpec -> Bytes
alignof/1, %% TypeSpec -> Bytes
field_info/2, %% (StructName, FieldName) -> {Type, Offset} | not_found
enum_to_int/2, %% (EnumName, Atom) -> integer()
enum_to_atom/2 %% (EnumName, Int) -> atom() | Int
]).
-define(TABLE, cffi_type_registry).
%% -------------------------------------------------------------------------
%% Internal: ETS lifecycle
%% -------------------------------------------------------------------------
ensure_table() ->
try ets:new(?TABLE, [named_table, public, set, {read_concurrency, true}])
catch error:badarg -> ok %% already exists
end.
put_type(Name, Info) ->
ensure_table(),
ets:insert(?TABLE, {Name, Info}),
ok.
%% -------------------------------------------------------------------------
%% Registration API
%% -------------------------------------------------------------------------
%% defcstruct(Name, [{FieldName, TypeSpec}]) -> ok
-spec defcstruct(atom(), [{atom(), term()}]) -> ok.
defcstruct(Name, Fields) ->
{Size, Align, LayoutFields} = compute_struct_layout(Fields),
put_type(Name, {struct, Size, Align, LayoutFields}).
%% defcunion(Name, [{FieldName, TypeSpec}]) -> ok
-spec defcunion(atom(), [{atom(), term()}]) -> ok.
defcunion(Name, Fields) ->
{Size, Align, LayoutFields} = compute_union_layout(Fields),
put_type(Name, {union, Size, Align, LayoutFields}).
%% defcenum(Name, [Atom] | [{Atom, Int}]) -> ok
-spec defcenum(atom(), list()) -> ok.
defcenum(Name, Values) ->
Pairs = normalize_enum(Values),
ToInt = maps:from_list(Pairs),
ToAtom = maps:from_list([{V, K} || {K, V} <- Pairs]),
put_type(Name, {enum, ToInt, ToAtom}).
%% defctype(Name, TypeSpec) -> ok
-spec defctype(atom(), term()) -> ok.
defctype(Name, TypeSpec) ->
put_type(Name, {typedef, TypeSpec}).
%% -------------------------------------------------------------------------
%% Query API
%% -------------------------------------------------------------------------
-spec lookup(atom()) -> term() | not_found.
lookup(Name) ->
ensure_table(),
case ets:lookup(?TABLE, Name) of
[{Name, Info}] -> Info;
[] -> not_found
end.
%% sizeof(TypeSpec) -> non_neg_integer()
-spec sizeof(term()) -> non_neg_integer().
sizeof({array, T, N}) -> sizeof(T) * N;
sizeof({ptr, _}) -> sizeof(pointer);
sizeof(T) when is_atom(T) ->
case prim_size(T) of
undefined ->
case lookup(T) of
{struct, S, _, _} -> S;
{union, S, _, _} -> S;
{typedef, Target} -> sizeof(Target);
{enum, _, _} -> sizeof(int32); %% enums are int32 by default
not_found -> error({unknown_type, T})
end;
S -> S
end.
%% alignof(TypeSpec) -> pos_integer()
-spec alignof(term()) -> pos_integer().
alignof({array, T, _N}) -> alignof(T);
alignof({ptr, _}) -> alignof(pointer);
alignof(T) when is_atom(T) ->
case prim_align(T) of
undefined ->
case lookup(T) of
{struct, _, A, _} -> A;
{union, _, A, _} -> A;
{typedef, Target} -> alignof(Target);
{enum, _, _} -> alignof(int32);
not_found -> error({unknown_type, T})
end;
A -> A
end.
%% field_info(StructOrUnionName, FieldName) -> {TypeSpec, Offset} | not_found
-spec field_info(atom(), atom()) -> {term(), non_neg_integer()} | not_found.
field_info(TypeName, FieldName) ->
case lookup(TypeName) of
{struct, _, _, Fields} -> find_field(Fields, FieldName);
{union, _, _, Fields} -> find_field(Fields, FieldName);
_ -> not_found
end.
%% enum_to_int(EnumName, Atom) -> integer()
-spec enum_to_int(atom(), atom()) -> integer().
enum_to_int(EnumName, Atom) ->
{enum, ToInt, _} = lookup(EnumName),
case maps:find(Atom, ToInt) of
{ok, V} -> V;
error -> error({bad_enum_value, EnumName, Atom})
end.
%% enum_to_atom(EnumName, Int) -> atom() | Int
-spec enum_to_atom(atom(), integer()) -> atom() | integer().
enum_to_atom(EnumName, Int) ->
{enum, _, ToAtom} = lookup(EnumName),
maps:get(Int, ToAtom, Int).
%% -------------------------------------------------------------------------
%% Layout computation
%% -------------------------------------------------------------------------
compute_struct_layout(Fields) ->
{FinalOff, MaxAlign, RevFields} =
lists:foldl(fun({FName, FType}, {Off, MA, Acc}) ->
FAlign = alignof(FType),
FSize = sizeof(FType),
AlignedOff = align_up(Off, FAlign),
{AlignedOff + FSize, max(MA, FAlign), [{FName, FType, AlignedOff} | Acc]}
end, {0, 1, []}, Fields),
TotalSize = align_up(FinalOff, MaxAlign),
{TotalSize, MaxAlign, lists:reverse(RevFields)}.
compute_union_layout(Fields) ->
{MaxSize, MaxAlign, RevFields} =
lists:foldl(fun({FName, FType}, {MS, MA, Acc}) ->
{max(MS, sizeof(FType)), max(MA, alignof(FType)),
[{FName, FType, 0} | Acc]}
end, {0, 1, []}, Fields),
TotalSize = align_up(MaxSize, MaxAlign),
{TotalSize, MaxAlign, lists:reverse(RevFields)}.
%% -------------------------------------------------------------------------
%% Helpers
%% -------------------------------------------------------------------------
align_up(N, 1) -> N;
align_up(N, A) -> (N + A - 1) band (bnot (A - 1)).
find_field([], _) -> not_found;
find_field([{Name, Type, Offset} | _], Name) -> {Type, Offset};
find_field([_ | Rest], Name) -> find_field(Rest, Name).
normalize_enum(Values) ->
{Pairs, _} = lists:mapfoldl(
fun(A, I) when is_atom(A) -> {{A, I}, I + 1};
({A, V}, _I) when is_atom(A) -> {{A, V}, V + 1}
end, 0, Values),
Pairs.
%% Primitive sizes (bytes). Returns undefined for unknown atoms.
prim_size(void) -> 0;
prim_size(bool) -> 1;
prim_size(int8) -> 1; prim_size(uint8) -> 1;
prim_size(int16) -> 2; prim_size(uint16) -> 2;
prim_size(int32) -> 4; prim_size(uint32) -> 4;
prim_size(int64) -> 8; prim_size(uint64) -> 8;
prim_size(float) -> 4;
prim_size(double) -> 8;
prim_size(pointer) -> 8;
prim_size(string) -> 8;
prim_size(_) -> undefined.
%% Primitive alignments (equal to size for all standard C types on x86-64).
prim_align(void) -> 1;
prim_align(bool) -> 1;
prim_align(T) ->
case prim_size(T) of
undefined -> undefined;
0 -> 1;
S -> S
end.