From 069cfb9a003ce425925641b55f16b10dce52a059 Mon Sep 17 00:00:00 2001 From: Paul Guyot Date: Thu, 7 Nov 2024 22:17:06 +0100 Subject: [PATCH] Add support for encoded refs in external terms Also add partial support for external refs. Also fix list representation of local refs as a ref with two words, matching external term encoding. Signed-off-by: Paul Guyot --- src/libAtomVM/context.c | 2 +- src/libAtomVM/ets_hashtable.c | 24 ++- src/libAtomVM/externalterm.c | 101 +++++++++++++ src/libAtomVM/memory.c | 8 + src/libAtomVM/nifs.c | 10 +- src/libAtomVM/otp_socket.c | 4 +- src/libAtomVM/posix_nifs.c | 2 +- src/libAtomVM/resources.c | 2 +- src/libAtomVM/term.c | 70 +++++++-- src/libAtomVM/term.h | 161 ++++++++++++++++++++- tests/erlang_tests/ref_to_list_test.erl | 41 +++--- tests/erlang_tests/test_binary_to_term.erl | 74 ++++++++++ tests/test.c | 2 +- 13 files changed, 455 insertions(+), 46 deletions(-) diff --git a/src/libAtomVM/context.c b/src/libAtomVM/context.c index 5555563782..561e2f8ec1 100644 --- a/src/libAtomVM/context.c +++ b/src/libAtomVM/context.c @@ -231,7 +231,7 @@ void context_process_flush_monitor_signal(Context *ctx, uint64_t ref_ticks, bool if (term_is_tuple(msg) && term_get_tuple_arity(msg) == 5 && term_get_tuple_element(msg, 0) == DOWN_ATOM - && term_is_reference(term_get_tuple_element(msg, 1)) + && term_is_local_reference(term_get_tuple_element(msg, 1)) && term_to_ref_ticks(term_get_tuple_element(msg, 1)) == ref_ticks) { mailbox_remove_message(&ctx->mailbox, &ctx->heap); // If option info is combined with option flush, false is returned if a flush was needed, otherwise true. diff --git a/src/libAtomVM/ets_hashtable.c b/src/libAtomVM/ets_hashtable.c index 5345921db5..de6176d8a0 100644 --- a/src/libAtomVM/ets_hashtable.c +++ b/src/libAtomVM/ets_hashtable.c @@ -266,7 +266,7 @@ static uint32_t hash_external_pid(term t, int32_t h, GlobalContext *global) return h * LARGE_PRIME_PID; } -static uint32_t hash_reference(term t, int32_t h, GlobalContext *global) +static uint32_t hash_local_reference(term t, int32_t h, GlobalContext *global) { UNUSED(global); uint64_t n = term_to_ref_ticks(t); @@ -277,6 +277,22 @@ static uint32_t hash_reference(term t, int32_t h, GlobalContext *global) return h * LARGE_PRIME_REF; } +static uint32_t hash_external_reference(term t, int32_t h, GlobalContext *global) +{ + UNUSED(global); + uint32_t l = term_get_external_reference_len(t); + uint32_t words[l]; + term_get_external_reference_words(t, words); + for (uint32_t i = 0; i < l; i++) { + uint32_t n = words[i]; + while (n) { + h = h * LARGE_PRIME_REF + (n & 0xFF); + n >>= 8; + } + } + return h * LARGE_PRIME_REF; +} + static uint32_t hash_binary(term t, int32_t h, GlobalContext *global) { UNUSED(global); @@ -300,8 +316,10 @@ static uint32_t hash_term_incr(term t, int32_t h, GlobalContext *global) return hash_local_pid(t, h, global); } else if (term_is_external_pid(t)) { return hash_external_pid(t, h, global); - } else if (term_is_reference(t)) { - return hash_reference(t, h, global); + } else if (term_is_local_reference(t)) { + return hash_local_reference(t, h, global); + } else if (term_is_external_reference(t)) { + return hash_external_reference(t, h, global); } else if (term_is_binary(t)) { return hash_binary(t, h, global); } else if (term_is_tuple(t)) { diff --git a/src/libAtomVM/externalterm.c b/src/libAtomVM/externalterm.c index 27387e628e..ffb443971c 100644 --- a/src/libAtomVM/externalterm.c +++ b/src/libAtomVM/externalterm.c @@ -36,6 +36,7 @@ #define NEW_FLOAT_EXT 70 #define NEW_PID_EXT 88 +#define NEWER_REFERENCE_EXT 90 #define SMALL_INTEGER_EXT 97 #define INTEGER_EXT 98 #define ATOM_EXT 100 @@ -421,6 +422,48 @@ static int serialize_term(uint8_t *buf, term t, GlobalContext *glb) WRITE_32_UNALIGNED(buf + k + 8, term_get_external_node_creation(t)); } return k + 12; + } else if (term_is_local_reference(t)) { + if (!IS_NULL_PTR(buf)) { + buf[0] = NEWER_REFERENCE_EXT; + } + size_t k = 1; + if (!IS_NULL_PTR(buf)) { + WRITE_16_UNALIGNED(buf + k, 2); // len + } + k += 2; + 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)) { + uint64_t ticks = term_to_ref_ticks(t); + WRITE_32_UNALIGNED(buf + k, creation); + WRITE_64_UNALIGNED(buf + k + 4, ticks); + } + return k + 12; + } else if (term_is_external_reference(t)) { + if (!IS_NULL_PTR(buf)) { + buf[0] = NEWER_REFERENCE_EXT; + } + size_t k = 1; + if (!IS_NULL_PTR(buf)) { + WRITE_16_UNALIGNED(buf + k, 2); // len + } + k += 2; + term node = term_get_external_node(t); + k += serialize_term(IS_NULL_PTR(buf) ? NULL : buf + k, node, glb); + uint32_t len = term_get_external_reference_len(t); + if (!IS_NULL_PTR(buf)) { + WRITE_32_UNALIGNED(buf + k, term_get_external_node_creation(t)); + } + k += 4; + if (!IS_NULL_PTR(buf)) { + uint32_t data[len]; + term_get_external_reference_words(t, data); + for (uint32_t i = 0; i < len; i++) { + WRITE_64_UNALIGNED(buf + k + (i * 4), data[i]); + } + } + return k + (4 * len); } else { fprintf(stderr, "Unknown external term type: %" TERM_U_FMT "\n", t); AVM_ABORT(); @@ -716,6 +759,37 @@ static term parse_external_terms(const uint8_t *external_term_buf, size_t *eterm } } + case NEWER_REFERENCE_EXT: { + uint16_t len = READ_16_UNALIGNED(external_term_buf + 1); + if (UNLIKELY(len > 5)) { + return term_invalid_term(); + } + size_t node_size; + term node = parse_external_terms(external_term_buf + 3, &node_size, copy, heap, glb); + if (UNLIKELY(!term_is_atom(node))) { + return term_invalid_term(); + } + uint32_t creation = READ_32_UNALIGNED(external_term_buf + node_size + 3); + uint32_t data[len]; + for (uint16_t i = 0; i < len; i++) { + data[i] = READ_32_UNALIGNED(external_term_buf + node_size + 7 + (i * 4)); + } + *eterm_size = node_size + 7 + (len * 4); + if (node != NONODE_AT_NOHOST_ATOM || len != 2 || creation != 0) { + term this_node = glb->node_name; + uint32_t this_creation = this_node == NONODE_AT_NOHOST_ATOM ? 0 : glb->creation; + 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 { + return term_make_external_reference(node, len, data, creation, heap); + } + } else { + uint64_t ticks = ((uint64_t) data[0]) << 32 | data[1]; + return term_from_ref_ticks(ticks, heap); + } + } + default: return term_invalid_term(); } @@ -1032,6 +1106,33 @@ static int calculate_heap_usage(const uint8_t *external_term_buf, size_t remaini return heap_size + u; } + case NEWER_REFERENCE_EXT: { + if (UNLIKELY(remaining < 3)) { + return INVALID_TERM_SIZE; + } + remaining -= 3; + int buf_pos = 3; + uint16_t len = READ_16_UNALIGNED(external_term_buf + 1); + size_t heap_size = EXTERNAL_REF_SIZE(len); + 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[3] == SMALL_ATOM_UTF8_EXT) { + // Check if it's non-distributed node, in which case it's always a local ref + if (len == 2 && external_term_buf[4] == strlen("nonode@nohost") && memcmp(external_term_buf + 5, "nonode@nohost", strlen("nonode@nohost")) == 0) { + heap_size = REF_SIZE; + } + // See above for pids + } else if (UNLIKELY(external_term_buf[3] != ATOM_EXT)) { + return INVALID_TERM_SIZE; + } + buf_pos += node_size; + *eterm_size = buf_pos + 4 + (len * 4); + return heap_size + u; + } + default: return INVALID_TERM_SIZE; } diff --git a/src/libAtomVM/memory.c b/src/libAtomVM/memory.c index a972880ac9..6558c019bf 100644 --- a/src/libAtomVM/memory.c +++ b/src/libAtomVM/memory.c @@ -628,6 +628,10 @@ static void memory_scan_and_copy(HeapFragment *old_fragment, term *mem_start, co TRACE("- Found external pid.\n"); break; + case TERM_BOXED_EXTERNAL_REF: + TRACE("- Found external ref.\n"); + break; + case TERM_BOXED_FUN: { TRACE("- Found fun, size: %i.\n", (int) arity); @@ -751,6 +755,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_REF: + ptr += EXTERNAL_REF_SIZE - 1; + break; + case TERM_BOXED_FUN: // Skip header and module and process next terms ptr++; diff --git a/src/libAtomVM/nifs.c b/src/libAtomVM/nifs.c index 502f4949d1..ecd73f808d 100644 --- a/src/libAtomVM/nifs.c +++ b/src/libAtomVM/nifs.c @@ -3343,7 +3343,7 @@ static term nif_ets_new(Context *ctx, int argc, term argv[]) static inline bool is_ets_table_id(term t) { - return term_is_reference(t) || term_is_atom(t); + return term_is_local_reference(t) || term_is_atom(t); } static term nif_ets_insert(Context *ctx, int argc, term argv[]) @@ -3494,11 +3494,11 @@ static term nif_erlang_ref_to_list(Context *ctx, int argc, term argv[]) term t = argv[0]; VALIDATE_VALUE(t, term_is_reference); + size_t max_len = term_is_external(t) ? EXTERNAL_REF_AS_CSTRING_LEN : LOCAL_REF_AS_CSTRING_LEN; - char buf[REF_AS_CSTRING_LEN]; - int str_len = term_snprint(buf, REF_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); } @@ -3798,7 +3798,7 @@ static term nif_erlang_demonitor(Context *ctx, int argc, term argv[]) info = interop_proplist_get_value_default(options, INFO_ATOM, FALSE_ATOM) == TRUE_ATOM; } - VALIDATE_VALUE(ref, term_is_reference); + VALIDATE_VALUE(ref, term_is_local_reference); uint64_t ref_ticks = term_to_ref_ticks(ref); bool result = globalcontext_demonitor(ctx->global, ref_ticks); diff --git a/src/libAtomVM/otp_socket.c b/src/libAtomVM/otp_socket.c index 5f7d84c46f..1b76a155e9 100644 --- a/src/libAtomVM/otp_socket.c +++ b/src/libAtomVM/otp_socket.c @@ -593,7 +593,7 @@ bool term_is_otp_socket(term socket_term) bool ret = term_is_tuple(socket_term) && term_get_tuple_arity(socket_term) == 2 && term_is_binary(term_get_tuple_element(socket_term, 0)) - && term_is_reference(term_get_tuple_element(socket_term, 1)); + && term_is_local_reference(term_get_tuple_element(socket_term, 1)); TRACE("term is a socket: %i\n", ret); @@ -898,7 +898,7 @@ static term nif_socket_select_read(Context *ctx, int argc, term argv[]) term select_ref_term = argv[1]; if (select_ref_term != UNDEFINED_ATOM) { - VALIDATE_VALUE(select_ref_term, term_is_reference); + VALIDATE_VALUE(select_ref_term, term_is_local_reference); } struct SocketResource *rsrc_obj; if (UNLIKELY(!term_to_otp_socket(argv[0], &rsrc_obj, ctx))) { diff --git a/src/libAtomVM/posix_nifs.c b/src/libAtomVM/posix_nifs.c index c8885d506e..8aa98b8523 100644 --- a/src/libAtomVM/posix_nifs.c +++ b/src/libAtomVM/posix_nifs.c @@ -431,7 +431,7 @@ static term nif_atomvm_posix_select(Context *ctx, term argv[], enum ErlNifSelect int32_t process_pid = term_to_local_process_id(process_pid_term); term select_ref_term = argv[2]; if (select_ref_term != UNDEFINED_ATOM) { - VALIDATE_VALUE(select_ref_term, term_is_reference); + VALIDATE_VALUE(select_ref_term, term_is_local_reference); } void *fd_obj_ptr; if (UNLIKELY(!enif_get_resource(erl_nif_env_from_context(ctx), argv[0], ctx->global->posix_fd_resource_type, &fd_obj_ptr))) { diff --git a/src/libAtomVM/resources.c b/src/libAtomVM/resources.c index c9e21ebd00..f19eb3834b 100644 --- a/src/libAtomVM/resources.c +++ b/src/libAtomVM/resources.c @@ -121,7 +121,7 @@ int enif_select(ErlNifEnv *env, ErlNifEvent event, enum ErlNifSelectFlags mode, if (!(mode & (ERL_NIF_SELECT_STOP | ERL_NIF_SELECT_READ | ERL_NIF_SELECT_WRITE))) { return ERL_NIF_SELECT_BADARG; } - if (UNLIKELY(mode & (ERL_NIF_SELECT_READ | ERL_NIF_SELECT_WRITE) && !term_is_reference(ref) && ref != UNDEFINED_ATOM)) { + if (UNLIKELY(mode & (ERL_NIF_SELECT_READ | ERL_NIF_SELECT_WRITE) && !term_is_local_reference(ref) && ref != UNDEFINED_ATOM)) { return ERL_NIF_SELECT_BADARG; } diff --git a/src/libAtomVM/term.c b/src/libAtomVM/term.c index da5699dbe2..18686148a0 100644 --- a/src/libAtomVM/term.c +++ b/src/libAtomVM/term.c @@ -384,11 +384,25 @@ int term_funprint(PrinterFun *fun, term t, const GlobalContext *global) ret += printed; return ret; - } else if (term_is_reference(t)) { + } else if (term_is_local_reference(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<0.0.0.%" PRIu64 ">", ref_ticks); + // Update also LOCAL_REF_AS_CSTRING_LEN when changing this format string + return fun->print(fun, "#Ref<0.%" PRIu32 ".%" PRIu32 ">", (uint32_t) (ref_ticks >> 32), (uint32_t) ref_ticks); + + } else if (term_is_external_reference(t)) { + // Update also EXTERNAL_REF_AS_CSTRING_LEN when changing this format string + uint32_t node_atom_index = term_to_atom_index(term_get_external_node(t)); + uint32_t len = term_get_external_reference_len(t); + uint32_t data[len]; + term_get_external_reference_words(t, data); + // creation is not printed + int ret = fun->print(fun, "#Ref<%" PRIu32, node_atom_index); + for (uint32_t i = 0; i < len; i++) { + ret += fun->print(fun, ".%" PRIu32, data[i]); + } + ret += fun->print(fun, ">"); + return ret; } else if (term_is_boxed_integer(t)) { int size = term_boxed_size(t); @@ -499,14 +513,52 @@ TermCompareResult term_compare(term t, term other, TermCompareOpts opts, GlobalC break; } else if (term_is_reference(t) && term_is_reference(other)) { - int64_t t_ticks = term_to_ref_ticks(t); - int64_t other_ticks = term_to_ref_ticks(other); - if (t_ticks == other_ticks) { - CMP_POP_AND_CONTINUE(); + if (!term_is_external(t) && !term_is_external(other)) { + int64_t t_ticks = term_to_ref_ticks(t); + int64_t other_ticks = term_to_ref_ticks(other); + if (t_ticks == other_ticks) { + CMP_POP_AND_CONTINUE(); + } else { + result = (t_ticks > other_ticks) ? TermGreaterThan : TermLessThan; + break; + } + } else if (term_is_external(t) && term_is_external(other)) { + term node = term_get_external_node(t); + term other_node = term_get_external_node(other); + if (node == other_node) { + uint32_t creation = term_get_external_node_creation(t); + uint32_t other_creation = term_get_external_node_creation(other); + if (creation == other_creation) { + uint32_t len = term_get_external_reference_len(t); + uint32_t other_len = term_get_external_reference_len(other); + if (len == other_len) { + uint32_t data[len]; + uint32_t other_data[len]; + term_get_external_reference_words(t, data); + term_get_external_reference_words(other, other_data); + int cmp = memcmp(data, other_data, len * sizeof(uint32_t)); + if (cmp == 0) { + CMP_POP_AND_CONTINUE(); + } else { + result = (cmp > 0) ? TermGreaterThan : TermLessThan; + break; + } + } else { + result = (len > other_len) ? TermGreaterThan : TermLessThan; + break; + } + } else { + result = (creation > other_creation) ? TermGreaterThan : TermLessThan; + break; + } + } else { + result = (node > other_node) ? TermGreaterThan : TermLessThan; + break; + } } else { - result = (t_ticks > other_ticks) ? TermGreaterThan : TermLessThan; - break; + result = term_is_external(t) ? TermGreaterThan : TermLessThan; } + break; } else if (term_is_nonempty_list(t) && term_is_nonempty_list(other)) { term t_tail = term_get_list_tail(t); diff --git a/src/libAtomVM/term.h b/src/libAtomVM/term.h index 1302b299e1..f43ca9b6d9 100644 --- a/src/libAtomVM/term.h +++ b/src/libAtomVM/term.h @@ -62,6 +62,7 @@ extern "C" { #define TERM_BOXED_EXTERNAL_THING 0x30 #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) @@ -94,6 +95,13 @@ extern "C" { #else #error #endif +#if TERM_BYTES == 8 + #define EXTERNAL_REF_SIZE(words) (2 + ((words + 1) / 2)) +#elif TERM_BYTES == 4 + #define EXTERNAL_REF_SIZE(words) (3 + words) +#else + #error +#endif #define TUPLE_SIZE(elems) ((int) (elems + 1)) #define CONS_SIZE 2 #define REFC_BINARY_CONS_OFFSET 4 @@ -121,9 +129,14 @@ extern "C" { #define TERM_FROM_ATOM_INDEX(atom_index) ((atom_index << 6) | 0xB) -// 2^64 = 18446744073709551616 (20 chars) -// "#Ref<0.0.0." ">\0" (13 chars) -#define REF_AS_CSTRING_LEN 33 +// 2^32-1 = 4294967295 (10 chars) +// "#Ref<0." "." ">\0" (10 chars) +#define LOCAL_REF_AS_CSTRING_LEN 30 + +// 2^26-1 = 67108863 (8 chars) (node, atom index) +// 2^32-1 = 4294967295 (10 chars) +// "#Ref<" "." "." "." "." "." ">\0" (12 chars) +#define EXTERNAL_REF_AS_CSTRING_LEN 70 // 2^28-1 = 268435455 (9 chars) // "<0." ".0>\0" (7 chars) @@ -603,7 +616,46 @@ 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) { + unsigned int boxed_type = boxed_value[0] & 0x3F; + return (boxed_type == TERM_BOXED_REF) || (boxed_type == TERM_BOXED_EXTERNAL_REF); + } + + return false; +} + +/** + * @brief Checks if a term is a local reference + * + * @details Returns \c true if a term is a local reference, 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_local_reference(term t) +{ + if (term_is_boxed(t)) { + const term *boxed_value = term_to_const_term_ptr(t); + const uint32_t header = boxed_value[0] & 0x3F; + if (header == TERM_BOXED_REF) { + return true; + } + } + + return false; +} + +/** + * @brief Checks if a term is an external reference + * + * @details Returns \c true if a term is a local reference, 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_reference(term t) +{ + if (term_is_boxed(t)) { + const term *boxed_value = term_to_const_term_ptr(t); + const uint32_t header = boxed_value[0] & 0x3F; + if (header == TERM_BOXED_EXTERNAL_REF) { return true; } } @@ -1281,7 +1333,7 @@ static inline term term_from_ref_ticks(uint64_t ref_ticks, Heap *heap) static inline uint64_t term_to_ref_ticks(term rt) { - TERM_DEBUG_ASSERT(term_is_reference(rt)); + TERM_DEBUG_ASSERT(term_is_local_reference(rt)); const term *boxed_value = term_to_const_term_ptr(rt); @@ -1409,6 +1461,105 @@ static inline uint32_t term_get_external_pid_serial(term t) #elif TERM_BYTES == 4 return (uint32_t) boxed_value[4]; + #else + #error "terms must be either 32 or 64 bit wide" + #endif +} + +/* + * @brief Make an external reference term from node, creation, number of words and words + * + * @param node name of the node (atom) + * @param len number of words (1..5) + * @param data words + * @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_make_external_reference(term node, uint16_t len, uint32_t *data, uint32_t creation, Heap *heap) +{ + TERM_DEBUG_ASSERT(term_is_atom(node)); + + term *boxed_value = memory_heap_alloc(heap, EXTERNAL_REF_SIZE(len)); + int atom_index = term_to_atom_index(node); + boxed_value[0] = ((EXTERNAL_REF_SIZE(len) - 1) << 6) | TERM_BOXED_EXTERNAL_REF; + + #if TERM_BYTES == 8 + boxed_value[1] = (term) (((uint64_t) creation) << 32 | atom_index); + boxed_value[2] = (term) (((uint64_t) len) << 32 | data[0]); + for (int i = 1; i < len; i += 2) { + uint64_t word = ((uint64_t) data[i]) << 32; + if (i + 1 < len) { + word |= data[i + 1]; + } + boxed_value[3 + ((i - 1) / 2)] = word; + } + + #elif TERM_BYTES == 4 + boxed_value[1] = (term) atom_index; + boxed_value[2] = (term) creation; + for (int i = 0; i < len; i++) { + boxed_value[3 + i] = data[i]; + } + + #else + #error "terms must be either 32 or 64 bit wide" + #endif + + return ((term) boxed_value) | TERM_BOXED_VALUE_TAG; +} + +/** + * @brief Get the number of words of an external reference + * + * @param term external term + * @return the number of external nodes + */ +static inline uint32_t term_get_external_reference_len(term t) +{ + TERM_DEBUG_ASSERT(term_is_external_reference(t)); + + const term *boxed_value = term_to_const_term_ptr(t); + + #if TERM_BYTES == 8 + return (uint32_t) (boxed_value[2] >> 32); + + #elif TERM_BYTES == 4 + return (uint32_t) (boxed_value[0] >> 6) - 2; + + #else + #error "terms must be either 32 or 64 bit wide" + #endif +} + +/** + * @brief Get the words of an external reference + * + * @param term external term + * @param data the words to read, must be at least len long (maximum: 5) + */ +static inline void term_get_external_reference_words(term t, uint32_t *data) +{ + TERM_DEBUG_ASSERT(term_is_external_reference(t)); + + const term *boxed_value = term_to_const_term_ptr(t); + + #if TERM_BYTES == 8 + size_t len = (uint32_t) (boxed_value[2] >> 32); + data[0] = (uint32_t) boxed_value[2]; + for (size_t i = 1; i < len; i += 2) { + uint64_t word = boxed_value[3 + ((i - 1) / 2)]; + data[i] = (uint32_t) (word >> 32); + if (i + 1 < len) { + data[i + 1] = (uint32_t) word; + } + } + + #elif TERM_BYTES == 4 + size_t len = (uint32_t) (boxed_value[0] >> 6) - 2; + for (size_t i = 0; i < len; i++) { + data[i] = boxed_value[3 + i]; + } #else #error "terms must be either 32 or 64 bit wide" diff --git a/tests/erlang_tests/ref_to_list_test.erl b/tests/erlang_tests/ref_to_list_test.erl index 03dcdf12f1..5c7bc9e0dd 100644 --- a/tests/erlang_tests/ref_to_list_test.erl +++ b/tests/erlang_tests/ref_to_list_test.erl @@ -20,26 +20,31 @@ -module(ref_to_list_test). --export([start/0, check/1, g/1]). +-export([start/0]). start() -> - [A1, A2, A3, A4, A5 | T] = g(h()), - A1 + A2 + A3 + A4 + A5 + check(T). + ok = test_loop(10), + 0. -check([$>]) -> - 3; -check([$. | T]) -> - 1 + check(T); -check([H | T]) when H >= $0 andalso H =< $9 -> - check(T). +test_loop(0) -> + ok; +test_loop(N) when N > 0 -> + RefStr = ref_to_list(make_ref()), + [0 | T] = parse_ref(RefStr), + ok = check_words(T), + test_loop(N - 1). -g(X) -> - try erlang:ref_to_list(X) of - Res -> Res - catch - error:badarg -> 0; - _:_ -> 10 - end. +parse_ref("#Ref<" ++ T) -> + parse_ref0(T, 0, []). -h() -> - erlang:make_ref(). +parse_ref0(">", N, Acc) -> + Acc ++ [N]; +parse_ref0([$. | T], N, Acc) -> + parse_ref0(T, 0, Acc ++ [N]); +parse_ref0([H | T], N, Acc) when H >= $0 andalso H =< $9 -> + parse_ref0(T, N * 10 + (H - $0), Acc). + +check_words([]) -> + ok; +check_words([W | T]) when is_integer(W) andalso W >= 0 andalso W < 1 bsl 32 -> + check_words(T). diff --git a/tests/erlang_tests/test_binary_to_term.erl b/tests/erlang_tests/test_binary_to_term.erl index efd3e6d5e6..947d6cf99f 100644 --- a/tests/erlang_tests/test_binary_to_term.erl +++ b/tests/erlang_tests/test_binary_to_term.erl @@ -174,6 +174,7 @@ start() -> ok = test_atom_decoding(), ok = test_atom_decoding_checks(), ok = test_encode_pid(), + ok = test_encode_reference(), 0. test_reverse(T, Interop) -> @@ -526,6 +527,79 @@ do_unsetnode({NetKernelPid, MonitorRef}) -> end, ok. +test_encode_reference() -> + TestRef = make_ref(), + Bin = term_to_binary(TestRef), + Ref = binary_to_term(Bin), + true = is_reference(Ref), + Ref123 = binary_to_term( + <<131, 90, 0, 2, 119, 13, "nonode@nohost", 0:32, 1:32, 2:32>> + ), + true = is_reference(Ref123), + ExpectedSize = + case erlang:system_info(machine) of + "ATOM" -> + % small utf8 atom & reference with 2 words + 31; + "BEAM" -> + OTPRelease = erlang:system_info(otp_release), + if + OTPRelease < "23" -> 33; + OTPRelease < "26" -> 36; + % small utf8 atom + true -> 35 + end + end, + ExpectedSize = byte_size(Bin), + Ref1 = binary_to_term(<<131, 90, 0, 1, 119, 4, "true", 1:32, 43:32>>), + Ref2 = binary_to_term(<<131, 90, 0, 1, 119, 4, "true", 2:32, 43:32>>), + false = Ref1 =:= Ref2, + "#Ref<1.43>" = ref_to_list(Ref1), + "#Ref<1.43>" = ref_to_list(Ref2), + + case has_setnode_creation() of + true -> + % Test distributed pid + Ref42 = do_setnode(test@test_node, 42), + DistributedRef123_42 = binary_to_term( + <<131, 90, 0, 2, 119, 14, "test@test_node", 42:32, 1:32, 2:32>> + ), + true = is_reference(DistributedRef123_42), + true = DistributedRef123_42 =:= Ref123, + + DistributedBin42 = term_to_binary(TestRef), + true = DistributedBin42 =/= Bin, + TestRef42 = binary_to_term(DistributedBin42), + true = TestRef42 =:= TestRef, + ExpectedSize = byte_size(DistributedBin42) - 1, + + ok = do_unsetnode(Ref42), + + Ref43 = do_setnode(test@test_node, 43), + DistributedRef123_43 = binary_to_term( + <<131, 90, 0, 2, 119, 14, "test@test_node", 43:32, 1:32, 2:32>> + ), + true = is_reference(DistributedRef123_43), + true = DistributedRef123_43 =:= Ref123, + + DistributedRef123_42_43 = binary_to_term( + <<131, 90, 0, 2, 119, 14, "test@test_node", 42:32, 1:32, 2:32>> + ), + true = is_reference(DistributedRef123_42_43), + false = DistributedRef123_42_43 =:= Ref123, + + DistributedBin43 = term_to_binary(TestRef), + true = DistributedBin43 =/= Bin, + TestRef43 = binary_to_term(DistributedBin43), + true = TestRef43 =:= TestRef, + + ok = do_unsetnode(Ref43), + ok; + false -> + ok + end, + ok. + make_binterm_fun(Id) -> fun() -> Bin = ?MODULE:get_binary(Id), diff --git a/tests/test.c b/tests/test.c index f8a33c702f..8b22a6c14e 100644 --- a/tests/test.c +++ b/tests/test.c @@ -482,7 +482,7 @@ struct Test tests[] = { TEST_CASE_EXPECTED(fail_apply_last, 17), TEST_CASE_EXPECTED(pid_to_list_test, 63), - TEST_CASE_EXPECTED(ref_to_list_test, 386), + TEST_CASE(ref_to_list_test), TEST_CASE_EXPECTED(test_binary_to_integer, 99), TEST_CASE(test_binary_to_integer_2),