Skip to content

Commit

Permalink
Merge pull request #1056 from bettio/support-extended-x-regs
Browse files Browse the repository at this point in the history
This PR is focused into removing 2 main limitations:
1. Functions can have any number of parameters
2. Up to 1023 live x registers are supported

This restriction has been easily workarounded so far, but removing this
limitation closes a gap that makes development smoother.

Since so far everything worked nicely with just 16 registers, this
implementation threats "extended x registers" as an uncommon case, so using
them is way slower, but still possible, while reducing memory footprint to bare
minimum.

There is still a limitation: NIFs with more than 16 parameters are still not
allowed, but this limitation will be likely stay unnoticed.

This PR also fixes an unlikely corruption when having exactly 16 live registers,
and enables support of more than > 16 typed y registers (I never observed any
crash due to this limitation).

See also for #943 for an alternative approach, decoding is based on Paul's work
from that PR.

These changes are made under both the "Apache 2.0" and the "GNU Lesser General
Public License 2.1 or later" license terms (dual license).

SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
  • Loading branch information
bettio committed Feb 27, 2024
2 parents b29a691 + 983544a commit 75a2ac7
Show file tree
Hide file tree
Showing 12 changed files with 457 additions and 56 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added Pico cmake option `AVM_WAIT_BOOTSEL_ON_EXIT` (default `ON`) to allow tools to use automated `BOOTSEL` mode after main application exits
- Use UTF-8 encoding for atoms when using `erlang:term_to_binary/1`, in conformance with OTP-26
- Pico: Wait for USB serial connection `cmake` configuration option `AVM_USB_WAIT_SECONDS` added with 20 second default.
- Support for code that makes use of more than 16 live registers, such as functions with > 16
parameters and complex pattern matchings.

### Fixed

Expand All @@ -25,6 +27,8 @@ used)
- ESP32: GPIO driver fix bug that would accept invalid `pull` direction, and silently set `pull` direction to `floating` without issuing an error.
- ESP32: fixed bug in gpio driver that would accept invalid pin numbers (either negative, or too large)
- RP2040: fixed bug in `gpio:set_pin_pull/2` that would accept any parameter as a valid `pull` mode.
- Support to function with 10 or more parameters
- Very unlikely but possible corruption caused by generated code that uses 16 live registers

### Changed

Expand Down
39 changes: 35 additions & 4 deletions src/libAtomVM/atom.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
3 changes: 3 additions & 0 deletions src/libAtomVM/context.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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;

Expand Down Expand Up @@ -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);
Expand Down
12 changes: 11 additions & 1 deletion src/libAtomVM/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -84,7 +86,8 @@ struct Context
GlobalContext *global;
Heap heap;
term *e;
term x[MAX_REG];
term x[MAX_REG + 1];
struct ListHead extended_x_regs;

struct ListHead processes_list_head;

Expand Down Expand Up @@ -167,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
*
Expand Down
7 changes: 7 additions & 0 deletions src/libAtomVM/memory.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
3 changes: 2 additions & 1 deletion src/libAtomVM/nifs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Loading

0 comments on commit 75a2ac7

Please sign in to comment.