@@ -1351,30 +1351,46 @@ def emit_yield_from_or_await(
13511351
13521352 stop_block , main_block , done_block = BasicBlock (), BasicBlock (), BasicBlock ()
13531353
1354- if isinstance (iter_reg .type , RInstance ) and iter_reg .type .class_ir .has_method (helper_method ):
1355- # Second fast path optimization: call helper directly (see also comment above).
1356- #
1357- # Calling a generated generator, so avoid raising StopIteration by passing
1358- # an extra PyObject ** argument to helper where the stop iteration value is stored.
1359- fast_path = True
1354+ fast_path = isinstance (iter_reg .type , RInstance ) and iter_reg .type .class_ir .has_method (
1355+ helper_method
1356+ )
1357+
1358+ # Register where a native child stores its return value instead of raising
1359+ # StopIteration (only used on the fast path).
1360+ stop_iter_val = Register (object_rprimitive ) if fast_path else None
1361+
1362+ def native_step (sent : Value ) -> Value :
1363+ """Advance a native generator/coroutine child by calling its helper directly.
1364+
1365+ Returns the value yielded by the child, or NULL if the child completed or
1366+ raised. On normal completion the return value is stored in stop_iter_val,
1367+ which is set to the error value if a real exception was raised instead.
1368+ """
1369+ assert stop_iter_val is not None
13601370 obj = builder .read (iter_reg , line )
13611371 nn = builder .none_object ()
1362- stop_iter_val = Register (object_rprimitive )
13631372 err = builder .add (LoadErrorValue (object_rprimitive , undefines = True ))
13641373 builder .assign (stop_iter_val , err , line )
13651374 ptr = builder .add (LoadAddress (object_pointer_rprimitive , stop_iter_val ))
1366- m = MethodCall (obj , helper_method , [nn , nn , nn , nn , ptr ], line )
1375+ m = MethodCall (obj , helper_method , [nn , nn , nn , sent , ptr ], line )
13671376 # Generators have custom error handling, so disable normal error handling.
13681377 m .error_kind = ERR_NEVER
1369- _y_init = builder .add (m )
1378+ return builder .add (m )
1379+
1380+ if fast_path :
1381+ # Second fast path optimization: call helper directly (see also comment above).
1382+ #
1383+ # Calling a generated generator, so avoid raising StopIteration by passing
1384+ # an extra PyObject ** argument to helper where the stop iteration value is stored.
1385+ _y_init = native_step (builder .none_object ())
13701386 else :
1371- fast_path = False
13721387 _y_init = builder .call_c (next_raw_op , [builder .read (iter_reg , line )], line )
13731388
13741389 builder .add (Branch (_y_init , stop_block , main_block , Branch .IS_ERROR ))
13751390
13761391 builder .activate_block (stop_block )
13771392 if fast_path :
1393+ assert stop_iter_val is not None
13781394 builder .primitive_op (propagate_if_error_op , [stop_iter_val ], line )
13791395 builder .assign (result , stop_iter_val , line )
13801396 else :
@@ -1423,11 +1439,18 @@ def except_body() -> None:
14231439 builder .nonlocal_control [- 1 ].gen_break (builder , line )
14241440
14251441 def else_body () -> None :
1426- # Do a next() or a .send(). It will return NULL on exception
1427- # but it won't automatically propagate.
1428- _y = builder .call_c (
1429- send_op , [builder .read (iter_reg , line ), builder .read (received_reg , line )], line
1430- )
1442+ # This path runs when the parent's yield is resumed normally via next() or send().
1443+ # An exception injected via throw() or close() takes the except_body path instead.
1444+ if fast_path :
1445+ # Reuse the direct helper call on resumes as well, so that native-to-native
1446+ # completion doesn't have to go through .send() and StopIteration.
1447+ _y = native_step (builder .read (received_reg , line ))
1448+ else :
1449+ # Do a next() or a .send(). It will return NULL on exception
1450+ # but it won't automatically propagate.
1451+ _y = builder .call_c (
1452+ send_op , [builder .read (iter_reg , line ), builder .read (received_reg , line )], line
1453+ )
14311454 ok , stop = BasicBlock (), BasicBlock ()
14321455 builder .add (Branch (_y , stop , ok , Branch .IS_ERROR ))
14331456
@@ -1436,10 +1459,17 @@ def else_body() -> None:
14361459 builder .assign (to_yield_reg , _y , line )
14371460 builder .nonlocal_control [- 1 ].gen_continue (builder , line )
14381461
1439- # Try extracting a return value from a StopIteration and return it.
1440- # If it wasn't, this rereaises the exception.
14411462 builder .activate_block (stop )
1442- builder .assign (result , builder .call_c (check_stop_op , [], line ), line )
1463+ if fast_path :
1464+ assert stop_iter_val is not None
1465+ # The child either returned a value through the out pointer, or raised
1466+ # a real exception (in which case this propagates it).
1467+ builder .primitive_op (propagate_if_error_op , [stop_iter_val ], line )
1468+ builder .assign (result , stop_iter_val , line )
1469+ else :
1470+ # Try extracting a return value from a StopIteration and return it.
1471+ # If it wasn't, this rereaises the exception.
1472+ builder .assign (result , builder .call_c (check_stop_op , [], line ), line )
14431473 builder .nonlocal_control [- 1 ].gen_break (builder , line )
14441474
14451475 builder .push_loop_stack (loop_block , done_block )
0 commit comments