Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement lists:reverse/1,2 as nif #806

Merged
merged 1 commit into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 78 additions & 61 deletions libs/estdlib/src/lists.erl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
member/2,
delete/2,
reverse/1,
reverse/2,
foreach/2,
keydelete/3,
keyfind/3,
Expand Down Expand Up @@ -111,28 +112,63 @@ delete(E, L) ->

%% @private
delete(_, [], Accum) ->
reverse(Accum);
?MODULE:reverse(Accum);
delete(E, [E | T], Accum) ->
reverse(Accum) ++ T;
?MODULE:reverse(Accum) ++ T;
delete(E, [H | T], Accum) ->
delete(E, T, [H | Accum]).

%%-----------------------------------------------------------------------------
%% @param L the list to reverse
%% @returns the elements of L in reverse order
%% @doc Reverse the elements of L.
%% @equiv lists:reverse(L, [])
%% @doc Erlang/OTP implementation of this function actually handles few simple
%% cases and calls lists:reverse/2 for the more genertic case. Consequently,
%% calling `lists:reverse/1` without a list or with an improper list of two
%% elements will fail with a function clause exception on Erlang/OTP and with a
%% badarg exception with this implementation.
%% @end
%%-----------------------------------------------------------------------------
-spec reverse(list()) -> list().
reverse(L) ->
%% TODO this should be done in unit time in a BIF
reverse(L, []).
-spec reverse(L :: list()) -> list().
reverse(_L) ->
erlang:nif_error(undefined).

%% @private
reverse([], Accum) ->
Accum;
reverse([H | T], Accum) ->
reverse(T, [H | Accum]).
%%-----------------------------------------------------------------------------
%% @param L the list to reverse
%% @param T the tail to append to the reversed list
%% @returns the elements of L in reverse order followed by T
%% @doc Reverse the elements of L, folled by T.
%% If T is not a list or not a proper list, it is appended anyway and the result
%% will be an improper list.
%%
%% If L is not a proper list, the function fails with badarg.
%%
%% Following Erlang/OTP tradition, `lists:reverse/1,2` is a nif. It computes
%% the length and then allocates memory for the list at once (2 * n terms).
%%
%% While this is much faster with AtomVM as allocations are expensive with
%% default heap growth strategy, it can consume more memory until the list
%% passed is garbage collected, as opposed to a recursive implementation where
%% the process garbage collect part of the input list during the reversal.
%%
%% Consequently, tail-recursive implementations calling `lists:reverse/2`
%% can be as expensive or more expensive in memory than list comprehensions or
%% non-tail recursive versions depending on the number of terms saved on the
%% stack between calls.
%%
%% For example, a non-tail recursive join/2 implementation requires two terms
%% on stack for each iteration, so when it returns it will use
%% `n * 3' (stack) + `n * 4' (result list)
%% a tail recursive version will use, on last iteration:
%% `n * 4' (reversed list) + n * 4' (result list)
%% @end
%%-----------------------------------------------------------------------------
-spec reverse
(L :: nonempty_list(E), T :: list(E)) -> nonempty_list(E);
(L :: nonempty_list(), T :: any()) -> maybe_improper_list();
(L :: [], T) -> T when T :: any().
reverse(_L, _T) ->
erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param Fun the predicate to evaluate
Expand Down Expand Up @@ -162,13 +198,13 @@ keydelete(K, I, L) ->

%% @private
keydelete(_K, _I, [], L) ->
reverse(L);
?MODULE:reverse(L);
keydelete(K, I, [H | T], L2) when is_tuple(H) ->
case I =< tuple_size(H) of
true ->
case element(I, H) of
K ->
reverse(L2) ++ T;
?MODULE:reverse(L2, T);
_ ->
keydelete(K, I, T, [H | L2])
end;
Expand Down Expand Up @@ -256,7 +292,7 @@ keyreplace(K, I, [H | T], L, NewTuple, NewList) when is_tuple(H) andalso is_tupl
true ->
case element(I, H) of
K ->
reverse(NewList) ++ [NewTuple | T];
?MODULE:reverse(NewList, [NewTuple | T]);
_ ->
keyreplace(K, I, T, L, NewTuple, [H | NewList])
end;
Expand Down Expand Up @@ -298,7 +334,7 @@ foldl(Fun, Acc0, [H | T]) ->
List :: list()
) -> Acc1 :: term().
foldr(Fun, Acc0, List) ->
foldl(Fun, Acc0, reverse(List)).
foldl(Fun, Acc0, ?MODULE:reverse(List)).

%%-----------------------------------------------------------------------------
%% @param Fun the predicate to evaluate
Expand Down Expand Up @@ -381,19 +417,8 @@ search(Pred, [H | T]) ->
%% @end
%%-----------------------------------------------------------------------------
-spec filter(Pred :: fun((Elem :: term()) -> boolean()), List :: list()) -> list().
filter(Pred, L) ->
filter(Pred, L, []).

%% @private
filter(_Pred, [], Accum) ->
reverse(Accum);
filter(Pred, [H | T], Accum) ->
case Pred(H) of
true ->
filter(Pred, T, [H | Accum]);
_ ->
filter(Pred, T, Accum)
end.
filter(Pred, L) when is_function(Pred, 1) ->
[X || X <- L, Pred(X)].

%%-----------------------------------------------------------------------------
%% @param Sep the separator
Expand All @@ -403,16 +428,16 @@ filter(Pred, [H | T], Accum) ->
%% @end
%%-----------------------------------------------------------------------------
-spec join(Sep :: any(), List :: list()) -> list().
join(Sep, L) ->
join(L, Sep, []).
join(_Sep, []) ->
[];
join(Sep, [H | Tail]) ->
[H | join_1(Sep, Tail)].

%% @private
join([], _Sep, Accum) ->
lists:reverse(Accum);
join([E | R], Sep, []) ->
join(R, Sep, [E]);
join([E | R], Sep, Accum) ->
join(R, Sep, [E, Sep | Accum]).
join_1(Sep, [H | Tail]) ->
[Sep, H | join_1(Sep, Tail)];
join_1(_Sep, []) ->
[].

%%-----------------------------------------------------------------------------
%% @param From from integer
Expand All @@ -424,8 +449,8 @@ join([E | R], Sep, Accum) ->
%% @end
%%-----------------------------------------------------------------------------
-spec seq(From :: integer(), To :: integer()) -> list().
seq(From, To) ->
seq(From, To, 1).
seq(From, To) when is_integer(From) andalso is_integer(To) andalso From =< To ->
seq_r(From, To, 1, []).

%%-----------------------------------------------------------------------------
%% @param From from integer
Expand All @@ -447,16 +472,12 @@ seq(From, To, Incr) when
seq(To, To, 0) ->
[To];
seq(From, To, Incr) ->
seq(From, To, Incr, []).
Last = From + ((To - From) div Incr) * Incr,
seq_r(From, Last, Incr, []).

%% @private
seq(From, To, Incr, Accum) when
(Incr > 0 andalso From > To) orelse
(Incr < 0 andalso To > From)
->
reverse(Accum);
seq(From, To, Incr, Accum) ->
seq(From + Incr, To, Incr, [From | Accum]).
seq_r(From, From, _Incr, Acc) -> [From | Acc];
pguyot marked this conversation as resolved.
Show resolved Hide resolved
seq_r(From, To, Incr, Acc) -> seq_r(From, To - Incr, Incr, [To | Acc]).

%%-----------------------------------------------------------------------------
%% @param List a list
Expand Down Expand Up @@ -523,20 +544,16 @@ unique(Sorted) ->
unique(Sorted, fun(X, Y) -> X =< Y end).

%% @private
unique(Sorted, Fun) ->
unique(Sorted, Fun, []).

%% @private
unique([], _Fun, []) ->
unique([], _Fun) ->
[];
unique([X], _Fun, Acc) ->
lists:reverse([X | Acc]);
unique([X, Y | Tail], Fun, Acc) ->
unique([X], _Fun) ->
[X];
unique([X, Y | Tail], Fun) ->
case Fun(X, Y) andalso Fun(Y, X) of
true ->
unique([Y | Tail], Fun, Acc);
unique([Y | Tail], Fun);
false ->
unique([Y | Tail], Fun, [X | Acc])
[X | unique([Y | Tail], Fun)]
fadushin marked this conversation as resolved.
Show resolved Hide resolved
end.

%%-----------------------------------------------------------------------------
Expand All @@ -563,9 +580,9 @@ duplicate(Count, Elem, Acc) -> duplicate(Count - 1, Elem, [Elem | Acc]).
%%-----------------------------------------------------------------------------
-spec sublist([Elem], integer()) -> [Elem].
sublist(List, Len) when is_integer(Len) andalso Len >= 0 ->
sublist0(List, Len, []).
sublist0(List, Len).

%% @private
sublist0(_List, 0, Acc) -> reverse(Acc);
sublist0([], _, Acc) -> reverse(Acc);
sublist0([H | Tail], Len, Acc) -> sublist0(Tail, Len - 1, [H | Acc]).
sublist0([], _Len) -> [];
sublist0(_, 0) -> [];
sublist0([H | Tail], Len) -> [H | sublist0(Tail, Len - 1)].
fadushin marked this conversation as resolved.
Show resolved Hide resolved
34 changes: 34 additions & 0 deletions src/libAtomVM/nifs.c
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ static term nif_base64_encode_to_string(Context *ctx, int argc, term argv[]);
static term nif_base64_decode_to_string(Context *ctx, int argc, term argv[]);
static term nif_code_load_abs(Context *ctx, int argc, term argv[]);
static term nif_code_load_binary(Context *ctx, int argc, term argv[]);
static term nif_lists_reverse(Context *ctx, int argc, term argv[]);
static term nif_maps_next(Context *ctx, int argc, term argv[]);
static term nif_unicode_characters_to_list(Context *ctx, int argc, term argv[]);
static term nif_unicode_characters_to_binary(Context *ctx, int argc, term argv[]);
Expand Down Expand Up @@ -694,6 +695,11 @@ static const struct Nif code_load_binary_nif =
.base.type = NIFFunctionType,
.nif_ptr = nif_code_load_binary
};
static const struct Nif lists_reverse_nif =
{
.base.type = NIFFunctionType,
.nif_ptr = nif_lists_reverse
};
static const struct Nif maps_next_nif =
{
.base.type = NIFFunctionType,
Expand Down Expand Up @@ -4242,6 +4248,34 @@ static term nif_code_load_binary(Context *ctx, int argc, term argv[])
return result;
}

static term nif_lists_reverse(Context *ctx, int argc, term argv[])
{
// Compared to erlang version, compute the length of the list and allocate
// at once the space for the reverse.
int proper;
size_t len = term_list_length(argv[0], &proper);
if (UNLIKELY(!proper)) {
RAISE_ERROR(BADARG_ATOM);
pguyot marked this conversation as resolved.
Show resolved Hide resolved
}

if (UNLIKELY(memory_ensure_free_with_roots(ctx, len * CONS_SIZE, 2, argv, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
fadushin marked this conversation as resolved.
Show resolved Hide resolved
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
}

term result = term_nil();
if (argc == 2) {
result = argv[1];
}
term list_crsr = argv[0];
while (!term_is_nil(list_crsr)) {
// term is a proper list as verified above
term *list_ptr = term_get_list_ptr(list_crsr);
result = term_list_prepend(list_ptr[LIST_HEAD_INDEX], result, &ctx->heap);
list_crsr = list_ptr[LIST_TAIL_INDEX];
}
return result;
}

static term nif_maps_next(Context *ctx, int argc, term argv[])
{
UNUSED(argc);
Expand Down
2 changes: 2 additions & 0 deletions src/libAtomVM/nifs.gperf
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ base64:encode/1, &base64_encode_nif
base64:decode/1, &base64_decode_nif
base64:encode_to_string/1, &base64_encode_to_string_nif
base64:decode_to_string/1, &base64_decode_to_string_nif
lists:reverse/1, &lists_reverse_nif
lists:reverse/2, &lists_reverse_nif
maps:next/1, &maps_next_nif
unicode:characters_to_list/1, &unicode_characters_to_list_nif
unicode:characters_to_list/2, &unicode_characters_to_list_nif
Expand Down
7 changes: 3 additions & 4 deletions src/libAtomVM/opcodesswitch.h
Original file line number Diff line number Diff line change
Expand Up @@ -3040,11 +3040,10 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
#ifdef IMPL_EXECUTE_LOOP
TRACE("get_list/3 %lx, %c%i, %c%i\n", src_value, T_DEST_REG(head_dreg), T_DEST_REG(tail_dreg));

term head = term_get_list_head(src_value);
term tail = term_get_list_tail(src_value);
term *list_ptr = term_get_list_ptr(src_value);

WRITE_REGISTER(head_dreg, head);
WRITE_REGISTER(tail_dreg, tail);
WRITE_REGISTER(head_dreg, list_ptr[LIST_HEAD_INDEX]);
WRITE_REGISTER(tail_dreg, list_ptr[LIST_TAIL_INDEX]);
#endif

#ifdef IMPL_CODE_LOADER
Expand Down
9 changes: 6 additions & 3 deletions src/libAtomVM/term.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ extern "C" {
#define CONS_SIZE 2
#define REFC_BINARY_CONS_OFFSET 4

#define LIST_HEAD_INDEX 1
#define LIST_TAIL_INDEX 0

#define TERM_BINARY_SIZE_IS_HEAP(size) ((size) < REFC_BINARY_MIN)

#if TERM_BYTES == 4
Expand Down Expand Up @@ -1288,7 +1291,7 @@ static inline term term_list_from_list_ptr(term *list_elem)
static inline term term_get_list_head(term t)
{
term *list_ptr = term_get_list_ptr(t);
return list_ptr[1];
return list_ptr[LIST_HEAD_INDEX];
}

/**
Expand All @@ -1300,7 +1303,7 @@ static inline term term_get_list_head(term t)
static inline term term_get_list_tail(term t)
{
term *list_ptr = term_get_list_ptr(t);
return *list_ptr;
return list_ptr[LIST_TAIL_INDEX];
}

/**
Expand All @@ -1312,7 +1315,7 @@ static inline term term_get_list_tail(term t)
*/
MALLOC_LIKE static inline term *term_list_alloc(Heap *heap)
{
return memory_heap_alloc(heap, 2);
return memory_heap_alloc(heap, CONS_SIZE);
}

/**
Expand Down
2 changes: 2 additions & 0 deletions tests/erlang_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ compile_erlang(test_list_processes)
compile_erlang(test_tl)
compile_erlang(test_list_to_atom)
compile_erlang(test_list_to_existing_atom)
compile_erlang(test_lists_reverse)
compile_erlang(test_binary_to_atom)
compile_erlang(test_binary_to_existing_atom)
compile_erlang(test_atom_to_list)
Expand Down Expand Up @@ -577,6 +578,7 @@ add_custom_target(erlang_test_modules DEPENDS
test_tl.beam
test_list_to_atom.beam
test_list_to_existing_atom.beam
test_lists_reverse.beam
test_binary_to_atom.beam
test_binary_to_existing_atom.beam
test_atom_to_list.beam
Expand Down
Loading
Loading