Skip to content

Commit 603ca1e

Browse files
committed
Implement lists:reverse/1,2 as a nif
Also rewrite several lists module functions to not use this nif, either because it was not necessary (lists:seq/2,3) or because a non-tail recursive loop is more efficient memory-wise. Signed-off-by: Paul Guyot <pguyot@kallisys.net>
1 parent 54e7a51 commit 603ca1e

File tree

9 files changed

+198
-81
lines changed

9 files changed

+198
-81
lines changed

libs/estdlib/src/lists.erl

Lines changed: 71 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
member/2,
3434
delete/2,
3535
reverse/1,
36+
reverse/2,
3637
foreach/2,
3738
keydelete/3,
3839
keyfind/3,
@@ -111,28 +112,56 @@ delete(E, L) ->
111112

112113
%% @private
113114
delete(_, [], Accum) ->
114-
reverse(Accum);
115+
?MODULE:reverse(Accum);
115116
delete(E, [E | T], Accum) ->
116-
reverse(Accum) ++ T;
117+
?MODULE:reverse(Accum) ++ T;
117118
delete(E, [H | T], Accum) ->
118119
delete(E, T, [H | Accum]).
119120

120121
%%-----------------------------------------------------------------------------
121122
%% @param L the list to reverse
122123
%% @returns the elements of L in reverse order
123-
%% @doc Reverse the elements of L.
124+
%% @equiv lists:reverse(L, [])
124125
%% @end
125126
%%-----------------------------------------------------------------------------
126-
-spec reverse(list()) -> list().
127-
reverse(L) ->
128-
%% TODO this should be done in unit time in a BIF
129-
reverse(L, []).
127+
-spec reverse(L :: list()) -> list().
128+
reverse(_L) ->
129+
erlang:nif_error(undefined).
130130

131-
%% @private
132-
reverse([], Accum) ->
133-
Accum;
134-
reverse([H | T], Accum) ->
135-
reverse(T, [H | Accum]).
131+
%%-----------------------------------------------------------------------------
132+
%% @param L the list to reverse
133+
%% @param T the tail to append to the reversed list
134+
%% @returns the elements of L in reverse order followed by T
135+
%% @doc Reverse the elements of L, folled by T.
136+
%% If T is not a list or not a proper list, it is appended anyway and the result
137+
%% will be an improper list.
138+
%%
139+
%% Following Erlang/OTP tradition, `lists:reverse/1,2` is a nif. It computes
140+
%% the length and then allocates memory for the list at once (2 * n terms).
141+
%%
142+
%% While this is much faster with AtomVM as allocations are expensive with
143+
%% default heap growth strategy, it can consume more memory until the list
144+
%% passed is garbage collected, as opposed to a recursive implementation where
145+
%% the process garbage collect part of the input list during the reversal.
146+
%%
147+
%% Consequently, tail-recursive implementations calling `lists:reverse/2`
148+
%% can be as expensive or more expensive in memory than list comprehensions or
149+
%% non-tail recursive versions depending on the number of terms saved on the
150+
%% stack between calls.
151+
%%
152+
%% For example, a non-tail recursive join/2 implementation requires two terms
153+
%% on stack for each iteration, so when it returns it will use
154+
%% `n * 3' (stack) + `n * 4' (result list)
155+
%% a tail recursive version will use, on last iteration:
156+
%% `n * 4' (reversed list) + n * 4' (result list)
157+
%% @end
158+
%%-----------------------------------------------------------------------------
159+
-spec reverse
160+
(L :: nonempty_list(E), T :: list(E)) -> nonempty_list(E);
161+
(L :: nonempty_list(), T :: any()) -> maybe_improper_list();
162+
(L :: [], T) -> T when T :: any().
163+
reverse(_L, _T) ->
164+
erlang:nif_error(undefined).
136165

137166
%%-----------------------------------------------------------------------------
138167
%% @param Fun the predicate to evaluate
@@ -162,13 +191,13 @@ keydelete(K, I, L) ->
162191

163192
%% @private
164193
keydelete(_K, _I, [], L) ->
165-
reverse(L);
194+
?MODULE:reverse(L);
166195
keydelete(K, I, [H | T], L2) when is_tuple(H) ->
167196
case I =< tuple_size(H) of
168197
true ->
169198
case element(I, H) of
170199
K ->
171-
reverse(L2) ++ T;
200+
?MODULE:reverse(L2, T);
172201
_ ->
173202
keydelete(K, I, T, [H | L2])
174203
end;
@@ -256,7 +285,7 @@ keyreplace(K, I, [H | T], L, NewTuple, NewList) when is_tuple(H) andalso is_tupl
256285
true ->
257286
case element(I, H) of
258287
K ->
259-
reverse(NewList) ++ [NewTuple | T];
288+
?MODULE:reverse(NewList, [NewTuple | T]);
260289
_ ->
261290
keyreplace(K, I, T, L, NewTuple, [H | NewList])
262291
end;
@@ -298,7 +327,7 @@ foldl(Fun, Acc0, [H | T]) ->
298327
List :: list()
299328
) -> Acc1 :: term().
300329
foldr(Fun, Acc0, List) ->
301-
foldl(Fun, Acc0, reverse(List)).
330+
foldl(Fun, Acc0, ?MODULE:reverse(List)).
302331

303332
%%-----------------------------------------------------------------------------
304333
%% @param Fun the predicate to evaluate
@@ -381,19 +410,8 @@ search(Pred, [H | T]) ->
381410
%% @end
382411
%%-----------------------------------------------------------------------------
383412
-spec filter(Pred :: fun((Elem :: term()) -> boolean()), List :: list()) -> list().
384-
filter(Pred, L) ->
385-
filter(Pred, L, []).
386-
387-
%% @private
388-
filter(_Pred, [], Accum) ->
389-
reverse(Accum);
390-
filter(Pred, [H | T], Accum) ->
391-
case Pred(H) of
392-
true ->
393-
filter(Pred, T, [H | Accum]);
394-
_ ->
395-
filter(Pred, T, Accum)
396-
end.
413+
filter(Pred, L) when is_function(Pred, 1) ->
414+
[X || X <- L, Pred(X)].
397415

398416
%%-----------------------------------------------------------------------------
399417
%% @param Sep the separator
@@ -403,16 +421,16 @@ filter(Pred, [H | T], Accum) ->
403421
%% @end
404422
%%-----------------------------------------------------------------------------
405423
-spec join(Sep :: any(), List :: list()) -> list().
406-
join(Sep, L) ->
407-
join(L, Sep, []).
424+
join(_Sep, []) ->
425+
[];
426+
join(Sep, [H | Tail]) ->
427+
[H | join_1(Sep, Tail)].
408428

409429
%% @private
410-
join([], _Sep, Accum) ->
411-
lists:reverse(Accum);
412-
join([E | R], Sep, []) ->
413-
join(R, Sep, [E]);
414-
join([E | R], Sep, Accum) ->
415-
join(R, Sep, [E, Sep | Accum]).
430+
join_1(Sep, [H | Tail]) ->
431+
[Sep, H | join_1(Sep, Tail)];
432+
join_1(_Sep, []) ->
433+
[].
416434

417435
%%-----------------------------------------------------------------------------
418436
%% @param From from integer
@@ -424,8 +442,8 @@ join([E | R], Sep, Accum) ->
424442
%% @end
425443
%%-----------------------------------------------------------------------------
426444
-spec seq(From :: integer(), To :: integer()) -> list().
427-
seq(From, To) ->
428-
seq(From, To, 1).
445+
seq(From, To) when is_integer(From) andalso is_integer(To) andalso From =< To ->
446+
seq_r(From, To, 1, []).
429447

430448
%%-----------------------------------------------------------------------------
431449
%% @param From from integer
@@ -447,16 +465,12 @@ seq(From, To, Incr) when
447465
seq(To, To, 0) ->
448466
[To];
449467
seq(From, To, Incr) ->
450-
seq(From, To, Incr, []).
468+
Last = From + ((To - From) div Incr) * Incr,
469+
seq_r(From, Last, Incr, []).
451470

452471
%% @private
453-
seq(From, To, Incr, Accum) when
454-
(Incr > 0 andalso From > To) orelse
455-
(Incr < 0 andalso To > From)
456-
->
457-
reverse(Accum);
458-
seq(From, To, Incr, Accum) ->
459-
seq(From + Incr, To, Incr, [From | Accum]).
472+
seq_r(From, From, _Incr, Acc) -> [From | Acc];
473+
seq_r(From, To, Incr, Acc) -> seq_r(From, To - Incr, Incr, [To | Acc]).
460474

461475
%%-----------------------------------------------------------------------------
462476
%% @param List a list
@@ -523,20 +537,16 @@ unique(Sorted) ->
523537
unique(Sorted, fun(X, Y) -> X =< Y end).
524538

525539
%% @private
526-
unique(Sorted, Fun) ->
527-
unique(Sorted, Fun, []).
528-
529-
%% @private
530-
unique([], _Fun, []) ->
540+
unique([], _Fun) ->
531541
[];
532-
unique([X], _Fun, Acc) ->
533-
lists:reverse([X | Acc]);
534-
unique([X, Y | Tail], Fun, Acc) ->
542+
unique([X], _Fun) ->
543+
[X];
544+
unique([X, Y | Tail], Fun) ->
535545
case Fun(X, Y) andalso Fun(Y, X) of
536546
true ->
537-
unique([Y | Tail], Fun, Acc);
547+
unique([Y | Tail], Fun);
538548
false ->
539-
unique([Y | Tail], Fun, [X | Acc])
549+
[X | unique([Y | Tail], Fun)]
540550
end.
541551

542552
%%-----------------------------------------------------------------------------
@@ -563,9 +573,9 @@ duplicate(Count, Elem, Acc) -> duplicate(Count - 1, Elem, [Elem | Acc]).
563573
%%-----------------------------------------------------------------------------
564574
-spec sublist([Elem], integer()) -> [Elem].
565575
sublist(List, Len) when is_integer(Len) andalso Len >= 0 ->
566-
sublist0(List, Len, []).
576+
sublist0(List, Len).
567577

568578
%% @private
569-
sublist0(_List, 0, Acc) -> reverse(Acc);
570-
sublist0([], _, Acc) -> reverse(Acc);
571-
sublist0([H | Tail], Len, Acc) -> sublist0(Tail, Len - 1, [H | Acc]).
579+
sublist0([], _Len) -> [];
580+
sublist0(_, 0) -> [];
581+
sublist0([H | Tail], Len) -> [H | sublist0(Tail, Len - 1)].

src/libAtomVM/nifs.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ static term nif_base64_encode_to_string(Context *ctx, int argc, term argv[]);
166166
static term nif_base64_decode_to_string(Context *ctx, int argc, term argv[]);
167167
static term nif_code_load_abs(Context *ctx, int argc, term argv[]);
168168
static term nif_code_load_binary(Context *ctx, int argc, term argv[]);
169+
static term nif_lists_reverse(Context *ctx, int argc, term argv[]);
169170
static term nif_maps_next(Context *ctx, int argc, term argv[]);
170171
static term nif_unicode_characters_to_list(Context *ctx, int argc, term argv[]);
171172
static term nif_unicode_characters_to_binary(Context *ctx, int argc, term argv[]);
@@ -694,6 +695,11 @@ static const struct Nif code_load_binary_nif =
694695
.base.type = NIFFunctionType,
695696
.nif_ptr = nif_code_load_binary
696697
};
698+
static const struct Nif lists_reverse_nif =
699+
{
700+
.base.type = NIFFunctionType,
701+
.nif_ptr = nif_lists_reverse
702+
};
697703
static const struct Nif maps_next_nif =
698704
{
699705
.base.type = NIFFunctionType,
@@ -4236,6 +4242,36 @@ static term nif_code_load_binary(Context *ctx, int argc, term argv[])
42364242
return result;
42374243
}
42384244

4245+
static term nif_lists_reverse(Context *ctx, int argc, term argv[])
4246+
{
4247+
// Compared to erlang version, compute the length of the list and allocate
4248+
// at once the space for the reverse.
4249+
int proper;
4250+
size_t len = term_list_length(argv[0], &proper);
4251+
if (!proper) {
4252+
RAISE_ERROR(BADARG_ATOM);
4253+
}
4254+
4255+
if (UNLIKELY(memory_ensure_free_with_roots(ctx, len * CONS_SIZE, 2, argv, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
4256+
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
4257+
}
4258+
4259+
term result = term_nil();
4260+
if (argc == 2) {
4261+
result = argv[1];
4262+
}
4263+
term list_crsr = argv[0];
4264+
while (!term_is_nil(list_crsr)) {
4265+
if (UNLIKELY(!term_is_nonempty_list(list_crsr))) {
4266+
RAISE_ERROR(BADARG_ATOM);
4267+
}
4268+
term *list_ptr = term_get_list_ptr(list_crsr);
4269+
result = term_list_prepend(list_ptr[LIST_HEAD_INDEX], result, &ctx->heap);
4270+
list_crsr = list_ptr[LIST_TAIL_INDEX];
4271+
}
4272+
return result;
4273+
}
4274+
42394275
static term nif_maps_next(Context *ctx, int argc, term argv[])
42404276
{
42414277
UNUSED(argc);

src/libAtomVM/nifs.gperf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ base64:encode/1, &base64_encode_nif
140140
base64:decode/1, &base64_decode_nif
141141
base64:encode_to_string/1, &base64_encode_to_string_nif
142142
base64:decode_to_string/1, &base64_decode_to_string_nif
143+
lists:reverse/1, &lists_reverse_nif
144+
lists:reverse/2, &lists_reverse_nif
143145
maps:next/1, &maps_next_nif
144146
unicode:characters_to_list/1, &unicode_characters_to_list_nif
145147
unicode:characters_to_list/2, &unicode_characters_to_list_nif

src/libAtomVM/opcodesswitch.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3011,11 +3011,10 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
30113011
#ifdef IMPL_EXECUTE_LOOP
30123012
TRACE("get_list/3 %lx, %c%i, %c%i\n", src_value, T_DEST_REG(head_dreg), T_DEST_REG(tail_dreg));
30133013

3014-
term head = term_get_list_head(src_value);
3015-
term tail = term_get_list_tail(src_value);
3014+
term *list_ptr = term_get_list_ptr(src_value);
30163015

3017-
WRITE_REGISTER(head_dreg, head);
3018-
WRITE_REGISTER(tail_dreg, tail);
3016+
WRITE_REGISTER(head_dreg, list_ptr[LIST_HEAD_INDEX]);
3017+
WRITE_REGISTER(tail_dreg, list_ptr[LIST_TAIL_INDEX]);
30193018
#endif
30203019

30213020
#ifdef IMPL_CODE_LOADER

src/libAtomVM/term.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ extern "C" {
8888
#define CONS_SIZE 2
8989
#define REFC_BINARY_CONS_OFFSET 4
9090

91+
#define LIST_HEAD_INDEX 1
92+
#define LIST_TAIL_INDEX 0
93+
9194
#define TERM_BINARY_SIZE_IS_HEAP(size) ((size) < REFC_BINARY_MIN)
9295

9396
#if TERM_BYTES == 4
@@ -1288,7 +1291,7 @@ static inline term term_list_from_list_ptr(term *list_elem)
12881291
static inline term term_get_list_head(term t)
12891292
{
12901293
term *list_ptr = term_get_list_ptr(t);
1291-
return list_ptr[1];
1294+
return list_ptr[LIST_HEAD_INDEX];
12921295
}
12931296

12941297
/**
@@ -1300,7 +1303,7 @@ static inline term term_get_list_head(term t)
13001303
static inline term term_get_list_tail(term t)
13011304
{
13021305
term *list_ptr = term_get_list_ptr(t);
1303-
return *list_ptr;
1306+
return list_ptr[LIST_TAIL_INDEX];
13041307
}
13051308

13061309
/**
@@ -1312,7 +1315,7 @@ static inline term term_get_list_tail(term t)
13121315
*/
13131316
MALLOC_LIKE static inline term *term_list_alloc(Heap *heap)
13141317
{
1315-
return memory_heap_alloc(heap, 2);
1318+
return memory_heap_alloc(heap, CONS_SIZE);
13161319
}
13171320

13181321
/**

tests/erlang_tests/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ compile_erlang(test_list_processes)
132132
compile_erlang(test_tl)
133133
compile_erlang(test_list_to_atom)
134134
compile_erlang(test_list_to_existing_atom)
135+
compile_erlang(test_lists_reverse)
135136
compile_erlang(test_binary_to_atom)
136137
compile_erlang(test_binary_to_existing_atom)
137138
compile_erlang(test_atom_to_list)
@@ -575,6 +576,7 @@ add_custom_target(erlang_test_modules DEPENDS
575576
test_tl.beam
576577
test_list_to_atom.beam
577578
test_list_to_existing_atom.beam
579+
test_lists_reverse.beam
578580
test_binary_to_atom.beam
579581
test_binary_to_existing_atom.beam
580582
test_atom_to_list.beam

0 commit comments

Comments
 (0)