Skip to content

Commit ddd8071

Browse files
committed
Implement lists:flatten/1 as a NIF
NIF optimizes memory allocation by using a minimal stack to flatten, allocating on the heap only what is required to hold the flattened list and reusing the largest reusable tail from the original list. Signed-off-by: Paul Guyot <[email protected]>
1 parent a18fccc commit ddd8071

File tree

4 files changed

+127
-16
lines changed

4 files changed

+127
-16
lines changed

libs/estdlib/src/lists.erl

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -452,25 +452,12 @@ any(Fun, L) ->
452452
%%-----------------------------------------------------------------------------
453453
%% @param L the list to flatten
454454
%% @returns flattened list
455-
%% @doc recursively flattens elements of L into a single list
455+
%% @doc Flattens elements of L into a single list
456456
%% @end
457457
%%-----------------------------------------------------------------------------
458458
-spec flatten(L :: list()) -> list().
459-
flatten(L) when is_list(L) ->
460-
flatten(L, []).
461-
462-
%% @private
463-
%% pre: Accum is flattened
464-
flatten([], Accum) ->
465-
Accum;
466-
flatten([H | T], Accum) when is_list(H) ->
467-
FlattenedT = flatten(T, Accum),
468-
flatten(H, FlattenedT);
469-
flatten([H | T], Accum) ->
470-
FlattenedT = flatten(T, Accum),
471-
[H | FlattenedT].
472-
473-
%% post: return is flattened
459+
flatten(_L) ->
460+
erlang:nif_error(undefined).
474461

475462
%%-----------------------------------------------------------------------------
476463
%% @param F the function to apply to elements of L

src/libAtomVM/nifs.c

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
#include "smp.h"
5959
#include "synclist.h"
6060
#include "sys.h"
61+
#include "tempstack.h"
6162
#include "term.h"
6263
#include "term_typedef.h"
6364
#include "unicode.h"
@@ -213,6 +214,7 @@ static term nif_jit_backend_module(Context *ctx, int argc, term argv[]);
213214
static term nif_jit_variant(Context *ctx, int argc, term argv[]);
214215
#endif
215216
static term nif_lists_reverse(Context *ctx, int argc, term argv[]);
217+
static term nif_lists_flatten(Context *ctx, int argc, term argv[]);
216218
static term nif_lists_keyfind(Context *ctx, int argc, term argv[]);
217219
static term nif_lists_keymember(Context *ctx, int argc, term argv[]);
218220
static term nif_lists_member(Context *ctx, int argc, term argv[]);
@@ -827,6 +829,10 @@ static const struct Nif erlang_lists_subtract_nif = {
827829
.base.type = NIFFunctionType,
828830
.nif_ptr = nif_erlang_lists_subtract
829831
};
832+
static const struct Nif lists_flatten_nif = {
833+
.base.type = NIFFunctionType,
834+
.nif_ptr = nif_lists_flatten
835+
};
830836
static const struct Nif lists_member_nif = {
831837
.base.type = NIFFunctionType,
832838
.nif_ptr = nif_lists_member
@@ -6125,6 +6131,119 @@ static term nif_erlang_lists_subtract(Context *ctx, int argc, term argv[])
61256131
return result;
61266132
}
61276133

6134+
static term nif_lists_flatten(Context *ctx, int argc, term argv[])
6135+
{
6136+
UNUSED(argc)
6137+
6138+
// Compute resulting list length, as well as the length of the reusable tail
6139+
size_t result_len = 0;
6140+
size_t tail_len = 0;
6141+
term list = argv[0];
6142+
6143+
if (term_is_nil(list)) {
6144+
return list;
6145+
}
6146+
6147+
VALIDATE_VALUE(list, term_is_nonempty_list);
6148+
6149+
struct TempStack temp_stack;
6150+
if (UNLIKELY(temp_stack_init(&temp_stack) != TempStackOk)) {
6151+
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
6152+
}
6153+
if (UNLIKELY(temp_stack_push(&temp_stack, list) != TempStackOk)) {
6154+
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
6155+
}
6156+
while (!temp_stack_is_empty(&temp_stack)) {
6157+
term t = temp_stack_pop(&temp_stack);
6158+
6159+
tail_len = 0;
6160+
6161+
do {
6162+
term t_head = term_get_list_head(t);
6163+
term t_tail = term_get_list_tail(t);
6164+
// Exit if t is not a proper list
6165+
if (!term_is_list(t_tail)) {
6166+
RAISE_ERROR(BADARG_ATOM);
6167+
}
6168+
6169+
if (term_is_nonempty_list(t_head)) {
6170+
tail_len = 0;
6171+
if (term_is_nonempty_list(t_tail)) {
6172+
if (UNLIKELY(temp_stack_push(&temp_stack, t_tail) != TempStackOk)) {
6173+
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
6174+
}
6175+
}
6176+
t = t_head;
6177+
} else {
6178+
if (term_is_nil(t_head)) {
6179+
tail_len = 0;
6180+
} else {
6181+
result_len++;
6182+
tail_len++;
6183+
}
6184+
t = t_tail;
6185+
}
6186+
} while (!term_is_nil(t));
6187+
}
6188+
6189+
// Allocate flattened list and build it.
6190+
if (result_len > tail_len) {
6191+
if (UNLIKELY(memory_ensure_free_with_roots(ctx, CONS_SIZE * (result_len - tail_len), 1, &list, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
6192+
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
6193+
}
6194+
}
6195+
6196+
term result = term_nil();
6197+
term *prev_term = NULL;
6198+
6199+
if (UNLIKELY(temp_stack_push(&temp_stack, list) != TempStackOk)) {
6200+
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
6201+
}
6202+
while (!temp_stack_is_empty(&temp_stack)) {
6203+
term t = temp_stack_pop(&temp_stack);
6204+
6205+
do {
6206+
term t_head = term_get_list_head(t);
6207+
term t_tail = term_get_list_tail(t);
6208+
if (term_is_nonempty_list(t_head)) {
6209+
if (term_is_nonempty_list(t_tail)) {
6210+
if (UNLIKELY(temp_stack_push(&temp_stack, t_tail) != TempStackOk)) {
6211+
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
6212+
}
6213+
}
6214+
t = t_head;
6215+
} else {
6216+
if (!term_is_nil(t_head)) {
6217+
// Append the tail of original list
6218+
if (result_len == tail_len) {
6219+
if (prev_term) {
6220+
prev_term[0] = t;
6221+
} else {
6222+
result = t;
6223+
}
6224+
break;
6225+
}
6226+
6227+
term *new_list_item = term_list_alloc(&ctx->heap);
6228+
if (prev_term) {
6229+
prev_term[0] = term_list_from_list_ptr(new_list_item);
6230+
} else {
6231+
result = term_list_from_list_ptr(new_list_item);
6232+
}
6233+
prev_term = new_list_item;
6234+
new_list_item[0] = term_nil();
6235+
new_list_item[1] = t_head;
6236+
6237+
result_len--;
6238+
}
6239+
t = t_tail;
6240+
}
6241+
} while (!term_is_nil(t));
6242+
}
6243+
6244+
return result;
6245+
}
6246+
61286247
static term nif_lists_member(Context *ctx, int argc, term argv[])
61296248
{
61306249
UNUSED(argc)

src/libAtomVM/nifs.gperf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ base64:encode/1, &base64_encode_nif
189189
base64:decode/1, &base64_decode_nif
190190
base64:encode_to_string/1, &base64_encode_to_string_nif
191191
base64:decode_to_string/1, &base64_decode_to_string_nif
192+
lists:flatten/1, &lists_flatten_nif
192193
lists:keyfind/3, &lists_keyfind_nif
193194
lists:keymember/3, &lists_keymember_nif
194195
lists:member/2, &lists_member_nif

tests/libs/estdlib/test_lists.erl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ test_list_match() ->
236236

237237
test_flatten() ->
238238
?ASSERT_MATCH(lists:flatten([]), []),
239+
?ASSERT_MATCH(lists:flatten([[]]), []),
239240
?ASSERT_MATCH(lists:flatten([a]), [a]),
240241
?ASSERT_MATCH(lists:flatten([a, []]), [a]),
241242
?ASSERT_MATCH(lists:flatten([[[[[[[[a]]]]]]]]), [a]),
@@ -248,6 +249,9 @@ test_flatten() ->
248249
lists:flatten([[a, b, c], [d, e, f], [g, h, i]]),
249250
[a, b, c, d, e, f, g, h, i]
250251
),
252+
?ASSERT_ERROR(lists:flatten([7 | {}])),
253+
?ASSERT_ERROR(lists:flatten([[] | [5 | 5]])),
254+
?ASSERT_ERROR(lists:flatten([[7 | 4], 2])),
251255
ok.
252256

253257
test_flatmap() ->

0 commit comments

Comments
 (0)