Skip to content

Commit a8f3760

Browse files
Andres-Salamancalanza
authored andcommitted
[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 80dd7f0 commit a8f3760

File tree

14 files changed

+514
-46
lines changed

14 files changed

+514
-46
lines changed

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

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2297,6 +2297,67 @@ def CIR_BrCondOp : CIR_Op<"brcond", [
22972297
}];
22982298
}
22992299

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

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
@@ -529,6 +529,34 @@ cir::TryOp CIRGenFunction::LexicalScope::getClosestTryParent() {
529529
return nullptr;
530530
}
531531

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

@@ -585,15 +613,23 @@ void CIRGenFunction::finishFunction(SourceLocation endLoc) {
585613

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

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

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

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

2003+
mlir::Block *CIRGenFunction::getIndirectGotoBlock(cir::BlockAddressOp op) {
2004+
// If we already made the indirect branch for indirect goto, return its block.
2005+
if (indirectGotoBlock)
2006+
return indirectGotoBlock;
2007+
2008+
mlir::OpBuilder::InsertionGuard guard(builder);
2009+
indirectGotoBlock = builder.createBlock(builder.getBlock()->getParent(), {},
2010+
{op.getType()}, {op.getLoc()});
2011+
return indirectGotoBlock;
2012+
}
2013+
19672014
mlir::Value CIRGenFunction::emitAlignmentAssumption(
19682015
mlir::Value ptrValue, QualType ty, SourceLocation loc,
19692016
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
@@ -4405,3 +4405,41 @@ CIRGenModule::mergeTBAAInfoForMemoryTransfer(TBAAAccessInfo destInfo,
44054405
return TBAAAccessInfo();
44064406
return tbaa->mergeTBAAInfoForConditionalOperator(destInfo, srcInfo);
44074407
}
4408+
4409+
void CIRGenModule::mapBlockAddress(cir::BlockAddrInfoAttr blockInfo,
4410+
cir::LabelOp label) {
4411+
auto result = blockAddressInfoToLabel.try_emplace(blockInfo, label);
4412+
(void)result;
4413+
assert(result.second &&
4414+
"attempting to map a blockaddress info that is already mapped");
4415+
}
4416+
4417+
void CIRGenModule::mapUnresolvedBlockAddress(cir::BlockAddressOp op) {
4418+
auto result = unresolvedBlockAddressToLabel.insert(op);
4419+
(void)result;
4420+
assert(result.second &&
4421+
"attempting to map a blockaddress operation that is already mapped");
4422+
}
4423+
4424+
void CIRGenModule::mapResolvedBlockAddress(cir::BlockAddressOp op,
4425+
cir::LabelOp label) {
4426+
auto result = blockAddressToLabel.try_emplace(op, label);
4427+
(void)result;
4428+
assert(result.second &&
4429+
"attempting to map a blockaddress operation that is already mapped");
4430+
}
4431+
4432+
void CIRGenModule::updateResolvedBlockAddress(cir::BlockAddressOp op,
4433+
cir::LabelOp newLabel) {
4434+
auto *it = blockAddressToLabel.find(op);
4435+
assert(it != blockAddressToLabel.end() &&
4436+
"trying to update a blockaddress not previously mapped");
4437+
assert(!it->second && "blockaddress already has a resolved label");
4438+
4439+
it->second = newLabel;
4440+
}
4441+
4442+
cir::LabelOp
4443+
CIRGenModule::lookupBlockAddressInfo(cir::BlockAddrInfoAttr blockInfo) {
4444+
return blockAddressInfoToLabel.lookup(blockInfo);
4445+
}

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: 22 additions & 5 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,15 +653,31 @@ 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

662-
mlir::LogicalResult CIRGenFunction::emitContinueStmt(
663-
const clang::ContinueStmt &S) {
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+
679+
mlir::LogicalResult
680+
CIRGenFunction::emitContinueStmt(const clang::ContinueStmt &S) {
664681
builder.createContinue(getLoc(S.getBeginLoc()));
665682

666683
// Insert the new block to continue codegen after the continue statement.

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

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

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

0 commit comments

Comments
 (0)