Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement erlang:exit/2 #766

Merged
merged 1 commit into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,10 @@ functions that default to `?ATOMVM_NVS_NS` are deprecated now).
- Added `unicode` module with `characters_to_list/1,2` and `characters_to_binary/1,2,3` functions
- Added support for `crypto:hash/2` (ESP32 and generic_unix with openssl)
- Added erlang:spawn_link/1,3
- Added erlang:exit/2
- Added links to process_info/2
- Added lists:usort/1,2
- Added missing documentation and specifications for available nifs

### Fixed
- Fixed issue with formatting integers with io:format() on STM32 platform
Expand All @@ -81,6 +83,8 @@ functions that default to `?ATOMVM_NVS_NS` are deprecated now).
- Fixed numerous bugs in memory allocations that could crash the VM
- Fixed SNTP support that had been broken in IDF 4.x builds
- Fixed `erlang:send/2` not sending to registered name
- Fixed incorrect exit reason for exceptions of class exit
- Fixed several incorrect type specifications

### Breaking Changes

Expand Down
49 changes: 49 additions & 0 deletions libs/estdlib/src/erlang.erl
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@
monitor/2,
demonitor/1,
demonitor/2,
exit/1,
exit/2,
open_port/2,
system_time/1,
group_leader/0,
Expand Down Expand Up @@ -929,6 +931,53 @@ demonitor(_Monitor) ->
demonitor(_Monitor, _Options) ->
erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param Reason reason for exit
%% @doc Raises an exception of class `exit' with reason `Reason'.
%% The exception can be caught. If it is not, the process exits.
%% If the exception is not caught the signal is sent to linked processes.
%% In this case, if `Reason' is `kill', it is not transformed into `killed' and
%% linked processes can trap it (unlike `exit/2').
%% @end
%%-----------------------------------------------------------------------------
-spec exit(Reason :: any()) -> no_return().
exit(_Reason) ->
erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param Process target process
%% @param Reason reason for exit
%% @returns `true'
%% @doc Send an exit signal to target process.
%% The consequences of the exit signal depends on `Reason', on whether
%% `Process' is self() or another process and whether target process is
%% trapping exit.
%% If `Reason' is not `kill' nor `normal':
%% <ul>
%% <li>If target process is not trapping exits, it exits with `Reason'</li>
%% <li>If traget process is trapping exits, it receives a message
%% ``{'EXIT', From, Reason}'' where `From' is the caller of `exit/2'.</li>
%% </ul>
%% If `Reason' is `kill', the target process exits with `Reason' changed to
%% `killed'.
%% If `Reason' is `normal' and `Process' is not `self()':
%% <ul>
%% <li>If target process is not trapping exits, nothing happens.</li>
%% <li>If traget process is trapping exits, it receives a message
%% ``{'EXIT', From, normal}'' where `From' is the caller of `exit/2'.</li>
%% </ul>
%% If `Reason' is `normal' and `Process' is `self()':
%% <ul>
%% <li>If target process is not trapping exits, it exits with `normal'.</li>
%% <li>If traget process is trapping exits, it receives a message
%% ``{'EXIT', From, normal}'' where `From' is the caller of `exit/2'.</li>
%% </ul>
%% @end
%%-----------------------------------------------------------------------------
-spec exit(Process :: pid(), Reason :: any()) -> true.
exit(_Process, _Reason) ->
erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param PortName Tuple {spawn, Name} identifying the port
%% @param Options Options, meaningful for the port
Expand Down
4 changes: 4 additions & 0 deletions src/libAtomVM/defaultatoms.c
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ static const char *const exports_atom = "\x7" "exports";

static const char *const incomplete_atom = "\xA" "incomplete";

static const char *const kill_atom = "\x4" "kill";
static const char *const killed_atom = "\x6" "killed";
static const char *const links_atom = "\x5" "links";

void defaultatoms_init(GlobalContext *glb)
Expand Down Expand Up @@ -276,6 +278,8 @@ void defaultatoms_init(GlobalContext *glb)

ok &= globalcontext_insert_atom(glb, incomplete_atom) == INCOMPLETE_ATOM_INDEX;

ok &= globalcontext_insert_atom(glb, kill_atom) == KILL_ATOM_INDEX;
ok &= globalcontext_insert_atom(glb, killed_atom) == KILLED_ATOM_INDEX;
ok &= globalcontext_insert_atom(glb, links_atom) == LINKS_ATOM_INDEX;

if (!ok) {
Expand Down
8 changes: 6 additions & 2 deletions src/libAtomVM/defaultatoms.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,11 @@ extern "C" {

#define INCOMPLETE_ATOM_INDEX 99

#define LINKS_ATOM_INDEX 100
#define KILL_ATOM_INDEX 100
#define KILLED_ATOM_INDEX 101
#define LINKS_ATOM_INDEX 102

#define PLATFORM_ATOMS_BASE_INDEX 101
#define PLATFORM_ATOMS_BASE_INDEX 103

#define FALSE_ATOM TERM_FROM_ATOM_INDEX(FALSE_ATOM_INDEX)
#define TRUE_ATOM TERM_FROM_ATOM_INDEX(TRUE_ATOM_INDEX)
Expand Down Expand Up @@ -285,6 +287,8 @@ extern "C" {

#define INCOMPLETE_ATOM TERM_FROM_ATOM_INDEX(INCOMPLETE_ATOM_INDEX)

#define KILL_ATOM TERM_FROM_ATOM_INDEX(KILL_ATOM_INDEX)
#define KILLED_ATOM TERM_FROM_ATOM_INDEX(KILLED_ATOM_INDEX)
#define LINKS_ATOM TERM_FROM_ATOM_INDEX(LINKS_ATOM_INDEX)

void defaultatoms_init(GlobalContext *glb);
Expand Down
46 changes: 42 additions & 4 deletions src/libAtomVM/nifs.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "defaultatoms.h"
#include "dictionary.h"
#include "externalterm.h"
#include "globalcontext.h"
#include "interop.h"
#include "mailbox.h"
#include "module.h"
Expand Down Expand Up @@ -3132,11 +3133,48 @@ static term nif_erlang_error(Context *ctx, int argc, term argv[])

static term nif_erlang_exit(Context *ctx, int argc, term argv[])
{
UNUSED(argc);
if (argc == 1) {
term reason = argv[0];
RAISE(LOWERCASE_EXIT_ATOM, reason);
} else {
term target_process = argv[0];
VALIDATE_VALUE(target_process, term_is_pid);
term reason = argv[1];
GlobalContext *glb = ctx->global;
Context *target = globalcontext_get_process_lock(glb, term_to_local_process_id(target_process));
bool self_is_signaled = false;
if (LIKELY(target)) {
if (reason == KILL_ATOM) {
mailbox_send_term_signal(target, KillSignal, KILLED_ATOM);
self_is_signaled = target == ctx;
} else {
if (target->trap_exit) {
if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(3)) != MEMORY_GC_OK)) {
globalcontext_get_process_unlock(glb, target);
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
}

term reason = argv[0];
ctx->exit_reason = reason;
RAISE(LOWERCASE_EXIT_ATOM, reason);
term info_tuple = term_alloc_tuple(3, &ctx->heap);
term_put_tuple_element(info_tuple, 0, EXIT_ATOM);
term_put_tuple_element(info_tuple, 1, term_from_local_process_id(ctx->process_id));
term_put_tuple_element(info_tuple, 2, reason);
mailbox_send(target, info_tuple);
} else if (ctx == target) {
mailbox_send_term_signal(target, KillSignal, reason);
self_is_signaled = target == ctx;
} else if (reason != NORMAL_ATOM){
mailbox_send_term_signal(target, KillSignal, reason);
self_is_signaled = target == ctx;
} // else there is no effect
}
globalcontext_get_process_unlock(glb, target);
}
if (self_is_signaled) {
context_update_flags(ctx, ~NoFlags, Trap);
return term_invalid_term();
}
return TRUE_ATOM;
}
}

static term nif_erlang_make_fun_3(Context *ctx, int argc, term argv[])
Expand Down
1 change: 1 addition & 0 deletions src/libAtomVM/nifs.gperf
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ erlang:erase/1, &erase_nif
erlang:error/1, &error_nif
erlang:error/2, &error_nif
erlang:exit/1, &exit_nif
erlang:exit/2, &exit_nif
erlang:display/1, &display_nif
erlang:float_to_binary/1, &float_to_binary_nif
erlang:float_to_binary/2, &float_to_binary_nif
Expand Down
10 changes: 8 additions & 2 deletions src/libAtomVM/opcodesswitch.h
Original file line number Diff line number Diff line change
Expand Up @@ -7169,9 +7169,14 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
}
}

dump(ctx);
// Do not print crash dump if reason is normal.
if (ctx->x[0] != LOWERCASE_EXIT_ATOM || ctx->x[1] != NORMAL_ATOM) {
dump(ctx);
}

{
if (ctx->x[0] == LOWERCASE_EXIT_ATOM) {
ctx->exit_reason = ctx->x[1];
} else {
bool throw = ctx->x[0] == THROW_ATOM;

int exit_reason_tuple_size = (throw ? TUPLE_SIZE(2) : 0) + TUPLE_SIZE(2);
Expand All @@ -7184,6 +7189,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
term_put_tuple_element(error_term, 0, NOCATCH_ATOM);
term_put_tuple_element(error_term, 1, ctx->x[1]);
} else {
// error
error_term = ctx->x[1];
}

Expand Down
4 changes: 4 additions & 0 deletions tests/erlang_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,8 @@ compile_erlang(link_kill_parent)
compile_erlang(link_throw)
compile_erlang(unlink_error)
compile_erlang(trap_exit_flag)
compile_erlang(test_exit1)
compile_erlang(test_exit2)

compile_erlang(test_stacktrace)
compile_erlang(small_big_ext)
Expand Down Expand Up @@ -907,6 +909,8 @@ add_custom_target(erlang_test_modules DEPENDS
link_throw.beam
unlink_error.beam
trap_exit_flag.beam
test_exit1.beam
test_exit2.beam

test_stacktrace.beam

Expand Down
Loading
Loading