kopia lustrzana https://github.com/micropython/micropython
py: Modify bytecode "with" behaviour so it doesn't use any heap.
Before this patch a "with" block needed to create a bound method object on the heap for the __exit__ call. Now it doesn't because we use load_method instead of load_attr, and save the method+self on the stack.pull/1197/head
rodzic
ede0f3ab3d
commit
8c1d23a0e2
|
@ -680,12 +680,15 @@ void mp_emit_bc_unwind_jump(emit_t *emit, mp_uint_t label, mp_uint_t except_dept
|
||||||
}
|
}
|
||||||
|
|
||||||
void mp_emit_bc_setup_with(emit_t *emit, mp_uint_t label) {
|
void mp_emit_bc_setup_with(emit_t *emit, mp_uint_t label) {
|
||||||
emit_bc_pre(emit, 7);
|
// TODO We can probably optimise the amount of needed stack space, since
|
||||||
|
// we don't actually need 4 slots during the entire with block, only in
|
||||||
|
// the cleanup handler in certain cases. It needs some thinking.
|
||||||
|
emit_bc_pre(emit, 4);
|
||||||
emit_write_bytecode_byte_unsigned_label(emit, MP_BC_SETUP_WITH, label);
|
emit_write_bytecode_byte_unsigned_label(emit, MP_BC_SETUP_WITH, label);
|
||||||
}
|
}
|
||||||
|
|
||||||
void mp_emit_bc_with_cleanup(emit_t *emit) {
|
void mp_emit_bc_with_cleanup(emit_t *emit) {
|
||||||
emit_bc_pre(emit, -7);
|
emit_bc_pre(emit, -4);
|
||||||
emit_write_bytecode_byte(emit, MP_BC_WITH_CLEANUP);
|
emit_write_bytecode_byte(emit, MP_BC_WITH_CLEANUP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
74
py/vm.c
74
py/vm.c
|
@ -548,68 +548,82 @@ dispatch_loop:
|
||||||
|
|
||||||
ENTRY(MP_BC_SETUP_WITH): {
|
ENTRY(MP_BC_SETUP_WITH): {
|
||||||
MARK_EXC_IP_SELECTIVE();
|
MARK_EXC_IP_SELECTIVE();
|
||||||
|
// stack: (..., ctx_mgr)
|
||||||
mp_obj_t obj = TOP();
|
mp_obj_t obj = TOP();
|
||||||
SET_TOP(mp_load_attr(obj, MP_QSTR___exit__));
|
mp_load_method(obj, MP_QSTR___exit__, sp);
|
||||||
mp_load_method(obj, MP_QSTR___enter__, sp + 1);
|
mp_load_method(obj, MP_QSTR___enter__, sp + 2);
|
||||||
mp_obj_t ret = mp_call_method_n_kw(0, 0, sp + 1);
|
mp_obj_t ret = mp_call_method_n_kw(0, 0, sp + 2);
|
||||||
|
sp += 1;
|
||||||
PUSH_EXC_BLOCK(1);
|
PUSH_EXC_BLOCK(1);
|
||||||
PUSH(ret);
|
PUSH(ret);
|
||||||
|
// stack: (..., __exit__, ctx_mgr, as_value)
|
||||||
DISPATCH();
|
DISPATCH();
|
||||||
}
|
}
|
||||||
|
|
||||||
ENTRY(MP_BC_WITH_CLEANUP): {
|
ENTRY(MP_BC_WITH_CLEANUP): {
|
||||||
MARK_EXC_IP_SELECTIVE();
|
MARK_EXC_IP_SELECTIVE();
|
||||||
// Arriving here, there's "exception control block" on top of stack,
|
// Arriving here, there's "exception control block" on top of stack,
|
||||||
// and __exit__ bound method underneath it. Bytecode calls __exit__,
|
// and __exit__ method (with self) underneath it. Bytecode calls __exit__,
|
||||||
// and "deletes" it off stack, shifting "exception control block"
|
// and "deletes" it off stack, shifting "exception control block"
|
||||||
// to its place.
|
// to its place.
|
||||||
static const mp_obj_t no_exc[] = {mp_const_none, mp_const_none, mp_const_none};
|
|
||||||
if (TOP() == mp_const_none) {
|
if (TOP() == mp_const_none) {
|
||||||
sp--;
|
// stack: (..., __exit__, ctx_mgr, None)
|
||||||
mp_obj_t obj = TOP();
|
sp[1] = mp_const_none;
|
||||||
|
sp[2] = mp_const_none;
|
||||||
|
sp -= 2;
|
||||||
|
mp_call_method_n_kw(3, 0, sp);
|
||||||
SET_TOP(mp_const_none);
|
SET_TOP(mp_const_none);
|
||||||
mp_call_function_n_kw(obj, 3, 0, no_exc);
|
|
||||||
} else if (MP_OBJ_IS_SMALL_INT(TOP())) {
|
} else if (MP_OBJ_IS_SMALL_INT(TOP())) {
|
||||||
mp_int_t cause_val = MP_OBJ_SMALL_INT_VALUE(TOP());
|
mp_int_t cause_val = MP_OBJ_SMALL_INT_VALUE(TOP());
|
||||||
if (cause_val == UNWIND_RETURN) {
|
if (cause_val == UNWIND_RETURN) {
|
||||||
mp_call_function_n_kw(sp[-2], 3, 0, no_exc);
|
// stack: (..., __exit__, ctx_mgr, ret_val, UNWIND_RETURN)
|
||||||
|
mp_obj_t ret_val = sp[-1];
|
||||||
|
sp[-1] = mp_const_none;
|
||||||
|
sp[0] = mp_const_none;
|
||||||
|
sp[1] = mp_const_none;
|
||||||
|
mp_call_method_n_kw(3, 0, sp - 3);
|
||||||
|
sp[-3] = ret_val;
|
||||||
|
sp[-2] = MP_OBJ_NEW_SMALL_INT(UNWIND_RETURN);
|
||||||
} else {
|
} else {
|
||||||
assert(cause_val == UNWIND_JUMP);
|
assert(cause_val == UNWIND_JUMP);
|
||||||
mp_call_function_n_kw(sp[-3], 3, 0, no_exc);
|
// stack: (..., __exit__, ctx_mgr, dest_ip, num_exc, UNWIND_JUMP)
|
||||||
// Pop __exit__ boundmethod at sp[-3]
|
mp_obj_t dest_ip = sp[-2];
|
||||||
sp[-3] = sp[-2];
|
mp_obj_t num_exc = sp[-1];
|
||||||
|
sp[-2] = mp_const_none;
|
||||||
|
sp[-1] = mp_const_none;
|
||||||
|
sp[0] = mp_const_none;
|
||||||
|
mp_call_method_n_kw(3, 0, sp - 4);
|
||||||
|
sp[-4] = dest_ip;
|
||||||
|
sp[-3] = num_exc;
|
||||||
|
sp[-2] = MP_OBJ_NEW_SMALL_INT(UNWIND_JUMP);
|
||||||
}
|
}
|
||||||
sp[-2] = sp[-1]; // copy retval down
|
sp -= 2; // we removed (__exit__, ctx_mgr)
|
||||||
sp[-1] = sp[0]; // copy cause down
|
|
||||||
sp--; // discard top value (was cause)
|
|
||||||
} else {
|
} else {
|
||||||
assert(mp_obj_is_exception_type(TOP()));
|
assert(mp_obj_is_exception_type(TOP()));
|
||||||
|
// stack: (..., __exit__, ctx_mgr, traceback, exc_val, exc_type)
|
||||||
// Need to pass (sp[0], sp[-1], sp[-2]) as arguments so must reverse the
|
// Need to pass (sp[0], sp[-1], sp[-2]) as arguments so must reverse the
|
||||||
// order of these on the value stack (don't want to create a temporary
|
// order of these on the value stack (don't want to create a temporary
|
||||||
// array because it increases stack footprint of the VM).
|
// array because it increases stack footprint of the VM).
|
||||||
mp_obj_t obj = sp[-2];
|
mp_obj_t obj = sp[-2];
|
||||||
sp[-2] = sp[0];
|
sp[-2] = sp[0];
|
||||||
sp[0] = obj;
|
sp[0] = obj;
|
||||||
mp_obj_t ret_value = mp_call_function_n_kw(sp[-3], 3, 0, &sp[-2]);
|
mp_obj_t ret_value = mp_call_method_n_kw(3, 0, sp - 4);
|
||||||
if (mp_obj_is_true(ret_value)) {
|
if (mp_obj_is_true(ret_value)) {
|
||||||
// This is what CPython does
|
// We need to silence/swallow the exception. This is done
|
||||||
//PUSH(MP_OBJ_NEW_SMALL_INT(UNWIND_SILENCED));
|
// by popping the exception and the __exit__ handler and
|
||||||
// But what we need to do is - pop exception from value stack...
|
// replacing it with None, which signals END_FINALLY to just
|
||||||
|
// execute the finally handler normally.
|
||||||
sp -= 4;
|
sp -= 4;
|
||||||
// ... pop "with" exception handler, and signal END_FINALLY
|
SET_TOP(mp_const_none);
|
||||||
// to just execute finally handler normally (by pushing None
|
|
||||||
// on value stack)
|
|
||||||
assert(exc_sp >= exc_stack);
|
assert(exc_sp >= exc_stack);
|
||||||
POP_EXC_BLOCK();
|
POP_EXC_BLOCK();
|
||||||
PUSH(mp_const_none);
|
|
||||||
} else {
|
} else {
|
||||||
// Pop __exit__ boundmethod at sp[-3], remembering that top 3 values
|
// We need to re-raise the exception. We pop __exit__ handler
|
||||||
// are reversed.
|
// and copy the 3 exception values down (remembering that they
|
||||||
sp[-3] = sp[0];
|
// are reversed due to above code).
|
||||||
obj = sp[-2];
|
sp[-4] = sp[0];
|
||||||
sp[-2] = sp[-1];
|
sp[-3] = sp[-1];
|
||||||
sp[-1] = obj;
|
sp -= 2;
|
||||||
sp--;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DISPATCH();
|
DISPATCH();
|
||||||
|
|
|
@ -29,7 +29,7 @@ Raw bytecode (code_info_size=\\d\+, bytecode_size=\\d\+):
|
||||||
########
|
########
|
||||||
\.\+5b
|
\.\+5b
|
||||||
arg names:
|
arg names:
|
||||||
(N_STATE 25)
|
(N_STATE 22)
|
||||||
(N_EXC_STACK 2)
|
(N_EXC_STACK 2)
|
||||||
(INIT_CELL 14)
|
(INIT_CELL 14)
|
||||||
(INIT_CELL 15)
|
(INIT_CELL 15)
|
||||||
|
|
Ładowanie…
Reference in New Issue