Skip to content

Commit bb901b8

Browse files
committed
Rewrite lists:keyfind, lists:keymember, lists:member as NIFs; implement erlang:list_to_bitstring
Signed-off-by: Mateusz Front <mateusz.front@swmansion.com>
1 parent b94dfea commit bb901b8

File tree

11 files changed

+476
-36
lines changed

11 files changed

+476
-36
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5555
- Added `supervisor:which_children/1` and `supervisor:count_children/1`
5656
- Added `monitored_by` in `process_info/2`
5757
- Added mock implementation for `current_stacktrace` in `process_info`
58+
- Added `erlang:list_to_bitstring`
59+
- Reimplemented `lists:keyfind`, `lists:keymember` and `lists:member` as NIFs
5860

5961
### Changed
6062

libs/estdlib/src/lists.erl

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,7 @@ last([_H | T]) -> last(T).
135135
%%-----------------------------------------------------------------------------
136136
-spec member(E :: term(), L :: list()) -> boolean().
137137
member(_, []) ->
138-
false;
139-
member(E, [E | _]) ->
140-
true;
141-
member(E, [_ | T]) ->
142-
member(E, T).
138+
erlang:nif_error(undefined).
143139

144140
%%-----------------------------------------------------------------------------
145141
%% @param E the member to delete
@@ -265,22 +261,8 @@ keydelete(K, I, [H | T], L2) ->
265261
%% @end
266262
%%-----------------------------------------------------------------------------
267263
-spec keyfind(K :: term(), I :: pos_integer(), L :: list(tuple())) -> tuple() | false.
268-
keyfind(_K, _I, []) ->
269-
false;
270-
keyfind(K, I, [H | T]) when is_tuple(H) ->
271-
case I =< tuple_size(H) of
272-
true ->
273-
case element(I, H) of
274-
K ->
275-
H;
276-
_ ->
277-
keyfind(K, I, T)
278-
end;
279-
false ->
280-
keyfind(K, I, T)
281-
end;
282-
keyfind(K, I, [_H | T]) ->
283-
keyfind(K, I, T).
264+
keyfind(_K, _I, _L) ->
265+
erlang:nif_error(undefined).
284266

285267
%%-----------------------------------------------------------------------------
286268
%% @param K the key to match
@@ -292,21 +274,7 @@ keyfind(K, I, [_H | T]) ->
292274
%%-----------------------------------------------------------------------------
293275
-spec keymember(K :: term(), I :: pos_integer(), L :: list(tuple())) -> boolean().
294276
keymember(_K, _I, []) ->
295-
false;
296-
keymember(K, I, [H | T]) when is_tuple(H) ->
297-
case I =< tuple_size(H) of
298-
true ->
299-
case element(I, H) of
300-
K ->
301-
true;
302-
_ ->
303-
keymember(K, I, T)
304-
end;
305-
false ->
306-
keymember(K, I, T)
307-
end;
308-
keymember(K, I, [_H | T]) ->
309-
keymember(K, I, T).
277+
erlang:nif_error(undefined).
310278

311279
%%-----------------------------------------------------------------------------
312280
%% @param K the key to match

src/libAtomVM/defaultatoms.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,6 @@ X(INIT_ATOM, "\x4", "init")
194194
X(MONITORED_BY_ATOM, "\xC", "monitored_by")
195195

196196
X(CURRENT_STACKTRACE_ATOM, "\x12", "current_stacktrace")
197+
198+
X(KEY_ATOM, "\x3", "key")
199+
X(VALUE_ATOM, "\x5", "value")

src/libAtomVM/nifs.c

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ static term nif_erlang_setnode_2(Context *ctx, int argc, term argv[]);
177177
static term nif_erlang_memory(Context *ctx, int argc, term argv[]);
178178
static term nif_erlang_monitor(Context *ctx, int argc, term argv[]);
179179
static term nif_erlang_demonitor(Context *ctx, int argc, term argv[]);
180+
static term nif_erlang_list_to_bitstring_1(Context *ctx, int argc, term argv[]);
180181
static term nif_erlang_unlink(Context *ctx, int argc, term argv[]);
181182
static term nif_atomvm_add_avm_pack_binary(Context *ctx, int argc, term argv[]);
182183
static term nif_atomvm_add_avm_pack_file(Context *ctx, int argc, term argv[]);
@@ -197,6 +198,9 @@ static term nif_code_ensure_loaded(Context *ctx, int argc, term argv[]);
197198
static term nif_erlang_module_loaded(Context *ctx, int argc, term argv[]);
198199
static term nif_erlang_nif_error(Context *ctx, int argc, term argv[]);
199200
static term nif_lists_reverse(Context *ctx, int argc, term argv[]);
201+
static term nif_lists_keyfind(Context *ctx, int argc, term argv[]);
202+
static term nif_lists_keymember(Context *ctx, int argc, term argv[]);
203+
static term nif_lists_member(Context *ctx, int argc, term argv[]);
200204
static term nif_maps_from_keys(Context *ctx, int argc, term argv[]);
201205
static term nif_maps_next(Context *ctx, int argc, term argv[]);
202206
static term nif_unicode_characters_to_list(Context *ctx, int argc, term argv[]);
@@ -764,6 +768,22 @@ static const struct Nif erlang_lists_subtract_nif = {
764768
.base.type = NIFFunctionType,
765769
.nif_ptr = nif_erlang_lists_subtract
766770
};
771+
static const struct Nif lists_member_nif = {
772+
.base.type = NIFFunctionType,
773+
.nif_ptr = nif_lists_member
774+
};
775+
static const struct Nif lists_keymember_nif = {
776+
.base.type = NIFFunctionType,
777+
.nif_ptr = nif_lists_keymember
778+
};
779+
static const struct Nif lists_keyfind_nif = {
780+
.base.type = NIFFunctionType,
781+
.nif_ptr = nif_lists_keyfind
782+
};
783+
static const struct Nif list_to_bitstring_nif = {
784+
.base.type = NIFFunctionType,
785+
.nif_ptr = nif_erlang_list_to_bitstring_1
786+
};
767787
static const struct Nif zlib_compress_nif = {
768788
.base.type = NIFFunctionType,
769789
.nif_ptr = nif_zlib_compress_1
@@ -5824,6 +5844,97 @@ static term nif_erlang_lists_subtract(Context *ctx, int argc, term argv[])
58245844
return result;
58255845
}
58265846

5847+
static term nif_lists_member(Context *ctx, int argc, term argv[])
5848+
{
5849+
UNUSED(argc)
5850+
term elem = argv[0];
5851+
term list = argv[1];
5852+
5853+
while (term_is_nonempty_list(list)) {
5854+
term head = term_get_list_head(list);
5855+
5856+
TermCompareResult cmp_result = term_compare(head, elem, TermCompareExact, ctx->global);
5857+
5858+
if (cmp_result == TermEquals) {
5859+
return TRUE_ATOM;
5860+
}
5861+
5862+
if (UNLIKELY(cmp_result == TermCompareMemoryAllocFail)) {
5863+
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
5864+
}
5865+
5866+
list = term_get_list_tail(list);
5867+
}
5868+
5869+
VALIDATE_VALUE(list, term_is_nil);
5870+
5871+
return FALSE_ATOM;
5872+
}
5873+
5874+
static term nif_lists_keymember(Context *ctx, int argc, term argv[])
5875+
{
5876+
term result = nif_lists_keyfind(ctx, argc, argv);
5877+
if (UNLIKELY(term_is_invalid_term(result))) {
5878+
return result;
5879+
}
5880+
return result == FALSE_ATOM ? FALSE_ATOM : TRUE_ATOM;
5881+
}
5882+
5883+
static term nif_lists_keyfind(Context *ctx, int argc, term argv[])
5884+
{
5885+
UNUSED(argc)
5886+
term key = argv[0];
5887+
term n = argv[1];
5888+
term tuple_list = argv[2];
5889+
5890+
VALIDATE_VALUE(n, term_is_integer);
5891+
5892+
avm_int_t n_pos = term_to_int(n);
5893+
5894+
if (n_pos <= 0) {
5895+
RAISE_ERROR(BADARG_ATOM);
5896+
}
5897+
5898+
while (term_is_nonempty_list(tuple_list)) {
5899+
term tuple = term_get_list_head(tuple_list);
5900+
5901+
if (!term_is_tuple(tuple)) {
5902+
tuple_list = term_get_list_tail(tuple_list);
5903+
continue;
5904+
}
5905+
5906+
int tuple_size = term_get_tuple_arity(tuple);
5907+
5908+
if (n_pos > tuple_size) {
5909+
tuple_list = term_get_list_tail(tuple_list);
5910+
continue;
5911+
}
5912+
5913+
term nth_element = term_get_tuple_element(tuple, n_pos - 1);
5914+
5915+
TermCompareResult cmp_result = term_compare(nth_element, key, TermCompareExact, ctx->global);
5916+
5917+
if (cmp_result == TermEquals) {
5918+
return tuple;
5919+
}
5920+
if (UNLIKELY(cmp_result == TermCompareMemoryAllocFail)) {
5921+
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
5922+
}
5923+
5924+
tuple_list = term_get_list_tail(tuple_list);
5925+
}
5926+
5927+
VALIDATE_VALUE(tuple_list, term_is_nil);
5928+
5929+
return FALSE_ATOM;
5930+
}
5931+
5932+
static term nif_erlang_list_to_bitstring_1(Context *ctx, int argc, term argv[])
5933+
{
5934+
// TODO: implement proper list_to_bitstring function when the bitstrings are supported
5935+
return nif_erlang_list_to_binary_1(ctx, argc, argv);
5936+
}
5937+
58275938
#ifdef WITH_ZLIB
58285939
static term nif_zlib_compress_1(Context *ctx, int argc, term argv[])
58295940
{

src/libAtomVM/nifs.gperf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ erlang:dist_ctrl_get_data/1, &dist_ctrl_get_data_nif
140140
erlang:dist_ctrl_put_data/2, &dist_ctrl_put_data_nif
141141
erlang:module_loaded/1,&module_loaded_nif
142142
erlang:nif_error/1,&nif_error_nif
143+
erlang:list_to_bitstring/1, &list_to_bitstring_nif
143144
erts_debug:flat_size/1, &flat_size_nif
144145
ets:new/2, &ets_new_nif
145146
ets:insert/2, &ets_insert_nif
@@ -180,6 +181,9 @@ base64:encode/1, &base64_encode_nif
180181
base64:decode/1, &base64_decode_nif
181182
base64:encode_to_string/1, &base64_encode_to_string_nif
182183
base64:decode_to_string/1, &base64_decode_to_string_nif
184+
lists:keyfind/3, &lists_keyfind_nif
185+
lists:keymember/3, &lists_keymember_nif
186+
lists:member/2, &lists_member_nif
183187
lists:reverse/1, &lists_reverse_nif
184188
lists:reverse/2, &lists_reverse_nif
185189
maps:from_keys/2, &maps_from_keys_nif

tests/erlang_tests/CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,11 @@ compile_assembler(test_op_bs_start_match_asm)
551551
compile_erlang(test_op_bs_create_bin)
552552
compile_assembler(test_op_bs_create_bin_asm)
553553

554+
compile_erlang(test_list_to_bitstring)
555+
compile_erlang(test_lists_member)
556+
compile_erlang(test_lists_keymember)
557+
compile_erlang(test_lists_keyfind)
558+
554559
if(Erlang_VERSION VERSION_GREATER_EQUAL "23")
555560
set(OTP23_OR_GREATER_TESTS
556561
test_op_bs_start_match_asm.beam
@@ -1070,6 +1075,12 @@ add_custom_target(erlang_test_modules DEPENDS
10701075
test_ets.beam
10711076
test_node.beam
10721077

1078+
1079+
test_list_to_bitstring.beam
1080+
test_lists_member.beam
1081+
test_lists_keymember.beam
1082+
test_lists_keyfind.beam
1083+
10731084
test_op_bs_start_match.beam
10741085
test_op_bs_create_bin.beam
10751086

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
%
2+
% This file is part of AtomVM.
3+
%
4+
% Copyright 2025 Franciszek Kubis <franciszek.kubis@swmansion.com>
5+
%
6+
% Licensed under the Apache License, Version 2.0 (the "License");
7+
% you may not use this file except in compliance with the License.
8+
% You may obtain a copy of the License at
9+
%
10+
% http://www.apache.org/licenses/LICENSE-2.0
11+
%
12+
% Unless required by applicable law or agreed to in writing, software
13+
% distributed under the License is distributed on an "AS IS" BASIS,
14+
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
% See the License for the specific language governing permissions and
16+
% limitations under the License.
17+
%
18+
% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
19+
%
20+
21+
-module(test_list_to_bitstring).
22+
23+
-export([start/0, concat/2, concat2/2, compare_bin/3, id/1]).
24+
25+
start() ->
26+
ok = test_concat(),
27+
ok = test_iolist(),
28+
ok = test_empty_list_to_binary(),
29+
0.
30+
31+
test_concat() ->
32+
Bin = concat("Hello", "world"),
33+
Bin2 = concat2("", ""),
34+
CompRes1 = compare_bin(Bin, <<"Hello world">>) - compare_bin(Bin, <<"HelloXworld">>),
35+
1 = CompRes1 + byte_size(Bin2) + invalid(42),
36+
ok.
37+
38+
test_iolist() ->
39+
<<"Hello world">> = list_to_bitstring(?MODULE:id([<<"Hello ">>, [<<"wor">>, [$l, $d]]])),
40+
ok.
41+
42+
test_empty_list_to_binary() ->
43+
<<"">> = erlang:list_to_bitstring(?MODULE:id([])),
44+
ok.
45+
46+
concat(A, B) ->
47+
list_to_bitstring(?MODULE:id(A ++ " " ++ B)).
48+
49+
concat2(A, B) ->
50+
list_to_bitstring(?MODULE:id(A ++ B)).
51+
52+
invalid(A) ->
53+
try list_to_bitstring(?MODULE:id(A)) of
54+
Any -> byte_size(Any)
55+
catch
56+
error:badarg -> 0;
57+
_:_ -> 1000
58+
end.
59+
60+
compare_bin(Bin1, Bin2) ->
61+
compare_bin(Bin1, Bin2, byte_size(Bin1) - 1).
62+
63+
compare_bin(_Bin1, _Bin2, -1) ->
64+
1;
65+
compare_bin(Bin1, Bin2, Index) ->
66+
B1 = binary:at(Bin1, Index),
67+
case binary:at(Bin2, Index) of
68+
B1 ->
69+
compare_bin(Bin1, Bin2, Index - 1);
70+
_Any ->
71+
0
72+
end.
73+
74+
id(X) ->
75+
X.

0 commit comments

Comments
 (0)