Skip to content

Commit f3cea1e

Browse files
committed
mlir: fix transitive loop-else break/continue binding
1 parent 0037a33 commit f3cea1e

1 file changed

Lines changed: 41 additions & 26 deletions

File tree

‎src/executable/mlir/Conversion/PythonToPythonBytecode/PythonToPythonBytecode.cpp‎

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -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 &region)
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

Comments
 (0)