Skip to content

[CIR] Implement support for emitting label address constants - #203644

Merged
andykaylor merged 4 commits into
llvm:mainfrom
andykaylor:cir-const-label-addr
Jun 23, 2026
Merged

[CIR] Implement support for emitting label address constants#203644
andykaylor merged 4 commits into
llvm:mainfrom
andykaylor:cir-const-label-addr

Conversation

@andykaylor

Copy link
Copy Markdown
Contributor

The evalloop.c test in the llvm-test-suite single source tests contains a static array that is initialized with the address of labels within the enclosing function. This wasn't implemented in CIR.

This change adds an implementation. The constant emitter change was trivial. We just needed to create a #cir.block_addr_info attribute. However, using that attribute as an initializer for a global requires some additional handling and special lowering for the initializer.

The goto solver also needed to be updated to consider uses of labels in global initializers.

The test case here was copied over directly from classic codegen. The original test has an additional test case for the difference between two label addresses. Support for that case will be added in a future change.

Assisted-by: Cursor / claude-opus-4.8

@llvmorg-github-actions llvmorg-github-actions Bot added clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project labels Jun 12, 2026
@llvmorg-github-actions

llvmorg-github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown

@llvm/pr-subscribers-clangir

@llvm/pr-subscribers-clang

Author: Andy Kaylor (andykaylor)

Changes

The evalloop.c test in the llvm-test-suite single source tests contains a static array that is initialized with the address of labels within the enclosing function. This wasn't implemented in CIR.

This change adds an implementation. The constant emitter change was trivial. We just needed to create a #cir.block_addr_info attribute. However, using that attribute as an initializer for a global requires some additional handling and special lowering for the initializer.

The goto solver also needed to be updated to consider uses of labels in global initializers.

The test case here was copied over directly from classic codegen. The original test has an additional test case for the difference between two label addresses. Support for that case will be added in a future change.

Assisted-by: Cursor / claude-opus-4.8


Full diff: https://github.com/llvm/llvm-project/pull/203644.diff

8 Files Affected:

  • (modified) clang/include/clang/CIR/Dialect/IR/CIRAttrs.td (+19-7)
  • (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+3)
  • (modified) clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp (+6-2)
  • (modified) clang/lib/CIR/CodeGen/CIRGenStmt.cpp (+2-2)
  • (modified) clang/lib/CIR/Dialect/IR/CIRDialect.cpp (+5-6)
  • (modified) clang/lib/CIR/Dialect/Transforms/GotoSolver.cpp (+28-3)
  • (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp (+33-11)
  • (added) clang/test/CIR/CodeGen/const-label-addr.c (+25)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index 4032d8219fff3..abfa73ca2a611 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -1454,25 +1454,37 @@ def CIR_UnwindAttr : CIR_UnitAttr<"Unwind", "unwind"> {
 // CIR_BlockAddrInfoAttr
 //===----------------------------------------------------------------------===//
 
-def CIR_BlockAddrInfoAttr : CIR_Attr<"BlockAddrInfo", "block_addr_info"> {
-  let summary = "Block Addres attribute";
+def CIR_BlockAddrInfoAttr
+    : CIR_ValueLikeAttr<"BlockAddrInfo", "block_addr_info"> {
+  let summary = "Block address attribute";
   let description = [{
     This attribute is used to represent the address of a basic block
     within a function. It combines the symbol reference to a function
     with the name of a label inside that function.
   }];
-  let parameters = (ins "mlir::FlatSymbolRefAttr":$func,
-                        "mlir::StringAttr":$label);
+  let parameters = (ins
+      AttributeSelfTypeParameter<
+          "", "cir::PointerType",
+          "cir::PointerType::get(cir::VoidType::get($_ctxt))">:$type,
+      "mlir::FlatSymbolRefAttr":$func,
+      "mlir::StringAttr":$label);
 
   let assemblyFormat = "`<` $func `,` $label `>`";
   let builders = [
     AttrBuilder<(ins "llvm::StringRef":$func_name,
-                     "llvm::StringRef":$label_name
-                     ), [{
-      return $_get($_ctxt, mlir::FlatSymbolRefAttr::get($_ctxt, func_name),
+                     "llvm::StringRef":$label_name), [{
+      return $_get($_ctxt,
+                   cir::PointerType::get(cir::VoidType::get($_ctxt)),
+                   mlir::FlatSymbolRefAttr::get($_ctxt, func_name),
                    mlir::StringAttr::get($_ctxt, label_name));
     }]>
   ];
+
+  // Block addresses require deferred basic-block resolution during the
+  // LowerToLLVM pass, so they are not handled by the generic attribute-to-value
+  // lowering.
+  let hasAttrToValueLowering = 0;
+
   let canHaveIllegalCXXABIType = 0;
 }
 
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 557e279d9bc71..2f07f0c783328 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -3134,6 +3134,9 @@ def CIR_GlobalOp : CIR_Op<"global", [
     mlir::SymbolRefAttr getComdatAttr(cir::GlobalOp &op,
                                       mlir::OpBuilder &builder) const;
   }];
+
+  let customLLVMLoweringConstructorDecl =
+    LoweringBuilders<(ins "LLVMBlockAddressInfo &":$blockInfoAddr)>;
 }
 
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
index 5208af44412a3..610a0e780cda5 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
@@ -1234,6 +1234,8 @@ struct ConstantLValue {
       : value(nullptr), hasOffsetApplied(false) {}
   /*implicit*/ ConstantLValue(cir::GlobalViewAttr address)
       : value(address), hasOffsetApplied(false) {}
+  /*implicit*/ ConstantLValue(cir::BlockAddrInfoAttr address)
+      : value(address), hasOffsetApplied(true) {}
 
   ConstantLValue() : value(nullptr), hasOffsetApplied(false) {}
 };
@@ -1514,8 +1516,10 @@ ConstantLValueEmitter::VisitPredefinedExpr(const PredefinedExpr *e) {
 
 ConstantLValue
 ConstantLValueEmitter::VisitAddrLabelExpr(const AddrLabelExpr *e) {
-  cgm.errorNYI(e->getSourceRange(), "ConstantLValueEmitter: addr label expr");
-  return {};
+  auto func = cast<cir::FuncOp>(emitter.cgf->curFn);
+  return cir::BlockAddrInfoAttr::get(cgm.getBuilder().getContext(),
+                                     func.getSymName(),
+                                     e->getLabel()->getName());
 }
 
 ConstantLValue ConstantLValueEmitter::VisitCallExpr(const CallExpr *e) {
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index 922140a93aa5a..101ecfab21b2d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -745,8 +745,8 @@ mlir::LogicalResult CIRGenFunction::emitLabel(const clang::LabelDecl &d) {
   builder.setInsertionPointToEnd(labelBlock);
   auto func = cast<cir::FuncOp>(curFn);
   cgm.mapBlockAddress(cir::BlockAddrInfoAttr::get(builder.getContext(),
-                                                  func.getSymNameAttr(),
-                                                  label.getLabelAttr()),
+                                                  func.getSymName(),
+                                                  label.getLabel()),
                       label);
   //  FIXME: emit debug info for labels, incrementProfileCounter
   assert(!cir::MissingFeatures::incrementProfileCounter());
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 660bed1544aac..8fb737b133efb 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -582,10 +582,10 @@ static LogicalResult checkConstantTypes(mlir::Operation *op, mlir::Type opType,
     return success();
   }
 
-  if (mlir::isa<cir::ConstArrayAttr, cir::ConstVectorAttr,
-                cir::ConstComplexAttr, cir::ConstRecordAttr,
-                cir::GlobalViewAttr, cir::PoisonAttr, cir::TypeInfoAttr,
-                cir::VTableAttr>(attrType))
+  if (mlir::isa<cir::BlockAddrInfoAttr, cir::ConstArrayAttr,
+                cir::ConstVectorAttr, cir::ConstComplexAttr,
+                cir::ConstRecordAttr, cir::GlobalViewAttr, cir::PoisonAttr,
+                cir::TypeInfoAttr, cir::VTableAttr>(attrType))
     return success();
 
   assert(isa<TypedAttr>(attrType) && "What else could we be looking at here?");
@@ -2144,8 +2144,7 @@ static ParseResult parseGlobalOpTypeAndInitialValue(OpAsmParser &parser,
 
       assert(mlir::isa<mlir::TypedAttr>(initialValueAttr) &&
              "Non-typed attrs shouldn't appear here.");
-      auto typedAttr = mlir::cast<mlir::TypedAttr>(initialValueAttr);
-      opTy = typedAttr.getType();
+      opTy = mlir::cast<mlir::TypedAttr>(initialValueAttr).getType();
     }
 
     // Parse destructor, example:
diff --git a/clang/lib/CIR/Dialect/Transforms/GotoSolver.cpp b/clang/lib/CIR/Dialect/Transforms/GotoSolver.cpp
index d590ccce1f540..e2a561cb3a003 100644
--- a/clang/lib/CIR/Dialect/Transforms/GotoSolver.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/GotoSolver.cpp
@@ -9,6 +9,8 @@
 #include "clang/CIR/Dialect/IR/CIRDialect.h"
 #include "clang/CIR/Dialect/Passes.h"
 #include "llvm/ADT/SmallSet.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/ADT/StringSet.h"
 #include "llvm/Support/TimeProfiler.h"
 #include <memory>
 
@@ -27,7 +29,8 @@ struct GotoSolverPass : public impl::GotoSolverBase<GotoSolverPass> {
   void runOnOperation() override;
 };
 
-static void process(cir::FuncOp func) {
+static void process(cir::FuncOp func,
+                    const llvm::StringSet<> &globalBlockAddrLabel) {
   mlir::OpBuilder rewriter(func.getContext());
   llvm::StringMap<Block *> labels;
   llvm::SmallVector<cir::GotoOp, 4> gotos;
@@ -46,7 +49,11 @@ static void process(cir::FuncOp func) {
   for (auto &lab : labels) {
     StringRef labelName = lab.getKey();
     Block *block = lab.getValue();
-    if (!blockAddrLabel.contains(labelName)) {
+    // Keep labels whose address is taken either by a cir.block_address op in
+    // this function or by a block-address attribute used elsewhere (e.g. in a
+    // global initializer).
+    if (!blockAddrLabel.contains(labelName) &&
+        !globalBlockAddrLabel.contains(labelName)) {
       // erase the LabelOp inside the block if safe
       if (auto lab = dyn_cast<cir::LabelOp>(&block->front())) {
         lab.erase();
@@ -65,7 +72,25 @@ static void process(cir::FuncOp func) {
 
 void GotoSolverPass::runOnOperation() {
   llvm::TimeTraceScope scope("Goto Solver");
-  getOperation()->walk(&process);
+
+  // Block addresses can also appear in attributes outside of any function body,
+  // such as global variable initializers. Collect, per target function, the
+  // labels referenced this way so their LabelOps are not erased below.
+  llvm::StringMap<llvm::StringSet<>> globalBlockAddrLabels;
+  getOperation()->walk([&](mlir::Operation *op) {
+    for (const mlir::NamedAttribute &namedAttr : op->getAttrs()) {
+      namedAttr.getValue().walk([&](cir::BlockAddrInfoAttr info) {
+        globalBlockAddrLabels[info.getFunc().getValue()].insert(
+            info.getLabel());
+      });
+    }
+  });
+
+  static const llvm::StringSet<> emptySet;
+  getOperation()->walk([&](cir::FuncOp func) {
+    auto it = globalBlockAddrLabels.find(func.getSymName());
+    process(func, it == globalBlockAddrLabels.end() ? emptySet : it->second);
+  });
 }
 
 } // namespace
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 1579e967885d8..119f6db905c04 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -2396,16 +2396,37 @@ CIRToLLVMGlobalOpLowering::matchAndRewriteRegionInitializedGlobal(
     cir::GlobalOp op, mlir::Attribute init,
     mlir::ConversionPatternRewriter &rewriter) const {
   // TODO: Generalize this handling when more types are needed here.
-  assert((isa<cir::ConstArrayAttr, cir::ConstRecordAttr, cir::ConstVectorAttr,
-              cir::ConstPtrAttr, cir::ConstComplexAttr, cir::GlobalViewAttr,
-              cir::TypeInfoAttr, cir::UndefAttr, cir::PoisonAttr,
-              cir::VTableAttr, cir::ZeroAttr>(init)));
+  assert((isa<cir::BlockAddrInfoAttr, cir::ConstArrayAttr, cir::ConstRecordAttr,
+              cir::ConstVectorAttr, cir::ConstPtrAttr, cir::ConstComplexAttr,
+              cir::GlobalViewAttr, cir::TypeInfoAttr, cir::UndefAttr,
+              cir::PoisonAttr, cir::VTableAttr, cir::ZeroAttr>(init)));
 
   // TODO(cir): once LLVM's dialect has proper equivalent attributes this
   // should be updated. For now, we use a custom op to initialize globals
   // to the appropriate value.
   const mlir::Location loc = op.getLoc();
   setupRegionInitializedLLVMGlobalOp(op, rewriter);
+
+  // A block address initializer is lowered to an llvm.blockaddress op that
+  // references a block tag inside the target function. The matching block tag
+  // may not have been emitted yet, in which case the address is recorded as
+  // unresolved and patched up later in resolveBlockAddressOp.
+  if (auto blockAddrInfo = mlir::dyn_cast<cir::BlockAddrInfoAttr>(init)) {
+    mlir::LLVM::BlockTagOp matchLabel =
+        blockInfoAddr.lookupBlockTag(blockAddrInfo);
+    mlir::LLVM::BlockTagAttr tagAttr =
+        matchLabel ? matchLabel.getTag() : mlir::LLVM::BlockTagAttr{};
+    auto blkAddr = mlir::LLVM::BlockAddressAttr::get(
+        rewriter.getContext(), blockAddrInfo.getFunc(), tagAttr);
+    auto blockAddressOp = mlir::LLVM::BlockAddressOp::create(
+        rewriter, loc, mlir::LLVM::LLVMPointerType::get(rewriter.getContext()),
+        blkAddr);
+    if (!matchLabel)
+      blockInfoAddr.addUnresolvedBlockAddress(blockAddressOp, blockAddrInfo);
+    mlir::LLVM::ReturnOp::create(rewriter, loc, blockAddressOp);
+    return mlir::success();
+  }
+
   CIRAttrToValue valueConverter(op, rewriter, typeConverter);
   mlir::Value value = valueConverter.visit(init);
   mlir::LLVM::ReturnOp::create(rewriter, loc, value);
@@ -2495,11 +2516,11 @@ mlir::LogicalResult CIRToLLVMGlobalOpLowering::matchAndRewrite(
         }
       }
       return matchAndRewriteRegionInitializedGlobal(op, init.value(), rewriter);
-    } else if (mlir::isa<cir::ConstVectorAttr, cir::ConstRecordAttr,
-                         cir::ConstPtrAttr, cir::ConstComplexAttr,
-                         cir::GlobalViewAttr, cir::TypeInfoAttr, cir::UndefAttr,
-                         cir::PoisonAttr, cir::VTableAttr, cir::ZeroAttr>(
-                   init.value())) {
+    } else if (mlir::isa<cir::BlockAddrInfoAttr, cir::ConstVectorAttr,
+                         cir::ConstRecordAttr, cir::ConstPtrAttr,
+                         cir::ConstComplexAttr, cir::GlobalViewAttr,
+                         cir::TypeInfoAttr, cir::UndefAttr, cir::PoisonAttr,
+                         cir::VTableAttr, cir::ZeroAttr>(init.value())) {
       // TODO(cir): once LLVM's dialect has proper equivalent attributes this
       // should be updated. For now, we use a custom op to initialize globals
       // to the appropriate value.
@@ -3656,8 +3677,9 @@ void ConvertCIRToLLVMPass::runOnOperation() {
   /// repeated O(M) module-wide symbol scans for every call site.
   mlir::SymbolTableCollection symbolTables;
   mlir::RewritePatternSet patterns(&getContext());
-  patterns.add<CIRToLLVMBlockAddressOpLowering, CIRToLLVMLabelOpLowering>(
-      converter, patterns.getContext(), dl, blockInfoAddr);
+  patterns.add<CIRToLLVMBlockAddressOpLowering, CIRToLLVMGlobalOpLowering,
+               CIRToLLVMLabelOpLowering>(converter, patterns.getContext(), dl,
+                                         blockInfoAddr);
   patterns.add<CIRToLLVMCallOpLowering, CIRToLLVMTryCallOpLowering>(
       converter, patterns.getContext(), dl, symbolTables);
 
diff --git a/clang/test/CIR/CodeGen/const-label-addr.c b/clang/test/CIR/CodeGen/const-label-addr.c
new file mode 100644
index 0000000000000..8541b23d3d4f6
--- /dev/null
+++ b/clang/test/CIR/CodeGen/const-label-addr.c
@@ -0,0 +1,25 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s
+
+void a(void) {
+A:;
+  static void *a = &&A;
+}
+
+// CIR: cir.global "private" internal dso_local @a.a = #cir.block_addr_info<@a, "A"> : !cir.ptr<!void>
+// CIR: cir.func{{.*}} @a()
+// CIR:   cir.br ^[[A_BLOCK:bb[0-9]+]]
+// CIR: ^[[A_BLOCK]]:
+// CIR:   cir.label "A"
+// CIR:   %[[STATIC_A:.*]] = cir.get_global @a.a : !cir.ptr<!cir.ptr<!void>>
+// CIR:   cir.return
+
+// LLVM: @a.a = internal global ptr blockaddress(@a, %[[A_BLOCK:.*]]), align 8
+// LLVM: define dso_local void @a()
+// LLVM:   br label %[[A_BLOCK]]
+// LLVM: [[A_BLOCK]]:
+// LLVM:   ret void

@adams381

Copy link
Copy Markdown
Contributor

A static array of label addresses crashes the compiler under -emit-llvm — that's the evalloop.c case this PR targets. The added test only covers the scalar form (static void *a = &&A;), so the array path isn't exercised.

Minimal repro (valid C, no computed goto):

void *f(int x) {
  static void *tbl[2] = {&&A, &&B};
A:
  if (x) return tbl[0];
B:
  return tbl[1];
}
clang -cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm t.c
error: unsupported CIR attribute in LLVM constant lowering #cir.block_addr_info<@f, "A">
Segmentation fault
  LLVM::InsertValueOp::create(...)
  CIRAttrToValue::visitCirAttr(cir::ConstArrayAttr)
  CIRToLLVMGlobalOpLowering::matchAndRewriteRegionInitializedGlobal(...)

The top-level GlobalOp lowering special-cases BlockAddrInfoAttr, but when the attribute is nested inside a ConstArrayAttr (or ConstRecordAttr/ConstVectorAttr) the initializer goes through CIRAttrToValue::visit. BlockAddrInfoAttr sets hasAttrToValueLowering = 0, so the visitor's default branch emits the diagnostic and returns a null Value, and the enclosing InsertValueOp::create dereferences it. It's a SIGSEGV, so no-assert builds hit it too. Before this PR the same input failed cleanly via errorNYI in VisitAddrLabelExpr.

The real fix is to give CIRAttrToValue access to the LLVMBlockAddressInfo and emit llvm.blockaddress (with the same unresolved-tag bookkeeping) for nested block addresses. If you'd rather keep this PR scalar-only, gate the aggregate case behind errorNYI in CIRGen so it fails cleanly again. Either way it shouldn't build an op from a null value.

@adams381 adams381 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes for the segfault on aggregate label-address initializers described in the comment above — a static array of label addresses (the evalloop.c case this PR targets) crashes lowering. Happy to clear once it either lowers nested block addresses or errorNYIs the aggregate case instead of building an op from a null value.

@andykaylor

Copy link
Copy Markdown
Contributor Author

A static array of label addresses crashes the compiler under -emit-llvm — that's the evalloop.c case this PR targets. The added test only covers the scalar form (static void *a = &&A;), so the array path isn't exercised.

Oh, you're right! The fix for that is already present in your #201644 PR. These would become even more similar with support for indirect goto added. I think I'd like to finish this with just the aggregate attribute handling, and then one of us can add the indirect goto handling separately.

@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown

✅ With the latest revision this PR passed the C/C++ code formatter.

@adams381

Copy link
Copy Markdown
Contributor

The array case is fixed. A sibling case still crashes, though: a label address inside a struct/record initializer aborts in CXXABILowering, before LowerToLLVM.

struct S { void *a, *b; };
void g(void) {
A:;
B:;
  static struct S s = {&&A, &&B};
}
clang -cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm t.c
clang: llvm/Support/Casting.h: Assertion `isa<X>(Val) && "cast_if_present<Ty>() argument of incompatible type!"' failed.
  lowerInitialValue(...)            // CXXABILowering.cpp
  CIRGlobalOpABILowering::matchAndRewrite(...)

lowerInitialValue's pointer branch only handles GlobalViewAttr and ConstPtrAttr, so a BlockAddrInfoAttr member falls into cast_if_present<cir::ConstPtrAttr> (assert in an asserts build, unchecked bad cast in release). A named record-typed global always lands here because the ABI type converter renames records, so the global's type isn't legal and CIRGlobalOpABILowering rewrites it; an array of void* is identity-converted, stays legal, and skips this pass to be handled later in LowerToLLVM, which is why the array form you fixed doesn't reach it. Both static and non-static struct locals trigger it. Passing BlockAddrInfoAttr through unchanged in the pointer branch (its type is plain ptr<void>, nothing to convert) should cover it.

Separately, not reachable from C today: the new visitCirAttr(BlockAddrInfoAttr) asserts blockInfoAddr is non-null, but the ConstantOp lowering paths (lowerCirAttrAsValue for array/record/vector consts) pass null. Constant aggregates currently get materialized as private globals, so they take the GlobalOp path that sets it -- but if a block-address cir.const ever reaches function-body lowering it'd be a null-deref instead of a diagnostic.

@adams381

Copy link
Copy Markdown
Contributor

Agreed on splitting it that way. My #201644 overlaps with this PR on the constant/aggregate lowering, and it adds a separate #cir.block_address attribute instead of reusing block_addr_info -- no reason for two. Once this lands I'll rebase #201644 on top and drop the duplicate attribute and the lowering you've covered here. That keeps it to the indirect-goto handling: the indirect_br successors plus the GotoSolver label-liveness that feeds the errorNYI you added.

@Andres-Salamanca Andres-Salamanca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for continuing with this! At the time I intentionally left out both the AddrLabelDiff case and the global label address case since we were still in the middle of upstreaming ConstantExprEmitter.

The evalloop.c test in the llvm-test-suite single source tests contains
a static array that is initialized with the address of labels within
the enclosing function. This wasn't implemented in CIR.

This change adds an implementation. The constant emitter change was
trivial. We just needed to create a #cir.block_addr_info attribute.
However, using that attribute as an initializer for a global requires
some additional handling and special lowering for the initializer.

The goto solver also needed to be updated to consider uses of labels
in global initializers.

The test case here was copied over directly from classic codegen. The
original test has an additional test case for the difference between
two label addresses. Support for that case will be added in a future
change.

Assisted-by: Cursor / claude-opus-4.8
@andykaylor
andykaylor force-pushed the cir-const-label-addr branch from 4fab24f to 6a0a226 Compare June 23, 2026 17:52

@adams381 adams381 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@andykaylor
andykaylor merged commit 0628d35 into llvm:main Jun 23, 2026
11 checks passed
@andykaylor
andykaylor deleted the cir-const-label-addr branch June 23, 2026 19:15
beamandala pushed a commit to beamandala/llvm-project that referenced this pull request Jun 24, 2026
…3644)

The evalloop.c test in the llvm-test-suite single source tests contains
a static array that is initialized with the address of labels within the
enclosing function. This wasn't implemented in CIR.

This change adds an implementation. The constant emitter change was
trivial. We just needed to create a #cir.block_addr_info attribute.
However, using that attribute as an initializer for a global requires
some additional handling and special lowering for the initializer.

The goto solver also needed to be updated to consider uses of labels in
global initializers.

The test case here was copied over directly from classic codegen. The
original test has an additional test case for the difference between two
label addresses. Support for that case will be added in a future change.

Assisted-by: Cursor / claude-opus-4.8
adams381 added a commit that referenced this pull request Jun 24, 2026
A computed goto through a constant dispatch table -- the GNU idiom
`static const void *tbl[] = {&&L1, &&L2}; goto *tbl[i];` -- reached
errorNYI("Indirect goto without a goto block") in emitIndirectGotoStmt.
#203644 emits the label-address constant (a value-like
#cir.block_addr_info) into the table, but it takes a label's address in a
constant context without registering the label as address-taken, so no
indirect-goto block is created and the following goto *tbl[i] has nothing
to branch to.

VisitAddrLabelExpr in the constant emitter now records each label via
takeAddressOfConstantLabel, which instantiates the indirect-goto block
and tracks the label.  finishIndirectBranch then adds those labels as
cir.indirect_br successors, alongside the existing op-form labels.  A
label named more than once in a table (`{&&A, &&A, &&B}`) is kept as a
distinct successor each time, matching classic codegen's
`indirectbr ... [label %A, label %A, label %B]`.

The runtime form `void *p = &&L; goto *p;` is unchanged.  New test
goto-address-label-table.c checks CIR, the CIR-lowered LLVM, and classic
OGCG across a few shapes: a plain table, a label named twice, a label
whose address is taken but never reached by a goto, and one reached
through both a constant table and a runtime block-address.
adams381 added a commit that referenced this pull request Jun 25, 2026
A computed goto through a constant dispatch table -- the GNU static
dispatch-table idiom `static const void *tbl[] = {&&L1, &&L2}; goto *tbl[i];`
-- reached `errorNYI("Indirect goto without a goto block")` in
`emitIndirectGotoStmt`. #203644 emits the label-address constant (the
value-like `#cir.block_addr_info`) into the table, but it takes a label's
address in a constant context without registering the label as address-taken,
so no indirect-goto block exists for the following `goto *tbl[i]` to branch to.
(#203644 landed the constant attribute, its lowering, and the GotoSolver label
retention; this is the remaining dispatch wiring.)

`VisitAddrLabelExpr` in the constant emitter now records each label via
`takeAddressOfConstantLabel`, which instantiates the indirect-goto block and
tracks the label; `finishIndirectBranch` then adds those labels as
`cir.indirect_br` successors alongside the existing op-form labels. A label
named more than once in a table is kept as a distinct successor each time, to
match classic codegen.

Registering the label needs a non-const `CIRGenFunction`, so `VisitAddrLabelExpr`
`const_cast`s `emitter.cgf` -- the same thing classic Clang does
(`ConstantEmitter::CGF` is non-const), matching the existing cast at
`CIRGenExprConstant.cpp:929`.

New test `goto-address-label-table.c` covers CIR, the CIR-lowered LLVM, and
classic OGCG: a plain table, a duplicate-label table, a label whose address is
taken but never reached by a goto, and one reached through both a constant table
and a runtime block-address.
maarcosrmz pushed a commit to maarcosrmz/llvm-project that referenced this pull request Jul 1, 2026
A computed goto through a constant dispatch table -- the GNU static
dispatch-table idiom `static const void *tbl[] = {&&L1, &&L2}; goto *tbl[i];`
-- reached `errorNYI("Indirect goto without a goto block")` in
`emitIndirectGotoStmt`. llvm#203644 emits the label-address constant (the
value-like `#cir.block_addr_info`) into the table, but it takes a label's
address in a constant context without registering the label as address-taken,
so no indirect-goto block exists for the following `goto *tbl[i]` to branch to.
(llvm#203644 landed the constant attribute, its lowering, and the GotoSolver label
retention; this is the remaining dispatch wiring.)

`VisitAddrLabelExpr` in the constant emitter now records each label via
`takeAddressOfConstantLabel`, which instantiates the indirect-goto block and
tracks the label; `finishIndirectBranch` then adds those labels as
`cir.indirect_br` successors alongside the existing op-form labels. A label
named more than once in a table is kept as a distinct successor each time, to
match classic codegen.

Registering the label needs a non-const `CIRGenFunction`, so `VisitAddrLabelExpr`
`const_cast`s `emitter.cgf` -- the same thing classic Clang does
(`ConstantEmitter::CGF` is non-const), matching the existing cast at
`CIRGenExprConstant.cpp:929`.

New test `goto-address-label-table.c` covers CIR, the CIR-lowered LLVM, and
classic OGCG: a plain table, a duplicate-label table, a label whose address is
taken but never reached by a goto, and one reached through both a constant table
and a runtime block-address.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants