From 8f4288a00ddc57614860871be249854a890de2a6 Mon Sep 17 00:00:00 2001 From: Adam Smith Date: Wed, 24 Jun 2026 13:12:22 -0700 Subject: [PATCH 1/3] [CIR] Wire const goto labels into indirect branch A computed goto through a constant dispatch table -- the GNU idiom `static const void *tbl[] = {&&L1, &&L2}; goto *tbl[i];` -- reached errorNYI("Indirect goto without a goto block") in emitIndirectGotoStmt. #203644 emits the label-address constant (a value-like #cir.block_addr_info) into the table, but it takes a label's address in a constant context without registering the label as address-taken, so no indirect-goto block is created and the following goto *tbl[i] has nothing to branch to. VisitAddrLabelExpr in the constant emitter now records each label via takeAddressOfConstantLabel, which instantiates the indirect-goto block and tracks the label. finishIndirectBranch then adds those labels as cir.indirect_br successors, alongside the existing op-form labels. A label named more than once in a table (`{&&A, &&A, &&B}`) is kept as a distinct successor each time, matching classic codegen's `indirectbr ... [label %A, label %A, label %B]`. The runtime form `void *p = &&L; goto *p;` is unchanged. New test goto-address-label-table.c checks CIR, the CIR-lowered LLVM, and classic OGCG across a few shapes: a plain table, a label named twice, a label whose address is taken but never reached by a goto, and one reached through both a constant table and a runtime block-address. --- clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp | 17 ++- clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 17 +++ clang/lib/CIR/CodeGen/CIRGenFunction.h | 10 ++ .../CIR/CodeGen/goto-address-label-table.c | 127 ++++++++++++++++++ 4 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 clang/test/CIR/CodeGen/goto-address-label-table.c diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp index c29b66ac2f8bc..92db632c35496 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp @@ -1521,10 +1521,19 @@ ConstantLValueEmitter::VisitPredefinedExpr(const PredefinedExpr *e) { ConstantLValue ConstantLValueEmitter::VisitAddrLabelExpr(const AddrLabelExpr *e) { - auto func = cast(emitter.cgf->curFn); - return cir::BlockAddrInfoAttr::get(cgm.getBuilder().getContext(), - func.getSymName(), - e->getLabel()->getName()); + // 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 the enclosing function + // gets an indirect-goto block with this label among its successors; otherwise + // a following `goto *tbl[i]` has no goto block to branch to. 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(emitter.cgf); + auto func = cast(cgf.curFn); + cir::BlockAddrInfoAttr info = cir::BlockAddrInfoAttr::get( + &cgf.getMLIRContext(), func.getSymName(), e->getLabel()->getName()); + cgf.takeAddressOfConstantLabel(info); + return info; } ConstantLValue ConstantLValueEmitter::VisitCallExpr(const CallExpr *e) { diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index 6606cf74c7dea..92d472c1c8004 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -625,10 +625,22 @@ void CIRGenFunction::finishIndirectBranch() { succesors.push_back(labelOp->getBlock()); rangeOperands.push_back(labelOp->getBlock()->getArguments()); } + // Labels whose address was taken only from a constant initializer have no + // function-local BlockAddressOp; add them as successors here. All labels + // are emitted by now, so the lookup resolves. A label may appear more than + // once (a dispatch table can name it twice), and each occurrence is kept as + // a distinct successor to match classic codegen. + for (cir::BlockAddrInfoAttr info : constBlockAddressLabels) { + cir::LabelOp labelOp = cgm.lookupBlockAddressInfo(info); + assert(labelOp && "expected cir.label to be emitted for const block addr"); + succesors.push_back(labelOp->getBlock()); + rangeOperands.push_back(labelOp->getBlock()->getArguments()); + } cir::IndirectBrOp::create(builder, builder.getUnknownLoc(), indirectGotoBlock->getArgument(0), false, rangeOperands, succesors); cgm.blockAddressToLabel.clear(); + constBlockAddressLabels.clear(); } void CIRGenFunction::finishFunction(SourceLocation endLoc) { @@ -1564,6 +1576,11 @@ void CIRGenFunction::instantiateIndirectGotoBlock() { {builder.getUnknownLoc()}); } +void CIRGenFunction::takeAddressOfConstantLabel(cir::BlockAddrInfoAttr info) { + constBlockAddressLabels.push_back(info); + instantiateIndirectGotoBlock(); +} + 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 b6a4a277fab92..3552d2a5412b2 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -730,6 +730,12 @@ class CIRGenFunction : public CIRGenTypeCache { /// been resolved. mlir::Block *indirectGotoBlock = nullptr; + /// Labels whose address is taken in a constant context (e.g. a static + /// computed-goto dispatch table). These have no function-local + /// BlockAddressOp, so they are tracked here and added as indirect-goto + /// branch successors in finishIndirectBranch. + llvm::SmallVector constBlockAddressLabels; + void resolveBlockAddresses(); void finishIndirectBranch(); @@ -1705,6 +1711,10 @@ class CIRGenFunction : public CIRGenTypeCache { void instantiateIndirectGotoBlock(); + /// Record a label whose address is taken from a constant initializer and + /// ensure the indirect-goto block exists. + void takeAddressOfConstantLabel(cir::BlockAddrInfoAttr info); + /// 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/test/CIR/CodeGen/goto-address-label-table.c b/clang/test/CIR/CodeGen/goto-address-label-table.c new file mode 100644 index 0000000000000..67d1c88a97c26 --- /dev/null +++ b/clang/test/CIR/CodeGen/goto-address-label-table.c @@ -0,0 +1,127 @@ +// 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 + +int f(int x) { + static const void *tbl[] = {&&L1, &&L2}; + goto *tbl[x]; +L1: + return 1; +L2: + return 2; +} + +// A appears twice in g's table; both occurrences are kept as indirect-branch +// successors, matching classic codegen. +int g(int x) { + static const void *tbl[] = {&&A, &&A, &&B}; + goto *tbl[x]; +A: + return 1; +B: + return 2; +} + +// A constant label address with no indirect goto reaching it: the indirect-goto +// block is created but has no predecessors, so it is left poisoned. +int h(int x) { + static const void *tbl[] = {&&L1}; + (void)tbl; + return x; +L1: + return 0; +} + +// A's address comes from a constant table, B's from a runtime block-address op; +// both feed the same indirect branch. +int m(int sel) { + static const void *ctbl[] = {&&A2}; + void *p = &&B2; + void *t = (void *)ctbl[0]; + void *dest = sel ? t : p; + goto *dest; +A2: + return 1; +B2: + return 2; +} + +// CIR-DAG: cir.global "private" internal dso_local @f.tbl = #cir.const_array<[#cir.block_addr_info<@f, "L1"> : !cir.ptr, #cir.block_addr_info<@f, "L2"> : !cir.ptr]> : !cir.array x 2> +// CIR-DAG: cir.global "private" internal dso_local @g.tbl = #cir.const_array<[#cir.block_addr_info<@g, "A"> : !cir.ptr, #cir.block_addr_info<@g, "A"> : !cir.ptr, #cir.block_addr_info<@g, "B"> : !cir.ptr]> : !cir.array x 3> +// CIR-DAG: cir.global "private" internal dso_local @h.tbl = #cir.const_array<[#cir.block_addr_info<@h, "L1"> : !cir.ptr]> : !cir.array x 1> +// CIR-DAG: cir.global "private" internal dso_local @m.ctbl = #cir.const_array<[#cir.block_addr_info<@m, "A2"> : !cir.ptr]> : !cir.array x 1> + +// CIR: cir.func {{.*}} @f +// CIR: %[[TBL:.*]] = cir.get_global @f.tbl +// CIR: cir.indirect_br %{{.*}} : !cir.ptr, [ +// CIR-NEXT: ^[[L1BB:.*]], +// CIR-NEXT: ^[[L2BB:.*]] +// CIR: ] +// CIR: ^[[L1BB]]: +// CIR: cir.label "L1" +// CIR: ^[[L2BB]]: +// CIR: cir.label "L2" + +// CIR: cir.func {{.*}} @g +// CIR: cir.indirect_br %{{.*}} : !cir.ptr, [ +// CIR-NEXT: ^[[ABB:.*]], +// CIR-NEXT: ^[[ABB]], +// CIR-NEXT: ^[[BBB:.*]] +// CIR: ] +// CIR: ^[[ABB]]: +// CIR: cir.label "A" +// CIR: ^[[BBB]]: +// CIR: cir.label "B" + +// No indirect goto reaches the label, so the goto block is poisoned. +// CIR: cir.func {{.*}} @h +// CIR: cir.indirect_br %{{.*}} poison : !cir.ptr, [ +// CIR-NEXT: ^[[HL1:.*]] +// CIR: ] +// CIR: ^[[HL1]]: +// CIR: cir.label "L1" + +// A2 comes from the constant table, B2 from a runtime block-address op; both +// are successors of the one indirect branch. +// CIR: cir.func {{.*}} @m +// CIR: cir.block_address <@m, "B2"> +// CIR: cir.indirect_br +// CIR-DAG: cir.label "A2" +// CIR-DAG: cir.label "B2" + +// LLVM-DAG: @f.tbl = internal global [2 x ptr] [ptr blockaddress(@f, %[[L1:[0-9]+]]), ptr blockaddress(@f, %[[L2:[0-9]+]])], align 16 +// LLVM-DAG: @g.tbl = internal global [3 x ptr] [ptr blockaddress(@g, %[[GA:[0-9]+]]), ptr blockaddress(@g, %[[GA]]), ptr blockaddress(@g, %[[GB:[0-9]+]])], align 16 +// LLVM-DAG: @h.tbl = internal global [1 x ptr] [ptr blockaddress(@h, %{{[0-9]+}})], align 8 +// LLVM-DAG: @m.ctbl = internal global [1 x ptr] [ptr blockaddress(@m, %{{[0-9]+}})], align 8 + +// LLVM: define dso_local i32 @f(i32 noundef %{{.*}}) +// LLVM: indirectbr ptr %{{.*}}, [label %[[L1]], label %[[L2]]] + +// LLVM: define dso_local i32 @g(i32 noundef %{{.*}}) +// LLVM: indirectbr ptr %{{.*}}, [label %[[GA]], label %[[GA]], label %[[GB]]] + +// LLVM: define dso_local i32 @h(i32 noundef %{{.*}}) +// LLVM: indirectbr ptr poison, [label %{{[0-9]+}}] + +// LLVM: define dso_local i32 @m(i32 noundef %{{.*}}) +// LLVM: indirectbr ptr %{{.*}}, [label %{{[0-9]+}}, label %{{[0-9]+}}] + +// OGCG-DAG: @f.tbl = internal global [2 x ptr] [ptr blockaddress(@f, %L1), ptr blockaddress(@f, %L2)], align 16 +// OGCG-DAG: @g.tbl = internal global [3 x ptr] [ptr blockaddress(@g, %A), ptr blockaddress(@g, %A), ptr blockaddress(@g, %B)], align 16 +// OGCG-DAG: @h.tbl = internal global [1 x ptr] [ptr blockaddress(@h, %L1)], align 8 +// OGCG-DAG: @m.ctbl = internal global [1 x ptr] [ptr blockaddress(@m, %A2)], align 8 + +// OGCG: define dso_local i32 @f(i32 noundef %{{.*}}) +// OGCG: indirectbr ptr %indirect.goto.dest, [label %L1, label %L2] + +// OGCG: define dso_local i32 @g(i32 noundef %{{.*}}) +// OGCG: indirectbr ptr %indirect.goto.dest, [label %A, label %A, label %B] + +// OGCG: define dso_local i32 @h(i32 noundef %{{.*}}) +// OGCG: indirectbr ptr poison, [label %L1] + +// OGCG: define dso_local i32 @m(i32 noundef %{{.*}}) +// OGCG: indirectbr ptr %indirect.goto.dest, [label %{{.*}}, label %{{.*}}] From c7e3c11cfccdaab36b14cf3dcaddca3a18b14f54 Mon Sep 17 00:00:00 2001 From: Adam Smith Date: Thu, 25 Jun 2026 09:55:36 -0700 Subject: [PATCH 2/3] [CIR] Simplify indirect-goto label tracking Track every address-taken label -- whether `&&label` appears as an operation or in a constant initializer -- in one per-function vector of BlockAddrInfoAttr, resolved to its LabelOp in finishIndirectBranch. This drops the BlockAddressOp->LabelOp map and the unresolved/resolved bookkeeping in CIRGenModule: a label always resolves once every label has been emitted, so the op-side deferral was unnecessary. Create the indirect-branch block only when a `goto *expr` is emitted, instead of the first time a label address is taken. A function that takes a label address but never branches indirectly now emits no indirect branch, rather than a dead one the verifier needs poisoned, so the poison fallback is gone too. Classic codegen still emits the dead indirectbr for that case, so the two diverge there; goto-address-label-table.c (h) and label-values.c (E) cover it. --- clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp | 7 +- clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp | 11 +- clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 63 +++------ clang/lib/CIR/CodeGen/CIRGenFunction.h | 24 ++-- clang/lib/CIR/CodeGen/CIRGenModule.cpp | 23 --- clang/lib/CIR/CodeGen/CIRGenModule.h | 11 -- clang/lib/CIR/CodeGen/CIRGenStmt.cpp | 11 +- .../CIR/CodeGen/goto-address-label-table.c | 131 ++++++++---------- clang/test/CIR/CodeGen/label-values.c | 39 +----- 9 files changed, 100 insertions(+), 220 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp index 92db632c35496..66766b9710e11 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp @@ -1523,16 +1523,15 @@ 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 the enclosing function - // gets an indirect-goto block with this label among its successors; otherwise - // a following `goto *tbl[i]` has no goto block to branch to. A label is + // 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 // always function-local, so cgf is set here. assert(emitter.cgf && "label address in a constant requires a function"); CIRGenFunction &cgf = *const_cast(emitter.cgf); auto func = cast(cgf.curFn); cir::BlockAddrInfoAttr info = cir::BlockAddrInfoAttr::get( &cgf.getMLIRContext(), func.getSymName(), e->getLabel()->getName()); - cgf.takeAddressOfConstantLabel(info); + cgf.takeAddressOfLabel(info); return info; } diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp index 5e8bb9df83ab3..047fc104afd86 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp @@ -211,16 +211,7 @@ class ScalarExprEmitter : public StmtVisitor { cir::BlockAddressOp blockAddressOp = cir::BlockAddressOp::create( builder, cgf.getLoc(e->getSourceRange()), cgf.convertType(e->getType()), blockInfoAttr); - cir::LabelOp resolvedLabel = cgf.cgm.lookupBlockAddressInfo(blockInfoAttr); - if (!resolvedLabel) { - cgf.cgm.mapUnresolvedBlockAddress(blockAddressOp); - // Still add the op to maintain insertion order it will be resolved in - // resolveBlockAddresses - cgf.cgm.mapResolvedBlockAddress(blockAddressOp, nullptr); - } else { - cgf.cgm.mapResolvedBlockAddress(blockAddressOp, resolvedLabel); - } - cgf.instantiateIndirectGotoBlock(); + cgf.takeAddressOfLabel(blockInfoAttr); return blockAddressOp; } diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index 92d472c1c8004..de7982df76e5d 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -604,60 +604,38 @@ void CIRGenFunction::startFunction(GlobalDecl gd, QualType returnType, } } -void CIRGenFunction::resolveBlockAddresses() { - for (cir::BlockAddressOp &blockAddress : cgm.unresolvedBlockAddressToLabel) { - cir::LabelOp labelOp = - cgm.lookupBlockAddressInfo(blockAddress.getBlockAddrInfo()); - assert(labelOp && "expected cir.labelOp to already be emitted"); - cgm.updateResolvedBlockAddress(blockAddress, labelOp); - } - cgm.unresolvedBlockAddressToLabel.clear(); -} - 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; - llvm::SmallVector succesors; + + // Every label is emitted by now, so each address-taken label resolves to its + // LabelOp. A label may appear more than once (a dispatch table can name it + // twice), and each occurrence is kept as a distinct successor to match + // classic codegen. + llvm::SmallVector successors; llvm::SmallVector rangeOperands; - mlir::OpBuilder::InsertionGuard guard(builder); - builder.setInsertionPointToEnd(indirectGotoBlock); - for (auto &[blockAdd, labelOp] : cgm.blockAddressToLabel) { - succesors.push_back(labelOp->getBlock()); - rangeOperands.push_back(labelOp->getBlock()->getArguments()); - } - // Labels whose address was taken only from a constant initializer have no - // function-local BlockAddressOp; add them as successors here. All labels - // are emitted by now, so the lookup resolves. A label may appear more than - // once (a dispatch table can name it twice), and each occurrence is kept as - // a distinct successor to match classic codegen. - for (cir::BlockAddrInfoAttr info : constBlockAddressLabels) { + for (cir::BlockAddrInfoAttr info : indirectGotoTargets) { cir::LabelOp labelOp = cgm.lookupBlockAddressInfo(info); - assert(labelOp && "expected cir.label to be emitted for const block addr"); - succesors.push_back(labelOp->getBlock()); + assert(labelOp && "expected cir.label to be emitted for block address"); + successors.push_back(labelOp->getBlock()); rangeOperands.push_back(labelOp->getBlock()->getArguments()); } + + mlir::OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToEnd(indirectGotoBlock); cir::IndirectBrOp::create(builder, builder.getUnknownLoc(), indirectGotoBlock->getArgument(0), false, - rangeOperands, succesors); - cgm.blockAddressToLabel.clear(); - constBlockAddressLabels.clear(); + rangeOperands, successors); + indirectGotoTargets.clear(); } void CIRGenFunction::finishFunction(SourceLocation endLoc) { - // Resolve block address-to-label mappings, then emit the indirect branch - // with the corresponding targets. - resolveBlockAddresses(); + // Emit the indirect branch with all resolved label destinations. finishIndirectBranch(); - // If a label address was taken but no indirect goto was used, we can't remove - // the block argument here. Instead, we mark the 'indirectbr' op - // as poison so that the cleanup can be deferred to lowering, since the - // verifier doesn't allow the 'indirectbr' target address to be null. - if (indirectGotoBlock && indirectGotoBlock->hasNoPredecessors()) { - auto indrBr = cast(indirectGotoBlock->front()); - indrBr.setPoison(true); - } - // 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 @@ -1576,9 +1554,8 @@ void CIRGenFunction::instantiateIndirectGotoBlock() { {builder.getUnknownLoc()}); } -void CIRGenFunction::takeAddressOfConstantLabel(cir::BlockAddrInfoAttr info) { - constBlockAddressLabels.push_back(info); - instantiateIndirectGotoBlock(); +void CIRGenFunction::takeAddressOfLabel(cir::BlockAddrInfoAttr info) { + indirectGotoTargets.push_back(info); } mlir::Value CIRGenFunction::emitAlignmentAssumption( diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 3552d2a5412b2..f4807a928ca2f 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -725,18 +725,16 @@ class CIRGenFunction : public CIRGenTypeCache { }; /// IndirectBranch - The first time an indirect goto is seen we create a block - /// reserved for the indirect branch. Unlike before,the actual 'indirectbr' - /// is emitted at the end of the function, once all block destinations have - /// been resolved. + /// 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 a constant context (e.g. a static - /// computed-goto dispatch table). These have no function-local - /// BlockAddressOp, so they are tracked here and added as indirect-goto - /// branch successors in finishIndirectBranch. - llvm::SmallVector constBlockAddressLabels; + /// Labels whose address is taken in this function (via `&&label`, as either + /// an operation or a constant initializer). They are resolved to their + /// LabelOps and wired as `cir.indirect_br` successors in + /// finishIndirectBranch. + llvm::SmallVector indirectGotoTargets; - void resolveBlockAddresses(); void finishIndirectBranch(); /// Perform the usual unary conversions on the specified expression and @@ -1711,9 +1709,11 @@ class CIRGenFunction : public CIRGenTypeCache { void instantiateIndirectGotoBlock(); - /// Record a label whose address is taken from a constant initializer and - /// ensure the indirect-goto block exists. - void takeAddressOfConstantLabel(cir::BlockAddrInfoAttr info); + /// Record a label whose address is taken (via `&&label`) as an indirect-goto + /// target. The branch block itself is created lazily when an indirect goto + /// statement is emitted, and the targets are wired as its successors in + /// finishIndirectBranch. + void takeAddressOfLabel(cir::BlockAddrInfoAttr info); /// 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 diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index 78c038733c6af..c7af93af53768 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -3881,29 +3881,6 @@ void CIRGenModule::mapBlockAddress(cir::BlockAddrInfoAttr blockInfo, "attempting to map a blockaddress info that is already mapped"); } -void CIRGenModule::mapUnresolvedBlockAddress(cir::BlockAddressOp op) { - [[maybe_unused]] auto result = unresolvedBlockAddressToLabel.insert(op); - assert(result.second && - "attempting to map a blockaddress operation that is already mapped"); -} - -void CIRGenModule::mapResolvedBlockAddress(cir::BlockAddressOp op, - cir::LabelOp label) { - [[maybe_unused]] auto result = blockAddressToLabel.try_emplace(op, label); - assert(result.second && - "attempting to map a blockaddress operation that is already mapped"); -} - -void CIRGenModule::updateResolvedBlockAddress(cir::BlockAddressOp op, - cir::LabelOp newLabel) { - auto *it = blockAddressToLabel.find(op); - assert(it != blockAddressToLabel.end() && - "trying to update a blockaddress not previously mapped"); - assert(!it->second && "blockaddress already has a resolved label"); - - it->second = newLabel; -} - cir::LabelOp CIRGenModule::lookupBlockAddressInfo(cir::BlockAddrInfoAttr blockInfo) { return blockAddressInfoToLabel.lookup(blockInfo); diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index c6fef5dde5fd9..144f8c7b9f3e7 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -195,19 +195,8 @@ class CIRGenModule : public CIRGenTypeCache { /// LabelOp. This provides the main lookup table used to resolve block /// addresses into their label operations. llvm::DenseMap blockAddressInfoToLabel; - /// Map CIR BlockAddressOps directly to their resolved LabelOps. - /// Used once a block address has been successfully lowered to a label. - llvm::MapVector blockAddressToLabel; - /// Track CIR BlockAddressOps that cannot be resolved immediately - /// because their LabelOp has not yet been emitted. These entries - /// are solved later once the corresponding label is available. - llvm::DenseSet unresolvedBlockAddressToLabel; cir::LabelOp lookupBlockAddressInfo(cir::BlockAddrInfoAttr blockInfo); void mapBlockAddress(cir::BlockAddrInfoAttr blockInfo, cir::LabelOp label); - void mapUnresolvedBlockAddress(cir::BlockAddressOp op); - void mapResolvedBlockAddress(cir::BlockAddressOp op, cir::LabelOp); - void updateResolvedBlockAddress(cir::BlockAddressOp op, - cir::LabelOp newLabel); /// 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 47c94cb4ec535..57d88b4d23c1b 100644 --- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp @@ -706,13 +706,10 @@ mlir::LogicalResult CIRGenFunction::emitGotoStmt(const clang::GotoStmt &s) { mlir::LogicalResult CIRGenFunction::emitIndirectGotoStmt(const IndirectGotoStmt &s) { mlir::Value val = emitScalarExpr(s.getTarget()); - if (!indirectGotoBlock) { - // If the target labels were emitted as constants, we have more work to do. - // This diagnostic is here to flag the condition, but the changes may end - // up being implemented elsewhere. - cgm.errorNYI(s.getSourceRange(), "Indirect goto without a goto block"); - return mlir::failure(); - } + // 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); builder.createBlock(builder.getBlock()->getParent()); diff --git a/clang/test/CIR/CodeGen/goto-address-label-table.c b/clang/test/CIR/CodeGen/goto-address-label-table.c index 67d1c88a97c26..f0fe62e23bdff 100644 --- a/clang/test/CIR/CodeGen/goto-address-label-table.c +++ b/clang/test/CIR/CodeGen/goto-address-label-table.c @@ -1,9 +1,19 @@ // 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 + +// CIR-DAG: cir.global "private" internal dso_local @f.tbl = #cir.const_array<[#cir.block_addr_info<@f, "L1"> : !cir.ptr, #cir.block_addr_info<@f, "L2"> : !cir.ptr]> : !cir.array x 2> +// CIR-DAG: cir.global "private" internal dso_local @g.tbl = #cir.const_array<[#cir.block_addr_info<@g, "A"> : !cir.ptr, #cir.block_addr_info<@g, "A"> : !cir.ptr, #cir.block_addr_info<@g, "B"> : !cir.ptr]> : !cir.array x 3> +// CIR-DAG: cir.global "private" internal dso_local @h.tbl = #cir.const_array<[#cir.block_addr_info<@h, "L1"> : !cir.ptr]> : !cir.array x 1> +// CIR-DAG: cir.global "private" internal dso_local @m.ctbl = #cir.const_array<[#cir.block_addr_info<@m, "A2"> : !cir.ptr]> : !cir.array x 1> + +// LLVM-DAG: @f.tbl = internal global [2 x ptr] [ptr blockaddress(@f, %[[FL1:[0-9a-zA-Z_.]+]]), ptr blockaddress(@f, %[[FL2:[0-9a-zA-Z_.]+]])], align 16 +// LLVM-DAG: @g.tbl = internal global [3 x ptr] [ptr blockaddress(@g, %[[GA:[0-9a-zA-Z_.]+]]), ptr blockaddress(@g, %[[GA]]), ptr blockaddress(@g, %[[GB:[0-9a-zA-Z_.]+]])], align 16 +// LLVM-DAG: @h.tbl = internal global [1 x ptr] [ptr blockaddress(@h, %{{[0-9a-zA-Z_.]+}})], align 8 +// LLVM-DAG: @m.ctbl = internal global [1 x ptr] [ptr blockaddress(@m, %[[MA2:[0-9a-zA-Z_.]+]])], align 8 int f(int x) { static const void *tbl[] = {&&L1, &&L2}; @@ -14,6 +24,20 @@ int f(int x) { return 2; } +// CIR-LABEL: cir.func {{.*}} @f +// CIR: %[[TBL:.*]] = cir.get_global @f.tbl +// CIR: cir.indirect_br %{{.*}} : !cir.ptr, [ +// CIR-NEXT: ^[[L1BB:.*]], +// CIR-NEXT: ^[[L2BB:.*]] +// CIR: ] +// CIR: ^[[L1BB]]: +// CIR: cir.label "L1" +// CIR: ^[[L2BB]]: +// CIR: cir.label "L2" + +// LLVM-LABEL: define dso_local i32 @f( +// LLVM: indirectbr ptr %{{.*}}, [label %[[FL1]], label %[[FL2]]] + // A appears twice in g's table; both occurrences are kept as indirect-branch // successors, matching classic codegen. int g(int x) { @@ -25,8 +49,22 @@ int g(int x) { return 2; } -// A constant label address with no indirect goto reaching it: the indirect-goto -// block is created but has no predecessors, so it is left poisoned. +// CIR-LABEL: cir.func {{.*}} @g +// CIR: cir.indirect_br %{{.*}} : !cir.ptr, [ +// CIR-NEXT: ^[[ABB:.*]], +// CIR-NEXT: ^[[ABB]], +// CIR-NEXT: ^[[BBB:.*]] +// CIR: ] +// CIR: ^[[ABB]]: +// CIR: cir.label "A" +// CIR: ^[[BBB]]: +// CIR: cir.label "B" + +// LLVM-LABEL: define dso_local i32 @g( +// LLVM: 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). int h(int x) { static const void *tbl[] = {&&L1}; (void)tbl; @@ -35,8 +73,15 @@ int h(int x) { return 0; } -// A's address comes from a constant table, B's from a runtime block-address op; -// both feed the same indirect branch. +// CIR-LABEL: cir.func {{.*}} @h +// CIR-NOT: cir.indirect_br + +// LLVM-LABEL: define dso_local i32 @h( +// LLVMCIR-NOT: indirectbr +// OGCG: indirectbr ptr poison, [label %{{.+}}] + +// A2's address comes from a constant table, B2's from a runtime block-address +// op; both feed the same indirect branch. int m(int sel) { static const void *ctbl[] = {&&A2}; void *p = &&B2; @@ -49,79 +94,11 @@ int m(int sel) { return 2; } -// CIR-DAG: cir.global "private" internal dso_local @f.tbl = #cir.const_array<[#cir.block_addr_info<@f, "L1"> : !cir.ptr, #cir.block_addr_info<@f, "L2"> : !cir.ptr]> : !cir.array x 2> -// CIR-DAG: cir.global "private" internal dso_local @g.tbl = #cir.const_array<[#cir.block_addr_info<@g, "A"> : !cir.ptr, #cir.block_addr_info<@g, "A"> : !cir.ptr, #cir.block_addr_info<@g, "B"> : !cir.ptr]> : !cir.array x 3> -// CIR-DAG: cir.global "private" internal dso_local @h.tbl = #cir.const_array<[#cir.block_addr_info<@h, "L1"> : !cir.ptr]> : !cir.array x 1> -// CIR-DAG: cir.global "private" internal dso_local @m.ctbl = #cir.const_array<[#cir.block_addr_info<@m, "A2"> : !cir.ptr]> : !cir.array x 1> - -// CIR: cir.func {{.*}} @f -// CIR: %[[TBL:.*]] = cir.get_global @f.tbl -// CIR: cir.indirect_br %{{.*}} : !cir.ptr, [ -// CIR-NEXT: ^[[L1BB:.*]], -// CIR-NEXT: ^[[L2BB:.*]] -// CIR: ] -// CIR: ^[[L1BB]]: -// CIR: cir.label "L1" -// CIR: ^[[L2BB]]: -// CIR: cir.label "L2" - -// CIR: cir.func {{.*}} @g -// CIR: cir.indirect_br %{{.*}} : !cir.ptr, [ -// CIR-NEXT: ^[[ABB:.*]], -// CIR-NEXT: ^[[ABB]], -// CIR-NEXT: ^[[BBB:.*]] -// CIR: ] -// CIR: ^[[ABB]]: -// CIR: cir.label "A" -// CIR: ^[[BBB]]: -// CIR: cir.label "B" - -// No indirect goto reaches the label, so the goto block is poisoned. -// CIR: cir.func {{.*}} @h -// CIR: cir.indirect_br %{{.*}} poison : !cir.ptr, [ -// CIR-NEXT: ^[[HL1:.*]] -// CIR: ] -// CIR: ^[[HL1]]: -// CIR: cir.label "L1" - -// A2 comes from the constant table, B2 from a runtime block-address op; both -// are successors of the one indirect branch. -// CIR: cir.func {{.*}} @m +// CIR-LABEL: cir.func {{.*}} @m // CIR: cir.block_address <@m, "B2"> // CIR: cir.indirect_br // CIR-DAG: cir.label "A2" // CIR-DAG: cir.label "B2" -// LLVM-DAG: @f.tbl = internal global [2 x ptr] [ptr blockaddress(@f, %[[L1:[0-9]+]]), ptr blockaddress(@f, %[[L2:[0-9]+]])], align 16 -// LLVM-DAG: @g.tbl = internal global [3 x ptr] [ptr blockaddress(@g, %[[GA:[0-9]+]]), ptr blockaddress(@g, %[[GA]]), ptr blockaddress(@g, %[[GB:[0-9]+]])], align 16 -// LLVM-DAG: @h.tbl = internal global [1 x ptr] [ptr blockaddress(@h, %{{[0-9]+}})], align 8 -// LLVM-DAG: @m.ctbl = internal global [1 x ptr] [ptr blockaddress(@m, %{{[0-9]+}})], align 8 - -// LLVM: define dso_local i32 @f(i32 noundef %{{.*}}) -// LLVM: indirectbr ptr %{{.*}}, [label %[[L1]], label %[[L2]]] - -// LLVM: define dso_local i32 @g(i32 noundef %{{.*}}) -// LLVM: indirectbr ptr %{{.*}}, [label %[[GA]], label %[[GA]], label %[[GB]]] - -// LLVM: define dso_local i32 @h(i32 noundef %{{.*}}) -// LLVM: indirectbr ptr poison, [label %{{[0-9]+}}] - -// LLVM: define dso_local i32 @m(i32 noundef %{{.*}}) -// LLVM: indirectbr ptr %{{.*}}, [label %{{[0-9]+}}, label %{{[0-9]+}}] - -// OGCG-DAG: @f.tbl = internal global [2 x ptr] [ptr blockaddress(@f, %L1), ptr blockaddress(@f, %L2)], align 16 -// OGCG-DAG: @g.tbl = internal global [3 x ptr] [ptr blockaddress(@g, %A), ptr blockaddress(@g, %A), ptr blockaddress(@g, %B)], align 16 -// OGCG-DAG: @h.tbl = internal global [1 x ptr] [ptr blockaddress(@h, %L1)], align 8 -// OGCG-DAG: @m.ctbl = internal global [1 x ptr] [ptr blockaddress(@m, %A2)], align 8 - -// OGCG: define dso_local i32 @f(i32 noundef %{{.*}}) -// OGCG: indirectbr ptr %indirect.goto.dest, [label %L1, label %L2] - -// OGCG: define dso_local i32 @g(i32 noundef %{{.*}}) -// OGCG: indirectbr ptr %indirect.goto.dest, [label %A, label %A, label %B] - -// OGCG: define dso_local i32 @h(i32 noundef %{{.*}}) -// OGCG: indirectbr ptr poison, [label %L1] - -// OGCG: define dso_local i32 @m(i32 noundef %{{.*}}) -// OGCG: indirectbr ptr %indirect.goto.dest, [label %{{.*}}, label %{{.*}}] +// LLVM-LABEL: define dso_local i32 @m( +// LLVM: indirectbr ptr %{{.*}}, [label %[[MA2]], label %{{.+}}] diff --git a/clang/test/CIR/CodeGen/label-values.c b/clang/test/CIR/CodeGen/label-values.c index cd54d91a57e9a..0cd608e296141 100644 --- a/clang/test/CIR/CodeGen/label-values.c +++ b/clang/test/CIR/CodeGen/label-values.c @@ -205,8 +205,8 @@ void D(void) { // OGCG: %indirect.goto.dest = phi ptr [ %[[BLOCKADD]], %entry ] // OGCG: indirectbr ptr %indirect.goto.dest, [label %LABEL_A, label %LABEL_A, label %LABEL_A] -// This test checks that CIR preserves insertion order of blockaddresses -// for indirectbr, even if some were resolved immediately and others later. +// E takes label addresses but never executes a `goto *`, so CIR emits no +// indirect branch (classic still emits a dead poisoned indirectbr, see OGCG). void E(void) { void *ptr = &&LABEL_D; void *ptr2 = &&LABEL_C; @@ -219,38 +219,11 @@ void E(void) { return; } -//CIR: cir.func {{.*}} @E() -//CIR: ^bb1({{.*}}: !cir.ptr {{.*}}): // no predecessors -//CIR: cir.indirect_br {{.*}} poison : !cir.ptr, [ -//CIR-NEXT: ^bb5, -//CIR-NEXT: ^bb4, -//CIR-NEXT: ^bb3, -//CIR-NEXT: ^bb2 -//CIR: ] -//CIR: ^bb2: // 2 preds: ^bb0, ^bb1 -//CIR: cir.label "LABEL_A" -//CIR: ^bb3: // 2 preds: ^bb1, ^bb2 -//CIR: cir.label "LABEL_B" -//CIR: ^bb4: // 2 preds: ^bb1, ^bb3 -//CIR: cir.label "LABEL_C" -//CIR: ^bb5: // 2 preds: ^bb1, ^bb4 -//CIR: cir.label "LABEL_D" +// CIR-LABEL: cir.func {{.*}} @E() +// CIR-NOT: cir.indirect_br -// LLVM: define dso_local void @E() -// LLVM: store ptr blockaddress(@E, %[[LABEL_D:.*]]) -// LLVM: store ptr blockaddress(@E, %[[LABEL_C:.*]]) -// LLVM: br label %[[LABEL_A:.*]] -// LLVM: [[indirectgoto:.*]]: ; No predecessors! -// LLVM: indirectbr ptr poison, [label %[[LABEL_D]], label %[[LABEL_C]], label %[[LABEL_B:.*]], label %[[LABEL_A]]] -// LLVM: [[LABEL_A]]: -// LLVM: br label %[[LABEL_B]] -// LLVM: [[LABEL_B]]: -// LLVM: store ptr blockaddress(@E, %[[LABEL_B]]) -// LLVM: store ptr blockaddress(@E, %[[LABEL_A]]) -// LLVM: br label %[[LABEL_C]] -// LLVM: [[LABEL_C]]: -// LLVM: br label %[[LABEL_D]] -// LLVM: [[LABEL_D]]: +// LLVM-LABEL: define dso_local void @E() +// LLVM-NOT: indirectbr // OGCG: define dso_local void @E() #0 { // OGCG: store ptr blockaddress(@E, %LABEL_D), ptr %ptr, align 8 From 6e2628f09d5c4910d386d71754c2288adee0b230 Mon Sep 17 00:00:00 2001 From: Adam Smith Date: Thu, 25 Jun 2026 12:09:50 -0700 Subject: [PATCH 3/3] [CIR] Dedup indirect branch successors A block only needs to appear once in a `cir.indirect_br` successor list, but `finishIndirectBranch` added one successor per address-taken label, so a dispatch table that named a label twice (or `&&label` taken twice) wired the same block in as several identical successors. The duplicates serve no purpose, so resolve each label to its block and skip blocks already seen. CIR diverges from classic codegen here, which keeps the duplicates. Also drop the `takeAddressOfLabel` wrapper; its only job was to push onto the public `indirectGotoTargets` vector, so the two callers push directly. Updates `goto-address-label-table.c` (g) and `label-values.c` (D), whose duplicate-label cases now emit a single successor on the CIR side while the OGCG checks keep classic's duplicates. --- clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp | 2 +- clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp | 2 +- clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 18 +++++++++--------- clang/lib/CIR/CodeGen/CIRGenFunction.h | 11 +++-------- .../CIR/CodeGen/goto-address-label-table.c | 8 ++++---- clang/test/CIR/CodeGen/label-values.c | 8 +++----- 6 files changed, 21 insertions(+), 28 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp index 66766b9710e11..eddb00c6fb2c1 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp @@ -1531,7 +1531,7 @@ ConstantLValueEmitter::VisitAddrLabelExpr(const AddrLabelExpr *e) { auto func = cast(cgf.curFn); cir::BlockAddrInfoAttr info = cir::BlockAddrInfoAttr::get( &cgf.getMLIRContext(), func.getSymName(), e->getLabel()->getName()); - cgf.takeAddressOfLabel(info); + cgf.indirectGotoTargets.push_back(info); return info; } diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp index 047fc104afd86..774cfc8ef7ab6 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp @@ -211,7 +211,7 @@ class ScalarExprEmitter : public StmtVisitor { cir::BlockAddressOp blockAddressOp = cir::BlockAddressOp::create( builder, cgf.getLoc(e->getSourceRange()), cgf.convertType(e->getType()), blockInfoAttr); - cgf.takeAddressOfLabel(blockInfoAttr); + cgf.indirectGotoTargets.push_back(blockInfoAttr); return blockAddressOp; } diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index de7982df76e5d..d0aedc0689404 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -612,16 +612,20 @@ void CIRGenFunction::finishIndirectBranch() { return; // Every label is emitted by now, so each address-taken label resolves to its - // LabelOp. A label may appear more than once (a dispatch table can name it - // twice), and each occurrence is kept as a distinct successor to match - // classic codegen. + // 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 successors; llvm::SmallVector rangeOperands; + llvm::SmallPtrSet seen; for (cir::BlockAddrInfoAttr info : indirectGotoTargets) { cir::LabelOp labelOp = cgm.lookupBlockAddressInfo(info); assert(labelOp && "expected cir.label to be emitted for block address"); - successors.push_back(labelOp->getBlock()); - rangeOperands.push_back(labelOp->getBlock()->getArguments()); + 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); @@ -1554,10 +1558,6 @@ void CIRGenFunction::instantiateIndirectGotoBlock() { {builder.getUnknownLoc()}); } -void CIRGenFunction::takeAddressOfLabel(cir::BlockAddrInfoAttr info) { - indirectGotoTargets.push_back(info); -} - 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 f4807a928ca2f..b903ec52d4c77 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -730,8 +730,9 @@ class CIRGenFunction : public CIRGenTypeCache { mlir::Block *indirectGotoBlock = nullptr; /// Labels whose address is taken in this function (via `&&label`, as either - /// an operation or a constant initializer). They are resolved to their - /// LabelOps and wired as `cir.indirect_br` successors in + /// 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 indirectGotoTargets; @@ -1709,12 +1710,6 @@ class CIRGenFunction : public CIRGenTypeCache { void instantiateIndirectGotoBlock(); - /// Record a label whose address is taken (via `&&label`) as an indirect-goto - /// target. The branch block itself is created lazily when an indirect goto - /// statement is emitted, and the targets are wired as its successors in - /// finishIndirectBranch. - void takeAddressOfLabel(cir::BlockAddrInfoAttr info); - /// 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/test/CIR/CodeGen/goto-address-label-table.c b/clang/test/CIR/CodeGen/goto-address-label-table.c index f0fe62e23bdff..2e273a7c392e9 100644 --- a/clang/test/CIR/CodeGen/goto-address-label-table.c +++ b/clang/test/CIR/CodeGen/goto-address-label-table.c @@ -38,8 +38,8 @@ int f(int x) { // LLVM-LABEL: define dso_local i32 @f( // LLVM: indirectbr ptr %{{.*}}, [label %[[FL1]], label %[[FL2]]] -// A appears twice in g's table; both occurrences are kept as indirect-branch -// successors, matching classic codegen. +// A appears twice in g's table, but a block only needs to be listed once as an +// indirect-branch successor, so CIR drops the duplicate (classic keeps it). int g(int x) { static const void *tbl[] = {&&A, &&A, &&B}; goto *tbl[x]; @@ -52,7 +52,6 @@ int g(int x) { // CIR-LABEL: cir.func {{.*}} @g // CIR: cir.indirect_br %{{.*}} : !cir.ptr, [ // CIR-NEXT: ^[[ABB:.*]], -// CIR-NEXT: ^[[ABB]], // CIR-NEXT: ^[[BBB:.*]] // CIR: ] // CIR: ^[[ABB]]: @@ -61,7 +60,8 @@ int g(int x) { // CIR: cir.label "B" // LLVM-LABEL: define dso_local i32 @g( -// LLVM: indirectbr ptr %{{.*}}, [label %[[GA]], label %[[GA]], label %[[GB]]] +// LLVMCIR: indirectbr ptr %{{.*}}, [label %[[GA]], label %[[GB]]] +// 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). diff --git a/clang/test/CIR/CodeGen/label-values.c b/clang/test/CIR/CodeGen/label-values.c index 0cd608e296141..f2bc5aa5a549f 100644 --- a/clang/test/CIR/CodeGen/label-values.c +++ b/clang/test/CIR/CodeGen/label-values.c @@ -165,11 +165,9 @@ void D(void) { // CIR: cir.br ^bb1 // CIR: ^bb1([[PHI:%*.]]: !cir.ptr {{.*}}): // pred: ^bb0 // CIR: cir.indirect_br [[PHI]] : !cir.ptr, [ -// CIR-DAG: ^bb2, -// CIR-DAG: ^bb2, -// CIR-DAG: ^bb2 +// CIR-NEXT: ^bb2 // CIR: ] -// CIR: ^bb2: // 3 preds: ^bb1, ^bb1, ^bb1 +// CIR: ^bb2: // pred: ^bb1 // CIR: cir.label "LABEL_A" // CIR: %[[BLK3:.*]] = cir.block_address <@D, "LABEL_A"> : !cir.ptr // CIR: cir.store align(8) %[[BLK3]], %[[PTR3]] : !cir.ptr, !cir.ptr> @@ -185,7 +183,7 @@ void D(void) { // LLVM: br label %[[indirectgoto:.*]] // LLVM: [[indirectgoto]]: // LLVM: [[PHI:%.*]] = phi ptr [ %[[BLOCKADD]], %[[ENTRY:.*]] ] -// LLVM: indirectbr ptr [[PHI]], [label %[[LABEL_A]], label %[[LABEL_A]], label %[[LABEL_A]]] +// LLVM: indirectbr ptr [[PHI]], [label %[[LABEL_A]]] // LLVM: [[LABEL_A]]: // LLVM: store ptr blockaddress(@D, %[[LABEL_A]]), ptr %[[PTR3]], align 8 // LLVM: ret void