Skip to content

Commit 86f2c28

Browse files
projectgusdpgeorge
authored andcommitted
py: Add new cstack API for stack checking, with limit margin macro.
Currently the stack limit margin is hard-coded in each port's call to `mp_stack_set_limit()`, but on threaded ports it's fiddlier and can lead to bugs (such as incorrect thread stack margin on esp32). This commit provides a new API to initialise the C Stack in one function call, with a config macro to set the margin. Where possible the new call is inlined to reduce code size in thread-free ports. Intended replacement for `MP_TASK_STACK_LIMIT_MARGIN` on esp32. The previous `stackctrl.h` API is still present and unmodified apart from a deprecation comment. However it's not available when the `MICROPY_PREVIEW_VERSION_2` macro is set. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <[email protected]>
1 parent 6c870dc commit 86f2c28

18 files changed

+167
-27
lines changed

examples/natmod/re/re.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
const char *stack_top;
1313

14-
void mp_stack_check(void) {
14+
void mp_cstack_check(void) {
1515
// Assumes descending stack on target
1616
volatile char dummy;
1717
if (stack_top - &dummy >= STACK_LIMIT) {

extmod/modre.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@
3131
#include "py/runtime.h"
3232
#include "py/binary.h"
3333
#include "py/objstr.h"
34-
#include "py/stackctrl.h"
34+
#include "py/cstack.h"
3535

3636
#if MICROPY_PY_BUILTINS_STR_UNICODE
3737
#include "py/unicode.h"
3838
#endif
3939

4040
#if MICROPY_PY_RE
4141

42-
#define re1_5_stack_chk() MP_STACK_CHECK()
42+
#define re1_5_stack_chk() mp_cstack_check()
4343

4444
#include "lib/re1.5/re1.5.h"
4545

py/cstack.c

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* This file is part of the MicroPython project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2014 Paul Sokolovsky
7+
* Copryight (c) 2024 Angus Gratton
8+
*
9+
* Permission is hereby granted, free of charge, to any person obtaining a copy
10+
* of this software and associated documentation files (the "Software"), to deal
11+
* in the Software without restriction, including without limitation the rights
12+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
* copies of the Software, and to permit persons to whom the Software is
14+
* furnished to do so, subject to the following conditions:
15+
*
16+
* The above copyright notice and this permission notice shall be included in
17+
* all copies or substantial portions of the Software.
18+
*
19+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
* THE SOFTWARE.
26+
*/
27+
28+
#include "py/runtime.h"
29+
#include "py/cstack.h"
30+
31+
void mp_cstack_init_with_sp_here(size_t stack_size) {
32+
#if __GNUC__ >= 13
33+
#pragma GCC diagnostic push
34+
#pragma GCC diagnostic ignored "-Wdangling-pointer"
35+
#endif
36+
volatile int stack_dummy;
37+
mp_cstack_init_with_top((void *)&stack_dummy, stack_size);
38+
#if __GNUC__ >= 13
39+
#pragma GCC diagnostic pop
40+
#endif
41+
}
42+
43+
mp_uint_t mp_cstack_usage(void) {
44+
// Assumes descending stack
45+
volatile int stack_dummy;
46+
return MP_STATE_THREAD(stack_top) - (char *)&stack_dummy;
47+
}
48+
49+
#if MICROPY_STACK_CHECK
50+
51+
void mp_cstack_check(void) {
52+
if (mp_cstack_usage() >= MP_STATE_THREAD(stack_limit)) {
53+
mp_raise_recursion_depth();
54+
}
55+
}
56+
57+
#endif // MICROPY_STACK_CHECK

py/cstack.h

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* This file is part of the MicroPython project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2014 Paul Sokolovsky
7+
* Copyright (c) 2024 Angus Gratton
8+
*
9+
* Permission is hereby granted, free of charge, to any person obtaining a copy
10+
* of this software and associated documentation files (the "Software"), to deal
11+
* in the Software without restriction, including without limitation the rights
12+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
* copies of the Software, and to permit persons to whom the Software is
14+
* furnished to do so, subject to the following conditions:
15+
*
16+
* The above copyright notice and this permission notice shall be included in
17+
* all copies or substantial portions of the Software.
18+
*
19+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
* THE SOFTWARE.
26+
*/
27+
#ifndef MICROPY_INCLUDED_PY_CSTACK_H
28+
#define MICROPY_INCLUDED_PY_CSTACK_H
29+
30+
#include "py/mpstate.h"
31+
32+
// Both init functions below accept the full stack size. Set the
33+
// MICROPY_STACK_CHECK_MARGIN to the number of bytes subtracted to account
34+
// for stack usage between checks.
35+
36+
void mp_cstack_init_with_sp_here(size_t stack_size);
37+
38+
inline static void mp_cstack_init_with_top(void *top, size_t stack_size) {
39+
MP_STATE_THREAD(stack_top) = (char *)top;
40+
41+
#if MICROPY_STACK_CHECK
42+
assert(stack_size > MICROPY_STACK_CHECK_MARGIN); // Should be enforced by port
43+
MP_STATE_THREAD(stack_limit) = stack_size - MICROPY_STACK_CHECK_MARGIN;
44+
#else
45+
(void)stack_size;
46+
#endif
47+
}
48+
49+
mp_uint_t mp_cstack_usage(void);
50+
51+
#if MICROPY_STACK_CHECK
52+
53+
void mp_cstack_check(void);
54+
55+
#else
56+
57+
inline static void mp_cstack_check(void) {
58+
// No-op when stack checking is disabled
59+
}
60+
61+
#endif
62+
63+
#endif // MICROPY_INCLUDED_PY_CSTACK_H

py/modmicropython.c

+4-4
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
#include <stdio.h>
2828

2929
#include "py/builtin.h"
30-
#include "py/stackctrl.h"
30+
#include "py/cstack.h"
3131
#include "py/runtime.h"
3232
#include "py/gc.h"
3333
#include "py/mphal.h"
@@ -76,9 +76,9 @@ mp_obj_t mp_micropython_mem_info(size_t n_args, const mp_obj_t *args) {
7676
#endif
7777
#if MICROPY_STACK_CHECK
7878
mp_printf(&mp_plat_print, "stack: " UINT_FMT " out of " UINT_FMT "\n",
79-
mp_stack_usage(), (mp_uint_t)MP_STATE_THREAD(stack_limit));
79+
mp_cstack_usage(), (mp_uint_t)MP_STATE_THREAD(stack_limit));
8080
#else
81-
mp_printf(&mp_plat_print, "stack: " UINT_FMT "\n", mp_stack_usage());
81+
mp_printf(&mp_plat_print, "stack: " UINT_FMT "\n", mp_cstack_usage());
8282
#endif
8383
#if MICROPY_ENABLE_GC
8484
gc_dump_info(&mp_plat_print);
@@ -111,7 +111,7 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_micropython_qstr_info_obj, 0, 1, m
111111

112112
#if MICROPY_PY_MICROPYTHON_STACK_USE
113113
static mp_obj_t mp_micropython_stack_use(void) {
114-
return MP_OBJ_NEW_SMALL_INT(mp_stack_usage());
114+
return MP_OBJ_NEW_SMALL_INT(mp_cstack_usage());
115115
}
116116
static MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_stack_use_obj, mp_micropython_stack_use);
117117
#endif

py/modthread.c

-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
#include <string.h>
2929

3030
#include "py/runtime.h"
31-
#include "py/stackctrl.h"
3231

3332
#if MICROPY_PY_THREAD
3433

py/mpconfig.h

+7
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,13 @@
690690
#define MICROPY_STACK_CHECK (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
691691
#endif
692692

693+
// Additional margin between the places in the runtime where Python stack is
694+
// checked and the actual end of the C stack. Needs to be large enough to avoid
695+
// overflows from function calls made between checks.
696+
#ifndef MICROPY_STACK_CHECK_MARGIN
697+
#define MICROPY_STACK_CHECK_MARGIN (0)
698+
#endif
699+
693700
// Whether to have an emergency exception buffer
694701
#ifndef MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF
695702
#define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (0)

py/obj.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
#include "py/objint.h"
3535
#include "py/objstr.h"
3636
#include "py/runtime.h"
37-
#include "py/stackctrl.h"
37+
#include "py/cstack.h"
3838
#include "py/stream.h" // for mp_obj_print
3939

4040
// Allocates an object and also sets type, for mp_obj_malloc{,_var} macros.
@@ -117,7 +117,7 @@ const char *mp_obj_get_type_str(mp_const_obj_t o_in) {
117117

118118
void mp_obj_print_helper(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) {
119119
// There can be data structures nested too deep, or just recursive
120-
MP_STACK_CHECK();
120+
mp_cstack_check();
121121
#ifndef NDEBUG
122122
if (o_in == MP_OBJ_NULL) {
123123
mp_print_str(print, "(nil)");

py/objfun.c

+5-5
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
#include "py/objfun.h"
3333
#include "py/runtime.h"
3434
#include "py/bc.h"
35-
#include "py/stackctrl.h"
35+
#include "py/cstack.h"
3636

3737
#if MICROPY_DEBUG_VERBOSE // print debugging info
3838
#define DEBUG_PRINT (1)
@@ -194,7 +194,7 @@ static void dump_args(const mp_obj_t *a, size_t sz) {
194194

195195
#if MICROPY_STACKLESS
196196
mp_code_state_t *mp_obj_fun_bc_prepare_codestate(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) {
197-
MP_STACK_CHECK();
197+
mp_cstack_check();
198198
mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in);
199199

200200
size_t n_state, state_size;
@@ -225,7 +225,7 @@ mp_code_state_t *mp_obj_fun_bc_prepare_codestate(mp_obj_t self_in, size_t n_args
225225
#endif
226226

227227
static mp_obj_t fun_bc_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) {
228-
MP_STACK_CHECK();
228+
mp_cstack_check();
229229

230230
DEBUG_printf("Input n_args: " UINT_FMT ", n_kw: " UINT_FMT "\n", n_args, n_kw);
231231
DEBUG_printf("Input pos args: ");
@@ -397,7 +397,7 @@ mp_obj_t mp_obj_new_fun_bc(const mp_obj_t *def_args, const byte *code, const mp_
397397
#if MICROPY_EMIT_NATIVE
398398

399399
static mp_obj_t fun_native_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) {
400-
MP_STACK_CHECK();
400+
mp_cstack_check();
401401
mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in);
402402
mp_call_fun_t fun = mp_obj_fun_native_get_function_start(self);
403403
return fun(self_in, n_args, n_kw, args);
@@ -431,7 +431,7 @@ MP_DEFINE_CONST_OBJ_TYPE(
431431
#if MICROPY_EMIT_NATIVE
432432

433433
static mp_obj_t fun_viper_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) {
434-
MP_STACK_CHECK();
434+
mp_cstack_check();
435435
mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in);
436436
mp_call_fun_t fun = MICROPY_MAKE_POINTER_CALLABLE((void *)self->bytecode);
437437
return fun(self_in, n_args, n_kw, args);

py/objgenerator.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
#include "py/objstr.h"
3434
#include "py/objgenerator.h"
3535
#include "py/objfun.h"
36-
#include "py/stackctrl.h"
36+
#include "py/cstack.h"
3737

3838
// Instance of GeneratorExit exception - needed by generator.close()
3939
const mp_obj_exception_t mp_const_GeneratorExit_obj = {{&mp_type_GeneratorExit}, 0, 0, NULL, (mp_obj_tuple_t *)&mp_const_empty_tuple_obj};
@@ -151,7 +151,7 @@ static void gen_instance_print(const mp_print_t *print, mp_obj_t self_in, mp_pri
151151
}
152152

153153
mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value, mp_obj_t *ret_val) {
154-
MP_STACK_CHECK();
154+
mp_cstack_check();
155155
mp_check_self(mp_obj_is_type(self_in, &mp_type_gen_instance));
156156
mp_obj_gen_instance_t *self = MP_OBJ_TO_PTR(self_in);
157157
if (self->code_state.ip == 0) {

py/objlist.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
#include "py/objlist.h"
3131
#include "py/runtime.h"
32-
#include "py/stackctrl.h"
32+
#include "py/cstack.h"
3333

3434
static mp_obj_t mp_obj_new_list_iterator(mp_obj_t list, size_t cur, mp_obj_iter_buf_t *iter_buf);
3535
static mp_obj_list_t *list_new(size_t n);
@@ -291,7 +291,7 @@ static mp_obj_t list_pop(size_t n_args, const mp_obj_t *args) {
291291
}
292292

293293
static void mp_quicksort(mp_obj_t *head, mp_obj_t *tail, mp_obj_t key_fn, mp_obj_t binop_less_result) {
294-
MP_STACK_CHECK();
294+
mp_cstack_check();
295295
while (head < tail) {
296296
mp_obj_t *h = head - 1;
297297
mp_obj_t *t = tail;

py/objstr.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
#include "py/objstr.h"
3333
#include "py/objlist.h"
3434
#include "py/runtime.h"
35-
#include "py/stackctrl.h"
35+
#include "py/cstack.h"
3636

3737
#if MICROPY_PY_BUILTINS_STR_OP_MODULO
3838
static mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_t *args, mp_obj_t dict);
@@ -1181,7 +1181,7 @@ static vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar
11811181
// type ::= "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | "o" | "s" | "x" | "X" | "%"
11821182

11831183
// recursively call the formatter to format any nested specifiers
1184-
MP_STACK_CHECK();
1184+
mp_cstack_check();
11851185
vstr_t format_spec_vstr = mp_obj_str_format_helper(format_spec, str, arg_i, n_args, args, kwargs);
11861186
const char *s = vstr_null_terminated_str(&format_spec_vstr);
11871187
const char *stop = s + format_spec_vstr.len;

py/py.cmake

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ set(MICROPY_SOURCE_PY
2020
${MICROPY_PY_DIR}/builtinhelp.c
2121
${MICROPY_PY_DIR}/builtinimport.c
2222
${MICROPY_PY_DIR}/compile.c
23+
${MICROPY_PY_DIR}/cstack.c
2324
${MICROPY_PY_DIR}/emitbc.c
2425
${MICROPY_PY_DIR}/emitcommon.c
2526
${MICROPY_PY_DIR}/emitglue.c

py/py.mk

+1
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ PY_CORE_O_BASENAME = $(addprefix py/,\
131131
nativeglue.o \
132132
pairheap.o \
133133
ringbuf.o \
134+
cstack.o \
134135
stackctrl.o \
135136
argcheck.o \
136137
warning.o \

py/runtime.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
#include "py/stream.h"
4444
#include "py/runtime.h"
4545
#include "py/builtin.h"
46-
#include "py/stackctrl.h"
46+
#include "py/cstack.h"
4747
#include "py/gc.h"
4848

4949
#if MICROPY_DEBUG_VERBOSE // print debugging info
@@ -1374,7 +1374,7 @@ mp_obj_t mp_iternext_allow_raise(mp_obj_t o_in) {
13741374
// will always return MP_OBJ_STOP_ITERATION instead of raising StopIteration() (or any subclass thereof)
13751375
// may raise other exceptions
13761376
mp_obj_t mp_iternext(mp_obj_t o_in) {
1377-
MP_STACK_CHECK(); // enumerate, filter, map and zip can recursively call mp_iternext
1377+
mp_cstack_check(); // enumerate, filter, map and zip can recursively call mp_iternext
13781378
const mp_obj_type_t *type = mp_obj_get_type(o_in);
13791379
if (TYPE_HAS_ITERNEXT(type)) {
13801380
MP_STATE_THREAD(stop_iteration_arg) = MP_OBJ_NULL;

py/runtime.h

+2-3
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
#include "py/mpstate.h"
3030
#include "py/pystack.h"
31-
#include "py/stackctrl.h"
31+
#include "py/cstack.h"
3232

3333
// For use with mp_call_function_1_from_nlr_jump_callback.
3434
#define MP_DEFINE_NLR_JUMP_CALLBACK_FUNCTION_1(ctx, f, a) \
@@ -159,8 +159,7 @@ void mp_call_function_1_from_nlr_jump_callback(void *ctx_in);
159159
static inline void mp_thread_init_state(mp_state_thread_t *ts, size_t stack_size, mp_obj_dict_t *locals, mp_obj_dict_t *globals) {
160160
mp_thread_set_state(ts);
161161

162-
mp_stack_set_top(ts + 1); // need to include ts in root-pointer scan
163-
mp_stack_set_limit(stack_size);
162+
mp_cstack_init_with_top(ts + 1, stack_size); // need to include ts in root-pointer scan
164163

165164
// GC starts off unlocked
166165
ts->gc_lock_depth = 0;

py/stackctrl.c

+7
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@
2424
* THE SOFTWARE.
2525
*/
2626

27+
// This API is deprecated, please use py/cstack.h instead
28+
2729
#include "py/runtime.h"
30+
31+
#if !MICROPY_PREVIEW_VERSION_2
32+
2833
#include "py/stackctrl.h"
2934

3035
void mp_stack_ctrl_init(void) {
@@ -62,3 +67,5 @@ void mp_stack_check(void) {
6267
}
6368

6469
#endif // MICROPY_STACK_CHECK
70+
71+
#endif // !MICROPY_PREVIEW_VERSION_2

0 commit comments

Comments
 (0)