Skip to content

Commit b0d0636

Browse files
authored
[CIR] Upstream support for break and continue statements (llvm#134181)
This adds ClangIR support for break and continue statements in loops. Because only loops are currently implemented upstream in CIR, only breaks in loops are supported here, but this same code will work (with minor changes to the verification and cfg flattening) when switch statements are upstreamed.
1 parent bdff739 commit b0d0636

File tree

8 files changed

+232
-25
lines changed

8 files changed

+232
-25
lines changed

clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,16 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
146146
return create<cir::ForOp>(loc, condBuilder, bodyBuilder, stepBuilder);
147147
}
148148

149+
/// Create a break operation.
150+
cir::BreakOp createBreak(mlir::Location loc) {
151+
return create<cir::BreakOp>(loc);
152+
}
153+
154+
/// Create a continue operation.
155+
cir::ContinueOp createContinue(mlir::Location loc) {
156+
return create<cir::ContinueOp>(loc);
157+
}
158+
149159
mlir::TypedAttr getConstPtrAttr(mlir::Type type, int64_t value) {
150160
auto valueAttr = mlir::IntegerAttr::get(
151161
mlir::IntegerType::get(type.getContext(), 64), value);

clang/include/clang/CIR/Dialect/IR/CIROps.td

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -518,20 +518,20 @@ def ConditionOp : CIR_Op<"condition", [
518518
`cir.bool` operand and, depending on its value, may branch to different
519519
regions:
520520

521-
- When in the `cond` region of a `cir.loop`, it continues the loop
521+
- When in the `cond` region of a loop, it continues the loop
522522
if true, or exits it if false.
523523
- When in the `ready` region of a `cir.await`, it branches to the `resume`
524524
region when true, and to the `suspend` region when false.
525525

526526
Example:
527527

528528
```mlir
529-
cir.loop for(cond : {
530-
cir.condition(%arg0) // Branches to `step` region or exits.
531-
}, step : {
532-
[...]
533-
}) {
534-
[...]
529+
cir.for cond {
530+
cir.condition(%val) // Branches to `step` region or exits.
531+
} body {
532+
cir.yield
533+
} step {
534+
cir.yield
535535
}
536536

537537
cir.await(user, ready : {
@@ -610,6 +610,36 @@ def YieldOp : CIR_Op<"yield", [ReturnLike, Terminator,
610610
];
611611
}
612612

613+
//===----------------------------------------------------------------------===//
614+
// BreakOp
615+
//===----------------------------------------------------------------------===//
616+
617+
def BreakOp : CIR_Op<"break", [Terminator]> {
618+
let summary = "C/C++ `break` statement equivalent";
619+
let description = [{
620+
The `cir.break` operation is used to cease the execution of the current loop
621+
or switch operation and transfer control to the parent operation. It is only
622+
allowed within a breakable operations (loops and switches).
623+
}];
624+
let assemblyFormat = "attr-dict";
625+
let hasVerifier = 1;
626+
}
627+
628+
//===----------------------------------------------------------------------===//
629+
// ContinueOp
630+
//===----------------------------------------------------------------------===//
631+
632+
def ContinueOp : CIR_Op<"continue", [Terminator]> {
633+
let summary = "C/C++ `continue` statement equivalent";
634+
let description = [{
635+
The `cir.continue` operation is used to end execution of the current
636+
iteration of a loop and resume execution beginning at the next iteration.
637+
It is only allowed within loop regions.
638+
}];
639+
let assemblyFormat = "attr-dict";
640+
let hasVerifier = 1;
641+
}
642+
613643
//===----------------------------------------------------------------------===//
614644
// ScopeOp
615645
//===----------------------------------------------------------------------===//

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,10 @@ struct MissingFeatures {
122122

123123
// Future CIR operations
124124
static bool awaitOp() { return false; }
125-
static bool breakOp() { return false; }
126125
static bool callOp() { return false; }
127126
static bool complexCreateOp() { return false; }
128127
static bool complexImagOp() { return false; }
129128
static bool complexRealOp() { return false; }
130-
static bool continueOp() { return false; }
131129
static bool ifOp() { return false; }
132130
static bool labelOp() { return false; }
133131
static bool ptrDiffOp() { return false; }

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,8 @@ class CIRGenFunction : public CIRGenTypeCache {
399399

400400
LValue emitBinaryOperatorLValue(const BinaryOperator *e);
401401

402+
mlir::LogicalResult emitBreakStmt(const clang::BreakStmt &s);
403+
mlir::LogicalResult emitContinueStmt(const clang::ContinueStmt &s);
402404
mlir::LogicalResult emitDoStmt(const clang::DoStmt &s);
403405

404406
/// Emit an expression as an initializer for an object (variable, field, etc.)

clang/lib/CIR/CodeGen/CIRGenStmt.cpp

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s,
5656
return mlir::success();
5757

5858
switch (s->getStmtClass()) {
59+
case Stmt::BreakStmtClass:
60+
case Stmt::CompoundStmtClass:
61+
case Stmt::ContinueStmtClass:
62+
case Stmt::DeclStmtClass:
63+
case Stmt::ReturnStmtClass:
64+
llvm_unreachable("should have emitted these statements as simple");
5965

6066
#define STMT(Type, Base)
6167
#define ABSTRACT_STMT(Op)
@@ -88,13 +94,9 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s,
8894
case Stmt::SEHFinallyStmtClass:
8995
case Stmt::MSDependentExistsStmtClass:
9096
case Stmt::NullStmtClass:
91-
case Stmt::CompoundStmtClass:
92-
case Stmt::DeclStmtClass:
9397
case Stmt::LabelStmtClass:
9498
case Stmt::AttributedStmtClass:
9599
case Stmt::GotoStmtClass:
96-
case Stmt::BreakStmtClass:
97-
case Stmt::ContinueStmtClass:
98100
case Stmt::DefaultStmtClass:
99101
case Stmt::CaseStmtClass:
100102
case Stmt::SEHLeaveStmtClass:
@@ -106,7 +108,6 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s,
106108
case Stmt::CXXTryStmtClass:
107109
case Stmt::CXXForRangeStmtClass:
108110
case Stmt::IndirectGotoStmtClass:
109-
case Stmt::ReturnStmtClass:
110111
case Stmt::GCCAsmStmtClass:
111112
case Stmt::MSAsmStmtClass:
112113
case Stmt::OMPParallelDirectiveClass:
@@ -219,7 +220,6 @@ mlir::LogicalResult CIRGenFunction::emitSimpleStmt(const Stmt *s,
219220
bool useCurrentScope) {
220221
switch (s->getStmtClass()) {
221222
default:
222-
// Only compound and return statements are supported right now.
223223
return mlir::failure();
224224
case Stmt::DeclStmtClass:
225225
return emitDeclStmt(cast<DeclStmt>(*s));
@@ -229,6 +229,10 @@ mlir::LogicalResult CIRGenFunction::emitSimpleStmt(const Stmt *s,
229229
else
230230
emitCompoundStmt(cast<CompoundStmt>(*s));
231231
break;
232+
case Stmt::ContinueStmtClass:
233+
return emitContinueStmt(cast<ContinueStmt>(*s));
234+
case Stmt::BreakStmtClass:
235+
return emitBreakStmt(cast<BreakStmt>(*s));
232236
case Stmt::ReturnStmtClass:
233237
return emitReturnStmt(cast<ReturnStmt>(*s));
234238
}
@@ -316,6 +320,25 @@ mlir::LogicalResult CIRGenFunction::emitReturnStmt(const ReturnStmt &s) {
316320
return mlir::success();
317321
}
318322

323+
mlir::LogicalResult
324+
CIRGenFunction::emitContinueStmt(const clang::ContinueStmt &s) {
325+
builder.createContinue(getLoc(s.getContinueLoc()));
326+
327+
// Insert the new block to continue codegen after the continue statement.
328+
builder.createBlock(builder.getBlock()->getParent());
329+
330+
return mlir::success();
331+
}
332+
333+
mlir::LogicalResult CIRGenFunction::emitBreakStmt(const clang::BreakStmt &s) {
334+
builder.createBreak(getLoc(s.getBreakLoc()));
335+
336+
// Insert the new block to continue codegen after the break statement.
337+
builder.createBlock(builder.getBlock()->getParent());
338+
339+
return mlir::success();
340+
}
341+
319342
mlir::LogicalResult CIRGenFunction::emitForStmt(const ForStmt &s) {
320343
cir::ForOp forOp;
321344

clang/lib/CIR/Dialect/IR/CIRDialect.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,17 @@ void cir::AllocaOp::build(mlir::OpBuilder &odsBuilder,
151151
odsState.addTypes(addr);
152152
}
153153

154+
//===----------------------------------------------------------------------===//
155+
// BreakOp
156+
//===----------------------------------------------------------------------===//
157+
158+
LogicalResult cir::BreakOp::verify() {
159+
assert(!cir::MissingFeatures::switchOp());
160+
if (!getOperation()->getParentOfType<LoopOpInterface>())
161+
return emitOpError("must be within a loop");
162+
return success();
163+
}
164+
154165
//===----------------------------------------------------------------------===//
155166
// ConditionOp
156167
//===----------------------------------------------------------------------===//
@@ -241,6 +252,16 @@ OpFoldResult cir::ConstantOp::fold(FoldAdaptor /*adaptor*/) {
241252
return getValue();
242253
}
243254

255+
//===----------------------------------------------------------------------===//
256+
// ContinueOp
257+
//===----------------------------------------------------------------------===//
258+
259+
LogicalResult cir::ContinueOp::verify() {
260+
if (!getOperation()->getParentOfType<LoopOpInterface>())
261+
return emitOpError("must be within a loop");
262+
return success();
263+
}
264+
244265
//===----------------------------------------------------------------------===//
245266
// CastOp
246267
//===----------------------------------------------------------------------===//

clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -148,23 +148,24 @@ class CIRLoopOpInterfaceFlattening
148148
// driver to customize the order that operations are visited.
149149

150150
// Lower continue statements.
151+
mlir::Block *dest = (step ? step : cond);
151152
op.walkBodySkippingNestedLoops([&](mlir::Operation *op) {
152-
// When continue ops are supported, there will be a check for them here
153-
// and a call to lowerTerminator(). The call to `advance()` handles the
154-
// case where this is not a continue op.
155-
assert(!cir::MissingFeatures::continueOp());
156-
return mlir::WalkResult::advance();
153+
if (!isa<cir::ContinueOp>(op))
154+
return mlir::WalkResult::advance();
155+
156+
lowerTerminator(op, dest, rewriter);
157+
return mlir::WalkResult::skip();
157158
});
158159

159160
// Lower break statements.
160161
assert(!cir::MissingFeatures::switchOp());
161162
walkRegionSkipping<cir::LoopOpInterface>(
162163
op.getBody(), [&](mlir::Operation *op) {
163-
// When break ops are supported, there will be a check for them here
164-
// and a call to lowerTerminator(). The call to `advance()` handles
165-
// the case where this is not a break op.
166-
assert(!cir::MissingFeatures::breakOp());
167-
return mlir::WalkResult::advance();
164+
if (!isa<cir::BreakOp>(op))
165+
return mlir::WalkResult::advance();
166+
167+
lowerTerminator(op, exit, rewriter);
168+
return mlir::WalkResult::skip();
168169
});
169170

170171
// Lower optional body region yield.

clang/test/CIR/CodeGen/loop.cpp

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,3 +265,125 @@ void test_empty_while_true() {
265265
// OGCG: br label %[[WHILE_BODY:.*]]
266266
// OGCG: [[WHILE_BODY]]:
267267
// OGCG: ret void
268+
269+
void unreachable_after_continue() {
270+
for (;;) {
271+
continue;
272+
int x = 1;
273+
}
274+
}
275+
276+
// CIR: cir.func @unreachable_after_continue
277+
// CIR: cir.scope {
278+
// CIR: cir.for : cond {
279+
// CIR: %[[TRUE:.*]] = cir.const #true
280+
// CIR: cir.condition(%[[TRUE]])
281+
// CIR: } body {
282+
// CIR: cir.scope {
283+
// CIR: %[[X:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["x", init] {alignment = 4 : i64}
284+
// CIR: cir.continue
285+
// CIR: ^bb1: // no predecessors
286+
// CIR: %[[ONE:.*]] = cir.const #cir.int<1> : !s32i
287+
// CIR: cir.store %[[ONE]], %[[X]] : !s32i, !cir.ptr<!s32i>
288+
// CIR: cir.yield
289+
// CIR: }
290+
// CIR: cir.yield
291+
// CIR: } step {
292+
// CIR: cir.yield
293+
// CIR: }
294+
// CIR: }
295+
// CIR: cir.return
296+
// CIR: }
297+
298+
// LLVM: define void @unreachable_after_continue()
299+
// LLVM: %[[X:.*]] = alloca i32, i64 1, align 4
300+
// LLVM: br label %[[LABEL1:.*]]
301+
// LLVM: [[LABEL1]]:
302+
// LLVM: br label %[[LABEL2:.*]]
303+
// LLVM: [[LABEL2]]:
304+
// LLVM: br i1 true, label %[[LABEL3:.*]], label %[[LABEL8:.*]]
305+
// LLVM: [[LABEL3]]:
306+
// LLVM: br label %[[LABEL4:.*]]
307+
// LLVM: [[LABEL4]]:
308+
// LLVM: br label %[[LABEL7:.*]]
309+
// LLVM: [[LABEL5:.*]]:
310+
// LLVM-SAME: ; No predecessors!
311+
// LLVM: store i32 1, ptr %[[X]], align 4
312+
// LLVM: br label %[[LABEL6:.*]]
313+
// LLVM: [[LABEL6]]:
314+
// LLVM: br label %[[LABEL7:.*]]
315+
// LLVM: [[LABEL7]]:
316+
// LLVM: br label %[[LABEL2]]
317+
// LLVM: [[LABEL8]]:
318+
// LLVM: br label %[[LABEL9:]]
319+
// LLVM: [[LABEL9]]:
320+
// LLVM: ret void
321+
322+
// OGCG: define{{.*}} void @_Z26unreachable_after_continuev()
323+
// OGCG: entry:
324+
// OGCG: %[[X:.*]] = alloca i32, align 4
325+
// OGCG: br label %[[FOR_COND:.*]]
326+
// OGCG: [[FOR_COND]]:
327+
// OGCG: br label %[[FOR_COND]]
328+
329+
void unreachable_after_break() {
330+
for (;;) {
331+
break;
332+
int x = 1;
333+
}
334+
}
335+
336+
// CIR: cir.func @unreachable_after_break
337+
// CIR: cir.scope {
338+
// CIR: cir.for : cond {
339+
// CIR: %[[TRUE:.*]] = cir.const #true
340+
// CIR: cir.condition(%[[TRUE]])
341+
// CIR: } body {
342+
// CIR: cir.scope {
343+
// CIR: %[[X:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["x", init] {alignment = 4 : i64}
344+
// CIR: cir.break
345+
// CIR: ^bb1: // no predecessors
346+
// CIR: %[[ONE:.*]] = cir.const #cir.int<1> : !s32i
347+
// CIR: cir.store %[[ONE]], %[[X]] : !s32i, !cir.ptr<!s32i>
348+
// CIR: cir.yield
349+
// CIR: }
350+
// CIR: cir.yield
351+
// CIR: } step {
352+
// CIR: cir.yield
353+
// CIR: }
354+
// CIR: }
355+
// CIR: cir.return
356+
// CIR: }
357+
358+
// LLVM: define void @unreachable_after_break()
359+
// LLVM: %[[X:.*]] = alloca i32, i64 1, align 4
360+
// LLVM: br label %[[LABEL1:.*]]
361+
// LLVM: [[LABEL1]]:
362+
// LLVM: br label %[[LABEL2:.*]]
363+
// LLVM: [[LABEL2]]:
364+
// LLVM: br i1 true, label %[[LABEL3:.*]], label %[[LABEL8:.*]]
365+
// LLVM: [[LABEL3]]:
366+
// LLVM: br label %[[LABEL4:.*]]
367+
// LLVM: [[LABEL4]]:
368+
// LLVM: br label %[[LABEL8]]
369+
// LLVM: [[LABEL5:.*]]:
370+
// LLVM-SAME: ; No predecessors!
371+
// LLVM: store i32 1, ptr %[[X]], align 4
372+
// LLVM: br label %[[LABEL6:.*]]
373+
// LLVM: [[LABEL6]]:
374+
// LLVM: br label %[[LABEL7:.*]]
375+
// LLVM: [[LABEL7]]:
376+
// LLVM: br label %[[LABEL2]]
377+
// LLVM: [[LABEL8]]:
378+
// LLVM: br label %[[LABEL9:]]
379+
// LLVM: [[LABEL9]]:
380+
// LLVM: ret void
381+
382+
// OGCG: define{{.*}} void @_Z23unreachable_after_breakv()
383+
// OGCG: entry:
384+
// OGCG: %[[X:.*]] = alloca i32, align 4
385+
// OGCG: br label %[[FOR_COND:.*]]
386+
// OGCG: [[FOR_COND]]:
387+
// OGCG: br label %[[FOR_END:.*]]
388+
// OGCG: [[FOR_END]]:
389+
// OGCG: ret void

0 commit comments

Comments
 (0)