From efe74c8c26a2641c7f704ebd5da0a17429d75091 Mon Sep 17 00:00:00 2001 From: Davide Bettio Date: Wed, 21 Feb 2024 00:23:23 +0100 Subject: [PATCH 1/4] Add support to functions with >= 10 parameters Fixes #993. Signed-off-by: Davide Bettio --- CHANGELOG.md | 1 + src/libAtomVM/atom.c | 39 +++++++++++++++++++++++++++++++++++---- src/libAtomVM/nifs.c | 3 ++- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f69e30751..58d698665 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `binary_to_term` checks atom encoding validity, and fix latin1 support (when non-ASCII chars are used) - ESP32: fixed bug in `gpio:set_pin_mode/2` and `gpio:set_direction/3` that would accept any atom for the mode parameter without an error. +- Support to function with 10 or more parameters ### Changed diff --git a/src/libAtomVM/atom.c b/src/libAtomVM/atom.c index 420730e33..102e93594 100644 --- a/src/libAtomVM/atom.c +++ b/src/libAtomVM/atom.c @@ -54,6 +54,39 @@ int atom_are_equals(AtomString a, AtomString b) } } +static void utoa10(unsigned int int_value, char *integer_string) +{ + /* + Generic version: + + size_t integer_string_len = 0; + unsigned int v = int_value; + do { + v = v / 10; + integer_string_len++; + } while (v != 0); + */ + + // let's avoid divisions + size_t integer_string_len = 1; + if (UNLIKELY(integer_string_len >= 1000)) { + integer_string_len = 4; + } else if (UNLIKELY(integer_string_len >= 100)) { + integer_string_len = 3; + } else if (integer_string_len >= 10) { + integer_string_len = 2; + } + + int ix = 1; + do { + unsigned int digit = int_value % 10; + integer_string[integer_string_len - ix] = '0' + digit; + int_value = int_value / 10; + ix++; + } while (int_value != 0); + integer_string[integer_string_len] = '\0'; +} + void atom_write_mfa(char *buf, size_t buf_size, AtomString module, AtomString function, unsigned int arity) { size_t module_name_len = atom_string_len(module); @@ -62,14 +95,12 @@ void atom_write_mfa(char *buf, size_t buf_size, AtomString module, AtomString fu buf[module_name_len] = ':'; size_t function_name_len = atom_string_len(function); - if (UNLIKELY((arity > 9) || (module_name_len + function_name_len + 4 > buf_size))) { + if (UNLIKELY((module_name_len + 1 + function_name_len + 1 + 4 + 1 > buf_size))) { fprintf(stderr, "Insufficient room to write mfa.\n"); AVM_ABORT(); } memcpy(buf + module_name_len + 1, atom_string_data(function), function_name_len); - // TODO: handle functions with more than 9 parameters buf[module_name_len + function_name_len + 1] = '/'; - buf[module_name_len + function_name_len + 2] = (char) ('0' + arity); - buf[module_name_len + function_name_len + 3] = 0; + utoa10(arity, buf + module_name_len + 1 + function_name_len + 1); } diff --git a/src/libAtomVM/nifs.c b/src/libAtomVM/nifs.c index 89717cfaa..8d595a1a0 100644 --- a/src/libAtomVM/nifs.c +++ b/src/libAtomVM/nifs.c @@ -791,7 +791,8 @@ const struct Nif *nifs_get(AtomString module, AtomString function, int arity) int function_name_len = atom_string_len(function); if (UNLIKELY((arity > 9) || (module_name_len + function_name_len + 4 > MAX_NIF_NAME_LEN))) { - AVM_ABORT(); + // In AtomVM NIFs are limited to 9 parameters + return NULL; } memcpy(nifname + module_name_len + 1, atom_string_data(function), function_name_len); From cd3bdd2ef3e454dc5b1483a655426245262ca440 Mon Sep 17 00:00:00 2001 From: Davide Bettio Date: Sat, 24 Feb 2024 12:22:02 +0100 Subject: [PATCH 2/4] Fix unlikely corruption when live registers are 16 with some opcodes x_regs[live] might cause a corruption when live = 16, since MAX_REG is 16. Signed-off-by: Davide Bettio --- CHANGELOG.md | 1 + src/libAtomVM/context.h | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58d698665..eb7541694 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 used) - ESP32: fixed bug in `gpio:set_pin_mode/2` and `gpio:set_direction/3` that would accept any atom for the mode parameter without an error. - Support to function with 10 or more parameters +- Very unlikely but possible corruption caused by generated code that uses 16 live registers ### Changed diff --git a/src/libAtomVM/context.h b/src/libAtomVM/context.h index 2606ff3b9..83e744a27 100644 --- a/src/libAtomVM/context.h +++ b/src/libAtomVM/context.h @@ -76,6 +76,8 @@ enum HeapGrowthStrategy // Max number of x(N) & fr(N) registers // BEAM sets this to 1024. +// x regs have an additional register that is used for storing an additional working element +// so the number of registers is MAX_REG + 1, but only MAX_REG are addressable as x registers #define MAX_REG 16 struct Context @@ -84,7 +86,7 @@ struct Context GlobalContext *global; Heap heap; term *e; - term x[MAX_REG]; + term x[MAX_REG + 1]; struct ListHead processes_list_head; From 31960dd5356cca4782c1bc48a37e5e603b281c0a Mon Sep 17 00:00:00 2001 From: Davide Bettio Date: Sat, 24 Feb 2024 12:22:46 +0100 Subject: [PATCH 3/4] Support more than 16 live x registers The compiler might emit code that uses more than 16 registers, such as when calling a function with 17 or more parameters, or when doing some advanced pattern matching on several items. Fixes #698. Signed-off-by: Davide Bettio --- CHANGELOG.md | 2 + src/libAtomVM/context.c | 3 + src/libAtomVM/context.h | 8 ++ src/libAtomVM/memory.c | 7 + src/libAtomVM/opcodesswitch.h | 236 +++++++++++++++++++++++++++------- 5 files changed, 206 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb7541694..b4ad5df02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support for utf8 encoding to `*_to_atom` and `atom_to_*` functions - `binary_to_atom/1` and `atom_to_binary/1` that default to utf8 (they were introduced with OTP23) - Added Pico cmake option `AVM_WAIT_BOOTSEL_ON_EXIT` (default `ON`) to allow tools to use automated `BOOTSEL` mode after main application exits +- Support for code that makes use of more than 16 live registers, such as functions with > 16 +parameters and complex pattern matchings. ### Fixed diff --git a/src/libAtomVM/context.c b/src/libAtomVM/context.c index 01ea786a6..7bac0b2cb 100644 --- a/src/libAtomVM/context.c +++ b/src/libAtomVM/context.c @@ -43,6 +43,7 @@ #define BYTES_PER_TERM (TERM_BITS / 8) static struct ResourceMonitor *context_monitors_handle_terminate(Context *ctx); +static void destroy_extended_registers(Context *ctx, unsigned int live); Context *context_new(GlobalContext *glb) { @@ -61,6 +62,7 @@ Context *context_new(GlobalContext *glb) ctx->e = ctx->heap.heap_end; context_clean_registers(ctx, 0); + list_init(&ctx->extended_x_regs); ctx->fr = NULL; @@ -151,6 +153,7 @@ void context_destroy(Context *ctx) // Any other process released our mailbox, so we can clear it. mailbox_destroy(&ctx->mailbox, &ctx->heap); + destroy_extended_registers(ctx, 0); free(ctx->fr); memory_destroy_heap(&ctx->heap, ctx->global); diff --git a/src/libAtomVM/context.h b/src/libAtomVM/context.h index 83e744a27..9a8a88fe4 100644 --- a/src/libAtomVM/context.h +++ b/src/libAtomVM/context.h @@ -87,6 +87,7 @@ struct Context Heap heap; term *e; term x[MAX_REG + 1]; + struct ListHead extended_x_regs; struct ListHead processes_list_head; @@ -169,6 +170,13 @@ struct ResourceMonitor struct ListHead resource_list_head; }; +struct ExtendedRegister +{ + struct ListHead head; + unsigned int index; + term value; +}; + /** * @brief Creates a new context * diff --git a/src/libAtomVM/memory.c b/src/libAtomVM/memory.c index aab3edd4e..81f5cb307 100644 --- a/src/libAtomVM/memory.c +++ b/src/libAtomVM/memory.c @@ -264,6 +264,13 @@ static enum MemoryGCResult memory_gc(Context *ctx, size_t new_size, size_t num_r entry->value = memory_shallow_copy_term(old_root_fragment, entry->value, &ctx->heap.heap_ptr, true); } + TRACE("- Running copy GC on process extended registers\n"); + LIST_FOR_EACH (item, &ctx->extended_x_regs) { + struct ExtendedRegister *ext_reg = GET_LIST_ENTRY(item, struct ExtendedRegister, head); + ext_reg->value = memory_shallow_copy_term( + old_root_fragment, ext_reg->value, &ctx->heap.heap_ptr, true); + } + TRACE("- Running copy GC on exit reason\n"); ctx->exit_reason = memory_shallow_copy_term(old_root_fragment, ctx->exit_reason, &ctx->heap.heap_ptr, true); diff --git a/src/libAtomVM/opcodesswitch.h b/src/libAtomVM/opcodesswitch.h index ec72f27e7..b03679989 100644 --- a/src/libAtomVM/opcodesswitch.h +++ b/src/libAtomVM/opcodesswitch.h @@ -60,6 +60,7 @@ #define COMPACT_LARGE_LITERAL 8 #define COMPACT_LARGE_INTEGER 9 #define COMPACT_LARGE_ATOM 10 +#define COMPACT_LARGE_XREG 11 #define COMPACT_LARGE_YREG 12 // OTP-20+ format @@ -179,13 +180,21 @@ typedef dreg_t dreg_gc_safe_t; } \ case COMPACT_EXTENDED_TYPED_REGISTER: { \ uint8_t reg_byte = *(decode_pc)++; \ - if (((reg_byte & 0x0F) != COMPACT_XREG) \ - && ((reg_byte & 0x0F) != COMPACT_YREG)) { \ - fprintf(stderr, "Unexpected reg byte %x @ %" PRIuPTR "\n", (int) reg_byte, (uintptr_t) ((decode_pc) - 1)); \ - AVM_ABORT(); \ + switch (reg_byte & 0x0F) { \ + case COMPACT_XREG: \ + case COMPACT_YREG: \ + break; \ + case COMPACT_LARGE_XREG: \ + case COMPACT_LARGE_YREG: \ + (decode_pc)++; \ + break; \ + default: \ + fprintf(stderr, "Unexpected reg byte %x @ %" PRIuPTR "\n", \ + (int) reg_byte, (uintptr_t) ((decode_pc) - 1)); \ + AVM_ABORT(); \ } \ int type_index; \ - DECODE_LITERAL(type_index, decode_pc) \ + DECODE_LITERAL(type_index, decode_pc) \ break; \ } \ default: \ @@ -218,6 +227,7 @@ typedef dreg_t dreg_gc_safe_t; } \ break; \ \ + case COMPACT_LARGE_XREG: \ case COMPACT_LARGE_YREG: \ (decode_pc)++; \ break; \ @@ -257,6 +267,7 @@ typedef dreg_t dreg_gc_safe_t; case COMPACT_YREG: \ (dreg).index = first_byte >> 4; \ break; \ + case COMPACT_LARGE_XREG: \ case COMPACT_LARGE_YREG: \ (dreg).index = (((first_byte & 0xE0) << 3) | *(decode_pc)++); \ break; \ @@ -278,6 +289,7 @@ typedef dreg_t dreg_gc_safe_t; case COMPACT_XREG: \ case COMPACT_YREG: \ break; \ + case COMPACT_LARGE_XREG: \ case COMPACT_LARGE_YREG: \ (decode_pc)++; \ break; \ @@ -458,13 +470,63 @@ typedef struct int index; } dreg_gc_safe_t; +static dreg_t extended_register_ptr(Context *ctx, unsigned int index) +{ + struct ListHead *item; + LIST_FOR_EACH (item, &ctx->extended_x_regs) { + struct ExtendedRegister *ext_reg = GET_LIST_ENTRY(item, struct ExtendedRegister, head); + if (ext_reg->index == index) { + return &ext_reg->value; + } + } + struct ExtendedRegister *new_ext_reg = malloc(sizeof(struct ExtendedRegister)); + new_ext_reg->index = index; + new_ext_reg->value = term_nil(); + list_append(&ctx->extended_x_regs, &new_ext_reg->head); + + return &new_ext_reg->value; +} + +static void destroy_extended_registers(Context *ctx, unsigned int live) +{ + struct ListHead *item; + struct ListHead *tmp; + MUTABLE_LIST_FOR_EACH (item, tmp, &ctx->extended_x_regs) { + struct ExtendedRegister *ext_reg = GET_LIST_ENTRY(item, struct ExtendedRegister, head); + if (ext_reg->index >= live) { + list_remove(item); + free(ext_reg); + } + } +} + +#define READ_ANY_XREG(out_term, reg_index) \ + { \ + if (LIKELY(reg_index < MAX_REG)) { \ + out_term = x_regs[reg_index]; \ + } else { \ + dreg_t reg = extended_register_ptr(ctx, reg_index); \ + out_term = *reg; \ + } \ + } + +#define TRIM_LIVE_REGS(live_regs_no) \ + if (UNLIKELY(!list_is_empty(&ctx->extended_x_regs))) { \ + destroy_extended_registers(ctx, live_regs_no); \ + } \ + if (UNLIKELY(live_regs_no > MAX_REG)) { \ + live_regs_no = MAX_REG; \ + } + #define DEST_REGISTER(reg) dreg_t reg #define GC_SAFE_DEST_REGISTER(reg) dreg_gc_safe_t reg +// TODO: fix register type heuristics, there is a chance that 'y' might be an "extended" x reg #define T_DEST_REG(dreg) \ ((dreg) >= x_regs && (dreg) < x_regs + MAX_REG) ? 'x' : 'y', \ (int) (((dreg) >= x_regs && (dreg) < x_regs + MAX_REG) ? ((dreg) - x_regs) : ((dreg) - ctx->e)) +// TODO: fix register type heuristics, there is a chance that 'y' might be an "extended" x reg #define T_DEST_REG_GC_SAFE(dreg_gc_safe) \ ((dreg).base == x_regs) ? 'x' : 'y', ((dreg).index) @@ -532,10 +594,34 @@ typedef struct } \ case COMPACT_EXTENDED_TYPED_REGISTER: { \ uint8_t reg_byte = *(decode_pc)++; \ - if ((reg_byte & 0x0F) == COMPACT_XREG) { \ - dest_term = x_regs[reg_byte >> 4]; \ - } else { \ - dest_term = ctx->e[reg_byte >> 4]; \ + switch (reg_byte & 0x0F) { \ + case COMPACT_XREG: \ + dest_term = x_regs[reg_byte >> 4]; \ + break; \ + case COMPACT_YREG: \ + dest_term = ctx->e[reg_byte >> 4]; \ + break; \ + case COMPACT_LARGE_XREG: \ + if (LIKELY((reg_byte & COMPACT_LARGE_IMM_MASK) == COMPACT_11BITS_VALUE)) { \ + unsigned int reg_index = (((reg_byte & 0xE0) << 3) | *(decode_pc)++); \ + dreg_t ext_reg = extended_register_ptr(ctx, reg_index); \ + if (IS_NULL_PTR(ext_reg)) { \ + RAISE_ERROR(OUT_OF_MEMORY_ATOM); \ + } \ + dest_term = *ext_reg; \ + } else { \ + VM_ABORT(); \ + } \ + break; \ + case COMPACT_LARGE_YREG: \ + if (LIKELY((reg_byte & COMPACT_LARGE_IMM_MASK) == COMPACT_11BITS_VALUE)) { \ + dest_term = ctx->e[((reg_byte & 0xE0) << 3) | *(decode_pc)++]; \ + } else { \ + VM_ABORT(); \ + } \ + break; \ + default: \ + VM_ABORT(); \ } \ int type_index; \ DECODE_LITERAL(type_index, decode_pc) \ @@ -580,6 +666,19 @@ typedef struct } \ break; \ \ + case COMPACT_LARGE_XREG: \ + if (LIKELY((first_byte & COMPACT_LARGE_IMM_MASK) == COMPACT_11BITS_VALUE)) { \ + unsigned int reg_index = (((first_byte & 0xE0) << 3) | *(decode_pc)++); \ + dreg_t ext_reg = extended_register_ptr(ctx, reg_index); \ + if (IS_NULL_PTR(ext_reg)) { \ + RAISE_ERROR(OUT_OF_MEMORY_ATOM); \ + } \ + dest_term = *ext_reg; \ + } else { \ + VM_ABORT(); \ + } \ + break; \ + \ case COMPACT_LARGE_YREG: \ if (LIKELY((first_byte & COMPACT_LARGE_IMM_MASK) == COMPACT_11BITS_VALUE)) { \ dest_term = ctx->e[((first_byte & 0xE0) << 3) | *(decode_pc)++]; \ @@ -635,6 +734,18 @@ typedef struct case COMPACT_YREG: \ (dreg) = ctx->e + reg_index; \ break; \ + case COMPACT_LARGE_XREG: \ + if (LIKELY((first_byte & COMPACT_LARGE_IMM_MASK) == COMPACT_11BITS_VALUE)) { \ + unsigned int reg_index = (((first_byte & 0xE0) << 3) | *(decode_pc)++); \ + dreg_t ext_reg = extended_register_ptr(ctx, reg_index); \ + if (IS_NULL_PTR(ext_reg)) { \ + RAISE_ERROR(OUT_OF_MEMORY_ATOM); \ + } \ + dreg = ext_reg; \ + } else { \ + VM_ABORT(); \ + } \ + break; \ case COMPACT_LARGE_YREG: \ if (LIKELY((first_byte & COMPACT_LARGE_IMM_MASK) == COMPACT_11BITS_VALUE)) { \ (dreg) = ctx->e + (((first_byte & 0xE0) << 3) | *(decode_pc)++); \ @@ -661,6 +772,19 @@ typedef struct (dreg_gc_safe).base = ctx->e; \ (dreg_gc_safe).index = reg_index; \ break; \ + case COMPACT_LARGE_XREG: \ + if (LIKELY((first_byte & COMPACT_LARGE_IMM_MASK) == COMPACT_11BITS_VALUE)) { \ + unsigned int reg_index = (((first_byte & 0xE0) << 3) | *(decode_pc)++); \ + dreg_t reg_base = extended_register_ptr(ctx, reg_index); \ + if (IS_NULL_PTR(reg_base)) { \ + RAISE_ERROR(OUT_OF_MEMORY_ATOM); \ + } \ + (dreg_gc_safe).base = reg_base; \ + (dreg_gc_safe).index = 0; \ + } else { \ + VM_ABORT(); \ + } \ + break; \ case COMPACT_LARGE_YREG: \ if (LIKELY((first_byte & COMPACT_LARGE_IMM_MASK) == COMPACT_11BITS_VALUE)) { \ (dreg_gc_safe).base = ctx->e; \ @@ -951,6 +1075,9 @@ typedef struct RAISE_ERROR(BADARG_ATOM); \ } +#define MAXI(A, B) ((A > B) ? (A) : (B)) +#define MINI(A, B) ((A > B) ? (B) : (A)) + #define CALL_FUN(fun, args_count) \ Module *fun_module; \ unsigned int fun_arity; \ @@ -995,8 +1122,14 @@ typedef struct if (UNLIKELY(args_count != fun_arity)) { \ RAISE_ERROR(BADARITY_ATOM); \ } \ - for (uint32_t i = 0; i < n_freeze; i++) { \ - x_regs[i + fun_arity] = boxed_value[i + 3]; \ + uint32_t lim_freeze = MINI(fun_arity + n_freeze, MAX_REG); \ + for (uint32_t i = fun_arity; i < lim_freeze; i++) { \ + x_regs[i] = boxed_value[i - fun_arity + 3]; \ + } \ + uint32_t ext_fun_arity = MAXI(fun_arity, MAX_REG); \ + for (uint32_t i = ext_fun_arity; i < fun_arity + n_freeze; i++) { \ + dreg_t ext_reg = extended_register_ptr(ctx, i); \ + *ext_reg = boxed_value[i - fun_arity + 3]; \ } \ ctx->cp = module_address(mod->module_index, pc - code); \ JUMP_TO_LABEL(fun_module, label); @@ -1328,7 +1461,8 @@ term make_fun(Context *ctx, const Module *mod, int fun_index, term argv[]) uint32_t n_freeze = module_get_fun_freeze(mod, fun_index); int size = BOXED_FUN_SIZE + n_freeze; - if (memory_ensure_free_with_roots(ctx, size, n_freeze, argv, MEMORY_CAN_SHRINK) != MEMORY_GC_OK) { + if (memory_ensure_free_with_roots(ctx, size, MINI(MAX_REG, n_freeze), argv, MEMORY_CAN_SHRINK) + != MEMORY_GC_OK) { return term_invalid_term(); } term *boxed_func = memory_heap_alloc(&ctx->heap, size); @@ -1337,9 +1471,13 @@ term make_fun(Context *ctx, const Module *mod, int fun_index, term argv[]) boxed_func[1] = (term) mod; boxed_func[2] = term_from_int(fun_index); - for (uint32_t i = 3; i < n_freeze + 3; i++) { + for (uint32_t i = 3; i < MINI(n_freeze, MAX_REG) + 3; i++) { boxed_func[i] = argv[i - 3]; } + for (uint32_t i = MINI(n_freeze, MAX_REG) + 3; i < n_freeze + 3; i++) { + dreg_t ext_reg = extended_register_ptr(ctx, i - 3); + boxed_func[i] = *ext_reg; + } return ((term) boxed_func) | TERM_BOXED_VALUE_TAG; } @@ -2010,15 +2148,9 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) USED_BY_TRACE(stack_need); USED_BY_TRACE(live); - #ifdef IMPL_CODE_LOADER - if (live > MAX_REG) { - fprintf(stderr, "Cannot use more than %d registers.\n", MAX_REG); - AVM_ABORT(); - } - #endif - #ifdef IMPL_EXECUTE_LOOP if (ctx->heap.root->next || ((ctx->heap.heap_ptr > ctx->e - (stack_need + 1)))) { + TRIM_LIVE_REGS(live); if (UNLIKELY(memory_ensure_free_with_roots(ctx, stack_need + 1, live, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); } @@ -2041,15 +2173,9 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) USED_BY_TRACE(heap_need); USED_BY_TRACE(live); - #ifdef IMPL_CODE_LOADER - if (live > MAX_REG) { - fprintf(stderr, "Cannot use more than %d registers.\n", MAX_REG); - AVM_ABORT(); - } - #endif - #ifdef IMPL_EXECUTE_LOOP if (ctx->heap.root->next || ((ctx->heap.heap_ptr + heap_need) > ctx->e - (stack_need + 1))) { + TRIM_LIVE_REGS(live); if (UNLIKELY(memory_ensure_free_with_roots(ctx, heap_need + stack_need + 1, live, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); } @@ -2069,15 +2195,9 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) USED_BY_TRACE(stack_need); USED_BY_TRACE(live); - #ifdef IMPL_CODE_LOADER - if (live > MAX_REG) { - fprintf(stderr, "Cannot use more than %d registers.\n", MAX_REG); - AVM_ABORT(); - } - #endif - #ifdef IMPL_EXECUTE_LOOP if (ctx->heap.root->next || ((ctx->heap.heap_ptr > ctx->e - (stack_need + 1)))) { + TRIM_LIVE_REGS(live); if (UNLIKELY(memory_ensure_free_with_roots(ctx, stack_need + 1, live, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); } @@ -2105,15 +2225,9 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) USED_BY_TRACE(heap_need); USED_BY_TRACE(live); - #ifdef IMPL_CODE_LOADER - if (live > MAX_REG) { - fprintf(stderr, "Cannot use more than %d registers.\n", MAX_REG); - AVM_ABORT(); - } - #endif - #ifdef IMPL_EXECUTE_LOOP if (ctx->heap.root->next || ((ctx->heap.heap_ptr + heap_need) > ctx->e - (stack_need + 1))) { + TRIM_LIVE_REGS(live); if (UNLIKELY(memory_ensure_free_with_roots(ctx, heap_need + stack_need + 1, live, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); } @@ -2142,12 +2256,14 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) size_t heap_free = context_avail_free_memory(ctx); // if we need more heap space than is currently free, then try to GC the needed space if (heap_free < heap_need) { + TRIM_LIVE_REGS(live_registers); if (UNLIKELY(memory_ensure_free_with_roots(ctx, heap_need, live_registers, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); } // otherwise, there is enough space for the needed heap, but there might // more more than necessary. In that case, try to shrink the heap. } else if (heap_free > heap_need * HEAP_NEED_GC_SHRINK_THRESHOLD_COEFF) { + TRIM_LIVE_REGS(live_registers); if (UNLIKELY(memory_ensure_free_with_roots(ctx, heap_need * (HEAP_NEED_GC_SHRINK_THRESHOLD_COEFF / 2), live_registers, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { TRACE("Unable to ensure free memory. heap_need=%i\n", heap_need); RAISE_ERROR(OUT_OF_MEMORY_ATOM); @@ -3258,7 +3374,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) USED_BY_TRACE(args_count); #ifdef IMPL_EXECUTE_LOOP - term fun = x_regs[args_count]; + term fun; + READ_ANY_XREG(fun, args_count); if (UNLIKELY(!term_is_function(fun))) { // We can gc as we are raising if (UNLIKELY(memory_ensure_free_with_roots(ctx, TUPLE_SIZE(2), 1, &fun, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { @@ -3604,6 +3721,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) VERIFY_IS_INTEGER(size, "bs_init2"); avm_int_t size_val = term_to_int(size); + TRIM_LIVE_REGS(live); if (UNLIKELY(memory_ensure_free_with_roots(ctx, words + term_binary_heap_size(size_val), live, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); } @@ -3651,6 +3769,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) RAISE_ERROR(UNSUPPORTED_ATOM); } + TRIM_LIVE_REGS(live); if (UNLIKELY(memory_ensure_free_with_roots(ctx, words + term_binary_heap_size(size_val / 8), live, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); } @@ -4115,6 +4234,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) } size_t src_size = term_binary_size(src); + TRIM_LIVE_REGS(live); // TODO: further investigate extra_val if (UNLIKELY(memory_ensure_free_with_roots(ctx, src_size + term_binary_heap_size(size_val / 8) + extra_val, live, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); @@ -4332,6 +4452,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) #ifdef IMPL_EXECUTE_LOOP int slots = term_to_int(slots_term); + TRIM_LIVE_REGS(live); // MEMORY_CAN_SHRINK because bs_start_match is classified as gc in beam_ssa_codegen.erl x_regs[live] = src; if (memory_ensure_free_with_roots(ctx, TERM_BOXED_BIN_MATCH_STATE_SIZE + slots, live + 1, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK) { @@ -4380,6 +4501,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) #ifdef IMPL_EXECUTE_LOOP // MEMORY_CAN_SHRINK because bs_start_match is classified as gc in beam_ssa_codegen.erl #ifdef IMPL_EXECUTE_LOOP + TRIM_LIVE_REGS(live); x_regs[live] = src; if (memory_ensure_free_with_roots(ctx, TERM_BOXED_BIN_MATCH_STATE_SIZE, live + 1, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); @@ -4459,6 +4581,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) size_t new_bin_size = src_size - start_pos; size_t heap_size = term_sub_binary_heap_size(bs_bin, src_size - start_pos); + TRIM_LIVE_REGS(live); x_regs[live] = src; if (UNLIKELY(memory_ensure_free_with_roots(ctx, heap_size, live + 1, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); @@ -4879,6 +5002,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) } else { term_set_match_state_offset(src, bs_offset + size_val * unit); + TRIM_LIVE_REGS(live); size_t heap_size = term_sub_binary_heap_size(bs_bin, size_val); if (UNLIKELY(memory_ensure_free_with_roots(ctx, heap_size, live, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); @@ -4948,8 +5072,10 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) uint32_t arity; DECODE_LITERAL(arity, pc) #ifdef IMPL_EXECUTE_LOOP - term module = x_regs[arity]; - term function = x_regs[arity + 1]; + term module; + READ_ANY_XREG(module, arity); + term function; + READ_ANY_XREG(function, arity + 1); TRACE("apply/1, module=%lu, function=%lu arity=%i\n", module, function, arity); if (UNLIKELY(!term_is_atom(module) || !term_is_atom(function))) { @@ -4999,8 +5125,10 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) uint32_t n_words; DECODE_LITERAL(n_words, pc); #ifdef IMPL_EXECUTE_LOOP - term module = x_regs[arity]; - term function = x_regs[arity + 1]; + term module; + READ_ANY_XREG(module, arity); + term function; + READ_ANY_XREG(function, arity + 1); TRACE("apply_last/1, module=%lu, function=%lu arity=%i deallocate=%i\n", module, function, arity, n_words); ctx->cp = ctx->e[n_words]; @@ -5123,6 +5251,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) DECODE_COMPACT_TERM(arg1, pc) #ifdef IMPL_EXECUTE_LOOP + TRIM_LIVE_REGS(live); + const struct ExportedFunction *exported_bif = mod->imported_funcs[bif]; GCBifImpl1 func = EXPORTED_FUNCTION_TO_GCBIF(exported_bif)->gcbif1_ptr; term ret = func(ctx, live, arg1); @@ -5168,6 +5298,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) DECODE_COMPACT_TERM(arg2, pc); #ifdef IMPL_EXECUTE_LOOP + TRIM_LIVE_REGS(live); + const struct ExportedFunction *exported_bif = mod->imported_funcs[bif]; GCBifImpl2 func = EXPORTED_FUNCTION_TO_GCBIF(exported_bif)->gcbif2_ptr; term ret = func(ctx, live, arg1, arg2); @@ -5239,6 +5371,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) DECODE_COMPACT_TERM(arg3, pc); #ifdef IMPL_EXECUTE_LOOP + TRIM_LIVE_REGS(live); + const struct ExportedFunction *exported_bif = mod->imported_funcs[bif]; GCBifImpl3 func = EXPORTED_FUNCTION_TO_GCBIF(exported_bif)->gcbif3_ptr; term ret = func(ctx, live, arg1, arg2, arg3); @@ -5380,6 +5514,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) size_t new_map_size = src_size + new_entries; bool is_shared = new_entries == 0; size_t heap_needed = term_map_size_in_terms_maybe_shared(new_map_size, is_shared); + TRIM_LIVE_REGS(live); // MEMORY_CAN_SHRINK because put_map is classified as gc in beam_ssa_codegen.erl x_regs[live] = src; if (memory_ensure_free_with_roots(ctx, heap_needed, live + 1, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK) { @@ -5502,6 +5637,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) // Maybe GC, and reset the src term in case it changed // size_t src_size = term_get_map_size(src); + TRIM_LIVE_REGS(live); // MEMORY_CAN_SHRINK because put_map is classified as gc in beam_ssa_codegen.erl x_regs[live] = src; if (memory_ensure_free_with_roots(ctx, term_map_size_in_terms_maybe_shared(src_size, true), live + 1, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK) { @@ -6055,6 +6191,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) uint32_t live; DECODE_LITERAL(live, pc); #ifdef IMPL_EXECUTE_LOOP + TRIM_LIVE_REGS(live); // MEMORY_CAN_SHRINK because bs_start_match is classified as gc in beam_ssa_codegen.erl if (memory_ensure_free_with_roots(ctx, TERM_BOXED_BIN_MATCH_STATE_SIZE, live, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); @@ -6194,10 +6331,6 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) #endif size_t nb_segments = list_len / 6; #ifdef IMPL_CODE_LOADER - if (live > MAX_REG) { - fprintf(stderr, "Cannot use more than %d registers.\n", MAX_REG); - AVM_ABORT(); - } if (list_len != nb_segments * 6) { fprintf(stderr, "Unexpected number of operations for bs_create_bin/6, each segment should be 6 elements\n"); AVM_ABORT(); @@ -6287,6 +6420,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) TRACE("bs_create_bin/6: total binary size (%li) is not evenly divisible by 8\n", binary_size); RAISE_ERROR(UNSUPPORTED_ATOM); } + TRIM_LIVE_REGS(live); if (UNLIKELY(memory_ensure_free_with_roots(ctx, alloc + term_binary_heap_size(binary_size / 8), live, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); } @@ -6679,6 +6813,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) goto bs_match_jump_to_fail; } size_t heap_size = term_sub_binary_heap_size(bs_bin, matched_bits / 8); + TRIM_LIVE_REGS(live); x_regs[live] = match_state; if (UNLIKELY(memory_ensure_free_with_roots(ctx, heap_size, live + 1, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); @@ -6712,6 +6847,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) RAISE_ERROR(BADARG_ATOM); } size_t heap_size = term_sub_binary_heap_size(bs_bin, tail_bits / 8); + TRIM_LIVE_REGS(live); x_regs[live] = match_state; if (UNLIKELY(memory_ensure_free_with_roots(ctx, heap_size, live + 1, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); From 983544a89150513841a28cae743e1a58bbef1a82 Mon Sep 17 00:00:00 2001 From: Davide Bettio Date: Thu, 22 Feb 2024 14:39:56 +0100 Subject: [PATCH 4/4] tests: add tests for > 16 live x registers Signed-off-by: Davide Bettio --- tests/erlang_tests/CMakeLists.txt | 9 +++ .../erlang_tests/complex_list_match_xregs.erl | 56 +++++++++++++ tests/erlang_tests/twentyone_param_fun.erl | 80 +++++++++++++++++++ .../erlang_tests/twentyone_param_function.erl | 58 ++++++++++++++ tests/test.c | 6 ++ 5 files changed, 209 insertions(+) create mode 100644 tests/erlang_tests/complex_list_match_xregs.erl create mode 100644 tests/erlang_tests/twentyone_param_fun.erl create mode 100644 tests/erlang_tests/twentyone_param_function.erl diff --git a/tests/erlang_tests/CMakeLists.txt b/tests/erlang_tests/CMakeLists.txt index 61e9336ff..6c01cece8 100644 --- a/tests/erlang_tests/CMakeLists.txt +++ b/tests/erlang_tests/CMakeLists.txt @@ -492,6 +492,10 @@ compile_erlang(test_atomvm_random) compile_erlang(float_decode) compile_erlang(test_utf8_atoms) +compile_erlang(twentyone_param_function) +compile_erlang(complex_list_match_xregs) +compile_erlang(twentyone_param_fun) + add_custom_target(erlang_test_modules DEPENDS code_load_files @@ -946,5 +950,10 @@ add_custom_target(erlang_test_modules DEPENDS test_atomvm_random.beam float_decode.beam + test_utf8_atoms.beam + + twentyone_param_function.beam + complex_list_match_xregs.beam + twentyone_param_fun.beam ) diff --git a/tests/erlang_tests/complex_list_match_xregs.erl b/tests/erlang_tests/complex_list_match_xregs.erl new file mode 100644 index 000000000..95469eb50 --- /dev/null +++ b/tests/erlang_tests/complex_list_match_xregs.erl @@ -0,0 +1,56 @@ +% +% This file is part of AtomVM. +% +% Copyright 2024 Davide Bettio +% +% Licensed under the Apache License, Version 2.0 (the "License"); +% you may not use this file except in compliance with the License. +% You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +% See the License for the specific language governing permissions and +% limitations under the License. +% +% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later +% + +-module(complex_list_match_xregs). +-export([start/0, test_a/1, list_a/0, list_b/0, list_c/0]). + +start() -> + {ok, {"first", "list"}} = ?MODULE:test_a(?MODULE:list_a()), + {x, $o} = ?MODULE:test_a(?MODULE:list_b()), + ok_baz = ?MODULE:test_a(?MODULE:list_c()), + 0. + +test_a(["This", "is", _, _, _, _, _, "baz", "baar"]) -> + error_a; +test_a(["This", "is", _, _, "foo", _, _, "baz"]) -> + ok_baz; +test_a(["This", "is", "baz", _, _, _, _, "baz"]) -> + error_c; +test_a(["This", "is", "not" | _Tail]) -> + error_d; +test_a(["This", "is", _, N, _, W]) -> + {ok, {N, W}}; +test_a(["This" | Tail]) -> + {foo, "This", Tail}; +test_a([[$F, X, $o | Tail], Tail]) -> + {x, X}; +test_a([[$F, Y, $o | _Tail1], _Tail2]) -> + {x, Y}; +test_a(List) -> + {error, List}. + +list_a() -> + ["This", "is", "the", "first", "test", "list"]. + +list_b() -> + [[$F, $o, $o, "bar"], "bar"]. + +list_c() -> + ["This", "is", "aaa", "bbb", "foo", "ccc", "ddd", "baz"]. diff --git a/tests/erlang_tests/twentyone_param_fun.erl b/tests/erlang_tests/twentyone_param_fun.erl new file mode 100644 index 000000000..2ba3102ee --- /dev/null +++ b/tests/erlang_tests/twentyone_param_fun.erl @@ -0,0 +1,80 @@ +% +% This file is part of AtomVM. +% +% Copyright 2024 Davide Bettio +% +% Licensed under the Apache License, Version 2.0 (the "License"); +% you may not use this file except in compliance with the License. +% You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +% See the License for the specific language governing permissions and +% limitations under the License. +% +% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later +% + +-module(twentyone_param_fun). +-export([start/0, sum_mul/3, id/1, g/18]). + +start() -> + {X, Fun} = ?MODULE:g(20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37), + Ref = erlang:make_ref(), + {A, Ref2} = Fun(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, Ref), + {B, Ref} = Fun( + 1, 2, 3, 40, 50, 60, 70, 80, 90, 100, 110, 120, 13, 14, 15, 16, 17, 18, 19, X, Ref2 + ), + 1337 - (A + B). + +g(C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, D, C11, C12, C13, C14, C15, C16, C17) when + is_integer(C17) +-> + Fun = + (fun( + P1, + P2, + P3, + P4, + P5, + P6, + P7, + P8, + P9, + P10, + P11, + P12, + P13, + P14, + P15, + P16, + P17, + P18, + P19, + P20, + Ref + ) when is_reference(Ref) -> + Sum = + ?MODULE:sum_mul(P1 - C1, P2, 1) + + ?MODULE:sum_mul(P3, P4 - C2, 2) + + ?MODULE:sum_mul(P5 - C3, P6, 3) + + ?MODULE:sum_mul(P5, P6 - C4, 4) + + ?MODULE:sum_mul(P7 - C5, P8, 5) + + ?MODULE:sum_mul(P9, P10 - C6, 6) + + ?MODULE:sum_mul(P11 - C7, P12, 7) + + ?MODULE:sum_mul(P13 - C9, P14 - C8, 8) + + ?MODULE:sum_mul(P15 - C10, P16 - C11, 9) + + ?MODULE:sum_mul(P17 - C13, P18 - C12, 10) + + ?MODULE:sum_mul(P19 - C14, P20 - C15, 11) + C16 - C17, + {?MODULE:id(Sum), ?MODULE:id(Ref)} + end), + {D, Fun}. + +sum_mul(A, B, C) -> + (A + B) * C. + +id(X) -> + X. diff --git a/tests/erlang_tests/twentyone_param_function.erl b/tests/erlang_tests/twentyone_param_function.erl new file mode 100644 index 000000000..d37f22a0f --- /dev/null +++ b/tests/erlang_tests/twentyone_param_function.erl @@ -0,0 +1,58 @@ +% +% This file is part of AtomVM. +% +% Copyright 2024 Davide Bettio +% +% Licensed under the Apache License, Version 2.0 (the "License"); +% you may not use this file except in compliance with the License. +% You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +% See the License for the specific language governing permissions and +% limitations under the License. +% +% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later +% + +-module(twentyone_param_function). +-export([start/0, f/21, sum_mul/3, id/1, get_mf/0]). + +start() -> + Ref = erlang:make_ref(), + {M, F} = ?MODULE:get_mf(), + {A, Ref2} = f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, Ref), + {B, Ref} = ?MODULE:f( + 1, 2, 3, 40, 50, 60, 70, 80, 90, 100, 110, 120, 13, 14, 15, 16, 17, 18, 19, 20, Ref2 + ), + {C, Ref} = M:F( + -1, 2, -3, 4, -5, 6, -7, 8, -9, 10, -11, 12, -13, 14, -15, 16, -17, 18, -19, 20, Ref + ), + A + B + C - 7417. + +f(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, Ref) -> + Sum = + ?MODULE:sum_mul(P1, P2, 1) + + ?MODULE:sum_mul(P3, P4, 2) + + ?MODULE:sum_mul(P5, P6, 3) + + ?MODULE:sum_mul(P5, P6, 4) + + ?MODULE:sum_mul(P7, P8, 5) + + ?MODULE:sum_mul(P9, P10, 6) + + ?MODULE:sum_mul(P11, P12, 7) + + ?MODULE:sum_mul(P13, P14, 8) + + ?MODULE:sum_mul(P15, P16, 9) + + ?MODULE:sum_mul(P17, P18, 10) + + ?MODULE:sum_mul(P19, P20, 11), + {?MODULE:id(Sum), ?MODULE:id(Ref)}. + +sum_mul(A, B, C) -> + (A + B) * C. + +id(X) -> + X. + +get_mf() -> + {?MODULE, f}. diff --git a/tests/test.c b/tests/test.c index aecf2ac76..ba9f5bca7 100644 --- a/tests/test.c +++ b/tests/test.c @@ -520,7 +520,13 @@ struct Test tests[] = { #endif TEST_CASE(float_decode), + TEST_CASE(test_utf8_atoms), + + TEST_CASE(twentyone_param_function), + TEST_CASE(complex_list_match_xregs), + TEST_CASE(twentyone_param_fun), + // TEST CRASHES HERE: TEST_CASE(memlimit), { NULL, 0, false, false }