8000 WIP: py: Implement mp_sched_vm_abort() to force a return to the very top level by dpgeorge · Pull Request #10241 · micropython/micropython · GitHub
[go: up one dir, main page]

Skip to content

WIP: py: Implement mp_sched_vm_abort() to force a return to the very top level #10241

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

Closed
wants to merge 6 commits into from
Closed
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
18 changes: 13 additions & 5 deletions ports/stm32/extint.c
Original file line number Diff line number Diff line change
Expand Up @@ -708,16 +708,24 @@ void Handle_EXTI_Irq(uint32_t line) {
// When executing code within a handler we must lock the GC to prevent
// any memory allocations. We must also catch any exceptions.
gc_lock();
nlr_buf_t *nlr_abort_orig = nlr_get_abort();
nlr_buf_t nlr;
if (nlr_push(&nlr) == 0) {
nlr_set_abort(&nlr);
mp_call_function_1(*cb, pyb_extint_callback_arg[line]);
nlr_pop();
} else {
// Uncaught exception; disable the callback so it doesn't run again.
*cb = mp_const_none;
extint_disable(line);
mp_printf(MICROPY_ERROR_PRINTER, "uncaught exception in ExtInt interrupt handler line %u\n", (unsigned int)line);
mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val));
nlr_set_abort(nlr_abort_orig);
if (nlr.ret_val == NULL) {
// Reschedule the abort.
mp_sched_vm_abort();
} else {
// Uncaught exception; disable the callback so it doesn't run again.
*cb = mp_const_none;
extint_disable(line);
mp_printf(MICROPY_ERROR_PRINTER, "uncaught exception in ExtInt interrupt handler line %u\n", (unsigned int)line);
mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val));
}
}
gc_unlock();
mp_sched_unlock();
Expand Down
8 changes: 6 additions & 2 deletions ports/stm32/modmachine.c
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,12 @@ STATIC mp_obj_t machine_reset(void) {
MP_DEFINE_CONST_FUN_OBJ_0(machine_reset_obj, machine_reset);

STATIC mp_obj_t machine_soft_reset(void) {
pyexec_system_exit = PYEXEC_FORCED_EXIT;
mp_raise_type(&mp_type_SystemExit);
mp_sched_vm_abort();
// Either handle the pending abort, or wait if we aren't the main thread.
for (;;) {
MICROPY_EVENT_POLL_HOOK;
}
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_0(machine_soft_reset_obj, machine_soft_reset);

Expand Down
1 change: 1 addition & 0 deletions ports/stm32/mpconfigport.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT)
#endif
#define MICROPY_USE_INTERNAL_ERRNO (1)
#define MICROPY_SCHED_VM_ABORT (1)
#define MICROPY_SCHEDULER_STATIC_NODES (1)
#define MICROPY_SCHEDULER_DEPTH (8)
#define MICROPY_VFS (1)
Expand Down
9 changes: 9 additions & 0 deletions py/mpconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,11 @@ typedef double mp_float_t;
#define MICROPY_USE_INTERNAL_PRINTF (1)
#endif

// Whether to support mp_sched_vm_abort to asynchronously abort to the top level.
#ifndef MICROPY_SCHED_VM_ABORT
#define MICROPY_SCHED_VM_ABORT (0)
#endif

// Support for internal scheduler
#ifndef MICROPY_ENABLE_SCHEDULER
#define MICROPY_ENABLE_SCHEDULER (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
Expand Down Expand Up @@ -1735,6 +1740,10 @@ typedef double mp_float_t;
#define MICROPY_WRAP_MP_SCHED_SCHEDULE(f) f
#endif

#ifndef MICROPY_WRAP_MP_SCHED_VM_ABORT
#define MICROPY_WRAP_MP_SCHED_VM_ABORT(f) f
#endif

/*****************************************************************************/
/* Miscellaneous settings */

Expand Down
26 changes: 17 additions & 9 deletions py/mpstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#ifndef MICROPY_INCLUDED_PY_MPSTATE_H
#define MICROPY_INCLUDED_PY_MPSTATE_H

#include <stdbool.h>
#include <stdint.h>

#include "py/mpconfig.h"
Expand Down Expand Up @@ -61,11 +62,6 @@ typedef struct mp_dynamic_compiler_t {
extern mp_dynamic_compiler_t mp_dynamic_compiler;
#endif

// These are the values for sched_state
#define MP_SCHED_IDLE (1)
#define MP_SCHED_LOCKED (-1)
#define MP_SCHED_PENDING (0) // 0 so it's a quick check in the VM

typedef struct _mp_sched_item_t {
mp_obj_t func;
mp_obj_t arg;
Expand Down Expand Up @@ -211,7 +207,12 @@ typedef struct _mp_state_vm_t {
#endif

#if MICROPY_ENABLE_SCHEDULER
volatile int16_t sched_state;
// This counts the depth of calls to mp_sched_lock/mp_sched_unlock.
volatile uint16_t sched_lock_depth;

// These index sched_queue.
uint8_t sched_len;
uint8_t sched_idx;

#if MICROPY_SCHEDULER_STATIC_NODES
// These will usually point to statically allocated memory. They are not
Expand All @@ -220,10 +221,11 @@ typedef struct _mp_state_vm_t {
struct _mp_sched_node_t *sched_head;
struct _mp_sched_node_t *sched_tail;
#endif
#endif

// These index sched_queue.
uint8_t sched_len;
uint8_t sched_idx;
#if MICROPY_SCHED_VM_ABORT
bool vm_abort;
nlr_buf_t *nlr_abort;
#endif

#if MICROPY_PY_THREAD_GIL
Expand Down Expand Up @@ -297,8 +299,14 @@ extern mp_state_ctx_t mp_state_ctx;
#if MICROPY_PY_THREAD
extern mp_state_thread_t *mp_thread_get_state(void);
#define MP_STATE_THREAD(x) (mp_thread_get_state()->x)
static inline bool mp_thread_is_main_thread(void) {
return mp_thread_get_state() == &mp_state_ctx.thread;
}
#else
#define MP_STATE_THREAD(x) MP_STATE_MAIN_THREAD(x)
static inline bool mp_thread_is_main_thread(void) {
return true;
}
#endif

#endif // MICROPY_INCLUDED_PY_MPSTATE_H
7 changes: 7 additions & 0 deletions py/nlr.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,10 @@ void nlr_pop(void) {
nlr_buf_t **top = &MP_STATE_THREAD(nlr_top);
*top = (*top)->prev;
}

#if MICROPY_SCHED_VM_ABORT
NORETURN void nlr_jump_abort(void) {
MP_STATE_THREAD(nlr_top) = MP_STATE_VM(nlr_abort);
nlr_jump(NULL);
}
#endif
6 changes: 6 additions & 0 deletions py/nlr.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ unsigned int nlr_push_tail(nlr_buf_t *top);
void nlr_pop(void);
NORETURN void nlr_jump(void *val);

#if MICROPY_SCHED_VM_ABORT
#define nlr_set_abort(buf) MP_STATE_VM(nlr_abort) = buf
#define nlr_get_abort() MP_STATE_VM(nlr_abort)
NORETURN void nlr_jump_abort(void);
#endif

// This must be implemented by a port. It's called by nlr_jump
// if no nlr buf has been pushed. It must not return, but rather
// should bail out with a fatal error.
Expand Down
9 changes: 4 additions & 5 deletions py/runtime.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,15 @@ void mp_init(void) {

// no pending exceptions to start with
MP_STATE_THREAD(mp_pending_exception) = MP_OBJ_NULL;

#if MICROPY_ENABLE_SCHEDULER
#if MICROPY_SCHEDULER_STATIC_NODES
if (MP_STATE_VM(sched_head) == NULL) {
// no pending callbacks to start with
MP_STATE_VM(sched_state) = MP_SCHED_IDLE;
} else {
if (MP_STATE_VM(sched_head) != NULL) {
// pending callbacks are on the list, eg from before a soft reset
MP_STATE_VM(sched_state) = MP_SCHED_PENDING;
MP_STATE_MAIN_THREAD(mp_pending_exception) = MP_OBJ_SENTINEL;
}
#endif
MP_STATE_VM(sched_lock_depth) = 0;
MP_STATE_VM(sched_idx) = 0;
MP_STATE_VM(sched_len) = 0;
#endif
Expand Down
3 changes: 3 additions & 0 deletions py/runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ void mp_deinit(void);

void mp_sched_exception(mp_obj_t exc);
void mp_sched_keyboard_interrupt(void);
#if MICROPY_SCHED_VM_ABORT
void mp_sched_vm_abort(void);
#endif
void mp_handle_pending(bool raise_exc);

#if MICROPY_ENABLE_SCHEDULER
Expand Down
94 changes: 55 additions & 39 deletions py/scheduler.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,6 @@
// sources such as interrupts and UNIX signal handlers).
void MICROPY_WRAP_MP_SCHED_EXCEPTION(mp_sched_exception)(mp_obj_t exc) {
MP_STATE_MAIN_THREAD(mp_pending_exception) = exc;

#if MICROPY_ENABLE_SCHEDULER && !MICROPY_PY_THREAD
// Optimisation for the case where we have scheduler but no threading.
// Allows the VM to do a single check to exclude both pending exception
// and queued tasks.
if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) {
MP_STATE_VM(sched_state) = MP_SCHED_PENDING;
}
#endif
}

#if MICROPY_KBD_EXCEPTION
Expand All @@ -51,13 +42,27 @@ void MICROPY_WRAP_MP_SCHED_KEYBOARD_INTERRUPT(mp_sched_keyboard_interrupt)(void)
}
#endif

#if MICROPY_SCHED_VM_ABORT
void MICROPY_WRAP_MP_SCHED_VM_ABORT(mp_sched_vm_abort)(void) {
mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();
MP_STATE_VM(vm_abort) = true;
mp_sched_exception(MP_OBJ_SENTINEL);
MICROPY_END_ATOMIC_SECTION(atomic_state);
}
#endif

#if MICROPY_ENABLE_SCHEDULER

#define IDX_MASK(i) ((i) & (MICROPY_SCHEDULER_DEPTH - 1))

// This is a macro so it is guaranteed to be inlined in functions like
// The following are macros so they are guaranteed to be inlined in functions like
// mp_sched_schedule that may be located in a special memory region.
#define mp_sched_full() (mp_sched_num_pending() == MICROPY_SCHEDULER_DEPTH)
#if MICROPY_SCHEDULER_STATIC_NODES
#define mp_sched_any() (MP_STATE_VM(sched_head) != NULL || mp_sched_num_pending())
#else
#define mp_sched_any() (mp_sched_num_pending())
#endif

static inline bool mp_sched_empty(void) {
MP_STATIC_ASSERT(MICROPY_SCHEDULER_DEPTH <= 255); // MICROPY_SCHEDULER_DEPTH must fit in 8 bits
Expand All @@ -68,16 +73,20 @@ static inline bool mp_sched_empty(void) {

static inline void mp_sched_run_pending(void) {
mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();
if (MP_STATE_VM(sched_state) != MP_SCHED_PENDING) {
if (MP_STATE_VM(sched_lock_depth)) {
// Something else (e.g. hard IRQ) locked the scheduler while we
// acquired the lock.
MICROPY_END_ATOMIC_SECTION(atomic_state);
return;
}

if (MP_STATE_MAIN_THREAD(mp_pending_exception) == MP_OBJ_SENTINEL) {
MP_STATE_MAIN_THREAD(mp_pending_exception) = MP_OBJ_NULL;
}

// Equivalent to mp_sched_lock(), but we're already in the atomic
// section and know that we're pending.
MP_STATE_VM(sched_state) = MP_SCHED_LOCKED;
++MP_STATE_VM(sched_lock_depth);

#if MICROPY_SCHEDULER_STATIC_NODES
// Run all pending C callbacks.
Expand Down Expand Up @@ -116,34 +125,23 @@ static inline void mp_sched_run_pending(void) {
// tasks and also in hard interrupts or GC finalisers.
void mp_sched_lock(void) {
mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();
if (MP_STATE_VM(sched_state) < 0) {
// Already locked, increment lock (recursive lock).
--MP_STATE_VM(sched_state);
} else {
// Pending or idle.
MP_STATE_VM(sched_state) = MP_SCHED_LOCKED;
}
++MP_STATE_VM(sched_lock_depth);
MICROPY_END_ATOMIC_SECTION(atomic_state);
}

void mp_sched_unlock(void) {
mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();
assert(MP_STATE_VM(sched_state) < 0);
if (++MP_STATE_VM(sched_state) == 0) {
assert(MP_STATE_VM(sched_lock_depth) > 0);
if (--MP_STATE_VM(sched_lock_depth) == 0) {
// Scheduler became unlocked. Check if there are still tasks in the
// queue and set sched_state accordingly.
if (
#if !MICROPY_PY_THREAD
// See optimisation in mp_sched_exception.
MP_STATE_THREAD(mp_pending_exception) != MP_OBJ_NULL ||
#endif
#if MICROPY_SCHEDULER_STATIC_NODES
MP_STATE_VM(sched_head) != NULL ||
#endif
mp_sched_num_pending()) {
MP_STATE_VM(sched_state) = MP_SCHED_PENDING;
} else {
MP_STATE_VM(sched_state) = MP_SCHED_IDLE;
// dpg: don't understand why this is needed: exceptions should be handled
// within scheduled code so mp_handle_pending should make sure this state
// is consistent
if (MP_STATE_MAIN_THREAD(mp_pending_exception) == MP_OBJ_NULL) {
if (mp_sched_any()) {
MP_STATE_MAIN_THREAD(mp_pending_exception) = MP_OBJ_SENTINEL;
}
}
}
MICROPY_END_ATOMIC_SECTION(atomic_state);
Expand All @@ -153,8 +151,8 @@ bool MICROPY_WRAP_MP_SCHED_SCHEDULE(mp_sched_schedule)(mp_obj_t function, mp_obj
mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();
bool ret;
if (!mp_sched_full()) {
if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) {
MP_STATE_VM(sched_state) = MP_SCHED_PENDING;
if (MP_STATE_MAIN_THREAD(mp_pending_exception) == MP_OBJ_NULL) {
MP_STATE_MAIN_THREAD(mp_pending_exception) = MP_OBJ_SENTINEL;
}
uint8_t iput = IDX_MASK(MP_STATE_VM(sched_idx) + MP_STATE_VM(sched_len)++);
MP_STATE_VM(sched_queue)[iput].func = function;
Expand All @@ -174,8 +172,8 @@ bool mp_sched_schedule_node(mp_sched_node_t *node, mp_sched_callback_t callback)
mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();
bool ret;
if (node->callback == NULL) {
if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) {
MP_STATE_VM(sched_state) = MP_SCHED_PENDING;
if (MP_STATE_MAIN_THREAD(mp_pending_exception) == MP_OBJ_NULL) {
MP_STATE_MAIN_THREAD(mp_pending_exception) = MP_OBJ_SENTINEL;
}
node->callback = callback;
node->next = NULL;
Expand Down Expand Up @@ -206,8 +204,26 @@ void mp_handle_pending(bool raise_exc) {
if (MP_STATE_THREAD(mp_pending_exception) != MP_OBJ_NULL) {
mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();
mp_obj_t obj = MP_STATE_THREAD(mp_pending_exception);
if (obj != MP_OBJ_NULL) {
#if MICROPY_SCHED_VM_ABORT
if (MP_STATE_VM(vm_abort) && mp_thread_is_main_thread()) {
MP_STATE_VM(vm_abort) = false;
MP_STATE_THREAD(mp_pending_exception) = MP_OBJ_NULL;
#if MICROPY_ENABLE_SCHEDULER
if (MP_STATE_MAIN_THREAD(mp_pending_exception) == MP_OBJ_NULL && mp_sched_any()) {
MP_STATE_MAIN_THREAD(mp_pending_exception) = MP_OBJ_SENTINEL;
}
#endif
MICROPY_END_ATOMIC_SECTION(atomic_state);
nlr_jump_abort();
}
#endif
if (obj != MP_OBJ_NULL && obj != MP_OBJ_SENTINEL) {
MP_STATE_THREAD(mp_pending_exception) = MP_OBJ_NULL;
#if MICROPY_ENABLE_SCHEDULER
if (MP_STATE_MAIN_THREAD(mp_pending_exception) == MP_OBJ_NULL && mp_sched_any()) {
MP_STATE_MAIN_THREAD(mp_pending_exception) = MP_OBJ_SENTINEL;
}
#endif
if (raise_exc) {
MICROPY_END_ATOMIC_SECTION(atomic_state);
nlr_raise(obj);
Expand All @@ -216,7 +232,7 @@ void mp_handle_pending(bool raise_exc) {
MICROPY_END_ATOMIC_SECTION(atomic_state);
}
#if MICROPY_ENABLE_SCHEDULER
if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) {
if (MP_STATE_MAIN_THREAD(mp_pending_exception) == MP_OBJ_SENTINEL) {
mp_sched_run_pending();
}
#endif
Expand Down
14 changes: 3 additions & 11 deletions py/vm.c
9634
Original file line number Diff line number Diff line change
Expand Up @@ -1321,17 +1321,9 @@ unwind_jump:;
// we can inline the check for the common case where there is
// neither.
if (
#if MICROPY_ENABLE_SCHEDULER
MP_STATE_MAIN_THREAD(mp_pending_exception) != MP_OBJ_NULL
#if MICROPY_PY_THREAD
// Scheduler + threading: Scheduler and pending exceptions are independent, check both.
MP_STATE_VM(sched_state) == MP_SCHED_PENDING || MP_STATE_THREAD(mp_pending_exception) != MP_OBJ_NULL
#else
// Scheduler + non-threading: Optimisation: pending exception sets sched_state, only check sched_state.
MP_STATE_VM(sched_state) == MP_SCHED_PENDING
#endif
#else
// No scheduler: Just check pending exception.
MP_STATE_THREAD(mp_pending_exception) != MP_OBJ_NULL
|| MP_STATE_THREAD(mp_pending_exception) != MP_OBJ_NULL
#endif
) {
MARK_EXC_IP_SELECTIVE();
Expand All @@ -1349,7 +1341,7 @@ unwind_jump:;
#endif
#if MICROPY_ENABLE_SCHEDULER
// can only switch threads if the scheduler is unlocked
if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE)
if (MP_STATE_VM(sched_lock_depth) == 0)
#endif
{
MP_THREAD_GIL_EXIT();
Expand Down
Loading
0