[clang] [CIR] Upstream GotoOp (PR #153701)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Aug 14 15:18:47 PDT 2025
https://github.com/Andres-Salamanca created https://github.com/llvm/llvm-project/pull/153701
This PR upstreams `GotoOp`. It moves some tests from the `goto` test file to the `label` test file, and adds verify logic to `FuncOp` respecting `goto` lowering. The `gotosSolver` will be implemented in a future PR.
>From 6a7e6851c085a6c3c22aea8fea31c005dcb5c7d9 Mon Sep 17 00:00:00 2001
From: Andres Salamanca <andrealebarbaritos at gmail.com>
Date: Thu, 14 Aug 2025 17:14:45 -0500
Subject: [PATCH] [CIR] Upstream GotoOp
---
.../CIR/Dialect/Builder/CIRBaseBuilder.h | 3 +-
clang/include/clang/CIR/Dialect/IR/CIROps.td | 56 +++++
clang/lib/CIR/CodeGen/CIRGenFunction.h | 2 +
clang/lib/CIR/CodeGen/CIRGenStmt.cpp | 21 ++
clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 22 +-
clang/test/CIR/CodeGen/goto.cpp | 203 ++++++++++++++++++
clang/test/CIR/CodeGen/label.c | 36 ++++
clang/test/CIR/IR/invalid-goto.cir | 9 +
8 files changed, 349 insertions(+), 3 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/goto.cpp
create mode 100644 clang/test/CIR/IR/invalid-goto.cir
diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
index 986c8c3d133ac..a62599fab60be 100644
--- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
+++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
@@ -503,8 +503,7 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
static OpBuilder::InsertPoint getBestAllocaInsertPoint(mlir::Block *block) {
auto last =
std::find_if(block->rbegin(), block->rend(), [](mlir::Operation &op) {
- // TODO: Add LabelOp missing feature here
- return mlir::isa<cir::AllocaOp>(&op);
+ return mlir::isa<cir::AllocaOp, cir::LabelOp>(&op);
});
if (last != block->rend())
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 2d7c2ec7843dd..33e0b3ad844b5 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -1060,6 +1060,62 @@ def CIR_BrOp : CIR_Op<"br",[
}];
}
+//===----------------------------------------------------------------------===//
+// GotoOp
+//===----------------------------------------------------------------------===//
+
+def CIR_GotoOp : CIR_Op<"goto", [Terminator]> {
+ let description = [{
+
+ Transfers control to the specified `label`. This requires a corresponding
+ `cir.label` to exist and is used by to represent source level `goto`s
+ that jump across region boundaries. Alternatively, `cir.br` is used to
+ construct goto's that don't violate such boundaries.
+
+ `cir.goto` is completely symbolic (i.e. it "jumps" on a label that isn't
+ yet materialized) and should be taken into account by passes and analysis
+ when deciding if it's safe to make some assumptions about a given region
+ or basic block.
+
+ Example:
+ ```C++
+ int test(int x) {
+ if (x)
+ goto label;
+ {
+ x = 10;
+ label:
+ return x;
+ }
+ }
+ ```
+
+ ```mlir
+ cir.scope { // REGION #1
+ %2 = cir.load %0 : !cir.ptr<!s32i>, !s32i
+ %3 = cir.cast(int_to_bool, %2 : !s32i), !cir.bool
+ cir.if %3 {
+ cir.goto "label"
+ }
+ }
+ cir.scope { // REGION #2
+ %2 = cir.const #cir.int<10> : !s32i
+ cir.store %2, %0 : !s32i, !cir.ptr<!s32i>
+ cir.br ^bb1
+ ^bb1: // pred: ^bb0
+ cir.label "label"
+ %3 = cir.load %0 : !cir.ptr<!s32i>, !s32i
+ cir.store %3, %1 : !s32i, !cir.ptr<!s32i>
+ %4 = cir.load %1 : !cir.ptr<!s32i>, !s32i
+ cir.return %4 : !s32i
+ }
+ cir.unreachable
+ ```
+ }];
+ let arguments = (ins StrAttr:$label);
+ let assemblyFormat = [{ $label attr-dict }];
+}
+
//===----------------------------------------------------------------------===//
// LabelOp
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index ddc1edd77010c..09e2f22b6cee9 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -1097,6 +1097,8 @@ class CIRGenFunction : public CIRGenTypeCache {
mlir::LogicalResult emitFunctionBody(const clang::Stmt *body);
+ mlir::LogicalResult emitGotoStmt(const clang::GotoStmt &s);
+
void emitImplicitAssignmentOperatorBody(FunctionArgList &args);
void emitInitializerForField(clang::FieldDecl *field, LValue lhs,
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index dffe8b408b6da..2f341cc234659 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -250,6 +250,8 @@ mlir::LogicalResult CIRGenFunction::emitSimpleStmt(const Stmt *s,
else
emitCompoundStmt(cast<CompoundStmt>(*s));
break;
+ case Stmt::GotoStmtClass:
+ return emitGotoStmt(cast<GotoStmt>(*s));
case Stmt::ContinueStmtClass:
return emitContinueStmt(cast<ContinueStmt>(*s));
@@ -433,6 +435,25 @@ mlir::LogicalResult CIRGenFunction::emitReturnStmt(const ReturnStmt &s) {
return mlir::success();
}
+mlir::LogicalResult CIRGenFunction::emitGotoStmt(const clang::GotoStmt &s) {
+ // FIXME: LLVM codegen inserts emit stop point here for debug info
+ // sake when the insertion point is available, but doesn't do
+ // anything special when there isn't. We haven't implemented debug
+ // info support just yet, look at this again once we have it.
+ if (!builder.getInsertionBlock())
+ cgm.errorNYI(s.getSourceRange(), "NYI");
+
+ builder.create<cir::GotoOp>(getLoc(s.getSourceRange()),
+ s.getLabel()->getName());
+
+ // A goto marks the end of a block, create a new one for codegen after
+ // emitGotoStmt can resume building in that block.
+ // Insert the new block to continue codegen after goto.
+ builder.createBlock(builder.getBlock()->getParent());
+
+ return mlir::success();
+}
+
mlir::LogicalResult
CIRGenFunction::emitContinueStmt(const clang::ContinueStmt &s) {
builder.createContinue(getLoc(s.getContinueLoc()));
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 936247e9d8fbb..da9efb339c5b5 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -1649,7 +1649,27 @@ void cir::FuncOp::print(OpAsmPrinter &p) {
// TODO(CIR): The properties of functions that require verification haven't
// been implemented yet.
-mlir::LogicalResult cir::FuncOp::verify() { return success(); }
+mlir::LogicalResult cir::FuncOp::verify() {
+
+ std::set<llvm::StringRef> labels;
+ std::set<llvm::StringRef> gotos;
+
+ getOperation()->walk([&](mlir::Operation *op) {
+ if (auto lab = dyn_cast<cir::LabelOp>(op)) {
+ labels.emplace(lab.getLabel());
+ } else if (auto goTo = dyn_cast<cir::GotoOp>(op)) {
+ gotos.emplace(goTo.getLabel());
+ }
+ });
+
+ std::vector<llvm::StringRef> mismatched;
+ std::set_difference(gotos.begin(), gotos.end(), labels.begin(), labels.end(),
+ std::back_inserter(mismatched));
+
+ if (!mismatched.empty())
+ return emitOpError() << "goto/label mismatch";
+ return success();
+}
//===----------------------------------------------------------------------===//
// BinOp
diff --git a/clang/test/CIR/CodeGen/goto.cpp b/clang/test/CIR/CodeGen/goto.cpp
new file mode 100644
index 0000000000000..6d7a8c99b9e97
--- /dev/null
+++ b/clang/test/CIR/CodeGen/goto.cpp
@@ -0,0 +1,203 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s --check-prefix=OGCG
+
+int shouldNotGenBranchRet(int x) {
+ if (x > 5)
+ goto err;
+ return 0;
+err:
+ return -1;
+}
+// CIR: cir.func dso_local @_Z21shouldNotGenBranchReti
+// CIR: cir.if {{.*}} {
+// CIR: cir.goto "err"
+// CIR: }
+// CIR: ^bb1:
+// CIR: [[LOAD:%.*]] = cir.load [[ZERO:%.*]] : !cir.ptr<!s32i>, !s32i
+// CIR: cir.return [[LOAD]] : !s32i
+// CIR: ^bb2:
+// CIR: cir.label "err"
+
+// OGCG: define dso_local noundef i32 @_Z21shouldNotGenBranchReti
+// OGCG: if.then:
+// OGCG: br label %err
+// OGCG: if.end:
+// OGCG: br label %return
+// OGCG: err:
+// OGCG: br label %return
+// OGCG: return:
+
+int shouldGenBranch(int x) {
+ if (x > 5)
+ goto err;
+ x++;
+err:
+ return -1;
+}
+// CIR: cir.func dso_local @_Z15shouldGenBranchi
+// CIR: cir.if {{.*}} {
+// CIR: cir.goto "err"
+// CIR: }
+// CIR: cir.br ^bb1
+// CIR: ^bb1:
+// CIR: cir.label "err"
+
+// OGCG: define dso_local noundef i32 @_Z15shouldGenBranchi
+// OGCG: if.then:
+// OGCG: br label %err
+// OGCG: if.end:
+// OGCG: br label %err
+// OGCG: err:
+// OGCG: ret
+
+void severalLabelsInARow(int a) {
+ int b = a;
+ goto end1;
+ b = b + 1;
+ goto end2;
+end1:
+end2:
+ b = b + 2;
+}
+// CIR: cir.func dso_local @_Z19severalLabelsInARowi
+// CIR: cir.goto "end1"
+// CIR: ^bb[[#BLK1:]]
+// CIR: cir.goto "end2"
+// CIR: ^bb[[#BLK2:]]:
+// CIR: cir.label "end1"
+// CIR: cir.br ^bb[[#BLK3:]]
+// CIR: ^bb[[#BLK3]]:
+// CIR: cir.label "end2"
+
+// OGCG: define dso_local void @_Z19severalLabelsInARowi
+// OGCG: br label %end1
+// OGCG: end1:
+// OGCG: br label %end2
+// OGCG: end2:
+// OGCG: ret
+
+void severalGotosInARow(int a) {
+ int b = a;
+ goto end;
+ goto end;
+end:
+ b = b + 2;
+}
+// CIR: cir.func dso_local @_Z18severalGotosInARowi
+// CIR: cir.goto "end"
+// CIR: ^bb[[#BLK1:]]:
+// CIR: cir.goto "end"
+// CIR: ^bb[[#BLK2:]]:
+// CIR: cir.label "end"
+
+// OGCG: define dso_local void @_Z18severalGotosInARowi(i32 noundef %a) #0 {
+// OGCG: br label %end
+// OGCG: end:
+// OGCG: ret void
+
+extern "C" void action1();
+extern "C" void action2();
+extern "C" void multiple_non_case(int v) {
+ switch (v) {
+ default:
+ action1();
+ l2:
+ action2();
+ break;
+ }
+}
+
+// CIR: cir.func dso_local @multiple_non_case
+// CIR: cir.switch
+// CIR: cir.case(default, []) {
+// CIR: cir.call @action1()
+// CIR: cir.br ^[[BB1:[a-zA-Z0-9]+]]
+// CIR: ^[[BB1]]:
+// CIR: cir.label
+// CIR: cir.call @action2()
+// CIR: cir.break
+
+// OGCG: define dso_local void @multiple_non_case
+// OGCG: sw.default:
+// OGCG: call void @action1()
+// OGCG: br label %l2
+// OGCG: l2:
+// OGCG: call void @action2()
+// OGCG: br label [[BREAK:%.*]]
+
+extern "C" void case_follow_label(int v) {
+ switch (v) {
+ case 1:
+ label:
+ case 2:
+ action1();
+ break;
+ default:
+ action2();
+ goto label;
+ }
+}
+
+// CIR: cir.func dso_local @case_follow_label
+// CIR: cir.switch
+// CIR: cir.case(equal, [#cir.int<1> : !s32i]) {
+// CIR: cir.label "label"
+// CIR: cir.case(equal, [#cir.int<2> : !s32i]) {
+// CIR: cir.call @action1()
+// CIR: cir.break
+// CIR: cir.case(default, []) {
+// CIR: cir.call @action2()
+// CIR: cir.goto "label"
+
+// OGCG: define dso_local void @case_follow_label
+// OGCG: sw.bb:
+// OGCG: br label %label
+// OGCG: label:
+// OGCG: br label %sw.bb1
+// OGCG: sw.bb1:
+// OGCG: call void @action1()
+// OGCG: br label %sw.epilog
+// OGCG: sw.default:
+// OGCG: call void @action2()
+// OGCG: br label %label
+// OGCG: sw.epilog:
+// OGCG: ret void
+
+extern "C" void default_follow_label(int v) {
+ switch (v) {
+ case 1:
+ case 2:
+ action1();
+ break;
+ label:
+ default:
+ action2();
+ goto label;
+ }
+}
+
+// CIR: cir.func dso_local @default_follow_label
+// CIR: cir.switch
+// CIR: cir.case(equal, [#cir.int<1> : !s32i]) {
+// CIR: cir.yield
+// CIR: cir.case(equal, [#cir.int<2> : !s32i]) {
+// CIR: cir.call @action1()
+// CIR: cir.break
+// CIR: cir.label "label"
+// CIR: cir.case(default, []) {
+// CIR: cir.call @action2()
+// CIR: cir.goto "label"
+
+// OGCG: define dso_local void @default_follow_label
+// OGCG: sw.bb:
+// OGCG: call void @action1()
+// OGCG: br label %sw.epilog
+// OGCG: label:
+// OGCG: br label %sw.default
+// OGCG: sw.default:
+// OGCG: call void @action2()
+// OGCG: br label %label
+// OGCG: sw.epilog:
+// OGCG: ret void
diff --git a/clang/test/CIR/CodeGen/label.c b/clang/test/CIR/CodeGen/label.c
index 2a515fc4046e8..797c44475a621 100644
--- a/clang/test/CIR/CodeGen/label.c
+++ b/clang/test/CIR/CodeGen/label.c
@@ -101,3 +101,39 @@ void after_unreachable() {
// OGCG: unreachable
// OGCG: label:
// OGCG: ret void
+
+void labelWithoutMatch() {
+end:
+ return;
+}
+// CIR: cir.func no_proto dso_local @labelWithoutMatch
+// CIR: cir.label "end"
+// CIR: cir.return
+// CIR: }
+
+// OGCG: define dso_local void @labelWithoutMatch
+// OGCG: br label %end
+// OGCG: end:
+// OGCG: ret void
+
+struct S {};
+struct S get();
+void bar(struct S);
+
+void foo() {
+ {
+ label:
+ bar(get());
+ }
+}
+
+// CIR: cir.func no_proto dso_local @foo
+// CIR: cir.scope {
+// CIR: cir.label "label"
+// CIR: %0 = cir.alloca !rec_S, !cir.ptr<!rec_S>, ["agg.tmp0"]
+
+// OGCG: define dso_local void @foo()
+// OGCG: %agg.tmp = alloca %struct.S, align 1
+// OGCG: %undef.agg.tmp = alloca %struct.S, align 1
+// OGCG: br label %label
+// OGCG: label:
diff --git a/clang/test/CIR/IR/invalid-goto.cir b/clang/test/CIR/IR/invalid-goto.cir
new file mode 100644
index 0000000000000..9f58bac92fa3f
--- /dev/null
+++ b/clang/test/CIR/IR/invalid-goto.cir
@@ -0,0 +1,9 @@
+// RUN: cir-opt %s -verify-diagnostics -split-input-file
+
+// expected-error at +1 {{goto/label mismatch}}
+cir.func @bad_goto() -> () {
+ cir.goto "somewhere"
+^bb1:
+ cir.label "label"
+ cir.return
+}
More information about the cfe-commits
mailing list