Skip to content

Commit

Permalink
py/scheduler: Implement VM abort flag and mp_sched_vm_abort().
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
dpgeorge committed Mar 21, 2023
1 parent 5d4bfce commit d54208a
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 2 deletions.
9 changes: 9 additions & 0 deletions py/mpconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 */

Expand Down
5 changes: 5 additions & 0 deletions py/mpstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
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_ENABLE_VM_ABORT
NORETURN void nlr_jump_abort(void) {
MP_STATE_THREAD(nlr_top) = MP_STATE_VM(nlr_abort);
nlr_jump(NULL);
}
#endif
17 changes: 15 additions & 2 deletions py/nlr.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
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_ENABLE_VM_ABORT
void mp_sched_vm_abort(void);
#endif
void mp_handle_pending(bool raise_exc);

#if MICROPY_ENABLE_SCHEDULER
Expand Down
19 changes: 19 additions & 0 deletions py/scheduler.c
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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);
Expand All @@ -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();
Expand Down
4 changes: 4 additions & 0 deletions py/vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit d54208a

Please sign in to comment.