@@ -56,39 +56,54 @@ namespace py {
5656 return mlir::isa<TryOp, ForLoopOp, WithOp, WhileOp, TryHandlerOp>(op);
5757 }
5858
59- // True when `yield_op` is a loop-control (break/continue) yield that binds to
60- // the loop *enclosing* `loop` rather than to `loop` itself — i.e. it sits in
61- // `loop`'s orelse, which is not part of the loop body.
62- bool binds_to_enclosing_loop (mlir::py::PyLoopOpInterface loop,
63- mlir::py::BranchYieldOp yield_op)
59+ // True when `yield_op`, a loop-control (break/continue) yield, binds to the
60+ // loop whose body region is `body`.
61+ //
62+ // Python binds break/continue to the innermost loop whose *body* lexically
63+ // contains it. An else clause is not part of its own loop's body, so a yield
64+ // sitting there keeps searching outwards — and transitively so: an else nested
65+ // inside another else is still lexically part of whatever body encloses the
66+ // pair. Regions that are neither body nor orelse (a try body, a with body) are
67+ // likewise transparent, which is what makes `break` inside a `try` bind to the
68+ // loop around it.
69+ bool binds_to_loop (mlir::Region &body, mlir::py::BranchYieldOp yield_op)
6470 {
65- return yield_op.getKind ().has_value () && loop.isLoopOrelse (yield_op->getParentRegion ());
71+ for (mlir::Region *region = yield_op->getParentRegion (); region != nullptr ;
72+ region = region->getParentRegion ()) {
73+ if (region == &body) { return true ; }
74+ auto loop =
75+ mlir::dyn_cast_if_present<mlir::py::PyLoopOpInterface>(region->getParentOp ());
76+ // A loop body (or a for's step) stops the search: the yield is that
77+ // loop's, and its own pattern claims it.
78+ if (loop && !loop.isLoopOrelse (region)) { return false ; }
79+ }
80+ return false ;
6681 }
6782
68- // True when some loop nested in `region` still holds a break/continue that
69- // binds to the loop being lowered — i.e. one sitting in that nested loop's
70- // orelse. Such a yield cannot be rewritten yet: it lives in a region that has
71- // not been flattened, so branching it to our target block would be a
72- // cross-region block reference, which is invalid IR.
83+ // True when a break/continue that binds to the loop whose body is `body` is
84+ // somewhere replace_loop_branch_yields cannot reach yet — inside a nested
85+ // region that has not been flattened into ours. Branching it to our target
86+ // block now would be a cross-region block reference, which is invalid IR.
7387 //
74- // The caller defers (fails the match) until the nested loop lowers and inlines
88+ // The caller defers (fails the match) until the nested op lowers and inlines
7589 // the yield into our region, the same innermost-first trick TryOpLowering uses
76- // for nested trys. Terminates because the innermost such loop has nothing
77- // nested to wait on.
78- bool has_pending_nested_orelse_control (mlir::Region ®ion )
90+ // for nested trys. Terminates because the innermost such op has nothing nested
91+ // to wait on.
92+ bool has_pending_nested_orelse_control (mlir::Region &body )
7993 {
80- if (region .empty ()) { return false ; }
94+ if (body .empty ()) { return false ; }
8195 bool pending = false ;
82- region.walk <WalkOrder::PreOrder>([&pending](mlir::Operation *op) {
83- auto loop = mlir::dyn_cast<mlir::py::PyLoopOpInterface>(op);
84- if (!loop) { return WalkResult::advance (); }
85- loop.getLoopOrelseRegion ().walk <WalkOrder::PreOrder>(
86- [&pending, loop](mlir::py::BranchYieldOp yield_op) {
87- if (binds_to_enclosing_loop (loop, yield_op)) { pending = true ; }
88- });
89- // Only this loop's own orelse matters here; anything deeper is the
90- // nested loop's problem and it defers on it in turn.
91- return WalkResult::skip ();
96+ body.walk <WalkOrder::PreOrder>([&pending, &body](mlir::Operation *op) {
97+ // Mirror replace_loop_branch_yields: what it walks through it rewrites
98+ // in place, so only what it skips over can be pending.
99+ if (!is_flattened_region_op (op)) { return WalkResult::advance (); }
100+ op->walk ([&pending, &body](mlir::py::BranchYieldOp yield_op) {
101+ if (!yield_op.getKind ().has_value ()) { return WalkResult::advance (); }
102+ if (!binds_to_loop (body, yield_op)) { return WalkResult::advance (); }
103+ pending = true ;
104+ return WalkResult::interrupt ();
105+ });
106+ return pending ? WalkResult::interrupt () : WalkResult::skip ();
92107 });
93108 return pending;
94109 }
0 commit comments