Skip to content

Commit 8c1d23a

Browse files
committed
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.
1 parent ede0f3a commit 8c1d23a

3 files changed

Lines changed: 50 additions & 33 deletions

File tree

py/emitbc.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -680,12 +680,15 @@ void mp_emit_bc_unwind_jump(emit_t *emit, mp_uint_t label, mp_uint_t except_dept
680680
}
681681

682682
void mp_emit_bc_setup_with(emit_t *emit, mp_uint_t label) {
683-
emit_bc_pre(emit, 7);
683+
// TODO We can probably optimise the amount of needed stack space, since
684+
// we don't actually need 4 slots during the entire with block, only in
685+
// the cleanup handler in certain cases. It needs some thinking.
686+
emit_bc_pre(emit, 4);
684687
emit_write_bytecode_byte_unsigned_label(emit, MP_BC_SETUP_WITH, label);
685688
}
686689

687690
void mp_emit_bc_with_cleanup(emit_t *emit) {
688-
emit_bc_pre(emit, -7);
691+
emit_bc_pre(emit, -4);
689692
emit_write_bytecode_byte(emit, MP_BC_WITH_CLEANUP);
690693
}
691694

py/vm.c

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -548,68 +548,82 @@ run_code_state: ;
548548

549549
ENTRY(MP_BC_SETUP_WITH): {
550550
MARK_EXC_IP_SELECTIVE();
551+
// stack: (..., ctx_mgr)
551552
mp_obj_t obj = TOP();
552-
SET_TOP(mp_load_attr(obj, MP_QSTR___exit__));
553-
mp_load_method(obj, MP_QSTR___enter__, sp + 1);
554-
mp_obj_t ret = mp_call_method_n_kw(0, 0, sp + 1);
553+
mp_load_method(obj, MP_QSTR___exit__, sp);
554+
mp_load_method(obj, MP_QSTR___enter__, sp + 2);
555+
mp_obj_t ret = mp_call_method_n_kw(0, 0, sp + 2);
556+
sp += 1;
555557
PUSH_EXC_BLOCK(1);
556558
PUSH(ret);
559+
// stack: (..., __exit__, ctx_mgr, as_value)
557560
DISPATCH();
558561
}
559562

560563
ENTRY(MP_BC_WITH_CLEANUP): {
561564
MARK_EXC_IP_SELECTIVE();
562565
// Arriving here, there's "exception control block" on top of stack,
563-
// and __exit__ bound method underneath it. Bytecode calls __exit__,
566+
// and __exit__ method (with self) underneath it. Bytecode calls __exit__,
564567
// and "deletes" it off stack, shifting "exception control block"
565568
// to its place.
566-
static const mp_obj_t no_exc[] = {mp_const_none, mp_const_none, mp_const_none};
567569
if (TOP() == mp_const_none) {
568-
sp--;
569-
mp_obj_t obj = TOP();
570+
// stack: (..., __exit__, ctx_mgr, None)
571+
sp[1] = mp_const_none;
572+
sp[2] = mp_const_none;
573+
sp -= 2;
574+
mp_call_method_n_kw(3, 0, sp);
570575
SET_TOP(mp_const_none);
571-
mp_call_function_n_kw(obj, 3, 0, no_exc);
572576
} else if (MP_OBJ_IS_SMALL_INT(TOP())) {
573577
mp_int_t cause_val = MP_OBJ_SMALL_INT_VALUE(TOP());
574578
if (cause_val == UNWIND_RETURN) {
575-
mp_call_function_n_kw(sp[-2], 3, 0, no_exc);
579+
// stack: (..., __exit__, ctx_mgr, ret_val, UNWIND_RETURN)
580+
mp_obj_t ret_val = sp[-1];
581+
sp[-1] = mp_const_none;
582+
sp[0] = mp_const_none;
583+
sp[1] = mp_const_none;
584+
mp_call_method_n_kw(3, 0, sp - 3);
585+
sp[-3] = ret_val;
586+
sp[-2] = MP_OBJ_NEW_SMALL_INT(UNWIND_RETURN);
576587
} else {
577588
assert(cause_val == UNWIND_JUMP);
578-
mp_call_function_n_kw(sp[-3], 3, 0, no_exc);
579-
// Pop __exit__ boundmethod at sp[-3]
580-
sp[-3] = sp[-2];
589+
// stack: (..., __exit__, ctx_mgr, dest_ip, num_exc, UNWIND_JUMP)
590+
mp_obj_t dest_ip = sp[-2];
591+
mp_obj_t num_exc = sp[-1];
592+
sp[-2] = mp_const_none;
593+
sp[-1] = mp_const_none;
594+
sp[0] = mp_const_none;
595+
mp_call_method_n_kw(3, 0, sp - 4);
596+
sp[-4] = dest_ip;
597+
sp[-3] = num_exc;
598+
sp[-2] = MP_OBJ_NEW_SMALL_INT(UNWIND_JUMP);
581599
}
582-
sp[-2] = sp[-1]; // copy retval down
583-
sp[-1] = sp[0]; // copy cause down
584-
sp--; // discard top value (was cause)
600+
sp -= 2; // we removed (__exit__, ctx_mgr)
585601
} else {
586602
assert(mp_obj_is_exception_type(TOP()));
603+
// stack: (..., __exit__, ctx_mgr, traceback, exc_val, exc_type)
587604
// Need to pass (sp[0], sp[-1], sp[-2]) as arguments so must reverse the
588605
// order of these on the value stack (don't want to create a temporary
589606
// array because it increases stack footprint of the VM).
590607
mp_obj_t obj = sp[-2];
591608
sp[-2] = sp[0];
592609
sp[0] = obj;
593-
mp_obj_t ret_value = mp_call_function_n_kw(sp[-3], 3, 0, &sp[-2]);
610+
mp_obj_t ret_value = mp_call_method_n_kw(3, 0, sp - 4);
594611
if (mp_obj_is_true(ret_value)) {
595-
// This is what CPython does
596-
//PUSH(MP_OBJ_NEW_SMALL_INT(UNWIND_SILENCED));
597-
// But what we need to do is - pop exception from value stack...
612+
// We need to silence/swallow the exception. This is done
613+
// by popping the exception and the __exit__ handler and
614+
// replacing it with None, which signals END_FINALLY to just
615+
// execute the finally handler normally.
598616
sp -= 4;
599-
// ... pop "with" exception handler, and signal END_FINALLY
600-
// to just execute finally handler normally (by pushing None
601-
// on value stack)
617+
SET_TOP(mp_const_none);
602618
assert(exc_sp >= exc_stack);
603619
POP_EXC_BLOCK();
604-
PUSH(mp_const_none);
605620
} else {
606-
// Pop __exit__ boundmethod at sp[-3], remembering that top 3 values
607-
// are reversed.
608-
sp[-3] = sp[0];
609-
obj = sp[-2];
610-
sp[-2] = sp[-1];
611-
sp[-1] = obj;
612-
sp--;
621+
// We need to re-raise the exception. We pop __exit__ handler
622+
// and copy the 3 exception values down (remembering that they
623+
// are reversed due to above code).
624+
sp[-4] = sp[0];
625+
sp[-3] = sp[-1];
626+
sp -= 2;
613627
}
614628
}
615629
DISPATCH();

tests/cmdline/cmd_showbc.py.exp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Raw bytecode (code_info_size=\\d\+, bytecode_size=\\d\+):
2929
########
3030
\.\+5b
3131
arg names:
32-
(N_STATE 25)
32+
(N_STATE 22)
3333
(N_EXC_STACK 2)
3434
(INIT_CELL 14)
3535
(INIT_CELL 15)

0 commit comments

Comments
 (0)