Skip to content

Commit 1e5e1e9

Browse files
committed
Merge pull request #1285 from pguyot/w38/add-code-ensure_loaded
Add `code:ensure_loaded/1` 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 256993d + 5588db8 commit 1e5e1e9

File tree

9 files changed

+122
-6
lines changed

9 files changed

+122
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ also non string parameters (e.g. `Enum.join([1, 2], ",")`
2424
- Add support for `handle_continue` callback in `gen_server`
2525
- Support for Elixir `List.Chars` protocol
2626
- Support for `gen_server:start_monitor/3,4`
27+
- Support for `code:ensure_loaded/1`
2728

2829
### Changed
2930

libs/estdlib/src/code.erl

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

27-
-export([load_abs/1, load_binary/3]).
27+
-export([load_abs/1, load_binary/3, ensure_loaded/1]).
2828

2929
%%-----------------------------------------------------------------------------
3030
%% @param Filename path to the beam to open, without .beams suffix
@@ -52,3 +52,18 @@ load_abs(_Filename) ->
5252
error | {module, module()}.
5353
load_binary(_Module, _Filename, _Binary) ->
5454
erlang:nif_error(undefined).
55+
56+
%%-----------------------------------------------------------------------------
57+
%% @param Module module to load
58+
%% @returns Tuple `{module, Module}' if module is loaded or `{error, embedded}'
59+
%% @doc Try to load a module if it's not already loaded. AtomVM works in
60+
%% an embedded-like mode where modules are loaded at start-up but modules
61+
%% can be loaded explicitely as well (especially from a binary with `load_binary/3').
62+
%% So this function can be used to determine if a module is loaded.
63+
%% It is called by Elixir Code module.
64+
%% @end
65+
%%-----------------------------------------------------------------------------
66+
-spec ensure_loaded(Module) -> {module, Module} | {error, embedded | any()} when
67+
Module :: atom().
68+
ensure_loaded(_Module) ->
69+
erlang:nif_error(undefined).

libs/exavmlib/lib/Code.ex

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,20 @@ defmodule Code do
2323
@moduledoc """
2424
This module is to satisfy certain code loading checks in Elixir,
2525
specifically with regards to protocols support.
26-
The functions are noop, and doesn't perform the actual checks,
27-
as they are not relevant on AtomVM.
2826
"""
2927

3028
@doc """
3129
required for protocols to work with Elixir >= 1.16, due to code loading checks.
3230
"""
3331
def ensure_compiled(module) do
34-
{:module, module}
32+
:code.ensure_loaded(module)
3533
end
3634

3735
@doc """
3836
previously required for protocols support, due to code loading checks.
3937
"""
4038
@deprecated "Use Code.ensure_compiled/1 instead"
41-
def ensure_compiled?(_) do
42-
true
39+
def ensure_compiled?(module) do
40+
match?({:module, ^module}, ensure_compiled(module))
4341
end
4442
end

src/libAtomVM/nifs.c

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ static term nif_base64_encode_to_string(Context *ctx, int argc, term argv[]);
172172
static term nif_base64_decode_to_string(Context *ctx, int argc, term argv[]);
173173
static term nif_code_load_abs(Context *ctx, int argc, term argv[]);
174174
static term nif_code_load_binary(Context *ctx, int argc, term argv[]);
175+
static term nif_code_ensure_loaded(Context *ctx, int argc, term argv[]);
175176
static term nif_lists_reverse(Context *ctx, int argc, term argv[]);
176177
static term nif_maps_from_keys(Context *ctx, int argc, term argv[]);
177178
static term nif_maps_next(Context *ctx, int argc, term argv[]);
@@ -702,6 +703,11 @@ static const struct Nif code_load_binary_nif =
702703
.base.type = NIFFunctionType,
703704
.nif_ptr = nif_code_load_binary
704705
};
706+
static const struct Nif code_ensure_loaded_nif =
707+
{
708+
.base.type = NIFFunctionType,
709+
.nif_ptr = nif_code_ensure_loaded
710+
};
705711
static const struct Nif lists_reverse_nif =
706712
{
707713
.base.type = NIFFunctionType,
@@ -4351,6 +4357,37 @@ static term nif_code_load_binary(Context *ctx, int argc, term argv[])
43514357
return result;
43524358
}
43534359

4360+
static const char *const embedded_atom = "\x8" "embedded";
4361+
4362+
static term nif_code_ensure_loaded(Context *ctx, int argc, term argv[])
4363+
{
4364+
UNUSED(argc);
4365+
4366+
term module_atom = argv[0];
4367+
if (UNLIKELY(!term_is_atom(module_atom))) {
4368+
RAISE_ERROR(BADARG_ATOM);
4369+
}
4370+
4371+
AtomString module_string = globalcontext_atomstring_from_term(ctx->global, module_atom);
4372+
Module *found_module = globalcontext_get_module(ctx->global, module_string);
4373+
4374+
if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2)) != MEMORY_GC_OK)) {
4375+
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
4376+
}
4377+
4378+
term result = term_alloc_tuple(2, &ctx->heap);
4379+
4380+
if (UNLIKELY(!found_module)) {
4381+
term_put_tuple_element(result, 0, ERROR_ATOM);
4382+
term_put_tuple_element(result, 1, globalcontext_make_atom(ctx->global, embedded_atom));
4383+
} else {
4384+
term_put_tuple_element(result, 0, MODULE_ATOM);
4385+
term_put_tuple_element(result, 1, module_atom);
4386+
}
4387+
4388+
return result;
4389+
}
4390+
43544391
static term nif_lists_reverse(Context *ctx, int argc, term argv[])
43554392
{
43564393
// Compared to erlang version, compute the length of the list and allocate

src/libAtomVM/nifs.gperf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ atomvm:posix_unlink/1, IF_HAVE_UNLINK(&atomvm_posix_unlink_nif)
140140
atomvm:posix_clock_settime/2, IF_HAVE_CLOCK_SETTIME_OR_SETTIMEOFDAY(&atomvm_posix_clock_settime_nif)
141141
code:load_abs/1, &code_load_abs_nif
142142
code:load_binary/3, &code_load_binary_nif
143+
code:ensure_loaded/1, &code_ensure_loaded_nif
143144
console:print/1, &console_print_nif
144145
base64:encode/1, &base64_encode_nif
145146
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
@@ -483,6 +483,7 @@ compile_erlang(test_crypto)
483483

484484
compile_erlang(test_code_load_binary)
485485
compile_erlang(test_code_load_abs)
486+
compile_erlang(test_code_ensure_loaded)
486487
compile_erlang(test_add_avm_pack_binary)
487488
compile_erlang(test_add_avm_pack_file)
488489
compile_erlang(test_close_avm_pack)
@@ -953,6 +954,7 @@ add_custom_target(erlang_test_modules DEPENDS
953954

954955
test_code_load_binary.beam
955956
test_code_load_abs.beam
957+
test_code_ensure_loaded.beam
956958
test_add_avm_pack_binary.beam
957959
test_add_avm_pack_file.beam
958960
test_close_avm_pack.beam
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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_ensure_loaded).
22+
23+
-export([start/0]).
24+
25+
start() ->
26+
test_self_module(),
27+
test_code(),
28+
test_non_existing_module(),
29+
test_badarg(),
30+
0.
31+
32+
test_self_module() ->
33+
{module, ?MODULE} = code:ensure_loaded(?MODULE),
34+
ok.
35+
36+
test_code() ->
37+
case erlang:system_info(machine) of
38+
"BEAM" ->
39+
{module, code} = code:ensure_loaded(code),
40+
ok;
41+
"ATOM" ->
42+
% This isn't supported for now as this test is ran without the
43+
% Erlang module being loaded.
44+
ok
45+
end,
46+
ok.
47+
48+
test_non_existing_module() ->
49+
{error, _} = code:ensure_loaded(non_existing_module),
50+
ok.
51+
52+
test_badarg() ->
53+
ok =
54+
try
55+
code:ensure_loaded("non_existing_module"),
56+
failure
57+
catch
58+
_:_ -> ok
59+
end.

tests/erlang_tests/test_code_load_binary.erl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626

2727
start() ->
2828
Bin = ?EXPORT_TEST_MODULE_DATA,
29+
{error, _} = code:ensure_loaded(export_test_module),
2930
{module, export_test_module} = code:load_binary(
3031
export_test_module, "export_test_module.beam", Bin
3132
),
33+
{module, export_test_module} = code:ensure_loaded(export_test_module),
3234
export_test_module:exported_func(4).

tests/test.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,7 @@ struct Test tests[] = {
522522

523523
TEST_CASE_EXPECTED(test_code_load_binary, 24),
524524
TEST_CASE_EXPECTED(test_code_load_abs, 24),
525+
TEST_CASE(test_code_ensure_loaded),
525526
TEST_CASE_ATOMVM_ONLY(test_add_avm_pack_binary, 24),
526527
TEST_CASE_ATOMVM_ONLY(test_add_avm_pack_file, 24),
527528
TEST_CASE_ATOMVM_ONLY(test_close_avm_pack, 0),

0 commit comments

Comments
 (0)