Skip to content

Commit 8dd5e0c

Browse files
[CIR] Implement IndirectBrOp to support GCC's labels-as-values extension (#1945)
This PR introduces the `cir.indirectbr` operation to support GCC’s labels-as-values extension, which allows `goto` statements to jump to computed block addresses. The implementation creates a dedicated block that hosts the `indirectbr`, where the target address is provided through a PHI Node, similar to how classic code generation handles indirect gotos.
1 parent 2b5e641 commit 8dd5e0c

File tree

14 files changed

+512
-44
lines changed

14 files changed

+512
-44
lines changed

clang/include/clang/CIR/Dialect/IR/CIROps.td

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2293,6 +2293,67 @@ def CIR_BrCondOp : CIR_Op<"brcond", [
22932293
}];
22942294
}
22952295

2296+
//===----------------------------------------------------------------------===//
2297+
// IndirectBrOp
2298+
//===----------------------------------------------------------------------===//
2299+
2300+
def CIR_IndirectBrOp : CIR_Op<"indirectbr", [
2301+
DeclareOpInterfaceMethods<BranchOpInterface>
2302+
, SameVariadicOperandSize, Terminator, Pure]> {
2303+
let summary = "Indirect branch";
2304+
let description = [{
2305+
The `cir.indirectbr` operation represents an indirect branch to one of
2306+
several possible successor blocks. The target block is computed from
2307+
the value of the given address operand.
2308+
2309+
This operation is typically generated when handling constructs like
2310+
the GCC extension `&&label` combined with an indirect `goto *ptr;`.
2311+
2312+
The `poison` attribute is used to mark an `indirectbr` that was created
2313+
but is known to be invalid — for instance, when a label address was
2314+
taken but no indirect branch was ever emitted.
2315+
2316+
Example:
2317+
2318+
```mlir
2319+
%0 = cir.alloca !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>, ["ptr", init]
2320+
%1 = cir.blockaddress <@A, "A"> -> !cir.ptr<!void>
2321+
cir.store align(8) %1, %0 : !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>
2322+
%2 = cir.load align(8) %0 : !cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>
2323+
cir.br ^bb1(%2 : !cir.ptr<!void>)
2324+
^bb1(%3: !cir.ptr<!void>):
2325+
cir.indirectbr %3 : <!void>, [
2326+
^bb2
2327+
]
2328+
```
2329+
or with a poison:
2330+
2331+
```mlir
2332+
cir.indirectbr %0 poison : <!void>, [
2333+
^bb3,
2334+
^bb2
2335+
]
2336+
```
2337+
}];
2338+
2339+
let arguments = (ins
2340+
CIR_VoidPtrType:$addr,
2341+
UnitAttr:$poison,
2342+
VariadicOfVariadic<AnyType, "indbr_operand_segments">:$succOperands,
2343+
DenseI32ArrayAttr:$indbr_operand_segments
2344+
);
2345+
2346+
let successors = (successor VariadicSuccessor<AnySuccessor>:$successors);
2347+
let assemblyFormat = [{
2348+
$addr ( `poison` $poison^ )? `:` type($addr) `,`
2349+
custom<IndirectBrOpSucessors>(ref(type($addr)),
2350+
$successors,
2351+
$succOperands,
2352+
type($succOperands))
2353+
attr-dict
2354+
}];
2355+
}
2356+
22962357
//===----------------------------------------------------------------------===//
22972358
// While & DoWhileOp
22982359
//===----------------------------------------------------------------------===//

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,6 @@ struct MissingFeatures {
240240
static bool emitScalarRangeCheck() { return false; }
241241
static bool stmtExprEvaluation() { return false; }
242242
static bool setCallingConv() { return false; }
243-
static bool indirectBranch() { return false; }
244243
static bool escapedLocals() { return false; }
245244
static bool deferredReplacements() { return false; }
246245
static bool shouldInstrumentFunction() { return false; }

clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,22 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
231231
auto func = cast<cir::FuncOp>(CGF.CurFn);
232232
auto blockInfoAttr = cir::BlockAddrInfoAttr::get(
233233
&CGF.getMLIRContext(), func.getSymName(), e->getLabel()->getName());
234-
return cir::BlockAddressOp::create(Builder, CGF.getLoc(e->getSourceRange()),
235-
CGF.convertType(e->getType()),
236-
blockInfoAttr);
234+
auto blockAddressOp = cir::BlockAddressOp::create(
235+
Builder, CGF.getLoc(e->getSourceRange()), CGF.convertType(e->getType()),
236+
blockInfoAttr);
237+
cir::LabelOp resolvedLabel = CGF.CGM.lookupBlockAddressInfo(blockInfoAttr);
238+
if (!resolvedLabel) {
239+
CGF.CGM.mapUnresolvedBlockAddress(blockAddressOp);
240+
// Still add the op to maintain insertion order it will be resolved in
241+
// resolveBlockAddresses
242+
CGF.CGM.mapResolvedBlockAddress(blockAddressOp, nullptr);
243+
} else {
244+
CGF.CGM.mapResolvedBlockAddress(blockAddressOp, resolvedLabel);
245+
}
246+
CGF.getIndirectGotoBlock(blockAddressOp);
247+
return blockAddressOp;
237248
}
249+
238250
mlir::Value VisitSizeOfPackExpr(SizeOfPackExpr *E) {
239251
llvm_unreachable("NYI");
240252
}

clang/lib/CIR/CodeGen/CIRGenFunction.cpp

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,34 @@ cir::TryOp CIRGenFunction::LexicalScope::getClosestTryParent() {
528528
return nullptr;
529529
}
530530

531+
void CIRGenFunction::resolveBlockAddresses() {
532+
533+
for (auto &blockAddress : CGM.unresolvedBlockAddressToLabel) {
534+
cir::LabelOp labelOp =
535+
CGM.lookupBlockAddressInfo(blockAddress.getBlockAddrInfo());
536+
assert(labelOp && "expected cir.labelOp to already be emitted");
537+
CGM.updateResolvedBlockAddress(blockAddress, labelOp);
538+
}
539+
CGM.unresolvedBlockAddressToLabel.clear();
540+
}
541+
542+
void CIRGenFunction::finishIndirectBranch() {
543+
if (!indirectGotoBlock)
544+
return;
545+
llvm::SmallVector<mlir::Block *> succesors;
546+
llvm::SmallVector<mlir::ValueRange> rangeOperands;
547+
mlir::OpBuilder::InsertionGuard guard(builder);
548+
builder.setInsertionPointToEnd(indirectGotoBlock);
549+
for (auto &[blockAdd, labelOp] : CGM.blockAddressToLabel) {
550+
succesors.push_back(labelOp->getBlock());
551+
rangeOperands.push_back(labelOp->getBlock()->getArguments());
552+
}
553+
cir::IndirectBrOp::create(builder, builder.getUnknownLoc(),
554+
indirectGotoBlock->getArgument(0), false,
555+
rangeOperands, succesors);
556+
CGM.blockAddressToLabel.clear();
557+
}
558+
531559
void CIRGenFunction::finishFunction(SourceLocation endLoc) {
532560
// CIRGen doesn't use a BreakContinueStack or evaluates OnlySimpleReturnStmts.
533561

@@ -584,15 +612,23 @@ void CIRGenFunction::finishFunction(SourceLocation endLoc) {
584612

585613
// If someone did an indirect goto, emit the indirect goto block at the end of
586614
// the function.
587-
assert(!cir::MissingFeatures::indirectBranch() && "NYI");
588615

616+
// Resolve block address-to-label mappings, then emit the indirect branch
617+
// with the corresponding targets.
618+
resolveBlockAddresses();
619+
finishIndirectBranch();
589620
// If some of our locals escaped, insert a call to llvm.localescape in the
590621
// entry block.
591622
assert(!cir::MissingFeatures::escapedLocals() && "NYI");
592623

593-
// If someone took the address of a label but never did an indirect goto, we
594-
// made a zero entry PHI node, which is illegal, zap it now.
595-
assert(!cir::MissingFeatures::indirectBranch() && "NYI");
624+
// If a label address was taken but no indirect goto was used, we can't remove
625+
// the block argument here. Instead, we mark the 'indirectbr' op
626+
// as poison so that the cleanup can be deferred to lowering, since the
627+
// verifier doesn't allow the 'indirectbr' target address to be null.
628+
if (indirectGotoBlock && indirectGotoBlock->hasNoPredecessors()) {
629+
auto indrBr = cast<cir::IndirectBrOp>(indirectGotoBlock->front());
630+
indrBr.setPoison(true);
631+
}
596632

597633
// CIRGen doesn't need to emit EHResumeBlock, TerminateLandingPad,
598634
// TerminateHandler, UnreachableBlock, TerminateFunclets, NormalCleanupDest
@@ -1965,6 +2001,17 @@ CIRGenFunction::emitArrayLength(const clang::ArrayType *origArrayType,
19652001
return numElements;
19662002
}
19672003

2004+
mlir::Block *CIRGenFunction::getIndirectGotoBlock(cir::BlockAddressOp op) {
2005+
// If we already made the indirect branch for indirect goto, return its block.
2006+
if (indirectGotoBlock)
2007+
return indirectGotoBlock;
2008+
2009+
mlir::OpBuilder::InsertionGuard guard(builder);
2010+
indirectGotoBlock = builder.createBlock(builder.getBlock()->getParent(), {},
2011+
{op.getType()}, {op.getLoc()});
2012+
return indirectGotoBlock;
2013+
}
2014+
19682015
mlir::Value CIRGenFunction::emitAlignmentAssumption(
19692016
mlir::Value ptrValue, QualType ty, SourceLocation loc,
19702017
SourceLocation assumptionLoc, mlir::IntegerAttr alignment,

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,15 @@ class CIRGenFunction : public CIRGenTypeCache {
100100
llvm::DenseMap<const clang::LabelDecl *, JumpDest> LabelMap;
101101
JumpDest &getJumpDestForLabel(const clang::LabelDecl *D);
102102

103+
/// IndirectBranch - The first time an indirect goto is seen we create a block
104+
/// reserved for the indirect branch. Unlike before,the actual 'indirectbr'
105+
/// is emitted at the end of the function, once all block destinations have
106+
/// been resolved.
107+
mlir::Block *indirectGotoBlock = nullptr;
108+
109+
void resolveBlockAddresses();
110+
void finishIndirectBranch();
111+
103112
// ---------------------
104113
// Opaque value handling
105114
// ---------------------
@@ -688,6 +697,8 @@ class CIRGenFunction : public CIRGenTypeCache {
688697

689698
int64_t getAccessedFieldNo(unsigned idx, const mlir::ArrayAttr elts);
690699

700+
mlir::Block *getIndirectGotoBlock(cir::BlockAddressOp op);
701+
691702
void checkTargetFeatures(const CallExpr *E, const FunctionDecl *TargetDecl);
692703
void checkTargetFeatures(SourceLocation Loc, const FunctionDecl *TargetDecl);
693704

@@ -2165,7 +2176,7 @@ class CIRGenFunction : public CIRGenTypeCache {
21652176
mlir::LogicalResult emitFunctionBody(const clang::Stmt *Body);
21662177

21672178
mlir::LogicalResult emitGotoStmt(const clang::GotoStmt &S);
2168-
2179+
mlir::LogicalResult emitIndirectGotoStmt(const IndirectGotoStmt &s);
21692180
/// Emit an if on a boolean condition to the specified blocks.
21702181
/// FIXME: Based on the condition, this might try to simplify the codegen of
21712182
/// the conditional based on the branch. TrueCount should be the number of

clang/lib/CIR/CodeGen/CIRGenModule.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4393,3 +4393,41 @@ CIRGenModule::mergeTBAAInfoForMemoryTransfer(TBAAAccessInfo destInfo,
43934393
return TBAAAccessInfo();
43944394
return tbaa->mergeTBAAInfoForConditionalOperator(destInfo, srcInfo);
43954395
}
4396+
4397+
void CIRGenModule::mapBlockAddress(cir::BlockAddrInfoAttr blockInfo,
4398+
cir::LabelOp label) {
4399+
auto result = blockAddressInfoToLabel.try_emplace(blockInfo, label);
4400+
(void)result;
4401+
assert(result.second &&
4402+
"attempting to map a blockaddress info that is already mapped");
4403+
}
4404+
4405+
void CIRGenModule::mapUnresolvedBlockAddress(cir::BlockAddressOp op) {
4406+
auto result = unresolvedBlockAddressToLabel.insert(op);
4407+
(void)result;
4408+
assert(result.second &&
4409+
"attempting to map a blockaddress operation that is already mapped");
4410+
}
4411+
4412+
void CIRGenModule::mapResolvedBlockAddress(cir::BlockAddressOp op,
4413+
cir::LabelOp label) {
4414+
auto result = blockAddressToLabel.try_emplace(op, label);
4415+
(void)result;
4416+
assert(result.second &&
4417+
"attempting to map a blockaddress operation that is already mapped");
4418+
}
4419+
4420+
void CIRGenModule::updateResolvedBlockAddress(cir::BlockAddressOp op,
4421+
cir::LabelOp newLabel) {
4422+
auto *it = blockAddressToLabel.find(op);
4423+
assert(it != blockAddressToLabel.end() &&
4424+
"trying to update a blockaddress not previously mapped");
4425+
assert(!it->second && "blockaddress already has a resolved label");
4426+
4427+
it->second = newLabel;
4428+
}
4429+
4430+
cir::LabelOp
4431+
CIRGenModule::lookupBlockAddressInfo(cir::BlockAddrInfoAttr blockInfo) {
4432+
return blockAddressInfoToLabel.lookup(blockInfo);
4433+
}

clang/lib/CIR/CodeGen/CIRGenModule.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,24 @@ class CIRGenModule : public CIRGenTypeCache {
206206
/// this if it ends up taking too much memory.
207207
llvm::DenseMap<const clang::FieldDecl *, llvm::StringRef> LambdaFieldToName;
208208

209+
/// Map BlockAddrInfoAttr (function name, label name) to the corresponding CIR
210+
/// LabelOp. This provides the main lookup table used to resolve block
211+
/// addresses into their label operations.
212+
llvm::DenseMap<cir::BlockAddrInfoAttr, cir::LabelOp> blockAddressInfoToLabel;
213+
/// Map CIR BlockAddressOps directly to their resolved LabelOps.
214+
/// Used once a block address has been successfully lowered to a label.
215+
llvm::MapVector<cir::BlockAddressOp, cir::LabelOp> blockAddressToLabel;
216+
/// Track CIR BlockAddressOps that cannot be resolved immediately
217+
/// because their LabelOp has not yet been emitted. These entries
218+
/// are solved later once the corresponding label is available.
219+
llvm::DenseSet<cir::BlockAddressOp> unresolvedBlockAddressToLabel;
220+
cir::LabelOp lookupBlockAddressInfo(cir::BlockAddrInfoAttr blockInfo);
221+
void mapBlockAddress(cir::BlockAddrInfoAttr blockInfo, cir::LabelOp label);
222+
void mapUnresolvedBlockAddress(cir::BlockAddressOp op);
223+
void mapResolvedBlockAddress(cir::BlockAddressOp op, cir::LabelOp);
224+
void updateResolvedBlockAddress(cir::BlockAddressOp op,
225+
cir::LabelOp newLabel);
226+
209227
/// If the declaration has internal linkage but is inside an
210228
/// extern "C" linkage specification, prepare to emit an alias for it
211229
/// to the expected name.

clang/lib/CIR/CodeGen/CIRGenStmt.cpp

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,9 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *S,
194194
return emitOMPTaskyieldDirective(cast<OMPTaskyieldDirective>(*S));
195195
case Stmt::OMPBarrierDirectiveClass:
196196
return emitOMPBarrierDirective(cast<OMPBarrierDirective>(*S));
197-
// Unsupported AST nodes:
198197
case Stmt::IndirectGotoStmtClass:
198+
return emitIndirectGotoStmt(cast<IndirectGotoStmt>(*S));
199+
// Unsupported AST nodes:
199200
case Stmt::CapturedStmtClass:
200201
case Stmt::ObjCAtTryStmtClass:
201202
case Stmt::ObjCAtThrowStmtClass:
@@ -652,13 +653,29 @@ mlir::LogicalResult CIRGenFunction::emitLabel(const LabelDecl *D) {
652653
}
653654

654655
builder.setInsertionPointToEnd(labelBlock);
655-
builder.create<cir::LabelOp>(getLoc(D->getSourceRange()), D->getName());
656+
auto label =
657+
builder.create<cir::LabelOp>(getLoc(D->getSourceRange()), D->getName());
656658
builder.setInsertionPointToEnd(labelBlock);
657-
659+
auto func = cast<cir::FuncOp>(CurFn);
660+
CGM.mapBlockAddress(cir::BlockAddrInfoAttr::get(builder.getContext(),
661+
func.getSymNameAttr(),
662+
label.getLabelAttr()),
663+
label);
658664
// FIXME: emit debug info for labels, incrementProfileCounter
659665
return mlir::success();
660666
}
661667

668+
mlir::LogicalResult
669+
CIRGenFunction::emitIndirectGotoStmt(const IndirectGotoStmt &s) {
670+
auto val = emitScalarExpr(s.getTarget());
671+
assert(indirectGotoBlock &&
672+
"If you jumping to a indirect branch should be alareadye emitted");
673+
cir::BrOp::create(builder, getLoc(s.getSourceRange()), indirectGotoBlock,
674+
val);
675+
builder.createBlock(builder.getBlock()->getParent());
676+
return mlir::success();
677+
}
678+
662679
mlir::LogicalResult
663680
CIRGenFunction::emitContinueStmt(const clang::ContinueStmt &S) {
664681
builder.createContinue(getLoc(S.getContinueLoc()));

clang/lib/CIR/Dialect/IR/CIRDialect.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1848,6 +1848,65 @@ Block *cir::BrCondOp::getSuccessorForOperands(ArrayRef<Attribute> operands) {
18481848
return nullptr;
18491849
}
18501850

1851+
//===----------------------------------------------------------------------===//
1852+
// IndirectBrCondOp
1853+
//===----------------------------------------------------------------------===//
1854+
1855+
mlir::SuccessorOperands
1856+
cir::IndirectBrOp::getSuccessorOperands(unsigned index) {
1857+
assert(index < getNumSuccessors() && "invalid successor index");
1858+
return mlir::SuccessorOperands(getSuccOperandsMutable()[index]);
1859+
}
1860+
1861+
ParseResult parseIndirectBrOpSucessors(
1862+
OpAsmParser &parser, Type &flagType,
1863+
SmallVectorImpl<Block *> &succOperandBlocks,
1864+
SmallVectorImpl<SmallVector<OpAsmParser::UnresolvedOperand>> &succOperands,
1865+
SmallVectorImpl<SmallVector<Type>> &succOperandsTypes) {
1866+
if (failed(parser.parseCommaSeparatedList(
1867+
OpAsmParser::Delimiter::Square,
1868+
[&]() {
1869+
Block *destination = nullptr;
1870+
SmallVector<OpAsmParser::UnresolvedOperand> operands;
1871+
SmallVector<Type> operandTypes;
1872+
1873+
if (parser.parseSuccessor(destination).failed())
1874+
return failure();
1875+
1876+
if (succeeded(parser.parseOptionalLParen())) {
1877+
if (failed(parser.parseOperandList(
1878+
operands, OpAsmParser::Delimiter::None)) ||
1879+
failed(parser.parseColonTypeList(operandTypes)) ||
1880+
failed(parser.parseRParen()))
1881+
return failure();
1882+
}
1883+
succOperandBlocks.push_back(destination);
1884+
succOperands.emplace_back(operands);
1885+
succOperandsTypes.emplace_back(operandTypes);
1886+
return success();
1887+
},
1888+
"successor blocks")))
1889+
return failure();
1890+
return success();
1891+
}
1892+
1893+
void printIndirectBrOpSucessors(OpAsmPrinter &p, cir::IndirectBrOp op,
1894+
Type flagType, SuccessorRange succs,
1895+
OperandRangeRange succOperands,
1896+
const TypeRangeRange &succOperandsTypes) {
1897+
p << "[";
1898+
llvm::interleave(
1899+
llvm::zip(succs, succOperands),
1900+
[&](auto i) {
1901+
p.printNewline();
1902+
p.printSuccessorAndUseList(std::get<0>(i), std::get<1>(i));
1903+
},
1904+
[&] { p << ','; });
1905+
if (!succOperands.empty())
1906+
p.printNewline();
1907+
p << "]";
1908+
}
1909+
18511910
//===----------------------------------------------------------------------===//
18521911
// CaseOp
18531912
//===----------------------------------------------------------------------===//

0 commit comments

Comments
 (0)