https://github.com/Andres-Salamanca created 
https://github.com/llvm/llvm-project/pull/152802

This PR introduces the `LabelOp`, which is required for implementing `GotoOp` 
lowering in the future.
Lowering to LLVM IR is **not** included in this patch, since it depends on the 
upcoming `GotoSolver`.

The `GotoSolver` traverses the function body, and if it finds a `LabelOp` 
without a matching `GotoOp`, it erases the label.
This means our implementation differs from the classic codegen approach, where 
labels may be retained even if unused.

Example:
https://godbolt.org/z/37Mvr4MMr

>From 89404b50da442ae168a0687bd4f54ec6b76d488e Mon Sep 17 00:00:00 2001
From: Andres Salamanca <andrealebarbari...@gmail.com>
Date: Fri, 8 Aug 2025 16:24:09 -0500
Subject: [PATCH] [CIR] Upstream LabelOp

---
 clang/include/clang/CIR/Dialect/IR/CIROps.td  | 14 ++++
 clang/lib/CIR/CodeGen/CIRGenFunction.h        |  3 +
 clang/lib/CIR/CodeGen/CIRGenStmt.cpp          | 40 ++++++++++++
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       | 13 ++++
 .../Dialect/Transforms/CIRCanonicalize.cpp    |  4 +-
 clang/test/CIR/CodeGen/label.c                | 65 +++++++++++++++++++
 clang/test/CIR/IR/label.cir                   | 26 ++++++++
 7 files changed, 163 insertions(+), 2 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/label.c
 create mode 100644 clang/test/CIR/IR/label.cir

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 72841a1c08441..bb16a75457ff5 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -1060,6 +1060,20 @@ def CIR_BrOp : CIR_Op<"br",[
   }];
 }
 
+//===----------------------------------------------------------------------===//
+// LabelOp
+//===----------------------------------------------------------------------===//
+
+// The LabelOp has AlwaysSpeculatable trait in order to not to be swept by 
canonicalizer
+def CIR_LabelOp : CIR_Op<"label", [AlwaysSpeculatable]> {
+  let description = [{
+    An identifier which may be referred by cir.goto operation
+  }];
+  let arguments = (ins StrAttr:$label);
+  let assemblyFormat = [{ $label attr-dict }];
+  let hasVerifier = 1;
+}
+
 
//===----------------------------------------------------------------------===//
 // UnaryOp
 
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h 
b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 2e60cfc8211e6..d2e9c163d7f79 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -1125,6 +1125,9 @@ class CIRGenFunction : public CIRGenTypeCache {
 
   mlir::Value emitOpOnBoolExpr(mlir::Location loc, const clang::Expr *cond);
 
+  mlir::LogicalResult emitLabel(const clang::LabelDecl *d);
+  mlir::LogicalResult emitLabelStmt(const clang::LabelStmt &s);
+
   mlir::LogicalResult emitIfStmt(const clang::IfStmt &s);
 
   /// Emit code to compute the specified expression,
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp 
b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index 50642e7ca48d7..49d1800aa15e5 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -256,6 +256,9 @@ mlir::LogicalResult CIRGenFunction::emitSimpleStmt(const 
Stmt *s,
   // NullStmt doesn't need any handling, but we need to say we handled it.
   case Stmt::NullStmtClass:
     break;
+
+  case Stmt::LabelStmtClass:
+    return emitLabelStmt(cast<LabelStmt>(*s));
   case Stmt::CaseStmtClass:
   case Stmt::DefaultStmtClass:
     // If we reached here, we must not handling a switch case in the top level.
@@ -272,6 +275,17 @@ mlir::LogicalResult CIRGenFunction::emitSimpleStmt(const 
Stmt *s,
   return mlir::success();
 }
 
+mlir::LogicalResult CIRGenFunction::emitLabelStmt(const clang::LabelStmt &s) {
+
+  if (emitLabel(s.getDecl()).failed())
+    return mlir::failure();
+
+  // IsEHa: not implemented.
+  assert(!(getContext().getLangOpts().EHAsynch && s.isSideEntry()));
+
+  return emitStmt(s.getSubStmt(), /*useCurrentScope*/ true);
+}
+
 // Add a terminating yield on a body region if no other terminators are used.
 static void terminateBody(CIRGenBuilderTy &builder, mlir::Region &r,
                           mlir::Location loc) {
@@ -429,6 +443,32 @@ CIRGenFunction::emitContinueStmt(const clang::ContinueStmt 
&s) {
   return mlir::success();
 }
 
+mlir::LogicalResult CIRGenFunction::emitLabel(const clang::LabelDecl *d) {
+  // Create a new block to tag with a label and add a branch from
+  // the current one to it. If the block is empty just call attach it
+  // to this label.
+  mlir::Block *currBlock = builder.getBlock();
+  mlir::Block *labelBlock = currBlock;
+
+  if (!currBlock->empty()) {
+    {
+      mlir::OpBuilder::InsertionGuard guard(builder);
+      labelBlock = builder.createBlock(builder.getBlock()->getParent());
+    }
+    builder.create<cir::BrOp>(getLoc(d->getSourceRange()), labelBlock);
+  }
+
+  builder.setInsertionPointToEnd(labelBlock);
+  builder.create<cir::LabelOp>(getLoc(d->getSourceRange()), d->getName());
+  builder.setInsertionPointToEnd(labelBlock);
+
+  //  FIXME: emit debug info for labels, incrementProfileCounter
+  assert(!cir::MissingFeatures::ehstackBranches());
+  assert(!cir::MissingFeatures::incrementProfileCounter());
+  assert(!cir::MissingFeatures::generateDebugInfo());
+  return mlir::success();
+}
+
 mlir::LogicalResult CIRGenFunction::emitBreakStmt(const clang::BreakStmt &s) {
   builder.createBreak(getLoc(s.getBreakLoc()));
 
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index d3fcac1edeb5a..32029f8f9b49b 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -1762,6 +1762,19 @@ LogicalResult cir::ShiftOp::verify() {
   return mlir::success();
 }
 
+//===----------------------------------------------------------------------===//
+// LabelOp Definitions
+//===----------------------------------------------------------------------===//
+
+LogicalResult cir::LabelOp::verify() {
+  mlir::Operation *op = getOperation();
+  mlir::Block *blk = op->getBlock();
+  if (&blk->front() != op)
+    return emitError() << "must be the first operation in a block";
+
+  return mlir::success();
+}
+
 
//===----------------------------------------------------------------------===//
 // UnaryOp
 
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp 
b/clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp
index 2eaa60c631a12..d41ea0af58938 100644
--- a/clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp
@@ -47,8 +47,8 @@ struct RemoveRedundantBranches : public 
OpRewritePattern<BrOp> {
     Block *block = op.getOperation()->getBlock();
     Block *dest = op.getDest();
 
-    assert(!cir::MissingFeatures::labelOp());
-
+    if (isa<cir::LabelOp>(dest->front()))
+      return failure();
     // Single edge between blocks: merge it.
     if (block->getNumSuccessors() == 1 &&
         dest->getSinglePredecessor() == block) {
diff --git a/clang/test/CIR/CodeGen/label.c b/clang/test/CIR/CodeGen/label.c
new file mode 100644
index 0000000000000..08db95a1392cb
--- /dev/null
+++ b/clang/test/CIR/CodeGen/label.c
@@ -0,0 +1,65 @@
+// 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
+
+void label() {
+labelA:
+  return;
+}
+
+// CIR:  cir.func no_proto dso_local @label
+// CIR:    cir.label "labelA"
+// CIR:    cir.return
+
+// OGCG: define dso_local void @label
+// OGCG:   br label %labelA
+// OGCG: labelA:
+// OGCG:   ret void
+
+void multiple_labels() {
+labelB:
+labelC:
+  return;
+}
+
+// CIR:  cir.func no_proto dso_local @multiple_labels
+// CIR:    cir.label "labelB"
+// CIR:    cir.br ^bb1
+// CIR:  ^bb1:  // pred: ^bb0
+// CIR:    cir.label "labelC"
+// CIR:    cir.return
+
+// OGCG: define dso_local void @multiple_labels
+// OGCG:   br label %labelB
+// OGCG: labelB:
+// OGCG:   br label %labelC
+// OGCG: labelC:
+// OGCG:   ret void
+
+void label_in_if(int cond) {
+  if (cond) {
+labelD:
+    cond++;
+  }
+}
+
+// CIR:  cir.func dso_local @label_in_if
+// CIR:      cir.if {{.*}} {
+// CIR:        cir.label "labelD"
+// CIR:        [[LOAD:%.*]] = cir.load align(4) [[COND:%.*]] : 
!cir.ptr<!s32i>, !s32i
+// CIR:        [[INC:%.*]] = cir.unary(inc, %3) nsw : !s32i, !s32i
+// CIR:        cir.store align(4) [[INC]], [[COND]] : !s32i, !cir.ptr<!s32i>
+// CIR:      }
+// CIR:    cir.return
+
+// OGCG: define dso_local void @label_in_if
+// OGCG: if.then:
+// OGCG:   br label %labelD
+// OGCG: labelD:
+// OGCG:   [[LOAD:%.*]] = load i32, ptr [[COND:%.*]], align 4
+// OGCG:   [[INC:%.*]] = add nsw i32 %1, 1
+// OGCG:   store i32 [[INC]], ptr [[COND]], align 4
+// OGCG:   br label %if.end
+// OGCG: if.end:
+// OGCG:   ret void
diff --git a/clang/test/CIR/IR/label.cir b/clang/test/CIR/IR/label.cir
new file mode 100644
index 0000000000000..2211a4e8da331
--- /dev/null
+++ b/clang/test/CIR/IR/label.cir
@@ -0,0 +1,26 @@
+// RUN: cir-opt %s | FileCheck %s
+
+!s32i = !cir.int<s, 32>
+
+module  {
+  cir.func @label() {
+    cir.label "label"
+    cir.return
+  }
+
+  cir.func @label2() {
+    %0 = cir.const #cir.int<0> : !s32i
+    cir.br ^bb1
+  ^bb1:  // pred: ^bb0
+    cir.label "label2"
+    cir.return
+  }
+}
+
+// CHECK:       cir.func @label
+// CHECK-NEXT:    cir.label "label"
+
+// CHECK:       cir.func @label2
+// CHECK:        cir.br ^bb1
+// CHECK-NEXT:  ^bb1:  // pred: ^bb0
+// CHECK-NEXT:    cir.label "label2"

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to