diff --git a/libs/estdlib/src/erlang.erl b/libs/estdlib/src/erlang.erl index bb4cc85ff..0df9e7f59 100644 --- a/libs/estdlib/src/erlang.erl +++ b/libs/estdlib/src/erlang.erl @@ -107,7 +107,8 @@ term_to_binary/1, timestamp/0, universaltime/0, - localtime/0 + localtime/0, + setnode/2 ]). -export_type([ @@ -1254,3 +1255,8 @@ universaltime() -> -spec localtime() -> calendar:datetime(). localtime() -> erlang:nif_error(undefined). + +%% @hidden +-spec setnode(atom(), pos_integer()) -> true. +setnode(_NodeName, _Creation) -> + erlang:nif_error(undefined). diff --git a/src/libAtomVM/bif.c b/src/libAtomVM/bif.c index 5888d645f..579fd16fe 100644 --- a/src/libAtomVM/bif.c +++ b/src/libAtomVM/bif.c @@ -72,6 +72,11 @@ term bif_erlang_self_0(Context *ctx) return term_from_local_process_id(ctx->process_id); } +term bif_erlang_node_0(Context *ctx) +{ + return ctx->global->node_name; +} + term bif_erlang_byte_size_1(Context *ctx, uint32_t fail_label, int live, term arg1) { UNUSED(live); diff --git a/src/libAtomVM/bif.h b/src/libAtomVM/bif.h index 5d257588f..5fda0e947 100644 --- a/src/libAtomVM/bif.h +++ b/src/libAtomVM/bif.h @@ -42,6 +42,7 @@ extern "C" { const struct ExportedFunction *bif_registry_get_handler(AtomString module, AtomString function, int arity); term bif_erlang_self_0(Context *ctx); +term bif_erlang_node_0(Context *ctx); term bif_erlang_byte_size_1(Context *ctx, uint32_t fail_label, int live, term arg1); term bif_erlang_bit_size_1(Context *ctx, uint32_t fail_label, int live, term arg1); term bif_erlang_length_1(Context *ctx, uint32_t fail_label, int live, term arg1); diff --git a/src/libAtomVM/bifs.gperf b/src/libAtomVM/bifs.gperf index d441da07a..8c204ee2f 100644 --- a/src/libAtomVM/bifs.gperf +++ b/src/libAtomVM/bifs.gperf @@ -36,6 +36,7 @@ struct BifNameAndPtr }; %% erlang:self/0, {.bif.base.type = BIFFunctionType, .bif.bif0_ptr = bif_erlang_self_0} +erlang:node/0, {.bif.base.type = BIFFunctionType, .bif.bif0_ptr = bif_erlang_node_0} erlang:length/1, {.gcbif.base.type = GCBIFFunctionType, .gcbif.gcbif1_ptr = bif_erlang_length_1} erlang:byte_size/1, {.gcbif.base.type = GCBIFFunctionType, .gcbif.gcbif1_ptr = bif_erlang_byte_size_1} erlang:bit_size/1, {.gcbif.base.type = GCBIFFunctionType, .gcbif.gcbif1_ptr = bif_erlang_bit_size_1} diff --git a/src/libAtomVM/context.c b/src/libAtomVM/context.c index da0589ff7..555556378 100644 --- a/src/libAtomVM/context.c +++ b/src/libAtomVM/context.c @@ -52,6 +52,7 @@ #define BYTES_PER_TERM (TERM_BITS / 8) static struct ResourceMonitor *context_monitors_handle_terminate(Context *ctx); +static void context_distribution_handle_terminate(Context *ctx); static void destroy_extended_registers(Context *ctx, unsigned int live); Context *context_new(GlobalContext *glb) @@ -132,6 +133,11 @@ void context_destroy(Context *ctx) // Ensure process is not registered globalcontext_maybe_unregister_process_id(ctx->global, ctx->process_id); + // Handle distribution termination + if (UNLIKELY(ctx->flags & Distribution)) { + context_distribution_handle_terminate(ctx); + } + // When monitor message is sent, process is no longer in the table // and is no longer registered either. struct ResourceMonitor *resource_monitor = context_monitors_handle_terminate(ctx); @@ -448,6 +454,14 @@ static struct ResourceMonitor *context_monitors_handle_terminate(Context *ctx) return result; } +static void context_distribution_handle_terminate(Context *ctx) +{ + // For now, the only process with Distribution flag set is net_kernel. + GlobalContext *glb = ctx->global; + glb->node_name = NONODE_AT_NOHOST_ATOM; + glb->creation = 0; +} + int context_link(Context *ctx, term link_pid) { struct ListHead *item; diff --git a/src/libAtomVM/context.h b/src/libAtomVM/context.h index b14089c15..527a37fa5 100644 --- a/src/libAtomVM/context.h +++ b/src/libAtomVM/context.h @@ -65,6 +65,7 @@ enum ContextFlags Ready = 8, Killed = 16, Trap = 32, + Distribution = 64, }; enum HeapGrowthStrategy diff --git a/src/libAtomVM/defaultatoms.c b/src/libAtomVM/defaultatoms.c index f10d24293..228dd79fc 100644 --- a/src/libAtomVM/defaultatoms.c +++ b/src/libAtomVM/defaultatoms.c @@ -162,6 +162,9 @@ static const char *const unicode_atom = "\x7" "unicode"; static const char *const global_atom = "\x6" "global"; +static const char *const nonode_at_nohost_atom = "\xD" "nonode@nohost"; +static const char *const net_kernel_atom = "\xA" "net_kernel"; + void defaultatoms_init(GlobalContext *glb) { int ok = 1; @@ -308,6 +311,9 @@ void defaultatoms_init(GlobalContext *glb) ok &= globalcontext_insert_atom(glb, global_atom) == GLOBAL_ATOM_INDEX; + ok &= globalcontext_insert_atom(glb, nonode_at_nohost_atom) == NONODE_AT_NOHOST_ATOM_INDEX; + ok &= globalcontext_insert_atom(glb, net_kernel_atom) == NET_KERNEL_ATOM_INDEX; + if (!ok) { AVM_ABORT(); } diff --git a/src/libAtomVM/defaultatoms.h b/src/libAtomVM/defaultatoms.h index 285ee4f0e..75b3ac9a5 100644 --- a/src/libAtomVM/defaultatoms.h +++ b/src/libAtomVM/defaultatoms.h @@ -171,7 +171,10 @@ extern "C" { #define GLOBAL_ATOM_INDEX 111 -#define PLATFORM_ATOMS_BASE_INDEX 112 +#define NONODE_AT_NOHOST_ATOM_INDEX 112 +#define NET_KERNEL_ATOM_INDEX 113 + +#define PLATFORM_ATOMS_BASE_INDEX 114 #define FALSE_ATOM TERM_FROM_ATOM_INDEX(FALSE_ATOM_INDEX) #define TRUE_ATOM TERM_FROM_ATOM_INDEX(TRUE_ATOM_INDEX) @@ -317,6 +320,9 @@ extern "C" { #define GLOBAL_ATOM TERM_FROM_ATOM_INDEX(GLOBAL_ATOM_INDEX) +#define NONODE_AT_NOHOST_ATOM TERM_FROM_ATOM_INDEX(NONODE_AT_NOHOST_ATOM_INDEX) +#define NET_KERNEL_ATOM TERM_FROM_ATOM_INDEX(NET_KERNEL_ATOM_INDEX) + void defaultatoms_init(GlobalContext *glb); void platform_defaultatoms_init(GlobalContext *glb); diff --git a/src/libAtomVM/globalcontext.c b/src/libAtomVM/globalcontext.c index 297e6c347..f10001828 100644 --- a/src/libAtomVM/globalcontext.c +++ b/src/libAtomVM/globalcontext.c @@ -122,6 +122,9 @@ GlobalContext *globalcontext_new() smp_spinlock_init(&glb->ref_ticks_spinlock); #endif + glb->node_name = NONODE_AT_NOHOST_ATOM; + glb->creation = 0; + #if HAVE_OPEN && HAVE_CLOSE ErlNifEnv env; erl_nif_env_partial_init_from_globalcontext(&env, glb); diff --git a/src/libAtomVM/globalcontext.h b/src/libAtomVM/globalcontext.h index b4d24efc8..1aadb2406 100644 --- a/src/libAtomVM/globalcontext.h +++ b/src/libAtomVM/globalcontext.h @@ -151,6 +151,9 @@ struct GlobalContext SpinLock env_spinlock; #endif + term ATOMIC node_name; + uint32_t ATOMIC creation; + #if HAVE_OPEN && HAVE_CLOSE ErlNifResourceType *posix_fd_resource_type; #endif diff --git a/src/libAtomVM/nifs.c b/src/libAtomVM/nifs.c index a4fa6804f..804814605 100644 --- a/src/libAtomVM/nifs.c +++ b/src/libAtomVM/nifs.c @@ -55,6 +55,7 @@ #include "synclist.h" #include "sys.h" #include "term.h" +#include "term_typedef.h" #include "unicode.h" #include "utils.h" @@ -164,6 +165,7 @@ static term nif_erlang_function_exported(Context *ctx, int argc, term argv[]); static term nif_erlang_garbage_collect(Context *ctx, int argc, term argv[]); static term nif_erlang_group_leader(Context *ctx, int argc, term argv[]); static term nif_erlang_get_module_info(Context *ctx, int argc, term argv[]); +static term nif_erlang_setnode(Context *ctx, int argc, term argv[]); static term nif_erlang_memory(Context *ctx, int argc, term argv[]); static term nif_erlang_monitor(Context *ctx, int argc, term argv[]); static term nif_erlang_demonitor(Context *ctx, int argc, term argv[]); @@ -653,6 +655,12 @@ static const struct Nif get_module_info_nif = .nif_ptr = nif_erlang_get_module_info }; +static const struct Nif setnode_nif = +{ + .base.type = NIFFunctionType, + .nif_ptr = nif_erlang_setnode +}; + static const struct Nif raise_nif = { .base.type = NIFFunctionType, @@ -3912,6 +3920,42 @@ static term nif_erlang_get_module_info(Context *ctx, int argc, term argv[]) return result; } +static term nif_erlang_setnode(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + VALIDATE_VALUE(argv[0], term_is_atom); + VALIDATE_VALUE(argv[1], term_is_integer); + + avm_int64_t creation = term_maybe_unbox_int64(argv[1]); + if (UNLIKELY(creation < 0 || creation > ((avm_int64_t) 1) << 32)) { + RAISE_ERROR(BADARG_ATOM); + } + + int netkernel_pid = globalcontext_get_registered_process(ctx->global, NET_KERNEL_ATOM_INDEX); + if (UNLIKELY(netkernel_pid == 0)) { + RAISE_ERROR(BADARG_ATOM); + } + + Context *net_kernel = globalcontext_get_process_lock(ctx->global, netkernel_pid); + if (IS_NULL_PTR(net_kernel)) { + RAISE_ERROR(BADARG_ATOM); + } + + // Take advantage of the lock on net_kernel process + if (UNLIKELY(ctx->global->node_name != NONODE_AT_NOHOST_ATOM)) { + globalcontext_get_process_unlock(ctx->global, net_kernel); + RAISE_ERROR(BADARG_ATOM); + } + ctx->global->node_name = argv[0]; + ctx->global->creation = (uint32_t) creation; + + context_update_flags(net_kernel, ~NoFlags, Distribution); + globalcontext_get_process_unlock(ctx->global, net_kernel); + + return TRUE_ATOM; +} + struct RefcBinaryAVMPack { struct AVMPackData base; diff --git a/src/libAtomVM/nifs.gperf b/src/libAtomVM/nifs.gperf index 120e7e892..14968532c 100644 --- a/src/libAtomVM/nifs.gperf +++ b/src/libAtomVM/nifs.gperf @@ -126,6 +126,7 @@ erlang:group_leader/0, &group_leader_nif erlang:group_leader/2, &group_leader_nif erlang:get_module_info/1, &get_module_info_nif erlang:get_module_info/2, &get_module_info_nif +erlang:setnode/2, &setnode_nif erts_debug:flat_size/1, &flat_size_nif ets:new/2, &ets_new_nif ets:insert/2, &ets_insert_nif diff --git a/tests/erlang_tests/CMakeLists.txt b/tests/erlang_tests/CMakeLists.txt index 383f6c793..da48bba51 100644 --- a/tests/erlang_tests/CMakeLists.txt +++ b/tests/erlang_tests/CMakeLists.txt @@ -513,6 +513,7 @@ compile_erlang(maps_nifs) compile_erlang(test_raw_raise) compile_erlang(test_ets) +compile_erlang(test_node) add_custom_target(erlang_test_modules DEPENDS code_load_files @@ -992,4 +993,5 @@ add_custom_target(erlang_test_modules DEPENDS test_raw_raise.beam test_ets.beam + test_node.beam ) diff --git a/tests/erlang_tests/test_node.erl b/tests/erlang_tests/test_node.erl new file mode 100644 index 000000000..0518548c5 --- /dev/null +++ b/tests/erlang_tests/test_node.erl @@ -0,0 +1,54 @@ +% +% This file is part of AtomVM. +% +% Copyright 2024 Paul Guyot +% +% 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_node). + +-export([start/0]). + +start() -> + ok = test_node_no_distribution(), + ok = test_node_distribution(), + 0. + +test_node_no_distribution() -> + nonode@nohost = node(), + nonode@nohost = erlang:node(), + ok. + +test_node_distribution() -> + {NetKernelPid, MonitorRef} = spawn_opt( + fun() -> + receive + quit -> ok + end + end, + [monitor] + ), + register(net_kernel, NetKernelPid), + true = erlang:setnode(test@test_node, 42), + test@test_node = node(), + NetKernelPid ! quit, + ok = + receive + {'DOWN', MonitorRef, process, NetKernelPid, normal} -> ok + after 1000 -> timeout + end, + nonode@nohost = node(), + ok. diff --git a/tests/test.c b/tests/test.c index ffd92a3e8..19f03be73 100644 --- a/tests/test.c +++ b/tests/test.c @@ -571,6 +571,7 @@ struct Test tests[] = { TEST_CASE(test_raw_raise), TEST_CASE(test_ets), + TEST_CASE(test_node), // TEST CRASHES HERE: TEST_CASE(memlimit),