Skip to content

Commit 52c3419

Browse files
committed
Merge pull request #1202 from pguyot/w25/code-all_loaded-all_available
Add code:all_loaded/0 and code:all_available/0 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 5679f11 + 250f0dc commit 52c3419

File tree

8 files changed

+235
-2
lines changed

8 files changed

+235
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
### Added
1010
- Added a limited implementation of the OTP `ets` interface
11+
- Added `code:all_loaded/0` and `code:all_available/0`
1112

1213
## [0.6.3] - Unreleased
1314

libs/estdlib/src/code.erl

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,37 @@
2424
%%-----------------------------------------------------------------------------
2525
-module(code).
2626

27-
-export([load_abs/1, load_binary/3]).
27+
-export([
28+
all_available/0,
29+
all_loaded/0,
30+
load_abs/1,
31+
load_binary/3
32+
]).
33+
34+
%%-----------------------------------------------------------------------------
35+
%% @returns A list of available modules, including loaded modules
36+
%% @doc Return all modules available from loaded avm packs, in addition
37+
%% to loaded modules. List of available modules may be incomplete if
38+
%% this function is called while a module is loaded.
39+
%% Result type differs from Erlang/OTP: names of modules is a binary
40+
%% (and not a string), and second term of tuples is currently
41+
%% unspecified
42+
%% @end
43+
%%-----------------------------------------------------------------------------
44+
-spec all_available() -> [{unicode:unicode_binary(), term(), boolean()}].
45+
all_available() ->
46+
erlang:nif_error(undefined).
47+
48+
%%-----------------------------------------------------------------------------
49+
%% @returns A list of all loaded modules
50+
%% @doc Return a list of all loaded modules.
51+
%% Result type differs from Erlang/OTP: second term of tuples is
52+
%% currently unspecified
53+
%% @end
54+
%%-----------------------------------------------------------------------------
55+
-spec all_loaded() -> [{atom(), term()}].
56+
all_loaded() ->
57+
erlang:nif_error(undefined).
2858

2959
%%-----------------------------------------------------------------------------
3060
%% @param Filename path to the beam to open, without .beams suffix

src/libAtomVM/globalcontext.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ struct GlobalContext
118118
RWLock *modules_lock;
119119
#endif
120120
Module **modules_by_index;
121-
int loaded_modules_count;
121+
int ATOMIC loaded_modules_count;
122122

123123
struct SyncList avmpack_data;
124124

src/libAtomVM/nifs.c

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ static term nif_base64_encode(Context *ctx, int argc, term argv[]);
177177
static term nif_base64_decode(Context *ctx, int argc, term argv[]);
178178
static term nif_base64_encode_to_string(Context *ctx, int argc, term argv[]);
179179
static term nif_base64_decode_to_string(Context *ctx, int argc, term argv[]);
180+
static term nif_code_all_available(Context *ctx, int argc, term argv[]);
181+
static term nif_code_all_loaded(Context *ctx, int argc, term argv[]);
180182
static term nif_code_load_abs(Context *ctx, int argc, term argv[]);
181183
static term nif_code_load_binary(Context *ctx, int argc, term argv[]);
182184
static term nif_lists_reverse(Context *ctx, int argc, term argv[]);
@@ -728,6 +730,16 @@ static const struct Nif base64_decode_to_string_nif =
728730
.base.type = NIFFunctionType,
729731
.nif_ptr = nif_base64_decode_to_string
730732
};
733+
static const struct Nif code_all_available_nif =
734+
{
735+
.base.type = NIFFunctionType,
736+
.nif_ptr = nif_code_all_available
737+
};
738+
static const struct Nif code_all_loaded_nif =
739+
{
740+
.base.type = NIFFunctionType,
741+
.nif_ptr = nif_code_all_loaded
742+
};
731743
static const struct Nif code_load_abs_nif =
732744
{
733745
.base.type = NIFFunctionType,
@@ -4435,6 +4447,121 @@ static term nif_base64_decode_to_string(Context *ctx, int argc, term argv[])
44354447
return base64_decode(ctx, argc, argv, false);
44364448
}
44374449

4450+
static term nif_code_all_loaded(Context *ctx, int argc, term argv[])
4451+
{
4452+
UNUSED(argc);
4453+
UNUSED(argv);
4454+
4455+
term result = term_nil();
4456+
int loaded_modules_count = ctx->global->loaded_modules_count;
4457+
if (UNLIKELY(memory_ensure_free(ctx, LIST_SIZE(loaded_modules_count, TUPLE_SIZE(2))) != MEMORY_GC_OK)) {
4458+
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
4459+
}
4460+
4461+
for (int ix = 0; ix < loaded_modules_count; ix++) {
4462+
Module *module = globalcontext_get_module_by_index(ctx->global, ix);
4463+
term module_tuple = term_alloc_tuple(2, &ctx->heap);
4464+
term_put_tuple_element(module_tuple, 0, module_get_name(module));
4465+
term_put_tuple_element(module_tuple, 1, UNDEFINED_ATOM);
4466+
result = term_list_prepend(module_tuple, result, &ctx->heap);
4467+
}
4468+
4469+
return result;
4470+
}
4471+
4472+
struct CodeAllAvailableAcc {
4473+
Context *ctx;
4474+
struct AVMPackData *avmpack_data;
4475+
term result;
4476+
size_t acc_count;
4477+
};
4478+
4479+
static void *nif_code_all_available_fold(void *accum, const void *section_ptr, uint32_t section_size, const void *beam_ptr, uint32_t flags, const char *section_name)
4480+
{
4481+
UNUSED(section_ptr);
4482+
UNUSED(section_size);
4483+
UNUSED(beam_ptr);
4484+
UNUSED(flags);
4485+
4486+
struct CodeAllAvailableAcc *acc = (struct CodeAllAvailableAcc *) accum;
4487+
size_t section_name_len = strlen(section_name);
4488+
if (section_name_len < 260) {
4489+
if (memcmp(".beam", section_name + section_name_len - 5, 5) == 0) {
4490+
bool loaded;
4491+
if (acc->avmpack_data->in_use) {
4492+
// Check if module is loaded
4493+
char atom_str[section_name_len - 5];
4494+
atom_str[0] = section_name_len - 5;
4495+
memcpy(atom_str + 1, section_name, atom_str[0]);
4496+
Module *loaded_module = globalcontext_get_module(acc->ctx->global, (AtomString) &atom_str);
4497+
loaded = loaded_module != NULL;
4498+
} else {
4499+
loaded = false;
4500+
}
4501+
if (!loaded) {
4502+
acc->acc_count++;
4503+
if (!term_is_invalid_term(acc->result)) {
4504+
term module_tuple = term_alloc_tuple(3, &acc->ctx->heap);
4505+
term_put_tuple_element(module_tuple, 0, term_from_const_binary(section_name, section_name_len - 5, &acc->ctx->heap, acc->ctx->global));
4506+
term_put_tuple_element(module_tuple, 1, UNDEFINED_ATOM);
4507+
term_put_tuple_element(module_tuple, 2, FALSE_ATOM);
4508+
acc->result = term_list_prepend(module_tuple, acc->result, &acc->ctx->heap);
4509+
}
4510+
}
4511+
}
4512+
}
4513+
return accum;
4514+
}
4515+
4516+
static term nif_code_all_available(Context *ctx, int argc, term argv[])
4517+
{
4518+
UNUSED(argc);
4519+
UNUSED(argv);
4520+
4521+
// We return the list of loaded modules and all modules that are
4522+
// found in loaded avm packs.
4523+
struct ListHead *item;
4524+
struct ListHead *avmpack_data = synclist_rdlock(&ctx->global->avmpack_data);
4525+
struct CodeAllAvailableAcc acc;
4526+
acc.ctx = ctx;
4527+
acc.result = term_invalid_term();
4528+
acc.acc_count = 0;
4529+
LIST_FOR_EACH (item, avmpack_data) {
4530+
struct AVMPackData *avmpack_data = GET_LIST_ENTRY(item, struct AVMPackData, avmpack_head);
4531+
acc.avmpack_data = avmpack_data;
4532+
avmpack_fold(&acc, avmpack_data->data, nif_code_all_available_fold);
4533+
}
4534+
4535+
size_t available_count = acc.acc_count + ctx->global->loaded_modules_count;
4536+
4537+
if (UNLIKELY(memory_ensure_free(ctx, LIST_SIZE(available_count, TUPLE_SIZE(3) + TERM_BOXED_REFC_BINARY_SIZE)) != MEMORY_GC_OK)) {
4538+
synclist_unlock(&ctx->global->avmpack_data);
4539+
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
4540+
}
4541+
4542+
// List may be incomplete if modules are loaded while we are iterating on them
4543+
acc.acc_count = 0;
4544+
acc.result = term_nil();
4545+
LIST_FOR_EACH (item, avmpack_data) {
4546+
struct AVMPackData *avmpack_data = GET_LIST_ENTRY(item, struct AVMPackData, avmpack_head);
4547+
acc.avmpack_data = avmpack_data;
4548+
avmpack_fold(&acc, avmpack_data->data, nif_code_all_available_fold);
4549+
}
4550+
synclist_unlock(&ctx->global->avmpack_data);
4551+
4552+
for (size_t ix = 0; ix < available_count - acc.acc_count; ix++) {
4553+
Module *module = globalcontext_get_module_by_index(ctx->global, ix);
4554+
term module_tuple = term_alloc_tuple(3, &ctx->heap);
4555+
AtomString module_atom_str = globalcontext_atomstring_from_term(ctx->global, module_get_name(module));
4556+
term_put_tuple_element(module_tuple, 0, term_from_const_binary(((const char *) module_atom_str) + 1, ((const char *) module_atom_str)[0], &ctx->heap, ctx->global));
4557+
term_put_tuple_element(module_tuple, 1, UNDEFINED_ATOM);
4558+
term_put_tuple_element(module_tuple, 2, TRUE_ATOM);
4559+
acc.result = term_list_prepend(module_tuple, acc.result, &ctx->heap);
4560+
}
4561+
4562+
return acc.result;
4563+
}
4564+
44384565
static term nif_code_load_abs(Context *ctx, int argc, term argv[])
44394566
{
44404567
UNUSED(argc);

src/libAtomVM/nifs.gperf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ atomvm:posix_unlink/1, IF_HAVE_UNLINK(&atomvm_posix_unlink_nif)
143143
atomvm:posix_clock_settime/2, IF_HAVE_CLOCK_SETTIME_OR_SETTIMEOFDAY(&atomvm_posix_clock_settime_nif)
144144
code:load_abs/1, &code_load_abs_nif
145145
code:load_binary/3, &code_load_binary_nif
146+
code:all_available/0, &code_all_available_nif
147+
code:all_loaded/0, &code_all_loaded_nif
146148
console:print/1, &console_print_nif
147149
base64:encode/1, &base64_encode_nif
148150
base64:decode/1, &base64_decode_nif

tests/erlang_tests/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,7 @@ compile_erlang(test_stacktrace)
476476
compile_erlang(small_big_ext)
477477
compile_erlang(test_crypto)
478478

479+
compile_erlang(test_code_all_available_loaded)
479480
compile_erlang(test_code_load_binary)
480481
compile_erlang(test_code_load_abs)
481482
compile_erlang(test_add_avm_pack_binary)
@@ -939,6 +940,7 @@ add_custom_target(erlang_test_modules DEPENDS
939940
small_big_ext.beam
940941
test_crypto.beam
941942

943+
test_code_all_available_loaded.beam
942944
test_code_load_binary.beam
943945
test_code_load_abs.beam
944946
test_add_avm_pack_binary.beam
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
%
2+
% This file is part of AtomVM.
3+
%
4+
% Copyright 2024 Paul Guyot <pguyot@kallisys.net>
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_code_all_available_loaded).
22+
23+
-export([start/0]).
24+
25+
start() ->
26+
ok = test_all_loaded(),
27+
ok = test_all_available(),
28+
0.
29+
30+
test_all_loaded() ->
31+
Loaded = code:all_loaded(),
32+
true = length(Loaded) > 0,
33+
true = has_key(?MODULE, Loaded),
34+
ok.
35+
36+
test_all_available() ->
37+
FunctionExists =
38+
case erlang:system_info(machine) of
39+
"BEAM" ->
40+
erlang:system_info(version) >= "11.";
41+
"ATOM" ->
42+
true
43+
end,
44+
test_all_available0(FunctionExists).
45+
46+
test_all_available0(false) ->
47+
ok;
48+
test_all_available0(true) ->
49+
Loaded = code:all_loaded(),
50+
Available = code:all_available(),
51+
true = length(Available) >= length(Loaded),
52+
AvailableLoaded = [
53+
{binary_to_atom(iolist_to_binary(Module), utf8), File}
54+
|| {Module, File, true} <- Available
55+
],
56+
true = set_equal(Loaded, AvailableLoaded),
57+
ok.
58+
59+
has_key(_Module, []) -> false;
60+
has_key(Module, [{Module, _} | _]) -> true;
61+
has_key(Module, [_H | Tail]) -> has_key(Module, Tail).
62+
63+
set_equal(L1, L2) ->
64+
set_equal0(L1, L2, []).
65+
66+
set_equal0([], [], []) -> true;
67+
set_equal0([H | T1], [H | T2], Acc) -> set_equal0(T1, T2 ++ Acc, []);
68+
set_equal0([_H1 | _T1] = L1, [H2 | T2], Acc) -> set_equal0(L1, T2, [H2 | Acc]);
69+
set_equal0(_L1, [], _Acc) -> false;
70+
set_equal0([], _L2, _Acc) -> false.

tests/test.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,7 @@ struct Test tests[] = {
487487
TEST_CASE_ATOMVM_ONLY(test_regecho_driver, 11),
488488
TEST_CASE_ATOMVM_ONLY(test_send_nif_and_echo, 22),
489489

490+
TEST_CASE(test_code_all_available_loaded),
490491
TEST_CASE_EXPECTED(test_code_load_binary, 24),
491492
TEST_CASE_EXPECTED(test_code_load_abs, 24),
492493
TEST_CASE_ATOMVM_ONLY(test_add_avm_pack_binary, 24),

0 commit comments

Comments
 (0)