Skip to content

Commit

Permalink
Merge pull request #1285 from pguyot/w38/add-code-ensure_loaded
Browse files Browse the repository at this point in the history
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
  • Loading branch information
bettio committed Sep 25, 2024
2 parents 256993d + 5588db8 commit 1e5e1e9
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ also non string parameters (e.g. `Enum.join([1, 2], ",")`
- Add support for `handle_continue` callback in `gen_server`
- Support for Elixir `List.Chars` protocol
- Support for `gen_server:start_monitor/3,4`
- Support for `code:ensure_loaded/1`

### Changed

Expand Down
17 changes: 16 additions & 1 deletion libs/estdlib/src/code.erl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
%%-----------------------------------------------------------------------------
-module(code).

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

%%-----------------------------------------------------------------------------
%% @param Filename path to the beam to open, without .beams suffix
Expand Down Expand Up @@ -52,3 +52,18 @@ load_abs(_Filename) ->
error | {module, module()}.
load_binary(_Module, _Filename, _Binary) ->
erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param Module module to load
%% @returns Tuple `{module, Module}' if module is loaded or `{error, embedded}'
%% @doc Try to load a module if it's not already loaded. AtomVM works in
%% an embedded-like mode where modules are loaded at start-up but modules
%% can be loaded explicitely as well (especially from a binary with `load_binary/3').
%% So this function can be used to determine if a module is loaded.
%% It is called by Elixir Code module.
%% @end
%%-----------------------------------------------------------------------------
-spec ensure_loaded(Module) -> {module, Module} | {error, embedded | any()} when
Module :: atom().
ensure_loaded(_Module) ->
erlang:nif_error(undefined).
8 changes: 3 additions & 5 deletions libs/exavmlib/lib/Code.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,20 @@ defmodule Code do
@moduledoc """
This module is to satisfy certain code loading checks in Elixir,
specifically with regards to protocols support.
The functions are noop, and doesn't perform the actual checks,
as they are not relevant on AtomVM.
"""

@doc """
required for protocols to work with Elixir >= 1.16, due to code loading checks.
"""
def ensure_compiled(module) do
{:module, module}
:code.ensure_loaded(module)
end

@doc """
previously required for protocols support, due to code loading checks.
"""
@deprecated "Use Code.ensure_compiled/1 instead"
def ensure_compiled?(_) do
true
def ensure_compiled?(module) do
match?({:module, ^module}, ensure_compiled(module))
end
end
37 changes: 37 additions & 0 deletions src/libAtomVM/nifs.c
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ static term nif_base64_encode_to_string(Context *ctx, int argc, term argv[]);
static term nif_base64_decode_to_string(Context *ctx, int argc, term argv[]);
static term nif_code_load_abs(Context *ctx, int argc, term argv[]);
static term nif_code_load_binary(Context *ctx, int argc, term argv[]);
static term nif_code_ensure_loaded(Context *ctx, int argc, term argv[]);
static term nif_lists_reverse(Context *ctx, int argc, term argv[]);
static term nif_maps_from_keys(Context *ctx, int argc, term argv[]);
static term nif_maps_next(Context *ctx, int argc, term argv[]);
Expand Down Expand Up @@ -702,6 +703,11 @@ static const struct Nif code_load_binary_nif =
.base.type = NIFFunctionType,
.nif_ptr = nif_code_load_binary
};
static const struct Nif code_ensure_loaded_nif =
{
.base.type = NIFFunctionType,
.nif_ptr = nif_code_ensure_loaded
};
static const struct Nif lists_reverse_nif =
{
.base.type = NIFFunctionType,
Expand Down Expand Up @@ -4351,6 +4357,37 @@ static term nif_code_load_binary(Context *ctx, int argc, term argv[])
return result;
}

static const char *const embedded_atom = "\x8" "embedded";

static term nif_code_ensure_loaded(Context *ctx, int argc, term argv[])
{
UNUSED(argc);

term module_atom = argv[0];
if (UNLIKELY(!term_is_atom(module_atom))) {
RAISE_ERROR(BADARG_ATOM);
}

AtomString module_string = globalcontext_atomstring_from_term(ctx->global, module_atom);
Module *found_module = globalcontext_get_module(ctx->global, module_string);

if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2)) != MEMORY_GC_OK)) {
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
}

term result = term_alloc_tuple(2, &ctx->heap);

if (UNLIKELY(!found_module)) {
term_put_tuple_element(result, 0, ERROR_ATOM);
term_put_tuple_element(result, 1, globalcontext_make_atom(ctx->global, embedded_atom));
} else {
term_put_tuple_element(result, 0, MODULE_ATOM);
term_put_tuple_element(result, 1, module_atom);
}

return result;
}

static term nif_lists_reverse(Context *ctx, int argc, term argv[])
{
// Compared to erlang version, compute the length of the list and allocate
Expand Down
1 change: 1 addition & 0 deletions src/libAtomVM/nifs.gperf
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ atomvm:posix_unlink/1, IF_HAVE_UNLINK(&atomvm_posix_unlink_nif)
atomvm:posix_clock_settime/2, IF_HAVE_CLOCK_SETTIME_OR_SETTIMEOFDAY(&atomvm_posix_clock_settime_nif)
code:load_abs/1, &code_load_abs_nif
code:load_binary/3, &code_load_binary_nif
code:ensure_loaded/1, &code_ensure_loaded_nif
console:print/1, &console_print_nif
base64:encode/1, &base64_encode_nif
base64:decode/1, &base64_decode_nif
Expand Down
2 changes: 2 additions & 0 deletions tests/erlang_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,7 @@ compile_erlang(test_crypto)

compile_erlang(test_code_load_binary)
compile_erlang(test_code_load_abs)
compile_erlang(test_code_ensure_loaded)
compile_erlang(test_add_avm_pack_binary)
compile_erlang(test_add_avm_pack_file)
compile_erlang(test_close_avm_pack)
Expand Down Expand Up @@ -953,6 +954,7 @@ add_custom_target(erlang_test_modules DEPENDS

test_code_load_binary.beam
test_code_load_abs.beam
test_code_ensure_loaded.beam
test_add_avm_pack_binary.beam
test_add_avm_pack_file.beam
test_close_avm_pack.beam
Expand Down
59 changes: 59 additions & 0 deletions tests/erlang_tests/test_code_ensure_loaded.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
%
% This file is part of AtomVM.
%
% Copyright 2024 Paul Guyot <pguyot@kallisys.net>
%
% Licensed under the Apache License, Version 2.0 (the "License");
% you may not use this file except in compliance with the License.
% You may obtain a copy of the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS,
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
% See the License for the specific language governing permissions and
% limitations under the License.
%
% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
%

-module(test_code_ensure_loaded).

-export([start/0]).

start() ->
test_self_module(),
test_code(),
test_non_existing_module(),
test_badarg(),
0.

test_self_module() ->
{module, ?MODULE} = code:ensure_loaded(?MODULE),
ok.

test_code() ->
case erlang:system_info(machine) of
"BEAM" ->
{module, code} = code:ensure_loaded(code),
ok;
"ATOM" ->
% This isn't supported for now as this test is ran without the
% Erlang module being loaded.
ok
end,
ok.

test_non_existing_module() ->
{error, _} = code:ensure_loaded(non_existing_module),
ok.

test_badarg() ->
ok =
try
code:ensure_loaded("non_existing_module"),
failure
catch
_:_ -> ok
end.
2 changes: 2 additions & 0 deletions tests/erlang_tests/test_code_load_binary.erl
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@

start() ->
Bin = ?EXPORT_TEST_MODULE_DATA,
{error, _} = code:ensure_loaded(export_test_module),
{module, export_test_module} = code:load_binary(
export_test_module, "export_test_module.beam", Bin
),
{module, export_test_module} = code:ensure_loaded(export_test_module),
export_test_module:exported_func(4).
1 change: 1 addition & 0 deletions tests/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,7 @@ struct Test tests[] = {

TEST_CASE_EXPECTED(test_code_load_binary, 24),
TEST_CASE_EXPECTED(test_code_load_abs, 24),
TEST_CASE(test_code_ensure_loaded),
TEST_CASE_ATOMVM_ONLY(test_add_avm_pack_binary, 24),
TEST_CASE_ATOMVM_ONLY(test_add_avm_pack_file, 24),
TEST_CASE_ATOMVM_ONLY(test_close_avm_pack, 0),
Expand Down

0 comments on commit 1e5e1e9

Please sign in to comment.