Author: Andres-Salamanca Date: 2025-08-13T09:52:00-05:00 New Revision: 4b112d23a5451a32fb6c0a06c2c52c7c6720e881
URL: https://github.com/llvm/llvm-project/commit/4b112d23a5451a32fb6c0a06c2c52c7c6720e881 DIFF: https://github.com/llvm/llvm-project/commit/4b112d23a5451a32fb6c0a06c2c52c7c6720e881.diff LOG: [CIR] Upstream LabelOp (#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 Added: clang/test/CIR/CodeGen/label.c clang/test/CIR/IR/invalid-label.cir clang/test/CIR/IR/label.cir Modified: clang/include/clang/CIR/Dialect/IR/CIROps.td clang/lib/CIR/CodeGen/CIRGenFunction.h clang/lib/CIR/CodeGen/CIRGenStmt.cpp clang/lib/CIR/Dialect/IR/CIRDialect.cpp clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp Removed: ################################################################################ diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 51cef239aeda2..2d7c2ec7843dd 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -1060,6 +1060,21 @@ 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 2333ec3209c3b..ddc1edd77010c 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -1181,6 +1181,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 332babdf43772..dffe8b408b6da 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(); + + if (getContext().getLangOpts().EHAsynch && s.isSideEntry()) + getCIRGenModule().errorNYI(s.getSourceRange(), "IsEHa: not implemented."); + + 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 7c8429432b0f9..936247e9d8fbb 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -1784,6 +1784,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..2a515fc4046e8 --- /dev/null +++ b/clang/test/CIR/CodeGen/label.c @@ -0,0 +1,103 @@ +// 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 + +// Note: We are not lowering to LLVM IR via CIR at this stage because that +// process depends on the GotoSolver. + +// 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 + +void after_return() { + return; + label: +} + +// CIR: cir.func no_proto dso_local @after_return +// CIR: cir.br ^bb1 +// CIR: ^bb1: // 2 preds: ^bb0, ^bb2 +// CIR: cir.return +// CIR: ^bb2: // no predecessors +// CIR: cir.label "label" +// CIR: cir.br ^bb1 + +// OGCG: define dso_local void @after_return +// OGCG: br label %label +// OGCG: label: +// OGCG: ret void + + +void after_unreachable() { + __builtin_unreachable(); + label: +} + +// CIR: cir.func no_proto dso_local @after_unreachable +// CIR: cir.unreachable +// CIR: ^bb1: +// CIR: cir.label "label" +// CIR: cir.return + +// OGCG: define dso_local void @after_unreachable +// OGCG: unreachable +// OGCG: label: +// OGCG: ret void diff --git a/clang/test/CIR/IR/invalid-label.cir b/clang/test/CIR/IR/invalid-label.cir new file mode 100644 index 0000000000000..4cb8d01864441 --- /dev/null +++ b/clang/test/CIR/IR/invalid-label.cir @@ -0,0 +1,12 @@ +// RUN: cir-opt %s -verify-diagnostics -split-input-file + +!s32i = !cir.int<s, 32> + +module { + // expected-error@+3 {{must be the first operation in a block}} + cir.func @error(){ + %0 = cir.const #cir.int<0> : !s32i + cir.label "label" + cir.return + } +} 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