diff --git a/src/libAtomVM/context.c b/src/libAtomVM/context.c index da0589ff7..ccfd35e94 100644 --- a/src/libAtomVM/context.c +++ b/src/libAtomVM/context.c @@ -225,7 +225,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 ad9265bd8..856a45991 100644 --- a/src/libAtomVM/ets_hashtable.c +++ b/src/libAtomVM/ets_hashtable.c @@ -287,7 +287,7 @@ static uint32_t hash_term_incr(term t, int32_t h, GlobalContext *global) return hash_float(t, h, global); } else if (term_is_local_pid(t)) { return hash_pid(t, h, global); - } else if (term_is_reference(t)) { + } else if (term_is_local_reference(t)) { return hash_reference(t, h, global); } else if (term_is_binary(t)) { return hash_binary(t, h, global); diff --git a/src/libAtomVM/externalterm.c b/src/libAtomVM/externalterm.c index cbce0e93c..9361fcf44 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 @@ -419,6 +420,46 @@ static int serialize_term(uint8_t *buf, term t, GlobalContext *glb) WRITE_32_UNALIGNED(buf + k + 8, term_to_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; + k += serialize_term(IS_NULL_PTR(buf) ? NULL : buf + k, NONODE_AT_NOHOST_ATOM, glb); + if (!IS_NULL_PTR(buf)) { + uint64_t ticks = term_to_ref_ticks(t); + WRITE_64_UNALIGNED(buf + k, ticks); + WRITE_32_UNALIGNED(buf + k + 8, 0); // creation + } + 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_to_external_node(t); + k += serialize_term(IS_NULL_PTR(buf) ? NULL : buf + k, node, glb); + uint32_t len = term_to_external_reference_len(t); + if (!IS_NULL_PTR(buf)) { + WRITE_32_UNALIGNED(buf + k, term_to_external_node_creation(t)); + } + k += 4; + if (!IS_NULL_PTR(buf)) { + uint32_t data[len]; + term_to_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(); @@ -709,6 +750,30 @@ 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) { + return term_from_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(); } @@ -1023,6 +1088,32 @@ 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; + size_t heap_size = EXTERNAL_REF_SIZE; + uint16_t len = READ_16_UNALIGNED(external_term_buf + 1); + 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 local node + 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; + } + } 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 ae765d888..51870e04d 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: { int fun_size = term_get_size_from_boxed_header(t); TRACE("- Found fun, size: %i.\n", fun_size); @@ -752,6 +756,10 @@ static void memory_scan_and_rewrite(size_t count, term *terms, const term *old_s ptr += EXTERNAL_PID_SIZE - 1; 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 5de4dc4c5..ba512b351 100644 --- a/src/libAtomVM/nifs.c +++ b/src/libAtomVM/nifs.c @@ -3301,7 +3301,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[]) @@ -3462,10 +3462,10 @@ static term nif_erlang_ref_to_list(Context *ctx, int argc, term argv[]) UNUSED(argc); term t = argv[0]; - VALIDATE_VALUE(t, term_is_reference); + VALIDATE_VALUE(t, term_is_local_reference); - char buf[REF_AS_CSTRING_LEN]; - int str_len = term_snprint(buf, REF_AS_CSTRING_LEN, t, ctx->global); + char buf[LOCAL_REF_AS_CSTRING_LEN]; + int str_len = term_snprint(buf, LOCAL_REF_AS_CSTRING_LEN, t, ctx->global); if (UNLIKELY(str_len < 0)) { // TODO: change to internal error or something like that RAISE_ERROR(OUT_OF_MEMORY_ATOM); @@ -3767,7 +3767,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 5f7d84c46..1b76a155e 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 c8885d506..8aa98b852 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 c9e21ebd0..f19eb3834 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 f702a1834..b090e9d68 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_to_external_node(t)); + uint32_t len = term_to_external_reference_len(t); + uint32_t data[len]; + term_to_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_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 len = term_to_external_reference_len(t); + uint32_t other_len = term_to_external_reference_len(other); + if (len == other_len) { + uint32_t data[len]; + uint32_t other_data[len]; + term_to_external_reference_words(t, data); + term_to_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 d6cf5be70..7a4b94cca 100644 --- a/src/libAtomVM/term.h +++ b/src/libAtomVM/term.h @@ -94,6 +94,13 @@ extern "C" { #else #error #endif +#if TERM_BYTES == 8 + #define EXTERNAL_REF_SIZE 5 +#elif TERM_BYTES == 4 + #define EXTERNAL_REF_SIZE 8 +#else + #error +#endif #define TUPLE_SIZE(elems) ((int) (elems + 1)) #define CONS_SIZE 2 #define REFC_BINARY_CONS_OFFSET 4 @@ -121,9 +128,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) @@ -335,6 +347,8 @@ static inline size_t term_get_size_from_boxed_header(term header) switch (masked_value) { case TERM_BOXED_EXTERNAL_PID: return EXTERNAL_PID_SIZE - 1; + case TERM_BOXED_EXTERNAL_REF: + return EXTERNAL_REF_SIZE - 1; default: return header >> 6; } @@ -594,6 +608,46 @@ static inline bool term_is_reference(term t) 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; + } + } + + return false; +} + /** * @brief Checks if a term is a fun * @@ -1264,7 +1318,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); @@ -1387,6 +1441,102 @@ static inline uint32_t term_to_external_pid_serial(term t) #endif } +/** + * @brief Get a 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_from_external_reference(term node, uint16_t len, uint32_t *data, uint32_t creation, Heap *heap) +{ + term *boxed_value = memory_heap_alloc(heap, EXTERNAL_REF_SIZE); + int atom_index = term_to_atom_index(node); + boxed_value[0] = (atom_index << 6) | TERM_BOXED_EXTERNAL_REF; + + #if TERM_BYTES == 8 + boxed_value[1] = (term) (((uint64_t) len) << 32 | creation); + for (int i = 0; i < len; i += 2) { + uint64_t word = ((uint64_t) data[i]) << 32; + if (i + 1 < len) { + word |= data[i + 1]; + } + boxed_value[2 + (i / 2)] = word; + } + + #elif TERM_BYTES == 4 + boxed_value[1] = (term) creation; + boxed_value[2] = (term) len; + 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_to_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[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 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_to_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[1] >> 32); + for (size_t i = 0; i < len; i += 2) { + uint64_t word = boxed_value[2 + (i / 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[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" + #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 02b69becd..076d2daef 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) -> @@ -453,6 +454,36 @@ test_encode_pid() -> true = TruePid1Again =:= TruePid1, ok. +test_encode_reference() -> + Bin = term_to_binary(make_ref()), + Ref = binary_to_term(Bin), + true = is_reference(Ref), + true = is_reference( + binary_to_term( + <<131, 90, 0, 3, 119, 13, "nonode@nohost", 0:32, 1:32, 2:32, 3:32>> + ) + ), + ExpectedSize = + case erlang:system_info(machine) of + "ATOM" -> + 31; % small utf8 atom & reference with 2 words + "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), + ok. + make_binterm_fun(Id) -> fun() -> Bin = ?MODULE:get_binary(Id),