From ee1158fa1cc4215bdcf81569505201e95132504d Mon Sep 17 00:00:00 2001 From: Paul Guyot Date: Sun, 10 Nov 2024 00:18:06 +0100 Subject: [PATCH] Introduce ports (local and external) Also add bif erlang:is_port/1 which is called by code compiled with OTP-21 Later versions of the compiler use is_port opcode instead. Signed-off-by: Paul Guyot --- src/libAtomVM/bif.c | 9 + src/libAtomVM/bif.h | 1 + src/libAtomVM/bifs.gperf | 1 + src/libAtomVM/context.c | 3 +- src/libAtomVM/externalterm.c | 57 +++++- src/libAtomVM/memory.c | 14 ++ src/libAtomVM/nifs.c | 40 +++- src/libAtomVM/nifs.gperf | 1 + src/libAtomVM/opcodesswitch.h | 15 +- src/libAtomVM/term.c | 48 ++++- src/libAtomVM/term.h | 172 +++++++++++++++--- .../components/avm_builtins/socket_driver.c | 8 +- .../main/test_erl_sources/test_socket.erl | 2 +- .../generic_unix/lib/socket_driver.c | 10 +- tests/erlang_tests/test_binary_to_term.erl | 105 +++++++++++ 15 files changed, 423 insertions(+), 63 deletions(-) diff --git a/src/libAtomVM/bif.c b/src/libAtomVM/bif.c index 579fd16fed..8776014ead 100644 --- a/src/libAtomVM/bif.c +++ b/src/libAtomVM/bif.c @@ -186,6 +186,15 @@ term bif_erlang_is_pid_1(Context *ctx, uint32_t fail_label, term arg1) return term_is_pid(arg1) ? TRUE_ATOM : FALSE_ATOM; } +// Generated by OTP-21 compiler +term bif_erlang_is_port_1(Context *ctx, uint32_t fail_label, term arg1) +{ + UNUSED(ctx); + UNUSED(fail_label); + + return term_is_port(arg1) ? TRUE_ATOM : FALSE_ATOM; +} + term bif_erlang_is_reference_1(Context *ctx, uint32_t fail_label, term arg1) { UNUSED(ctx); diff --git a/src/libAtomVM/bif.h b/src/libAtomVM/bif.h index 5fda0e9475..5cb3d9982b 100644 --- a/src/libAtomVM/bif.h +++ b/src/libAtomVM/bif.h @@ -56,6 +56,7 @@ term bif_erlang_is_integer_1(Context *ctx, uint32_t fail_label, term arg1); term bif_erlang_is_list_1(Context *ctx, uint32_t fail_label, term arg1); term bif_erlang_is_number_1(Context *ctx, uint32_t fail_label, term arg1); term bif_erlang_is_pid_1(Context *ctx, uint32_t fail_label, term arg1); +term bif_erlang_is_port_1(Context *ctx, uint32_t fail_label, term arg1); term bif_erlang_is_reference_1(Context *ctx, uint32_t fail_label, term arg1); term bif_erlang_is_tuple_1(Context *ctx, uint32_t fail_label, term arg1); term bif_erlang_is_map_1(Context *ctx, uint32_t fail_label, term arg1); diff --git a/src/libAtomVM/bifs.gperf b/src/libAtomVM/bifs.gperf index 8c204ee2fd..6e0d83974e 100644 --- a/src/libAtomVM/bifs.gperf +++ b/src/libAtomVM/bifs.gperf @@ -51,6 +51,7 @@ erlang:is_integer/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erla erlang:is_list/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_is_list_1} erlang:is_number/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_is_number_1} erlang:is_pid/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_is_pid_1} +erlang:is_port/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_is_port_1} erlang:is_reference/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_is_reference_1} erlang:is_tuple/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_is_tuple_1} erlang:is_map/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_is_map_1} diff --git a/src/libAtomVM/context.c b/src/libAtomVM/context.c index 561e2f8ec1..7cde2bb495 100644 --- a/src/libAtomVM/context.c +++ b/src/libAtomVM/context.c @@ -440,10 +440,11 @@ static struct ResourceMonitor *context_monitors_handle_terminate(Context *ctx) term_put_tuple_element(info_tuple, 1, ref); if (ctx->native_handler != NULL) { term_put_tuple_element(info_tuple, 2, PORT_ATOM); + term_put_tuple_element(info_tuple, 3, term_port_from_local_process_id(ctx->process_id)); } else { term_put_tuple_element(info_tuple, 2, PROCESS_ATOM); + term_put_tuple_element(info_tuple, 3, term_from_local_process_id(ctx->process_id)); } - term_put_tuple_element(info_tuple, 3, term_from_local_process_id(ctx->process_id)); term_put_tuple_element(info_tuple, 4, ctx->exit_reason); mailbox_send(target, info_tuple); diff --git a/src/libAtomVM/externalterm.c b/src/libAtomVM/externalterm.c index 67913f6a8b..10a3028f79 100644 --- a/src/libAtomVM/externalterm.c +++ b/src/libAtomVM/externalterm.c @@ -51,6 +51,7 @@ #define EXPORT_EXT 113 #define MAP_EXT 116 #define SMALL_ATOM_UTF8_EXT 119 +#define V4_PORT_EXT 120 #define INVALID_TERM_SIZE -1 #define NEW_FLOAT_EXT_SIZE 9 @@ -422,6 +423,31 @@ 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_port(t)) { + if (!IS_NULL_PTR(buf)) { + buf[0] = V4_PORT_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_64_UNALIGNED(buf + k, term_to_local_process_id(t)); + WRITE_32_UNALIGNED(buf + k + 8, creation); // creation + } + return k + 12; + } else if (term_is_external_port(t)) { + if (!IS_NULL_PTR(buf)) { + buf[0] = V4_PORT_EXT; + } + size_t k = 1; + term node = term_get_external_node(t); + k += serialize_term(IS_NULL_PTR(buf) ? NULL : buf + k, node, glb); + if (!IS_NULL_PTR(buf)) { + WRITE_64_UNALIGNED(buf + k, term_get_external_port_number(t)); + 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; @@ -759,6 +785,34 @@ static term parse_external_terms(const uint8_t *external_term_buf, size_t *eterm } } + case V4_PORT_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(); + } + uint64_t number = READ_64_UNALIGNED(external_term_buf + node_size + 1); + 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) { + if (UNLIKELY(number > TERM_MAX_LOCAL_PROCESS_ID)) { + return term_invalid_term(); + } + return term_port_from_local_process_id(number); + } else { + return term_make_external_port_number(node, number, creation, heap); + } + } else { + if (UNLIKELY(number > TERM_MAX_LOCAL_PROCESS_ID || creation != 0)) { + return term_invalid_term(); + } + return term_port_from_local_process_id(number); + } + } + case NEWER_REFERENCE_EXT: { uint16_t len = READ_16_UNALIGNED(external_term_buf + 1); if (UNLIKELY(len > 5)) { @@ -1079,7 +1133,8 @@ static int calculate_heap_usage(const uint8_t *external_term_buf, size_t remaini return 0; } - case NEW_PID_EXT: { + case NEW_PID_EXT: + case V4_PORT_EXT: { if (UNLIKELY(remaining < 1)) { return INVALID_TERM_SIZE; } diff --git a/src/libAtomVM/memory.c b/src/libAtomVM/memory.c index 84dfb6c790..62af25dc40 100644 --- a/src/libAtomVM/memory.c +++ b/src/libAtomVM/memory.c @@ -479,6 +479,9 @@ unsigned long memory_estimate_usage(term t) } else if (term_is_local_pid(t)) { t = temp_stack_pop(&temp_stack); + } else if (term_is_local_port(t)) { + t = temp_stack_pop(&temp_stack); + } else if (term_is_nonempty_list(t)) { acc += 2; if (UNLIKELY(temp_stack_push(&temp_stack, term_get_list_tail(t)) != TempStackOk)) { @@ -624,6 +627,10 @@ static void memory_scan_and_copy(HeapFragment *old_fragment, term *mem_start, co TRACE("- Found external pid.\n"); break; + case TERM_BOXED_EXTERNAL_PORT: + TRACE("- Found external port.\n"); + break; + case TERM_BOXED_EXTERNAL_REF: TRACE("- Found external ref.\n"); break; @@ -751,6 +758,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_PORT: + ptr += EXTERNAL_PORT_SIZE - 1; + break; + case TERM_BOXED_EXTERNAL_REF: ptr += term_get_size_from_boxed_header(t); break; @@ -828,6 +839,9 @@ HOT_FUNC static term memory_shallow_copy_term(HeapFragment *old_fragment, term t } else if (term_is_local_pid(t)) { return t; + } else if (term_is_local_port(t)) { + return t; + } else if (term_is_cp(t)) { // CP is valid only on stack return t; diff --git a/src/libAtomVM/nifs.c b/src/libAtomVM/nifs.c index 72362becf0..aee01ffc9d 100644 --- a/src/libAtomVM/nifs.c +++ b/src/libAtomVM/nifs.c @@ -160,6 +160,7 @@ static term nif_ets_lookup(Context *ctx, int argc, term argv[]); static term nif_ets_lookup_element(Context *ctx, int argc, term argv[]); static term nif_ets_delete(Context *ctx, int argc, term argv[]); static term nif_erlang_pid_to_list(Context *ctx, int argc, term argv[]); +static term nif_erlang_port_to_list(Context *ctx, int argc, term argv[]); static term nif_erlang_ref_to_list(Context *ctx, int argc, term argv[]); static term nif_erlang_fun_to_list(Context *ctx, int argc, term argv[]); static term nif_erlang_function_exported(Context *ctx, int argc, term argv[]); @@ -590,6 +591,12 @@ static const struct Nif pid_to_list_nif = .nif_ptr = nif_erlang_pid_to_list }; +static const struct Nif port_to_list_nif = +{ + .base.type = NIFFunctionType, + .nif_ptr = nif_erlang_port_to_list +}; + static const struct Nif ref_to_list_nif = { .base.type = NIFFunctionType, @@ -992,7 +999,7 @@ static term nif_erlang_open_port_2(Context *ctx, int argc, term argv[]) if (!new_ctx) { RAISE_ERROR(BADARG_ATOM); } else { - return term_from_local_process_id(new_ctx->process_id); + return term_port_from_local_process_id(new_ctx->process_id); } } @@ -1003,7 +1010,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_local_pid); + VALIDATE_VALUE(pid_or_port_term, term_is_local_pid_or_port); int atom_index = term_to_atom_index(reg_name_term); int32_t pid = term_to_local_process_id(pid_or_port_term); @@ -1069,7 +1076,7 @@ static NativeHandlerResult process_echo_mailbox(Context *ctx) } result = NativeTerminate; term reply = term_alloc_tuple(2, &ctx->heap); - term_put_tuple_element(reply, 0, term_from_local_process_id(ctx->process_id)); + term_put_tuple_element(reply, 0, term_port_from_local_process_id(ctx->process_id)); term_put_tuple_element(reply, 1, CLOSED_ATOM); port_send_message(ctx->global, pid, reply); } else { @@ -1101,7 +1108,7 @@ static NativeHandlerResult process_console_message(Context *ctx, term msg) result = NativeTerminate; term pid = term_get_tuple_element(msg, 0); term reply = term_alloc_tuple(2, &ctx->heap); - term_put_tuple_element(reply, 0, term_from_local_process_id(ctx->process_id)); + term_put_tuple_element(reply, 0, term_port_from_local_process_id(ctx->process_id)); term_put_tuple_element(reply, 1, CLOSED_ATOM); port_send_message(ctx->global, pid, reply); } else if (is_tagged_tuple(msg, IO_REQUEST_ATOM, 4)) { @@ -1407,7 +1414,7 @@ static term nif_erlang_send_2(Context *ctx, int argc, term argv[]) term target = argv[0]; GlobalContext *glb = ctx->global; - if (term_is_local_pid(target)) { + if (term_is_local_pid_or_port(target)) { int32_t local_process_id = term_to_local_process_id(target); globalcontext_send_message(glb, local_process_id, argv[1]); @@ -3487,6 +3494,23 @@ static term nif_erlang_pid_to_list(Context *ctx, int argc, term argv[]) return make_list_from_ascii_buf((uint8_t *) buf, str_len, ctx); } +static term nif_erlang_port_to_list(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + term t = argv[0]; + VALIDATE_VALUE(t, term_is_port); + size_t max_len = term_is_external(t) ? EXTERNAL_PORT_AS_CSTRING_LEN : LOCAL_PORT_AS_CSTRING_LEN; + + char buf[max_len]; + int str_len = term_snprint(buf, max_len, t, ctx->global); + if (UNLIKELY(str_len < 0)) { + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + + return make_list_from_ascii_buf((uint8_t *) buf, str_len, ctx); +} + static term nif_erlang_ref_to_list(Context *ctx, int argc, term argv[]) { UNUSED(argc); @@ -3747,7 +3771,7 @@ static term nif_erlang_monitor(Context *ctx, int argc, term argv[]) RAISE_ERROR(BADARG_ATOM); } - VALIDATE_VALUE(target_pid, term_is_local_pid); + VALIDATE_VALUE(target_pid, term_is_local_pid_or_port); int local_process_id = term_to_local_process_id(target_pid); Context *target = globalcontext_get_process_lock(ctx->global, local_process_id); @@ -3815,7 +3839,7 @@ static term nif_erlang_link(Context *ctx, int argc, term argv[]) term target_pid = argv[0]; - VALIDATE_VALUE(target_pid, term_is_local_pid); + VALIDATE_VALUE(target_pid, term_is_local_pid_or_port); int local_process_id = term_to_local_process_id(target_pid); Context *target = globalcontext_get_process_lock(ctx->global, local_process_id); @@ -3846,7 +3870,7 @@ static term nif_erlang_unlink(Context *ctx, int argc, term argv[]) term target_pid = argv[0]; - VALIDATE_VALUE(target_pid, term_is_local_pid); + VALIDATE_VALUE(target_pid, term_is_local_pid_or_port); int local_process_id = term_to_local_process_id(target_pid); Context *target = globalcontext_get_process_lock(ctx->global, local_process_id); diff --git a/src/libAtomVM/nifs.gperf b/src/libAtomVM/nifs.gperf index 898b37ad46..199aeb3d55 100644 --- a/src/libAtomVM/nifs.gperf +++ b/src/libAtomVM/nifs.gperf @@ -117,6 +117,7 @@ erlang:throw/1, &throw_nif erlang:raise/3, &raise_nif erlang:unlink/1, &unlink_nif erlang:pid_to_list/1, &pid_to_list_nif +erlang:port_to_list/1, &port_to_list_nif erlang:ref_to_list/1, &ref_to_list_nif erlang:fun_to_list/1, &fun_to_list_nif erlang:function_exported/3, &function_exported_nif diff --git a/src/libAtomVM/opcodesswitch.h b/src/libAtomVM/opcodesswitch.h index 64fbfeea3c..3c7b8dae96 100644 --- a/src/libAtomVM/opcodesswitch.h +++ b/src/libAtomVM/opcodesswitch.h @@ -2404,7 +2404,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_local_pid(recipient_term)) { + if (term_is_local_pid_or_port(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)); @@ -3004,18 +3004,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_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; - if (target) { - is_port_driver = context_is_port_driver(target); - globalcontext_get_process_unlock(ctx->global, target); - } - if (!is_port_driver) { - pc = mod->labels[label]; - } - } else { + if (!term_is_port(arg1)) { pc = mod->labels[label]; } #endif diff --git a/src/libAtomVM/term.c b/src/libAtomVM/term.c index 5ae5f5cba2..4e7207f81c 100644 --- a/src/libAtomVM/term.c +++ b/src/libAtomVM/term.c @@ -202,6 +202,16 @@ int term_funprint(PrinterFun *fun, term t, const GlobalContext *global) // creation is not printed return fun->print(fun, "<%" PRIu32 ".%" PRIu32 ".%" PRIu32 ">", node_atom_index, number, serial); + } else if (term_is_local_port(t)) { + int32_t process_id = term_to_local_process_id(t); + return fun->print(fun, "#Port<0.%" PRIu32 ".0>", process_id); + + } else if (term_is_external_port(t)) { + uint32_t node_atom_index = term_to_atom_index(term_get_external_node(t)); + uint64_t number = term_get_external_port_number(t); + // creation is not printed + return fun->print(fun, "#Port<%" PRIu32 ".%" PRIu64 ">", node_atom_index, number); + } else if (term_is_function(t)) { const term *boxed_value = term_to_const_term_ptr(t); @@ -448,24 +458,27 @@ static int term_type_to_index(term t) } else if (term_is_function(t)) { return 5; - } else if (term_is_pid(t)) { + } else if (term_is_port(t)) { return 6; - } else if (term_is_tuple(t)) { + } else if (term_is_pid(t)) { return 7; - } else if (term_is_nil(t)) { + } else if (term_is_tuple(t)) { return 8; - } else if (term_is_nonempty_list(t)) { + } else if (term_is_nil(t)) { return 9; - } else if (term_is_binary(t)) { + } else if (term_is_nonempty_list(t)) { return 10; - } else if (term_is_map(t)) { + } else if (term_is_binary(t)) { return 11; + } else if (term_is_map(t)) { + return 12; + } else { AVM_ABORT(); } @@ -770,6 +783,29 @@ TermCompareResult term_compare(term t, term other, TermCompareOpts opts, GlobalC result = (process_id > other_process_id) ? TermGreaterThan : TermLessThan; break; } + } else if (term_is_port(t) && term_is_port(other)) { + 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) { + uint64_t port_number = term_is_external(t) ? term_get_external_port_number(t) : (uint64_t) term_to_local_process_id(t); + uint64_t other_port_number = term_is_external(other) ? term_get_external_port_number(other) : (uint64_t) term_to_local_process_id(other); + if (port_number == other_port_number) { + CMP_POP_AND_CONTINUE(); + } else { + result = (port_number > other_port_number) ? TermGreaterThan : TermLessThan; + break; + } + } else { + result = (creation > other_creation) ? TermGreaterThan : TermLessThan; + break; + } + } else { + t = node; + other = other_node; + } } 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 984309fa48..e6fd008bb0 100644 --- a/src/libAtomVM/term.h +++ b/src/libAtomVM/term.h @@ -45,8 +45,11 @@ extern "C" { #endif #define TERM_BOXED_VALUE_TAG 0x2 + +#define TERM_IMMED_TAG_MASK 0xF +#define TERM_PID_TAG 0x3 +#define TERM_PORT_TAG 0x7 #define TERM_INTEGER_TAG 0xF -#define TERM_CATCH_TAG 0x1B #define TERM_BOXED_TAG_MASK 0x3F #define TERM_BOXED_TUPLE 0x0 @@ -55,12 +58,14 @@ extern "C" { #define TERM_BOXED_REF 0x10 #define TERM_BOXED_FUN 0x14 #define TERM_BOXED_FLOAT 0x18 +#define TERM_CATCH_TAG 0x1B #define TERM_BOXED_REFC_BINARY 0x20 #define TERM_BOXED_HEAP_BINARY 0x24 #define TERM_BOXED_SUB_BINARY 0x28 #define TERM_BOXED_MAP 0x2C #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 @@ -80,6 +85,8 @@ extern "C" { #error #endif +#define TERM_MAX_LOCAL_PROCESS_ID ((1 << 28) - 1) + #define BINARY_HEADER_SIZE 2 #define FUNCTION_REFERENCE_SIZE 4 #define BOXED_INT_SIZE (BOXED_TERMS_REQUIRED_FOR_INT + 1) @@ -94,6 +101,7 @@ extern "C" { #else #error #endif +#define EXTERNAL_PORT_SIZE EXTERNAL_PID_SIZE #if TERM_BYTES == 8 #define EXTERNAL_REF_SIZE(words) (3 + (words / 2)) #elif TERM_BYTES == 4 @@ -138,6 +146,15 @@ extern "C" { #define REF_AS_CSTRING_LEN 70 // Local pid is at most 16 bytes: +// 2^28-1 = 268435455 (9 chars) +// "#Port<0." ">\0" (10 chars) +#define LOCAL_PORT_AS_CSTRING_LEN 19 + +// 2^26-1 = 67108863 (8 chars) (node, atom index) +// 2^64-1 = 18446744073709551615 (20 chars) +// "#Port<" "." ">\0" (9 chars) +#define EXTERNAL_PORT_AS_CSTRING_LEN 37 + // 2^28-1 = 268435455 (9 chars) // "<0." ".0>\0" (7 chars) // External pid is at most 32 bytes: @@ -267,7 +284,7 @@ static inline const term *term_to_const_term_ptr(term t) static inline bool term_is_atom(term t) { /* atom: | atom index | 00 10 11 */ - return ((t & 0x3F) == 0xB); + return ((t & TERM_BOXED_TAG_MASK) == 0xB); } /** @@ -292,7 +309,7 @@ static inline bool term_is_invalid_term(term t) static inline bool term_is_nil(term t) { /* nil: 11 10 11 */ - return ((t & 0x3F) == 0x3B); + return ((t & TERM_BOXED_TAG_MASK) == 0x3B); } /** @@ -466,7 +483,7 @@ static inline bool term_is_sub_binary(term t) static inline bool term_is_integer(term t) { /* integer: 11 11 */ - return ((t & 0xF) == 0xF); + return ((t & TERM_IMMED_TAG_MASK) == TERM_INTEGER_TAG); } /** @@ -478,7 +495,7 @@ static inline bool term_is_integer(term t) */ static inline bool term_is_uint8(term t) { - return ((t & ~((term) 0xFF0)) == 0xF); + return ((t & ~((term) 0xFF0)) == TERM_INTEGER_TAG); } static inline bool term_is_boxed_integer(term t) @@ -500,7 +517,7 @@ static inline bool term_is_any_integer(term t) static inline bool term_is_catch_label(term t) { - return (t & 0x3F) == TERM_CATCH_TAG; + return (t & TERM_BOXED_TAG_MASK) == TERM_CATCH_TAG; } /** @@ -513,7 +530,7 @@ static inline bool term_is_catch_label(term t) static inline bool term_is_local_pid(term t) { /* integer: 00 11 */ - return ((t & 0xF) == 0x3); + return ((t & TERM_IMMED_TAG_MASK) == TERM_PID_TAG); } /** @@ -527,7 +544,7 @@ 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) { + if ((boxed_value[0] & TERM_BOXED_TAG_MASK) == TERM_BOXED_EXTERNAL_PID) { return true; } } @@ -566,6 +583,62 @@ 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 a local port + * + * @details Returns \c true if a term is a local 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_local_port(term t) +{ + /* integer: 01 11 */ + return ((t & TERM_IMMED_TAG_MASK) == TERM_PORT_TAG); +} + +/** + * @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] & TERM_BOXED_TAG_MASK) == TERM_BOXED_EXTERNAL_PORT) { + return true; + } + } + + return false; +} + +/** + * @brief Checks if a term is a port + * + * @details Returns \c true if a term is a 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_port(term t) +{ + return term_is_local_port(t) || term_is_external_port(t); +} + +/** + * @brief Checks if a term is a local port or a local pid + * + * @details Returns \c true if a term is a local port or a local 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_local_pid_or_port(term t) +{ + return term_is_local_pid(t) || term_is_local_port(t); +} + /** * @brief Checks if a term is a tuple * @@ -577,7 +650,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) == TERM_BOXED_TUPLE) { + if ((boxed_value[0] & TERM_BOXED_TAG_MASK) == TERM_BOXED_TUPLE) { return true; } } @@ -647,7 +720,7 @@ static inline bool term_is_function(term t) { if (term_is_boxed(t)) { const term *boxed_value = term_to_const_term_ptr(t); - if ((boxed_value[0] & 0x3F) == TERM_BOXED_FUN) { + if ((boxed_value[0] & TERM_BOXED_TAG_MASK) == TERM_BOXED_FUN) { return true; } } @@ -788,7 +861,7 @@ static inline int term_to_catch_label_and_module(term t, int *module_index) } /** - * @brief Gets process table index + * @brief Gets process table index for a local pid or port * * @details Returns local process table index for given atom term. * @param t the term that will be converted to local process table index, term type is checked. @@ -796,7 +869,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_local_pid(t)); + TERM_DEBUG_ASSERT(term_is_local_pid(t) || term_is_local_port(t)); return t >> 4; } @@ -810,7 +883,7 @@ static inline int32_t term_to_local_process_id(term t) */ static inline term term_from_int4(int8_t value) { - return (value << 4) | 0xF; + return (value << 4) | TERM_INTEGER_TAG; } /** @@ -822,7 +895,7 @@ static inline term term_from_int4(int8_t value) */ static inline term term_from_int11(int16_t value) { - return (value << 4) | 0xF; + return (value << 4) | TERM_INTEGER_TAG; } /** @@ -842,11 +915,11 @@ static inline term term_from_int32(int32_t value) AVM_ABORT(); } else { - return (value << 4) | 0xF; + return (value << 4) | TERM_INTEGER_TAG; } #elif TERM_BITS == 64 - return (value << 4) | 0xF; + return (value << 4) | TERM_INTEGER_TAG; #else #error "Wrong TERM_BITS define" @@ -863,7 +936,7 @@ static inline term term_from_int64(int64_t value) AVM_ABORT(); } else { - return (value << 4) | 0xF; + return (value << 4) | TERM_INTEGER_TAG; } #elif TERM_BITS == 64 @@ -874,7 +947,7 @@ static inline term term_from_int64(int64_t value) AVM_ABORT(); } else { - return (value << 4) | 0xF; + return (value << 4) | TERM_INTEGER_TAG; } #else @@ -884,7 +957,7 @@ static inline term term_from_int64(int64_t value) static inline term term_from_int(avm_int_t value) { - return (value << 4) | 0xF; + return (value << 4) | TERM_INTEGER_TAG; } static inline avm_int_t term_unbox_int(term boxed_int) @@ -1014,7 +1087,19 @@ static inline term term_from_catch_label(unsigned int module_index, unsigned int */ static inline term term_from_local_process_id(uint32_t local_process_id) { - return (local_process_id << 4) | 0x3; + return (local_process_id << 4) | TERM_PID_TAG; +} + +/** + * @brief Port term from local process id + * + * @details Returns a term for a given local process table index. + * @param local_process_id the local process table index that will be converted to a term. + * @return a term that encapsulates a PID. + */ +static inline term term_port_from_local_process_id(uint32_t local_process_id) +{ + return (local_process_id << 4) | TERM_PORT_TAG; } /** @@ -1347,6 +1432,30 @@ static inline term term_make_external_process_id(term node, uint32_t process_id, return ((term) boxed_value) | TERM_BOXED_VALUE_TAG; } +/** + * @brief Get a port term from node, number and creation + * + * @param node name of the node (atom) + * @param number port number 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_make_external_port_number(term node, uint64_t number, uint32_t creation, Heap *heap) +{ + term *boxed_value = memory_heap_alloc(heap, EXTERNAL_PORT_SIZE); + int atom_index = term_to_atom_index(node); + boxed_value[0] = ((EXTERNAL_PORT_SIZE - 1) << 6) | TERM_BOXED_EXTERNAL_PORT; + uint32_t *external_thing_words = (uint32_t *) &boxed_value[1]; + + external_thing_words[0] = atom_index; + external_thing_words[1] = creation; + external_thing_words[2] = number >> 32; + external_thing_words[3] = (uint32_t) number; + + return ((term) boxed_value) | TERM_BOXED_VALUE_TAG; +} + /** * @brief Get the name of a node for a given external thing * @@ -1407,7 +1516,22 @@ static inline uint32_t term_get_external_pid_serial(term t) return external_thing_words[3]; } -/* +/** + * @brief Get the port number of an external port + * + * @param term external port + * @return the port number of the external port + */ +static inline uint64_t term_get_external_port_number(term t) +{ + TERM_DEBUG_ASSERT(term_is_external_port(t)); + + const term *boxed_value = term_to_const_term_ptr(t); + const uint32_t *external_thing_words = (const uint32_t *) &boxed_value[1]; + return (((uint64_t) external_thing_words[2]) << 32) | (uint64_t) 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) @@ -1528,7 +1652,7 @@ static inline void term_put_tuple_element(term t, uint32_t elem_index, term put_ term *boxed_value = term_to_term_ptr(t); - TERM_DEBUG_ASSERT(((boxed_value[0] & 0x3F) == 0) && (elem_index < (boxed_value[0] >> 6))); + TERM_DEBUG_ASSERT(((boxed_value[0] & TERM_BOXED_TAG_MASK) == 0) && (elem_index < (boxed_value[0] >> 6))); boxed_value[elem_index + 1] = put_value; } @@ -1547,7 +1671,7 @@ static inline term term_get_tuple_element(term t, int elem_index) const term *boxed_value = term_to_const_term_ptr(t); - TERM_DEBUG_ASSERT(((boxed_value[0] & 0x3F) == 0) && (elem_index < (boxed_value[0] >> 6))); + TERM_DEBUG_ASSERT(((boxed_value[0] & TERM_BOXED_TAG_MASK) == 0) && (elem_index < (boxed_value[0] >> 6))); return boxed_value[elem_index + 1]; } @@ -1824,7 +1948,7 @@ static inline bool term_is_match_state(term t) { if (term_is_boxed(t)) { const term *boxed_value = term_to_const_term_ptr(t); - if ((boxed_value[0] & 0x3F) == TERM_BOXED_BIN_MATCH_STATE) { + if ((boxed_value[0] & TERM_BOXED_TAG_MASK) == TERM_BOXED_BIN_MATCH_STATE) { return true; } } diff --git a/src/platforms/esp32/components/avm_builtins/socket_driver.c b/src/platforms/esp32/components/avm_builtins/socket_driver.c index 585c02a2e1..6cec74abb8 100644 --- a/src/platforms/esp32/components/avm_builtins/socket_driver.c +++ b/src/platforms/esp32/components/avm_builtins/socket_driver.c @@ -515,7 +515,7 @@ static void accept_conn(Context *ctx, struct TCPServerSocketData *tcp_data, uint Context *new_ctx = context_new(glb); new_ctx->native_handler = socket_consume_mailbox; - term socket_pid = term_from_local_process_id(new_ctx->process_id); + term socket_pid = term_port_from_local_process_id(new_ctx->process_id); struct TCPClientSocketData *new_tcp_data = tcp_client_socket_data_new(new_ctx, accepted_conn, sockets, pid); socket_data_postinit(platform); @@ -585,7 +585,7 @@ static void do_send_socket_error(Context *ctx, err_t status) term reason_atom = lwip_error_atom(glb, status); term result_tuple = term_alloc_tuple(3, &ctx->heap); term_put_tuple_element(result_tuple, 0, globalcontext_make_atom(glb, tcp_error_atom)); - term socket_pid = term_from_local_process_id(ctx->process_id); + term socket_pid = term_port_from_local_process_id(ctx->process_id); term socket_wrapper = create_tcp_socket_wrapper(socket_pid, &ctx->heap, glb); term_put_tuple_element(result_tuple, 1, socket_wrapper); term_put_tuple_element(result_tuple, 2, reason_atom); @@ -609,7 +609,7 @@ static void do_send_tcp_closed(Context *ctx) } term result_tuple = term_alloc_tuple(2, &ctx->heap); term_put_tuple_element(result_tuple, 0, TCP_CLOSED_ATOM); - term socket_pid = term_from_local_process_id(ctx->process_id); + term socket_pid = term_port_from_local_process_id(ctx->process_id); term socket_wrapper = create_tcp_socket_wrapper(socket_pid, &ctx->heap, glb); term_put_tuple_element(result_tuple, 1, socket_wrapper); globalcontext_send_message(glb, socket_data->controlling_process_pid, result_tuple); @@ -750,7 +750,7 @@ static NativeHandlerResult do_receive_data(Context *ctx) if (socket_data->active) { term active_tuple = term_alloc_tuple(socket_data->type == TCPClientSocket ? 3 : 5, &ctx->heap); term_put_tuple_element(active_tuple, 0, socket_data->type == TCPClientSocket ? TCP_ATOM : UDP_ATOM); - term socket_pid = term_from_local_process_id(ctx->process_id); + term socket_pid = term_port_from_local_process_id(ctx->process_id); term socket_wrapper = socket_data->type == UDPSocket ? create_udp_socket_wrapper(socket_pid, &ctx->heap, ctx->global) : diff --git a/src/platforms/esp32/test/main/test_erl_sources/test_socket.erl b/src/platforms/esp32/test/main/test_erl_sources/test_socket.erl index 29b80d2489..12974b037c 100644 --- a/src/platforms/esp32/test/main/test_erl_sources/test_socket.erl +++ b/src/platforms/esp32/test/main/test_erl_sources/test_socket.erl @@ -217,7 +217,7 @@ test_tcp_server(Active, Port) -> link(Pid), {ok, ConnectedSocket} = case call(ServerSocket, {accept, 30000}) of - {ok, Socket} when is_pid(Socket) -> + {ok, Socket} when is_port(Socket) -> {ok, Socket}; X -> {unexpected_accept, X} diff --git a/src/platforms/generic_unix/lib/socket_driver.c b/src/platforms/generic_unix/lib/socket_driver.c index ebf52e83dc..b582ad1ff1 100644 --- a/src/platforms/generic_unix/lib/socket_driver.c +++ b/src/platforms/generic_unix/lib/socket_driver.c @@ -722,7 +722,7 @@ static EventListener *active_recv_callback(GlobalContext *glb, EventListener *ba // {tcp_closed, {Moniker :: atom(), Socket :: pid(), Module :: module()}} BEGIN_WITH_STACK_HEAP(TUPLE_SIZE(2) + TUPLE_SIZE(3), heap); term pid = socket_data->controlling_process; - term socket_pid = term_from_local_process_id(ctx->process_id); + term socket_pid = term_port_from_local_process_id(ctx->process_id); term socket_wrapper = create_tcp_socket_wrapper(socket_pid, &heap, glb); term msgs[2] = { TCP_CLOSED_ATOM, socket_wrapper }; term msg = port_heap_create_tuple_n(&heap, 2, msgs); @@ -749,7 +749,7 @@ static EventListener *active_recv_callback(GlobalContext *glb, EventListener *ba } term pid = socket_data->controlling_process; term packet = socket_create_packet_term(buf, len, socket_data->binary, &heap, glb); - term socket_pid = term_from_local_process_id(ctx->process_id); + term socket_pid = term_port_from_local_process_id(ctx->process_id); term socket_wrapper = create_tcp_socket_wrapper(socket_pid, &heap, glb); term msgs[3] = { TCP_ATOM, socket_wrapper, packet }; term msg = port_heap_create_tuple_n(&heap, 3, msgs); @@ -868,7 +868,7 @@ static EventListener *active_recvfrom_callback(GlobalContext *glb, EventListener // {udp, {Moniker :: atom(), Socket :: pid(), Module :: module()}, {error, {SysCall, Errno}}} BEGIN_WITH_STACK_HEAP(TUPLE_SIZE(3) + TUPLE_SIZE(3) + TUPLE_SIZE(2) + TUPLE_SIZE(2), heap); term pid = socket_data->controlling_process; - term socket_pid = term_from_local_process_id(ctx->process_id); + term socket_pid = term_port_from_local_process_id(ctx->process_id); // printf("Sending tcp_closed wrapper to %i\n", ctx->process_id); term socket_wrapper = create_udp_socket_wrapper(socket_pid, &heap, glb); term msgs[3] = { UDP_ATOM, socket_wrapper, port_heap_create_sys_error_tuple(&heap, RECVFROM_ATOM, errno) }; @@ -894,7 +894,7 @@ static EventListener *active_recvfrom_callback(GlobalContext *glb, EventListener term addr = socket_heap_tuple_from_addr(&heap, htonl(clientaddr.sin_addr.s_addr)); term port = term_from_int32(htons(clientaddr.sin_port)); term packet = socket_create_packet_term(buf, len, socket_data->binary, &heap, glb); - term socket_pid = term_from_local_process_id(ctx->process_id); + term socket_pid = term_port_from_local_process_id(ctx->process_id); term socket_wrapper = create_udp_socket_wrapper(socket_pid, &heap, glb); term msgs[5] = { UDP_ATOM, socket_wrapper, addr, port, packet }; term msg = port_heap_create_tuple_n(&heap, 5, msgs); @@ -1072,7 +1072,7 @@ static EventListener *accept_callback(GlobalContext *glb, EventListener *base_li } // {Ref, Socket} - term socket_pid = term_from_local_process_id(new_ctx->process_id); + term socket_pid = term_port_from_local_process_id(new_ctx->process_id); BEGIN_WITH_STACK_HEAP(10, heap); term ref = term_from_ref_ticks(listener->ref_ticks, &heap); term payload = port_heap_create_ok_tuple(&heap, socket_pid); diff --git a/tests/erlang_tests/test_binary_to_term.erl b/tests/erlang_tests/test_binary_to_term.erl index ed74fcb9be..127e153147 100644 --- a/tests/erlang_tests/test_binary_to_term.erl +++ b/tests/erlang_tests/test_binary_to_term.erl @@ -29,6 +29,7 @@ get_binary/1, test_atom_decoding_checks/0, test_encode_pid/0, + test_encode_port/0, id/1 ]). @@ -175,6 +176,7 @@ start() -> ok = test_atom_decoding_checks(), ok = test_encode_pid(), ok = test_encode_reference(), + ok = test_encode_port(), 0. test_reverse(T, Interop) -> @@ -602,6 +604,109 @@ do_unsetnode({NetKernelPid, MonitorRef}) -> end, ok. +test_encode_port() -> + TestPort = open_port({spawn, "echo"}, []), + Bin = term_to_binary(TestPort), + TestPort = binary_to_term(Bin), + true = is_port(TestPort), + {ExpectedSize, SupportsV4PortEncoding} = + case erlang:system_info(machine) of + "ATOM" -> + % small utf8 atom + {29, true}; + "BEAM" -> + OTPRelease = erlang:system_info(otp_release), + if + OTPRelease < "23" -> {23, false}; + OTPRelease < "24" -> {26, false}; + % v4 is supported but not the default + OTPRelease < "26" -> {26, true}; + % small utf8 atom + true -> {29, true} + end + end, + ExpectedSize = byte_size(Bin), + case SupportsV4PortEncoding of + true -> + true = is_port( + binary_to_term( + <<131, 120, 119, 13, "nonode@nohost", 1:64, 0:32>> + ) + ), + Port1 = binary_to_term(<<131, 120, 119, 4, "true", 43:64, 0:32>>), + Port2 = binary_to_term(<<131, 120, 119, 4, "true", 43:64, 1:32>>), + false = Port1 =:= Port2, + true = Port1 < Port2, + "#Port<1.43>" = port_to_list(Port1), + "#Port<1.43>" = port_to_list(Port2), + + % Order + FalsePort_42_43 = binary_to_term_idempotent( + <<131, 120, 119, 5, "false", 42:64, 43:32>>, "26" + ), + FalsePort_43_42 = binary_to_term_idempotent( + <<131, 120, 119, 5, "false", 43:64, 42:32>>, "26" + ), + FalsePort_43_43 = binary_to_term_idempotent( + <<131, 120, 119, 5, "false", 43:64, 43:32>>, "26" + ), + FalsfPort_42_41 = binary_to_term_idempotent( + <<131, 120, 119, 5, "falsf", 42:64, 41:32>>, "26" + ), + + % Node first, creation second, number third + true = FalsePort_42_43 > FalsePort_43_42, + true = FalsfPort_42_41 > FalsePort_42_43, + true = FalsfPort_42_41 > FalsePort_43_42, + true = FalsePort_43_42 < FalsePort_43_43, + + % Order is done by node atom, with local pid as nonode@nohost + true = + TestPort > + binary_to_term_idempotent(<<131, 120, 119, 5, "false", 1:64, 0:32>>, "26"), + true = + TestPort > + binary_to_term_idempotent(<<131, 120, 119, 6, "nonode", 1:64, 0:32>>, "26"), + true = + TestPort < + binary_to_term_idempotent(<<131, 120, 119, 6, "nonodf", 1:64, 0:32>>, "26"), + + ok; + false -> + ok + end, + case has_setnode_creation() of + true -> + % Test distributed ports + % Test doesn't pass on BEAM if we use 42 and 43 like for refs, + % as there probably is a side-effect we don't have + Ref42 = do_setnode(test@test_node, 1042), + DistributedBin42 = term_to_binary(TestPort), + true = DistributedBin42 =/= Bin, + TestRef42 = binary_to_term(DistributedBin42), + true = TestRef42 =:= TestPort, + ExpectedSize = byte_size(DistributedBin42) - 1, + + ok = do_unsetnode(Ref42), + + Ref43 = do_setnode(test@test_node, 1043), + DistributedBin43 = term_to_binary(TestPort), + true = DistributedBin43 =/= DistributedBin42, + TestRef43 = binary_to_term(DistributedBin43), + true = TestRef43 =:= TestPort, + ExpectedSize = byte_size(DistributedBin43) - 1, + + % If our creation is 1043, encoded binary with creation 1042 is a different port + TestRef42_43 = binary_to_term(DistributedBin42), + false = TestRef42_43 =:= TestPort, + + ok = do_unsetnode(Ref43), + ok; + false -> + ok + end, + ok. + test_encode_reference() -> TestRef = make_ref(), Bin = term_to_binary(TestRef),