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