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
