From 2cc981aa8d296be07eb6c7f4b8d9dafa20f2c8dc Mon Sep 17 00:00:00 2001 From: Paul Guyot Date: Sun, 3 Nov 2024 17:00:57 +0100 Subject: [PATCH] Add support for encoded local pids in external terms Also add partial support for external pids. Signed-off-by: Paul Guyot --- src/libAtomVM/ets_hashtable.c | 2 +- src/libAtomVM/externalterm.c | 84 +++++++ src/libAtomVM/memory.c | 18 +- src/libAtomVM/nifs.c | 28 +-- src/libAtomVM/opcodesswitch.h | 4 +- src/libAtomVM/posix_nifs.c | 2 +- src/libAtomVM/term.c | 45 +++- src/libAtomVM/term.h | 244 ++++++++++++++++++--- tests/erlang_tests/test_binary_to_term.erl | 118 ++++++++++ 9 files changed, 488 insertions(+), 57 deletions(-) diff --git a/src/libAtomVM/ets_hashtable.c b/src/libAtomVM/ets_hashtable.c index 77e2d2f9b5..ad9265bd82 100644 --- a/src/libAtomVM/ets_hashtable.c +++ b/src/libAtomVM/ets_hashtable.c @@ -285,7 +285,7 @@ static uint32_t hash_term_incr(term t, int32_t h, GlobalContext *global) return hash_integer(t, h, global); } else if (term_is_float(t)) { return hash_float(t, h, global); - } else if (term_is_pid(t)) { + } else if (term_is_local_pid(t)) { return hash_pid(t, h, global); } else if (term_is_reference(t)) { return hash_reference(t, h, global); diff --git a/src/libAtomVM/externalterm.c b/src/libAtomVM/externalterm.c index 186a5591ed..1bb618f063 100644 --- a/src/libAtomVM/externalterm.c +++ b/src/libAtomVM/externalterm.c @@ -29,13 +29,17 @@ #include #include "bitstring.h" +#include "defaultatoms.h" +#include "term.h" #include "unicode.h" #include "utils.h" #define NEW_FLOAT_EXT 70 +#define NEW_PID_EXT 88 #define SMALL_INTEGER_EXT 97 #define INTEGER_EXT 98 #define ATOM_EXT 100 +#define PID_EXT 103 #define SMALL_TUPLE_EXT 104 #define LARGE_TUPLE_EXT 105 #define NIL_EXT 106 @@ -390,6 +394,33 @@ static int serialize_term(uint8_t *buf, term t, GlobalContext *glb) k += serialize_term(IS_NULL_PTR(buf) ? NULL : buf + k, mfa, glb); } return k; + } else if (term_is_local_pid(t)) { + if (!IS_NULL_PTR(buf)) { + buf[0] = NEW_PID_EXT; + } + size_t k = 1; + term node_name = glb->node_name; + uint32_t creation = node_name == NONODE_AT_NOHOST_ATOM ? 0 : glb->creation; + k += serialize_term(IS_NULL_PTR(buf) ? NULL : buf + k, node_name, glb); + if (!IS_NULL_PTR(buf)) { + WRITE_32_UNALIGNED(buf + k, term_to_local_process_id(t)); + WRITE_32_UNALIGNED(buf + k + 4, 0); // serial + WRITE_32_UNALIGNED(buf + k + 8, creation); + } + return k + 12; + } else if (term_is_external_pid(t)) { + if (!IS_NULL_PTR(buf)) { + buf[0] = NEW_PID_EXT; + } + size_t k = 1; + term node = term_to_external_node(t); + k += serialize_term(IS_NULL_PTR(buf) ? NULL : buf + k, node, glb); + if (!IS_NULL_PTR(buf)) { + WRITE_32_UNALIGNED(buf + k, term_to_external_pid_process_id(t)); + WRITE_32_UNALIGNED(buf + k + 4, term_to_external_pid_serial(t)); + WRITE_32_UNALIGNED(buf + k + 8, term_to_external_node_creation(t)); + } + return k + 12; } else { fprintf(stderr, "Unknown external term type: %" TERM_U_FMT "\n", t); AVM_ABORT(); @@ -659,6 +690,32 @@ static term parse_external_terms(const uint8_t *external_term_buf, size_t *eterm return term_from_atom_index(global_atom_id); } + case NEW_PID_EXT: { + size_t node_size; + term node = parse_external_terms(external_term_buf + 1, &node_size, copy, heap, glb); + if (UNLIKELY(!term_is_atom(node))) { + return term_invalid_term(); + } + uint32_t number = READ_32_UNALIGNED(external_term_buf + node_size + 1); + uint32_t serial = READ_32_UNALIGNED(external_term_buf + node_size + 5); + uint32_t creation = READ_32_UNALIGNED(external_term_buf + node_size + 9); + *eterm_size = node_size + 13; + if (node != NONODE_AT_NOHOST_ATOM) { + term this_node = glb->node_name; + uint32_t this_creation = this_node == NONODE_AT_NOHOST_ATOM ? 0 : glb->creation; + if (node == this_node && creation == this_creation) { + return term_from_local_process_id(number); + } else { + return term_from_external_process_id(node, number, serial, creation, heap); + } + } else { + if (UNLIKELY(serial != 0 || creation != 0)) { + return term_invalid_term(); + } + return term_from_local_process_id(number); + } + } + default: return term_invalid_term(); } @@ -948,6 +1005,33 @@ static int calculate_heap_usage(const uint8_t *external_term_buf, size_t remaini return 0; } + case NEW_PID_EXT: { + if (UNLIKELY(remaining < 1)) { + return INVALID_TERM_SIZE; + } + remaining -= 1; + int buf_pos = 1; + size_t heap_size = EXTERNAL_PID_SIZE; + size_t node_size = 0; + int u = calculate_heap_usage(external_term_buf + buf_pos, remaining, &node_size, copy); + if (UNLIKELY(u == INVALID_TERM_SIZE)) { + return INVALID_TERM_SIZE; + } + if (external_term_buf[1] == SMALL_ATOM_UTF8_EXT) { + // Check if it's non-distributed node, in which case it's always a local pid + if (external_term_buf[2] == strlen("nonode@nohost") && memcmp(external_term_buf + 3, "nonode@nohost", strlen("nonode@nohost")) == 0) { + heap_size = 0; + } + // If this is our node, but we're distributed, we'll allocate more memory and may not use it. + // This way we're sure to not go out of bounds if distribution changes between now and when we deserialize + } else if (UNLIKELY(external_term_buf[1] != ATOM_EXT)) { + return INVALID_TERM_SIZE; + } + buf_pos += node_size; + *eterm_size = buf_pos + 12; + return heap_size + u; + } + default: return INVALID_TERM_SIZE; } diff --git a/src/libAtomVM/memory.c b/src/libAtomVM/memory.c index 6a593a3c52..ae765d8884 100644 --- a/src/libAtomVM/memory.c +++ b/src/libAtomVM/memory.c @@ -476,7 +476,11 @@ unsigned long memory_estimate_usage(term t) } else if (term_is_nil(t)) { t = temp_stack_pop(&temp_stack); - } else if (term_is_pid(t)) { + } else if (term_is_local_pid(t)) { + t = temp_stack_pop(&temp_stack); + + } else if (term_is_external_pid(t)) { + acc += EXTERNAL_PID_SIZE; t = temp_stack_pop(&temp_stack); } else if (term_is_nonempty_list(t)) { @@ -587,7 +591,7 @@ static void memory_scan_and_copy(HeapFragment *old_fragment, term *mem_start, co TRACE("Found NIL (%" TERM_X_FMT ")\n", t); ptr++; - } else if (term_is_pid(t)) { + } else if (term_is_local_pid(t)) { TRACE("Found PID (%" TERM_X_FMT ")\n", t); ptr++; @@ -620,6 +624,10 @@ static void memory_scan_and_copy(HeapFragment *old_fragment, term *mem_start, co TRACE("- Found ref.\n"); break; + case TERM_BOXED_EXTERNAL_PID: + TRACE("- Found external pid.\n"); + break; + case TERM_BOXED_FUN: { int fun_size = term_get_size_from_boxed_header(t); TRACE("- Found fun, size: %i.\n", fun_size); @@ -740,6 +748,10 @@ static void memory_scan_and_rewrite(size_t count, term *terms, const term *old_s ptr += term_get_size_from_boxed_header(t); break; + case TERM_BOXED_EXTERNAL_PID: + ptr += EXTERNAL_PID_SIZE - 1; + break; + case TERM_BOXED_FUN: // Skip header and module and process next terms ptr++; @@ -810,7 +822,7 @@ HOT_FUNC static term memory_shallow_copy_term(HeapFragment *old_fragment, term t } else if (term_is_nil(t)) { return t; - } else if (term_is_pid(t)) { + } else if (term_is_local_pid(t)) { return t; } else if (term_is_cp(t)) { diff --git a/src/libAtomVM/nifs.c b/src/libAtomVM/nifs.c index 804814605b..9c70af0f5b 100644 --- a/src/libAtomVM/nifs.c +++ b/src/libAtomVM/nifs.c @@ -996,7 +996,7 @@ static term nif_erlang_register_2(Context *ctx, int argc, term argv[]) term reg_name_term = argv[0]; VALIDATE_VALUE(reg_name_term, term_is_atom); term pid_or_port_term = argv[1]; - VALIDATE_VALUE(pid_or_port_term, term_is_pid); + VALIDATE_VALUE(pid_or_port_term, term_is_local_pid); int atom_index = term_to_atom_index(reg_name_term); int32_t pid = term_to_local_process_id(pid_or_port_term); @@ -1400,7 +1400,7 @@ static term nif_erlang_send_2(Context *ctx, int argc, term argv[]) term target = argv[0]; GlobalContext *glb = ctx->global; - if (term_is_pid(target)) { + if (term_is_local_pid(target)) { int32_t local_process_id = term_to_local_process_id(target); globalcontext_send_message(glb, local_process_id, argv[1]); @@ -2741,7 +2741,7 @@ static term nif_erlang_process_flag(Context *ctx, int argc, term argv[]) flag = argv[1]; value = argv[2]; - VALIDATE_VALUE(pid, term_is_pid); + VALIDATE_VALUE(pid, term_is_local_pid); int local_process_id = term_to_local_process_id(pid); Context *target = globalcontext_get_process_lock(ctx->global, local_process_id); if (IS_NULL_PTR(target)) { @@ -3191,7 +3191,7 @@ static term nif_binary_split(Context *ctx, int argc, term argv[]) if (num_segments == 1) { // not found - if (UNLIKELY(memory_ensure_free_with_roots(ctx, 2, 1, argv, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { + if (UNLIKELY(memory_ensure_free_with_roots(ctx, LIST_SIZE(1, 0), 1, argv, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); } @@ -3443,11 +3443,11 @@ static term nif_erlang_pid_to_list(Context *ctx, int argc, term argv[]) term t = argv[0]; VALIDATE_VALUE(t, term_is_pid); + size_t max_len = term_is_external(t) ? EXTERNAL_PID_AS_CSTRING_LEN : LOCAL_PID_AS_CSTRING_LEN; - char buf[PID_AS_CSTRING_LEN]; - int str_len = term_snprint(buf, PID_AS_CSTRING_LEN, t, ctx->global); + char buf[max_len]; + int str_len = term_snprint(buf, max_len, t, ctx->global); if (UNLIKELY(str_len < 0)) { - // TODO: change to internal error or something like that RAISE_ERROR(OUT_OF_MEMORY_ATOM); } @@ -3559,7 +3559,7 @@ static term nif_erlang_garbage_collect(Context *ctx, int argc, term argv[]) } else { // argc == 1 term t = argv[0]; - VALIDATE_VALUE(t, term_is_pid); + VALIDATE_VALUE(t, term_is_local_pid); int local_id = term_to_local_process_id(t); Context *target = globalcontext_get_process_lock(ctx->global, local_id); @@ -3602,7 +3602,7 @@ static term nif_erlang_exit(Context *ctx, int argc, term argv[]) RAISE(LOWERCASE_EXIT_ATOM, reason); } else { term target_process = argv[0]; - VALIDATE_VALUE(target_process, term_is_pid); + VALIDATE_VALUE(target_process, term_is_local_pid); term reason = argv[1]; GlobalContext *glb = ctx->global; Context *target = globalcontext_get_process_lock(glb, term_to_local_process_id(target_process)); @@ -3715,7 +3715,7 @@ static term nif_erlang_monitor(Context *ctx, int argc, term argv[]) RAISE_ERROR(BADARG_ATOM); } - VALIDATE_VALUE(target_pid, term_is_pid); + VALIDATE_VALUE(target_pid, term_is_local_pid); int local_process_id = term_to_local_process_id(target_pid); Context *target = globalcontext_get_process_lock(ctx->global, local_process_id); @@ -3783,7 +3783,7 @@ static term nif_erlang_link(Context *ctx, int argc, term argv[]) term target_pid = argv[0]; - VALIDATE_VALUE(target_pid, term_is_pid); + VALIDATE_VALUE(target_pid, term_is_local_pid); int local_process_id = term_to_local_process_id(target_pid); Context *target = globalcontext_get_process_lock(ctx->global, local_process_id); @@ -3814,7 +3814,7 @@ static term nif_erlang_unlink(Context *ctx, int argc, term argv[]) term target_pid = argv[0]; - VALIDATE_VALUE(target_pid, term_is_pid); + VALIDATE_VALUE(target_pid, term_is_local_pid); int local_process_id = term_to_local_process_id(target_pid); Context *target = globalcontext_get_process_lock(ctx->global, local_process_id); @@ -3845,8 +3845,8 @@ static term nif_erlang_group_leader(Context *ctx, int argc, term argv[]) } else { term leader = argv[0]; term pid = argv[1]; - VALIDATE_VALUE(pid, term_is_pid); - VALIDATE_VALUE(leader, term_is_pid); + VALIDATE_VALUE(pid, term_is_local_pid); + VALIDATE_VALUE(leader, term_is_local_pid); int local_process_id = term_to_local_process_id(pid); Context *target = globalcontext_get_process_lock(ctx->global, local_process_id); diff --git a/src/libAtomVM/opcodesswitch.h b/src/libAtomVM/opcodesswitch.h index f660968bc1..240ad56263 100644 --- a/src/libAtomVM/opcodesswitch.h +++ b/src/libAtomVM/opcodesswitch.h @@ -2384,7 +2384,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) #ifdef IMPL_EXECUTE_LOOP term recipient_term = x_regs[0]; int local_process_id; - if (term_is_pid(recipient_term)) { + if (term_is_local_pid(recipient_term)) { local_process_id = term_to_local_process_id(recipient_term); } else if (term_is_atom(recipient_term)) { local_process_id = globalcontext_get_registered_process(ctx->global, term_to_atom_index(recipient_term)); @@ -2984,7 +2984,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) #ifdef IMPL_EXECUTE_LOOP TRACE("is_port/2, label=%i, arg1=%lx\n", label, arg1); - if (term_is_pid(arg1)) { + if (term_is_local_pid(arg1)) { int local_process_id = term_to_local_process_id(arg1); Context *target = globalcontext_get_process_lock(ctx->global, local_process_id); bool is_port_driver = false; diff --git a/src/libAtomVM/posix_nifs.c b/src/libAtomVM/posix_nifs.c index ceeb9f8547..c8885d506e 100644 --- a/src/libAtomVM/posix_nifs.c +++ b/src/libAtomVM/posix_nifs.c @@ -427,7 +427,7 @@ static term nif_atomvm_posix_write(Context *ctx, int argc, term argv[]) static term nif_atomvm_posix_select(Context *ctx, term argv[], enum ErlNifSelectFlags mode) { term process_pid_term = argv[1]; - VALIDATE_VALUE(process_pid_term, term_is_pid); + VALIDATE_VALUE(process_pid_term, term_is_local_pid); int32_t process_pid = term_to_local_process_id(process_pid_term); term select_ref_term = argv[2]; if (select_ref_term != UNDEFINED_ATOM) { diff --git a/src/libAtomVM/term.c b/src/libAtomVM/term.c index 5a958884ba..f702a1834f 100644 --- a/src/libAtomVM/term.c +++ b/src/libAtomVM/term.c @@ -189,10 +189,17 @@ int term_funprint(PrinterFun *fun, term t, const GlobalContext *global) ret += printed; return ret; } - } else if (term_is_pid(t)) { + } else if (term_is_local_pid(t)) { int32_t process_id = term_to_local_process_id(t); return fun->print(fun, "<0.%" PRIu32 ".0>", process_id); + } else if (term_is_external_pid(t)) { + uint32_t node_atom_index = term_to_atom_index(term_to_external_node(t)); + uint32_t number = term_to_external_pid_process_id(t); + uint32_t serial = term_to_external_pid_serial(t); + // creation is not printed + return fun->print(fun, "<%" PRIu32 ".%" PRIu32 ".%" PRIu32 ">", node_atom_index, number, serial); + } else if (term_is_function(t)) { const term *boxed_value = term_to_const_term_ptr(t); @@ -664,11 +671,45 @@ TermCompareResult term_compare(term t, term other, TermCompareOpts opts, GlobalC result = (atom_cmp_result > 0) ? TermGreaterThan : TermLessThan; break; - } else if (term_is_pid(t) && term_is_pid(other)) { + } else if (term_is_external_pid(t) && term_is_external_pid(other)) { + term node = term_to_external_node(t); + term other_node = term_to_external_node(other); + if (node == other_node) { + uint32_t creation = term_to_external_node_creation(t); + uint32_t other_creation = term_to_external_node_creation(other); + if (creation == other_creation) { + uint32_t serial = term_to_external_pid_serial(t); + uint32_t other_serial = term_to_external_pid_serial(other); + if (serial == other_serial) { + uint32_t process_id = term_to_external_pid_process_id(t); + uint32_t other_process_id = term_to_external_pid_process_id(other); + if (process_id == other_process_id) { + CMP_POP_AND_CONTINUE(); + } else { + result = (process_id > other_process_id) ? TermGreaterThan : TermLessThan; + break; + } + } else { + result = (serial > other_serial) ? TermGreaterThan : TermLessThan; + break; + } + } else { + result = (creation > other_creation) ? TermGreaterThan : TermLessThan; + break; + } + } else { + result = (node > other_node) ? TermGreaterThan : TermLessThan; + break; + } + } else if (term_is_local_pid(t) && term_is_local_pid(other)) { //TODO: handle ports result = (t > other) ? TermGreaterThan : TermLessThan; break; + } else if (term_is_pid(t) && term_is_pid(other)) { + result = term_is_local_pid(other) ? TermGreaterThan : TermLessThan; + break; + } else { result = (term_type_to_index(t) > term_type_to_index(other)) ? TermGreaterThan : TermLessThan; break; diff --git a/src/libAtomVM/term.h b/src/libAtomVM/term.h index 67be1cceb3..d6cf5be709 100644 --- a/src/libAtomVM/term.h +++ b/src/libAtomVM/term.h @@ -57,8 +57,11 @@ extern "C" { #define TERM_BOXED_FLOAT 0x18 #define TERM_BOXED_REFC_BINARY 0x20 #define TERM_BOXED_HEAP_BINARY 0x24 -#define TERM_BOXED_MAP 0x3C #define TERM_BOXED_SUB_BINARY 0x28 +#define TERM_BOXED_MAP 0x2C +#define TERM_BOXED_EXTERNAL_PID 0x30 +#define TERM_BOXED_EXTERNAL_PORT 0x34 +#define TERM_BOXED_EXTERNAL_REF 0x38 #define TERM_UNUSED 0x2B #define TERM_RESERVED_MARKER(x) ((x << 6) | TERM_UNUSED) @@ -84,6 +87,13 @@ extern "C" { #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)) +#if TERM_BYTES == 8 + #define EXTERNAL_PID_SIZE 3 +#elif TERM_BYTES == 4 + #define EXTERNAL_PID_SIZE 4 +#else + #error +#endif #define TUPLE_SIZE(elems) ((int) (elems + 1)) #define CONS_SIZE 2 #define REFC_BINARY_CONS_OFFSET 4 @@ -115,9 +125,15 @@ extern "C" { // "#Ref<0.0.0." ">\0" (13 chars) #define REF_AS_CSTRING_LEN 33 -// 2^32 = 4294967296 (10 chars) +// 2^28-1 = 268435455 (9 chars) // "<0." ".0>\0" (7 chars) -#define PID_AS_CSTRING_LEN 17 +#define LOCAL_PID_AS_CSTRING_LEN 16 + +// 2^26-1 = 67108863 (8 chars) (node, atom index) +// 2^28-1 = 268435455 (9 chars) (pid number) +// 2^32-1 = 4294967295 (10 chars) (pid serial) +// "<" "." "." ">\0" (5 chars) +#define EXTERNAL_PID_AS_CSTRING_LEN 32 #ifndef TYPEDEF_GLOBALCONTEXT #define TYPEDEF_GLOBALCONTEXT @@ -306,40 +322,22 @@ static inline bool term_is_boxed(term t) return ((t & 0x3) == 0x2); } -/** - * @brief Checks if a term is a movable boxed value - * - * @details Returns \c true if a term is a boxed value that can be safely copied with memcpy. - * @param t the term that will checked. - * @return \c true if check succeeds, \c false otherwise. - */ -static inline bool term_is_movable_boxed(term t) -{ - /* boxed: 10 */ - if ((t & 0x3) == 0x2) { - const term *boxed_value = term_to_const_term_ptr(t); - switch (boxed_value[0] & TERM_BOXED_TAG_MASK) { - case 0x10: - return true; - - default: - return false; - } - } else { - return false; - } -} - /** * @brief Returns size of a boxed term from its header * - * @details Returns the size that is stored in boxed term header most significant bits. + * @details Returns the size that is stored in boxed term header most significant bits for variable size boxed terms. * @param header the boxed term header. * @return the size of the boxed term that follows the header. 0 is returned if the boxed term is just the header. */ static inline size_t term_get_size_from_boxed_header(term header) { - return header >> 6; + int masked_value = header & TERM_BOXED_TAG_MASK; + switch (masked_value) { + case TERM_BOXED_EXTERNAL_PID: + return EXTERNAL_PID_SIZE - 1; + default: + return header >> 6; + } } /** @@ -476,18 +474,87 @@ static inline bool term_is_catch_label(term t) } /** - * @brief Checks if a term is a pid + * @brief Checks if a term is a local pid * * @details Returns \c true if a term is a process id, otherwise \c false. * @param t the term that will be checked. * @return \c true if check succeeds, \c false otherwise. */ -static inline bool term_is_pid(term t) +static inline bool term_is_local_pid(term t) { /* integer: 00 11 */ return ((t & 0xF) == 0x3); } +/** + * @brief Checks if a term is an external pid + * + * @details Returns \c true if a term is an external process id, otherwise \c false. + * @param t the term that will be checked. + * @return \c true if check succeeds, \c false otherwise. + */ +static inline bool term_is_external_pid(term t) +{ + if (term_is_boxed(t)) { + const term *boxed_value = term_to_const_term_ptr(t); + if ((boxed_value[0] & 0x3F) == TERM_BOXED_EXTERNAL_PID) { + return true; + } + } + + return false; +} + +/** + * @brief Checks if a term is an external thing + * + * @details Returns \c true if a term is an thing, otherwise \c false. + * @param t the term that will be checked. + * @return \c true if check succeeds, \c false otherwise. + */ +static inline bool term_is_external(term t) +{ + if (term_is_boxed(t)) { + const term *boxed_value = term_to_const_term_ptr(t); + if ((boxed_value[0] & 0x33) == TERM_BOXED_EXTERNAL_PID) { + return true; + } + } + + return false; +} + +/** + * @brief Checks if a term is a pid + * + * @details Returns \c true if a term is a process id, otherwise \c false. + * @param t the term that will be checked. + * @return \c true if check succeeds, \c false otherwise. + */ +static inline bool term_is_pid(term t) +{ + return term_is_local_pid(t) || term_is_external_pid(t); +} + +/** + * @brief Checks if a term is an external port + * + * @details Returns \c true if a term is an external port, otherwise \c false. + * @param t the term that will be checked. + * @return \c true if check succeeds, \c false otherwise. + */ +static inline bool term_is_external_port(term t) +{ + if (term_is_boxed(t)) { + const term *boxed_value = term_to_const_term_ptr(t); + if ((boxed_value[0] & 0x3F) == TERM_BOXED_EXTERNAL_PORT) { + return true; + } + } + + return false; +} + /** * @brief Checks if a term is a tuple * @@ -499,7 +566,7 @@ static inline bool term_is_tuple(term t) { if (term_is_boxed(t)) { const term *boxed_value = term_to_const_term_ptr(t); - if ((boxed_value[0] & 0x3F) == 0) { + if ((boxed_value[0] & 0x3F) == TERM_BOXED_TUPLE) { return true; } } @@ -518,7 +585,8 @@ static inline bool term_is_reference(term t) { if (term_is_boxed(t)) { const term *boxed_value = term_to_const_term_ptr(t); - if ((boxed_value[0] & 0x3F) == TERM_BOXED_REF) { + const uint32_t header = boxed_value[0] & 0x3F; + if (header == TERM_BOXED_REF || header == TERM_BOXED_EXTERNAL_REF) { return true; } } @@ -687,7 +755,7 @@ static inline int term_to_catch_label_and_module(term t, int *module_index) */ static inline int32_t term_to_local_process_id(term t) { - TERM_DEBUG_ASSERT(term_is_pid(t)); + TERM_DEBUG_ASSERT(term_is_local_pid(t)); return t >> 4; } @@ -1211,6 +1279,114 @@ static inline uint64_t term_to_ref_ticks(term rt) #endif } +/** + * @brief Get a pid term from node, process_id, serial and creation + * + * @param node name of the node (atom) + * @param process_id process id on that node + * @param serial serial of process id on that node + * @param creation creation of that node + * @param heap the heap to allocate memory in + * @return an external heap term created using given parameters. + */ +static inline term term_from_external_process_id(term node, uint32_t process_id, uint32_t serial, uint32_t creation, Heap *heap) +{ + term *boxed_value = memory_heap_alloc(heap, EXTERNAL_PID_SIZE); + int atom_index = term_to_atom_index(node); + boxed_value[0] = (atom_index << 6) | TERM_BOXED_EXTERNAL_PID; + + #if TERM_BYTES == 8 + boxed_value[1] = (term) (((uint64_t) process_id) << 32 | creation); + boxed_value[2] = (term) serial; + + #elif TERM_BYTES == 4 + boxed_value[1] = (term) creation; + boxed_value[2] = (term) process_id; + boxed_value[3] = (term) serial; + + #else + #error "terms must be either 32 or 64 bit wide" + #endif + + return ((term) boxed_value) | TERM_BOXED_VALUE_TAG; +} + +/** + * @brief Get the name of a node for a given external thing + * + * @param term external term + * @return the name of the node + */ +static inline term term_to_external_node(term t) +{ + TERM_DEBUG_ASSERT(term_is_external(t)); + + const term *boxed_value = term_to_const_term_ptr(t); + + return TERM_FROM_ATOM_INDEX(boxed_value[0] >> 6); +} + +/** + * @brief Get the creation for a given external thing + * + * @param term external term + * @return the serial of the external pid + */ +static inline uint32_t term_to_external_node_creation(term t) +{ + TERM_DEBUG_ASSERT(term_is_external_pid(t)); + + const term *boxed_value = term_to_const_term_ptr(t); + + return (uint32_t) boxed_value[1]; +} + +/** + * @brief Get the process id of an external pid + * + * @param term external pid + * @return the process id of the external pid + */ +static inline uint32_t term_to_external_pid_process_id(term t) +{ + TERM_DEBUG_ASSERT(term_is_external_pid(t)); + + const term *boxed_value = term_to_const_term_ptr(t); + + #if TERM_BYTES == 8 + return (uint32_t) (boxed_value[1] >> 32); + + #elif TERM_BYTES == 4 + return (uint32_t) boxed_value[2]; + + #else + #error "terms must be either 32 or 64 bit wide" + #endif +} + +/** + * @brief Get the serial of an external pid + * + * @param term external term + * @return the serial of the external pid + */ +static inline uint32_t term_to_external_pid_serial(term t) +{ + TERM_DEBUG_ASSERT(term_is_external_pid(t)); + + const term *boxed_value = term_to_const_term_ptr(t); + + #if TERM_BYTES == 8 + return (uint32_t) boxed_value[2]; + + #elif TERM_BYTES == 4 + return (uint32_t) boxed_value[3]; + + #else + #error "terms must be either 32 or 64 bit wide" + #endif +} + /** * @brief Allocates a tuple on a context heap * diff --git a/tests/erlang_tests/test_binary_to_term.erl b/tests/erlang_tests/test_binary_to_term.erl index c09dba1ced..5f2a65073a 100644 --- a/tests/erlang_tests/test_binary_to_term.erl +++ b/tests/erlang_tests/test_binary_to_term.erl @@ -28,6 +28,7 @@ get_atom/1, get_binary/1, test_atom_decoding_checks/0, + test_encode_pid/0, id/1 ]). @@ -172,6 +173,7 @@ start() -> ok = test_mutate_encodings(), ok = test_atom_decoding(), ok = test_atom_decoding_checks(), + ok = test_encode_pid(), 0. test_reverse(T, Interop) -> @@ -399,6 +401,122 @@ test_atom_decoding_checks() -> ok = expect_badarg(make_binterm_fun(invalid_utf8_seq_3)), ok. +test_encode_pid() -> + Bin = term_to_binary(self()), + Pid = binary_to_term(Bin), + Pid ! hello, + Pid1 = binary_to_term( + <<131, 88, 119, 13, "nonode@nohost", 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0>> + ), + true = is_pid(Pid1), + true = is_process_alive(Pid1), + ok = + receive + hello -> ok + after 500 -> error + end, + ExpectedSize = + case erlang:system_info(machine) of + "ATOM" -> + 29; + "BEAM" -> + OTPRelease = erlang:system_info(otp_release), + if + OTPRelease < "23" -> 27; + OTPRelease < "26" -> 30; + % small utf8 atom + true -> 29 + end + end, + ExpectedSize = byte_size(Bin), + FalsePid1 = binary_to_term( + <<131, 88, 119, 5, "false", 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0>> + ), + true = is_pid(FalsePid1), + "<0.1.0>" = pid_to_list(FalsePid1), + FalsePid1Cr = binary_to_term( + <<131, 88, 119, 5, "false", 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1>> + ), + "<0.1.0>" = pid_to_list(FalsePid1Cr), + false = FalsePid1 =:= FalsePid1Cr, + FalsePid2 = binary_to_term( + <<131, 88, 119, 5, "false", 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0>> + ), + "<0.2.0>" = pid_to_list(FalsePid2), + false = FalsePid1 =:= FalsePid2, + TruePid1 = binary_to_term( + <<131, 88, 119, 4, "true", 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0>> + ), + true = is_pid(TruePid1), + "<1.1.0>" = pid_to_list(TruePid1), + TruePid1Again = binary_to_term(term_to_binary(TruePid1)), + true = TruePid1Again =:= TruePid1, + + case has_setnode_creation() of + true -> + % Test distributed pid + Ref42 = do_setnode(test@test_node, 42), + DistributedPid1 = binary_to_term( + <<131, 88, 119, 14, "test@test_node", 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 42>> + ), + true = is_pid(DistributedPid1), + true = is_process_alive(DistributedPid1), + + DistributedBin42 = term_to_binary(self()), + true = DistributedBin42 =/= Bin, + DistributedPid42 = binary_to_term(DistributedBin42), + true = DistributedPid42 =:= Pid, + ExpectedSize = byte_size(DistributedBin42) - 1, + + ok = do_unsetnode(Ref42), + Bin = term_to_binary(self()), + + Ref43 = do_setnode(test@test_node, 43), + DistributedBin43 = term_to_binary(self()), + true = DistributedBin43 =/= DistributedBin42, + DistributedPid43 = binary_to_term(DistributedBin43), + true = DistributedPid43 =:= Pid, + + ok = do_unsetnode(Ref43), + Bin = term_to_binary(self()), + ok; + false -> ok + end, + ok. + +has_setnode_creation() -> + case erlang:system_info(machine) of + "ATOM" -> + true; + "BEAM" -> + OTPRelease = erlang:system_info(otp_release), + OTPRelease >= "23" + end. + +do_setnode(Node, Creation) -> + {NetKernelPid, MonitorRef} = spawn_opt( + fun() -> + receive + quit -> ok + end + end, + [monitor] + ), + register(net_kernel, NetKernelPid), + true = erlang:setnode(Node, Creation), + Node = node(), + {NetKernelPid, MonitorRef}. + +do_unsetnode({NetKernelPid, MonitorRef}) -> + NetKernelPid ! quit, + ok = + receive + {'DOWN', MonitorRef, process, NetKernelPid, normal} -> ok + after 1000 -> timeout + end, + nonode@nohost = node(), + ok. + make_binterm_fun(Id) -> fun() -> Bin = ?MODULE:get_binary(Id),