Skip to content

Commit 02d830c

Browse files
committed
py: Introduce a Python stack for scoped allocation.
This patch introduces the MICROPY_ENABLE_PYSTACK option (disabled by default) which enables a "Python stack" that allows to allocate and free memory in a scoped, or Last-In-First-Out (LIFO) way, similar to alloca(). A new memory allocation API is introduced along with this Py-stack. It includes both "local" and "nonlocal" LIFO allocation. Local allocation is intended to be equivalent to using alloca(), whereby the same function must free the memory. Nonlocal allocation is where another function may free the memory, so long as it's still LIFO. Follow-up patches will convert all uses of alloca() and VLA to the new scoped allocation API. The old behaviour (using alloca()) will still be available, but when MICROPY_ENABLE_PYSTACK is enabled then alloca() is no longer required or used. The benefits of enabling this option are (or will be once subsequent patches are made to convert alloca()/VLA): - Toolchains without alloca() can use this feature to obtain correct and efficient scoped memory allocation (compared to using the heap instead of alloca(), which is slower). - Even if alloca() is available, enabling the Py-stack gives slightly more efficient use of stack space when calling nested Python functions, due to the way that compilers implement alloca(). - Enabling the Py-stack with the stackless mode allows for even more efficient stack usage, as well as retaining high performance (because the heap is no longer used to build and destroy stackless code states). - With Py-stack and stackless enabled, Python-calling-Python is no longer recursive in the C mp_execute_bytecode function. The micropython.pystack_use() function is included to measure usage of the Python stack.
1 parent 5b8998d commit 02d830c

16 files changed

Lines changed: 249 additions & 2 deletions

py/gc.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,11 +320,18 @@ void gc_collect_start(void) {
320320
#endif
321321
MP_STATE_MEM(gc_stack_overflow) = 0;
322322
MP_STATE_MEM(gc_sp) = MP_STATE_MEM(gc_stack);
323+
323324
// Trace root pointers. This relies on the root pointers being organised
324325
// correctly in the mp_state_ctx structure. We scan nlr_top, dict_locals,
325326
// dict_globals, then the root pointer section of mp_state_vm.
326327
void **ptrs = (void**)(void*)&mp_state_ctx;
327328
gc_collect_root(ptrs, offsetof(mp_state_ctx_t, vm.qstr_last_chunk) / sizeof(void*));
329+
330+
#if MICROPY_ENABLE_PYSTACK
331+
// Trace root pointers from the Python stack.
332+
ptrs = (void**)(void*)MP_STATE_THREAD(pystack_start);
333+
gc_collect_root(ptrs, (MP_STATE_THREAD(pystack_cur) - MP_STATE_THREAD(pystack_start)) / sizeof(void*));
334+
#endif
328335
}
329336

330337
void gc_collect_root(void **ptrs, size_t len) {

py/modmicropython.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,13 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_stack_use_obj, mp_micropython_st
112112

113113
#endif // MICROPY_PY_MICROPYTHON_MEM_INFO
114114

115+
#if MICROPY_ENABLE_PYSTACK
116+
STATIC mp_obj_t mp_micropython_pystack_use(void) {
117+
return MP_OBJ_NEW_SMALL_INT(mp_pystack_usage());
118+
}
119+
STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_pystack_use_obj, mp_micropython_pystack_use);
120+
#endif
121+
115122
#if MICROPY_ENABLE_GC
116123
STATIC mp_obj_t mp_micropython_heap_lock(void) {
117124
gc_lock();
@@ -167,6 +174,9 @@ STATIC const mp_rom_map_elem_t mp_module_micropython_globals_table[] = {
167174
#if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF && (MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE == 0)
168175
{ MP_ROM_QSTR(MP_QSTR_alloc_emergency_exception_buf), MP_ROM_PTR(&mp_alloc_emergency_exception_buf_obj) },
169176
#endif
177+
#if MICROPY_ENABLE_PYSTACK
178+
{ MP_ROM_QSTR(MP_QSTR_pystack_use), MP_ROM_PTR(&mp_micropython_pystack_use_obj) },
179+
#endif
170180
#if MICROPY_ENABLE_GC
171181
{ MP_ROM_QSTR(MP_QSTR_heap_lock), MP_ROM_PTR(&mp_micropython_heap_lock_obj) },
172182
{ MP_ROM_QSTR(MP_QSTR_heap_unlock), MP_ROM_PTR(&mp_micropython_heap_unlock_obj) },

py/modthread.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,12 @@ STATIC void *thread_entry(void *args_in) {
165165
mp_stack_set_top(&ts + 1); // need to include ts in root-pointer scan
166166
mp_stack_set_limit(args->stack_size);
167167

168+
#if MICROPY_ENABLE_PYSTACK
169+
// TODO threading and pystack is not fully supported, for now just make a small stack
170+
mp_obj_t mini_pystack[128];
171+
mp_pystack_init(mini_pystack, &mini_pystack[128]);
172+
#endif
173+
168174
// set locals and globals from the calling context
169175
mp_locals_set(args->dict_locals);
170176
mp_globals_set(args->dict_globals);

py/mpconfig.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,17 @@
441441
#define MICROPY_ENABLE_FINALISER (0)
442442
#endif
443443

444+
// Whether to enable a separate allocator for the Python stack.
445+
// If enabled then the code must call mp_pystack_init before mp_init.
446+
#ifndef MICROPY_ENABLE_PYSTACK
447+
#define MICROPY_ENABLE_PYSTACK (0)
448+
#endif
449+
450+
// Number of bytes that memory returned by mp_pystack_alloc will be aligned by.
451+
#ifndef MICROPY_PYSTACK_ALIGN
452+
#define MICROPY_PYSTACK_ALIGN (8)
453+
#endif
454+
444455
// Whether to check C stack usage. C stack used for calling Python functions,
445456
// etc. Not checking means segfault on overflow.
446457
#ifndef MICROPY_STACK_CHECK

py/mpstate.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,12 @@ typedef struct _mp_state_thread_t {
224224
#if MICROPY_STACK_CHECK
225225
size_t stack_limit;
226226
#endif
227+
228+
#if MICROPY_ENABLE_PYSTACK
229+
uint8_t *pystack_start;
230+
uint8_t *pystack_end;
231+
uint8_t *pystack_cur;
232+
#endif
227233
} mp_state_thread_t;
228234

229235
// This structure combines the above 3 structures.

py/nlr.h

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,32 @@ struct _nlr_buf_t {
6262
#if MICROPY_NLR_SETJMP
6363
jmp_buf jmpbuf;
6464
#endif
65+
66+
#if MICROPY_ENABLE_PYSTACK
67+
void *pystack;
68+
#endif
6569
};
6670

71+
// Helper macros to save/restore the pystack state
72+
#if MICROPY_ENABLE_PYSTACK
73+
#define MP_NLR_SAVE_PYSTACK(nlr_buf) (nlr_buf)->pystack = MP_STATE_THREAD(pystack_cur)
74+
#define MP_NLR_RESTORE_PYSTACK(nlr_buf) MP_STATE_THREAD(pystack_cur) = (nlr_buf)->pystack
75+
#else
76+
#define MP_NLR_SAVE_PYSTACK(nlr_buf) (void)nlr_buf
77+
#define MP_NLR_RESTORE_PYSTACK(nlr_buf) (void)nlr_buf
78+
#endif
79+
6780
#if MICROPY_NLR_SETJMP
6881
#include "py/mpstate.h"
6982

7083
NORETURN void nlr_setjmp_jump(void *val);
7184
// nlr_push() must be defined as a macro, because "The stack context will be
7285
// invalidated if the function which called setjmp() returns."
73-
#define nlr_push(buf) ((buf)->prev = MP_STATE_THREAD(nlr_top), MP_STATE_THREAD(nlr_top) = (buf), setjmp((buf)->jmpbuf))
86+
#define nlr_push(buf) ( \
87+
(buf)->prev = MP_STATE_THREAD(nlr_top), \
88+
MP_NLR_SAVE_PYSTACK(buf), \
89+
MP_STATE_THREAD(nlr_top) = (buf), \
90+
setjmp((buf)->jmpbuf))
7491
#define nlr_pop() { MP_STATE_THREAD(nlr_top) = MP_STATE_THREAD(nlr_top)->prev; }
7592
#define nlr_jump(val) nlr_setjmp_jump(val)
7693
#else

py/nlrsetjmp.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ void nlr_setjmp_jump(void *val) {
3535
nlr_jump_fail(val);
3636
}
3737
top->ret_val = val;
38+
MP_NLR_RESTORE_PYSTACK(top);
3839
*top_ptr = top->prev;
3940
longjmp(top->jmpbuf, 1);
4041
}

py/nlrthumb.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ __attribute__((naked)) unsigned int nlr_push(nlr_buf_t *nlr) {
8282
__attribute__((used)) unsigned int nlr_push_tail(nlr_buf_t *nlr) {
8383
nlr_buf_t **top = &MP_STATE_THREAD(nlr_top);
8484
nlr->prev = *top;
85+
MP_NLR_SAVE_PYSTACK(nlr);
8586
*top = nlr;
8687
return 0; // normal return
8788
}
@@ -99,6 +100,7 @@ NORETURN __attribute__((naked)) void nlr_jump(void *val) {
99100
}
100101

101102
top->ret_val = val;
103+
MP_NLR_RESTORE_PYSTACK(top);
102104
*top_ptr = top->prev;
103105

104106
__asm volatile (

py/nlrx64.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ unsigned int nlr_push(nlr_buf_t *nlr) {
9191
__attribute__((used)) unsigned int nlr_push_tail(nlr_buf_t *nlr) {
9292
nlr_buf_t **top = &MP_STATE_THREAD(nlr_top);
9393
nlr->prev = *top;
94+
MP_NLR_SAVE_PYSTACK(nlr);
9495
*top = nlr;
9596
return 0; // normal return
9697
}
@@ -108,6 +109,7 @@ NORETURN void nlr_jump(void *val) {
108109
}
109110

110111
top->ret_val = val;
112+
MP_NLR_RESTORE_PYSTACK(top);
111113
*top_ptr = top->prev;
112114

113115
__asm volatile (

py/nlrx86.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ unsigned int nlr_push(nlr_buf_t *nlr) {
7373
__attribute__((used)) unsigned int nlr_push_tail(nlr_buf_t *nlr) {
7474
nlr_buf_t **top = &MP_STATE_THREAD(nlr_top);
7575
nlr->prev = *top;
76+
MP_NLR_SAVE_PYSTACK(nlr);
7677
*top = nlr;
7778
return 0; // normal return
7879
}
@@ -90,6 +91,7 @@ NORETURN void nlr_jump(void *val) {
9091
}
9192

9293
top->ret_val = val;
94+
MP_NLR_RESTORE_PYSTACK(top);
9395
*top_ptr = top->prev;
9496

9597
__asm volatile (

0 commit comments

Comments
 (0)