https://github.com/adams381 updated 
https://github.com/llvm/llvm-project/pull/206176

>From 5fb71b9eebfeeff9456bdd349e675845acbe57fe Mon Sep 17 00:00:00 2001
From: Adam Smith <[email protected]>
Date: Fri, 26 Jun 2026 13:30:28 -0700
Subject: [PATCH 1/3] [CIR] Defer indirect goto resolution to GotoSolver

A `goto *p` inside a nested scope (an if or loop body) failed the region
verifier with "reference to block defined in another region".  Unlike a
regular goto, which CIRGen emits as a symbolic `cir.goto` that GotoSolver
resolves into a `cir.br` after FlattenCFG merges regions, indirect goto was
resolved during CIRGen: emitIndirectGotoStmt built a real indirect-branch
block and branched to it, and finishIndirectBranch wired its successors at the
end of the function -- all while nested scopes were still separate regions.

This mirrors the symbolic-goto design.  A new terminator `cir.goto.indirect`
carries the target address and references no block, so it is valid in any
region.  GotoSolver, which already runs after FlattenCFG, now also rewrites
each `cir.goto.indirect` into a `cir.br` to a shared block holding the
`cir.indirect_br` over every address-taken label; by then every block lives in
one region, so the cross-region branch cannot arise.  GotoSolver already
discovers address-taken labels -- from `cir.block_address` ops and from
block-address attributes in global initializers -- so the CIRGen-side
bookkeeping it replaces (the indirect-goto block, the target list, and the
CIRGenModule label map) is removed.

An indirect goto that leaves a scope needing cleanup (a VLA stack restore or a
destructor on the edge) needs that cleanup run on the branch.  That is not
implemented, so emitIndirectGotoStmt reports it with errorNYI rather than
emitting a branch that skips the cleanup.
---
 clang/include/clang/CIR/Dialect/IR/CIROps.td  | 29 ++++++
 clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp  | 12 +--
 clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp    | 10 +-
 clang/lib/CIR/CodeGen/CIRGenFunction.cpp      | 46 ----------
 clang/lib/CIR/CodeGen/CIRGenFunction.h        | 16 ----
 clang/lib/CIR/CodeGen/CIRGenModule.cpp        | 13 ---
 clang/lib/CIR/CodeGen/CIRGenModule.h          |  6 --
 clang/lib/CIR/CodeGen/CIRGenStmt.cpp          | 30 +++---
 .../lib/CIR/Dialect/Transforms/GotoSolver.cpp | 92 ++++++++++++++-----
 .../CIR/CodeGen/goto-address-label-table.c    | 20 +---
 .../CIR/CodeGen/goto-indirect-cleanup-nyi.c   | 13 +++
 clang/test/CIR/CodeGen/goto-indirect-nested.c | 90 ++++++++++++++++++
 clang/test/CIR/CodeGen/label-values.c         | 52 ++++-------
 clang/test/CIR/IR/goto-indirect.cir           | 15 +++
 clang/test/CIR/Transforms/goto_solver.cir     | 28 ++++++
 15 files changed, 293 insertions(+), 179 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/goto-indirect-cleanup-nyi.c
 create mode 100644 clang/test/CIR/CodeGen/goto-indirect-nested.c
 create mode 100644 clang/test/CIR/IR/goto-indirect.cir

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 7d1c48b994b27..e4ab57c247196 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -1852,6 +1852,35 @@ def CIR_GotoOp : CIR_Op<"goto", [Terminator]> {
   let hasLLVMLowering = false;
 }
 
+//===----------------------------------------------------------------------===//
+// GotoIndirectOp
+//===----------------------------------------------------------------------===//
+
+def CIR_GotoIndirectOp : CIR_Op<"goto.indirect", [Terminator]> {
+  let summary = "Symbolic indirect goto";
+  let description = [{
+    Transfers control to the block whose address is held in `$addr`, the
+    void-pointer value of a `goto *expr;` (the GNU computed-goto extension).
+    Like `cir.goto`, it is symbolic: it references no successor block, so it is
+    valid inside any region even before `FlattenCFG` merges nested scopes.
+    `GotoSolver` runs after `FlattenCFG` and rewrites each `cir.goto.indirect`
+    into a `cir.br` to a shared block holding a `cir.indirect_br` over every
+    address-taken label.
+
+    Example:
+
+    ```mlir
+      %0 = cir.load %p : !cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>
+      cir.goto.indirect %0 : !cir.ptr<!void>
+    ```
+  }];
+
+  let arguments = (ins CIR_VoidPtrType:$addr);
+  let assemblyFormat = [{ $addr `:` qualified(type($addr)) attr-dict }];
+
+  let hasLLVMLowering = false;
+}
+
 
//===----------------------------------------------------------------------===//
 // LabelOp
 
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp 
b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
index eddb00c6fb2c1..1f8979c90b0c6 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
@@ -1522,17 +1522,15 @@ ConstantLValueEmitter::VisitPredefinedExpr(const 
PredefinedExpr *e) {
 ConstantLValue
 ConstantLValueEmitter::VisitAddrLabelExpr(const AddrLabelExpr *e) {
   // A label address taken in a constant context, e.g. a static computed-goto
-  // dispatch table `static const void *tbl[] = {&&L1, &&L2}`.  Besides 
emitting
-  // the constant, register the label as address-taken so a following
-  // `goto *tbl[i]` lists it among the indirect branch's successors.  A label 
is
+  // dispatch table `static const void *tbl[] = {&&L1, &&L2}`.  GotoSolver 
later
+  // collects this block-address attribute (here, from a global initializer) so
+  // the label survives and joins the indirect branch's successors.  A label is
   // always function-local, so cgf is set here.
   assert(emitter.cgf && "label address in a constant requires a function");
   CIRGenFunction &cgf = *const_cast<CIRGenFunction *>(emitter.cgf);
   auto func = cast<cir::FuncOp>(cgf.curFn);
-  cir::BlockAddrInfoAttr info = cir::BlockAddrInfoAttr::get(
-      &cgf.getMLIRContext(), func.getSymName(), e->getLabel()->getName());
-  cgf.indirectGotoTargets.push_back(info);
-  return info;
+  return cir::BlockAddrInfoAttr::get(&cgf.getMLIRContext(), func.getSymName(),
+                                     e->getLabel()->getName());
 }
 
 ConstantLValue ConstantLValueEmitter::VisitCallExpr(const CallExpr *e) {
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp 
b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
index 774cfc8ef7ab6..ff78619e3d7b1 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
@@ -208,11 +208,11 @@ class ScalarExprEmitter : public 
StmtVisitor<ScalarExprEmitter, mlir::Value> {
     auto func = cast<cir::FuncOp>(cgf.curFn);
     cir::BlockAddrInfoAttr blockInfoAttr = cir::BlockAddrInfoAttr::get(
         &cgf.getMLIRContext(), func.getSymName(), e->getLabel()->getName());
-    cir::BlockAddressOp blockAddressOp = cir::BlockAddressOp::create(
-        builder, cgf.getLoc(e->getSourceRange()), 
cgf.convertType(e->getType()),
-        blockInfoAttr);
-    cgf.indirectGotoTargets.push_back(blockInfoAttr);
-    return blockAddressOp;
+    // GotoSolver collects this cir.block_address op after FlattenCFG to keep
+    // the label and wire it as an indirect-branch successor.
+    return cir::BlockAddressOp::create(builder, 
cgf.getLoc(e->getSourceRange()),
+                                       cgf.convertType(e->getType()),
+                                       blockInfoAttr);
   }
 
   mlir::Value VisitIntegerLiteral(const IntegerLiteral *e) {
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp 
b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index d0aedc0689404..adcd0b1d65264 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -604,42 +604,7 @@ void CIRGenFunction::startFunction(GlobalDecl gd, QualType 
returnType,
   }
 }
 
-void CIRGenFunction::finishIndirectBranch() {
-  // The block is created on the first `goto *expr`, so if it is absent the
-  // function has no indirect goto and nothing needs wiring -- a label whose
-  // address is merely taken still emits its address constant on its own.
-  if (!indirectGotoBlock)
-    return;
-
-  // Every label is emitted by now, so each address-taken label resolves to its
-  // LabelOp.  A label may be named more than once (a dispatch table can list 
it
-  // twice), but a block only needs to appear once in the successor list, so
-  // duplicates are dropped.
-  llvm::SmallVector<mlir::Block *> successors;
-  llvm::SmallVector<mlir::ValueRange> rangeOperands;
-  llvm::SmallPtrSet<mlir::Block *, 8> seen;
-  for (cir::BlockAddrInfoAttr info : indirectGotoTargets) {
-    cir::LabelOp labelOp = cgm.lookupBlockAddressInfo(info);
-    assert(labelOp && "expected cir.label to be emitted for block address");
-    mlir::Block *dest = labelOp->getBlock();
-    if (!seen.insert(dest).second)
-      continue;
-    successors.push_back(dest);
-    rangeOperands.push_back(dest->getArguments());
-  }
-
-  mlir::OpBuilder::InsertionGuard guard(builder);
-  builder.setInsertionPointToEnd(indirectGotoBlock);
-  cir::IndirectBrOp::create(builder, builder.getUnknownLoc(),
-                            indirectGotoBlock->getArgument(0), false,
-                            rangeOperands, successors);
-  indirectGotoTargets.clear();
-}
-
 void CIRGenFunction::finishFunction(SourceLocation endLoc) {
-  // Emit the indirect branch with all resolved label destinations.
-  finishIndirectBranch();
-
   // Pop any cleanups that might have been associated with the
   // parameters.  Do this in whatever block we're currently in; it's
   // important to do this before we enter the return block or return
@@ -1547,17 +1512,6 @@ CIRGenFunction::emitArrayLength(const clang::ArrayType 
*origArrayType,
   return numElements;
 }
 
-void CIRGenFunction::instantiateIndirectGotoBlock() {
-  // If we already made the indirect branch for indirect goto, return its 
block.
-  if (indirectGotoBlock)
-    return;
-
-  mlir::OpBuilder::InsertionGuard guard(builder);
-  indirectGotoBlock =
-      builder.createBlock(builder.getBlock()->getParent(), {}, {voidPtrTy},
-                          {builder.getUnknownLoc()});
-}
-
 mlir::Value CIRGenFunction::emitAlignmentAssumption(
     mlir::Value ptrValue, QualType ty, SourceLocation loc,
     SourceLocation assumptionLoc, int64_t alignment, mlir::Value offsetValue) {
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h 
b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 322355fde3957..695f6961ce0e1 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -724,20 +724,6 @@ class CIRGenFunction : public CIRGenTypeCache {
     }
   };
 
-  /// IndirectBranch - The first time an indirect goto is seen we create a 
block
-  /// reserved for the indirect branch.  The actual `cir.indirect_br` is 
emitted
-  /// at the end of the function, once every label destination is known.
-  mlir::Block *indirectGotoBlock = nullptr;
-
-  /// Labels whose address is taken in this function (via `&&label`, as either
-  /// an operation or a constant initializer).  The indirect branch block is
-  /// created lazily on the first `goto *expr`; these targets are resolved to
-  /// their LabelOps and wired as `cir.indirect_br` successors in
-  /// finishIndirectBranch.
-  llvm::SmallVector<cir::BlockAddrInfoAttr> indirectGotoTargets;
-
-  void finishIndirectBranch();
-
   /// Perform the usual unary conversions on the specified expression and
   /// compare the result against zero, returning an Int1Ty value.
   mlir::Value evaluateExprAsBool(const clang::Expr *e);
@@ -1708,8 +1694,6 @@ class CIRGenFunction : public CIRGenTypeCache {
 
   int64_t getAccessedFieldNo(unsigned idx, mlir::ArrayAttr elts);
 
-  void instantiateIndirectGotoBlock();
-
   /// Emit a simple LLVM intrinsic that takes N scalar arguments.  The 
intrinsic
   /// name is used verbatim; any overload mangling (e.g. `.f32`, `.p1`) must be
   /// baked into \p intrinName by the caller.  The result type defaults to the
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp 
b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 69dfadf58aa49..b345393ea26c6 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -3873,19 +3873,6 @@ void CIRGenModule::errorUnsupported(const Decl *d, 
llvm::StringRef type) {
   diags.Report(astContext.getFullLoc(d->getLocation()), diagId) << type;
 }
 
-void CIRGenModule::mapBlockAddress(cir::BlockAddrInfoAttr blockInfo,
-                                   cir::LabelOp label) {
-  [[maybe_unused]] auto result =
-      blockAddressInfoToLabel.try_emplace(blockInfo, label);
-  assert(result.second &&
-         "attempting to map a blockaddress info that is already mapped");
-}
-
-cir::LabelOp
-CIRGenModule::lookupBlockAddressInfo(cir::BlockAddrInfoAttr blockInfo) {
-  return blockAddressInfoToLabel.lookup(blockInfo);
-}
-
 mlir::Operation *
 CIRGenModule::getAddrOfGlobalTemporary(const MaterializeTemporaryExpr *mte,
                                        const Expr *init) {
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h 
b/clang/lib/CIR/CodeGen/CIRGenModule.h
index 144f8c7b9f3e7..39639f7eb438b 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -191,12 +191,6 @@ class CIRGenModule : public CIRGenTypeCache {
   /// the pointers are supposed to be uniqued, should be fine. Revisit this if
   /// it ends up taking too much memory.
   llvm::DenseMap<const clang::FieldDecl *, llvm::StringRef> lambdaFieldToName;
-  /// Map BlockAddrInfoAttr (function name, label name) to the corresponding 
CIR
-  /// LabelOp. This provides the main lookup table used to resolve block
-  /// addresses into their label operations.
-  llvm::DenseMap<cir::BlockAddrInfoAttr, cir::LabelOp> blockAddressInfoToLabel;
-  cir::LabelOp lookupBlockAddressInfo(cir::BlockAddrInfoAttr blockInfo);
-  void mapBlockAddress(cir::BlockAddrInfoAttr blockInfo, cir::LabelOp label);
 
   /// Add a global value to the llvmUsed list.
   void addUsedGlobal(cir::CIRGlobalValueInterface gv);
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp 
b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index d3acac5801e74..fb15e35d4eb70 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -706,13 +706,22 @@ mlir::LogicalResult CIRGenFunction::emitGotoStmt(const 
clang::GotoStmt &s) {
 
 mlir::LogicalResult
 CIRGenFunction::emitIndirectGotoStmt(const IndirectGotoStmt &s) {
+  // An indirect goto that branches out of a scope needing cleanup (a VLA stack
+  // restore or a non-trivial destructor on the edge) must run that cleanup on
+  // the branch.  That is not implemented, so report it rather than emit a
+  // branch that silently skips the cleanup.
+  if (ehStack.stable_begin() != prologueCleanupDepth) {
+    cgm.errorNYI(s.getSourceRange(), "indirect goto across a cleanup scope");
+    return mlir::success();
+  }
+
   mlir::Value val = emitScalarExpr(s.getTarget());
-  // Create the shared indirect-branch block on first use.  Its successors are
-  // every address-taken label, wired in finishIndirectBranch once all labels
-  // are emitted.
-  instantiateIndirectGotoBlock();
-  cir::BrOp::create(builder, getLoc(s.getSourceRange()), indirectGotoBlock,
-                    val);
+  // Emit a symbolic indirect goto.  GotoSolver resolves it into the shared
+  // indirect-branch block after FlattenCFG merges regions, so this stays valid
+  // even when the goto sits inside a nested scope.
+  cir::GotoIndirectOp::create(builder, getLoc(s.getSourceRange()), val);
+
+  // The indirect goto ends the block; open a fresh one so codegen can resume.
   builder.createBlock(builder.getBlock()->getParent());
   return mlir::success();
 }
@@ -743,14 +752,7 @@ mlir::LogicalResult CIRGenFunction::emitLabel(const 
clang::LabelDecl &d) {
   }
 
   builder.setInsertionPointToEnd(labelBlock);
-  cir::LabelOp label =
-      cir::LabelOp::create(builder, getLoc(d.getSourceRange()), d.getName());
-  builder.setInsertionPointToEnd(labelBlock);
-  auto func = cast<cir::FuncOp>(curFn);
-  cgm.mapBlockAddress(cir::BlockAddrInfoAttr::get(builder.getContext(),
-                                                  func.getSymName(),
-                                                  label.getLabel()),
-                      label);
+  cir::LabelOp::create(builder, getLoc(d.getSourceRange()), d.getName());
   //  FIXME: emit debug info for labels, incrementProfileCounter
   assert(!cir::MissingFeatures::incrementProfileCounter());
   assert(!cir::MissingFeatures::generateDebugInfo());
diff --git a/clang/lib/CIR/Dialect/Transforms/GotoSolver.cpp 
b/clang/lib/CIR/Dialect/Transforms/GotoSolver.cpp
index e2a561cb3a003..289754c638ba1 100644
--- a/clang/lib/CIR/Dialect/Transforms/GotoSolver.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/GotoSolver.cpp
@@ -8,7 +8,6 @@
 #include "PassDetail.h"
 #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"
@@ -30,37 +29,53 @@ struct GotoSolverPass : public 
impl::GotoSolverBase<GotoSolverPass> {
 };
 
 static void process(cir::FuncOp func,
-                    const llvm::StringSet<> &globalBlockAddrLabel) {
+                    llvm::ArrayRef<StringRef> globalBlockAddrLabels) {
   mlir::OpBuilder rewriter(func.getContext());
   llvm::StringMap<Block *> labels;
   llvm::SmallVector<cir::GotoOp, 4> gotos;
-  llvm::SmallSet<StringRef, 4> blockAddrLabel;
+  llvm::SmallVector<cir::GotoIndirectOp> indirectGotos;
+  // Labels whose address is taken by a cir.block_address op in this function,
+  // in IR order.
+  llvm::SmallVector<StringRef> opBlockAddrLabels;
 
   func.getBody().walk([&](mlir::Operation *op) {
     if (auto lab = dyn_cast<cir::LabelOp>(op)) {
       labels.try_emplace(lab.getLabel(), lab->getBlock());
     } else if (auto goTo = dyn_cast<cir::GotoOp>(op)) {
       gotos.push_back(goTo);
+    } else if (auto indirect = dyn_cast<cir::GotoIndirectOp>(op)) {
+      indirectGotos.push_back(indirect);
     } else if (auto blockAddr = dyn_cast<cir::BlockAddressOp>(op)) {
-      blockAddrLabel.insert(blockAddr.getBlockAddrInfo().getLabel());
+      opBlockAddrLabels.push_back(blockAddr.getBlockAddrInfo().getLabel());
     }
   });
 
+  // Address-taken labels in a deterministic order: those referenced from
+  // global initializers first (in initializer order), then those taken by a
+  // cir.block_address op (in IR order).  A label may be named more than once 
(a
+  // dispatch table can list it twice); a block only needs to be a successor
+  // once, so keep the first occurrence.
+  llvm::SmallVector<StringRef> addrTakenLabels;
+  llvm::StringSet<> addrTaken;
+  auto noteAddrTaken = [&](StringRef name) {
+    if (addrTaken.insert(name).second)
+      addrTakenLabels.push_back(name);
+  };
+  for (StringRef name : globalBlockAddrLabels)
+    noteAddrTaken(name);
+  for (StringRef name : opBlockAddrLabels)
+    noteAddrTaken(name);
+
+  // Drop LabelOps whose address is never taken; the rest may be 
indirect-branch
+  // successors and must survive.
   for (auto &lab : labels) {
-    StringRef labelName = lab.getKey();
-    Block *block = lab.getValue();
-    // 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();
-      }
+    if (!addrTaken.contains(lab.getKey())) {
+      if (auto labelOp = dyn_cast<cir::LabelOp>(&lab.getValue()->front()))
+        labelOp.erase();
     }
   }
 
+  // Resolve regular symbolic gotos to direct branches.
   for (auto goTo : gotos) {
     mlir::OpBuilder::InsertionGuard guard(rewriter);
     rewriter.setInsertionPoint(goTo);
@@ -68,28 +83,63 @@ static void process(cir::FuncOp func,
     cir::BrOp::create(rewriter, goTo.getLoc(), dest);
     goTo.erase();
   }
+
+  // A label whose address is merely taken still emits its address constant; an
+  // indirect branch is only needed when the function actually branches with a
+  // `goto *expr`.
+  if (indirectGotos.empty())
+    return;
+
+  // Resolve indirect gotos.  FlattenCFG has already merged the nested scopes
+  // into one region, so the shared indirect-branch block and its successors 
all
+  // live in func's body now -- the cross-region branch that broke a nested
+  // `goto *` during CIRGen cannot arise here.
+  mlir::Location loc = indirectGotos.front().getLoc();
+  mlir::Type addrType = indirectGotos.front().getAddr().getType();
+  Block *indirectGotoBlock = rewriter.createBlock(
+      &func.getBody(), func.getBody().end(), {addrType}, {loc});
+
+  llvm::SmallVector<Block *> successors;
+  llvm::SmallVector<mlir::ValueRange> succOperands;
+  for (StringRef name : addrTakenLabels) {
+    Block *dest = labels[name];
+    assert(dest && "address-taken label has no cir.label in this function");
+    successors.push_back(dest);
+    succOperands.push_back(dest->getArguments());
+  }
+  cir::IndirectBrOp::create(rewriter, loc, indirectGotoBlock->getArgument(0),
+                            /*poison=*/false, succOperands, successors);
+
+  for (auto indirect : indirectGotos) {
+    mlir::OpBuilder::InsertionGuard guard(rewriter);
+    rewriter.setInsertionPoint(indirect);
+    cir::BrOp::create(rewriter, indirect.getLoc(), indirectGotoBlock,
+                      indirect.getAddr());
+    indirect.erase();
+  }
 }
 
 void GotoSolverPass::runOnOperation() {
   llvm::TimeTraceScope scope("Goto Solver");
 
   // 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;
+  // such as global variable initializers.  Collect, per target function and in
+  // initializer order, the labels referenced this way so their LabelOps 
survive
+  // and join the indirect branch's successors.
+  llvm::StringMap<llvm::SmallVector<StringRef>> globalBlockAddrLabels;
   getOperation()->walk([&](mlir::Operation *op) {
     for (const mlir::NamedAttribute &namedAttr : op->getAttrs()) {
       namedAttr.getValue().walk([&](cir::BlockAddrInfoAttr info) {
-        globalBlockAddrLabels[info.getFunc().getValue()].insert(
+        globalBlockAddrLabels[info.getFunc().getValue()].push_back(
             info.getLabel());
       });
     }
   });
 
-  static const llvm::StringSet<> emptySet;
+  static const llvm::SmallVector<StringRef> empty;
   getOperation()->walk([&](cir::FuncOp func) {
     auto it = globalBlockAddrLabels.find(func.getSymName());
-    process(func, it == globalBlockAddrLabels.end() ? emptySet : it->second);
+    process(func, it == globalBlockAddrLabels.end() ? empty : it->second);
   });
 }
 
diff --git a/clang/test/CIR/CodeGen/goto-address-label-table.c 
b/clang/test/CIR/CodeGen/goto-address-label-table.c
index 2e273a7c392e9..53fa22df6ce5a 100644
--- a/clang/test/CIR/CodeGen/goto-address-label-table.c
+++ b/clang/test/CIR/CodeGen/goto-address-label-table.c
@@ -26,13 +26,8 @@ int f(int x) {
 
 // CIR-LABEL: cir.func {{.*}} @f
 // CIR:   %[[TBL:.*]] = cir.get_global @f.tbl
-// CIR:   cir.indirect_br %{{.*}} : !cir.ptr<!void>, [
-// CIR-NEXT: ^[[L1BB:.*]],
-// CIR-NEXT: ^[[L2BB:.*]]
-// CIR:   ]
-// CIR: ^[[L1BB]]:
+// CIR:   cir.goto.indirect %{{.*}} : !cir.ptr<!void>
 // CIR:   cir.label "L1"
-// CIR: ^[[L2BB]]:
 // CIR:   cir.label "L2"
 
 // LLVM-LABEL: define dso_local i32 @f(
@@ -50,13 +45,8 @@ int g(int x) {
 }
 
 // CIR-LABEL: cir.func {{.*}} @g
-// CIR:   cir.indirect_br %{{.*}} : !cir.ptr<!void>, [
-// CIR-NEXT: ^[[ABB:.*]],
-// CIR-NEXT: ^[[BBB:.*]]
-// CIR:   ]
-// CIR: ^[[ABB]]:
+// CIR:   cir.goto.indirect %{{.*}} : !cir.ptr<!void>
 // CIR:   cir.label "A"
-// CIR: ^[[BBB]]:
 // CIR:   cir.label "B"
 
 // LLVM-LABEL: define dso_local i32 @g(
@@ -64,7 +54,7 @@ int g(int x) {
 // OGCG:   indirectbr ptr %{{.*}}, [label %[[GA]], label %[[GA]], label 
%[[GB]]]
 
 // h takes a label address but never executes a `goto *`, so CIR emits no
-// indirect branch (classic still emits a dead poisoned indirectbr).
+// indirect goto (classic still emits a dead poisoned indirectbr).
 int h(int x) {
   static const void *tbl[] = {&&L1};
   (void)tbl;
@@ -74,7 +64,7 @@ int h(int x) {
 }
 
 // CIR-LABEL: cir.func {{.*}} @h
-// CIR-NOT: cir.indirect_br
+// CIR-NOT: cir.goto.indirect
 
 // LLVM-LABEL: define dso_local i32 @h(
 // LLVMCIR-NOT: indirectbr
@@ -96,7 +86,7 @@ int m(int sel) {
 
 // CIR-LABEL: cir.func {{.*}} @m
 // CIR:   cir.block_address <@m, "B2">
-// CIR:   cir.indirect_br
+// CIR:   cir.goto.indirect
 // CIR-DAG:   cir.label "A2"
 // CIR-DAG:   cir.label "B2"
 
diff --git a/clang/test/CIR/CodeGen/goto-indirect-cleanup-nyi.c 
b/clang/test/CIR/CodeGen/goto-indirect-cleanup-nyi.c
new file mode 100644
index 0000000000000..e2dc42c37673e
--- /dev/null
+++ b/clang/test/CIR/CodeGen/goto-indirect-cleanup-nyi.c
@@ -0,0 +1,13 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir 
-verify %s
+
+// A `goto *p` that leaves a scope needing cleanup (here a VLA stack restore)
+// must run that cleanup on the branch.  That is not implemented yet, so it is
+// reported rather than lowered to a branch that skips the cleanup.
+int vla(int n) {
+  int a[n];
+  void *p = &&done;
+  // expected-error@+1 {{indirect goto across a cleanup scope}}
+  goto *p;
+done:
+  return a[0];
+}
diff --git a/clang/test/CIR/CodeGen/goto-indirect-nested.c 
b/clang/test/CIR/CodeGen/goto-indirect-nested.c
new file mode 100644
index 0000000000000..79b3c88311fc1
--- /dev/null
+++ b/clang/test/CIR/CodeGen/goto-indirect-nested.c
@@ -0,0 +1,90 @@
+// 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 -fclangir -emit-llvm %s -o 
%t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefix=LLVM
+// 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
+
+// A `goto *p` inside a nested scope, jumping to a top-level label.
+int nested_goto(int x) {
+  void *p = &&done;
+  if (x)
+    goto *p;
+done:
+  return 0;
+}
+
+// CIR-LABEL: cir.func {{.*}} @nested_goto
+// CIR:   %[[P:.*]] = cir.alloca "p"
+// CIR:   cir.block_address <@nested_goto, "done"> : !cir.ptr<!void>
+// CIR:   cir.scope {
+// CIR:     cir.if %{{.*}} {
+// CIR:       %[[T:.*]] = cir.load align(8) %[[P]]
+// CIR:       cir.goto.indirect %[[T]] : !cir.ptr<!void>
+// CIR:     }
+// CIR:   }
+// CIR:   cir.label "done"
+
+// LLVM-LABEL: define dso_local i32 @nested_goto
+// LLVM:   store ptr blockaddress(@nested_goto, %[[DONE:[0-9]+]]), ptr 
%{{.*}}, align 8
+// LLVM:   indirectbr ptr %{{.*}}, [label %[[DONE]]]
+
+// OGCG-LABEL: define dso_local i32 @nested_goto
+// OGCG:   store ptr blockaddress(@nested_goto, %[[DONE:.*]]), ptr %{{.*}}, 
align 8
+// OGCG:   indirectbr ptr %{{.*}}, [label %[[DONE]]]
+
+// A top-level `goto *p` whose target label sits inside a nested scope.
+int nested_label(int x) {
+  void *p;
+  if (x) {
+  inner:
+    return 1;
+  }
+  p = &&inner;
+  goto *p;
+}
+
+// CIR-LABEL: cir.func {{.*}} @nested_label
+// CIR:   cir.scope {
+// CIR:     cir.if %{{.*}} {
+// CIR:       cir.label "inner"
+// CIR:     }
+// CIR:   }
+// CIR:   cir.block_address <@nested_label, "inner"> : !cir.ptr<!void>
+// CIR:   %[[T:.*]] = cir.load align(8)
+// CIR:   cir.goto.indirect %[[T]] : !cir.ptr<!void>
+
+// LLVM-LABEL: define dso_local i32 @nested_label
+// LLVM:   store ptr blockaddress(@nested_label, %[[INNER:[0-9]+]]), ptr 
%{{.*}}, align 8
+// LLVM:   indirectbr ptr %{{.*}}, [label %[[INNER]]]
+
+// OGCG-LABEL: define dso_local i32 @nested_label
+// OGCG:   indirectbr ptr %{{.*}}, [label %[[INNER:.*]]]
+// OGCG:   store ptr blockaddress(@nested_label, %[[INNER]]), ptr %{{.*}}, 
align 8
+
+// A `goto *p` inside a loop body.
+int goto_in_loop(int n) {
+  void *p = &&out;
+  for (int i = 0; i < n; ++i)
+    goto *p;
+out:
+  return n;
+}
+
+// CIR-LABEL: cir.func {{.*}} @goto_in_loop
+// CIR:   cir.block_address <@goto_in_loop, "out"> : !cir.ptr<!void>
+// CIR:   cir.for : cond {
+// CIR:   } body {
+// CIR:     %[[T:.*]] = cir.load align(8)
+// CIR:     cir.goto.indirect %[[T]] : !cir.ptr<!void>
+// CIR:   } step {
+// CIR:   }
+// CIR:   cir.label "out"
+
+// LLVM-LABEL: define dso_local i32 @goto_in_loop
+// LLVM:   store ptr blockaddress(@goto_in_loop, %[[OUT:[0-9]+]]), ptr 
%{{.*}}, align 8
+// LLVM:   indirectbr ptr %{{.*}}, [label %[[OUT]]]
+
+// OGCG-LABEL: define dso_local i32 @goto_in_loop
+// OGCG:   store ptr blockaddress(@goto_in_loop, %[[OUT:.*]]), ptr %{{.*}}, 
align 8
+// OGCG:   indirectbr ptr %{{.*}}, [label %[[OUT]]]
diff --git a/clang/test/CIR/CodeGen/label-values.c 
b/clang/test/CIR/CodeGen/label-values.c
index f2bc5aa5a549f..4187a146b4f69 100644
--- a/clang/test/CIR/CodeGen/label-values.c
+++ b/clang/test/CIR/CodeGen/label-values.c
@@ -16,12 +16,7 @@ void A(void) {
 // CIR:    [[BLOCK:%.*]] = cir.block_address <@A, "LABEL_A"> : !cir.ptr<!void>
 // CIR:    cir.store align(8) [[BLOCK]], [[PTR]] : !cir.ptr<!void>, 
!cir.ptr<!cir.ptr<!void>>
 // CIR:    [[BLOCKADD:%.*]] = cir.load align(8) [[PTR]] : 
!cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>
-// CIR:    cir.br ^bb1([[BLOCKADD]] : !cir.ptr<!void>)
-// CIR:  ^bb1([[PHI:%.*]]: !cir.ptr<!void> {{.*}}):  // pred: ^bb0
-// CIR:    cir.indirect_br [[PHI]] : !cir.ptr<!void>, [
-// CIR:    ^bb2
-// CIR:    ]
-// CIR:  ^bb2:  // pred: ^bb1
+// CIR:    cir.goto.indirect [[BLOCKADD]] : !cir.ptr<!void>
 // CIR:    cir.label "LABEL_A"
 // CIR:    cir.return
 
@@ -30,11 +25,11 @@ void A(void) {
 // LLVM:   store ptr blockaddress(@A, %[[LABEL_A:.*]]), ptr [[PTR]], align 8
 // LLVM:   [[BLOCKADD:%.*]] = load ptr, ptr [[PTR]], align 8
 // LLVM:   br label %[[indirectgoto:.*]]
-// LLVM: [[indirectgoto]]:                                                ; 
preds = %[[ENTRY:.*]]
-// LLVM:  [[PHI:%.*]] = phi ptr [ [[BLOCKADD]], %[[ENTRY]] ]
-// LLVM:  indirectbr ptr [[PHI]], [label %[[LABEL_A]]]
 // LLVM: [[LABEL_A]]:                                                ; preds = 
%[[indirectgoto]]
 // LLVM:   ret void
+// LLVM: [[indirectgoto]]:                                                ; 
preds = %[[ENTRY:.*]]
+// LLVM:   [[PHI:%.*]] = phi ptr [ [[BLOCKADD]], %[[ENTRY]] ]
+// LLVM:   indirectbr ptr [[PHI]], [label %[[LABEL_A]]]
 
 // OGCG: define dso_local void @A()
 // OGCG:   [[PTR:%.*]] = alloca ptr, align 8
@@ -56,16 +51,12 @@ void B(void) {
 // CIR:  cir.func {{.*}} @B()
 // CIR:    [[PTR:%.*]] = cir.alloca "ptr" align(8) init : 
!cir.ptr<!cir.ptr<!void>>
 // CIR:    cir.br ^bb1
-// CIR:   ^bb1: // 2 preds: ^bb0, ^bb2
+// CIR:   ^bb1:
 // CIR:    cir.label "LABEL_B"
 // CIR:    [[BLOCK:%.*]] = cir.block_address <@B, "LABEL_B"> : !cir.ptr<!void>
 // CIR:    cir.store align(8) [[BLOCK]], [[PTR]] : !cir.ptr<!void>, 
!cir.ptr<!cir.ptr<!void>>
 // CIR:    [[BLOCKADD:%.*]] = cir.load align(8) [[PTR]] : 
!cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>
-// CIR:    cir.br ^bb2([[BLOCKADD]] : !cir.ptr<!void>)
-// CIR:  ^bb2([[PHI:%.*]]: !cir.ptr<!void> {{.*}}):  // pred: ^bb1
-// CIR:    cir.indirect_br [[PHI]] : !cir.ptr<!void>, [
-// CIR-NEXT:    ^bb1
-// CIR:    ]
+// CIR:    cir.goto.indirect [[BLOCKADD]] : !cir.ptr<!void>
 
 // LLVM: define dso_local void @B
 // LLVM:   %[[PTR:.*]] = alloca ptr, i64 1, align 8
@@ -104,16 +95,9 @@ void C(int x) {
 // CIR:    [[COND:%.*]] = cir.select if [[CMP:%.*]] then [[BLOCK1]] else 
[[BLOCK2]] : (!cir.bool, !cir.ptr<!void>, !cir.ptr<!void>) -> !cir.ptr<!void>
 // CIR:    cir.store{{.*}} [[COND]], [[PTR:%.*]] : !cir.ptr<!void>, 
!cir.ptr<!cir.ptr<!void>>
 // CIR:    [[BLOCKADD:%.*]] = cir.load{{.*}} [[PTR]] : 
!cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>
-// CIR:    cir.br ^[[INDIRECT_GOTO:.*]]([[BLOCKADD]] : !cir.ptr<!void>)
-// CIR:  ^[[INDIRECT_GOTO]]([[PHI:%.*]]: !cir.ptr<!void> {{.*}}):
-// CIR:    cir.indirect_br [[PHI]] : !cir.ptr<!void>, [
-// CIR-NEXT:    ^[[LABEL_A_BB:.*]],
-// CIR-NEXT:    ^[[LABEL_B_BB:.*]]
-// CIR:    ]
-// CIR:  ^[[LABEL_A_BB]]:
+// CIR:    cir.goto.indirect [[BLOCKADD]] : !cir.ptr<!void>
 // CIR:    cir.label "LABEL_A"
 // CIR:    cir.return
-// CIR:  ^[[LABEL_B_BB]]:
 // CIR:    cir.label "LABEL_B"
 // CIR:    cir.return
 
@@ -122,13 +106,13 @@ void C(int x) {
 // LLVM:   store ptr [[COND]], ptr [[PTR:%.*]], align 8
 // LLVM:   [[BLOCKADD:%.*]] = load ptr, ptr [[PTR]], align 8
 // LLVM:   br label %[[INDIRECT_GOTO:.*]]
-// LLVM: [[INDIRECT_GOTO]]:
-// LLVM:   [[PHI:%.*]] = phi ptr [ [[BLOCKADD]], %[[ENTRY:.*]] ]
-// LLVM:   indirectbr ptr [[PHI]], [label %[[LABEL_A]], label %[[LABEL_B]]]
 // LLVM: [[LABEL_A]]:
 // LLVM:   ret void
 // LLVM: [[LABEL_B]]:
 // LLVM:   ret void
+// LLVM: [[INDIRECT_GOTO]]:
+// LLVM:   [[PHI:%.*]] = phi ptr [ [[BLOCKADD]], %[[ENTRY:.*]] ]
+// LLVM:   indirectbr ptr [[PHI]], [label %[[LABEL_A]], label %[[LABEL_B]]]
 
 // OGCG: define dso_local void @C
 // OGCG:   [[COND:%.*]] = select i1 [[CMP:%.*]], ptr blockaddress(@C, 
%LABEL_A), ptr blockaddress(@C, %LABEL_B)
@@ -162,12 +146,8 @@ void D(void) {
 // CIR:    cir.store align(8) %[[BLK1]], %[[PTR]] : !cir.ptr<!void>, 
!cir.ptr<!cir.ptr<!void>>
 // CIR:    %[[BLK2:.*]] = cir.block_address <@D, "LABEL_A"> : !cir.ptr<!void>
 // CIR:    cir.store align(8) %[[BLK2]], %[[PTR2]] : !cir.ptr<!void>, 
!cir.ptr<!cir.ptr<!void>>
-// CIR:    cir.br ^bb1
-// CIR:  ^bb1([[PHI:%*.]]: !cir.ptr<!void> {{.*}}):  // pred: ^bb0
-// CIR:    cir.indirect_br [[PHI]] : !cir.ptr<!void>, [
-// CIR-NEXT:    ^bb2
-// CIR:    ]
-// CIR:  ^bb2:  // pred: ^bb1
+// CIR:    %[[BLOCKADD:.*]] = cir.load align(8) %[[PTR2]] : 
!cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>
+// CIR:    cir.goto.indirect %[[BLOCKADD]] : !cir.ptr<!void>
 // CIR:    cir.label "LABEL_A"
 // CIR:    %[[BLK3:.*]] = cir.block_address <@D, "LABEL_A"> : !cir.ptr<!void>
 // CIR:    cir.store align(8) %[[BLK3]], %[[PTR3]] : !cir.ptr<!void>, 
!cir.ptr<!cir.ptr<!void>>
@@ -181,12 +161,12 @@ void D(void) {
 // LLVM:   store ptr blockaddress(@D, %[[LABEL_A]]), ptr %[[PTR2]], align 8
 // LLVM:   %[[BLOCKADD:.*]] = load ptr, ptr %[[PTR2]], align 8
 // LLVM:   br label %[[indirectgoto:.*]]
-// LLVM: [[indirectgoto]]:
-// LLVM:   [[PHI:%.*]] = phi ptr [ %[[BLOCKADD]], %[[ENTRY:.*]] ]
-// LLVM:   indirectbr ptr [[PHI]], [label %[[LABEL_A]]]
 // LLVM: [[LABEL_A]]:
 // LLVM:   store ptr blockaddress(@D, %[[LABEL_A]]), ptr %[[PTR3]], align 8
 // LLVM:   ret void
+// LLVM: [[indirectgoto]]:
+// LLVM:   [[PHI:%.*]] = phi ptr [ %[[BLOCKADD]], %[[ENTRY:.*]] ]
+// LLVM:   indirectbr ptr [[PHI]], [label %[[LABEL_A]]]
 
 // OGCG: define dso_local void @D
 // OGCG:   %[[PTR:.*]] = alloca ptr, align 8
@@ -218,7 +198,7 @@ void E(void) {
 }
 
 // CIR-LABEL: cir.func {{.*}} @E()
-// CIR-NOT:   cir.indirect_br
+// CIR-NOT:   cir.goto.indirect
 
 // LLVM-LABEL: define dso_local void @E()
 // LLVM-NOT:   indirectbr
diff --git a/clang/test/CIR/IR/goto-indirect.cir 
b/clang/test/CIR/IR/goto-indirect.cir
new file mode 100644
index 0000000000000..7097ea0bbe5c5
--- /dev/null
+++ b/clang/test/CIR/IR/goto-indirect.cir
@@ -0,0 +1,15 @@
+// RUN: cir-opt %s | cir-opt | FileCheck %s
+
+!void = !cir.void
+
+cir.func @f() {
+  %0 = cir.alloca "p" align(8) init : !cir.ptr<!cir.ptr<!void>>
+  %1 = cir.load align(8) %0 : !cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>
+  cir.goto.indirect %1 : !cir.ptr<!void>
+^bb1:
+  cir.label "l"
+  cir.return
+}
+
+// CHECK: cir.func @f
+// CHECK:   cir.goto.indirect %{{.*}} : !cir.ptr<!void>
diff --git a/clang/test/CIR/Transforms/goto_solver.cir 
b/clang/test/CIR/Transforms/goto_solver.cir
index 31ee10b70f613..c27a84fac4a40 100644
--- a/clang/test/CIR/Transforms/goto_solver.cir
+++ b/clang/test/CIR/Transforms/goto_solver.cir
@@ -60,3 +60,31 @@ cir.func @c() {
 // CHECK:   cir.label "label1"
 // CHECK:   %1 = cir.block_address <@c, "label1"> : !cir.ptr<!void>
 // CHECK:   cir.store align(8) %1, {{.*}} : !cir.ptr<!void>, 
!cir.ptr<!cir.ptr<!void>>
+
+cir.func @d() {
+  %0 = cir.alloca "ptr" align(8) init : !cir.ptr<!cir.ptr<!void>>
+  %1 = cir.block_address <@d, "label1"> : !cir.ptr<!void>
+  cir.store align(8) %1, %0 : !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>
+  %2 = cir.load align(8) %0 : !cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>
+  cir.goto.indirect %2 : !cir.ptr<!void>
+^bb1:
+  cir.label "label1"
+  cir.return
+^bb2:
+  // Not address-taken, so this label is removed.
+  cir.label "label2"
+  cir.return
+}
+
+// The indirect goto is resolved to a branch into a shared block holding a
+// cir.indirect_br over every address-taken label.
+// CHECK: cir.func @d()
+// CHECK:   %[[ADDR:.*]] = cir.load align(8) {{.*}} : 
!cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>
+// CHECK:   cir.br ^[[IGB:bb[0-9]+]](%[[ADDR]] : !cir.ptr<!void>)
+// CHECK: ^[[L1BB:bb[0-9]+]]:
+// CHECK:   cir.label "label1"
+// CHECK-NOT: cir.label "label2"
+// CHECK: ^[[IGB]](%[[DEST:.*]]: !cir.ptr<!void>{{.*}}):
+// CHECK:   cir.indirect_br %[[DEST]] : !cir.ptr<!void>, [
+// CHECK:   ^[[L1BB]]
+// CHECK:   ]

>From 42eb6fc4f5c225cbcb47e56dfa0548602737f30a Mon Sep 17 00:00:00 2001
From: Adam Smith <[email protected]>
Date: Mon, 29 Jun 2026 14:11:43 -0700
Subject: [PATCH 2/3] [CIR] Address review on indirect goto GotoSolver

Rename cir.goto.indirect to cir.indirect_goto so it pairs with
cir.indirect_br the way cir.goto pairs with cir.br.  In GotoSolver, fuse
the locations of all indirect gotos that share the resolved block, and
collect global-initializer block-address labels in a SmallSetVector so a
label named more than once is recorded once.  Add leading_label to
goto-indirect-nested.c, an address-taken label as the first statement,
confirming emitLabel gives the label its own block so it is a valid
non-entry indirect-branch successor.
---
 clang/include/clang/CIR/Dialect/IR/CIROps.td  |  8 ++---
 clang/lib/CIR/CodeGen/CIRGenStmt.cpp          |  2 +-
 .../lib/CIR/Dialect/Transforms/GotoSolver.cpp | 26 ++++++++++----
 .../CIR/CodeGen/goto-address-label-table.c    |  8 ++---
 clang/test/CIR/CodeGen/goto-indirect-nested.c | 34 +++++++++++++++++--
 clang/test/CIR/CodeGen/label-values.c         | 10 +++---
 clang/test/CIR/IR/goto-indirect.cir           |  4 +--
 clang/test/CIR/Transforms/goto_solver.cir     |  2 +-
 8 files changed, 67 insertions(+), 27 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index e4ab57c247196..cf25a3b43a6ef 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -1853,17 +1853,17 @@ def CIR_GotoOp : CIR_Op<"goto", [Terminator]> {
 }
 
 
//===----------------------------------------------------------------------===//
-// GotoIndirectOp
+// IndirectGotoOp
 
//===----------------------------------------------------------------------===//
 
-def CIR_GotoIndirectOp : CIR_Op<"goto.indirect", [Terminator]> {
+def CIR_IndirectGotoOp : CIR_Op<"indirect_goto", [Terminator]> {
   let summary = "Symbolic indirect goto";
   let description = [{
     Transfers control to the block whose address is held in `$addr`, the
     void-pointer value of a `goto *expr;` (the GNU computed-goto extension).
     Like `cir.goto`, it is symbolic: it references no successor block, so it is
     valid inside any region even before `FlattenCFG` merges nested scopes.
-    `GotoSolver` runs after `FlattenCFG` and rewrites each `cir.goto.indirect`
+    `GotoSolver` runs after `FlattenCFG` and rewrites each `cir.indirect_goto`
     into a `cir.br` to a shared block holding a `cir.indirect_br` over every
     address-taken label.
 
@@ -1871,7 +1871,7 @@ def CIR_GotoIndirectOp : CIR_Op<"goto.indirect", 
[Terminator]> {
 
     ```mlir
       %0 = cir.load %p : !cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>
-      cir.goto.indirect %0 : !cir.ptr<!void>
+      cir.indirect_goto %0 : !cir.ptr<!void>
     ```
   }];
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp 
b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index fb15e35d4eb70..97691ebb8cb56 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -719,7 +719,7 @@ CIRGenFunction::emitIndirectGotoStmt(const IndirectGotoStmt 
&s) {
   // Emit a symbolic indirect goto.  GotoSolver resolves it into the shared
   // indirect-branch block after FlattenCFG merges regions, so this stays valid
   // even when the goto sits inside a nested scope.
-  cir::GotoIndirectOp::create(builder, getLoc(s.getSourceRange()), val);
+  cir::IndirectGotoOp::create(builder, getLoc(s.getSourceRange()), val);
 
   // The indirect goto ends the block; open a fresh one so codegen can resume.
   builder.createBlock(builder.getBlock()->getParent());
diff --git a/clang/lib/CIR/Dialect/Transforms/GotoSolver.cpp 
b/clang/lib/CIR/Dialect/Transforms/GotoSolver.cpp
index 289754c638ba1..f2d1677ce0dc2 100644
--- a/clang/lib/CIR/Dialect/Transforms/GotoSolver.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/GotoSolver.cpp
@@ -8,6 +8,7 @@
 #include "PassDetail.h"
 #include "clang/CIR/Dialect/IR/CIRDialect.h"
 #include "clang/CIR/Dialect/Passes.h"
+#include "llvm/ADT/SetVector.h"
 #include "llvm/ADT/StringMap.h"
 #include "llvm/ADT/StringSet.h"
 #include "llvm/Support/TimeProfiler.h"
@@ -33,7 +34,7 @@ static void process(cir::FuncOp func,
   mlir::OpBuilder rewriter(func.getContext());
   llvm::StringMap<Block *> labels;
   llvm::SmallVector<cir::GotoOp, 4> gotos;
-  llvm::SmallVector<cir::GotoIndirectOp> indirectGotos;
+  llvm::SmallVector<cir::IndirectGotoOp> indirectGotos;
   // Labels whose address is taken by a cir.block_address op in this function,
   // in IR order.
   llvm::SmallVector<StringRef> opBlockAddrLabels;
@@ -43,7 +44,7 @@ static void process(cir::FuncOp func,
       labels.try_emplace(lab.getLabel(), lab->getBlock());
     } else if (auto goTo = dyn_cast<cir::GotoOp>(op)) {
       gotos.push_back(goTo);
-    } else if (auto indirect = dyn_cast<cir::GotoIndirectOp>(op)) {
+    } else if (auto indirect = dyn_cast<cir::IndirectGotoOp>(op)) {
       indirectGotos.push_back(indirect);
     } else if (auto blockAddr = dyn_cast<cir::BlockAddressOp>(op)) {
       opBlockAddrLabels.push_back(blockAddr.getBlockAddrInfo().getLabel());
@@ -94,7 +95,14 @@ static void process(cir::FuncOp func,
   // into one region, so the shared indirect-branch block and its successors 
all
   // live in func's body now -- the cross-region branch that broke a nested
   // `goto *` during CIRGen cannot arise here.
-  mlir::Location loc = indirectGotos.front().getLoc();
+  // The shared block represents every `goto *expr` that funnels into it, so
+  // fuse their locations when there is more than one.
+  llvm::SmallVector<mlir::Location> gotoLocs;
+  for (cir::IndirectGotoOp indirect : indirectGotos)
+    gotoLocs.push_back(indirect.getLoc());
+  mlir::Location loc = gotoLocs.size() == 1
+                           ? gotoLocs.front()
+                           : mlir::FusedLoc::get(func.getContext(), gotoLocs);
   mlir::Type addrType = indirectGotos.front().getAddr().getType();
   Block *indirectGotoBlock = rewriter.createBlock(
       &func.getBody(), func.getBody().end(), {addrType}, {loc});
@@ -125,12 +133,14 @@ void GotoSolverPass::runOnOperation() {
   // Block addresses can also appear in attributes outside of any function 
body,
   // such as global variable initializers.  Collect, per target function and in
   // initializer order, the labels referenced this way so their LabelOps 
survive
-  // and join the indirect branch's successors.
-  llvm::StringMap<llvm::SmallVector<StringRef>> globalBlockAddrLabels;
+  // and join the indirect branch's successors.  A SetVector keeps the first
+  // occurrence in order: a label named more than once across initializers 
needs
+  // to be a successor only once.
+  llvm::StringMap<llvm::SmallSetVector<StringRef, 4>> globalBlockAddrLabels;
   getOperation()->walk([&](mlir::Operation *op) {
     for (const mlir::NamedAttribute &namedAttr : op->getAttrs()) {
       namedAttr.getValue().walk([&](cir::BlockAddrInfoAttr info) {
-        globalBlockAddrLabels[info.getFunc().getValue()].push_back(
+        globalBlockAddrLabels[info.getFunc().getValue()].insert(
             info.getLabel());
       });
     }
@@ -139,7 +149,9 @@ void GotoSolverPass::runOnOperation() {
   static const llvm::SmallVector<StringRef> empty;
   getOperation()->walk([&](cir::FuncOp func) {
     auto it = globalBlockAddrLabels.find(func.getSymName());
-    process(func, it == globalBlockAddrLabels.end() ? empty : it->second);
+    process(func, it == globalBlockAddrLabels.end()
+                      ? llvm::ArrayRef<StringRef>(empty)
+                      : it->second.getArrayRef());
   });
 }
 
diff --git a/clang/test/CIR/CodeGen/goto-address-label-table.c 
b/clang/test/CIR/CodeGen/goto-address-label-table.c
index 53fa22df6ce5a..9265a783483c7 100644
--- a/clang/test/CIR/CodeGen/goto-address-label-table.c
+++ b/clang/test/CIR/CodeGen/goto-address-label-table.c
@@ -26,7 +26,7 @@ int f(int x) {
 
 // CIR-LABEL: cir.func {{.*}} @f
 // CIR:   %[[TBL:.*]] = cir.get_global @f.tbl
-// CIR:   cir.goto.indirect %{{.*}} : !cir.ptr<!void>
+// CIR:   cir.indirect_goto %{{.*}} : !cir.ptr<!void>
 // CIR:   cir.label "L1"
 // CIR:   cir.label "L2"
 
@@ -45,7 +45,7 @@ int g(int x) {
 }
 
 // CIR-LABEL: cir.func {{.*}} @g
-// CIR:   cir.goto.indirect %{{.*}} : !cir.ptr<!void>
+// CIR:   cir.indirect_goto %{{.*}} : !cir.ptr<!void>
 // CIR:   cir.label "A"
 // CIR:   cir.label "B"
 
@@ -64,7 +64,7 @@ int h(int x) {
 }
 
 // CIR-LABEL: cir.func {{.*}} @h
-// CIR-NOT: cir.goto.indirect
+// CIR-NOT: cir.indirect_goto
 
 // LLVM-LABEL: define dso_local i32 @h(
 // LLVMCIR-NOT: indirectbr
@@ -86,7 +86,7 @@ int m(int sel) {
 
 // CIR-LABEL: cir.func {{.*}} @m
 // CIR:   cir.block_address <@m, "B2">
-// CIR:   cir.goto.indirect
+// CIR:   cir.indirect_goto
 // CIR-DAG:   cir.label "A2"
 // CIR-DAG:   cir.label "B2"
 
diff --git a/clang/test/CIR/CodeGen/goto-indirect-nested.c 
b/clang/test/CIR/CodeGen/goto-indirect-nested.c
index 79b3c88311fc1..81d2d327bfa54 100644
--- a/clang/test/CIR/CodeGen/goto-indirect-nested.c
+++ b/clang/test/CIR/CodeGen/goto-indirect-nested.c
@@ -20,7 +20,7 @@ int nested_goto(int x) {
 // CIR:   cir.scope {
 // CIR:     cir.if %{{.*}} {
 // CIR:       %[[T:.*]] = cir.load align(8) %[[P]]
-// CIR:       cir.goto.indirect %[[T]] : !cir.ptr<!void>
+// CIR:       cir.indirect_goto %[[T]] : !cir.ptr<!void>
 // CIR:     }
 // CIR:   }
 // CIR:   cir.label "done"
@@ -52,7 +52,7 @@ int nested_label(int x) {
 // CIR:   }
 // CIR:   cir.block_address <@nested_label, "inner"> : !cir.ptr<!void>
 // CIR:   %[[T:.*]] = cir.load align(8)
-// CIR:   cir.goto.indirect %[[T]] : !cir.ptr<!void>
+// CIR:   cir.indirect_goto %[[T]] : !cir.ptr<!void>
 
 // LLVM-LABEL: define dso_local i32 @nested_label
 // LLVM:   store ptr blockaddress(@nested_label, %[[INNER:[0-9]+]]), ptr 
%{{.*}}, align 8
@@ -76,7 +76,7 @@ int goto_in_loop(int n) {
 // CIR:   cir.for : cond {
 // CIR:   } body {
 // CIR:     %[[T:.*]] = cir.load align(8)
-// CIR:     cir.goto.indirect %[[T]] : !cir.ptr<!void>
+// CIR:     cir.indirect_goto %[[T]] : !cir.ptr<!void>
 // CIR:   } step {
 // CIR:   }
 // CIR:   cir.label "out"
@@ -88,3 +88,31 @@ int goto_in_loop(int n) {
 // OGCG-LABEL: define dso_local i32 @goto_in_loop
 // OGCG:   store ptr blockaddress(@goto_in_loop, %[[OUT:.*]]), ptr %{{.*}}, 
align 8
 // OGCG:   indirectbr ptr %{{.*}}, [label %[[OUT]]]
+
+// An address-taken label as the first statement of the function.
+int leading_label(int x) {
+first:;
+  void *p = &&first;
+  if (x)
+    goto *p;
+  return 0;
+}
+
+// CIR-LABEL: cir.func {{.*}} @leading_label
+// CIR:   cir.br ^bb1
+// CIR: ^bb1:
+// CIR:   cir.label "first"
+// CIR:   cir.block_address <@leading_label, "first"> : !cir.ptr<!void>
+// CIR:   cir.indirect_goto %{{.*}} : !cir.ptr<!void>
+
+// LLVM-LABEL: define dso_local i32 @leading_label
+// LLVM:   br label %[[FIRST:[0-9]+]]
+// LLVM: [[FIRST]]:
+// LLVM:   store ptr blockaddress(@leading_label, %[[FIRST]]), ptr %{{.*}}, 
align 8
+// LLVM:   indirectbr ptr %{{.*}}, [label %[[FIRST]]]
+
+// OGCG-LABEL: define dso_local i32 @leading_label
+// OGCG:   br label %[[FIRST:.*]]
+// OGCG: [[FIRST]]:
+// OGCG:   store ptr blockaddress(@leading_label, %[[FIRST]]), ptr %{{.*}}, 
align 8
+// OGCG:   indirectbr ptr %{{.*}}, [label %[[FIRST]]]
diff --git a/clang/test/CIR/CodeGen/label-values.c 
b/clang/test/CIR/CodeGen/label-values.c
index 4187a146b4f69..61559ba0b7858 100644
--- a/clang/test/CIR/CodeGen/label-values.c
+++ b/clang/test/CIR/CodeGen/label-values.c
@@ -16,7 +16,7 @@ void A(void) {
 // CIR:    [[BLOCK:%.*]] = cir.block_address <@A, "LABEL_A"> : !cir.ptr<!void>
 // CIR:    cir.store align(8) [[BLOCK]], [[PTR]] : !cir.ptr<!void>, 
!cir.ptr<!cir.ptr<!void>>
 // CIR:    [[BLOCKADD:%.*]] = cir.load align(8) [[PTR]] : 
!cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>
-// CIR:    cir.goto.indirect [[BLOCKADD]] : !cir.ptr<!void>
+// CIR:    cir.indirect_goto [[BLOCKADD]] : !cir.ptr<!void>
 // CIR:    cir.label "LABEL_A"
 // CIR:    cir.return
 
@@ -56,7 +56,7 @@ void B(void) {
 // CIR:    [[BLOCK:%.*]] = cir.block_address <@B, "LABEL_B"> : !cir.ptr<!void>
 // CIR:    cir.store align(8) [[BLOCK]], [[PTR]] : !cir.ptr<!void>, 
!cir.ptr<!cir.ptr<!void>>
 // CIR:    [[BLOCKADD:%.*]] = cir.load align(8) [[PTR]] : 
!cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>
-// CIR:    cir.goto.indirect [[BLOCKADD]] : !cir.ptr<!void>
+// CIR:    cir.indirect_goto [[BLOCKADD]] : !cir.ptr<!void>
 
 // LLVM: define dso_local void @B
 // LLVM:   %[[PTR:.*]] = alloca ptr, i64 1, align 8
@@ -95,7 +95,7 @@ void C(int x) {
 // CIR:    [[COND:%.*]] = cir.select if [[CMP:%.*]] then [[BLOCK1]] else 
[[BLOCK2]] : (!cir.bool, !cir.ptr<!void>, !cir.ptr<!void>) -> !cir.ptr<!void>
 // CIR:    cir.store{{.*}} [[COND]], [[PTR:%.*]] : !cir.ptr<!void>, 
!cir.ptr<!cir.ptr<!void>>
 // CIR:    [[BLOCKADD:%.*]] = cir.load{{.*}} [[PTR]] : 
!cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>
-// CIR:    cir.goto.indirect [[BLOCKADD]] : !cir.ptr<!void>
+// CIR:    cir.indirect_goto [[BLOCKADD]] : !cir.ptr<!void>
 // CIR:    cir.label "LABEL_A"
 // CIR:    cir.return
 // CIR:    cir.label "LABEL_B"
@@ -147,7 +147,7 @@ void D(void) {
 // CIR:    %[[BLK2:.*]] = cir.block_address <@D, "LABEL_A"> : !cir.ptr<!void>
 // CIR:    cir.store align(8) %[[BLK2]], %[[PTR2]] : !cir.ptr<!void>, 
!cir.ptr<!cir.ptr<!void>>
 // CIR:    %[[BLOCKADD:.*]] = cir.load align(8) %[[PTR2]] : 
!cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>
-// CIR:    cir.goto.indirect %[[BLOCKADD]] : !cir.ptr<!void>
+// CIR:    cir.indirect_goto %[[BLOCKADD]] : !cir.ptr<!void>
 // CIR:    cir.label "LABEL_A"
 // CIR:    %[[BLK3:.*]] = cir.block_address <@D, "LABEL_A"> : !cir.ptr<!void>
 // CIR:    cir.store align(8) %[[BLK3]], %[[PTR3]] : !cir.ptr<!void>, 
!cir.ptr<!cir.ptr<!void>>
@@ -198,7 +198,7 @@ void E(void) {
 }
 
 // CIR-LABEL: cir.func {{.*}} @E()
-// CIR-NOT:   cir.goto.indirect
+// CIR-NOT:   cir.indirect_goto
 
 // LLVM-LABEL: define dso_local void @E()
 // LLVM-NOT:   indirectbr
diff --git a/clang/test/CIR/IR/goto-indirect.cir 
b/clang/test/CIR/IR/goto-indirect.cir
index 7097ea0bbe5c5..a52c13bea28cd 100644
--- a/clang/test/CIR/IR/goto-indirect.cir
+++ b/clang/test/CIR/IR/goto-indirect.cir
@@ -5,11 +5,11 @@
 cir.func @f() {
   %0 = cir.alloca "p" align(8) init : !cir.ptr<!cir.ptr<!void>>
   %1 = cir.load align(8) %0 : !cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>
-  cir.goto.indirect %1 : !cir.ptr<!void>
+  cir.indirect_goto %1 : !cir.ptr<!void>
 ^bb1:
   cir.label "l"
   cir.return
 }
 
 // CHECK: cir.func @f
-// CHECK:   cir.goto.indirect %{{.*}} : !cir.ptr<!void>
+// CHECK:   cir.indirect_goto %{{.*}} : !cir.ptr<!void>
diff --git a/clang/test/CIR/Transforms/goto_solver.cir 
b/clang/test/CIR/Transforms/goto_solver.cir
index c27a84fac4a40..26cd9827ae318 100644
--- a/clang/test/CIR/Transforms/goto_solver.cir
+++ b/clang/test/CIR/Transforms/goto_solver.cir
@@ -66,7 +66,7 @@ cir.func @d() {
   %1 = cir.block_address <@d, "label1"> : !cir.ptr<!void>
   cir.store align(8) %1, %0 : !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>
   %2 = cir.load align(8) %0 : !cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>
-  cir.goto.indirect %2 : !cir.ptr<!void>
+  cir.indirect_goto %2 : !cir.ptr<!void>
 ^bb1:
   cir.label "label1"
   cir.return

>From 61d75aac5d6a28faf9af5d549b7217cc14e663b0 Mon Sep 17 00:00:00 2001
From: Adam Smith <[email protected]>
Date: Mon, 20 Jul 2026 13:52:46 -0700
Subject: [PATCH 3/3] [CIR] Simplify GotoSolver label set and location fusing

Consolidate the address-taken label tracking in GotoSolver into a single
SmallSetVector seeded from the global block-address labels, replacing the
parallel vector plus StringSet and the merge loops.  Build the shared
indirect-branch block's location with FusedLoc::get unconditionally,
dropping the single-goto special case.

Reword the indirect-goto NYI diagnostic to "indirect goto with active
cleanup": the guard fires whenever a cleanup is pending, which is not the
same as proving the dynamic destination leaves the scope.

Combine the byte-compatible LLVM and OGCG checks in goto-indirect-nested.c
into one LLVM prefix, keeping nested_label split where CIR and classic
emit the store and indirectbr in opposite order.
---
 clang/lib/CIR/CodeGen/CIRGenStmt.cpp          |  8 ++--
 .../lib/CIR/Dialect/Transforms/GotoSolver.cpp | 37 ++++++-------------
 .../CIR/CodeGen/goto-indirect-cleanup-nyi.c   |  6 +--
 clang/test/CIR/CodeGen/goto-indirect-nested.c | 30 ++++-----------
 4 files changed, 24 insertions(+), 57 deletions(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp 
b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index 97691ebb8cb56..fb1f12aa8fec1 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -706,12 +706,10 @@ mlir::LogicalResult CIRGenFunction::emitGotoStmt(const 
clang::GotoStmt &s) {
 
 mlir::LogicalResult
 CIRGenFunction::emitIndirectGotoStmt(const IndirectGotoStmt &s) {
-  // An indirect goto that branches out of a scope needing cleanup (a VLA stack
-  // restore or a non-trivial destructor on the edge) must run that cleanup on
-  // the branch.  That is not implemented, so report it rather than emit a
-  // branch that silently skips the cleanup.
+  // An indirect goto with an active cleanup may leave its scope.  Determining
+  // whether its dynamic destination requires cleanup is not implemented.
   if (ehStack.stable_begin() != prologueCleanupDepth) {
-    cgm.errorNYI(s.getSourceRange(), "indirect goto across a cleanup scope");
+    cgm.errorNYI(s.getSourceRange(), "indirect goto with active cleanup");
     return mlir::success();
   }
 
diff --git a/clang/lib/CIR/Dialect/Transforms/GotoSolver.cpp 
b/clang/lib/CIR/Dialect/Transforms/GotoSolver.cpp
index f2d1677ce0dc2..2ecef988ca58e 100644
--- a/clang/lib/CIR/Dialect/Transforms/GotoSolver.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/GotoSolver.cpp
@@ -10,7 +10,6 @@
 #include "clang/CIR/Dialect/Passes.h"
 #include "llvm/ADT/SetVector.h"
 #include "llvm/ADT/StringMap.h"
-#include "llvm/ADT/StringSet.h"
 #include "llvm/Support/TimeProfiler.h"
 #include <memory>
 
@@ -35,9 +34,13 @@ static void process(cir::FuncOp func,
   llvm::StringMap<Block *> labels;
   llvm::SmallVector<cir::GotoOp, 4> gotos;
   llvm::SmallVector<cir::IndirectGotoOp> indirectGotos;
-  // Labels whose address is taken by a cir.block_address op in this function,
-  // in IR order.
-  llvm::SmallVector<StringRef> opBlockAddrLabels;
+  // Address-taken labels in a deterministic order: those referenced from 
global
+  // initializers first (in initializer order), then those taken by a
+  // cir.block_address op (in IR order).  A label may be named more than once 
(a
+  // dispatch table can list it twice); a block only needs to be a successor
+  // once, so keep the first occurrence.
+  llvm::SmallSetVector<StringRef, 4> addrTakenLabels(llvm::from_range,
+                                                     globalBlockAddrLabels);
 
   func.getBody().walk([&](mlir::Operation *op) {
     if (auto lab = dyn_cast<cir::LabelOp>(op)) {
@@ -47,30 +50,14 @@ static void process(cir::FuncOp func,
     } else if (auto indirect = dyn_cast<cir::IndirectGotoOp>(op)) {
       indirectGotos.push_back(indirect);
     } else if (auto blockAddr = dyn_cast<cir::BlockAddressOp>(op)) {
-      opBlockAddrLabels.push_back(blockAddr.getBlockAddrInfo().getLabel());
+      addrTakenLabels.insert(blockAddr.getBlockAddrInfo().getLabel());
     }
   });
 
-  // Address-taken labels in a deterministic order: those referenced from
-  // global initializers first (in initializer order), then those taken by a
-  // cir.block_address op (in IR order).  A label may be named more than once 
(a
-  // dispatch table can list it twice); a block only needs to be a successor
-  // once, so keep the first occurrence.
-  llvm::SmallVector<StringRef> addrTakenLabels;
-  llvm::StringSet<> addrTaken;
-  auto noteAddrTaken = [&](StringRef name) {
-    if (addrTaken.insert(name).second)
-      addrTakenLabels.push_back(name);
-  };
-  for (StringRef name : globalBlockAddrLabels)
-    noteAddrTaken(name);
-  for (StringRef name : opBlockAddrLabels)
-    noteAddrTaken(name);
-
   // Drop LabelOps whose address is never taken; the rest may be 
indirect-branch
   // successors and must survive.
   for (auto &lab : labels) {
-    if (!addrTaken.contains(lab.getKey())) {
+    if (!addrTakenLabels.contains(lab.getKey())) {
       if (auto labelOp = dyn_cast<cir::LabelOp>(&lab.getValue()->front()))
         labelOp.erase();
     }
@@ -96,13 +83,11 @@ static void process(cir::FuncOp func,
   // live in func's body now -- the cross-region branch that broke a nested
   // `goto *` during CIRGen cannot arise here.
   // The shared block represents every `goto *expr` that funnels into it, so
-  // fuse their locations when there is more than one.
+  // fuse their locations.
   llvm::SmallVector<mlir::Location> gotoLocs;
   for (cir::IndirectGotoOp indirect : indirectGotos)
     gotoLocs.push_back(indirect.getLoc());
-  mlir::Location loc = gotoLocs.size() == 1
-                           ? gotoLocs.front()
-                           : mlir::FusedLoc::get(func.getContext(), gotoLocs);
+  mlir::Location loc = mlir::FusedLoc::get(func.getContext(), gotoLocs);
   mlir::Type addrType = indirectGotos.front().getAddr().getType();
   Block *indirectGotoBlock = rewriter.createBlock(
       &func.getBody(), func.getBody().end(), {addrType}, {loc});
diff --git a/clang/test/CIR/CodeGen/goto-indirect-cleanup-nyi.c 
b/clang/test/CIR/CodeGen/goto-indirect-cleanup-nyi.c
index e2dc42c37673e..9a312ddd95d10 100644
--- a/clang/test/CIR/CodeGen/goto-indirect-cleanup-nyi.c
+++ b/clang/test/CIR/CodeGen/goto-indirect-cleanup-nyi.c
@@ -1,12 +1,10 @@
 // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir 
-verify %s
 
-// A `goto *p` that leaves a scope needing cleanup (here a VLA stack restore)
-// must run that cleanup on the branch.  That is not implemented yet, so it is
-// reported rather than lowered to a branch that skips the cleanup.
+// An indirect goto with an active VLA cleanup is not implemented.
 int vla(int n) {
   int a[n];
   void *p = &&done;
-  // expected-error@+1 {{indirect goto across a cleanup scope}}
+  // expected-error@+1 {{indirect goto with active cleanup}}
   goto *p;
 done:
   return a[0];
diff --git a/clang/test/CIR/CodeGen/goto-indirect-nested.c 
b/clang/test/CIR/CodeGen/goto-indirect-nested.c
index 81d2d327bfa54..4986869e4e7c6 100644
--- a/clang/test/CIR/CodeGen/goto-indirect-nested.c
+++ b/clang/test/CIR/CodeGen/goto-indirect-nested.c
@@ -1,9 +1,9 @@
 // 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 -fclangir -emit-llvm %s -o 
%t-cir.ll
-// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefix=LLVM
+// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefixes=LLVM,LLVMCIR
 // 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
+// RUN: FileCheck --input-file=%t.ll %s --check-prefixes=LLVM,OGCG
 
 // A `goto *p` inside a nested scope, jumping to a top-level label.
 int nested_goto(int x) {
@@ -26,13 +26,9 @@ int nested_goto(int x) {
 // CIR:   cir.label "done"
 
 // LLVM-LABEL: define dso_local i32 @nested_goto
-// LLVM:   store ptr blockaddress(@nested_goto, %[[DONE:[0-9]+]]), ptr 
%{{.*}}, align 8
+// LLVM:   store ptr blockaddress(@nested_goto, %[[DONE:.*]]), ptr %{{.*}}, 
align 8
 // LLVM:   indirectbr ptr %{{.*}}, [label %[[DONE]]]
 
-// OGCG-LABEL: define dso_local i32 @nested_goto
-// OGCG:   store ptr blockaddress(@nested_goto, %[[DONE:.*]]), ptr %{{.*}}, 
align 8
-// OGCG:   indirectbr ptr %{{.*}}, [label %[[DONE]]]
-
 // A top-level `goto *p` whose target label sits inside a nested scope.
 int nested_label(int x) {
   void *p;
@@ -54,9 +50,9 @@ int nested_label(int x) {
 // CIR:   %[[T:.*]] = cir.load align(8)
 // CIR:   cir.indirect_goto %[[T]] : !cir.ptr<!void>
 
-// LLVM-LABEL: define dso_local i32 @nested_label
-// LLVM:   store ptr blockaddress(@nested_label, %[[INNER:[0-9]+]]), ptr 
%{{.*}}, align 8
-// LLVM:   indirectbr ptr %{{.*}}, [label %[[INNER]]]
+// LLVMCIR-LABEL: define dso_local i32 @nested_label
+// LLVMCIR:   store ptr blockaddress(@nested_label, %[[INNER:.*]]), ptr 
%{{.*}}, align 8
+// LLVMCIR:   indirectbr ptr %{{.*}}, [label %[[INNER]]]
 
 // OGCG-LABEL: define dso_local i32 @nested_label
 // OGCG:   indirectbr ptr %{{.*}}, [label %[[INNER:.*]]]
@@ -82,13 +78,9 @@ int goto_in_loop(int n) {
 // CIR:   cir.label "out"
 
 // LLVM-LABEL: define dso_local i32 @goto_in_loop
-// LLVM:   store ptr blockaddress(@goto_in_loop, %[[OUT:[0-9]+]]), ptr 
%{{.*}}, align 8
+// LLVM:   store ptr blockaddress(@goto_in_loop, %[[OUT:.*]]), ptr %{{.*}}, 
align 8
 // LLVM:   indirectbr ptr %{{.*}}, [label %[[OUT]]]
 
-// OGCG-LABEL: define dso_local i32 @goto_in_loop
-// OGCG:   store ptr blockaddress(@goto_in_loop, %[[OUT:.*]]), ptr %{{.*}}, 
align 8
-// OGCG:   indirectbr ptr %{{.*}}, [label %[[OUT]]]
-
 // An address-taken label as the first statement of the function.
 int leading_label(int x) {
 first:;
@@ -106,13 +98,7 @@ first:;
 // CIR:   cir.indirect_goto %{{.*}} : !cir.ptr<!void>
 
 // LLVM-LABEL: define dso_local i32 @leading_label
-// LLVM:   br label %[[FIRST:[0-9]+]]
+// LLVM:   br label %[[FIRST:.*]]
 // LLVM: [[FIRST]]:
 // LLVM:   store ptr blockaddress(@leading_label, %[[FIRST]]), ptr %{{.*}}, 
align 8
 // LLVM:   indirectbr ptr %{{.*}}, [label %[[FIRST]]]
-
-// OGCG-LABEL: define dso_local i32 @leading_label
-// OGCG:   br label %[[FIRST:.*]]
-// OGCG: [[FIRST]]:
-// OGCG:   store ptr blockaddress(@leading_label, %[[FIRST]]), ptr %{{.*}}, 
align 8
-// OGCG:   indirectbr ptr %{{.*}}, [label %[[FIRST]]]

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to