diff options
author | Raghuram Subramani <raghus2247@gmail.com> | 2022-06-19 19:47:51 +0530 |
---|---|---|
committer | Raghuram Subramani <raghus2247@gmail.com> | 2022-06-19 19:47:51 +0530 |
commit | 4fd287655a72b9aea14cdac715ad5b90ed082ed2 (patch) | |
tree | 65d393bc0e699dd12d05b29ba568e04cea666207 /circuitpython/py/objgenerator.c | |
parent | 0150f70ce9c39e9e6dd878766c0620c85e47bed0 (diff) |
add circuitpython code
Diffstat (limited to 'circuitpython/py/objgenerator.c')
-rw-r--r-- | circuitpython/py/objgenerator.c | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/circuitpython/py/objgenerator.c b/circuitpython/py/objgenerator.c new file mode 100644 index 0000000..c63ea6b --- /dev/null +++ b/circuitpython/py/objgenerator.c @@ -0,0 +1,423 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * SPDX-FileCopyrightText: Copyright (c) 2013-2019 Damien P. George + * SPDX-FileCopyrightText: Copyright (c) 2014-2017 Paul Sokolovsky + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include <stdlib.h> +#include <assert.h> + +#include "py/runtime.h" +#include "py/bc.h" +#include "py/objstr.h" +#include "py/objgenerator.h" +#include "py/objfun.h" +#include "py/stackctrl.h" + +#include "supervisor/shared/translate.h" + +// Instance of GeneratorExit exception - needed by generator.close() +const mp_obj_exception_t mp_const_GeneratorExit_obj = {{&mp_type_GeneratorExit}, (mp_obj_tuple_t *)&mp_const_empty_tuple_obj, (mp_obj_traceback_t *)&mp_const_empty_traceback_obj}; + +/******************************************************************************/ +/* generator wrapper */ + +typedef struct _mp_obj_gen_wrap_t { + mp_obj_base_t base; + mp_obj_t *fun; + bool coroutine_generator; +} mp_obj_gen_wrap_t; + +typedef struct _mp_obj_gen_instance_t { + mp_obj_base_t base; + // mp_const_none: Not-running, no exception. + // MP_OBJ_NULL: Running, no exception. + // other: Not running, pending exception. + mp_obj_t pend_exc; + bool coroutine_generator; + mp_code_state_t code_state; +} mp_obj_gen_instance_t; + +/******************************************************************************/ +// native generator wrapper + +#if MICROPY_EMIT_NATIVE + +STATIC mp_obj_t native_gen_wrap_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_obj_gen_wrap_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_fun_bc_t *self_fun = (mp_obj_fun_bc_t *)self->fun; + + // Determine start of prelude, and extract n_state from it + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wcast-align" + uintptr_t prelude_offset = ((uintptr_t *)self_fun->bytecode)[0]; + #pragma GCC diagnostic pop + + #if MICROPY_EMIT_NATIVE_PRELUDE_AS_BYTES_OBJ + // Prelude is in bytes object in const_table, at index prelude_offset + mp_obj_str_t *prelude_bytes = MP_OBJ_TO_PTR(self_fun->const_table[prelude_offset]); + prelude_offset = (const byte *)prelude_bytes->data - self_fun->bytecode; + #endif + const uint8_t *ip = self_fun->bytecode + prelude_offset; + size_t n_state, n_exc_stack_unused, scope_flags, n_pos_args, n_kwonly_args, n_def_args; + MP_BC_PRELUDE_SIG_DECODE_INTO(ip, n_state, n_exc_stack_unused, scope_flags, n_pos_args, n_kwonly_args, n_def_args); + size_t n_exc_stack = 0; + + // Allocate the generator object, with room for local stack and exception stack + mp_obj_gen_instance_t *o = m_new_obj_var(mp_obj_gen_instance_t, byte, + n_state * sizeof(mp_obj_t) + n_exc_stack * sizeof(mp_exc_stack_t)); + o->base.type = &mp_type_gen_instance; + + // Parse the input arguments and set up the code state + o->coroutine_generator = self->coroutine_generator; + o->pend_exc = mp_const_none; + o->code_state.fun_bc = self_fun; + o->code_state.ip = (const byte *)prelude_offset; + o->code_state.n_state = n_state; + mp_setup_code_state(&o->code_state, n_args, n_kw, args); + + // Indicate we are a native function, which doesn't use this variable + o->code_state.exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_SENTINEL; + + // Prepare the generator instance for execution + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wcast-align" + uintptr_t start_offset = ((uintptr_t *)self_fun->bytecode)[1]; + #pragma GCC diagnostic pop + o->code_state.ip = MICROPY_MAKE_POINTER_CALLABLE((void *)(self_fun->bytecode + start_offset)); + + return MP_OBJ_FROM_PTR(o); +} + +#endif // MICROPY_EMIT_NATIVE + +STATIC mp_obj_t bc_gen_wrap_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_obj_gen_wrap_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_fun_bc_t *self_fun = (mp_obj_fun_bc_t *)self->fun; + assert(self_fun->base.type == &mp_type_fun_bc); + + // bytecode prelude: get state size and exception stack size + const uint8_t *ip = self_fun->bytecode; + MP_BC_PRELUDE_SIG_DECODE(ip); + + // allocate the generator object, with room for local stack and exception stack + mp_obj_gen_instance_t *o = m_new_obj_var(mp_obj_gen_instance_t, byte, + n_state * sizeof(mp_obj_t) + n_exc_stack * sizeof(mp_exc_stack_t)); + o->base.type = &mp_type_gen_instance; + + o->coroutine_generator = self->coroutine_generator; + o->pend_exc = mp_const_none; + o->code_state.fun_bc = self_fun; + o->code_state.ip = 0; + o->code_state.n_state = n_state; + mp_setup_code_state(&o->code_state, n_args, n_kw, args); + return MP_OBJ_FROM_PTR(o); +} + +STATIC mp_obj_t gen_wrap_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + #if MICROPY_EMIT_NATIVE + mp_obj_gen_wrap_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_fun_bc_t *self_fun = (mp_obj_fun_bc_t *)self->fun; + if (self_fun->base.type == &mp_type_fun_native) { + return native_gen_wrap_call(self, n_args, n_kw, args); + } + #endif + return bc_gen_wrap_call(self_in, n_args, n_kw, args); +} + +#if MICROPY_PY_FUNCTION_ATTRS +static void gen_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + mp_obj_gen_wrap_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_fun_bc_t *self_fun = (mp_obj_fun_bc_t *)self->fun; + mp_obj_fun_bc_attr(MP_OBJ_FROM_PTR(self_fun), attr, dest); +} +#endif + +const mp_obj_type_t mp_type_gen_wrap = { + { &mp_type_type }, + .flags = MP_TYPE_FLAG_BINDS_SELF | MP_TYPE_FLAG_EXTENDED, + .name = MP_QSTR_generator, + #if MICROPY_PY_FUNCTION_ATTRS + .attr = gen_attr, + #endif + MP_TYPE_EXTENDED_FIELDS( + .call = gen_wrap_call, + .unary_op = mp_generic_unary_op, + ), +}; + + +mp_obj_t mp_obj_new_gen_wrap(mp_obj_t fun, bool is_coroutine) { + mp_obj_gen_wrap_t *o = m_new_obj(mp_obj_gen_wrap_t); + o->base.type = &mp_type_gen_wrap; + o->fun = MP_OBJ_TO_PTR(fun); + o->coroutine_generator = is_coroutine; + return MP_OBJ_FROM_PTR(o); +} + +/******************************************************************************/ +/* generator instance */ + +STATIC void gen_instance_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_gen_instance_t *self = MP_OBJ_TO_PTR(self_in); + #if MICROPY_PY_ASYNC_AWAIT + if (self->coroutine_generator) { + mp_printf(print, "<%q object '%q' at %p>", MP_QSTR_coroutine, mp_obj_fun_get_name(MP_OBJ_FROM_PTR(self->code_state.fun_bc)), self); + } else { + mp_printf(print, "<%q object '%q' at %p>", MP_QSTR_generator, mp_obj_fun_get_name(MP_OBJ_FROM_PTR(self->code_state.fun_bc)), self); + } + #else + mp_printf(print, "<generator object '%q' at %p>", mp_obj_fun_get_name(MP_OBJ_FROM_PTR(self->code_state.fun_bc)), self); + #endif +} + +mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value, mp_obj_t *ret_val) { + MP_STACK_CHECK(); + mp_check_self(mp_obj_is_type(self_in, &mp_type_gen_instance)); + mp_obj_gen_instance_t *self = MP_OBJ_TO_PTR(self_in); + if (self->code_state.ip == 0) { + // Trying to resume an already stopped generator. + // This is an optimised "raise StopIteration(None)". + *ret_val = mp_const_none; + return MP_VM_RETURN_NORMAL; + } + + // Ensure the generator cannot be reentered during execution + if (self->pend_exc == MP_OBJ_NULL) { + mp_raise_ValueError(MP_ERROR_TEXT("generator already executing")); + } + + #if MICROPY_PY_GENERATOR_PEND_THROW + // If exception is pending (set using .pend_throw()), process it now. + if (self->pend_exc != mp_const_none) { + throw_value = self->pend_exc; + } + #endif + + // If the generator is started, allow sending a value. + if (self->code_state.sp == self->code_state.state - 1) { + if (send_value != mp_const_none) { + mp_raise_TypeError(MP_ERROR_TEXT("can't send non-None value to a just-started generator")); + } + } else { + *self->code_state.sp = send_value; + } + + // Mark as running + self->pend_exc = MP_OBJ_NULL; + + // Set up the correct globals context for the generator and execute it + self->code_state.old_globals = mp_globals_get(); + mp_globals_set(self->code_state.fun_bc->globals); + + mp_vm_return_kind_t ret_kind; + + #if MICROPY_EMIT_NATIVE + if (self->code_state.exc_sp_idx == MP_CODE_STATE_EXC_SP_IDX_SENTINEL) { + // A native generator, with entry point 2 words into the "bytecode" pointer + typedef uintptr_t (*mp_fun_native_gen_t)(void *, mp_obj_t); + mp_fun_native_gen_t fun = MICROPY_MAKE_POINTER_CALLABLE((const void *)(self->code_state.fun_bc->bytecode + 2 * sizeof(uintptr_t))); + ret_kind = fun((void *)&self->code_state, throw_value); + } else + #endif + { + // A bytecode generator + ret_kind = mp_execute_bytecode(&self->code_state, throw_value); + } + + mp_globals_set(self->code_state.old_globals); + + // Mark as not running + self->pend_exc = mp_const_none; + + switch (ret_kind) { + case MP_VM_RETURN_NORMAL: + default: + // Explicitly mark generator as completed. If we don't do this, + // subsequent next() may re-execute statements after last yield + // again and again, leading to side effects. + self->code_state.ip = 0; + // This is an optimised "raise StopIteration(*ret_val)". + *ret_val = *self->code_state.sp; + break; + + case MP_VM_RETURN_YIELD: + *ret_val = *self->code_state.sp; + #if MICROPY_PY_GENERATOR_PEND_THROW + *self->code_state.sp = mp_const_none; + #endif + break; + + case MP_VM_RETURN_EXCEPTION: { + self->code_state.ip = 0; + *ret_val = self->code_state.state[0]; + // PEP479: if StopIteration is raised inside a generator it is replaced with RuntimeError + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(*ret_val)), MP_OBJ_FROM_PTR(&mp_type_StopIteration))) { + *ret_val = mp_obj_new_exception_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("generator raised StopIteration")); + } + break; + } + } + + return ret_kind; +} + +STATIC mp_obj_t gen_resume_and_raise(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value, bool raise_stop_iteration) { + mp_obj_t ret; + switch (mp_obj_gen_resume(self_in, send_value, throw_value, &ret)) { + case MP_VM_RETURN_NORMAL: + default: + // A normal return is a StopIteration, either raise it or return + // MP_OBJ_STOP_ITERATION as an optimisation. + if (ret == mp_const_none) { + ret = MP_OBJ_NULL; + } + if (raise_stop_iteration) { + mp_raise_StopIteration(ret); + } else { + return mp_make_stop_iteration(ret); + } + + case MP_VM_RETURN_YIELD: + return ret; + + case MP_VM_RETURN_EXCEPTION: + nlr_raise(ret); + } +} + +STATIC mp_obj_t gen_instance_iternext(mp_obj_t self_in) { + #if MICROPY_PY_ASYNC_AWAIT + // This translate is literally too much for m0 boards + mp_obj_gen_instance_t *self = MP_OBJ_TO_PTR(self_in); + if (self->coroutine_generator) { + mp_raise_TypeError(MP_ERROR_TEXT("'coroutine' object is not an iterator")); + } + #endif + return gen_resume_and_raise(self_in, mp_const_none, MP_OBJ_NULL, false); +} + +STATIC mp_obj_t gen_instance_send(mp_obj_t self_in, mp_obj_t send_value) { + return gen_resume_and_raise(self_in, send_value, MP_OBJ_NULL, true); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(gen_instance_send_obj, gen_instance_send); + +#if MICROPY_PY_ASYNC_AWAIT +STATIC mp_obj_t gen_instance_await(mp_obj_t self_in) { + mp_obj_gen_instance_t *self = MP_OBJ_TO_PTR(self_in); + if (!self->coroutine_generator) { + // Pretend like a generator does not have this coroutine behavior. + // Pay no attention to the dir() behind the curtain + mp_raise_AttributeError(MP_ERROR_TEXT("type object 'generator' has no attribute '__await__'")); + } + // You can directly call send on a coroutine generator or you can __await__ then send on the return of that. + return self; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(gen_instance_await_obj, gen_instance_await); +#endif + +STATIC mp_obj_t gen_instance_close(mp_obj_t self_in); +STATIC mp_obj_t gen_instance_throw(size_t n_args, const mp_obj_t *args) { + // The signature of this function is: throw(type[, value[, traceback]]) + // CPython will pass all given arguments through the call chain and process them + // at the point they are used (native generators will handle them differently to + // user-defined generators with a throw() method). To save passing multiple + // values, MicroPython instead does partial processing here to reduce it down to + // one argument and passes that through: + // - if only args[1] is given, or args[2] is given but is None, args[1] is + // passed through (in the standard case it is an exception class or instance) + // - if args[2] is given and not None it is passed through (in the standard + // case it would be an exception instance and args[1] its corresponding class) + // - args[3] is always ignored + + mp_obj_t exc = args[1]; + if (n_args > 2 && args[2] != mp_const_none) { + exc = args[2]; + } + + return gen_resume_and_raise(args[0], mp_const_none, exc, true); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(gen_instance_throw_obj, 2, 4, gen_instance_throw); + +STATIC mp_obj_t gen_instance_close(mp_obj_t self_in) { + mp_obj_t ret; + switch (mp_obj_gen_resume(self_in, mp_const_none, MP_OBJ_FROM_PTR(&mp_const_GeneratorExit_obj), &ret)) { + case MP_VM_RETURN_YIELD: + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("generator ignored GeneratorExit")); + + // Swallow GeneratorExit (== successful close), and re-raise any other + case MP_VM_RETURN_EXCEPTION: + // ret should always be an instance of an exception class + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(ret)), MP_OBJ_FROM_PTR(&mp_type_GeneratorExit))) { + return mp_const_none; + } + nlr_raise(ret); + + default: + // The only choice left is MP_VM_RETURN_NORMAL which is successful close + return mp_const_none; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(gen_instance_close_obj, gen_instance_close); + +#if MICROPY_PY_GENERATOR_PEND_THROW +STATIC mp_obj_t gen_instance_pend_throw(mp_obj_t self_in, mp_obj_t exc_in) { + mp_obj_gen_instance_t *self = MP_OBJ_TO_PTR(self_in); + if (self->pend_exc == MP_OBJ_NULL) { + mp_raise_ValueError(MP_ERROR_TEXT("generator already executing")); + } + mp_obj_t prev = self->pend_exc; + self->pend_exc = exc_in; + return prev; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(gen_instance_pend_throw_obj, gen_instance_pend_throw); +#endif + +STATIC const mp_rom_map_elem_t gen_instance_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&gen_instance_close_obj) }, + { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&gen_instance_send_obj) }, + { MP_ROM_QSTR(MP_QSTR_throw), MP_ROM_PTR(&gen_instance_throw_obj) }, + #if MICROPY_PY_GENERATOR_PEND_THROW + { MP_ROM_QSTR(MP_QSTR_pend_throw), MP_ROM_PTR(&gen_instance_pend_throw_obj) }, + #endif + #if MICROPY_PY_ASYNC_AWAIT + { MP_ROM_QSTR(MP_QSTR___await__), MP_ROM_PTR(&gen_instance_await_obj) }, + #endif +}; + +STATIC MP_DEFINE_CONST_DICT(gen_instance_locals_dict, gen_instance_locals_dict_table); + +const mp_obj_type_t mp_type_gen_instance = { + { &mp_type_type }, + .flags = MP_TYPE_FLAG_EXTENDED, + .name = MP_QSTR_generator, + .print = gen_instance_print, + .locals_dict = (mp_obj_dict_t *)&gen_instance_locals_dict, + MP_TYPE_EXTENDED_FIELDS( + .unary_op = mp_generic_unary_op, + .getiter = mp_identity_getiter, + .iternext = gen_instance_iternext, + ), +}; |