Skip to content

objgenerator: Implement exception handling for generators #358

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 22, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion py/bc.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ typedef enum {
MP_VM_RETURN_EXCEPTION,
} mp_vm_return_kind_t;

// Exception stack entry
typedef struct _mp_exc_stack {
const byte *handler;
// bit 0 is saved currently_in_except_block value
machine_uint_t val_sp;
// We might only have 2 interesting cases here: SETUP_EXCEPT & SETUP_FINALLY,
// consider storing it in bit 1 of val_sp. TODO: SETUP_WITH?
byte opcode;
} mp_exc_stack;

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);
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, mp_exc_stack *exc_stack, mp_exc_stack **exc_sp_in_out, volatile mp_obj_t inject_exc);
void mp_byte_code_print(const byte *code, int len);
35 changes: 28 additions & 7 deletions py/objgenerator.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,10 @@ typedef struct _mp_obj_gen_instance_t {
const byte *code_info;
const byte *ip;
mp_obj_t *sp;
mp_exc_stack *exc_sp;
uint n_state;
mp_obj_t state[];
mp_obj_t state[0]; // Variable-length
mp_exc_stack exc_state[0]; // Variable-length
} mp_obj_gen_instance_t;

void gen_instance_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
Expand All @@ -68,7 +70,7 @@ mp_obj_t gen_instance_getiter(mp_obj_t self_in) {
return self_in;
}

STATIC mp_obj_t gen_next_send(mp_obj_t self_in, mp_obj_t send_value) {
STATIC mp_obj_t gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value) {
mp_obj_gen_instance_t *self = self_in;
if (self->ip == 0) {
return mp_const_stop_iteration;
Expand All @@ -80,7 +82,9 @@ STATIC mp_obj_t gen_next_send(mp_obj_t self_in, mp_obj_t send_value) {
} else {
*self->sp = send_value;
}
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);
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, (mp_exc_stack*)(self->state + self->n_state),
&self->exc_sp, throw_value);
switch (vm_return_kind) {
case MP_VM_RETURN_NORMAL:
// Explicitly mark generator as completed. If we don't do this,
Expand All @@ -100,19 +104,21 @@ STATIC mp_obj_t gen_next_send(mp_obj_t self_in, mp_obj_t send_value) {
return *self->sp;

case MP_VM_RETURN_EXCEPTION:
self->ip = 0;
nlr_jump(self->state[self->n_state - 1]);

default:
// TODO
assert(0);
return mp_const_none;
}
}

mp_obj_t gen_instance_iternext(mp_obj_t self_in) {
return gen_next_send(self_in, mp_const_none);
return gen_resume(self_in, mp_const_none, MP_OBJ_NULL);
}

STATIC mp_obj_t gen_instance_send(mp_obj_t self_in, mp_obj_t send_value) {
mp_obj_t ret = gen_next_send(self_in, send_value);
mp_obj_t ret = gen_resume(self_in, send_value, MP_OBJ_NULL);
if (ret == mp_const_stop_iteration) {
nlr_jump(mp_obj_new_exception(&mp_type_StopIteration));
} else {
Expand All @@ -122,8 +128,21 @@ STATIC mp_obj_t gen_instance_send(mp_obj_t self_in, mp_obj_t send_value) {

STATIC MP_DEFINE_CONST_FUN_OBJ_2(gen_instance_send_obj, gen_instance_send);

STATIC mp_obj_t gen_instance_throw(uint n_args, const mp_obj_t *args) {
mp_obj_t ret = gen_resume(args[0], mp_const_none, n_args == 2 ? args[1] : args[2]);
if (ret == mp_const_stop_iteration) {
nlr_jump(mp_obj_new_exception(&mp_type_StopIteration));
} else {
return ret;
}
}

STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(gen_instance_throw_obj, 2, 4, gen_instance_throw);


STATIC const mp_method_t gen_type_methods[] = {
{ "send", &gen_instance_send_obj },
{ "throw", &gen_instance_throw_obj },
{ NULL, NULL }, // end-of-list sentinel
};

Expand All @@ -137,11 +156,13 @@ const mp_obj_type_t gen_instance_type = {
};

mp_obj_t mp_obj_new_gen_instance(const byte *bytecode, uint n_state, int n_args, const mp_obj_t *args) {
mp_obj_gen_instance_t *o = m_new_obj_var(mp_obj_gen_instance_t, mp_obj_t, n_state);
// TODO: 4 is hardcoded number from vm.c, calc exc stack size instead.
mp_obj_gen_instance_t *o = m_new_obj_var(mp_obj_gen_instance_t, byte, n_state * sizeof(mp_obj_t) + 4 * sizeof(mp_exc_stack));
o->base.type = &gen_instance_type;
o->code_info = bytecode;
o->ip = bytecode;
o->sp = &o->state[0] - 1; // sp points to top of stack, which starts off 1 below the state
o->exc_sp = (mp_exc_stack*)(o->state + n_state) - 1;
o->n_state = n_state;

// copy args to end of state array, in reverse (that's how mp_execute_byte_code_2 needs it)
Expand Down
43 changes: 23 additions & 20 deletions py/vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,6 @@
// top element.
// Exception stack also grows up, top element is also pointed at.

// Exception stack entry
typedef struct _mp_exc_stack {
const byte *handler;
// bit 0 is saved currently_in_except_block value
machine_uint_t val_sp;
// We might only have 2 interesting cases here: SETUP_EXCEPT & SETUP_FINALLY,
// consider storing it in bit 1 of val_sp. TODO: SETUP_WITH?
byte opcode;
} mp_exc_stack;

// Exception stack unwind reasons (WHY_* in CPython-speak)
// TODO perhaps compress this to RETURN=0, JUMP>0, with number of unwinds
// left to do encoded in the JUMP number
Expand Down Expand Up @@ -89,8 +79,10 @@ mp_vm_return_kind_t mp_execute_byte_code(const byte *code, const mp_obj_t *args,
}
}

mp_exc_stack exc_stack[4];
mp_exc_stack *exc_sp = &exc_stack[0] - 1;
// execute the byte code
mp_vm_return_kind_t vm_return_kind = mp_execute_byte_code_2(code, &ip, &state[n_state - 1], &sp);
mp_vm_return_kind_t vm_return_kind = mp_execute_byte_code_2(code, &ip, &state[n_state - 1], &sp, exc_stack, &exc_sp, MP_OBJ_NULL);

switch (vm_return_kind) {
case MP_VM_RETURN_NORMAL:
Expand All @@ -113,7 +105,10 @@ mp_vm_return_kind_t mp_execute_byte_code(const byte *code, const mp_obj_t *args,
// 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) {
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,
mp_exc_stack *exc_stack, mp_exc_stack **exc_sp_in_out,
volatile mp_obj_t inject_exc) {
// careful: be sure to declare volatile any variables read in the exception handler (written is ok, I think)

const byte *ip = *ip_in_out;
Expand All @@ -123,14 +118,21 @@ mp_vm_return_kind_t mp_execute_byte_code_2(const byte *code_info, const byte **i
mp_obj_t obj1, obj2;
nlr_buf_t nlr;

volatile machine_uint_t currently_in_except_block = 0; // 0 or 1, to detect nested exceptions
mp_exc_stack exc_stack[4];
mp_exc_stack *volatile exc_sp = &exc_stack[0] - 1; // stack grows up, exc_sp points to top of stack
volatile machine_uint_t currently_in_except_block = (int)*exc_sp_in_out & 1; // 0 or 1, to detect nested exceptions
mp_exc_stack *volatile exc_sp = (void*)((int)*exc_sp_in_out & ~1); // stack grows up, exc_sp points to top of stack
const byte *volatile save_ip = ip; // this is so we can access ip in the exception handler without making ip volatile (which means the compiler can't keep it in a register in the main loop)

// outer exception handling loop
for (;;) {
if (nlr_push(&nlr) == 0) {
// If we have exception to inject, now that we finish setting up
// execution context, raise it. This works as if RAISE_VARARGS
// bytecode was executed.
if (inject_exc != MP_OBJ_NULL) {
mp_obj_t t = inject_exc;
inject_exc = MP_OBJ_NULL;
nlr_jump(rt_make_raise_obj(t));
}
// loop to execute byte code
for (;;) {
dispatch_loop:
Expand Down Expand Up @@ -434,7 +436,7 @@ mp_vm_return_kind_t mp_execute_byte_code_2(const byte *code_info, const byte **i
// matched against: SETUP_EXCEPT, SETUP_FINALLY, SETUP_WITH
case MP_BC_POP_BLOCK:
// we are exiting an exception handler, so pop the last one of the exception-stack
assert(exc_sp >= &exc_stack[0]);
assert(exc_sp >= exc_stack);
currently_in_except_block = (exc_sp->val_sp & 1); // restore previous state
exc_sp--; // pop back to previous exception handler
break;
Expand All @@ -443,7 +445,7 @@ mp_vm_return_kind_t mp_execute_byte_code_2(const byte *code_info, const byte **i
case MP_BC_POP_EXCEPT:
// TODO need to work out how blocks work etc
// pops block, checks it's an exception block, and restores the stack, saving the 3 exception values to local threadstate
assert(exc_sp >= &exc_stack[0]);
assert(exc_sp >= exc_stack);
assert(currently_in_except_block);
//sp = (mp_obj_t*)(*exc_sp--);
//exc_sp--; // discard ip
Expand Down Expand Up @@ -592,7 +594,7 @@ mp_vm_return_kind_t mp_execute_byte_code_2(const byte *code_info, const byte **i
}
nlr_pop();
*sp_in_out = sp;
assert(exc_sp == &exc_stack[0] - 1);
assert(exc_sp == exc_stack - 1);
return MP_VM_RETURN_NORMAL;

case MP_BC_RAISE_VARARGS:
Expand All @@ -605,6 +607,7 @@ mp_vm_return_kind_t mp_execute_byte_code_2(const byte *code_info, const byte **i
nlr_pop();
*ip_in_out = ip;
*sp_in_out = sp;
*exc_sp_in_out = (void*)((int)exc_sp | currently_in_except_block);
return MP_VM_RETURN_YIELD;

case MP_BC_IMPORT_NAME:
Expand Down Expand Up @@ -654,7 +657,7 @@ mp_vm_return_kind_t mp_execute_byte_code_2(const byte *code_info, const byte **i
while (currently_in_except_block) {
// nested exception

assert(exc_sp >= &exc_stack[0]);
assert(exc_sp >= exc_stack);

// TODO make a proper message for nested exception
// at the moment we are just raising the very last exception (the one that caused the nested exception)
Expand All @@ -664,7 +667,7 @@ mp_vm_return_kind_t mp_execute_byte_code_2(const byte *code_info, const byte **i
exc_sp--; // pop back to previous exception handler
}

if (exc_sp >= &exc_stack[0]) {
if (exc_sp >= exc_stack) {
// set flag to indicate that we are now handling an exception
currently_in_except_block = 1;

Expand Down
53 changes: 53 additions & 0 deletions tests/basics/generator-exc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Test proper handling of exceptions within generator across yield
def gen():
try:
yield 1
raise ValueError
except ValueError:
print("Caught")
yield 2

for i in gen():
print(i)


# Test throwing exceptions out of generator
def gen2():
yield 1
raise ValueError
yield 2
yield 3

g = gen2()
print(next(g))
try:
print(next(g))
except ValueError:
print("ValueError")

try:
print(next(g))
except StopIteration:
print("StopIteration")


# Test throwing exception into generator
def gen3():
yield 1
try:
yield 2
except ValueError:
print("ValueError received")
yield 3
yield 4
yield 5

g = gen3()
print(next(g))
print(next(g))
print("out of throw:", g.throw(ValueError))
print(next(g))
try:
print("out of throw2:", g.throw(ValueError))
except ValueError:
print("Boomerang ValueError caught")
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy