Skip to content

Commit 54454e4

Browse files
committed
[CIR] Upstream support for break and continue statements
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 dedb632 commit 54454e4

File tree

8 files changed

+218
-12
lines changed

8 files changed

+218
-12
lines changed

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

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

139+
/// Create a break operation.
140+
cir::BreakOp createBreak(mlir::Location loc) {
141+
return create<cir::BreakOp>(loc);
142+
}
143+
144+
/// Create a continue operation.
145+
cir::ContinueOp createContinue(mlir::Location loc) {
146+
return create<cir::ContinueOp>(loc);
147+
}
148+
139149
mlir::TypedAttr getConstPtrAttr(mlir::Type type, int64_t value) {
140150
auto valueAttr = mlir::IntegerAttr::get(
141151
mlir::IntegerType::get(type.getContext(), 64), value);

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,35 @@ def YieldOp : CIR_Op<"yield", [ReturnLike, Terminator,
569569
];
570570
}
571571

572+
//===----------------------------------------------------------------------===//
573+
// BreakOp
574+
//===----------------------------------------------------------------------===//
575+
576+
def BreakOp : CIR_Op<"break", [Terminator]> {
577+
let summary = "C/C++ `break` statement equivalent";
578+
let description = [{
579+
The `cir.break` operation is used to cease the control flow to the parent
580+
operation, exiting its region's control flow. It is only allowed if it is
581+
within a breakable operation (loops and `switch`).
582+
}];
583+
let assemblyFormat = "attr-dict";
584+
let hasVerifier = 1;
585+
}
586+
587+
//===----------------------------------------------------------------------===//
588+
// ContinueOp
589+
//===----------------------------------------------------------------------===//
590+
591+
def ContinueOp : CIR_Op<"continue", [Terminator]> {
592+
let summary = "C/C++ `continue` statement equivalent";
593+
let description = [{
594+
The `cir.continue` operation is used to continue execution to the next
595+
iteration of a loop. It is only allowed within `cir.loop` regions.
596+
}];
597+
let assemblyFormat = "attr-dict";
598+
let hasVerifier = 1;
599+
}
600+
572601
//===----------------------------------------------------------------------===//
573602
// ScopeOp
574603
//===----------------------------------------------------------------------===//

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
@@ -395,6 +395,8 @@ class CIRGenFunction : public CIRGenTypeCache {
395395

396396
LValue emitBinaryOperatorLValue(const BinaryOperator *e);
397397

398+
mlir::LogicalResult emitBreakStmt(const clang::BreakStmt &s);
399+
mlir::LogicalResult emitContinueStmt(const clang::ContinueStmt &s);
398400
mlir::LogicalResult emitDoStmt(const clang::DoStmt &s);
399401

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

clang/lib/CIR/CodeGen/CIRGenStmt.cpp

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