8000 py/nlr: Implement jump callbacks. · micropython/micropython@2757acf · GitHub
[go: up one dir, main page]

Skip to content

Commit 2757acf

Browse files
committed
py/nlr: Implement jump callbacks.
NLR buffers are usually quite large (use lots of C stack) and expensive to push and pop. Some of the time they are only needed to perform clean up if an exception happens, and then they re-raise the exception. This commit allows optimizing that scenario by introducing a linked-list of NLR callbacks that are called automatically when an exception is raised. They are essentially a light-weight NLR handler that can implement a "finally" block, i.e. clean-up when an exception is raised, or (by passing `true` to nlr_pop_jump_callback) when execution leaves the scope. Signed-off-by: Damien George <damien@micropython.org>
1 parent f36ae5e commit 2757acf

File tree

3 files changed

+56
-2
lines changed

3 files changed

+56
-2
lines changed

py/mpstate.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ typedef struct _mp_state_thread_t {
271271
mp_obj_dict_t *dict_globals;
272272

273273
nlr_buf_t *nlr_top;
274+
nlr_jump_callback_node_t *nlr_jump_callback_top;
274275

275276
// pending exception object (MP_OBJ_NULL if not pending)
276277
volatile mp_obj_t mp_pending_exception;

py/nlr.c

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*
44
* The MIT License (MIT)
55
*
6-
* Copyright (c) 2013-2017 Damien P. George
6+
* Copyright (c) 2013-2023 Damien P. George
77
*
88
* Permission is hereby granted, free of charge, to any person obtaining a copy
99
* of this software and associated documentation files (the "Software"), to deal
@@ -50,6 +50,36 @@ void nlr_pop(void) {
5050
*top = (*top)->prev;
5151
}
5252

53+
void nlr_push_jump_callback(nlr_jump_callback_node_t *node, nlr_jump_callback_fun_t fun) {
54+
nlr_jump_callback_node_t **top = &MP_STATE_THREAD(nlr_jump_callback_top);
55+
node->prev = *top;
56+
node->fun = fun;
57+
*top = node;
58+
}
59+
60+
void nlr_pop_jump_callback(bool run_callback) {
61+
nlr_jump_callback_node_t **top = &MP_STATE_THREAD(nlr_jump_callback_top);
62+
nlr_jump_callback_node_t *cur = *top;
63+
*top = (*top)->prev;
64+
if (run_callback) {
65+
cur->fun(cur);
66+
}
67+
}
68+
69+
// This function pops and runs all callbacks that were registered after `nlr`
70+
// was pushed (via nlr_push). It assumes:
71+
// - a descending C stack,
72+
// - that all nlr_jump_callback_node_t's in the linked-list pointed to by
73+
// nlr_jump_callback_top are on the C stack
74+
// It works by popping each node in turn until the next node is NULL or above
75+
// the `nlr` pointer on the C stack (and so pushed before `nlr` was pushed).
76+
void nlr_call_jump_callbacks(nlr_buf_t *nlr) {
77+
nlr_jump_callback_node_t **top = &MP_STATE_THREAD(nlr_jump_callback_top);
78+
while (*top != NULL && (void *)*top < (void *)nlr) {
79+
nlr_pop_jump_callback(true);
80+
}
81+
}
82+
5383
#if MICROPY_ENABLE_VM_ABORT
5484
NORETURN void nlr_jump_abort(void) {
5585
MP_STATE_THREAD(nlr_top) = MP_STATE_VM(nlr_abort);

py/nlr.h

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*
44
* The MIT License (MIT)
55
*
6-
* Copyright (c) 2013, 2014 Damien P. George
6+
* Copyright (c) 2013-2023 Damien P. George
77
*
88
* Permission is hereby granted, free of charge, to any person obtaining a copy
99
* of this software and associated documentation files (the "Software"), to deal
@@ -31,6 +31,7 @@
3131

3232
#include <limits.h>
3333
#include <assert.h>
34+
#include <stdbool.h>
3435

3536
#include "py/mpconfig.h"
3637

@@ -123,6 +124,15 @@ struct _nlr_buf_t {
123124
#endif
124125
};
125126

127+
typedef void (*nlr_jump_callback_fun_t)(void *ctx);
128+
129+
typedef struct _nlr_jump_callback_node_t nlr_jump_callback_node_t;
130+
131+
struct _nlr_jump_callback_node_t {
132+
nlr_jump_callback_node_t *prev;
133+
nlr_jump_callback_fun_t fun;
134+
};
135+
126136
// Helper macros to save/restore the pystack state
127137
#if MICROPY_ENABLE_PYSTACK
128138
#define MP_NLR_SAVE_PYSTACK(nlr_buf) (nlr_buf)->pystack = MP_STATE_THREAD(pystack_cur)
@@ -140,6 +150,7 @@ struct _nlr_buf_t {
140150
nlr_jump_fail(val); \
141151
} \
142152
top->ret_val = val; \
153+
nlr_call_jump_callbacks(top); \
143154
MP_NLR_RESTORE_PYSTACK(top); \
144155
*_top_ptr = top->prev; \
145156

@@ -187,4 +198,16 @@ NORETURN void nlr_jump_fail(void *val);
187198

188199
#endif
189200

201+
// Push a callback on to the linked-list of NLR jump callbacks. The `node` pointer must
202+
// be on the C stack. The `fun` callback will be executed if an NLR jump is taken which
203+
// unwinds the C stack through this `node`.
204+
void nlr_push_jump_callback(nlr_jump_callback_node_t *node, nlr_jump_callback_fun_t fun);
205+
206+
// Pop a callback from the linked-list of NLR jump callbacks. The corresponding function
207+
// will be called if `run_callback` is true.
208+
void nlr_pop_jump_callback(bool run_callback);
209+
210+
// Pop and call all NLR jump callbacks that were registered after `nlr` buffer was pushed.
211+
void nlr_call_jump_callbacks(nlr_buf_t *nlr);
212+
190213
#endif // MICROPY_INCLUDED_PY_NLR_H

0 commit comments

Comments
 (0)
0