Skip to content

Commit 7a170c8

Browse files
committed
Merge remote-tracking branch 'origin/main' into main
2 parents f55e79f + 57b4d01 commit 7a170c8

File tree

15 files changed

+358
-63
lines changed

15 files changed

+358
-63
lines changed

lib/utils/pyexec.c

Lines changed: 58 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@
4444
#include "lib/utils/pyexec.h"
4545
#include "genhdr/mpversion.h"
4646

47+
#if CIRCUITPY_ATEXIT
48+
#include "shared-module/atexit/__init__.h"
49+
#endif
50+
4751
pyexec_mode_kind_t pyexec_mode_kind = PYEXEC_MODE_FRIENDLY_REPL;
4852
int pyexec_system_exit = 0;
4953

@@ -58,6 +62,7 @@ STATIC bool repl_display_debugging_info = 0;
5862
#define EXEC_FLAG_SOURCE_IS_VSTR (16)
5963
#define EXEC_FLAG_SOURCE_IS_FILENAME (32)
6064
#define EXEC_FLAG_SOURCE_IS_READER (64)
65+
#define EXEC_FLAG_SOURCE_IS_ATEXIT (128)
6166

6267
// parses, compiles and executes the code in the lexer
6368
// frees the lexer before returning
@@ -81,52 +86,65 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input
8186
nlr.ret_val = NULL;
8287
if (nlr_push(&nlr) == 0) {
8388
mp_obj_t module_fun;
84-
#if MICROPY_MODULE_FROZEN_MPY
85-
if (exec_flags & EXEC_FLAG_SOURCE_IS_RAW_CODE) {
86-
// source is a raw_code object, create the function
87-
module_fun = mp_make_function_from_raw_code(source, MP_OBJ_NULL, MP_OBJ_NULL);
88-
} else
89+
#if CIRCUITPY_ATEXIT
90+
if (!(exec_flags & EXEC_FLAG_SOURCE_IS_ATEXIT))
8991
#endif
9092
{
91-
#if MICROPY_ENABLE_COMPILER
92-
mp_lexer_t *lex;
93-
if (exec_flags & EXEC_FLAG_SOURCE_IS_VSTR) {
94-
const vstr_t *vstr = source;
95-
lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, vstr->buf, vstr->len, 0);
96-
} else if (exec_flags & EXEC_FLAG_SOURCE_IS_READER) {
97-
lex = mp_lexer_new(MP_QSTR__lt_stdin_gt_, *(mp_reader_t *)source);
98-
} else if (exec_flags & EXEC_FLAG_SOURCE_IS_FILENAME) {
99-
lex = mp_lexer_new_from_file(source);
100-
} else {
101-
lex = (mp_lexer_t *)source;
93+
#if MICROPY_MODULE_FROZEN_MPY
94+
if (exec_flags & EXEC_FLAG_SOURCE_IS_RAW_CODE) {
95+
// source is a raw_code object, create the function
96+
module_fun = mp_make_function_from_raw_code(source, MP_OBJ_NULL, MP_OBJ_NULL);
97+
} else
98+
#endif
99+
{
100+
#if MICROPY_ENABLE_COMPILER
101+
mp_lexer_t *lex;
102+
if (exec_flags & EXEC_FLAG_SOURCE_IS_VSTR) {
103+
const vstr_t *vstr = source;
104+
lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, vstr->buf, vstr->len, 0);
105+
} else if (exec_flags & EXEC_FLAG_SOURCE_IS_READER) {
106+
lex = mp_lexer_new(MP_QSTR__lt_stdin_gt_, *(mp_reader_t *)source);
107+
} else if (exec_flags & EXEC_FLAG_SOURCE_IS_FILENAME) {
108+
lex = mp_lexer_new_from_file(source);
109+
} else {
110+
lex = (mp_lexer_t *)source;
111+
}
112+
// source is a lexer, parse and compile the script
113+
qstr source_name = lex->source_name;
114+
if (input_kind == MP_PARSE_FILE_INPUT) {
115+
mp_store_global(MP_QSTR___file__, MP_OBJ_NEW_QSTR(source_name));
116+
}
117+
mp_parse_tree_t parse_tree = mp_parse(lex, input_kind);
118+
module_fun = mp_compile(&parse_tree, source_name, exec_flags & EXEC_FLAG_IS_REPL);
119+
// Clear the parse tree because it has a heap pointer we don't need anymore.
120+
*((uint32_t volatile *)&parse_tree.chunk) = 0;
121+
#else
122+
mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("script compilation not supported"));
123+
#endif
102124
}
103-
// source is a lexer, parse and compile the script
104-
qstr source_name = lex->source_name;
125+
126+
// If the code was loaded from a file it's likely to be running for a while so we'll long
127+
// live it and collect any garbage before running.
105128
if (input_kind == MP_PARSE_FILE_INPUT) {
106-
mp_store_global(MP_QSTR___file__, MP_OBJ_NEW_QSTR(source_name));
129+
module_fun = make_obj_long_lived(module_fun, 6);
130+
gc_collect();
107131
}
108-
mp_parse_tree_t parse_tree = mp_parse(lex, input_kind);
109-
module_fun = mp_compile(&parse_tree, source_name, exec_flags & EXEC_FLAG_IS_REPL);
110-
// Clear the parse tree because it has a heap pointer we don't need anymore.
111-
*((uint32_t volatile *)&parse_tree.chunk) = 0;
112-
#else
113-
mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("script compilation not supported"));
114-
#endif
115-
}
116-
117-
// If the code was loaded from a file it's likely to be running for a while so we'll long
118-
// live it and collect any garbage before running.
119-
if (input_kind == MP_PARSE_FILE_INPUT) {
120-
module_fun = make_obj_long_lived(module_fun, 6);
121-
gc_collect();
122132
}
123133

124134
// execute code
125135
mp_hal_set_interrupt_char(CHAR_CTRL_C); // allow ctrl-C to interrupt us
126136
#if MICROPY_REPL_INFO
127137
start = mp_hal_ticks_ms();
128138
#endif
129-
mp_call_function_0(module_fun);
139+
#if CIRCUITPY_ATEXIT
140+
if (exec_flags & EXEC_FLAG_SOURCE_IS_ATEXIT) {
141+
atexit_callback_t *callback = (atexit_callback_t *)source;
142+
mp_call_function_n_kw(callback->func, callback->n_pos, callback->n_kw, callback->args);
143+
} else
144+
#endif
145+
{
146+
mp_call_function_0(module_fun);
147+
}
130148
mp_hal_set_interrupt_char(-1); // disable interrupt
131149
mp_handle_pending(true); // handle any pending exceptions (and any callbacks)
132150
nlr_pop();
@@ -746,6 +764,12 @@ int pyexec_frozen_module(const char *name, pyexec_result_t *result) {
746764
}
747765
#endif
748766

767+
#if CIRCUITPY_ATEXIT
768+
int pyexec_exit_handler(const void *source, pyexec_result_t *result) {
769+
return parse_compile_execute(source, MP_PARSE_FILE_INPUT, EXEC_FLAG_SOURCE_IS_ATEXIT, result);
770+
}
771+
#endif
772+
749773
#if MICROPY_REPL_INFO
750774
mp_obj_t pyb_set_repl_info(mp_obj_t o_value) {
751775
repl_display_debugging_info = mp_obj_get_int(o_value);

lib/utils/pyexec.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ void pyexec_event_repl_init(void);
5959
int pyexec_event_repl_process_char(int c);
6060
extern uint8_t pyexec_repl_active;
6161

62+
#if CIRCUITPY_ATEXIT
63+
int pyexec_exit_handler(const void *source, pyexec_result_t *result);
64+
#endif
65+
6266
#if MICROPY_REPL_INFO
6367
mp_obj_t pyb_set_repl_info(mp_obj_t o_value);
6468
MP_DECLARE_CONST_FUN_OBJ_1(pyb_set_repl_info_obj);

locale/circuitpython.pot

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ msgstr ""
189189
msgid "'%q' object is not an iterator"
190190
msgstr ""
191191

192-
#: py/objtype.c py/runtime.c
192+
#: py/objtype.c py/runtime.c shared-module/atexit/__init__.c
193193
msgid "'%q' object is not callable"
194194
msgstr ""
195195

main.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@
7070
#include "shared-bindings/alarm/__init__.h"
7171
#endif
7272

73+
#if CIRCUITPY_ATEXIT
74+
#include "shared-module/atexit/__init__.h"
75+
#endif
76+
7377
#if CIRCUITPY_BLEIO
7478
#include "shared-bindings/_bleio/__init__.h"
7579
#include "supervisor/shared/bluetooth/bluetooth.h"
@@ -213,6 +217,9 @@ STATIC bool maybe_run_list(const char * const * filenames, pyexec_result_t* exec
213217
decompress(compressed, decompressed);
214218
mp_hal_stdout_tx_str(decompressed);
215219
pyexec_file(filename, exec_result);
220+
#if CIRCUITPY_ATEXIT
221+
shared_module_atexit_execute(exec_result);
222+
#endif
216223
return true;
217224
}
218225

@@ -253,6 +260,11 @@ STATIC void cleanup_after_vm(supervisor_allocation* heap, mp_obj_t exception) {
253260

254261
// Reset port-independent devices, like CIRCUITPY_BLEIO_HCI.
255262
reset_devices();
263+
264+
#if CIRCUITPY_ATEXIT
265+
atexit_reset();
266+
#endif
267+
256268
// Turn off the display and flush the filesystem before the heap disappears.
257269
#if CIRCUITPY_DISPLAYIO
258270
reset_displays();
@@ -759,6 +771,13 @@ STATIC int run_repl(void) {
759771
} else {
760772
exit_code = pyexec_friendly_repl();
761773
}
774+
#if CIRCUITPY_ATEXIT
775+
pyexec_result_t result;
776+
shared_module_atexit_execute(&result);
777+
if (result.return_code == PYEXEC_DEEP_SLEEP) {
778+
exit_code = PYEXEC_DEEP_SLEEP;
779+
}
780+
#endif
762781
cleanup_after_vm(heap, MP_OBJ_SENTINEL);
763782
#if CIRCUITPY_STATUS_LED
764783
status_led_init();
@@ -875,6 +894,10 @@ void gc_collect(void) {
875894
common_hal_alarm_gc_collect();
876895
#endif
877896

897+
#if CIRCUITPY_ATEXIT
898+
atexit_gc_collect();
899+
#endif
900+
878901
#if CIRCUITPY_DISPLAYIO
879902
displayio_gc_collect();
880903
#endif

ports/unix/mpconfigport.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@
107107
#define MICROPY_PY_BUILTINS_SLICE_ATTRS (1)
108108
#define MICROPY_PY_BUILTINS_SLICE_INDICES (1)
109109
#define MICROPY_PY_SYS_EXIT (1)
110-
#define MICROPY_PY_SYS_ATEXIT (1)
111110
#if MICROPY_PY_SYS_SETTRACE
112111
#define MICROPY_PERSISTENT_CODE_SAVE (1)
113112
#define MICROPY_COMP_CONST (0)

py/circuitpy_defns.mk

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ endif
114114
ifeq ($(CIRCUITPY_ANALOGIO),1)
115115
SRC_PATTERNS += analogio/%
116116
endif
117+
ifeq ($(CIRCUITPY_ATEXIT),1)
118+
SRC_PATTERNS += atexit/%
119+
endif
117120
ifeq ($(CIRCUITPY_AUDIOBUSIO),1)
118121
SRC_PATTERNS += audiobusio/%
119122
endif
@@ -485,6 +488,7 @@ SRC_SHARED_MODULE_ALL = \
485488
_stage/__init__.c \
486489
aesio/__init__.c \
487490
aesio/aes.c \
491+
atexit/__init__.c \
488492
audiocore/RawSample.c \
489493
audiocore/WaveFile.c \
490494
audiocore/__init__.c \

py/circuitpy_mpconfig.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,13 @@ extern const struct _mp_obj_module_t analogio_module;
265265
#define ANALOGIO_MODULE
266266
#endif
267267

268+
#if CIRCUITPY_ATEXIT
269+
extern const struct _mp_obj_module_t atexit_module;
270+
#define ATEXIT_MODULE { MP_OBJ_NEW_QSTR(MP_QSTR_atexit), (mp_obj_t)&atexit_module },
271+
#else
272+
#define ATEXIT_MODULE
273+
#endif
274+
268275
#if CIRCUITPY_AUDIOBUSIO
269276
#define AUDIOBUSIO_MODULE { MP_OBJ_NEW_QSTR(MP_QSTR_audiobusio), (mp_obj_t)&audiobusio_module },
270277
extern const struct _mp_obj_module_t audiobusio_module;
@@ -886,6 +893,7 @@ extern const struct _mp_obj_module_t msgpack_module;
886893
AESIO_MODULE \
887894
ALARM_MODULE \
888895
ANALOGIO_MODULE \
896+
ATEXIT_MODULE \
889897
AUDIOBUSIO_MODULE \
890898
AUDIOCORE_MODULE \
891899
AUDIOIO_MODULE \

py/circuitpy_mpconfig.mk

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ CFLAGS += -DCIRCUITPY_ALARM=$(CIRCUITPY_ALARM)
4848
CIRCUITPY_ANALOGIO ?= 1
4949
CFLAGS += -DCIRCUITPY_ANALOGIO=$(CIRCUITPY_ANALOGIO)
5050

51+
CIRCUITPY_ATEXIT ?= $(CIRCUITPY_FULL_BUILD)
52+
CFLAGS += -DCIRCUITPY_ATEXIT=$(CIRCUITPY_ATEXIT)
53+
5154
CIRCUITPY_AUDIOBUSIO ?= $(CIRCUITPY_FULL_BUILD)
5255
CFLAGS += -DCIRCUITPY_AUDIOBUSIO=$(CIRCUITPY_AUDIOBUSIO)
5356

shared-bindings/atexit/__init__.c

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* This file is part of the Micro Python project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2021 microDev
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
27+
#include "shared-module/atexit/__init__.h"
28+
29+
//| """Atexit Module
30+
//|
31+
//| This module defines functions to register and unregister cleanup functions.
32+
//| Functions thus registered are automatically executed upon normal vm termination.
33+
//|
34+
//| These functions are run in the reverse order in which they were registered;
35+
//| if you register ``A``, ``B``, and ``C``, they will be run in the order ``C``, ``B``, ``A``.
36+
//|
37+
//| """
38+
//| ...
39+
//|
40+
41+
//| def register(func: Callable[..., Any], *args: Optional[Any], **kwargs: Optional[Any]) -> Callable[..., Any]:
42+
//|
43+
//| """Register func as a function to be executed at termination.
44+
//|
45+
//| Any optional arguments that are to be passed to func must be passed as arguments to `register()`.
46+
//| It is possible to register the same function and arguments more than once.
47+
//|
48+
//| At normal program termination (for instance, if `sys.exit()` is called or the vm execution completes),
49+
//| all functions registered are called in last in, first out order.
50+
//|
51+
//| If an exception is raised during execution of the exit handler,
52+
//| a traceback is printed (unless `SystemExit` is raised) and the execution stops.
53+
//|
54+
//| This function returns func, which makes it possible to use it as a decorator.
55+
//|
56+
//| """
57+
//| ...
58+
//|
59+
STATIC mp_obj_t atexit_register(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
60+
shared_module_atexit_register(pos_args[0], (n_args - 1), ((n_args > 1) ? &pos_args[1] : NULL), kw_args);
61+
return pos_args[0];
62+
}
63+
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(atexit_register_obj, 1, atexit_register);
64+
65+
//| def unregister(func: Callable[..., Any]) -> None:
66+
//|
67+
//| """Remove func from the list of functions to be run at termination.
68+
//|
69+
//| `unregister()` silently does nothing if func was not previously registered. If func has been registered more than once,
70+
//| every occurrence of that function in the atexit call stack will be removed.
71+
//|
72+
//| """
73+
//| ...
74+
//|
75+
STATIC mp_obj_t atexit_unregister(const mp_obj_t self_in) {
76+
shared_module_atexit_unregister(&self_in);
77+
return mp_const_none;
78+
}
79+
STATIC MP_DEFINE_CONST_FUN_OBJ_1(atexit_unregister_obj, atexit_unregister);
80+
81+
STATIC const mp_rom_map_elem_t atexit_module_globals_table[] = {
82+
// module name
83+
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_atexit) },
84+
// module functions
85+
{ MP_ROM_QSTR(MP_QSTR_register), MP_ROM_PTR(&atexit_register_obj) },
86+
{ MP_ROM_QSTR(MP_QSTR_unregister), MP_ROM_PTR(&atexit_unregister_obj) },
87+
};
88+
STATIC MP_DEFINE_CONST_DICT(atexit_module_globals, atexit_module_globals_table);
89+
90+
const mp_obj_module_t atexit_module = {
91+
.base = { &mp_type_module },
92+
.globals = (mp_obj_dict_t *)&atexit_module_globals,
93+
};

0 commit comments

Comments
 (0)