Skip to content
Open
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
54 changes: 52 additions & 2 deletions libs/estdlib/src/erlang.erl
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
make_ref/0,
send/2,
monitor/2,
monitor/3,
demonitor/1,
demonitor/2,
exit/1,
Expand Down Expand Up @@ -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([
Expand Down Expand Up @@ -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()
Expand All @@ -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.
Expand Down Expand Up @@ -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();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

monitor can be also used with atoms or with tuples for processes on remote nodes.
I suggest introducing monitor_process_identifier() as Erlang does.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is copy-pasted from the typespec above, I think it should be fixed in a separate PR

(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'
Expand Down Expand Up @@ -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).
104 changes: 85 additions & 19 deletions src/libAtomVM/context.c

Large diffs are not rendered by default.

45 changes: 39 additions & 6 deletions src/libAtomVM/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -200,18 +208,25 @@ struct LinkLocalMonitor
struct MonitorLocalMonitor
{
struct Monitor monitor;
uint64_t ref_ticks;
RefData ref_data;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use this as an example.
We are storing the entire the entire RefData structure even if we already know in advance: which monitor type is and that we will use only ref_ticks.
This is not efficient in terms of memory usage as soon as we starting having around several monitors.

Copy link
Contributor Author

@mat-hek mat-hek Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this particular case, RefData is needed, because when making a term out of it, we need to know whether to create a process ref or a regular ref. If we need a process ref, then we also need a process id, which we now have in RefData, which is convenient, but we could probably keep a flag instead and get process_id from the context. Still, we'd have to keep a flag, so the savings would be marginal, if any.

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
{
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
*
Expand Down
6 changes: 6 additions & 0 deletions src/libAtomVM/defaultatoms.def
Original file line number Diff line number Diff line change
Expand Up @@ -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")
2 changes: 1 addition & 1 deletion src/libAtomVM/ets.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
19 changes: 18 additions & 1 deletion src/libAtomVM/external_term.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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);
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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;
}
Expand Down
2 changes: 0 additions & 2 deletions src/libAtomVM/globalcontext.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@
extern "C" {
#endif

#define INVALID_PROCESS_ID 0

struct Context;

#ifndef TYPEDEF_CONTEXT
Expand Down
36 changes: 28 additions & 8 deletions src/libAtomVM/jit.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment about duplicated code in opcodesswitch.h.

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;
}

Expand Down
Loading