diff --git a/libs/estdlib/src/erlang.erl b/libs/estdlib/src/erlang.erl index e7a9b35468..bae702dd4e 100644 --- a/libs/estdlib/src/erlang.erl +++ b/libs/estdlib/src/erlang.erl @@ -100,6 +100,7 @@ make_ref/0, send/2, monitor/2, + monitor/3, demonitor/1, demonitor/2, exit/1, @@ -135,7 +136,9 @@ dist_ctrl_put_data/2, unique_integer/0, unique_integer/1, - raise/3 + raise/3, + alias/0, + unalias/1 ]). -export_type([ @@ -179,7 +182,8 @@ | {max_heap_size, pos_integer()} | {atomvm_heap_growth, atomvm_heap_growth_strategy()} | link - | monitor. + | monitor + | {monitor, [monitor_option()]}. -type send_destination() :: pid() @@ -205,6 +209,8 @@ -type raise_stacktrace() :: [{module(), atom(), arity() | [term()]} | {function(), arity() | [term()]}] | stacktrace(). +-type monitor_option() :: {alias, explicit_unalias | demonitor | reply_demonitor}. + %%----------------------------------------------------------------------------- %% @param Time time in milliseconds after which to send the timeout message. %% @param Dest Pid or server name to which to send the timeout message. @@ -1173,6 +1179,28 @@ send(_Target, _Message) -> monitor(_Type, _PidOrPort) -> erlang:nif_error(undefined). +%%----------------------------------------------------------------------------- +%% @param Type type of monitor to create +%% @param PidOrPort pid or port of the object to monitor +%% @param Options monitor options +%% @returns a monitor reference +%% @doc Creates a monitor and allows passing additional options. +%% Currently, only the `{alias, AliasMode}' option is supported. Passing it +%% makes the monitor also an alias on the calling process (see `alias/0'). +%% `AliasMode' defines the behaviour of the alias: +%% - explicit_unalias - the alias can be only removed with `unalias/1', +%% - demonitor - the alias is also removed when `demonitor/1' is called +%% on the monitor, +%% - reply_demonitor - the alias is also removed after a first message +%% is sent via it. +%% @end +%%----------------------------------------------------------------------------- +-spec monitor + (Type :: process, Pid :: pid() | atom(), [monitor_option()]) -> reference(); + (Type :: port, Port :: port() | atom(), [monitor_option()]) -> reference(). +monitor(_Type, _PidOrPort, _Options) -> + erlang:nif_error(undefined). + %%----------------------------------------------------------------------------- %% @param Monitor reference of monitor to remove %% @returns `true' @@ -1593,3 +1621,25 @@ nif_error(_Reason) -> no_return(). raise(_Class, _Reason, _Stacktrace) -> erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @returns A reference aliasing the calling process. +%% @doc Creates an alias for the calling process. The alias can be used +%% to send messages to the process like the PID. The alias can also be +%% created along with a monitor - see `monitor/3'. The alias can be +%% removed by calling `unalias/1'. +%% @end +%%----------------------------------------------------------------------------- +-spec alias() -> Alias when Alias :: reference(). +alias() -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Alias the alias to be removed. +%% @returns `true' if alias was removed, `false' if it was not found +%% @doc Removes process alias. See `alias/0' for more information. +%% @end +%%----------------------------------------------------------------------------- +-spec unalias(Alias) -> boolean() when Alias :: reference(). +unalias(_Alias) -> + erlang:nif_error(undefined). diff --git a/src/libAtomVM/context.c b/src/libAtomVM/context.c index 0e35082ad1..c94c7d59a7 100644 --- a/src/libAtomVM/context.c +++ b/src/libAtomVM/context.c @@ -264,6 +264,7 @@ void context_destroy(Context *ctx) case CONTEXT_MONITOR_MONITORED_LOCAL: case CONTEXT_MONITOR_MONITORING_LOCAL: case CONTEXT_MONITOR_MONITORING_LOCAL_REGISTEREDNAME: + case CONTEXT_MONITOR_ALIAS: UNREACHABLE(); } } @@ -431,7 +432,7 @@ void context_process_monitor_down_signal(Context *ctx, struct TermSignal *signal struct Monitor *monitor = GET_LIST_ENTRY(item, struct Monitor, monitor_list_head); if (monitor->monitor_type == CONTEXT_MONITOR_MONITORING_LOCAL) { struct MonitorLocalMonitor *monitoring_monitor = CONTAINER_OF(monitor, struct MonitorLocalMonitor, monitor); - if (monitoring_monitor->monitor_obj == monitor_obj && monitoring_monitor->ref_ticks == ref_ticks) { + if (monitoring_monitor->monitor_obj == monitor_obj && monitoring_monitor->ref_data.ref_ticks == ref_ticks) { // Remove link list_remove(&monitor->monitor_list_head); free(monitoring_monitor); @@ -442,7 +443,7 @@ void context_process_monitor_down_signal(Context *ctx, struct TermSignal *signal } else if (monitor->monitor_type == CONTEXT_MONITOR_MONITORING_LOCAL_REGISTEREDNAME) { int32_t monitor_process_id = term_to_local_process_id(monitor_obj); struct MonitorLocalRegisteredNameMonitor *monitoring_monitor = CONTAINER_OF(monitor, struct MonitorLocalRegisteredNameMonitor, monitor); - if (monitoring_monitor->monitor_process_id == monitor_process_id && monitoring_monitor->ref_ticks == ref_ticks) { + if (monitoring_monitor->monitor_process_id == monitor_process_id && monitoring_monitor->ref_data.ref_ticks == ref_ticks) { // Remove link list_remove(&monitor->monitor_list_head); @@ -718,7 +719,7 @@ static struct Monitor *context_monitors_handle_terminate(Context *ctx) Context *target = globalcontext_get_process_nolock(glb, local_process_id); if (LIKELY(target != NULL)) { // target can be null if we didn't process a MonitorDownSignal - mailbox_send_ref_signal(target, DemonitorSignal, monitoring_monitor->ref_ticks); + mailbox_send_ref_signal(target, DemonitorSignal, monitoring_monitor->ref_data.ref_ticks); } free(monitoring_monitor); break; @@ -730,7 +731,7 @@ static struct Monitor *context_monitors_handle_terminate(Context *ctx) Context *target = globalcontext_get_process_nolock(glb, local_process_id); if (LIKELY(target != NULL)) { // target can be null if we didn't process a MonitorDownSignal - mailbox_send_ref_signal(target, DemonitorSignal, monitoring_monitor->ref_ticks); + mailbox_send_ref_signal(target, DemonitorSignal, monitoring_monitor->ref_data.ref_ticks); } free(monitoring_monitor); break; @@ -778,7 +779,7 @@ static struct Monitor *context_monitors_handle_terminate(Context *ctx) Context *target = globalcontext_get_process_nolock(glb, local_process_id); // Target cannot be NULL as we processed Demonitor signals assert(target != NULL); - int required_terms = REF_SIZE + TUPLE_SIZE(5); + int required_terms = TERM_BOXED_REFERENCE_PROCESS_SIZE + TUPLE_SIZE(5); if (UNLIKELY(memory_ensure_free(ctx, required_terms) != MEMORY_GC_OK)) { // TODO: handle out of memory here fprintf(stderr, "Cannot handle out of memory.\n"); @@ -786,7 +787,7 @@ static struct Monitor *context_monitors_handle_terminate(Context *ctx) AVM_ABORT(); } // Prepare the message on ctx's heap which will be freed afterwards. - term ref = term_from_ref_ticks(monitored_monitor->ref_ticks, &ctx->heap); + term ref = term_from_ref_data(&monitored_monitor->ref_data, &ctx->heap); term port_or_process = term_pid_or_port_from_context(ctx); term port_or_process_atom @@ -803,6 +804,11 @@ static struct Monitor *context_monitors_handle_terminate(Context *ctx) free(monitored_monitor); break; } + case CONTEXT_MONITOR_ALIAS: { + struct MonitorAlias *alias = CONTAINER_OF(monitor, struct MonitorAlias, monitor); + free(alias); + break; + } } } return result; @@ -842,7 +848,7 @@ struct Monitor *monitor_link_new(term link_pid) } } -struct Monitor *monitor_new(term monitor_pid, uint64_t ref_ticks, bool is_monitoring) +struct Monitor *monitor_new(term monitor_pid, RefData *ref_data, bool is_monitoring) { struct MonitorLocalMonitor *monitor = malloc(sizeof(struct MonitorLocalMonitor)); if (IS_NULL_PTR(monitor)) { @@ -854,12 +860,12 @@ struct Monitor *monitor_new(term monitor_pid, uint64_t ref_ticks, bool is_monito monitor->monitor.monitor_type = CONTEXT_MONITOR_MONITORED_LOCAL; } monitor->monitor_obj = monitor_pid; - monitor->ref_ticks = ref_ticks; + monitor->ref_data = *ref_data; return &monitor->monitor; } -struct Monitor *monitor_registeredname_monitor_new(int32_t monitor_process_id, term monitor_name, uint64_t ref_ticks) +struct Monitor *monitor_registeredname_monitor_new(int32_t monitor_process_id, term monitor_name, RefData *ref_data) { struct MonitorLocalRegisteredNameMonitor *monitor = malloc(sizeof(struct MonitorLocalRegisteredNameMonitor)); if (IS_NULL_PTR(monitor)) { @@ -868,7 +874,20 @@ struct Monitor *monitor_registeredname_monitor_new(int32_t monitor_process_id, t monitor->monitor.monitor_type = CONTEXT_MONITOR_MONITORING_LOCAL_REGISTEREDNAME; monitor->monitor_process_id = monitor_process_id; monitor->monitor_name = monitor_name; - monitor->ref_ticks = ref_ticks; + monitor->ref_data = *ref_data; + + return &monitor->monitor; +} + +struct Monitor *monitor_alias_new(RefData *ref_data, enum ContextMonitorAliasType alias_type) +{ + struct MonitorAlias *monitor = malloc(sizeof(struct MonitorAlias)); + if (IS_NULL_PTR(monitor)) { + return NULL; + } + monitor->monitor.monitor_type = CONTEXT_MONITOR_ALIAS; + monitor->ref_data = *ref_data; + monitor->alias_type = alias_type; return &monitor->monitor; } @@ -907,7 +926,7 @@ bool context_add_monitor(Context *ctx, struct Monitor *new_monitor) case CONTEXT_MONITOR_MONITORED_LOCAL: { struct MonitorLocalMonitor *new_local_monitor = CONTAINER_OF(new_monitor, struct MonitorLocalMonitor, monitor); struct MonitorLocalMonitor *existing_local_monitor = CONTAINER_OF(existing, struct MonitorLocalMonitor, monitor); - if (UNLIKELY(existing_local_monitor->monitor_obj == new_local_monitor->monitor_obj && existing_local_monitor->ref_ticks == new_local_monitor->ref_ticks)) { + if (UNLIKELY(existing_local_monitor->monitor_obj == new_local_monitor->monitor_obj && existing_local_monitor->ref_data.ref_ticks == new_local_monitor->ref_data.ref_ticks)) { free(new_local_monitor); return false; } @@ -918,12 +937,22 @@ bool context_add_monitor(Context *ctx, struct Monitor *new_monitor) struct MonitorLocalRegisteredNameMonitor *existing_local_registeredname_monitor = CONTAINER_OF(existing, struct MonitorLocalRegisteredNameMonitor, monitor); if (UNLIKELY(existing_local_registeredname_monitor->monitor_process_id == new_local_registeredname_monitor->monitor_process_id && existing_local_registeredname_monitor->monitor_name == new_local_registeredname_monitor->monitor_name - && existing_local_registeredname_monitor->ref_ticks == new_local_registeredname_monitor->ref_ticks)) { + && existing_local_registeredname_monitor->ref_data.ref_ticks == new_local_registeredname_monitor->ref_data.ref_ticks)) { free(new_local_registeredname_monitor); return false; } break; } + case CONTEXT_MONITOR_ALIAS: { + struct MonitorAlias *new_alias_monitor = CONTAINER_OF(new_monitor, struct MonitorAlias, monitor); + struct MonitorAlias *existing_alias_monitor = CONTAINER_OF(existing, struct MonitorAlias, monitor); + + if (UNLIKELY(existing_alias_monitor->alias_type == new_alias_monitor->alias_type && existing_alias_monitor->ref_data.ref_ticks == new_alias_monitor->ref_data.ref_ticks)) { + free(new_monitor); + return false; + } + break; + } case CONTEXT_MONITOR_RESOURCE: { struct ResourceContextMonitor *new_resource_monitor = CONTAINER_OF(new_monitor, struct ResourceContextMonitor, monitor); struct ResourceContextMonitor *existing_resource_monitor = CONTAINER_OF(existing, struct ResourceContextMonitor, monitor); @@ -1057,6 +1086,11 @@ void context_unlink_ack(Context *ctx, term link_pid, uint64_t unlink_id) void context_demonitor(Context *ctx, uint64_t ref_ticks) { + struct MonitorAlias *alias = context_find_alias(ctx, ref_ticks); + if (alias != NULL && alias->alias_type != ContextMonitorAliasExplicitUnalias) { + context_unalias(alias); + } + struct ListHead *item; LIST_FOR_EACH (item, &ctx->monitors_head) { struct Monitor *monitor = GET_LIST_ENTRY(item, struct Monitor, monitor_list_head); @@ -1064,7 +1098,7 @@ void context_demonitor(Context *ctx, uint64_t ref_ticks) case CONTEXT_MONITOR_MONITORING_LOCAL: case CONTEXT_MONITOR_MONITORED_LOCAL: { struct MonitorLocalMonitor *local_monitor = CONTAINER_OF(monitor, struct MonitorLocalMonitor, monitor); - if (local_monitor->ref_ticks == ref_ticks) { + if (local_monitor->ref_data.ref_ticks == ref_ticks) { list_remove(&monitor->monitor_list_head); free(local_monitor); return; @@ -1073,7 +1107,7 @@ void context_demonitor(Context *ctx, uint64_t ref_ticks) } case CONTEXT_MONITOR_MONITORING_LOCAL_REGISTEREDNAME: { struct MonitorLocalRegisteredNameMonitor *local_registeredname_monitor = CONTAINER_OF(monitor, struct MonitorLocalRegisteredNameMonitor, monitor); - if (local_registeredname_monitor->ref_ticks == ref_ticks) { + if (local_registeredname_monitor->ref_data.ref_ticks == ref_ticks) { list_remove(&monitor->monitor_list_head); free(local_registeredname_monitor); return; @@ -1090,11 +1124,36 @@ void context_demonitor(Context *ctx, uint64_t ref_ticks) } case CONTEXT_MONITOR_LINK_LOCAL: case CONTEXT_MONITOR_LINK_REMOTE: + case CONTEXT_MONITOR_ALIAS: break; } } } +struct MonitorAlias *context_find_alias(Context *ctx, uint64_t ref_ticks) +{ + struct ListHead *item; + LIST_FOR_EACH (item, &ctx->monitors_head) { + struct Monitor *monitor = GET_LIST_ENTRY(item, struct Monitor, monitor_list_head); + if (monitor->monitor_type == CONTEXT_MONITOR_ALIAS) { + struct MonitorAlias *alias_monitor = CONTAINER_OF(monitor, struct MonitorAlias, monitor); + if (alias_monitor->ref_data.ref_ticks == ref_ticks) { + return alias_monitor; + } + } + } + + return NULL; +} + +void context_unalias(struct MonitorAlias *alias) +{ + TERM_DEBUG_ASSERT(alias != NULL); + struct Monitor *monitor = &alias->monitor; + list_remove(&monitor->monitor_list_head); + free(alias); +} + term context_get_monitor_pid(Context *ctx, uint64_t ref_ticks, bool *is_monitoring) { struct ListHead *item; @@ -1104,7 +1163,7 @@ term context_get_monitor_pid(Context *ctx, uint64_t ref_ticks, bool *is_monitori case CONTEXT_MONITOR_MONITORING_LOCAL: case CONTEXT_MONITOR_MONITORED_LOCAL: { struct MonitorLocalMonitor *local_monitor = CONTAINER_OF(monitor, struct MonitorLocalMonitor, monitor); - if (local_monitor->ref_ticks == ref_ticks) { + if (local_monitor->ref_data.ref_ticks == ref_ticks) { *is_monitoring = monitor->monitor_type == CONTEXT_MONITOR_MONITORING_LOCAL; return local_monitor->monitor_obj; } @@ -1112,7 +1171,7 @@ term context_get_monitor_pid(Context *ctx, uint64_t ref_ticks, bool *is_monitori } case CONTEXT_MONITOR_MONITORING_LOCAL_REGISTEREDNAME: { struct MonitorLocalRegisteredNameMonitor *local_registeredname_monitor = CONTAINER_OF(monitor, struct MonitorLocalRegisteredNameMonitor, monitor); - if (local_registeredname_monitor->ref_ticks == ref_ticks) { + if (local_registeredname_monitor->ref_data.ref_ticks == ref_ticks) { *is_monitoring = true; return term_from_local_process_id(local_registeredname_monitor->monitor_process_id); } @@ -1121,6 +1180,7 @@ term context_get_monitor_pid(Context *ctx, uint64_t ref_ticks, bool *is_monitori case CONTEXT_MONITOR_LINK_LOCAL: case CONTEXT_MONITOR_LINK_REMOTE: case CONTEXT_MONITOR_RESOURCE: + case CONTEXT_MONITOR_ALIAS: break; } } @@ -1246,7 +1306,13 @@ COLD_FUNC void context_dump(Context *ctx) struct MonitorLocalMonitor *monitoring_monitor = CONTAINER_OF(monitor, struct MonitorLocalMonitor, monitor); fprintf(stderr, "monitor to "); term_display(stderr, monitoring_monitor->monitor_obj, ctx); - fprintf(stderr, " ref=%lu", (long unsigned) monitoring_monitor->ref_ticks); + fprintf(stderr, " ref=%lu", (long unsigned) monitoring_monitor->ref_data.ref_ticks); + fprintf(stderr, "\n"); + break; + } + case CONTEXT_MONITOR_ALIAS: { + struct MonitorAlias *monitor_alias = CONTAINER_OF(monitor, struct MonitorAlias, monitor); + fprintf(stderr, "has alias ref=%lu", (long unsigned) monitor_alias->ref_data.ref_ticks); fprintf(stderr, "\n"); break; } @@ -1254,7 +1320,7 @@ COLD_FUNC void context_dump(Context *ctx) struct MonitorLocalMonitor *monitored_monitor = CONTAINER_OF(monitor, struct MonitorLocalMonitor, monitor); fprintf(stderr, "monitored by "); term_display(stderr, monitored_monitor->monitor_obj, ctx); - fprintf(stderr, " ref=%lu", (long unsigned) monitored_monitor->ref_ticks); + fprintf(stderr, " ref=%lu", (long unsigned) monitored_monitor->ref_data.ref_ticks); fprintf(stderr, "\n"); break; } @@ -1264,7 +1330,7 @@ COLD_FUNC void context_dump(Context *ctx) term_display(stderr, local_registeredname_monitor->monitor_name, ctx); fprintf(stderr, " ("); term_display(stderr, term_from_local_process_id(local_registeredname_monitor->monitor_process_id), ctx); - fprintf(stderr, ") ref=%lu", (long unsigned) local_registeredname_monitor->ref_ticks); + fprintf(stderr, ") ref=%lu", (long unsigned) local_registeredname_monitor->ref_data.ref_ticks); fprintf(stderr, "\n"); break; } diff --git a/src/libAtomVM/context.h b/src/libAtomVM/context.h index 52ac76d3bd..34806018d4 100644 --- a/src/libAtomVM/context.h +++ b/src/libAtomVM/context.h @@ -177,6 +177,14 @@ enum ContextMonitorType CONTEXT_MONITOR_RESOURCE, CONTEXT_MONITOR_LINK_REMOTE, CONTEXT_MONITOR_MONITORING_LOCAL_REGISTEREDNAME, + CONTEXT_MONITOR_ALIAS, +}; + +enum ContextMonitorAliasType +{ + ContextMonitorAliasExplicitUnalias, + ContextMonitorAliasDemonitor, + ContextMonitorAliasReplyDemonitor, }; #define UNLINK_ID_LINK_ACTIVE 0x0 @@ -200,18 +208,25 @@ struct LinkLocalMonitor struct MonitorLocalMonitor { struct Monitor monitor; - uint64_t ref_ticks; + RefData ref_data; term monitor_obj; }; struct MonitorLocalRegisteredNameMonitor { struct Monitor monitor; - uint64_t ref_ticks; + RefData ref_data; int32_t monitor_process_id; term monitor_name; }; +struct MonitorAlias +{ + struct Monitor monitor; + RefData ref_data; + enum ContextMonitorAliasType alias_type; +}; + // The other half is called ResourceMonitor and is a linked list of resources struct ResourceContextMonitor { @@ -511,21 +526,23 @@ struct Monitor *monitor_link_new(term link_pid); * @brief Create a monitor on a process. * * @param monitor_pid monitored process - * @param ref_ticks reference of the monitor + * @param ref_data reference of the monitor * @param is_monitoring if ctx is the monitoring process * @return the allocated monitor or NULL if allocation failed */ -struct Monitor *monitor_new(term monitor_pid, uint64_t ref_ticks, bool is_monitoring); +struct Monitor *monitor_new(term monitor_pid, RefData *ref_data, bool is_monitoring); + +struct Monitor *monitor_alias_new(RefData *ref_data, enum ContextMonitorAliasType alias_type); /** * @brief Create a monitor on a process by registered name. * * @param monitor_process_id monitored process id * @param monitor_name name of the monitor (atom) - * @param ref_ticks reference of the monitor + * @param ref_data reference of the monitor * @return the allocated monitor or NULL if allocation failed */ -struct Monitor *monitor_registeredname_monitor_new(int32_t monitor_process_id, term monitor_name, uint64_t ref_ticks); +struct Monitor *monitor_registeredname_monitor_new(int32_t monitor_process_id, term monitor_name, RefData *ref_data); /** * @brief Create a resource monitor. @@ -582,6 +599,22 @@ void context_unlink_ack(Context *ctx, term link_pid, uint64_t unlink_id); */ void context_demonitor(Context *ctx, uint64_t ref_ticks); +/** + * @brief Find a process alias + * + * @param ctx the context being executed + * @param ref_ticks reference of the alias to find + * @return found alias or NULL + */ +struct MonitorAlias *context_find_alias(Context *ctx, uint64_t ref_ticks); + +/** + * @brief Remove an alias of a process + * + * @param alias The alias to remove, can be obtained using context_find_alias + */ +void context_unalias(struct MonitorAlias *alias); + /** * @brief Get target of a monitor. * diff --git a/src/libAtomVM/defaultatoms.def b/src/libAtomVM/defaultatoms.def index 03236eb45e..859048b0ff 100644 --- a/src/libAtomVM/defaultatoms.def +++ b/src/libAtomVM/defaultatoms.def @@ -212,3 +212,9 @@ X(JIT_X86_64_ATOM, "\xA", "jit_x86_64") X(JIT_AARCH64_ATOM, "\xB", "jit_aarch64") X(JIT_ARMV6M_ATOM, "\xA", "jit_armv6m") X(JIT_RISCV32_ATOM, "\xB", "jit_riscv32") + +X(ALIAS_ATOM, "\x5", "alias") +X(DEMONITOR_ATOM, "\x9", "demonitor") +X(EXPLICIT_UNALIAS_ATOM, "\x10", "explicit_unalias") +X(REPLY_DEMONITOR_ATOM, "\xF", "reply_demonitor") +X(TAG_ATOM, "\x3", "tag") diff --git a/src/libAtomVM/ets.c b/src/libAtomVM/ets.c index 36f51e83bd..c65b2923c1 100644 --- a/src/libAtomVM/ets.c +++ b/src/libAtomVM/ets.c @@ -178,7 +178,7 @@ EtsErrorCode ets_create_table_maybe_gc(term name, bool is_named, EtsTableType ta if (is_named) { *ret = name; } else { - if (UNLIKELY(memory_ensure_free_opt(ctx, REF_SIZE, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { + if (UNLIKELY(memory_ensure_free_opt(ctx, TERM_BOXED_REFERENCE_SHORT_SIZE, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { ets_hashtable_destroy(hashtable, ctx->global); free(ets_table); return EtsAllocationFailure; diff --git a/src/libAtomVM/external_term.c b/src/libAtomVM/external_term.c index 7720d3c290..8c2db94794 100644 --- a/src/libAtomVM/external_term.c +++ b/src/libAtomVM/external_term.c @@ -522,6 +522,8 @@ static int serialize_term(uint8_t *buf, term t, GlobalContext *glb) uint32_t len; if (term_is_resource_reference(t)) { len = 4; + } else if (term_is_process_reference(t)) { + len = 3; } else { len = 2; } @@ -543,6 +545,15 @@ static int serialize_term(uint8_t *buf, term t, GlobalContext *glb) WRITE_64_UNALIGNED(buf + k + 12, ((uintptr_t) serialize_ref)); } return k + 20; + } else if (term_is_process_reference(t)) { + if (!IS_NULL_PTR(buf)) { + uint64_t ticks = term_to_ref_ticks(t); + uint32_t process_id = term_process_ref_to_process_id(t); + WRITE_32_UNALIGNED(buf + k, creation); + WRITE_64_UNALIGNED(buf + k + 4, ticks); + WRITE_32_UNALIGNED(buf + k + 12, process_id); + } + return k + 16; } else { if (!IS_NULL_PTR(buf)) { uint64_t ticks = term_to_ref_ticks(t); @@ -931,6 +942,10 @@ static term parse_external_terms(const uint8_t *external_term_buf, size_t *eterm if (len == 2 && node == this_node && creation == this_creation) { uint64_t ticks = ((uint64_t) data[0]) << 32 | data[1]; return term_from_ref_ticks(ticks, heap); + } else if (len == 3 && node == this_node && creation == this_creation) { + uint64_t ticks = ((uint64_t) data[0]) << 32 | data[1]; + uint32_t process_id = data[2]; + return term_make_process_reference(process_id, ticks, heap); } else if (len == 4 && node == this_node && creation == this_creation) { // This is a resource uint64_t resource_type_ptr = ((uint64_t) data[0]) << 32 | data[1]; @@ -1350,7 +1365,9 @@ static int calculate_heap_usage(const uint8_t *external_term_buf, size_t remaini // Check if it's non-distributed node, in which case it's always a local ref if (external_term_buf[4] == strlen("nonode@nohost") && memcmp(external_term_buf + 5, "nonode@nohost", strlen("nonode@nohost")) == 0) { if (len == 2) { - heap_size = REF_SIZE; + heap_size = TERM_BOXED_REFERENCE_SHORT_SIZE; + } else if (len == 3) { + heap_size = TERM_BOXED_REFERENCE_PROCESS_SIZE; } else if (len == 4) { heap_size = TERM_BOXED_REFERENCE_RESOURCE_SIZE; } diff --git a/src/libAtomVM/globalcontext.h b/src/libAtomVM/globalcontext.h index ff2a63a4cd..eed831f64d 100644 --- a/src/libAtomVM/globalcontext.h +++ b/src/libAtomVM/globalcontext.h @@ -46,8 +46,6 @@ extern "C" { #endif -#define INVALID_PROCESS_ID 0 - struct Context; #ifndef TYPEDEF_CONTEXT diff --git a/src/libAtomVM/jit.c b/src/libAtomVM/jit.c index 21feac6264..2dcdbf538e 100644 --- a/src/libAtomVM/jit.c +++ b/src/libAtomVM/jit.c @@ -758,15 +758,16 @@ static bool jit_send(Context *ctx, JITState *jit_state) return false; } ctx->x[0] = return_value; - } else { - if (term_is_atom(recipient_term)) { - recipient_term = globalcontext_get_registered_process(ctx->global, term_to_atom_index(recipient_term)); - if (UNLIKELY(recipient_term == UNDEFINED_ATOM)) { - set_error(ctx, jit_state, 0, BADARG_ATOM); - return false; - } + } else if (term_is_local_pid_or_port(recipient_term)) { + int local_process_id = term_to_local_process_id(recipient_term); + globalcontext_send_message(ctx->global, local_process_id, ctx->x[1]); + ctx->x[0] = ctx->x[1]; + } else if (term_is_atom(recipient_term)) { + recipient_term = globalcontext_get_registered_process(ctx->global, term_to_atom_index(recipient_term)); + if (UNLIKELY(recipient_term == UNDEFINED_ATOM)) { + set_error(ctx, jit_state, 0, BADARG_ATOM); + return false; } - int local_process_id; if (term_is_local_pid_or_port(recipient_term)) { local_process_id = term_to_local_process_id(recipient_term); @@ -776,7 +777,26 @@ static bool jit_send(Context *ctx, JITState *jit_state) } globalcontext_send_message(ctx->global, local_process_id, ctx->x[1]); ctx->x[0] = ctx->x[1]; + } else if (term_is_process_reference(recipient_term)) { + int32_t process_id = term_process_ref_to_process_id(recipient_term); + int64_t ref_ticks = term_to_ref_ticks(recipient_term); + Context *p = globalcontext_get_process_lock(ctx->global, process_id); + if (p) { + struct MonitorAlias *alias = context_find_alias(p, ref_ticks); + if (!IS_NULL_PTR(alias)) { + if (alias->alias_type == ContextMonitorAliasReplyDemonitor) { + context_unalias(alias); + } + mailbox_send(p, ctx->x[1]); + } + globalcontext_get_process_unlock(ctx->global, p); + } + ctx->x[0] = ctx->x[1]; + } else if (!term_is_reference(recipient_term)) { + set_error(ctx, jit_state, 0, BADARG_ATOM); + return false; } + return true; } diff --git a/src/libAtomVM/nifs.c b/src/libAtomVM/nifs.c index 20f2eea4f7..6f7f7a806e 100644 --- a/src/libAtomVM/nifs.c +++ b/src/libAtomVM/nifs.c @@ -280,6 +280,8 @@ static term nif_maps_next(Context *ctx, int argc, term argv[]); static term nif_unicode_characters_to_list(Context *ctx, int argc, term argv[]); static term nif_unicode_characters_to_binary(Context *ctx, int argc, term argv[]); static term nif_erlang_lists_subtract(Context *ctx, int argc, term argv[]); +static term nif_erlang_alias(Context *ctx, int argc, term argv[]); +static term nif_erlang_unalias(Context *ctx, int argc, term argv[]); static term nif_zlib_compress_1(Context *ctx, int argc, term argv[]); #define DECLARE_MATH_NIF_FUN(moniker) \ @@ -910,6 +912,14 @@ static const struct Nif list_to_bitstring_nif = { .base.type = NIFFunctionType, .nif_ptr = nif_erlang_list_to_bitstring_1 }; +static const struct Nif erlang_alias_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_erlang_alias +}; +static const struct Nif erlang_unalias_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_erlang_unalias +}; static const struct Nif zlib_compress_nif = { .base.type = NIFFunctionType, .nif_ptr = nif_zlib_compress_1 @@ -1193,7 +1203,7 @@ static NativeHandlerResult process_console_message(Context *ctx, term msg) { // msg is not in the port's heap NativeHandlerResult result = NativeContinue; - if (UNLIKELY(memory_ensure_free_opt(ctx, 12, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { + if (UNLIKELY(memory_ensure_free_opt(ctx, TUPLE_SIZE(3) + TERM_BOXED_REFERENCE_PROCESS_SIZE, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { fprintf(stderr, "Unable to allocate sufficient memory for console driver.\n"); AVM_ABORT(); } @@ -1285,6 +1295,42 @@ static NativeHandlerResult process_console_mailbox(Context *ctx) return result; } +static term parse_monitor_opts(Context *ctx, term monitor_opts, bool *is_alias, enum ContextMonitorAliasType *alias_type) +{ + *is_alias = false; + while (term_is_nonempty_list(monitor_opts)) { + term option = term_get_list_head(monitor_opts); + if (term_is_tuple(option) && term_get_tuple_arity(option) == 2 && term_get_tuple_element(option, 0) == ALIAS_ATOM) { + *is_alias = true; + switch (term_get_tuple_element(option, 1)) { + case EXPLICIT_UNALIAS_ATOM: + *alias_type = ContextMonitorAliasExplicitUnalias; + break; + case DEMONITOR_ATOM: + *alias_type = ContextMonitorAliasDemonitor; + break; + case REPLY_DEMONITOR_ATOM: + *alias_type = ContextMonitorAliasReplyDemonitor; + break; + default: + RAISE_ERROR(BADARG_ATOM); + } + } else if (term_is_tuple(option) && term_get_tuple_arity(option) == 2 && term_get_tuple_element(option, 0) == TAG_ATOM) { + RAISE_ERROR(UNSUPPORTED_ATOM); + } else { + RAISE_ERROR(BADARG_ATOM); + } + + monitor_opts = term_get_list_tail(monitor_opts); + } + + if (UNLIKELY(!term_is_nil(monitor_opts))) { + RAISE_ERROR(BADARG_ATOM); + } + + return OK_ATOM; +} + // Common handling of spawn/1, spawn/3, spawn_opt/2, spawn_opt/4 // opts_term is [] for spawn/1,3 static term do_spawn(Context *ctx, Context *new_ctx, size_t arity, size_t n_freeze, term opts_term) @@ -1292,7 +1338,7 @@ static term do_spawn(Context *ctx, Context *new_ctx, size_t arity, size_t n_free term min_heap_size_term = interop_proplist_get_value(opts_term, MIN_HEAP_SIZE_ATOM); term max_heap_size_term = interop_proplist_get_value(opts_term, MAX_HEAP_SIZE_ATOM); term link_term = interop_proplist_get_value(opts_term, LINK_ATOM); - term monitor_term = interop_proplist_get_value(opts_term, MONITOR_ATOM); + term monitor_term = interop_proplist_get_value_default(opts_term, MONITOR_ATOM, term_invalid_term()); term heap_growth_strategy = interop_proplist_get_value_default(opts_term, ATOMVM_HEAP_GROWTH_ATOM, BOUNDED_FREE_ATOM); term request_term = interop_proplist_get_value_default(opts_term, REQUEST_ATOM, UNDEFINED_ATOM); term group_leader; @@ -1362,7 +1408,8 @@ static term do_spawn(Context *ctx, Context *new_ctx, size_t arity, size_t n_free context_destroy(new_ctx); RAISE_ERROR(BADARG_ATOM); } - uint64_t ref_ticks = 0; + RefData ref_data; + bool is_spawn_monitor = false; term new_pid = term_from_local_process_id(new_ctx->process_id); if (link_term == TRUE_ATOM) { @@ -1382,25 +1429,53 @@ static term do_spawn(Context *ctx, Context *new_ctx, size_t arity, size_t n_free context_add_monitor(ctx, self_link); } if (monitor_term == TRUE_ATOM) { - // We can call context_add_monitor directly on new process because it's not started yet - ref_ticks = globalcontext_get_ref_ticks(ctx->global); - struct Monitor *new_monitor = monitor_new(term_from_local_process_id(ctx->process_id), ref_ticks, false); + monitor_term = term_nil(); + } + if (term_is_list(monitor_term)) { + is_spawn_monitor = true; + bool is_alias; + enum ContextMonitorAliasType alias_type; + + if (UNLIKELY(term_is_invalid_term(parse_monitor_opts(ctx, monitor_term, &is_alias, &alias_type)))) { + context_destroy(new_ctx); + return term_invalid_term(); + } + struct Monitor *alias_monitor = NULL; + if (is_alias) { + ref_data = (RefData){ .ref_ticks = globalcontext_get_ref_ticks(ctx->global), .process_id = ctx->process_id }; + alias_monitor = monitor_alias_new(&ref_data, alias_type); + if (IS_NULL_PTR(alias_monitor)) { + context_destroy(new_ctx); + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + } else { + ref_data = (RefData){ .ref_ticks = globalcontext_get_ref_ticks(ctx->global), .process_id = INVALID_PROCESS_ID }; + } + + struct Monitor *new_monitor = monitor_new(term_from_local_process_id(ctx->process_id), &ref_data, false); if (IS_NULL_PTR(new_monitor)) { context_destroy(new_ctx); + free(alias_monitor); RAISE_ERROR(OUT_OF_MEMORY_ATOM); } - struct Monitor *self_monitor = monitor_new(new_pid, ref_ticks, true); + struct Monitor *self_monitor = monitor_new(new_pid, &ref_data, true); if (IS_NULL_PTR(self_monitor)) { + free(alias_monitor); free(new_monitor); context_destroy(new_ctx); RAISE_ERROR(OUT_OF_MEMORY_ATOM); } + + // We can call context_add_monitor directly on new process because it's not started yet context_add_monitor(new_ctx, new_monitor); context_add_monitor(ctx, self_monitor); + if (is_alias) { + context_add_monitor(ctx, alias_monitor); + } } - if (ref_ticks) { - int res_size = REF_SIZE + TUPLE_SIZE(2); + if (is_spawn_monitor) { + int res_size = TERM_BOXED_REFERENCE_PROCESS_SIZE + TUPLE_SIZE(2); if (UNLIKELY(memory_ensure_free_opt(ctx, res_size, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { context_destroy(new_ctx); RAISE_ERROR(OUT_OF_MEMORY_ATOM); @@ -1408,13 +1483,13 @@ static term do_spawn(Context *ctx, Context *new_ctx, size_t arity, size_t n_free scheduler_init_ready(new_ctx); - term ref = term_from_ref_ticks(ref_ticks, &ctx->heap); + term ref = term_from_ref_data(&ref_data, &ctx->heap); - term pid_ref_tuple = term_alloc_tuple(2, &ctx->heap); - term_put_tuple_element(pid_ref_tuple, 0, new_pid); - term_put_tuple_element(pid_ref_tuple, 1, ref); + term process_ref_tuple = term_alloc_tuple(2, &ctx->heap); + term_put_tuple_element(process_ref_tuple, 0, new_pid); + term_put_tuple_element(process_ref_tuple, 1, ref); - return pid_ref_tuple; + return process_ref_tuple; } else if (UNLIKELY(valid_request)) { // Handling of spawn_request // spawn_request requires that the reply is enqueued before @@ -1597,6 +1672,20 @@ static term nif_erlang_send_2(Context *ctx, int argc, term argv[]) globalcontext_send_message(glb, local_process_id, argv[1]); + } else if (term_is_process_reference(target)) { + int32_t process_id = term_process_ref_to_process_id(target); + int64_t ref_ticks = term_to_ref_ticks(target); + Context *p = globalcontext_get_process_lock(glb, process_id); + if (p) { + struct MonitorAlias *alias = context_find_alias(p, ref_ticks); + if (alias != NULL) { + if (alias->alias_type == ContextMonitorAliasReplyDemonitor) { + context_unalias(alias); + } + mailbox_send(p, argv[1]); + } + globalcontext_get_process_unlock(glb, p); + } } else if (term_is_atom(target)) { // We need to hold a lock on the processes_table until the message is sent to avoid a race condition, // otherwise the receiving process could be killed at any point between checking it is registered, @@ -1621,7 +1710,7 @@ static term nif_erlang_send_2(Context *ctx, int argc, term argv[]) globalcontext_send_message_nolock(glb, local_process_id, argv[1]); synclist_unlock(&glb->processes_table); - } else { + } else if (!term_is_reference(target)) { RAISE_ERROR(BADARG_ATOM); } @@ -1700,7 +1789,7 @@ term nif_erlang_make_ref_0(Context *ctx, int argc, term argv[]) UNUSED(argv); // a ref is 64 bits, hence 8 bytes - if (UNLIKELY(memory_ensure_free_opt(ctx, REF_SIZE, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { + if (UNLIKELY(memory_ensure_free_opt(ctx, TERM_BOXED_REFERENCE_SHORT_SIZE, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); } @@ -4314,17 +4403,22 @@ static term nif_erlang_memory(Context *ctx, int argc, term argv[]) static term nif_erlang_monitor(Context *ctx, int argc, term argv[]) { - UNUSED(argc); term object_type = argv[0]; term target_proc = argv[1]; + term options = argc == 3 ? argv[2] : term_nil(); term target_pid; size_t target_proc_size = 0; + bool is_alias; + enum ContextMonitorAliasType alias_type; if (object_type != PROCESS_ATOM && object_type != PORT_ATOM) { RAISE_ERROR(BADARG_ATOM); } + if (UNLIKELY(term_is_invalid_term(parse_monitor_opts(ctx, options, &is_alias, &alias_type)))) { + return term_invalid_term(); + } if (term_is_atom(target_proc)) { target_pid = globalcontext_get_registered_process(ctx->global, term_to_atom_index(target_proc)); target_proc_size = TUPLE_SIZE(2); @@ -4345,7 +4439,7 @@ static term nif_erlang_monitor(Context *ctx, int argc, term argv[]) local_process_id = term_to_local_process_id(target_pid); // Monitoring self is possible but no monitor is actually created if (UNLIKELY(local_process_id == ctx->process_id)) { - if (UNLIKELY(memory_ensure_free_opt(ctx, REF_SIZE, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { + if (UNLIKELY(memory_ensure_free_opt(ctx, TERM_BOXED_REFERENCE_SHORT_SIZE, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); } uint64_t ref_ticks = globalcontext_get_ref_ticks(ctx->global); @@ -4357,7 +4451,7 @@ static term nif_erlang_monitor(Context *ctx, int argc, term argv[]) } if (IS_NULL_PTR(target)) { - int res_size = REF_SIZE + TUPLE_SIZE(5) + target_proc_size; + int res_size = TERM_BOXED_REFERENCE_PROCESS_SIZE + TUPLE_SIZE(5) + target_proc_size; if (UNLIKELY(memory_ensure_free_opt(ctx, res_size, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); } @@ -4383,20 +4477,34 @@ static term nif_erlang_monitor(Context *ctx, int argc, term argv[]) if ((object_type == PROCESS_ATOM && target->native_handler != NULL) || (object_type == PORT_ATOM && target->native_handler == NULL)) { RAISE_ERROR(BADARG_ATOM); } - uint64_t ref_ticks = globalcontext_get_ref_ticks(ctx->global); - term monitoring_pid = term_from_local_process_id(ctx->process_id); + + RefData ref_data; + struct Monitor *alias_monitor = NULL; + if (is_alias) { + ref_data = (RefData){ .ref_ticks = globalcontext_get_ref_ticks(ctx->global), .process_id = ctx->process_id }; + alias_monitor = monitor_alias_new(&ref_data, alias_type); + if (IS_NULL_PTR(alias_monitor)) { + globalcontext_get_process_unlock(ctx->global, target); + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + } else { + ref_data = (RefData){ .ref_ticks = globalcontext_get_ref_ticks(ctx->global), .process_id = INVALID_PROCESS_ID }; + } struct Monitor *self_monitor; if (term_is_atom(target_proc)) { - self_monitor = monitor_registeredname_monitor_new(local_process_id, target_proc, ref_ticks); + self_monitor = monitor_registeredname_monitor_new(local_process_id, target_proc, &ref_data); } else { - self_monitor = monitor_new(target_pid, ref_ticks, true); + self_monitor = monitor_new(target_pid, &ref_data, true); } if (IS_NULL_PTR(self_monitor)) { globalcontext_get_process_unlock(ctx->global, target); + free(alias_monitor); RAISE_ERROR(OUT_OF_MEMORY_ATOM); } - struct Monitor *other_monitor = monitor_new(monitoring_pid, ref_ticks, false); + term monitoring_pid = term_from_local_process_id(ctx->process_id); + struct Monitor *other_monitor = monitor_new(monitoring_pid, &ref_data, false); if (IS_NULL_PTR(other_monitor)) { + free(alias_monitor); free(self_monitor); globalcontext_get_process_unlock(ctx->global, target); RAISE_ERROR(OUT_OF_MEMORY_ATOM); @@ -4405,12 +4513,15 @@ static term nif_erlang_monitor(Context *ctx, int argc, term argv[]) globalcontext_get_process_unlock(ctx->global, target); context_add_monitor(ctx, self_monitor); + if (is_alias) { + context_add_monitor(ctx, alias_monitor); + } - if (UNLIKELY(memory_ensure_free_opt(ctx, REF_SIZE, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { + if (UNLIKELY(memory_ensure_free_opt(ctx, TERM_BOXED_REFERENCE_PROCESS_SIZE, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); } - return term_from_ref_ticks(ref_ticks, &ctx->heap); + return term_from_ref_data(&ref_data, &ctx->heap); } static term nif_erlang_demonitor(Context *ctx, int argc, term argv[]) @@ -6568,6 +6679,42 @@ static term nif_erlang_list_to_bitstring_1(Context *ctx, int argc, term argv[]) return nif_erlang_list_to_binary_1(ctx, argc, argv); } +static term nif_erlang_alias(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + UNUSED(argv); + + if (UNLIKELY(memory_ensure_free_opt(ctx, TERM_BOXED_REFERENCE_PROCESS_SIZE, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + + RefData ref_data = { .ref_ticks = globalcontext_get_ref_ticks(ctx->global), .process_id = ctx->process_id }; + term process_ref = term_from_ref_data(&ref_data, &ctx->heap); + struct Monitor *monitor = monitor_alias_new(&ref_data, ContextMonitorAliasExplicitUnalias); + if (IS_NULL_PTR(monitor)) { + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + context_add_monitor(ctx, monitor); + return process_ref; +} + +static term nif_erlang_unalias(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + term process_ref = argv[0]; + VALIDATE_VALUE(process_ref, term_is_local_reference); + uint64_t ref_ticks = term_to_ref_ticks(process_ref); + + struct MonitorAlias *alias = context_find_alias(ctx, ref_ticks); + if (IS_NULL_PTR(alias)) { + return FALSE_ATOM; + } else { + context_unalias(alias); + return TRUE_ATOM; + } +} + #ifdef WITH_ZLIB static term nif_zlib_compress_1(Context *ctx, int argc, term argv[]) { diff --git a/src/libAtomVM/nifs.gperf b/src/libAtomVM/nifs.gperf index 9c87c362f9..0548761cea 100644 --- a/src/libAtomVM/nifs.gperf +++ b/src/libAtomVM/nifs.gperf @@ -89,6 +89,7 @@ erlang:make_ref/0, &make_ref_nif erlang:make_tuple/2, &make_tuple_nif erlang:memory/1, &memory_nif erlang:monitor/2, &monitor_nif +erlang:monitor/3, &monitor_nif erlang:demonitor/1, &demonitor_nif erlang:demonitor/2, &demonitor_nif erlang:is_process_alive/1, &is_process_alive_nif @@ -145,6 +146,8 @@ erlang:dist_ctrl_put_data/2, &dist_ctrl_put_data_nif erlang:module_loaded/1,&module_loaded_nif erlang:nif_error/1,&nif_error_nif erlang:list_to_bitstring/1, &list_to_bitstring_nif +erlang:alias/0, &erlang_alias_nif +erlang:unalias/1, &erlang_unalias_nif erts_debug:flat_size/1, &flat_size_nif ets:new/2, &ets_new_nif ets:insert/2, &ets_insert_nif diff --git a/src/libAtomVM/opcodesswitch.h b/src/libAtomVM/opcodesswitch.h index ab78fb8392..04f4ec7f55 100644 --- a/src/libAtomVM/opcodesswitch.h +++ b/src/libAtomVM/opcodesswitch.h @@ -2825,12 +2825,28 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) int local_process_id; if (term_is_local_pid_or_port(recipient_term)) { local_process_id = term_to_local_process_id(recipient_term); - } else { + TRACE("send/0 target_pid=%i\n", local_process_id); + TRACE_SEND(ctx, x_regs[0], x_regs[1]); + globalcontext_send_message(ctx->global, local_process_id, x_regs[1]); + } else if (term_is_process_reference(recipient_term)) { + int32_t local_process_id = term_process_ref_to_process_id(recipient_term); + TRACE("send/0 target_pid=%i\n", local_process_id); + TRACE_SEND(ctx, x_regs[0], x_regs[1]); + int64_t ref_ticks = term_to_ref_ticks(recipient_term); + Context *p = globalcontext_get_process_lock(glb, local_process_id); + if (p) { + struct MonitorAlias *alias = context_find_alias(p, ref_ticks); + if (alias != NULL) { + if (alias->alias_type == ContextMonitorAliasReplyDemonitor) { + context_unalias(alias); + } + mailbox_send(p, x_regs[1]); + } + globalcontext_get_process_unlock(glb, p); + } + } else if (!term_is_reference(recipient_term)) { RAISE_ERROR(BADARG_ATOM); } - TRACE("send/0 target_pid=%i\n", local_process_id); - TRACE_SEND(ctx, x_regs[0], x_regs[1]); - globalcontext_send_message(ctx->global, local_process_id, x_regs[1]); x_regs[0] = x_regs[1]; } #endif diff --git a/src/libAtomVM/otp_socket.c b/src/libAtomVM/otp_socket.c index 63e25919a8..bb8778c2e5 100644 --- a/src/libAtomVM/otp_socket.c +++ b/src/libAtomVM/otp_socket.c @@ -246,7 +246,7 @@ static const AtomStringIntPair otp_socket_setopt_level_table[] = { static ErlNifResourceType *socket_resource_type; -#define SOCKET_MAKE_SELECT_NOTIFICATION_SIZE (TUPLE_SIZE(4) + REF_SIZE + TUPLE_SIZE(2) + REF_SIZE + TERM_BOXED_REFERENCE_RESOURCE_SIZE) +#define SOCKET_MAKE_SELECT_NOTIFICATION_SIZE (TUPLE_SIZE(4) + TERM_BOXED_REFERENCE_SHORT_SIZE + TUPLE_SIZE(2) + TERM_BOXED_REFERENCE_SHORT_SIZE + TERM_BOXED_REFERENCE_RESOURCE_SIZE) static term socket_make_select_notification(struct SocketResource *rsrc_obj, Heap *heap); // @@ -644,7 +644,7 @@ static term nif_socket_open(Context *ctx, int argc, term argv[]) term obj = enif_make_resource(erl_nif_env_from_context(ctx), rsrc_obj); enif_release_resource(rsrc_obj); // decrement refcount after enif_alloc_resource - size_t requested_size = TUPLE_SIZE(2) + TUPLE_SIZE(2) + REF_SIZE; + size_t requested_size = TUPLE_SIZE(2) + TUPLE_SIZE(2) + TERM_BOXED_REFERENCE_SHORT_SIZE; if (UNLIKELY(memory_ensure_free_with_roots(ctx, requested_size, 1, &obj, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { AVM_LOGW(TAG, "Failed to allocate memory: %s:%i.", __FILE__, __LINE__); RAISE_ERROR(OUT_OF_MEMORY_ATOM); @@ -695,7 +695,7 @@ bool term_is_otp_socket(term socket_term) static int send_closed_notification(Context *ctx, term socket_term, int32_t selecting_process_id, struct SocketResource *rsrc_obj) { // send a {'$socket', Socket, abort, {Ref | undefined, closed}} message to the pid - if (UNLIKELY(memory_ensure_free_with_roots(ctx, TUPLE_SIZE(4) + TUPLE_SIZE(2) + REF_SIZE, 1, &socket_term, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { + if (UNLIKELY(memory_ensure_free_with_roots(ctx, TUPLE_SIZE(4) + TUPLE_SIZE(2) + TERM_BOXED_REFERENCE_SHORT_SIZE, 1, &socket_term, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { AVM_LOGW(TAG, "Failed to allocate memory: %s:%i.", __FILE__, __LINE__); return -1; } @@ -1902,7 +1902,7 @@ static term nif_socket_accept(Context *ctx, int argc, term argv[]) term new_resource = enif_make_resource(erl_nif_env_from_context(ctx), conn_rsrc_obj); enif_release_resource(conn_rsrc_obj); // decrement refcount after enif_alloc_resource - size_t requested_size = TUPLE_SIZE(2) + TUPLE_SIZE(2) + REF_SIZE; + size_t requested_size = TUPLE_SIZE(2) + TUPLE_SIZE(2) + TERM_BOXED_REFERENCE_SHORT_SIZE; if (UNLIKELY(memory_ensure_free_with_roots(ctx, requested_size, 1, &new_resource, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { AVM_LOGW(TAG, "Failed to allocate memory: %s:%i.", __FILE__, __LINE__); RAISE_ERROR(OUT_OF_MEMORY_ATOM); @@ -1933,7 +1933,7 @@ static term nif_socket_accept(Context *ctx, int argc, term argv[]) SMP_RWLOCK_UNLOCK(rsrc_obj->socket_lock); RAISE_ERROR(OUT_OF_MEMORY_ATOM); } - size_t requested_size = TERM_BOXED_REFERENCE_RESOURCE_SIZE + TUPLE_SIZE(2) + TUPLE_SIZE(2) + REF_SIZE; + size_t requested_size = TERM_BOXED_REFERENCE_RESOURCE_SIZE + TUPLE_SIZE(2) + TUPLE_SIZE(2) + TERM_BOXED_REFERENCE_SHORT_SIZE; if (UNLIKELY(memory_ensure_free_with_roots(ctx, requested_size, 1, argv, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { AVM_LOGW(TAG, "Failed to allocate memory: %s:%i.", __FILE__, __LINE__); LWIP_END(); @@ -2311,7 +2311,7 @@ static term nif_socket_recv_lwip(Context *ctx, term resource_term, struct Socket } size_t ensure_packet_avail = term_binary_data_size_in_terms(len) + BINARY_HEADER_SIZE; - size_t requested_size = REF_SIZE + 2 * TUPLE_SIZE(2) + ensure_packet_avail + (is_recvfrom ? (TUPLE_SIZE(2) + INET_ADDR4_TUPLE_SIZE + TERM_MAP_SIZE(2)) : 0); + size_t requested_size = TERM_BOXED_REFERENCE_SHORT_SIZE + 2 * TUPLE_SIZE(2) + ensure_packet_avail + (is_recvfrom ? (TUPLE_SIZE(2) + INET_ADDR4_TUPLE_SIZE + TERM_MAP_SIZE(2)) : 0); // Because resource is locked, we must ensure it's not garbage collected if (UNLIKELY(memory_ensure_free_with_roots(ctx, requested_size, 1, &resource_term, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { AVM_LOGW(TAG, "Failed to allocate memory: %s:%i.\n", __FILE__, __LINE__); diff --git a/src/libAtomVM/resources.h b/src/libAtomVM/resources.h index 5c95a5c4ed..58c97cf9b6 100644 --- a/src/libAtomVM/resources.h +++ b/src/libAtomVM/resources.h @@ -164,7 +164,7 @@ void select_event_count_and_destroy_closed(struct ListHead *select_events, size_ */ void destroy_resource_monitors(struct RefcBinary *resource, GlobalContext *global); -#define SELECT_EVENT_NOTIFICATION_SIZE (TUPLE_SIZE(4) + REF_SIZE + TERM_BOXED_REFERENCE_RESOURCE_SIZE) +#define SELECT_EVENT_NOTIFICATION_SIZE (TUPLE_SIZE(4) + TERM_BOXED_REFERENCE_SHORT_SIZE + TERM_BOXED_REFERENCE_RESOURCE_SIZE) /** * @brief Build a select event notification. diff --git a/src/libAtomVM/term.c b/src/libAtomVM/term.c index eb627dd018..ac5110fa56 100644 --- a/src/libAtomVM/term.c +++ b/src/libAtomVM/term.c @@ -405,6 +405,12 @@ int term_funprint(PrinterFun *fun, term t, const GlobalContext *global) uint64_t resource_ptr = (uintptr_t) refc_binary->data; return fun->print(fun, "#Ref<0.%" PRIu32 ".%" PRIu32 ".%" PRIu32 ".%" PRIu32 ">", (uint32_t) (resource_type_ptr >> 32), (uint32_t) resource_type_ptr, (uint32_t) (resource_ptr >> 32), (uint32_t) resource_ptr); + } else if (term_is_process_reference(t)) { + int32_t process_id = term_process_ref_to_process_id(t); + uint64_t ref_ticks = term_to_ref_ticks(t); + + // Update also REF_AS_CSTRING_LEN when changing this format string + return fun->print(fun, "#Ref<%" PRId32 ".%" PRIu32 ".%" PRIu32 ">", process_id, (uint32_t) (ref_ticks >> 32), (uint32_t) ref_ticks); } else if (term_is_local_reference(t)) { // Update also REF_AS_CSTRING_LEN when changing this format string uint64_t ref_ticks = term_to_ref_ticks(t); @@ -676,11 +682,15 @@ TermCompareResult term_compare(term t, term other, TermCompareOpts opts, GlobalC uint32_t len, other_len; if (term_is_resource_reference(t)) { len = 4; + } else if (term_is_process_reference(t)) { + len = 3; } else { len = 2; } if (term_is_resource_reference(other)) { other_len = 4; + } else if (term_is_process_reference(other)) { + other_len = 3; } else { other_len = 2; } @@ -694,6 +704,15 @@ TermCompareResult term_compare(term t, term other, TermCompareOpts opts, GlobalC int64_t other_ticks = term_to_ref_ticks(other); other_data[0] = other_ticks >> 32; other_data[1] = (uint32_t) other_ticks; + } else if (len == 3) { + data[0] = term_process_ref_to_process_id(t); + int64_t t_ticks = term_to_ref_ticks(t); + data[1] = t_ticks >> 32; + data[2] = (uint32_t) t_ticks; + other_data[0] = term_process_ref_to_process_id(other); + int64_t other_ticks = term_to_ref_ticks(other); + other_data[1] = other_ticks >> 32; + other_data[2] = (uint32_t) other_ticks; } else { // len == 4 struct RefcBinary *refc = term_resource_refc_binary_ptr(t); diff --git a/src/libAtomVM/term.h b/src/libAtomVM/term.h index eae9f58635..b3d531eb07 100644 --- a/src/libAtomVM/term.h +++ b/src/libAtomVM/term.h @@ -129,7 +129,14 @@ extern "C" { #define TERM_BOXED_REFC_BINARY_SIZE 6 #define TERM_BOXED_BIN_MATCH_STATE_SIZE 4 #define TERM_BOXED_SUB_BINARY_SIZE 4 +#if TERM_BYTES == 8 #define TERM_BOXED_REFERENCE_RESOURCE_SIZE 4 +#else +// Enough size would be 4, but reference types +// are distinguished by size and 4 conflicts with +// TERM_BOXED_REFERENCE_PROCESS_SIZE on 32bit arch. +#define TERM_BOXED_REFERENCE_RESOURCE_SIZE 5 +#endif #define TERM_BOXED_REFERENCE_RESOURCE_HEADER (((TERM_BOXED_REFERENCE_RESOURCE_SIZE - 1) << 6) | TERM_BOXED_REF) #define TERM_BOXED_RESOURCE_SIZE TERM_BOXED_REFERENCE_RESOURCE_SIZE @@ -151,7 +158,13 @@ extern "C" { #define BOXED_INT64_SIZE (BOXED_TERMS_REQUIRED_FOR_INT64 + 1) #define BOXED_FUN_SIZE 3 #define FLOAT_SIZE (sizeof(float_term_t) / sizeof(term) + 1) -#define REF_SIZE ((int) ((sizeof(uint64_t) / sizeof(term)) + 1)) +// Reference types are distinguished by their size. +// If you change a reference size, make sure it doesn't +// conflict with other reference sizes on all architectures. +#define TERM_BOXED_REFERENCE_SHORT_SIZE ((int) ((sizeof(uint64_t) / sizeof(term)) + 1)) +#define REF_SIZE _Pragma("REF_SIZE is deprecated, use TERM_BOXED_REFERENCE_SHORT_SIZE instead") TERM_BOXED_REFERENCE_SHORT_SIZE +#define TERM_BOXED_REFERENCE_PROCESS_SIZE (TERM_BOXED_REFERENCE_SHORT_SIZE + 1) +#define TERM_BOXED_REFERENCE_PROCESS_HEADER (((TERM_BOXED_REFERENCE_PROCESS_SIZE - 1) << 6) | TERM_BOXED_REF) #if TERM_BYTES == 8 #define EXTERNAL_PID_SIZE 3 #elif TERM_BYTES == 4 @@ -167,10 +180,23 @@ extern "C" { #else #error #endif +#define EXTERNAL_REF_MAX_WORDS 5 +#define TERM_BOXED_REFERENCE_MAX_SIZE EXTERNAL_REF_SIZE(EXTERNAL_REF_MAX_WORDS) +_Static_assert(TERM_BOXED_REFERENCE_SHORT_SIZE < TERM_BOXED_REFERENCE_PROCESS_SIZE, "Short ref size must be smaller than process ref size"); +_Static_assert(TERM_BOXED_REFERENCE_PROCESS_SIZE < TERM_BOXED_REFERENCE_RESOURCE_SIZE, "Process ref size must be smaller than reference resource size"); +_Static_assert(TERM_BOXED_REFERENCE_PROCESS_SIZE <= TERM_BOXED_REFERENCE_MAX_SIZE, "Max ref size can't be smaller than all other ref sizes"); #define TUPLE_SIZE(elems) ((int) (elems + 1)) #define CONS_SIZE 2 #define REFC_BINARY_CONS_OFFSET 4 #define REFERENCE_RESOURCE_CONS_OFFSET 2 + +#if TERM_BYTES == 4 +#define REFERENCE_PROCESS_PID_OFFSET 3 + +#elif TERM_BYTES == 8 +#define REFERENCE_PROCESS_PID_OFFSET 2 +#endif + #define LIST_SIZE(num_elements, element_size) ((num_elements) * ((element_size) + CONS_SIZE)) #define TERM_STRING_SIZE(length) (2 * (length)) #define TERM_MAP_SIZE(num_elements) (3 + 2 * (num_elements)) @@ -179,6 +205,8 @@ extern "C" { #define LIST_HEAD_INDEX 1 #define LIST_TAIL_INDEX 0 +#define INVALID_PROCESS_ID 0 + #define TERM_BINARY_SIZE_IS_HEAP(size) ((size) < REFC_BINARY_MIN) #if TERM_BYTES == 4 @@ -210,6 +238,9 @@ extern "C" { // Local ref is at most 30 bytes: // 2^32-1 = 4294967295 (10 chars) // "#Ref<0." "." ">\0" (10 chars) +// Process ref is at most 39 bytes: +// 2^32-1 = 4294967295 (10 chars) +// "#Ref<" "." "." ">\0" (9 chars) // Resource ref is at most 52 bytes: // 2^32-1 = 4294967295 (10 chars) // "#Ref<0." "." "." "." ">\0" (12 chars) @@ -243,6 +274,21 @@ extern "C" { typedef struct GlobalContext GlobalContext; #endif +enum RefType +{ + RefTypeShort, + RefTypeProcess, + RefTypeResource, + RefTypeExternal +}; + +typedef struct RefData RefData; +struct RefData +{ + uint64_t ref_ticks; + int32_t process_id; +}; + typedef struct PrinterFun PrinterFun; typedef int (*printer_function_t)(PrinterFun *fun, const char *fmt, ...) PRINTF_FORMAT_ARGS(2, 3); @@ -877,6 +923,25 @@ static inline bool term_is_local_reference(term t) return false; } +/** + * @brief Checks if a term is a process reference + * + * @details See \c term_make_process_reference(). + * @param t the term that will be checked. + * @return \c true if check succeeds, \c false otherwise. + */ +static inline bool term_is_process_reference(term t) +{ + if (term_is_boxed(t)) { + const term *boxed_value = term_to_const_term_ptr(t); + if (boxed_value[0] == TERM_BOXED_REFERENCE_PROCESS_HEADER) { + return true; + } + } + + return false; +} + /** * @brief Checks if a term is an external reference * @@ -2156,8 +2221,8 @@ static inline int term_bs_insert_binary(term t, int offset, term src, int n) */ static inline term term_from_ref_ticks(uint64_t ref_ticks, Heap *heap) { - term *boxed_value = memory_heap_alloc(heap, REF_SIZE); - boxed_value[0] = ((REF_SIZE - 1) << 6) | TERM_BOXED_REF; + term *boxed_value = memory_heap_alloc(heap, TERM_BOXED_REFERENCE_SHORT_SIZE); + boxed_value[0] = ((TERM_BOXED_REFERENCE_SHORT_SIZE - 1) << 6) | TERM_BOXED_REF; #if TERM_BYTES == 8 boxed_value[1] = (term) ref_ticks; @@ -2190,6 +2255,50 @@ static inline uint64_t term_to_ref_ticks(term rt) #endif } +/** + * @brief Creates a process reference + * @details Process reference contains ref_ticks and process_id of a process. + * They are used by process aliases and monitors. + * + * @param process_id process_id of a process that the reference will identify. + * @param ref_ticks an unique uint64 value that will be used to create ref term. + * @param heap the heap to allocate memory in + * @return a ref term created using given ref ticks. + */ +static inline term term_make_process_reference(int32_t process_id, uint64_t ref_ticks, Heap *heap) +{ + term *boxed_value = memory_heap_alloc(heap, TERM_BOXED_REFERENCE_PROCESS_SIZE); + boxed_value[0] = TERM_BOXED_REFERENCE_PROCESS_HEADER; + +#if TERM_BYTES == 4 + boxed_value[1] = (ref_ticks >> 32); + boxed_value[2] = (ref_ticks & 0xFFFFFFFF); + boxed_value[3] = process_id; + +#elif TERM_BYTES == 8 + boxed_value[1] = (term) ref_ticks; + boxed_value[2] = process_id; + +#else +#error "terms must be either 32 or 64 bit wide" +#endif + + return ((term) boxed_value) | TERM_PRIMARY_BOXED; +} + +static inline uint32_t term_process_ref_to_process_id(term rt) +{ + TERM_DEBUG_ASSERT(term_is_process_reference(rt)); + const term *boxed_value = term_to_const_term_ptr(rt); +#if TERM_BYTES == 4 + return (uint32_t) boxed_value[3]; +#elif TERM_BYTES == 8 + return (uint32_t) boxed_value[2]; +#else +#error "terms must be either 32 or 64 bit wide" +#endif +} + /** * @brief Make an external pid term from node, process_id, serial and creation * @@ -2326,7 +2435,7 @@ static inline uint64_t term_get_external_port_number(term t) * @param heap the heap to allocate memory in * @return an external heap term created using given parameters. */ -static inline term term_make_external_reference(term node, uint16_t len, uint32_t *data, uint32_t creation, Heap *heap) +static inline term term_make_external_reference(term node, uint16_t len, const uint32_t *data, uint32_t creation, Heap *heap) { TERM_DEBUG_ASSERT(term_is_atom(node)); @@ -2995,6 +3104,15 @@ static inline term term_from_resource(void *resource, Heap *heap) return ret; } +static inline term term_from_ref_data(RefData *ref_data, Heap *heap) +{ + if (ref_data->process_id == INVALID_PROCESS_ID) { + return term_from_ref_ticks(ref_data->ref_ticks, heap); + } else { + return term_make_process_reference(ref_data->process_id, ref_data->ref_ticks, heap); + } +} + /** * @brief Get a resource term from a resource type and a serialization reference * number. diff --git a/src/platforms/emscripten/src/lib/websocket_nifs.c b/src/platforms/emscripten/src/lib/websocket_nifs.c index a6d0bc8c89..ab8e084652 100644 --- a/src/platforms/emscripten/src/lib/websocket_nifs.c +++ b/src/platforms/emscripten/src/lib/websocket_nifs.c @@ -95,7 +95,7 @@ static void websocket_down(ErlNifEnv *caller_env, void *obj, ErlNifPid *pid, Erl } } -#define TERM_WEBSOCKET_RESOURCE_SIZE (TERM_BOXED_RESOURCE_SIZE + REF_SIZE + TUPLE_SIZE(3)) +#define TERM_WEBSOCKET_RESOURCE_SIZE (TERM_BOXED_RESOURCE_SIZE + TERM_BOXED_REFERENCE_SHORT_SIZE + TUPLE_SIZE(3)) static term term_make_websocket_resource(struct WebsocketResource *rsrc, Heap *heap) { diff --git a/src/platforms/esp32/components/avm_builtins/adc_driver.c b/src/platforms/esp32/components/avm_builtins/adc_driver.c index 67eaa6ac3b..3a7392e401 100644 --- a/src/platforms/esp32/components/avm_builtins/adc_driver.c +++ b/src/platforms/esp32/components/avm_builtins/adc_driver.c @@ -352,7 +352,7 @@ static term nif_adc_init(Context *ctx, int argc, term argv[]) enif_release_resource(unit_rsrc); // decrement refcount after enif_alloc_resource // {ok, {'$adc', Unit :: resource(), ref()}} - size_t requested_size = TUPLE_SIZE(2) + TUPLE_SIZE(3) + REF_SIZE; + size_t requested_size = TUPLE_SIZE(2) + TUPLE_SIZE(3) + TERM_BOXED_REFERENCE_SHORT_SIZE; ESP_LOGD(TAG, "Requesting memory size %u for return message", requested_size); if (UNLIKELY(memory_ensure_free_with_roots(ctx, requested_size, 1, &unit_obj, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { ESP_LOGE(TAG, "failed to allocate tuple memory size %u: %s:%i.", requested_size, __FILE__, __LINE__); @@ -504,7 +504,7 @@ static term nif_adc_acquire(Context *ctx, int argc, term argv[]) enif_release_resource(chan_rsrc); // decrement refcount after enif_alloc_resource // {ok, {'$adc', resource(), ref()}} - size_t requested_size = TUPLE_SIZE(2) + TUPLE_SIZE(3) + REF_SIZE; + size_t requested_size = TUPLE_SIZE(2) + TUPLE_SIZE(3) + TERM_BOXED_REFERENCE_SHORT_SIZE; ESP_LOGD(TAG, "Requesting memory size %u for return message", requested_size); if (UNLIKELY(memory_ensure_free_with_roots(ctx, requested_size, 1, &chan_obj, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { ESP_LOGE(TAG, "failed to allocate tuple memory size %u: %s:%i.", requested_size, __FILE__, __LINE__); diff --git a/src/platforms/esp32/components/avm_builtins/i2c_resource.c b/src/platforms/esp32/components/avm_builtins/i2c_resource.c index 246b8aa386..5df5ecdcf7 100644 --- a/src/platforms/esp32/components/avm_builtins/i2c_resource.c +++ b/src/platforms/esp32/components/avm_builtins/i2c_resource.c @@ -230,7 +230,7 @@ static term nif_i2c_open(Context *ctx, int argc, term argv[]) // // {'$i2c', Resource :: resource(), Ref :: reference()} :: i2c() - size_t requested_size = TUPLE_SIZE(3) + REF_SIZE; + size_t requested_size = TUPLE_SIZE(3) + TERM_BOXED_REFERENCE_SHORT_SIZE; if (UNLIKELY(memory_ensure_free_with_roots(ctx, requested_size, 1, &obj, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { i2c_driver_delete(i2c_num); ESP_LOGW(TAG, "Failed to allocate memory: %s:%i.", __FILE__, __LINE__); diff --git a/src/platforms/esp32/components/avm_builtins/network_driver.c b/src/platforms/esp32/components/avm_builtins/network_driver.c index c67d481f06..b02bbf678d 100644 --- a/src/platforms/esp32/components/avm_builtins/network_driver.c +++ b/src/platforms/esp32/components/avm_builtins/network_driver.c @@ -58,7 +58,7 @@ #define TCPIP_HOSTNAME_MAX_SIZE 255 #define TAG "network_driver" -#define PORT_REPLY_SIZE (TUPLE_SIZE(2) + REF_SIZE) +#define PORT_REPLY_SIZE (TUPLE_SIZE(2) + TERM_BOXED_REFERENCE_SHORT_SIZE) static const char *const ap_atom = ATOM_STR("\x2", "ap"); static const char *const ap_channel_atom = ATOM_STR("\xA", "ap_channel"); @@ -1044,7 +1044,7 @@ static NativeHandlerResult consume_mailbox(Context *ctx) return NativeContinue; } - // TODO: port this code to standard port (and gen_message) + //TODO: port this code to standard port (and gen_message) term pid = term_get_tuple_element(msg, 0); term ref = term_get_tuple_element(msg, 1); term cmd = term_get_tuple_element(msg, 2); @@ -1086,7 +1086,7 @@ static NativeHandlerResult consume_mailbox(Context *ctx) default: { ESP_LOGE(TAG, "Unrecognized command: %x", cmd); // {Ref, {error, badarg}} - size_t heap_size = TUPLE_SIZE(2) + REF_SIZE + TUPLE_SIZE(2); + size_t heap_size = TUPLE_SIZE(2) + TERM_BOXED_REFERENCE_SHORT_SIZE + TUPLE_SIZE(2); if (UNLIKELY(memory_ensure_free(ctx, heap_size) != MEMORY_GC_OK)) { ESP_LOGE(TAG, "Unable to allocate heap space for error; no message sent"); return NativeContinue; @@ -1096,7 +1096,7 @@ static NativeHandlerResult consume_mailbox(Context *ctx) } } else { // {Ref, {error, badarg}} - size_t heap_size = TUPLE_SIZE(2) + REF_SIZE + TUPLE_SIZE(2); + size_t heap_size = TUPLE_SIZE(2) + TERM_BOXED_REFERENCE_SHORT_SIZE + TUPLE_SIZE(2); if (UNLIKELY(memory_ensure_free(ctx, heap_size) != MEMORY_GC_OK)) { ESP_LOGE(TAG, "Unable to allocate heap space for error; no message sent"); return NativeContinue; diff --git a/src/platforms/esp32/components/avm_builtins/socket_driver.c b/src/platforms/esp32/components/avm_builtins/socket_driver.c index 00fc7d9e3c..feb75178e2 100644 --- a/src/platforms/esp32/components/avm_builtins/socket_driver.c +++ b/src/platforms/esp32/components/avm_builtins/socket_driver.c @@ -438,7 +438,7 @@ static struct UDPSocketData *udp_socket_data_new(Context *ctx, struct netconn *c } // When this method is called, ensure free was called with REPLY_SIZE -#define REPLY_SIZE (TUPLE_SIZE(2) + REF_SIZE) +#define REPLY_SIZE (TUPLE_SIZE(2) + TERM_BOXED_REFERENCE_SHORT_SIZE) static void do_send_reply(Context *ctx, term reply, uint64_t ref_ticks, int32_t pid) { GlobalContext *glb = ctx->global; diff --git a/src/platforms/esp32/components/avm_builtins/uart_driver.c b/src/platforms/esp32/components/avm_builtins/uart_driver.c index 3f73369876..e913057629 100644 --- a/src/platforms/esp32/components/avm_builtins/uart_driver.c +++ b/src/platforms/esp32/components/avm_builtins/uart_driver.c @@ -128,7 +128,7 @@ EventListener *uart_interrupt_callback(GlobalContext *glb, EventListener *listen int bin_size = term_binary_heap_size(event.size); Heap heap; - if (UNLIKELY(memory_init_heap(&heap, bin_size + REF_SIZE + TUPLE_SIZE(2) * 2) != MEMORY_GC_OK)) { + if (UNLIKELY(memory_init_heap(&heap, bin_size + TERM_BOXED_REFERENCE_SHORT_SIZE + TUPLE_SIZE(2) * 2) != MEMORY_GC_OK)) { fprintf(stderr, "Failed to allocate memory: %s:%i.\n", __FILE__, __LINE__); AVM_ABORT(); } @@ -511,7 +511,7 @@ static NativeHandlerResult uart_driver_consume_mailbox(Context *ctx) int local_pid = term_to_local_process_id(gen_message.pid); if (is_closed) { - if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2) * 2 + REF_SIZE) != MEMORY_GC_OK)) { + if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2) * 2 + TERM_BOXED_REFERENCE_SHORT_SIZE) != MEMORY_GC_OK)) { ESP_LOGE(TAG, "[uart_driver_consume_mailbox] Failed to allocate space for error tuple"); globalcontext_send_message(glb, local_pid, OUT_OF_MEMORY_ATOM); } diff --git a/src/platforms/rp2/src/lib/networkdriver.c b/src/platforms/rp2/src/lib/networkdriver.c index bdef70eb75..374f9e85f0 100644 --- a/src/platforms/rp2/src/lib/networkdriver.c +++ b/src/platforms/rp2/src/lib/networkdriver.c @@ -41,7 +41,7 @@ #pragma GCC diagnostic pop -#define PORT_REPLY_SIZE (TUPLE_SIZE(2) + REF_SIZE) +#define PORT_REPLY_SIZE (TUPLE_SIZE(2) + TERM_BOXED_REFERENCE_SHORT_SIZE) #define DEFAULT_HOSTNAME_FMT "atomvm-%02x%02x%02x%02x%02x%02x" #define DEFAULT_HOSTNAME_SIZE (strlen("atomvm-") + 12 + 1) @@ -758,7 +758,7 @@ static NativeHandlerResult consume_mailbox(Context *ctx) default: { // {Ref, {error, badarg}} - size_t heap_size = TUPLE_SIZE(2) + REF_SIZE + TUPLE_SIZE(2); + size_t heap_size = TUPLE_SIZE(2) + TERM_BOXED_REFERENCE_SHORT_SIZE + TUPLE_SIZE(2); if (UNLIKELY(memory_ensure_free(ctx, heap_size) != MEMORY_GC_OK)) { return NativeContinue; } @@ -767,7 +767,7 @@ static NativeHandlerResult consume_mailbox(Context *ctx) } } else { // {Ref, {error, badarg}} - size_t heap_size = TUPLE_SIZE(2) + REF_SIZE + TUPLE_SIZE(2); + size_t heap_size = TUPLE_SIZE(2) + TERM_BOXED_REFERENCE_SHORT_SIZE + TUPLE_SIZE(2); if (UNLIKELY(memory_ensure_free(ctx, heap_size) != MEMORY_GC_OK)) { return NativeContinue; } diff --git a/tests/erlang_tests/test_binary_to_term.erl b/tests/erlang_tests/test_binary_to_term.erl index eb6129dc74..616479ad7c 100644 --- a/tests/erlang_tests/test_binary_to_term.erl +++ b/tests/erlang_tests/test_binary_to_term.erl @@ -180,6 +180,7 @@ start() -> ok = test_encode_port(), ok = test_atom_encoding(), ok = test_encode_resource(), + ok = test_encode_process_ref(), 0. test_reverse(T, Interop) -> @@ -1136,23 +1137,34 @@ test_encode_resource(OTPVersion) -> AlteredResource4 = binary_to_term(AlteredResourceBin4), false = AlteredResource4 =:= Resource, ok. +test_encode_process_ref() -> + AliasesAvailable = is_atomvm_or_otp_version_at_least("24"), + if + AliasesAvailable -> + ProcessRef = erlang:alias(), + ProcessRef = binary_to_term(term_to_binary(ProcessRef)), + ok; + true -> + ok + end. % Some binaries are re-encoded differently on earlier BEAM. Verify % term_to_binary(binary_to_term(Bin)) is idempotent on AtomVM and recent OTPs. binary_to_term_idempotent(Binary, OTPVersion) -> Term = binary_to_term(Binary), - case erlang:system_info(machine) of - "ATOM" -> - Binary = term_to_binary(Term); - "BEAM" -> - OTPRelease = erlang:system_info(otp_release), - if - OTPRelease >= OTPVersion -> Binary = term_to_binary(Term); - true -> ok - end + CanCheck = is_atomvm_or_otp_version_at_least(OTPVersion), + if + CanCheck -> Binary = term_to_binary(Term); + true -> ok end, Term. +is_atomvm_or_otp_version_at_least(OTPVersion) -> + case erlang:system_info(machine) of + "ATOM" -> true; + "BEAM" -> erlang:system_info(otp_release) >= OTPVersion + end. + test_atom_encoding() -> true = compare_pair_encoding(latin1_as_utf8_1), true = compare_pair_encoding(latin1_as_utf8_2), diff --git a/tests/erlang_tests/test_monitor.erl b/tests/erlang_tests/test_monitor.erl index 55b070e74a..7d742fee44 100644 --- a/tests/erlang_tests/test_monitor.erl +++ b/tests/erlang_tests/test_monitor.erl @@ -34,6 +34,33 @@ start() -> ok = test_monitor_demonitor_from_other(), ok = test_monitor_registered(), ok = test_monitor_registered_noproc(), + + AliasesAvailable = + case erlang:system_info(machine) of + "ATOM" -> true; + "BEAM" -> erlang:system_info(otp_release) >= "24" + end, + if + AliasesAvailable -> + ok = test_alias(), + ok = test_multiple_aliases(), + ok = test_multiple_unaliases(), + ok = test_unalias_from_wrong_process(), + ok = test_monitor_alias_dead_process(), + ok = test_monitor_multiple_aliases_monitors(fun spawn_monitor/2), + ok = test_monitor_multiple_aliases_monitors(fun spawn_and_monitor/2), + ok = test_monitor_alias_demonitor(fun spawn_monitor/2), + ok = test_monitor_alias_demonitor(fun spawn_and_monitor/2), + ok = test_monitor_alias_explicit_unalias(fun spawn_monitor/2), + ok = test_monitor_alias_explicit_unalias(fun spawn_and_monitor/2), + ok = test_monitor_alias_reply_demonitor(fun spawn_monitor/2), + ok = test_monitor_alias_reply_demonitor(fun spawn_and_monitor/2), + ok = test_monitor_down_alias(fun spawn_monitor/2), + ok = test_monitor_down_alias(fun spawn_and_monitor/2), + ok; + true -> + ok + end, 0. test_monitor_normal() -> @@ -229,7 +256,131 @@ test_monitor_demonitor_from_other() -> end, ok. +test_alias() -> + P = spawn_opt(fun echo_loop/0, []), + Alias = erlang:alias(), + do_test_alias(P, Alias), + ok. + +test_multiple_aliases() -> + P = spawn_opt(fun echo_loop/0, []), + A1 = erlang:alias(), + A2 = erlang:alias(), + A3 = erlang:alias(), + do_test_alias(P, A1), + do_test_alias(P, A3), + do_test_alias(P, A2), + ok. + +test_multiple_unaliases() -> + A = erlang:alias(), + true = erlang:unalias(A), + false = erlang:unalias(A), + false = erlang:unalias(A), + ok. + +test_unalias_from_wrong_process() -> + A = erlang:alias(), + TestProcess = self(), + spawn_opt(fun() -> TestProcess ! erlang:unalias(A) end, [link]), + false = recv_one(), + P = spawn_opt(fun echo_loop/0, []), + do_test_alias(P, A), + ok. + +do_test_alias(P, Alias) -> + do_test_alias(P, Alias, fun erlang:unalias/1). + +do_test_alias(P, Alias, UnaliasFun) -> + Ref = make_ref(), + P ! {{m1, Ref}, Alias}, + {m1, Ref} = recv_one(), + UnaliasFun(Alias), + P ! {{m2, Ref}, Alias}, + P ! {{m3, Ref}, self()}, + {m3, Ref} = recv_one(), + ok. + +test_monitor_alias_demonitor(SpawnFun) -> + {P, Mon} = SpawnFun(fun echo_loop/0, [{alias, demonitor}]), + do_test_alias(P, Mon, fun demonitor/1), + ok. + +test_monitor_alias_explicit_unalias(SpawnFun) -> + {P, Mon} = SpawnFun(fun echo_loop/0, [{alias, explicit_unalias}]), + P ! {m1, Mon}, + m1 = recv_one(), + demonitor(Mon), + do_test_alias(P, Mon), + ok. + +test_monitor_alias_reply_demonitor(SpawnFun) -> + {P, Mon} = SpawnFun(fun echo_loop/0, [{alias, reply_demonitor}]), + do_test_alias(P, Mon, fun(_Mon) -> ok end), + ok. + +test_monitor_down_alias(SpawnFun) -> + {P, Mon} = SpawnFun(fun echo_loop/0, [{alias, demonitor}]), + erlang:unalias(Mon), + P ! {m1, Mon}, + P ! {m2, self()}, + m2 = recv_one(), + P ! quit, + {'DOWN', Mon, process, P, normal} = recv_one(), + ok. + +test_monitor_multiple_aliases_monitors(SpawnFun) -> + {P, Mon1} = SpawnFun(fun echo_loop/0, [{alias, demonitor}]), + Mon2 = erlang:monitor(process, P, [{alias, reply_demonitor}]), + Mon3 = erlang:monitor(process, P, [{alias, explicit_unalias}]), + Mon4 = erlang:monitor(process, P), + A1 = erlang:alias(), + A2 = erlang:alias(), + do_test_alias(P, A2), + do_test_alias(P, Mon3), + do_test_alias(P, A1), + do_test_alias(P, Mon1, fun demonitor/1), + P ! quit, + {'DOWN', Mon2, process, P, normal} = recv_one(), + {'DOWN', Mon3, process, P, normal} = recv_one(), + {'DOWN', Mon4, process, P, normal} = recv_one(), + ok. + +test_monitor_alias_dead_process() -> + {P, Mon0} = spawn_opt(fun() -> ok end, [monitor]), + {'DOWN', Mon0, process, P, normal} = recv_one(), + Mon1 = erlang:monitor(process, P, [{alias, demonitor}]), + {'DOWN', Mon1, process, P, noproc} = recv_one(), + Mon2 = erlang:monitor(process, P, [{alias, reply_demonitor}]), + {'DOWN', Mon2, process, P, noproc} = recv_one(), + Mon3 = erlang:monitor(process, P, [{alias, explicit_unalias}]), + {'DOWN', Mon3, process, P, noproc} = recv_one(), + ok. + +spawn_monitor(LoopFun, Opts) -> + spawn_opt(LoopFun, [{monitor, Opts}]). + +spawn_and_monitor(LoopFun, Opts) -> + P = spawn_opt(LoopFun, []), + Mon = erlang:monitor(process, P, Opts), + {P, Mon}. + normal_loop() -> receive {Caller, quit} -> Caller ! {self(), finished} end. + +echo_loop() -> + receive + quit -> + ok; + {Msg, ReplyTo} -> + ReplyTo ! Msg, + echo_loop() + end. + +recv_one() -> + receive + Msg -> Msg + after 500 -> timeout + end. diff --git a/tests/erlang_tests/test_refs_ordering.erl b/tests/erlang_tests/test_refs_ordering.erl index 5071ae5633..6b54f8bf24 100644 --- a/tests/erlang_tests/test_refs_ordering.erl +++ b/tests/erlang_tests/test_refs_ordering.erl @@ -20,17 +20,17 @@ -module(test_refs_ordering). --export([start/0, sort/1, insert/2, check/2, get_ref/2]). +-export([start/0, sort/1, insert/2, check/2, get_ref/3, make_alias_ref/0]). start() -> - A = get_ref(3, []), - B = get_ref(7, []), - C = get_ref(1, []), - D = get_ref(3, []), - E = get_ref(4, []), + A = get_ref(3, [], fun make_ref/0), + B = get_ref(7, [], fun make_alias_ref/0), + C = get_ref(1, [], fun make_ref/0), + D = get_ref(3, [], fun make_alias_ref/0), + E = get_ref(4, [], fun make_ref/0), Sorted = sort([E, C, D, A, B]), - check(Sorted, [A, B, C, D, E]) + - bool_to_n(Sorted < [make_ref()]) * 2 + + check(Sorted, [A, C, E, B, D]) + + bool_to_n(Sorted < [make_alias_ref()]) * 2 + bool_to_n(Sorted > {make_ref()}) * 4. sort(L) -> @@ -57,12 +57,25 @@ check(T, Expected) when T == Expected -> check(T, Expected) when T /= Expected -> 0. -get_ref(0, Acc) -> +get_ref(0, Acc, _Generator) -> Acc; -get_ref(N, _Acc) -> - get_ref(N - 1, make_ref()). +get_ref(N, _Acc, Generator) -> + get_ref(N - 1, Generator(), Generator). bool_to_n(true) -> 1; bool_to_n(false) -> 0. + +make_alias_ref() -> + AliasesAvailable = + case erlang:system_info(machine) of + "ATOM" -> true; + "BEAM" -> erlang:system_info(otp_release) >= "24" + end, + if + AliasesAvailable -> + erlang:alias(); + true -> + {mock_alias_ref, make_ref()} + end.