diff --git a/py/bc.h b/py/bc.h index 814201160..aac35954d 100644 --- a/py/bc.h +++ b/py/bc.h @@ -1,3 +1,9 @@ -mp_obj_t mp_execute_byte_code(const byte *code, const mp_obj_t *args, uint n_args, const mp_obj_t *args2, uint n_args2, uint n_state); -bool mp_execute_byte_code_2(const byte *code_info, const byte **ip_in_out, mp_obj_t *fastn, mp_obj_t **sp_in_out); +typedef enum { + MP_VM_RETURN_NORMAL, + MP_VM_RETURN_YIELD, + MP_VM_RETURN_EXCEPTION, +} mp_vm_return_kind_t; + +mp_vm_return_kind_t mp_execute_byte_code(const byte *code, const mp_obj_t *args, uint n_args, const mp_obj_t *args2, uint n_args2, uint n_state, mp_obj_t *ret); +mp_vm_return_kind_t mp_execute_byte_code_2(const byte *code_info, const byte **ip_in_out, mp_obj_t *fastn, mp_obj_t **sp_in_out); void mp_byte_code_print(const byte *code, int len); diff --git a/py/objexcept.c b/py/objexcept.c index dbe702fa9..65ec53316 100644 --- a/py/objexcept.c +++ b/py/objexcept.c @@ -27,6 +27,7 @@ STATIC void mp_obj_exception_print(void (*print)(void *env, const char *fmt, ... print(env, "%s: %s", qstr_str(o->base.type->name), vstr_str(o->msg)); } else { // Yes, that's how CPython has it + // TODO now that exceptions are classes and instances, I think this needs to be changed to match CPython if (kind == PRINT_REPR) { print(env, "%s", qstr_str(o->base.type->name)); } @@ -162,7 +163,7 @@ void mp_obj_exception_add_traceback(mp_obj_t self_in, qstr file, machine_uint_t // for traceback, we are just using the list object for convenience, it's not really a list of Python objects if (self->traceback == MP_OBJ_NULL) { - self->traceback = mp_obj_new_list(3, NULL); + self->traceback = mp_obj_new_list(0, NULL); } mp_obj_list_append(self->traceback, (mp_obj_t)(machine_uint_t)file); mp_obj_list_append(self->traceback, (mp_obj_t)(machine_uint_t)line); diff --git a/py/objfun.c b/py/objfun.c index 433da039b..0c7ccf798 100644 --- a/py/objfun.c +++ b/py/objfun.c @@ -152,10 +152,15 @@ STATIC mp_obj_t fun_bc_call(mp_obj_t self_in, uint n_args, uint n_kw, const mp_o uint use_def_args = self->n_args - n_args; mp_map_t *old_globals = rt_globals_get(); rt_globals_set(self->globals); - mp_obj_t result = mp_execute_byte_code(self->bytecode, args, n_args, self->def_args + self->n_def_args - use_def_args, use_def_args, self->n_state); + mp_obj_t result; + mp_vm_return_kind_t vm_return_kind = mp_execute_byte_code(self->bytecode, args, n_args, self->def_args + self->n_def_args - use_def_args, use_def_args, self->n_state, &result); rt_globals_set(old_globals); - return result; + if (vm_return_kind == MP_VM_RETURN_NORMAL) { + return result; + } else { // MP_VM_RETURN_EXCEPTION + nlr_jump(result); + } } const mp_obj_type_t fun_bc_type = { diff --git a/py/objgenerator.c b/py/objgenerator.c index 226b902da..6a03af856 100644 --- a/py/objgenerator.c +++ b/py/objgenerator.c @@ -82,22 +82,30 @@ STATIC mp_obj_t gen_next_send(mp_obj_t self_in, mp_obj_t send_value) { } else { *self->sp = send_value; } - bool yield = mp_execute_byte_code_2(self->code_info, &self->ip, &self->state[self->n_state - 1], &self->sp); - if (yield) { - return *self->sp; - } else { - // 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. - // TODO: check how return with value behaves under such conditions - // in CPython. - self->ip = 0; - if (*self->sp == mp_const_none) { - return mp_const_stop_iteration; - } else { - // TODO return StopIteration with value *self->sp - return mp_const_stop_iteration; - } + mp_vm_return_kind_t vm_return_kind = mp_execute_byte_code_2(self->code_info, &self->ip, &self->state[self->n_state - 1], &self->sp); + switch (vm_return_kind) { + case MP_VM_RETURN_NORMAL: + // 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. + // TODO: check how return with value behaves under such conditions + // in CPython. + self->ip = 0; + if (*self->sp == mp_const_none) { + return mp_const_stop_iteration; + } else { + // TODO return StopIteration with value *self->sp + return mp_const_stop_iteration; + } + + case MP_VM_RETURN_YIELD: + return *self->sp; + + case MP_VM_RETURN_EXCEPTION: + default: + // TODO + assert(0); + return mp_const_none; } } diff --git a/py/vm.c b/py/vm.c index 461fecbda..573167b57 100644 --- a/py/vm.c +++ b/py/vm.c @@ -47,7 +47,7 @@ typedef enum { #define TOP() (*sp) #define SET_TOP(val) *sp = (val) -mp_obj_t mp_execute_byte_code(const byte *code, const mp_obj_t *args, uint n_args, const mp_obj_t *args2, uint n_args2, uint n_state) { +mp_vm_return_kind_t mp_execute_byte_code(const byte *code, const mp_obj_t *args, uint n_args, const mp_obj_t *args2, uint n_args2, uint n_state, mp_obj_t *ret) { // allocate state for locals and stack mp_obj_t temp_state[10]; mp_obj_t *state = &temp_state[0]; @@ -83,20 +83,30 @@ mp_obj_t mp_execute_byte_code(const byte *code, const mp_obj_t *args, uint n_arg } // execute the byte code - if (mp_execute_byte_code_2(code, &ip, &state[n_state - 1], &sp)) { - // it shouldn't yield - assert(0); - } + mp_vm_return_kind_t vm_return_kind = mp_execute_byte_code_2(code, &ip, &state[n_state - 1], &sp); - // TODO check fails if, eg, return from within for loop - //assert(sp == &state[17]); - return *sp; + switch (vm_return_kind) { + case MP_VM_RETURN_NORMAL: + *ret = *sp; + return MP_VM_RETURN_NORMAL; + case MP_VM_RETURN_EXCEPTION: + *ret = state[n_state - 1]; + return MP_VM_RETURN_EXCEPTION; + case MP_VM_RETURN_YIELD: // byte-code shouldn't yield + default: + assert(0); + *ret = mp_const_none; + return MP_VM_RETURN_NORMAL; + } } // fastn has items in reverse order (fastn[0] is local[0], fastn[-1] is local[1], etc) // sp points to bottom of stack which grows up -// returns true if bytecode yielded -bool mp_execute_byte_code_2(const byte *code_info, const byte **ip_in_out, mp_obj_t *fastn, mp_obj_t **sp_in_out) { +// returns: +// MP_VM_RETURN_NORMAL, sp valid, return value in *sp +// MP_VM_RETURN_YIELD, ip, sp valid, yielded value in *sp +// MP_VM_RETURN_EXCEPTION, exception in fastn[0] +mp_vm_return_kind_t mp_execute_byte_code_2(const byte *code_info, const byte **ip_in_out, mp_obj_t *fastn, mp_obj_t **sp_in_out) { // careful: be sure to declare volatile any variables read in the exception handler (written is ok, I think) const byte *ip = *ip_in_out; @@ -569,7 +579,7 @@ unwind_return: nlr_pop(); *sp_in_out = sp; assert(exc_sp == &exc_stack[0] - 1); - return false; + return MP_VM_RETURN_NORMAL; case MP_BC_RAISE_VARARGS: unum = *ip++; @@ -581,7 +591,7 @@ unwind_return: nlr_pop(); *ip_in_out = ip; *sp_in_out = sp; - return true; + return MP_VM_RETURN_YIELD; case MP_BC_IMPORT_NAME: DECODE_QSTR; @@ -603,7 +613,7 @@ unwind_return: printf("code %p, byte code 0x%02x not implemented\n", ip, op); assert(0); nlr_pop(); - return false; + return MP_VM_RETURN_NORMAL; } } @@ -653,9 +663,10 @@ unwind_return: PUSH(nlr.ret_val); // TODO should be type(nlr.ret_val), I think... } else { - // re-raise exception to higher level - // TODO what to do if this is a generator?? - nlr_jump(nlr.ret_val); + // propagate exception to higher level + // TODO what to do about ip and sp? they don't really make sense at this point + fastn[0] = nlr.ret_val; // must put exception here because sp is invalid + return MP_VM_RETURN_EXCEPTION; } } } diff --git a/tests/basics/try-module.py b/tests/basics/try-module.py index e62df8487..03a9db15b 100644 --- a/tests/basics/try-module.py +++ b/tests/basics/try-module.py @@ -3,7 +3,7 @@ import import1b def func1(): - return + print('func1') def func2(): try: