Skip to content

Commit fce094e

Browse files
committed
Merge pull request #2354 from pguyot/w25/erlang
erlang: add three missing functions These changes are made under both the "Apache 2.0" and the "GNU Lesser General Public License 2.1 or later" license terms (dual license). SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
2 parents f7594af + 815a2d2 commit fce094e

9 files changed

Lines changed: 363 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3232
- Added USB CDC port drivers for ESP32, RP2, and STM32 platforms
3333
- Added a Linux `gpio` driver for the generic_unix port (in `avm_unix`) using sysfs
3434
- Added `console:print_err/1` to write to standard error
35+
- Added `erlang:term_to_binary/2`, `erlang:is_builtin/3` and `erlang:bitstring_to_list/1`
3536

3637
### Changed
3738
- Updated network type db() to dbm() to reflect the actual representation of the type

libs/estdlib/src/erlang.erl

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
erase/0,
5353
erase/1,
5454
function_exported/3,
55+
is_builtin/3,
5556
loaded/0,
5657
module_loaded/1,
5758
display/1,
@@ -71,6 +72,7 @@
7172
binary_to_integer/1,
7273
binary_to_integer/2,
7374
binary_to_list/1,
75+
bitstring_to_list/1,
7476
atom_to_binary/1,
7577
atom_to_binary/2,
7678
atom_to_list/1,
@@ -123,6 +125,7 @@
123125
garbage_collect/1,
124126
binary_to_term/1,
125127
term_to_binary/1,
128+
term_to_binary/2,
126129
split_binary/2,
127130
crc32/1,
128131
crc32/2,
@@ -187,7 +190,8 @@
187190
atomvm_heap_growth_strategy/0,
188191
stacktrace/0,
189192
stacktrace_extrainfo/0,
190-
raise_stacktrace/0
193+
raise_stacktrace/0,
194+
term_to_binary_option/0
191195
]).
192196

193197
-type atom_encoding() :: latin1 | utf8 | unicode.
@@ -206,6 +210,12 @@
206210

207211
-type demonitor_option() :: flush | {flush, boolean()} | info | {info, boolean()}.
208212

213+
-type term_to_binary_option() ::
214+
compressed
215+
| {compressed, Level :: 0..9}
216+
| deterministic
217+
| {minor_version, Version :: 0..2}.
218+
209219
-type atomvm_heap_growth_strategy() ::
210220
bounded_free
211221
| minimum
@@ -686,6 +696,27 @@ erase(_Key) ->
686696
function_exported(_Module, _Function, _Arity) ->
687697
erlang:nif_error(undefined).
688698

699+
%%-----------------------------------------------------------------------------
700+
%% @param Module module name
701+
%% @param Function function name
702+
%% @param Arity function arity
703+
%% @returns `true' if `Module:Function/Arity' is a builtin, `false' otherwise.
704+
%% @doc Determine whether a function is implemented natively (as a BIF or a
705+
%% NIF) by AtomVM. As on BEAM, this reflects functions "implemented in
706+
%% C" rather than in Erlang, but it is answered against AtomVM's own
707+
%% registry. It therefore differs from BEAM whenever the two virtual
708+
%% machines disagree on whether a given function is native: for
709+
%% example `erlang:atom_to_binary/1' is native on AtomVM but
710+
%% Erlang-implemented on BEAM, while `erlang:md5/1' is a BEAM BIF that
711+
%% AtomVM implements in Erlang. An arity that no builtin has (including a
712+
%% negative integer) returns `false'. Raises `badarg' if `Module' or
713+
%% `Function' is not an atom, or `Arity' is not an integer.
714+
%% @end
715+
%%-----------------------------------------------------------------------------
716+
-spec is_builtin(Module :: module(), Function :: atom(), Arity :: arity()) -> boolean().
717+
is_builtin(_Module, _Function, _Arity) ->
718+
erlang:nif_error(undefined).
719+
689720
%%-----------------------------------------------------------------------------
690721
%% @returns list of loaded modules
691722
%% @doc Returns all loaded modules.
@@ -900,6 +931,20 @@ binary_to_integer(_Binary, _Base) ->
900931
binary_to_list(_Binary) ->
901932
erlang:nif_error(undefined).
902933

934+
%%-----------------------------------------------------------------------------
935+
%% @param Bitstring Bitstring to convert to list
936+
%% @returns a list of bytes from the bitstring
937+
%% @doc Convert a bitstring to a list of bytes.
938+
%%
939+
%% Unlike Erlang/OTP, AtomVM only supports byte-aligned bitstrings (binaries),
940+
%% so the returned list never has a trailing bitstring and this function
941+
%% behaves like `binary_to_list/1'.
942+
%% @end
943+
%%-----------------------------------------------------------------------------
944+
-spec bitstring_to_list(Bitstring :: bitstring()) -> [byte()].
945+
bitstring_to_list(_Bitstring) ->
946+
erlang:nif_error(undefined).
947+
903948
%%-----------------------------------------------------------------------------
904949
%% @param Atom Atom to convert
905950
%% @returns a binary with the atom's name
@@ -1555,10 +1600,8 @@ garbage_collect(_Pid) ->
15551600
%%-----------------------------------------------------------------------------
15561601
%% @returns A term decoded from passed binary
15571602
%% @param Binary binary to decode
1558-
%% @doc Decode a term that was previously encodes with `term_to_binary/1'
1603+
%% @doc Decode a term that was previously encoded with `term_to_binary/1'.
15591604
%% This function should be mostly compatible with its Erlang/OTP counterpart.
1560-
%% Unlike modern Erlang/OTP, resources are currently serialized as empty
1561-
%% binaries and cannot be unserialized.
15621605
%% @end
15631606
%%-----------------------------------------------------------------------------
15641607
-spec binary_to_term(Binary :: binary()) -> any().
@@ -1570,14 +1613,42 @@ binary_to_term(_Binary) ->
15701613
%% @param Term term to encode
15711614
%% @doc Encode a term to a binary that can later be decoded with `binary_to_term/1'.
15721615
%% This function should be mostly compatible with its Erlang/OTP counterpart.
1573-
%% Unlike modern Erlang/OTP, resources are currently serialized as empty
1574-
%% binaries.
15751616
%% @end
15761617
%%-----------------------------------------------------------------------------
15771618
-spec term_to_binary(Term :: any()) -> binary().
15781619
term_to_binary(_Term) ->
15791620
erlang:nif_error(undefined).
15801621

1622+
%%-----------------------------------------------------------------------------
1623+
%% @returns A binary encoding passed term.
1624+
%% @param Term term to encode
1625+
%% @param Options encoding options
1626+
%% @doc Encode a term to a binary that can later be decoded with `binary_to_term/1'.
1627+
%%
1628+
%% The following options are accepted:
1629+
%% <ul>
1630+
%% <li>`compressed' and `{compressed, Level}' (`Level' from 0 to 9): accepted
1631+
%% for compatibility but ignored. AtomVM's encoding is always
1632+
%% uncompressed; the resulting binary is still a valid external term that
1633+
%% `binary_to_term/1' can decode.</li>
1634+
%% <li>`deterministic': accepted for compatibility. AtomVM's encoding is
1635+
%% already deterministic, so this option has no effect.</li>
1636+
%% <li>`{minor_version, Version}' (`Version' from 0 to 2): accepted for
1637+
%% compatibility. AtomVM always encodes floats using the `NEW_FLOAT_EXT'
1638+
%% representation (minor version 1 and later).</li>
1639+
%% </ul>
1640+
%%
1641+
%% Unlike Erlang/OTP, the `local' option is not supported: AtomVM never emits
1642+
%% the node-local `LOCAL_EXT' encoding. Passing `local', or any other unknown
1643+
%% option, raises a `badarg' error.
1644+
%%
1645+
%% This function should be mostly compatible with its Erlang/OTP counterpart.
1646+
%% @end
1647+
%%-----------------------------------------------------------------------------
1648+
-spec term_to_binary(Term :: any(), Options :: [term_to_binary_option()]) -> binary().
1649+
term_to_binary(_Term, _Options) ->
1650+
erlang:nif_error(undefined).
1651+
15811652
%%-----------------------------------------------------------------------------
15821653
%% @returns A tuple with two subbinaries
15831654
%% @param Bin binary to split

src/libAtomVM/nifs.c

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ static term nif_erlang_port_to_list(Context *ctx, int argc, term argv[]);
241241
static term nif_erlang_ref_to_list(Context *ctx, int argc, term argv[]);
242242
static term nif_erlang_fun_to_list(Context *ctx, int argc, term argv[]);
243243
static term nif_erlang_function_exported(Context *ctx, int argc, term argv[]);
244+
static term nif_erlang_is_builtin(Context *ctx, int argc, term argv[]);
244245
static term nif_erlang_garbage_collect(Context *ctx, int argc, term argv[]);
245246
static term nif_erlang_group_leader(Context *ctx, int argc, term argv[]);
246247
static term nif_erlang_get_module_info(Context *ctx, int argc, term argv[]);
@@ -691,6 +692,10 @@ static const struct Nif function_exported_nif = {
691692
.base.type = NIFFunctionType,
692693
.nif_ptr = nif_erlang_function_exported
693694
};
695+
static const struct Nif is_builtin_nif = {
696+
.base.type = NIFFunctionType,
697+
.nif_ptr = nif_erlang_is_builtin
698+
};
694699

695700
static const struct Nif garbage_collect_nif = {
696701
.base.type = NIFFunctionType,
@@ -3562,10 +3567,48 @@ static term nif_erlang_binary_to_term(Context *ctx, int argc, term argv[])
35623567
}
35633568
}
35643569

3570+
// AtomVM's external term encoding is already deterministic and always uses
3571+
// NEW_FLOAT_EXT (minor_version >= 1). `compressed' is accepted but ignored:
3572+
// the uncompressed encoding is a valid external term that any
3573+
// binary_to_term can read.
3574+
static bool is_valid_term_to_binary_option(Context *ctx, term opt)
3575+
{
3576+
if (term_is_atom(opt)) {
3577+
return globalcontext_is_term_equal_to_atom_string(ctx->global, opt, ATOM_STR("\xD", "deterministic"))
3578+
|| globalcontext_is_term_equal_to_atom_string(ctx->global, opt, ATOM_STR("\xA", "compressed"));
3579+
}
3580+
if (term_is_tuple(opt) && term_get_tuple_arity(opt) == 2) {
3581+
term name = term_get_tuple_element(opt, 0);
3582+
term value = term_get_tuple_element(opt, 1);
3583+
if (!term_is_integer(value)) {
3584+
return false;
3585+
}
3586+
avm_int_t int_value = term_to_int(value);
3587+
if (globalcontext_is_term_equal_to_atom_string(ctx->global, name, ATOM_STR("\xD", "minor_version"))) {
3588+
return int_value >= 0 && int_value <= 2;
3589+
}
3590+
if (globalcontext_is_term_equal_to_atom_string(ctx->global, name, ATOM_STR("\xA", "compressed"))) {
3591+
return int_value >= 0 && int_value <= 9;
3592+
}
3593+
}
3594+
return false;
3595+
}
3596+
35653597
static term nif_erlang_term_to_binary(Context *ctx, int argc, term argv[])
35663598
{
3567-
UNUSED(argc);
35683599
term t = argv[0];
3600+
if (argc == 2) {
3601+
term options = argv[1];
3602+
while (term_is_nonempty_list(options)) {
3603+
if (UNLIKELY(!is_valid_term_to_binary_option(ctx, term_get_list_head(options)))) {
3604+
RAISE_ERROR(BADARG_ATOM);
3605+
}
3606+
options = term_get_list_tail(options);
3607+
}
3608+
if (UNLIKELY(!term_is_nil(options))) {
3609+
RAISE_ERROR(BADARG_ATOM);
3610+
}
3611+
}
35693612
term ret = external_term_to_binary(ctx, t);
35703613
if (term_is_invalid_term(ret)) {
35713614
RAISE_ERROR(BADARG_ATOM);
@@ -4784,6 +4827,36 @@ static term nif_erlang_function_exported(Context *ctx, int argc, term argv[])
47844827
return TRUE_ATOM;
47854828
}
47864829

4830+
static term nif_erlang_is_builtin(Context *ctx, int argc, term argv[])
4831+
{
4832+
UNUSED(argc);
4833+
4834+
term module = argv[0];
4835+
term function = argv[1];
4836+
term arity_term = argv[2];
4837+
4838+
VALIDATE_VALUE(module, term_is_atom);
4839+
VALIDATE_VALUE(function, term_is_atom);
4840+
VALIDATE_VALUE(arity_term, term_is_integer);
4841+
4842+
atom_index_t module_name_ix = term_to_atom_index(module);
4843+
atom_index_t function_name_ix = term_to_atom_index(function);
4844+
4845+
avm_int_t arity = term_to_int(arity_term);
4846+
4847+
char mfa[MAX_MFA_NAME_LEN];
4848+
atom_table_write_mfa(ctx->global->atom_table, mfa, sizeof(mfa), module_name_ix, function_name_ix, arity);
4849+
4850+
// A function is a builtin iff it is implemented natively in AtomVM, i.e.
4851+
// registered as a BIF or a NIF. Unlike function_exported/3, a function
4852+
// exported by a loaded (Erlang) module is not a builtin.
4853+
if (bif_registry_get_handler(mfa) != NULL || nifs_get(mfa) != NULL) {
4854+
return TRUE_ATOM;
4855+
}
4856+
4857+
return FALSE_ATOM;
4858+
}
4859+
47874860
static term nif_erlang_garbage_collect(Context *ctx, int argc, term argv[])
47884861
{
47894862
if (argc == 0) {

src/libAtomVM/nifs.gperf

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ erlang:binary_to_float/1, &binary_to_float_nif
6565
erlang:binary_to_integer/1, &binary_to_integer_nif
6666
erlang:binary_to_integer/2, &binary_to_integer_nif
6767
erlang:binary_to_list/1, &binary_to_list_nif
68+
erlang:bitstring_to_list/1, &binary_to_list_nif
6869
erlang:binary_to_existing_atom/1, &binary_to_existing_atom_1_nif
6970
erlang:delete_element/2, &delete_element_nif
7071
erlang:erase/0, &erase_0_nif
@@ -134,6 +135,7 @@ erlang:put/2, &put_nif
134135
erlang:binary_to_term/1, &binary_to_term_nif
135136
erlang:binary_to_term/2, &binary_to_term_nif
136137
erlang:term_to_binary/1, &term_to_binary_nif
138+
erlang:term_to_binary/2, &term_to_binary_nif
137139
erlang:split_binary/2, &split_binary_nif
138140
erlang:throw/1, &throw_nif
139141
erlang:raise/3, &raise_nif
@@ -143,6 +145,7 @@ erlang:port_to_list/1, &port_to_list_nif
143145
erlang:ref_to_list/1, &ref_to_list_nif
144146
erlang:fun_to_list/1, &fun_to_list_nif
145147
erlang:function_exported/3, &function_exported_nif
148+
erlang:is_builtin/3, &is_builtin_nif
146149
erlang:!/2, &send_nif
147150
erlang:garbage_collect/0, &garbage_collect_nif
148151
erlang:garbage_collect/1, &garbage_collect_nif

tests/erlang_tests/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ compile_erlang(test_abs)
234234
compile_erlang(test_is_process_alive)
235235
compile_erlang(test_is_not_type)
236236
compile_erlang(test_is_bitstring_is_binary)
237+
compile_erlang(test_is_builtin)
237238
compile_erlang(test_badarith)
238239
compile_erlang(test_badarith2)
239240
compile_erlang(test_badarith3)
@@ -271,6 +272,7 @@ compile_erlang(test_heap_growth)
271272
compile_erlang(test_system_flag)
272273
compile_erlang(test_system_info)
273274
compile_erlang(test_binary_to_term)
275+
compile_erlang(test_bitstring_to_list)
274276
compile_erlang(test_selective_receive)
275277
compile_erlang(test_timeout_not_integer)
276278
compile_erlang(test_undef)
@@ -793,6 +795,7 @@ set(erlang_test_beams
793795
test_is_process_alive.beam
794796
test_is_not_type.beam
795797
test_is_bitstring_is_binary.beam
798+
test_is_builtin.beam
796799
test_badarith.beam
797800
test_badarith2.beam
798801
test_badarith3.beam
@@ -830,6 +833,7 @@ set(erlang_test_beams
830833
test_system_flag.beam
831834
test_system_info.beam
832835
test_binary_to_term.beam
836+
test_bitstring_to_list.beam
833837
test_selective_receive.beam
834838
test_timeout_not_integer.beam
835839
test_undef.beam

tests/erlang_tests/test_binary_to_term.erl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,30 @@ start() ->
158158
ok = test_invalid_export_fun_encoding(),
159159
ok = test_atom_utf8_ext_node(),
160160
ok = test_encode_process_ref(),
161+
ok = test_term_to_binary_options(),
161162
0.
162163

164+
test_term_to_binary_options() ->
165+
T = ?MODULE:id({foo, [1, 2, 3], #{a => <<"b">>}, 3.14}),
166+
Plain = erlang:term_to_binary(T),
167+
Plain = erlang:term_to_binary(T, []),
168+
Plain = erlang:term_to_binary(T, [deterministic]),
169+
Plain = erlang:term_to_binary(T, [{minor_version, 2}, deterministic]),
170+
T = erlang:binary_to_term(erlang:term_to_binary(T, [{minor_version, 1}])),
171+
% compressed output may use a different encoding but must round-trip
172+
T = erlang:binary_to_term(erlang:term_to_binary(T, [compressed])),
173+
Big = duplicate(200, T, []),
174+
Big = erlang:binary_to_term(erlang:term_to_binary(Big, [compressed, deterministic])),
175+
Big = erlang:binary_to_term(erlang:term_to_binary(Big, [{compressed, 6}])),
176+
ok = expect_badarg(fun() -> erlang:term_to_binary(T, [bad_option]) end),
177+
ok = expect_badarg(fun() -> erlang:term_to_binary(T, not_a_list) end),
178+
ok = expect_badarg(fun() -> erlang:term_to_binary(T, [{minor_version, 17}]) end),
179+
ok = expect_badarg(fun() -> erlang:term_to_binary(T, [{compressed, 42}]) end),
180+
ok.
181+
182+
duplicate(0, _T, Acc) -> Acc;
183+
duplicate(N, T, Acc) -> duplicate(N - 1, T, [T | Acc]).
184+
163185
test_reverse(T, Interop) ->
164186
test_reverse(T, Interop, []).
165187

0 commit comments

Comments
 (0)