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..b87018fdec 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,21 @@ 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); + const uint32_t *words = term_get_external_reference_words(t); + 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 +315,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..d683092c39 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,47 @@ 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)) { + const uint32_t *data = term_get_external_reference_words(t); + for (uint32_t i = 0; i < len; i++) { + WRITE_32_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 +758,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[5]; + 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(); } @@ -1028,10 +1101,45 @@ static int calculate_heap_usage(const uint8_t *external_term_buf, size_t remaini return INVALID_TERM_SIZE; } buf_pos += node_size; + remaining -= node_size; + if (UNLIKELY(remaining < 3 * 4)) { + return INVALID_TERM_SIZE; + } *eterm_size = buf_pos + 12; 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; + remaining -= node_size; + if (UNLIKELY(remaining < (size_t) ((len + 1) * 4))) { + return INVALID_TERM_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 1c0540023f..84dfb6c790 100644 --- a/src/libAtomVM/memory.c +++ b/src/libAtomVM/memory.c @@ -624,6 +624,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); @@ -747,6 +751,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 += term_get_size_from_boxed_header(t); + 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..72362becf0 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[]) @@ -3477,10 +3477,9 @@ 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[max_len]; - int str_len = term_snprint(buf, max_len, t, ctx->global); + char buf[PID_AS_CSTRING_LEN]; + int str_len = term_snprint(buf, PID_AS_CSTRING_LEN, t, ctx->global); if (UNLIKELY(str_len < 0)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); } @@ -3498,7 +3497,6 @@ static term nif_erlang_ref_to_list(Context *ctx, int argc, term argv[]) char buf[REF_AS_CSTRING_LEN]; int str_len = term_snprint(buf, 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); } @@ -3798,7 +3796,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..5ae5f5cba2 100644 --- a/src/libAtomVM/term.c +++ b/src/libAtomVM/term.c @@ -23,6 +23,7 @@ #include "atom.h" #include "atom_table.h" #include "context.h" +#include "defaultatoms.h" #include "interop.h" #include "module.h" #include "tempstack.h" @@ -31,6 +32,7 @@ #include #include #include +#include #include struct FprintfFun @@ -384,11 +386,24 @@ 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); + 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 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); + const uint32_t *data = term_get_external_reference_words(t); + // creation is not printed + int ret = fun->print(fun, "#Ref<%" PRIu32, node_atom_index); + for (int i = len - 1; i >= 0; 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,13 +514,67 @@ 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 { - result = (t_ticks > other_ticks) ? TermGreaterThan : TermLessThan; - break; + term node = term_is_external(t) ? term_get_external_node(t) : NONODE_AT_NOHOST_ATOM; + term other_node = term_is_external(other) ? term_get_external_node(other) : NONODE_AT_NOHOST_ATOM; + if (node == other_node) { + uint32_t creation = term_is_external(t) ? term_get_external_node_creation(t) : 0; + uint32_t other_creation = term_is_external(other) ? term_get_external_node_creation(other) : 0; + if (creation == other_creation) { + uint32_t len = term_is_external(t) ? term_get_external_reference_len(t) : 2; + uint32_t other_len = term_is_external(other) ? term_get_external_reference_len(other) : 2; + if (len == other_len) { + const uint32_t *data; + const uint32_t *other_data; + uint32_t local_data[2]; + if (term_is_external(t)) { + data = term_get_external_reference_words(t); + } else { + uint64_t ref_ticks = term_to_ref_ticks(t); + local_data[0] = ref_ticks >> 32; + local_data[1] = (uint32_t) ref_ticks; + data = local_data; + } + if (term_is_external(other)) { + other_data = term_get_external_reference_words(other); + } else { + uint64_t ref_ticks = term_to_ref_ticks(other); + local_data[0] = ref_ticks >> 32; + local_data[1] = (uint32_t) ref_ticks; + other_data = local_data; + } + // Comparison is done in reverse order + for (int i = len - 1; i >= 0; i--) { + if (data[i] != other_data[i]) { + result = (data[i] > other_data[i]) ? TermGreaterThan : TermLessThan; + break; + } + } + if (result != TermEquals) { + break; + } + CMP_POP_AND_CONTINUE(); + } else { + result = (len > other_len) ? TermGreaterThan : TermLessThan; + break; + } + } else { + result = (creation > other_creation) ? TermGreaterThan : TermLessThan; + break; + } + } else { + t = node; + other = other_node; + } } } else if (term_is_nonempty_list(t) && term_is_nonempty_list(other)) { @@ -671,45 +740,36 @@ TermCompareResult term_compare(term t, term other, TermCompareOpts opts, GlobalC result = (atom_cmp_result > 0) ? TermGreaterThan : TermLessThan; break; - } else if (term_is_external_pid(t) && term_is_external_pid(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 serial = term_get_external_pid_serial(t); - uint32_t other_serial = term_get_external_pid_serial(other); - if (serial == other_serial) { - uint32_t process_id = term_get_external_pid_process_id(t); - uint32_t other_process_id = term_get_external_pid_process_id(other); - if (process_id == other_process_id) { + } else if (term_is_pid(t) && term_is_pid(other)) { + uint32_t process_id = term_is_external(t) ? term_get_external_pid_process_id(t) : (uint32_t) term_to_local_process_id(t); + uint32_t other_process_id = term_is_external(other) ? term_get_external_pid_process_id(other) : (uint32_t) term_to_local_process_id(other); + if (process_id == other_process_id) { + uint32_t serial = term_is_external(t) ? term_get_external_pid_serial(t) : 0; + uint32_t other_serial = term_is_external(other) ? term_get_external_pid_serial(other) : 0; + if (serial == other_serial) { + term node = term_is_external(t) ? term_get_external_node(t) : NONODE_AT_NOHOST_ATOM; + term other_node = term_is_external(other) ? term_get_external_node(other) : NONODE_AT_NOHOST_ATOM; + if (node == other_node) { + uint32_t creation = term_is_external(t) ? term_get_external_node_creation(t) : 0; + uint32_t other_creation = term_is_external(other) ? term_get_external_node_creation(other) : 0; + if (creation == other_creation) { CMP_POP_AND_CONTINUE(); } else { - result = (process_id > other_process_id) ? TermGreaterThan : TermLessThan; + result = (creation > other_creation) ? TermGreaterThan : TermLessThan; break; } } else { - result = (serial > other_serial) ? TermGreaterThan : TermLessThan; - break; + t = node; + other = other_node; } } else { - result = (creation > other_creation) ? TermGreaterThan : TermLessThan; + result = (serial > other_serial) ? TermGreaterThan : TermLessThan; break; } } else { - result = (node > other_node) ? TermGreaterThan : TermLessThan; + result = (process_id > other_process_id) ? 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 31996c2181..984309fa48 100644 --- a/src/libAtomVM/term.h +++ b/src/libAtomVM/term.h @@ -61,6 +61,7 @@ extern "C" { #define TERM_BOXED_MAP 0x2C #define TERM_BOXED_EXTERNAL_THING 0x30 #define TERM_BOXED_EXTERNAL_PID 0x30 +#define TERM_BOXED_EXTERNAL_REF 0x38 #define TERM_UNUSED 0x2B #define TERM_RESERVED_MARKER(x) ((x << 6) | TERM_UNUSED) @@ -93,6 +94,13 @@ extern "C" { #else #error #endif +#if TERM_BYTES == 8 + #define EXTERNAL_REF_SIZE(words) (3 + (words / 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 @@ -120,19 +128,24 @@ 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 +// Local ref is at most 30 bytes: +// 2^32-1 = 4294967295 (10 chars) +// "#Ref<0." "." ">\0" (10 chars) +// External ref is at most 70 bytes: +// 2^26-1 = 67108863 (8 chars) (node, atom index) +// 2^32-1 = 4294967295 (10 chars) +// "#Ref<" "." "." "." "." "." ">\0" (12 chars) +#define REF_AS_CSTRING_LEN 70 +// Local pid is at most 16 bytes: // 2^28-1 = 268435455 (9 chars) // "<0." ".0>\0" (7 chars) -#define LOCAL_PID_AS_CSTRING_LEN 16 - +// External pid is at most 32 bytes: // 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 +#define PID_AS_CSTRING_LEN 32 #ifndef TYPEDEF_GLOBALCONTEXT #define TYPEDEF_GLOBALCONTEXT @@ -573,13 +586,13 @@ static inline bool term_is_tuple(term t) } /** - * @brief Checks if a term is a reference + * @brief Checks if a term is a local reference * - * @details Returns \c true if a term is a reference, otherwise \c false. + * @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_reference(term t) +static inline bool term_is_local_reference(term t) { if (term_is_boxed(t)) { const term *boxed_value = term_to_const_term_ptr(t); @@ -591,6 +604,37 @@ static inline bool term_is_reference(term t) 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); + if ((boxed_value[0] & 0x3F) == TERM_BOXED_EXTERNAL_REF) { + return true; + } + } + + return false; +} + +/** + * @brief Checks if a term is a reference + * + * @details Returns \c true if a term is a 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_reference(term t) +{ + return term_is_local_reference(t) || term_is_external_reference(t); +} + /** * @brief Checks if a term is a fun * @@ -1261,7 +1305,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); @@ -1293,20 +1337,12 @@ static inline term term_make_external_process_id(term node, uint32_t process_id, term *boxed_value = memory_heap_alloc(heap, EXTERNAL_PID_SIZE); int atom_index = term_to_atom_index(node); boxed_value[0] = ((EXTERNAL_PID_SIZE - 1) << 6) | TERM_BOXED_EXTERNAL_PID; + uint32_t *external_thing_words = (uint32_t *) &boxed_value[1]; - #if TERM_BYTES == 8 - boxed_value[1] = (term) (((uint64_t) creation) << 32 | atom_index); - boxed_value[2] = (term) (((uint64_t) serial) << 32 | process_id); - - #elif TERM_BYTES == 4 - boxed_value[1] = (term) atom_index; - boxed_value[2] = (term) creation; - boxed_value[3] = (term) process_id; - boxed_value[4] = (term) serial; - - #else - #error "terms must be either 32 or 64 bit wide" - #endif + external_thing_words[0] = atom_index; + external_thing_words[1] = creation; + external_thing_words[2] = process_id; + external_thing_words[3] = serial; return ((term) boxed_value) | TERM_BOXED_VALUE_TAG; } @@ -1322,8 +1358,8 @@ static inline term term_get_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((uint32_t) boxed_value[1]); + const uint32_t *external_thing_words = (const uint32_t *) &boxed_value[1]; + return term_from_atom_index(external_thing_words[0]); } /** @@ -1337,35 +1373,99 @@ static inline uint32_t term_get_external_node_creation(term t) TERM_DEBUG_ASSERT(term_is_external(t)); const term *boxed_value = term_to_const_term_ptr(t); + const uint32_t *external_thing_words = (const uint32_t *) &boxed_value[1]; + return external_thing_words[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_get_external_pid_process_id(term t) +{ + TERM_DEBUG_ASSERT(term_is_external_pid(t)); + + const term *boxed_value = term_to_const_term_ptr(t); + const uint32_t *external_thing_words = (const uint32_t *) &boxed_value[1]; + return external_thing_words[2]; +} + +/** + * @brief Get the serial of an external pid + * + * @param term external term + * @return the serial of the external pid + */ +static inline uint32_t term_get_external_pid_serial(term t) +{ + TERM_DEBUG_ASSERT(term_is_external_pid(t)); + + const term *boxed_value = term_to_const_term_ptr(t); + const uint32_t *external_thing_words = (const uint32_t *) &boxed_value[1]; + return external_thing_words[3]; +} + +/* + * @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; + uint32_t *external_thing_words = (uint32_t *) &boxed_value[1]; #if TERM_BYTES == 8 - return (uint32_t) (boxed_value[1] >> 32); + external_thing_words[0] = atom_index; + external_thing_words[1] = creation; + external_thing_words[2] = len; + for (int i = 0; i < len; i++) { + external_thing_words[3 + i] = data[i]; + } #elif TERM_BYTES == 4 - return (uint32_t) boxed_value[2]; + external_thing_words[0] = atom_index; + external_thing_words[1] = creation; + for (int i = 0; i < len; i++) { + external_thing_words[2 + 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 process id of an external pid + * @brief Get the number of words of an external reference * - * @param term external pid - * @return the process id of the external pid + * @param term external term + * @return the number of words of the external reference (from 1 to 5) */ -static inline uint32_t term_get_external_pid_process_id(term t) +static inline uint32_t term_get_external_reference_len(term t) { - TERM_DEBUG_ASSERT(term_is_external_pid(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]; + const uint32_t *external_thing_words = (const uint32_t *) &boxed_value[1]; + return (uint32_t) external_thing_words[2]; #elif TERM_BYTES == 4 - return (uint32_t) boxed_value[3]; + return (uint32_t) (boxed_value[0] >> 6) - 2; #else #error "terms must be either 32 or 64 bit wide" @@ -1373,22 +1473,23 @@ static inline uint32_t term_get_external_pid_process_id(term t) } /** - * @brief Get the serial of an external pid + * @brief Get the words of an external reference * * @param term external term - * @return the serial of the external pid + * @return a pointer to (len) words of the external reference */ -static inline uint32_t term_get_external_pid_serial(term t) +static inline const uint32_t *term_get_external_reference_words(term t) { - TERM_DEBUG_ASSERT(term_is_external_pid(t)); + TERM_DEBUG_ASSERT(term_is_external_reference(t)); const term *boxed_value = term_to_const_term_ptr(t); + const uint32_t *external_thing_words = (const uint32_t *) &boxed_value[1]; #if TERM_BYTES == 8 - return (uint32_t) (boxed_value[2] >> 32); + return external_thing_words + 3; #elif TERM_BYTES == 4 - return (uint32_t) boxed_value[4]; + return external_thing_words + 2; #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..100d9a9d9b 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) -> @@ -429,21 +430,29 @@ test_encode_pid() -> end end, ExpectedSize = byte_size(Bin), + + % Creation is not displayed in list representation FalsePid1 = binary_to_term( - <<131, 88, 119, 5, "false", 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0>> + <<131, 88, 119, 5, "false", 1:32, 0:32, 0:32>> ), 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>> + <<131, 88, 119, 5, "false", 1:32, 0:32, 1:32>> ), "<0.1.0>" = pid_to_list(FalsePid1Cr), false = FalsePid1 =:= FalsePid1Cr, + true = FalsePid1 < FalsePid1Cr, + + % Order is done by pid on a given node 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, + true = FalsePid1 < FalsePid2, + true = FalsePid1Cr < FalsePid2, + TruePid1 = binary_to_term( <<131, 88, 119, 4, "true", 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0>> ), @@ -452,6 +461,72 @@ test_encode_pid() -> TruePid1Again = binary_to_term(term_to_binary(TruePid1)), true = TruePid1Again =:= TruePid1, + % Order is done by node atom, with local pid as nonode@nohost + true = Pid1 > binary_to_term(<<131, 88, 119, 5, "false", 1:32, 0:32, 0:32>>), + true = Pid1 > binary_to_term(<<131, 88, 119, 6, "nonode", 1:32, 0:32, 0:32>>), + true = Pid1 < binary_to_term(<<131, 88, 119, 6, "nonodf", 1:32, 0:32, 0:32>>), + + % Order + FalsePid_42_43_44 = binary_to_term( + <<131, 88, 119, 5, "false", 42:32, 43:32, 44:32>> + ), + FalsePid_42_44_43 = binary_to_term( + <<131, 88, 119, 5, "false", 42:32, 44:32, 43:32>> + ), + TruePid_42_43_44 = binary_to_term( + <<131, 88, 119, 4, "true", 42:32, 43:32, 44:32>> + ), + FalsePid_44_43_42 = binary_to_term( + <<131, 88, 119, 5, "false", 44:32, 43:32, 42:32>> + ), + TruePid_44_43_42 = binary_to_term( + <<131, 88, 119, 4, "true", 44:32, 43:32, 42:32>> + ), + FalsePid_43_43_43 = binary_to_term( + <<131, 88, 119, 5, "false", 43:32, 43:32, 43:32>> + ), + FalsePid_42_43_45 = binary_to_term( + <<131, 88, 119, 5, "false", 42:32, 43:32, 45:32>> + ), + + % Pid first, serial second, atom third and creation fourth + true = FalsePid_42_43_44 < FalsePid_43_43_43, + true = FalsePid_42_43_44 < FalsePid_42_44_43, + true = FalsePid_43_43_43 < FalsePid_44_43_42, + true = FalsePid_42_43_44 < TruePid_42_43_44, + true = FalsePid_42_43_44 < TruePid_44_43_42, + true = FalsePid_44_43_42 > TruePid_42_43_44, + true = FalsePid_42_43_45 < TruePid_42_43_44, + true = FalsePid_42_44_43 > TruePid_42_43_44, + + % Not enough data + ok = + try + binary_to_term(<<131, 88, 119, 4, "true", 1:32, 2:32>>) + catch + error:badarg -> ok + end, + ok = + try + binary_to_term(<<131, 88, 119, 4, "true", 1:32>>) + catch + error:badarg -> ok + end, + ok = + try + binary_to_term(<<131, 88, 119, 4, "true">>) + catch + error:badarg -> ok + end, + + % Node must be an atom + ok = + try + binary_to_term(<<131, 88, 106, 1:32, 2:32, 3:32>>) + catch + error:badarg -> ok + end, + case has_setnode_creation() of true -> % Test distributed pid @@ -526,6 +601,245 @@ 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, HasV4NC} = + case erlang:system_info(machine) of + "ATOM" -> + % small utf8 atom & reference with 2 words + {31, true}; + "BEAM" -> + OTPRelease = erlang:system_info(otp_release), + if + OTPRelease < "23" -> {33, false}; + OTPRelease < "24" -> {36, false}; + OTPRelease < "26" -> {36, true}; + % small utf8 atom + true -> {35, true} + end + end, + ExpectedSize = byte_size(Bin), + + % Creation is not displayed in list representation + 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, + true = Ref1 < Ref2, + "#Ref<1.43>" = ref_to_list(Ref1), + "#Ref<1.43>" = ref_to_list(Ref2), + + % Test order + Ref3 = binary_to_term(<<131, 90, 0, 2, 119, 4, "true", 1:32, 43:32, 44:32>>), + Ref4 = binary_to_term(<<131, 90, 0, 2, 119, 4, "true", 1:32, 44:32, 43:32>>), + "#Ref<1.44.43>" = ref_to_list(Ref3), + "#Ref<1.43.44>" = ref_to_list(Ref4), + true = Ref3 > Ref4, + true = Ref4 > Ref1, + + Ref5 = binary_to_term(<<131, 90, 0, 3, 119, 4, "true", 1:32, 42:32, 43:32, 44:32>>), + Ref6 = binary_to_term(<<131, 90, 0, 3, 119, 4, "true", 1:32, 44:32, 43:32, 42:32>>), + "#Ref<1.44.43.42>" = ref_to_list(Ref5), + "#Ref<1.42.43.44>" = ref_to_list(Ref6), + true = Ref5 > Ref6, + true = Ref6 > Ref3, + + % Starting from OTP-24 that introduced DFLAG_V4_NC, references can have 4 or 5 words + if + HasV4NC -> + Ref7 = binary_to_term( + <<131, 90, 0, 4, 119, 4, "true", 1:32, 41:32, 42:32, 43:32, 44:32>> + ), + Ref8 = binary_to_term( + <<131, 90, 0, 4, 119, 4, "true", 1:32, 44:32, 43:32, 42:32, 41:32>> + ), + "#Ref<1.44.43.42.41>" = ref_to_list(Ref7), + "#Ref<1.41.42.43.44>" = ref_to_list(Ref8), + true = Ref7 > Ref8, + true = Ref8 > Ref5, + + Ref9 = binary_to_term( + <<131, 90, 0, 5, 119, 4, "true", 1:32, 40:32, 41:32, 42:32, 43:32, 44:32>> + ), + RefA = binary_to_term( + <<131, 90, 0, 5, 119, 4, "true", 1:32, 44:32, 43:32, 42:32, 41:32, 40:32>> + ), + "#Ref<1.44.43.42.41.40>" = ref_to_list(Ref9), + "#Ref<1.40.41.42.43.44>" = ref_to_list(RefA), + true = Ref9 > RefA, + true = RefA > Ref7; + true -> + ok + end, + + % Zero-length is tolerated + RefB = binary_to_term(<<131, 90, 0, 0, 119, 4, "true", 1:32>>), + "#Ref<1>" = ref_to_list(RefB), + true = RefB < Ref1, + + % Creation comes before first word in comparison + % 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>>), + RefC = binary_to_term(<<131, 90, 0, 1, 119, 4, "true", 2:32, 42:32>>), + true = RefC < Ref2, + true = RefC > Ref1, + + % Node atom comes first + RefD = binary_to_term(<<131, 90, 0, 1, 119, 5, "false", 1:32, 44:32>>), + "#Ref<0.44>" = ref_to_list(RefD), + true = RefD < Ref1, + + % Local references count like references from node "false" (atom index 0) + LocalRef = make_ref(), + RefE = binary_to_term(<<131, 90, 0, 1, 119, 5, "false", 1:32, 0:32>>), + RefF = binary_to_term(<<131, 90, 0, 1, 119, 4, "true", 1:32, 0:32>>), + true = RefE < LocalRef, + true = RefF > LocalRef, + + % Local ref is sorted at nonode@nohost + case term_to_binary(LocalRef) of + <<131, 90, 0, Count, 119, 13, "nonode@nohost", Rest/binary>> -> + true = LocalRef > binary_to_term(<<131, 90, 0, Count, 119, 5, "false", Rest/binary>>), + true = LocalRef < binary_to_term(<<131, 90, 0, Count, 119, 4, "true", Rest/binary>>), + true = LocalRef > binary_to_term(<<131, 90, 0, Count, 119, 6, "foobar", Rest/binary>>), + true = LocalRef > binary_to_term(<<131, 90, 0, Count, 119, 6, "nonode", Rest/binary>>), + true = LocalRef < binary_to_term(<<131, 90, 0, Count, 119, 6, "nonodf", Rest/binary>>); + <<131, 90, 0, Count, 100, 0, 13, "nonode@nohost", Rest/binary>> -> + true = LocalRef > binary_to_term(<<131, 90, 0, Count, 119, 5, "false", Rest/binary>>), + true = LocalRef < binary_to_term(<<131, 90, 0, Count, 119, 4, "true", Rest/binary>>), + true = LocalRef > binary_to_term(<<131, 90, 0, Count, 119, 6, "foobar", Rest/binary>>), + true = LocalRef > binary_to_term(<<131, 90, 0, Count, 119, 6, "nonode", Rest/binary>>), + true = LocalRef < binary_to_term(<<131, 90, 0, Count, 119, 6, "nonodf", Rest/binary>>); + <<131, 114, 0, Count, 100, 0, 13, "nonode@nohost", Creation, Rest/binary>> -> + true = + LocalRef > + binary_to_term( + <<131, 90, 0, Count, 119, 5, "false", Creation:32, Rest/binary>> + ), + true = + LocalRef < + binary_to_term(<<131, 90, 0, Count, 119, 4, "true", Creation:32, Rest/binary>>), + true = + LocalRef > + binary_to_term( + <<131, 90, 0, Count, 119, 6, "foobar", Creation:32, Rest/binary>> + ), + true = + LocalRef > + binary_to_term( + <<131, 90, 0, Count, 119, 6, "nonode", Creation:32, Rest/binary>> + ), + true = + LocalRef < + binary_to_term( + <<131, 90, 0, Count, 119, 6, "nonodf", Creation:32, Rest/binary>> + ) + end, + + % More than 5 words is not tolerated + ok = + try + binary_to_term( + <<131, 90, 0, 6, 119, 4, "true", 1:32, 2:32, 3:32, 4:32, 5:32, 6:32, 7:32>> + ) + catch + error:badarg -> ok + end, + % Not enough data + ok = + try + binary_to_term(<<131, 90, 0, 5, 119, 4, "true", 1:32, 2:32, 3:32, 4:32, 5:32>>) + catch + error:badarg -> ok + end, + ok = + try + binary_to_term(<<131, 90, 0, 4, 119, 4, "true", 1:32, 2:32, 3:32, 4:32>>) + catch + error:badarg -> ok + end, + ok = + try + binary_to_term(<<131, 90, 0, 3, 119, 4, "true", 1:32, 2:32, 3:32>>) + catch + error:badarg -> ok + end, + ok = + try + binary_to_term(<<131, 90, 0, 2, 119, 4, "true", 1:32, 2:32>>) + catch + error:badarg -> ok + end, + ok = + try + binary_to_term(<<131, 90, 0, 1, 119, 4, "true", 1:32>>) + catch + error:badarg -> ok + end, + ok = + try + binary_to_term(<<131, 90, 0, 0, 119, 4, "true">>) + catch + error:badarg -> ok + end, + + % Node must be an atom + ok = + try + binary_to_term(<<131, 90, 0, 0, 106, 1:32>>) + catch + error:badarg -> ok + end, + + 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),