From 2d299b0399e98631e7ef19f9e7d64016d6f36023 Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Wed, 14 Oct 2026 20:22:29 -0700 Subject: [PATCH 1/2] ports/stm: keep the heap below the stack when they share RAM Since 1cc4e6fdf2 (#9962) port_heap_get_top() returns _ld_heap_end, the top of RAM. That is right for common_tcm.ld, where the stack lives in DTCM, but common_default.ld and common_nvm.ld put the stack at the top of RAM too. On those boards (Feather STM32F405 and 17 others) the TLSF pool covered the whole stack, 25 KB on the F405, so a large heap block could be handed out on top of it. A list growing to 32 KB in a fragmented heap then hard-faulted, or overwrote the stack canary and reset into safe mode with STACK_OVERFLOW, instead of raising MemoryError. Stop the heap at port_stack_get_limit() when the stack top falls inside the heap's RAM, as atmel-samd, nordic and raspberrypi do. Co-Authored-By: Claude Opus 5.5 (1M context) --- ports/stm/supervisor/port.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ports/stm/supervisor/port.c b/ports/stm/supervisor/port.c index 6e86e19caf0..c82c7788d3c 100644 --- a/ports/stm/supervisor/port.c +++ b/ports/stm/supervisor/port.c @@ -422,8 +422,14 @@ uint32_t *port_heap_get_bottom(void) { return &_ld_heap_start; } -// heap memory can be set in SRAM and stack can be set in DTCM +// The heap runs to the top of RAM unless the stack is there too. common_tcm.ld puts the stack in +// DTCM, outside the heap's RAM. common_default.ld and common_nvm.ld put it at the top of RAM, so +// the heap has to stop at the stack limit or heap blocks get handed out on top of the stack. uint32_t *port_heap_get_top(void) { + uintptr_t stack_top = (uintptr_t)port_stack_get_top(); + if (stack_top > (uintptr_t)&_ld_heap_start && stack_top <= (uintptr_t)&_ld_heap_end) { + return port_stack_get_limit(); + } return &_ld_heap_end; } From eeee1cdd4d422ce019c2923c21c18877f39df4d8 Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Thu, 15 Oct 2026 10:33:58 -0700 Subject: [PATCH 2/2] ports/stm: end the heap below the stack in the linker script Move the fix from port_heap_get_top() into common_default.ld and common_nvm.ld, as suggested in review. _ld_heap_end now stops at the same address port_stack_get_limit() returns. Co-Authored-By: Claude Opus 5.5 (1M context) --- ports/stm/boards/common_default.ld | 4 +++- ports/stm/boards/common_nvm.ld | 4 +++- ports/stm/supervisor/port.c | 8 +------- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/ports/stm/boards/common_default.ld b/ports/stm/boards/common_default.ld index 62a6d63d11c..cdbe7d59a72 100644 --- a/ports/stm/boards/common_default.ld +++ b/ports/stm/boards/common_default.ld @@ -87,7 +87,9 @@ SECTIONS . = . + _minimum_heap_size; . = ALIGN(4); } >RAM - _ld_heap_end = ORIGIN(RAM) + LENGTH(RAM); + /* The stack sits at the top of RAM, so end the heap below it. The extra 1K matches + CIRCUITPY_EXCEPTION_STACK_SIZE, which port_stack_get_limit() counts as stack too. */ + _ld_heap_end = ORIGIN(RAM) + LENGTH(RAM) - _ld_default_stack_size - 1K; /* this just checks there is enough RAM for the stack */ .stack : diff --git a/ports/stm/boards/common_nvm.ld b/ports/stm/boards/common_nvm.ld index f2a0456e000..b057d9480fa 100644 --- a/ports/stm/boards/common_nvm.ld +++ b/ports/stm/boards/common_nvm.ld @@ -97,7 +97,9 @@ SECTIONS . = . + _minimum_heap_size; . = ALIGN(4); } >RAM - _ld_heap_end = ORIGIN(RAM) + LENGTH(RAM); + /* The stack sits at the top of RAM, so end the heap below it. The extra 1K matches + CIRCUITPY_EXCEPTION_STACK_SIZE, which port_stack_get_limit() counts as stack too. */ + _ld_heap_end = ORIGIN(RAM) + LENGTH(RAM) - _ld_default_stack_size - 1K; /* this just checks there is enough RAM for the stack */ .stack : diff --git a/ports/stm/supervisor/port.c b/ports/stm/supervisor/port.c index c82c7788d3c..6e86e19caf0 100644 --- a/ports/stm/supervisor/port.c +++ b/ports/stm/supervisor/port.c @@ -422,14 +422,8 @@ uint32_t *port_heap_get_bottom(void) { return &_ld_heap_start; } -// The heap runs to the top of RAM unless the stack is there too. common_tcm.ld puts the stack in -// DTCM, outside the heap's RAM. common_default.ld and common_nvm.ld put it at the top of RAM, so -// the heap has to stop at the stack limit or heap blocks get handed out on top of the stack. +// heap memory can be set in SRAM and stack can be set in DTCM uint32_t *port_heap_get_top(void) { - uintptr_t stack_top = (uintptr_t)port_stack_get_top(); - if (stack_top > (uintptr_t)&_ld_heap_start && stack_top <= (uintptr_t)&_ld_heap_end) { - return port_stack_get_limit(); - } return &_ld_heap_end; }