Skip to content

Commit 038125b

Browse files
committed
py/emitnative: Fix native async with.
The code generating the entry to the finally handler of an async-with statement was simply wrong for the case of the native emitter. Among other things the layout of the stack was incorrect. This is fixed by this commit. The setup of the async-with finally handler is now put in a dedicated emit function, for both the bytecode and native emitters to implement in their own way (the bytecode emitter is unchanged, just factored to a function). With this fix all of the async-with tests now work when using the native emitter. Signed-off-by: Damien George <[email protected]>
1 parent a19214d commit 038125b

File tree

5 files changed

+70
-20
lines changed

5 files changed

+70
-20
lines changed

py/compile.c

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1899,19 +1899,7 @@ static void compile_async_with_stmt_helper(compiler_t *comp, size_t n, mp_parse_
18991899

19001900
// Handle case 1: call __aexit__
19011901
// Stack: (..., ctx_mgr)
1902-
EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); // to tell end_finally there's no exception
1903-
EMIT(rot_two);
1904-
EMIT_ARG(jump, l_aexit_no_exc); // jump to code below to call __aexit__
1905-
1906-
// Start of "finally" block
1907-
// At this point we have case 2 or 3, we detect which one by the TOS being an exception or not
1908-
EMIT_ARG(label_assign, l_finally_block);
1909-
1910-
// Detect if TOS an exception or not
1911-
EMIT(dup_top);
1912-
EMIT_LOAD_GLOBAL(MP_QSTR_BaseException);
1913-
EMIT_ARG(binary_op, MP_BINARY_OP_EXCEPTION_MATCH);
1914-
EMIT_ARG(pop_jump_if, false, l_ret_unwind_jump); // if not an exception then we have case 3
1902+
EMIT_ARG(async_with_setup_finally, l_aexit_no_exc, l_finally_block, l_ret_unwind_jump);
19151903

19161904
// Handle case 2: call __aexit__ and either swallow or re-raise the exception
19171905
// Stack: (..., ctx_mgr, exc)
@@ -1937,6 +1925,7 @@ static void compile_async_with_stmt_helper(compiler_t *comp, size_t n, mp_parse_
19371925
EMIT_ARG(pop_jump_if, false, l_end);
19381926
EMIT(pop_top); // pop exception
19391927
EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); // replace with None to swallow exception
1928+
// Stack: (..., None)
19401929
EMIT_ARG(jump, l_end);
19411930
EMIT_ARG(adjust_stack_size, 2);
19421931

@@ -1946,13 +1935,16 @@ static void compile_async_with_stmt_helper(compiler_t *comp, size_t n, mp_parse_
19461935
EMIT(rot_three);
19471936
EMIT(rot_three);
19481937
EMIT_ARG(label_assign, l_aexit_no_exc);
1938+
// We arrive here from either case 1 (a jump) or case 3 (fall through)
1939+
// Stack: case 1: (..., None, ctx_mgr) or case 3: (..., X, INT, ctx_mgr)
19491940
EMIT_ARG(load_method, MP_QSTR___aexit__, false);
19501941
EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE);
19511942
EMIT(dup_top);
19521943
EMIT(dup_top);
19531944
EMIT_ARG(call_method, 3, 0, 0);
19541945
compile_yield_from(comp);
19551946
EMIT(pop_top);
1947+
// Stack: case 1: (..., None) or case 3: (..., X, INT)
19561948
EMIT_ARG(adjust_stack_size, -1);
19571949

19581950
// End of "finally" block

py/emit.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ typedef struct _emit_method_table_t {
144144
void (*unwind_jump)(emit_t *emit, mp_uint_t label, mp_uint_t except_depth);
145145
void (*setup_block)(emit_t *emit, mp_uint_t label, int kind);
146146
void (*with_cleanup)(emit_t *emit, mp_uint_t label);
147+
#if MICROPY_PY_ASYNC_AWAIT
148+
void (*async_with_setup_finally)(emit_t *emit, mp_uint_t label_aexit_no_exc, mp_uint_t label_finally_block, mp_uint_t label_ret_unwind_jump);
149+
#endif
147150
void (*end_finally)(emit_t *emit);
148151
void (*get_iter)(emit_t *emit, bool use_stack);
149152
void (*for_iter)(emit_t *emit, mp_uint_t label);
@@ -264,6 +267,9 @@ void mp_emit_bc_jump_if_or_pop(emit_t *emit, bool cond, mp_uint_t label);
264267
void mp_emit_bc_unwind_jump(emit_t *emit, mp_uint_t label, mp_uint_t except_depth);
265268
void mp_emit_bc_setup_block(emit_t *emit, mp_uint_t label, int kind);
266269
void mp_emit_bc_with_cleanup(emit_t *emit, mp_uint_t label);
270+
#if MICROPY_PY_ASYNC_AWAIT
271+
void mp_emit_bc_async_with_setup_finally(emit_t *emit, mp_uint_t label_aexit_no_exc, mp_uint_t label_finally_block, mp_uint_t label_ret_unwind_jump);
272+
#endif
267273
void mp_emit_bc_end_finally(emit_t *emit);
268274
void mp_emit_bc_get_iter(emit_t *emit, bool use_stack);
269275
void mp_emit_bc_for_iter(emit_t *emit, mp_uint_t label);

py/emitbc.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,27 @@ void mp_emit_bc_with_cleanup(emit_t *emit, mp_uint_t label) {
666666
mp_emit_bc_adjust_stack_size(emit, -4);
667667
}
668668

669+
#if MICROPY_PY_ASYNC_AWAIT
670+
void mp_emit_bc_async_with_setup_finally(emit_t *emit, mp_uint_t label_aexit_no_exc, mp_uint_t label_finally_block, mp_uint_t label_ret_unwind_jump) {
671+
// The async-with body has executed and no exception was raised, the execution fell through to this point.
672+
// Stack: (..., ctx_mgr)
673+
674+
// Finish async-with body and prepare to enter "finally" block.
675+
mp_emit_bc_load_const_tok(emit, MP_TOKEN_KW_NONE); // to tell end_finally there's no exception
676+
mp_emit_bc_rot_two(emit);
677+
mp_emit_bc_jump(emit, label_aexit_no_exc); // jump to code to call __aexit__
678+
679+
// Start of "finally" block which is entered via one of: an exception propagating out, a return, an unwind jump.
680+
mp_emit_bc_label_assign(emit, label_finally_block);
681+
682+
// Detect which case we have by the TOS being an exception or not.
683+
mp_emit_bc_dup_top(emit);
684+
mp_emit_bc_load_global(emit, MP_QSTR_BaseException, MP_EMIT_IDOP_GLOBAL_GLOBAL);
685+
mp_emit_bc_binary_op(emit, MP_BINARY_OP_EXCEPTION_MATCH);
686+
mp_emit_bc_pop_jump_if(emit, false, label_ret_unwind_jump); // if not an exception then we have return or unwind jump.
687+
}
688+
#endif
689+
669690
void mp_emit_bc_end_finally(emit_t *emit) {
670691
emit_write_bytecode_byte(emit, -1, MP_BC_END_FINALLY);
671692
}
@@ -862,6 +883,9 @@ const emit_method_table_t emit_bc_method_table = {
862883
mp_emit_bc_unwind_jump,
863884
mp_emit_bc_setup_block,
864885
mp_emit_bc_with_cleanup,
886+
#if MICROPY_PY_ASYNC_AWAIT
887+
mp_emit_bc_async_with_setup_finally,
888+
#endif
865889
mp_emit_bc_end_finally,
866890
mp_emit_bc_get_iter,
867891
mp_emit_bc_for_iter,

py/emitnative.c

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,7 +1145,7 @@ static void emit_native_label_assign(emit_t *emit, mp_uint_t l) {
11451145
if (is_finally) {
11461146
// Label is at start of finally handler: store TOS into exception slot
11471147
vtype_kind_t vtype;
1148-
emit_pre_pop_reg(emit, &vtype, REG_TEMP0);
1148+
emit_access_stack(emit, 1, &vtype, REG_TEMP0);
11491149
ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_EXC_VAL(emit), REG_TEMP0);
11501150
}
11511151

@@ -1201,6 +1201,10 @@ static void emit_native_global_exc_entry(emit_t *emit) {
12011201
ASM_XOR_REG_REG(emit->as, REG_TEMP0, REG_TEMP0);
12021202
ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_EXC_HANDLER_UNWIND(emit), REG_TEMP0);
12031203

1204+
// clear nlr.ret_val, because it's passed to mp_native_raise regardless
1205+
// of whether there was an exception or not
1206+
ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_EXC_VAL(emit), REG_TEMP0);
1207+
12041208
// Put PC of start code block into REG_LOCAL_1
12051209
ASM_MOV_REG_PCREL(emit->as, REG_LOCAL_1, start_label);
12061210

@@ -2235,8 +2239,34 @@ static void emit_native_with_cleanup(emit_t *emit, mp_uint_t label) {
22352239
emit_native_label_assign(emit, *emit->label_slot + 1);
22362240

22372241
// Exception is in nlr_buf.ret_val slot
2242+
adjust_stack(emit, 1);
22382243
}
22392244

2245+
#if MICROPY_PY_ASYNC_AWAIT
2246+
static void emit_native_async_with_setup_finally(emit_t *emit, mp_uint_t label_aexit_no_exc, mp_uint_t label_finally_block, mp_uint_t label_ret_unwind_jump) {
2247+
// The async-with body has executed and no exception was raised, the execution fell through to this point.
2248+
// Stack: (..., ctx_mgr)
2249+
2250+
// Insert a dummy value into the stack so the stack has the same layout to execute the code starting at label_aexit_no_exc
2251+
emit_native_adjust_stack_size(emit, 1); // push dummy value, it won't ever be used
2252+
emit_native_rot_two(emit);
2253+
emit_native_load_const_tok(emit, MP_TOKEN_KW_NONE); // to tell end_finally there's no exception
2254+
emit_native_rot_two(emit);
2255+
// Stack: (..., <dummy>, None, ctx_mgr)
2256+
emit_native_jump(emit, label_aexit_no_exc); // jump to code to call __aexit__
2257+
emit_native_adjust_stack_size(emit, -1);
2258+
2259+
// Start of "finally" block which is entered via one of: an exception propagating out, a return, an unwind jump.
2260+
emit_native_label_assign(emit, label_finally_block);
2261+
2262+
// Detect which case we have by the local exception slot holding an exception or not.
2263+
emit_pre_pop_discard(emit);
2264+
ASM_MOV_REG_LOCAL(emit->as, REG_ARG_1, LOCAL_IDX_EXC_VAL(emit)); // get exception
2265+
emit_post_push_reg(emit, VTYPE_PYOBJ, REG_ARG_1);
2266+
ASM_JUMP_IF_REG_ZERO(emit->as, REG_ARG_1, label_ret_unwind_jump, false); // if not an exception then we have return or unwind jump.
2267+
}
2268+
#endif
2269+
22402270
static void emit_native_end_finally(emit_t *emit) {
22412271
// logic:
22422272
// exc = pop_stack
@@ -2245,7 +2275,7 @@ static void emit_native_end_finally(emit_t *emit) {
22452275
// the check if exc is None is done in the MP_F_NATIVE_RAISE stub
22462276
DEBUG_printf("end_finally\n");
22472277

2248-
emit_native_pre(emit);
2278+
emit_pre_pop_discard(emit);
22492279
ASM_MOV_REG_LOCAL(emit->as, REG_ARG_1, LOCAL_IDX_EXC_VAL(emit));
22502280
emit_call(emit, MP_F_NATIVE_RAISE);
22512281

@@ -3033,7 +3063,6 @@ static void emit_native_start_except_handler(emit_t *emit) {
30333063
}
30343064

30353065
static void emit_native_end_except_handler(emit_t *emit) {
3036-
adjust_stack(emit, -1); // pop the exception (end_finally didn't use it)
30373066
}
30383067

30393068
const emit_method_table_t EXPORT_FUN(method_table) = {
@@ -3082,6 +3111,9 @@ const emit_method_table_t EXPORT_FUN(method_table) = {
30823111
emit_native_unwind_jump,
30833112
emit_native_setup_block,
30843113
emit_native_with_cleanup,
3114+
#if MICROPY_PY_ASYNC_AWAIT
3115+
emit_native_async_with_setup_finally,
3116+
#endif
30853117
emit_native_end_finally,
30863118
emit_native_get_iter,
30873119
emit_native_for_iter,

tests/run-tests.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -717,9 +717,6 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1):
717717
# Remove them from the below when they work
718718
if args.emit == "native":
719719
skip_tests.add("basics/gen_yield_from_close.py") # require raise_varargs
720-
skip_tests.update(
721-
{"basics/async_%s.py" % t for t in "with with2 with_break with_return".split()}
722-
) # require async_with
723720
skip_tests.update(
724721
{"basics/%s.py" % t for t in "try_reraise try_reraise2".split()}
725722
) # require raise_varargs
@@ -731,7 +728,6 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1):
731728
skip_tests.add("basics/sys_tracebacklimit.py") # requires traceback info
732729
skip_tests.add("basics/try_finally_return2.py") # requires raise_varargs
733730
skip_tests.add("basics/unboundlocal.py") # requires checking for unbound local
734-
skip_tests.add("extmod/asyncio_lock.py") # requires async with
735731
skip_tests.add("misc/features.py") # requires raise_varargs
736732
skip_tests.add(
737733
"misc/print_exception.py"

0 commit comments

Comments
 (0)