Skip to content

Conversation

@mat-hek
Copy link
Contributor

@mat-hek mat-hek commented Dec 8, 2025

TODO:

  • support aliases in spawn_opt
  • reply_demonitor
  • C docs
  • erlang docs
  • process reference serialisation

These changes are made under both the "Apache 2.0" and the "GNU Lesser General
Public License 2.1 or later" license terms (dual license).

SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later

@mat-hek mat-hek force-pushed the mf/upstream-aliases branch 8 times, most recently from e2c4216 to f30b1f3 Compare December 9, 2025 09:18
@mat-hek
Copy link
Contributor Author

mat-hek commented Dec 9, 2025

@bettio it seems that CI sometimes fails because it cannot find erlang:alias. I added it to erlang.erl, nifs.gperf and nifs.c, am I missing something?

#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))
#define TERM_BOXED_PID_REF_SIZE (REF_SIZE + 1)
Copy link
Collaborator

Choose a reason for hiding this comment

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

For consistency with the work we are doing lately I suggest renaming all PID_REF and pid_reference to PROCESS_REF and process_reference

term *boxed_value = memory_heap_alloc(heap, TERM_BOXED_PID_REF_SIZE);
boxed_value[0] = TERM_BOXED_PID_REF_HEADER;

#if TERM_BYTES == 8
Copy link
Collaborator

Choose a reason for hiding this comment

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

nitpicking/very low priority comment: just for consistency I would suggest moving first the 4 branch:
#if TERM_BYTES == 4 ... #elif TERM_BYTES == 8.
Same applies to the other functions as well.

return ret;

} else if (term_is_pid_reference(t)) {
int32_t process_id = term_pid_ref_to_process_id(t);
Copy link
Collaborator

Choose a reason for hiding this comment

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

We are using uint32_t for process id.

Copy link
Contributor Author

@mat-hek mat-hek Dec 10, 2025

Choose a reason for hiding this comment

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

Are you sure? It's int32 here: https://github.com/atomvm/AtomVM/blob/main/src/libAtomVM/context.h#L106 and in many other places

ok = test_alias(),
ok = test_monitor_alias(),
ok = test_monitor_alias_explicit_unalias(),
ok = test_monitor_down_alias(),
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should also test unhappy paths, such as:

  1. double/triple unalias
  2. alias a dead process
  3. unalias a dead process

In addition multiple aliases should be tested, this is legit but is should be properly tested.

RAISE_ERROR(BADARG_ATOM);
}

while (!term_is_nil(options)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should use term_is_nonempty_list to avoid issues with non proper lists.

RAISE_ERROR(UNSUPPORTED_ATOM);
} else {
RAISE_ERROR(BADARG_ATOM);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

here we check that last list item is nil.

enum ContextMonitorAliasType
{
CONTEXT_MONITOR_ALIAS_EXPLICIT_UNALIAS,
CONTEXT_MONITOR_ALIAS_DEMONITOR,
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 PascalCase. See AVMCCS-N004 rule in our coding style guide.
(There is still some old code that has to be migrated to updated style).

@mat-hek mat-hek force-pushed the mf/upstream-aliases branch 9 times, most recently from 4b23228 to 07373d0 Compare December 12, 2025 14:47
@mat-hek mat-hek requested a review from bettio December 12, 2025 16:41
@mat-hek
Copy link
Contributor Author

mat-hek commented Dec 13, 2025

@bettio added more tests and missing features. The CI is failing, but it seems unrelated to the changes

Copy link
Collaborator

@bettio bettio left a comment

Choose a reason for hiding this comment

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

Looks mostly good: there is only one change related to IS_NULL_PTR that is required. I suggest also rebasing on top of main, since main has a lot of fixes for flaky tests.

Context *p = globalcontext_get_process_lock(glb, local_process_id);
if (p) {
struct MonitorAlias *alias = context_find_alias(p, ref_ticks);
if (!IS_NULL_PTR(alias)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should not use !IS_NULL_PTR (x) since it is a shorthand for UNLIKELY(x == NULL). So we must use it only when it is unlikely that the pointer is null (that is mostly for error handling purposes).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, but there are many occurrences of !IS_NULL_PTR in the code

@bettio bettio requested a review from pguyot December 15, 2025 15:34
@mat-hek mat-hek force-pushed the mf/upstream-aliases branch 2 times, most recently from 92db4b7 to 1bdef14 Compare December 15, 2025 16:13
@pguyot
Copy link
Collaborator

pguyot commented Dec 15, 2025

esp32 tests are crashing

Copy link
Collaborator

@pguyot pguyot left a comment

Choose a reason for hiding this comment

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

My main concern so far is that monitors are now extended to 2 or 3 words instead of 1 or 2. This may break some code that took monitors and passed it around as references, as the term_to_ref_ticks/term_from_ref_ticks is a common pattern. A similar breakage existed with resources being larger references if one tries to pass a resource where a 1/2 words ref is expected, but it is less likely as resources were not references so far. This may be the crash cause of esp32 tests.

port.erl

call(Port, Message, Timeout) ->
    MonitorRef = monitor(port, Port),
    Port ! {'$call', {self(), MonitorRef}, Message},
    Result =
        receive
            {'DOWN', MonitorRef, port, Port, normal} ->
                {error, noproc};
            {'DOWN', MonitorRef, port, Port, Reason} ->
                {error, Reason};
            out_of_memory ->
                out_of_memory;
            {MonitorRef, Ret} ->
                Ret
        after Timeout ->
            {error, timeout}
        end,
    demonitor(MonitorRef, [flush]),
    Result.

uart_driver.c:

static void uart_driver_do_read(Context *ctx, GenMessage gen_message)
{
    GlobalContext *glb = ctx->global;
    struct UARTData *uart_data = ctx->platform_data;
    term pid = gen_message.pid;
    term ref = gen_message.ref;
    uint64_t ref_ticks = term_to_ref_ticks(ref);

-type raise_stacktrace() ::
[{module(), atom(), arity() | [term()]} | {function(), arity() | [term()]}] | stacktrace().

-type monitor_option() :: {'alias', 'explicit_unalias' | 'demonitor' | 'reply_demonitor'}.
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is not enforced by style guide but usually we don't quote atoms when it's not required.

}
// 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_make_process_reference(target->process_id, monitored_monitor->ref_ticks, &ctx->heap);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is not what OTP does and there probably is a good reason.
Regular monitors are smaller than aliases.

7> Pid = spawn(fun() -> receive ok -> ok end end).
<0.94.0>
8> monitor(process, Pid).
#Ref<0.3008598808.1200095248.57512>
9> monitor(process, Pid, [{alias, explicit_unalias}]).
#Ref<0.0.11651.3008598808.1200160784.57533>
10> make_ref().
#Ref<0.3008598808.1200095248.57553>

boxed_value[0] = TERM_BOXED_PROCESS_REF_HEADER;

#if TERM_BYTES == 4
boxed_value[1] = (ref_ticks >> 4);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This was fixed for regular refs, we probably want ref_ticks >> 32

end,
Term.

is_atomvm_or_otp_version_at_least(OTPVersion) ->
Copy link
Collaborator

Choose a reason for hiding this comment

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

get_otp_version/0 already exists in this module.

Also there is a poor usage (fixed in #2025 )

    OTPVersion = get_otp_version(),
    if
        OTPVersion =:= atomvm orelse OTPVersion >= 26 ->
            B == erlang:term_to_binary(A);
        true ->
            true
    end.

be sure to rather do:

    OTPVersion = get_otp_version(),
    if
        OTPVersion >= 26 ->
            B == erlang:term_to_binary(A);
        true ->
            true
    end.

as atoms sort higher than integers.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also there is a poor usage

I would call it 'a readable way' :P

@mat-hek mat-hek force-pushed the mf/upstream-aliases branch from ce57282 to eac7682 Compare December 19, 2025 09:45
@mat-hek
Copy link
Contributor Author

mat-hek commented Dec 19, 2025

@pguyot the reason for the STM32 tests failing was that the size of a regular reference is actually 3 terms there, which means process reference takes 4 terms, and it probably conflicts with resource reference size, which is always 4 terms. I changed the process reference size to 5, but that's not a perfect solution :P So we need to come up with another way of distinguishing references - do you have anything in mind?

I also added RefData as a more universal representation of references than ref_ticks. WYDT? Would it make sense for it to support resource references too?

@mat-hek mat-hek requested a review from pguyot December 19, 2025 13:16
@mat-hek mat-hek requested a review from bettio December 22, 2025 11:46
@mat-hek mat-hek force-pushed the mf/upstream-aliases branch from eac7682 to 68ccb22 Compare December 22, 2025 11:55
Signed-off-by: Mateusz Front <[email protected]>
Signed-off-by: Mateusz Front <[email protected]>
@mat-hek mat-hek force-pushed the mf/upstream-aliases branch from 68ccb22 to f759361 Compare January 21, 2026 10:41
Signed-off-by: Mateusz Front <[email protected]>
Signed-off-by: Mateusz Front <[email protected]>
@mat-hek mat-hek force-pushed the mf/upstream-aliases branch from f759361 to fe5d3c7 Compare January 21, 2026 10:42
Signed-off-by: Mateusz Front <[email protected]>
@mat-hek mat-hek force-pushed the mf/upstream-aliases branch from fe5d3c7 to c882f24 Compare January 21, 2026 10:43
}
} else if (term_is_local_pid_or_port(recipient_term)) {
int local_process_id;
if (term_is_local_pid_or_port(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.

This condition was checked on line 704.

#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))
Copy link
Collaborator

Choose a reason for hiding this comment

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

A lot of downstream code uses REF_SIZE. If we want to rename it, we shoud keep the previous macro with a deprecation warning.

#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
Copy link
Collaborator

Choose a reason for hiding this comment

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

We can distinguish reference types by size (instead of a common header) and make resources larger than process aliases since aliases should be more commonly created. However, I would welcome static asserts to ensure sizes and headers are indeed different.

Also, we probably want the same prefix TERM_BOX_REFERENCE_*

In this current implementation, we have:

type 32 bits 64 bits
short refs 3 words 2 words
process aliases 4 words 3 words
resources 5 words (2 unused) 4 words (1 unused)

We could also have:

type 32 bits 64 bits
short refs 3 words 2 words
process aliases 4 words 4 words
resources 4 words 4 words

and distinguish process aliases from resources with last word being INVALID_PROCESS_ID for resources.

#define TUPLE_SIZE(elems) ((int) (elems + 1))
#define CONS_SIZE 2
#define REFC_BINARY_CONS_OFFSET 4
#define REFERENCE_RESOURCE_CONS_OFFSET 2
Copy link
Collaborator

Choose a reason for hiding this comment

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

We're missing a constant to access the process id from the process ref.


typedef struct RefData RefData;

struct RefData
Copy link
Collaborator

Choose a reason for hiding this comment

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

RefData is an excellent initiative, howerer it should be a way to serialize/deserialize any ref (local or external). And also we would need a TERM_BOXED_REFERENCE_MAX_SIZE to always be able to deserialize it.

}
case CONTEXT_MONITOR_ALIAS: {
free(monitor);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

We may want a break here line what was there on line 800.

// 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, 13, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

12 was a mistake, we want a sum of constants.

*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_element(option, 0) == ALIAS_ATOM) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

We need to check the tuple size.

default:
RAISE_ERROR(BADARG_ATOM);
}
} else if (term_is_tuple(option) && term_get_tuple_element(option, 0) == TAG_ATOM) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

We need to check the tuple size.

// If you change a reference size, make sure it doesn't
// conflict with other reference sizes on all architectures.
#define SHORT_REF_SIZE ((int) ((sizeof(uint64_t) / sizeof(term)) + 1))
#define TERM_BOXED_PROCESS_REF_SIZE SHORT_REF_SIZE + 1
Copy link
Collaborator

Choose a reason for hiding this comment

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

Macro value should be parenthesized (see AVMCCS-L016)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants