From d54208a2ff2ff8c2104597f715a586541ac6e663 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 16 Dec 2022 17:31:21 +1100 Subject: [PATCH] py/scheduler: Implement VM abort flag and mp_sched_vm_abort(). This is intended to be used by the very outer caller of the VM/runtime. It allows setting a top-level NLR handler that can be jumped to directly, in order to forcefully abort the VM/runtime. Enable using: #define MICROPY_ENABLE_VM_ABORT (1) Set up the handler at the top level using: nlr_buf_t nlr; nlr.ret_val = NULL; if (nlr_push(&nlr) == 0) { nlr_set_abort(&nlr); // call into the VM/runtime ... nlr_pop(); } else { if (nlr.ret_val == NULL) { // handle abort ... } else { // handle other exception that propagated to the top level ... } } nlr_set_abort(NULL); Schedule an abort, eg from an interrupt handler, using: mp_sched_vm_abort(); Signed-off-by: Damien George --- py/mpconfig.h | 9 +++++++++ py/mpstate.h | 5 +++++ py/nlr.c | 7 +++++++ py/nlr.h | 17 +++++++++++++++-- py/runtime.h | 3 +++ py/scheduler.c | 19 +++++++++++++++++++ py/vm.c | 4 ++++ 7 files changed, 62 insertions(+), 2 deletions(-) diff --git a/py/mpconfig.h b/py/mpconfig.h index db9c623f49fe..80e58d1cc41f 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -909,6 +909,11 @@ typedef double mp_float_t; #define MICROPY_INTERNAL_PRINTF_PRINTER (&mp_plat_print) #endif +// Whether to support mp_sched_vm_abort to asynchronously abort to the top level. +#ifndef MICROPY_ENABLE_VM_ABORT +#define MICROPY_ENABLE_VM_ABORT (0) +#endif + // Support for internal scheduler #ifndef MICROPY_ENABLE_SCHEDULER #define MICROPY_ENABLE_SCHEDULER (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) @@ -1740,6 +1745,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 */ diff --git a/py/mpstate.h b/py/mpstate.h index 4c9380097a53..f6b911af5658 100644 --- a/py/mpstate.h +++ b/py/mpstate.h @@ -226,6 +226,11 @@ typedef struct _mp_state_vm_t { uint8_t sched_idx; #endif + #if MICROPY_ENABLE_VM_ABORT + bool vm_abort; + nlr_buf_t *nlr_abort; + #endif + #if MICROPY_PY_THREAD_GIL // This is a global mutex used to make the VM/runtime thread-safe. mp_thread_mutex_t gil_mutex; diff --git a/py/nlr.c b/py/nlr.c index a35e7d229c51..92db8ffb1979 100644 --- a/py/nlr.c +++ b/py/nlr.c @@ -49,3 +49,10 @@ void nlr_pop(void) { nlr_buf_t **top = &MP_STATE_THREAD(nlr_top); *top = (*top)->prev; } + +#if MICROPY_ENABLE_VM_ABORT +NORETURN void nlr_jump_abort(void) { + MP_STATE_THREAD(nlr_top) = MP_STATE_VM(nlr_abort); + nlr_jump(NULL); +} +#endif diff --git a/py/nlr.h b/py/nlr.h index 338a67570409..09ef66ef7202 100644 --- a/py/nlr.h +++ b/py/nlr.h @@ -101,9 +101,16 @@ typedef struct _nlr_buf_t nlr_buf_t; struct _nlr_buf_t { - // the entries here must all be machine word size + // The entries in this struct must all be machine word size. + + // Pointer to the previous nlr_buf_t in the chain. + // Or NULL if it's the top-level one. nlr_buf_t *prev; - void *ret_val; // always a concrete object (an exception instance) + + // The exception that is being raised: + // - NULL means the jump is because of a VM abort (only if MICROPY_ENABLE_VM_ABORT enabled) + // - otherwise it's always a concrete object (an exception instance) + void *ret_val; #if MICROPY_NLR_SETJMP jmp_buf jmpbuf; @@ -149,6 +156,12 @@ unsigned int nlr_push_tail(nlr_buf_t *top); void nlr_pop(void); NORETURN void nlr_jump(void *val); +#if MICROPY_ENABLE_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. diff --git a/py/runtime.h b/py/runtime.h index 36b3caa6c73e..d57c25c92c8c 100644 --- a/py/runtime.h +++ b/py/runtime.h @@ -75,6 +75,9 @@ void mp_deinit(void); void mp_sched_exception(mp_obj_t exc); void mp_sched_keyboard_interrupt(void); +#if MICROPY_ENABLE_VM_ABORT +void mp_sched_vm_abort(void); +#endif void mp_handle_pending(bool raise_exc); #if MICROPY_ENABLE_SCHEDULER diff --git a/py/scheduler.c b/py/scheduler.c index db090b09911c..165b26dc8652 100644 --- a/py/scheduler.c +++ b/py/scheduler.c @@ -51,6 +51,12 @@ void MICROPY_WRAP_MP_SCHED_KEYBOARD_INTERRUPT(mp_sched_keyboard_interrupt)(void) } #endif +#if MICROPY_ENABLE_VM_ABORT +void MICROPY_WRAP_MP_SCHED_VM_ABORT(mp_sched_vm_abort)(void) { + MP_STATE_VM(vm_abort) = true; +} +#endif + #if MICROPY_ENABLE_SCHEDULER #define IDX_MASK(i) ((i) & (MICROPY_SCHEDULER_DEPTH - 1)) @@ -203,6 +209,17 @@ MP_REGISTER_ROOT_POINTER(mp_sched_item_t sched_queue[MICROPY_SCHEDULER_DEPTH]); // Called periodically from the VM or from "waiting" code (e.g. sleep) to // process background tasks and pending exceptions (e.g. KeyboardInterrupt). void mp_handle_pending(bool raise_exc) { + // Handle pending VM abort. + #if MICROPY_ENABLE_VM_ABORT + if (MP_STATE_VM(vm_abort) && mp_thread_is_main_thread()) { + MP_STATE_VM(vm_abort) = false; + if (raise_exc && nlr_get_abort() != NULL) { + nlr_jump_abort(); + } + } + #endif + + // Handle any pending exception. 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); @@ -215,6 +232,8 @@ void mp_handle_pending(bool raise_exc) { } MICROPY_END_ATOMIC_SECTION(atomic_state); } + + // Handle any pending callbacks. #if MICROPY_ENABLE_SCHEDULER if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) { mp_sched_run_pending(); diff --git a/py/vm.c b/py/vm.c index 9273dda02eea..385d13ee4097 100644 --- a/py/vm.c +++ b/py/vm.c @@ -1333,6 +1333,10 @@ unwind_jump:; // No scheduler: Just check pending exception. MP_STATE_THREAD(mp_pending_exception) != MP_OBJ_NULL #endif + #if MICROPY_ENABLE_VM_ABORT + // Check if the VM should abort execution. + || MP_STATE_VM(vm_abort) + #endif ) { MARK_EXC_IP_SELECTIVE(); mp_handle_pending(true);