Skip to content

Improve STM32 heap allocation performance and debugging. #1662

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added Function.ex and Protocol.ex improving Elixir 1.18 support
- Added WiFi support for ESP32P4 via esp-wifi-external for build with ESP-IDF v5.4 and later
- Added Process.link/1 and unlink/1 to Elixir Process.ex
- Added `erlang:system_info/1` keys `atomvm_free_heap_size` and `atomvm_minimum_free_size` on STM32 platform

### Changed

- Removed `externalterm_to_term_copy` added in [0.6.5] and introduced flags to `externalterm_to_term` to perform copy.
- Release images for ESP32 chips are built with ESP-IDF v5.4
- ESP32: SPI peripheral defaults to `"spi2"` instead of deprecated `hspi`
- Deprecated ESP32 `erlang:system_info/1` memory keys `esp32_free_heap_size` and
`esp32_minimum_free_size` in favor of `atomvm_` prefixed keys with warnings to update applications
before the old keys are removed.

### Fixed

Expand Down
3 changes: 3 additions & 0 deletions UPDATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ port socket driver, are also represented by a port and some matching code may ne
`is_pid/1` to `is_port/1`.
- Ports and pids can be registered. Function `globalcontext_get_registered_process` result now is
a term that can be a `port()` or a `pid()`.
- ESP32 `erlang:system_info/1` memory keys `esp32_free_heap_size` and `esp32_minimum_free_size` have
been deprecated in favor of `atomvm_` prefixed keys. Applications should be updated to use
`atomvm_free_heap_size` and `atomvm_minimum_free_size` instead.

## v0.6.4 -> v0.6.5

Expand Down
9 changes: 6 additions & 3 deletions doc/src/programmers-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,11 @@ For example,
io:format("Atom Count: ~p~n", [erlang:system_info(atom_count)]).
```

In addition AtomVM supports the following keys on ESP32 and STM32 platforms:

* `atomvm_free_heap_size` Returns the available free space in the heap.
* `atomvm_minimum_free_size` Returns the smallest ever free space available in the heap since boot, this will tell you how close you have come to running out of free memory.

```{note}
Additional platform-specific information is supported, depending on the platform type. See below.
```
Expand Down Expand Up @@ -1141,9 +1146,7 @@ As noted above, the [`erlang:system_info/1`](./apidocs/erlang/estdlib/erlang.md#
You can request ESP32-specific information using using the following input atoms:
* `esp32_free_heap_size` Returns the available free space in the ESP32 heap.
* `esp32_largest_free_block` Returns the size of the largest free continuous block in the ESP32 heap.
* `esp32_minimum_free_size` Returns the smallest ever free space available in the ESP32 heap since boot, this will tell you how close you have come to running out of free memory.
* `esp32_chip_info` Returns map of the form `#{features := Features, cores := Cores, revision := Revision, model := Model}`, where `Features` is a list of features enabled in the chip, from among the following atoms: `[emb_flash, bgn, ble, bt]`; `Cores` is the number of CPU cores on the chip; `Revision` is the chip version; and `Model` is one of the following atoms: `esp32`, `esp32_s2`, `esp32_s3`, `esp32_c3`, etc.
* `esp_idf_version` Return the IDF SDK version, as a string.
Expand Down Expand Up @@ -1591,7 +1594,7 @@ The read options take the form of a proplist, if the key `raw` is true (`{raw, t
If the key `voltage` is true (or simply appears in the list as an atom), then a calibrated voltage value will be returned in millivolts in the second element of the returned tuple. Otherwise, this element will be the atom `undefined`.
You may specify the number of samples (1 - 100000) to be taken and averaged over using the tuple `{samples, Samples :: 1..100000}`, the default is `64`.
You may specify the number of samples (1 - 100000) to be taken and averaged over using the tuple `{samples, Samples :: 1..100000}`, the default is `64`.
```{warning}
Using a large number of samples can significantly increase the amount of time before a response, up to several seconds.
Expand Down
8 changes: 6 additions & 2 deletions libs/estdlib/src/erlang.erl
Original file line number Diff line number Diff line change
Expand Up @@ -289,11 +289,15 @@ process_info(_Pid, _Key) ->
%% <li><b>schedulers</b> the number of schedulers, equal to the number of online processors (integer)</li>
%% <li><b>schedulers_online</b> the current number of schedulers (integer)</li>
%% </ul>
%% The following keys are supported on ESP32 and STM32 platforms:
%% <ul>
%% <li><b>atomvm_free_heap_size</b> the number of (noncontiguous) free bytes in the STM32 heap (integer)</li>
%% <li><b>atomvm_minimum_free_size</b> the smallest number of free bytes in the STM32 heap since boot (integer)</li>
%% </ul>
%%
%% The following keys are supported on the ESP32 platform:
%% <ul>
%% <li><b>esp32_free_heap_size</b> the number of (noncontiguous) free bytes in the ESP32 heap (integer)</li>
%% <li><b>esp32_largest_free_block</b> the number of the largest contiguous free bytes in the ESP32 heap (integer)</li>
%% <li><b>esp32_minimum_free_size</b> the smallest number of free bytes in the ESP32 heap since boot (integer)</li>
%% <li><b>esp32_chip_info</b> Details about the model and capabilities of the ESP32 device (map)</li>
%% </ul>
%%
Expand Down
18 changes: 18 additions & 0 deletions src/platforms/esp32/components/avm_sys/sys.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,12 @@ static void *select_thread_loop(void *);
static void select_thread_signal(struct ESP32PlatformData *platform);

// clang-format off
// TODO: remove deprecated `atomvm_free_heap_size_atom` for the 0.8.x release cycle
static const char *const atomvm_free_heap_size_atom = "\x15" "atomvm_free_heap_size";
static const char *const esp_free_heap_size_atom = "\x14" "esp32_free_heap_size";
static const char *const esp_largest_free_block_atom = "\x18" "esp32_largest_free_block";
static const char *const atomvm_get_minimum_free_size_atom = "\x18" "atomvm_minimum_free_size";
// TODO: remove deprecated `esp_get_minimum_free_size_atom` for the 0.8.x release cycle
static const char *const esp_get_minimum_free_size_atom = "\x17" "esp32_minimum_free_size";
static const char *const esp_chip_info_atom = "\xF" "esp32_chip_info";
static const char *const esp_idf_version_atom = "\xF" "esp_idf_version";
Expand Down Expand Up @@ -499,13 +503,27 @@ static term get_features(Context *ctx, uint32_t features)
term sys_get_info(Context *ctx, term key)
{
GlobalContext *glb = ctx->global;
// TODO: remove this deprecated key for the 0.8.x release cycle
if (key == globalcontext_make_atom(glb, esp_free_heap_size_atom)) {
ESP_LOGW(TAG, "The system_info/1 key 'esp32_free_heap_size' has been deprecated in favor of the \
muti-platform key 'atomvm_free_heap_size'. This key will be removed in a future release, please \
update your application to use 'atomvm_free_heap_size'.");
return term_from_int32(esp_get_free_heap_size());
}
if (key == globalcontext_make_atom(glb, atomvm_free_heap_size_atom)) {
return term_from_int32(esp_get_free_heap_size());
}
if (key == globalcontext_make_atom(glb, esp_largest_free_block_atom)) {
return term_from_int32(heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT));
}
// TODO: remove this deprecated key for the 0.8.x release cycle
if (key == globalcontext_make_atom(glb, esp_get_minimum_free_size_atom)) {
ESP_LOGW(TAG, "The system_info/1 key 'esp32_minimum_free_size' has been deprecated in favor of the \
muti-platform key 'atomvm_minimum_free_size'. This key will be removed in a future release, please \
update your application to use 'atomvm_minimum_free_size'.");
return term_from_int32(esp_get_minimum_free_heap_size());
}
if (key == globalcontext_make_atom(glb, atomvm_get_minimum_free_size_atom)) {
return term_from_int32(esp_get_minimum_free_heap_size());
}
if (key == globalcontext_make_atom(glb, esp_chip_info_atom)) {
Expand Down
2 changes: 1 addition & 1 deletion src/platforms/stm32/cmake/libopencm3.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ message(STATUS "Generated Linker File : ${CMAKE_CURRENT_BINARY_DIR}/${LINKER_S
# ARCH_FLAGS has to be passed as a string here
JOIN("${ARCH_FLAGS}" " " ARCH_FLAGS)
# Set linker flags
set(LINKER_FLAGS "${LINKER_FLAGS} -specs=nosys.specs -nostartfiles -Wl,--undefined,_printf_float -Wl,--undefined,_scanf_float -T${CMAKE_CURRENT_BINARY_DIR}/${LINKER_SCRIPT} ${ARCH_FLAGS}")
set(LINKER_FLAGS "${LINKER_FLAGS} -specs=nosys.specs -nostartfiles -Wl,--undefined,_printf_float -Wl,--undefined,_scanf_float -Wl,--print-memory-usage -T${CMAKE_CURRENT_BINARY_DIR}/${LINKER_SCRIPT} ${ARCH_FLAGS}")
message(STATUS "Linker Flags : ${LINKER_FLAGS}")

# Compiler flags
Expand Down
27 changes: 24 additions & 3 deletions src/platforms/stm32/src/lib/sys.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ extern uint8_t _ebss, _stack;

static uint8_t *_cur_brk = NULL;
static uint8_t *_heap_end = NULL;
static uint8_t *_heap_high_mark = NULL;

/*
* If not overridden, this puts the heap into the left
Expand All @@ -63,13 +64,14 @@ static void __local_ram(uint8_t **start, uint8_t **end)
{
*start = &_ebss;
*end = (uint8_t *) (((uintptr_t) &_stack - RESERVE_STACK_SIZE));
_heap_high_mark = 0;
}

void *_sbrk_r(struct _reent *reent, ptrdiff_t diff)
{
uint8_t *_old_brk;

if (_heap_end == NULL) {
if (UNLIKELY(_heap_end == NULL)) {
local_heap_setup(&_cur_brk, &_heap_end);
}

Expand All @@ -79,9 +81,23 @@ void *_sbrk_r(struct _reent *reent, ptrdiff_t diff)
return (void *) -1;
}
_cur_brk += diff;
if (_cur_brk > _heap_high_mark) {
_heap_high_mark = _cur_brk;
}

return _old_brk;
}

static int sys_get_free_heap()
{
return (int) ((uint8_t *) (((uintptr_t) &_stack - RESERVE_STACK_SIZE))) - (_cur_brk == NULL ? (int) &_ebss : (int) _cur_brk);
}

static int sys_least_free_heap()
{
return (int) ((uint8_t *) (((uintptr_t) &_stack - RESERVE_STACK_SIZE))) - (int) _heap_high_mark;
}

// Monotonically increasing number of milliseconds from reset
static volatile uint64_t system_millis;

Expand Down Expand Up @@ -263,8 +279,13 @@ Context *sys_create_port(GlobalContext *glb, const char *driver_name, term opts)

term sys_get_info(Context *ctx, term key)
{
UNUSED(ctx);
UNUSED(key);
GlobalContext *glb = ctx->global;
if (key == globalcontext_existing_term_from_atom_string(glb, ATOM_STR("\x15", "atomvm_free_heap_size"))) {
return term_from_int32(sys_get_free_heap());
}
if (key == globalcontext_existing_term_from_atom_string(glb, ATOM_STR("\x18", "atomvm_minimum_free_size"))) {
return term_from_int32(sys_least_free_heap());
}
return UNDEFINED_ATOM;
}

Expand Down
Loading