Skip to content

Commit e635457

Browse files
committed
revamp supervisor allocations
These now interact better with the GC heap, through the magic of a little layer violation. New Supervisor Allocator * The supervisor_allocation structure is now placed in static storage by each user of supervisor allocations. The max number of supervisor allocations must still be statically known, but it's known implicitly instead of explicitly * the structures get placed into a single-linked list so that they they can be traversed * 'doing' a supervisor allocation initializes the gc heap if necessary because the implementation is JUST a gc_alloc plus bookkeeping! * a layer violation in gc is introduced to support gc-reset surviving allocations: gc_reserve() takes a pointer and length, and marks it as an allocated gc block sans finalizer * the "high address" allocation is specifically for the C stack, since it must be contiguous with the statically allocated stack. * objects are allowed to register a move callback function. A "simple move" function uses memmove; NULL specifies not movable; and the special value SUPERVISOR_STACK_ALLOCATION indicates the stack allocation which is both high-address and non-movable of course it doesn't boot.
1 parent c0fbcec commit e635457

File tree

21 files changed

+235
-447
lines changed

21 files changed

+235
-447
lines changed

main.c

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,15 @@ uint8_t value_out = 0;
126126
static size_t PLACE_IN_DTCM_BSS(_pystack[CIRCUITPY_PYSTACK_SIZE / sizeof(size_t)]);
127127
#endif
128128

129+
supervisor_allocation prev_traceback;
130+
129131
static void reset_devices(void) {
130132
#if CIRCUITPY_BLEIO_HCI
131133
bleio_reset();
132134
#endif
133135
}
134136

135-
STATIC void start_mp(supervisor_allocation *heap) {
137+
STATIC void start_mp(void) {
136138
supervisor_workflow_reset();
137139

138140
// Stack limit should be less than real stack size, so we have a chance
@@ -163,9 +165,7 @@ STATIC void start_mp(supervisor_allocation *heap) {
163165
mp_pystack_init(_pystack, _pystack + (sizeof(_pystack) / sizeof(size_t)));
164166
#endif
165167

166-
#if MICROPY_ENABLE_GC
167-
gc_init(heap->ptr, heap->ptr + get_allocation_length(heap) / 4);
168-
#endif
168+
supervisor_gc_init();
169169
mp_init();
170170
mp_obj_list_init((mp_obj_list_t *)mp_sys_path, 0);
171171
mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR_)); // current dir (or base dir of the script)
@@ -196,7 +196,7 @@ STATIC void stop_mp(void) {
196196
usb_background();
197197
#endif
198198

199-
gc_deinit();
199+
supervisor_gc_deinit();
200200
}
201201

202202
STATIC const char *_current_executing_filename = NULL;
@@ -264,30 +264,29 @@ STATIC void count_strn(void *data, const char *str, size_t len) {
264264
*(size_t *)data += len;
265265
}
266266

267-
STATIC void cleanup_after_vm(supervisor_allocation *heap, mp_obj_t exception) {
267+
STATIC void cleanup_after_vm(mp_obj_t exception) {
268268
// Get the traceback of any exception from this run off the heap.
269269
// MP_OBJ_SENTINEL means "this run does not contribute to traceback storage, don't touch it"
270270
// MP_OBJ_NULL (=0) means "this run completed successfully, clear any stored traceback"
271271
if (exception != MP_OBJ_SENTINEL) {
272-
free_memory(prev_traceback_allocation);
272+
free_memory(&prev_traceback);
273273
// ReloadException is exempt from traceback printing in pyexec_file(), so treat it as "no
274274
// traceback" here too.
275275
if (exception && exception != MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_reload_exception))) {
276276
size_t traceback_len = 0;
277277
mp_print_t print_count = {&traceback_len, count_strn};
278278
mp_obj_print_exception(&print_count, exception);
279-
prev_traceback_allocation = allocate_memory(align32_size(traceback_len + 1), false, true);
280279
// Empirically, this never fails in practice - even when the heap is totally filled up
281280
// with single-block-sized objects referenced by a root pointer, exiting the VM frees
282281
// up several hundred bytes, sufficient for the traceback (which tends to be shortened
283282
// because there wasn't memory for the full one). There may be convoluted ways of
284283
// making it fail, but at this point I believe they are not worth spending code on.
285-
if (prev_traceback_allocation != NULL) {
284+
if (allocate_memory(&prev_traceback, traceback_len + 1, supervisor_simple_move)) {
286285
vstr_t vstr;
287-
vstr_init_fixed_buf(&vstr, traceback_len, (char *)prev_traceback_allocation->ptr);
286+
vstr_init_fixed_buf(&vstr, traceback_len + 1, (char *)prev_traceback_allocation->ptr);
288287
mp_print_t print = {&vstr, (mp_print_strn_t)vstr_add_strn};
289288
mp_obj_print_exception(&print, exception);
290-
((char *)prev_traceback_allocation->ptr)[traceback_len] = '\0';
289+
vstr_null_terminated_str(&vstr);
291290
}
292291
} else {
293292
prev_traceback_allocation = NULL;
@@ -344,8 +343,6 @@ STATIC void cleanup_after_vm(supervisor_allocation *heap, mp_obj_t exception) {
344343
// Free the heap last because other modules may reference heap memory and need to shut down.
345344
filesystem_flush();
346345
stop_mp();
347-
free_memory(heap);
348-
supervisor_move_memory();
349346

350347
// Let the workflows know we've reset in case they want to restart.
351348
supervisor_workflow_reset();
@@ -399,18 +396,16 @@ STATIC bool run_code_py(safe_mode_t safe_mode, bool *simulate_reset) {
399396
};
400397
#endif
401398

402-
supervisor_allocation *heap = allocate_remaining_memory();
403-
404399
// Prepare the VM state.
405-
start_mp(heap);
400+
start_mp();
406401

407402
#if CIRCUITPY_USB
408403
usb_setup_with_vm();
409404
#endif
410405

411406
// Check if a different run file has been allocated
412-
if (next_code_allocation) {
413-
next_code_info_t *info = ((next_code_info_t *)next_code_allocation->ptr);
407+
if (next_code_allocation.ptr) {
408+
next_code_info_t *info = ((next_code_info_t *)next_code_allocation.ptr);
414409
info->options &= ~SUPERVISOR_NEXT_CODE_OPT_NEWLY_SET;
415410
next_code_options = info->options;
416411
if (info->filename[0] != '\0') {
@@ -450,15 +445,15 @@ STATIC bool run_code_py(safe_mode_t safe_mode, bool *simulate_reset) {
450445

451446

452447
// Finished executing python code. Cleanup includes filesystem flush and a board reset.
453-
cleanup_after_vm(heap, _exec_result.exception);
448+
cleanup_after_vm(_exec_result.exception);
454449
_exec_result.exception = NULL;
455450

456451
// If a new next code file was set, that is a reason to keep it (obviously). Stuff this into
457452
// the options because it can be treated like any other reason-for-stickiness bit. The
458453
// source is different though: it comes from the options that will apply to the next run,
459454
// while the rest of next_code_options is what applied to this run.
460-
if (next_code_allocation != NULL &&
461-
(((next_code_info_t *)next_code_allocation->ptr)->options & SUPERVISOR_NEXT_CODE_OPT_NEWLY_SET)) {
455+
if (next_code_allocation.ptr &&
456+
(((next_code_info_t *)next_code_allocation.ptr)->options & SUPERVISOR_NEXT_CODE_OPT_NEWLY_SET)) {
462457
next_code_options |= SUPERVISOR_NEXT_CODE_OPT_NEWLY_SET;
463458
}
464459

@@ -706,8 +701,7 @@ STATIC bool run_code_py(safe_mode_t safe_mode, bool *simulate_reset) {
706701

707702
// free code allocation if unused
708703
if ((next_code_options & next_code_stickiness_situation) == 0) {
709-
free_memory(next_code_allocation);
710-
next_code_allocation = NULL;
704+
free_memory(&next_code_allocation);
711705
}
712706

713707
#if CIRCUITPY_STATUS_LED
@@ -746,9 +740,7 @@ STATIC void __attribute__ ((noinline)) run_boot_py(safe_mode_t safe_mode) {
746740

747741
// Do USB setup even if boot.py is not run.
748742

749-
supervisor_allocation *heap = allocate_remaining_memory();
750-
751-
start_mp(heap);
743+
start_mp();
752744

753745
#if CIRCUITPY_USB
754746
// Set up default USB values after boot.py VM starts but before running boot.py.
@@ -834,7 +826,7 @@ STATIC void __attribute__ ((noinline)) run_boot_py(safe_mode_t safe_mode) {
834826

835827
port_post_boot_py(true);
836828

837-
cleanup_after_vm(heap, _exec_result.exception);
829+
cleanup_after_vm(_exec_result.exception);
838830
_exec_result.exception = NULL;
839831

840832
port_post_boot_py(false);
@@ -849,8 +841,7 @@ STATIC int run_repl(void) {
849841
int exit_code = PYEXEC_FORCED_EXIT;
850842
stack_resize();
851843
filesystem_flush();
852-
supervisor_allocation *heap = allocate_remaining_memory();
853-
start_mp(heap);
844+
start_mp();
854845

855846
#if CIRCUITPY_USB
856847
usb_setup_with_vm();
@@ -893,7 +884,7 @@ STATIC int run_repl(void) {
893884
exit_code = PYEXEC_DEEP_SLEEP;
894885
}
895886
#endif
896-
cleanup_after_vm(heap, MP_OBJ_SENTINEL);
887+
cleanup_after_vm(MP_OBJ_SENTINEL);
897888

898889
// Also reset bleio. The above call omits it in case workflows should continue. In this case,
899890
// we're switching straight to another VM so we want to reset.

ports/espressif/common-hal/espidf/__init__.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ bool common_hal_espidf_set_reserved_psram(size_t amount) {
7474
#endif
7575
}
7676

77-
supervisor_allocation *psram_for_idf;
77+
supervisor_allocation psram_for_idf;
7878

7979
void common_hal_espidf_reserve_psram(void) {
8080
#ifdef CONFIG_SPIRAM
@@ -83,8 +83,7 @@ void common_hal_espidf_reserve_psram(void) {
8383
if (reserved_psram == 0) {
8484
return;
8585
}
86-
psram_for_idf = allocate_memory(reserved_psram, true, false);
87-
if (psram_for_idf) {
86+
if (allocate_memory(&psram_for_idf, reserved_psram, NULL)) {
8887
intptr_t psram_for_idf_start = (intptr_t)psram_for_idf->ptr;
8988
intptr_t psram_for_idf_end = psram_for_idf_start + reserved_psram;
9089
ESP_LOGI(TAG, "Reserved %x..%x", psram_for_idf_start, psram_for_idf_end);

py/gc.c

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,23 @@ bool gc_alloc_possible(void) {
500500
return MP_STATE_MEM(gc_pool_start) != 0;
501501
}
502502

503+
void gc_reserve_blocks(size_t start_block, size_t end_block) {
504+
// mark first block as used head
505+
ATB_FREE_TO_HEAD(start_block);
506+
507+
// mark rest of blocks as used tail
508+
// TODO for a run of many blocks can make this more efficient
509+
for (size_t bl = start_block + 1; bl <= end_block; bl++) {
510+
ATB_FREE_TO_TAIL(bl);
511+
}
512+
}
513+
514+
void gc_reserve(void *ptr, size_t n_bytes) {
515+
size_t start_block = BLOCK_FROM_PTR(ptr);
516+
size_t n_blocks = ((n_bytes + BYTES_PER_BLOCK - 1) & (~(BYTES_PER_BLOCK - 1))) / BYTES_PER_BLOCK;
517+
gc_reserve_blocks(start_block, start_block + n_blocks);
518+
}
519+
503520
// We place long lived objects at the end of the heap rather than the start. This reduces
504521
// fragmentation by localizing the heap churn to one portion of memory (the start of the heap.)
505522
void *gc_alloc(size_t n_bytes, unsigned int alloc_flags, bool long_lived) {
@@ -623,14 +640,7 @@ void *gc_alloc(size_t n_bytes, unsigned int alloc_flags, bool long_lived) {
623640
gc_log_change(start_block, end_block - start_block + 1);
624641
#endif
625642

626-
// mark first block as used head
627-
ATB_FREE_TO_HEAD(start_block);
628-
629-
// mark rest of blocks as used tail
630-
// TODO for a run of many blocks can make this more efficient
631-
for (size_t bl = start_block + 1; bl <= end_block; bl++) {
632-
ATB_FREE_TO_TAIL(bl);
633-
}
643+
gc_reserve_blocks(start_block, end_block);
634644

635645
// get pointer to first block
636646
// we must create this pointer before unlocking the GC so a collection can find it

py/gc.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ size_t gc_nbytes(const void *ptr);
7979
bool gc_has_finaliser(const void *ptr);
8080
void *gc_make_long_lived(void *old_ptr);
8181
void *gc_realloc(void *ptr, size_t n_bytes, bool allow_move);
82+
void gc_reserve(void *ptr, size_t n_bytes);
83+
void gc_reserve_blocks(size_t start_block, size_t end_block);
8284

8385
// Prevents a pointer from ever being freed because it establishes a permanent reference to it. Use
8486
// very sparingly because it can leak memory.

shared-bindings/supervisor/__init__.c

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -153,18 +153,13 @@ STATIC mp_obj_t supervisor_set_next_code_file(size_t n_args, const mp_obj_t *pos
153153
}
154154
size_t len;
155155
const char *filename = mp_obj_str_get_data(args.filename.u_obj, &len);
156-
free_memory(next_code_allocation);
156+
free_memory(&next_code_allocation);
157157
if (options != 0 || len != 0) {
158-
next_code_allocation = allocate_memory(align32_size(sizeof(next_code_info_t) + len + 1), false, true);
159-
if (next_code_allocation == NULL) {
160-
m_malloc_fail(sizeof(next_code_info_t) + len + 1);
161-
}
162-
next_code_info_t *next_code = (next_code_info_t *)next_code_allocation->ptr;
158+
allocate_memory_throw(&next_code_allocation, sizeof(next_code_info_t) + len + 1, supervisor_simple_move);
159+
next_code_info_t *next_code = (next_code_info_t *)next_code_allocation.ptr;
163160
next_code->options = options | SUPERVISOR_NEXT_CODE_OPT_NEWLY_SET;
164161
memcpy(&next_code->filename, filename, len);
165162
next_code->filename[len] = '\0';
166-
} else {
167-
next_code_allocation = NULL;
168163
}
169164
return mp_const_none;
170165
}
@@ -293,10 +288,10 @@ STATIC mp_obj_t supervisor_set_usb_identification(size_t n_args, const mp_obj_t
293288
} args;
294289
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, (mp_arg_val_t *)&args);
295290

296-
if (!usb_identification_allocation) {
297-
usb_identification_allocation = allocate_memory(sizeof(usb_identification_t), false, true);
291+
if (!usb_identification_allocation.ptr) {
292+
allocate_memory_throw(&usb_identification_allocation, sizeof(usb_identification_t), supervisor_simple_move);
298293
}
299-
usb_identification_t *identification = (usb_identification_t *)usb_identification_allocation->ptr;
294+
usb_identification_t *identification = (usb_identification_t *)usb_identification_allocation.ptr;
300295

301296
mp_arg_validate_int_range(args.vid.u_int, -1, (1 << 16) - 1, MP_QSTR_vid);
302297
mp_arg_validate_int_range(args.pid.u_int, -1, (1 << 16) - 1, MP_QSTR_pid);

shared-module/is31fl3741/FrameBuffer.c

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,14 @@ void common_hal_is31fl3741_FrameBuffer_construct(is31fl3741_FrameBuffer_obj_t *s
6161
mp_raise_ValueError(translate("LED mappings must match display size"));
6262
}
6363

64-
self->mapping = common_hal_is31fl3741_allocator_impl(sizeof(uint16_t) * len);
64+
allocate_memory_throw(&self->mapping_allocation, sizeof(uint16_t) * len, supervisor_simple_move);
6565
for (size_t i = 0; i < len; i++) {
6666
mp_int_t value = mp_obj_get_int(items[i]);
6767
// We only store up to 16 bits
6868
if (value > 0xFFFF) {
6969
value = 0xFFFF;
7070
}
71-
self->mapping[i] = (uint16_t)value;
71+
self->mapping_allocation.ptr[i] = (uint16_t)value;
7272
}
7373

7474
common_hal_is31fl3741_FrameBuffer_reconstruct(self, framebuffer);
@@ -89,10 +89,11 @@ void common_hal_is31fl3741_FrameBuffer_reconstruct(is31fl3741_FrameBuffer_obj_t
8989
// verify that the matrix is big enough
9090
mp_get_index(mp_obj_get_type(self->framebuffer), self->bufinfo.len, MP_OBJ_NEW_SMALL_INT(self->bufsize - 1), false);
9191
} else {
92-
common_hal_is31fl3741_free_impl(self->bufinfo.buf);
92+
free_memory(&self->framebuffer_allocation);
93+
allocate_memory(&self->framebuffer_allocation, self->bufsize, supervisor_simple_move);
9394

9495
self->framebuffer = NULL;
95-
self->bufinfo.buf = common_hal_is31fl3741_allocator_impl(self->bufsize);
96+
self->bufinfo.buf = self->framebuffer_allocation.buf;
9697
self->bufinfo.len = self->bufsize;
9798
self->bufinfo.typecode = 'H' | MP_OBJ_ARRAY_TYPECODE_FLAG_RW;
9899
}
@@ -117,10 +118,8 @@ void common_hal_is31fl3741_FrameBuffer_deinit(is31fl3741_FrameBuffer_obj_t *self
117118

118119
common_hal_is31fl3741_IS31FL3741_deinit(self->is31fl3741);
119120

120-
if (self->mapping != 0) {
121-
common_hal_is31fl3741_free_impl(self->mapping);
122-
self->mapping = 0;
123-
}
121+
supervisor_free(&self->framebuffer_allocation);
122+
supervisor_free(&self->mapping_allocation);
124123

125124
self->base.type = NULL;
126125

@@ -212,17 +211,3 @@ int common_hal_is31fl3741_FrameBuffer_get_width(is31fl3741_FrameBuffer_obj_t *se
212211
int common_hal_is31fl3741_FrameBuffer_get_height(is31fl3741_FrameBuffer_obj_t *self) {
213212
return self->height;
214213
}
215-
216-
void *common_hal_is31fl3741_allocator_impl(size_t sz) {
217-
supervisor_allocation *allocation = allocate_memory(align32_size(sz), false, true);
218-
return allocation ? allocation->ptr : NULL;
219-
}
220-
221-
void common_hal_is31fl3741_free_impl(void *ptr_in) {
222-
free_memory(allocation_from_ptr(ptr_in));
223-
}
224-
225-
void is31fl3741_FrameBuffer_collect_ptrs(is31fl3741_FrameBuffer_obj_t *self) {
226-
gc_collect_ptr(self->framebuffer);
227-
gc_collect_ptr(self->mapping);
228-
}

shared-module/is31fl3741/allocator.h

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,3 @@
2525
*/
2626

2727
#pragma once
28-
29-
#include <stdbool.h>
30-
#include "py/gc.h"
31-
#include "py/misc.h"
32-
#include "supervisor/memory.h"
33-
34-
extern void *common_hal_is31fl3741_allocator_impl(size_t sz);
35-
extern void common_hal_is31fl3741_free_impl(void *);

shared-module/rgbmatrix/RGBMatrix.c

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include "shared-module/framebufferio/FramebufferDisplay.h"
4242

4343
extern Protomatter_core *_PM_protoPtr;
44+
static supervisor_allocation allocation_slots[4];
4445

4546
void common_hal_rgbmatrix_rgbmatrix_construct(rgbmatrix_rgbmatrix_obj_t *self, int width, int bit_depth, uint8_t rgb_count, uint8_t *rgb_pins, uint8_t addr_count, uint8_t *addr_pins, uint8_t clock_pin, uint8_t latch_pin, uint8_t oe_pin, bool doublebuffer, mp_obj_t framebuffer, int8_t tile, bool serpentine, void *timer) {
4647
self->width = width;
@@ -214,9 +215,22 @@ int common_hal_rgbmatrix_rgbmatrix_get_height(rgbmatrix_rgbmatrix_obj_t *self) {
214215
return computed_height;
215216
}
216217

218+
STATIC supervisor_allocation *get_allocation_slot(void) {
219+
for (size_t i = 0; i < MP_ARRAY_SIZE(allocation_slots); i++) {
220+
if (!allocation_slots[i].ptr) {
221+
return &allocation_slots[i];
222+
}
223+
}
224+
return NULL;
225+
}
226+
217227
void *common_hal_rgbmatrix_allocator_impl(size_t sz) {
218-
supervisor_allocation *allocation = allocate_memory(align32_size(sz), false, true);
219-
return allocation ? allocation->ptr : NULL;
228+
supervisor_allocation *allocation = get_allocation_slot();
229+
if (!allocation) {
230+
return NULL;
231+
}
232+
allocate_memory(allocation, sz, NULL);
233+
return allocation->ptr;
220234
}
221235

222236
void common_hal_rgbmatrix_free_impl(void *ptr_in) {

0 commit comments

Comments
 (0)