Skip to content

Commit 9427a18

Browse files
committed
[CIR] Add support for GNU ifunc attribute
This patch implements support for the GNU indirect function (ifunc) attribute in ClangIR, enabling runtime CPU feature detection and function dispatch. Background: The ifunc attribute is a GNU extension that allows selecting function implementations at runtime based on CPU capabilities. A resolver function is called at program startup to return a pointer to the appropriate implementation. Implementation: - Added cir.func.ifunc operation to CIROps.td to represent ifunc declarations in CIR with a resolver function reference - Implemented emitIFuncDefinition() in CIRGenModule to generate cir.func.ifunc operations from AST IFuncDecls - Extended GetOrCreateCIRFunction() to handle ifunc lookups and create/retrieve IFuncOp operations - Updated ReplaceUsesOfNonProtoTypeWithRealFunction() to support replacing ifunc operations when prototypes change - Modified emitDirectCallee() to allow calls through IFuncOp - Added CallOp verifier support to accept IFuncOp as valid callee - Implemented CIRToLLVMIFuncOpLowering to lower cir.func.ifunc to LLVM dialect IFuncOp, using ptr type for resolver_type parameter The implementation closely follows the original CodeGen approach for generating ifunc declarations, adapted to MLIR's operation-based model. Testing: Added comprehensive test coverage in clang/test/CIR/CodeGen/ifunc.c with three scenarios: 1. Basic ifunc with simple resolver 2. Multiple implementations with function pointer typedef 3. Extern declaration followed by ifunc definition Tests verify: - CIR emission produces correct cir.func.ifunc operations - Direct-to-LLVM lowering generates proper ifunc declarations - Output matches original CodeGen behavior Test Plan: $ build/Release/bin/llvm-lit clang/test/CIR/CodeGen/ifunc.c PASS: Clang :: CIR/CodeGen/ifunc.c (1 of 1) ghstack-source-id: 8c51a5b Pull-Request: #2012
1 parent 62d85f3 commit 9427a18

File tree

10 files changed

+332
-22
lines changed

10 files changed

+332
-22
lines changed

clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,16 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
719719
callingConv, sideEffect, extraFnAttr);
720720
}
721721

722+
cir::CallOp createCallOp(mlir::Location loc, cir::IFuncOp callee,
723+
mlir::ValueRange operands = mlir::ValueRange(),
724+
cir::CallingConv callingConv = cir::CallingConv::C,
725+
cir::SideEffect sideEffect = cir::SideEffect::All,
726+
cir::ExtraFuncAttributesAttr extraFnAttr = {}) {
727+
return createCallOp(loc, mlir::SymbolRefAttr::get(callee),
728+
callee.getFunctionType().getReturnType(), operands,
729+
callingConv, sideEffect, extraFnAttr);
730+
}
731+
722732
cir::CallOp
723733
createIndirectCallOp(mlir::Location loc, mlir::Value ind_target,
724734
cir::FuncType fn_type,
@@ -775,6 +785,17 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
775785
callingConv, sideEffect, extraFnAttr);
776786
}
777787

788+
cir::CallOp
789+
createTryCallOp(mlir::Location loc, cir::IFuncOp callee,
790+
mlir::ValueRange operands,
791+
cir::CallingConv callingConv = cir::CallingConv::C,
792+
cir::SideEffect sideEffect = cir::SideEffect::All,
793+
cir::ExtraFuncAttributesAttr extraFnAttr = {}) {
794+
return createTryCallOp(loc, mlir::SymbolRefAttr::get(callee),
795+
callee.getFunctionType().getReturnType(), operands,
796+
callingConv, sideEffect, extraFnAttr);
797+
}
798+
778799
cir::CallOp
779800
createIndirectTryCallOp(mlir::Location loc, mlir::Value ind_target,
780801
cir::FuncType fn_type, mlir::ValueRange operands,

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2715,6 +2715,53 @@ def CIR_GetGlobalOp : CIR_Op<"get_global", [
27152715
}];
27162716
}
27172717

2718+
def CIR_IFuncOp : CIR_Op<"func.ifunc", [Symbol]> {
2719+
let summary = "Indirect function (ifunc) declaration";
2720+
let description = [{
2721+
The `cir.func.ifunc` operation declares an indirect function, which allows
2722+
runtime selection of function implementations based on CPU features or other
2723+
runtime conditions. The actual function to call is determined by a resolver
2724+
function at runtime.
2725+
2726+
The resolver function must return a pointer to a function with the same
2727+
signature as the ifunc. The resolver typically inspects CPU features or
2728+
other runtime conditions to select the appropriate implementation.
2729+
2730+
This corresponds to the GNU indirect function attribute:
2731+
`__attribute__((ifunc("resolver")))`
2732+
2733+
Example:
2734+
```mlir
2735+
// Resolver function that returns a function pointer
2736+
cir.func internal @resolve_foo() -> !cir.ptr<!cir.func<i32 ()>> {
2737+
...
2738+
cir.return %impl : !cir.ptr<!cir.func<i32 ()>>
2739+
}
2740+
2741+
// IFunc declaration
2742+
cir.func.ifunc @foo resolver(@resolve_foo) : !cir.func<i32 ()>
2743+
2744+
// Usage
2745+
cir.func @use_foo() {
2746+
%result = cir.call @foo() : () -> i32
2747+
cir.return
2748+
}
2749+
```
2750+
}];
2751+
2752+
let arguments = (ins SymbolNameAttr:$sym_name,
2753+
CIR_VisibilityAttr:$global_visibility,
2754+
TypeAttrOf<CIR_FuncType>:$function_type, FlatSymbolRefAttr:$resolver,
2755+
DefaultValuedAttr<CIR_GlobalLinkageKind,
2756+
"GlobalLinkageKind::ExternalLinkage">:$linkage,
2757+
OptionalAttr<StrAttr>:$sym_visibility);
2758+
2759+
let assemblyFormat = [{
2760+
$sym_name `resolver` `(` $resolver `)` attr-dict `:`
2761+
$function_type
2762+
}];
2763+
}
2764+
27182765
//===----------------------------------------------------------------------===//
27192766
// VTableAddrPointOp
27202767
//===----------------------------------------------------------------------===//

clang/lib/CIR/CodeGen/CIRGenCall.cpp

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ void CIRGenModule::constructAttributeList(
321321
static cir::CIRCallOpInterface
322322
emitCallLikeOp(CIRGenFunction &CGF, mlir::Location callLoc,
323323
cir::FuncType indirectFuncTy, mlir::Value indirectFuncVal,
324-
cir::FuncOp directFuncOp,
324+
mlir::Operation *directCalleeOp,
325325
SmallVectorImpl<mlir::Value> &CIRCallArgs, bool isInvoke,
326326
cir::CallingConv callingConv, cir::SideEffect sideEffect,
327327
cir::ExtraFuncAttributesAttr extraFnAttrs) {
@@ -378,9 +378,13 @@ emitCallLikeOp(CIRGenFunction &CGF, mlir::Location callLoc,
378378
callOpWithExceptions = builder.createIndirectTryCallOp(
379379
callLoc, indirectFuncVal, indirectFuncTy, CIRCallArgs, callingConv,
380380
sideEffect);
381+
} else if (auto funcOp = mlir::dyn_cast<cir::FuncOp>(directCalleeOp)) {
382+
callOpWithExceptions = builder.createTryCallOp(
383+
callLoc, funcOp, CIRCallArgs, callingConv, sideEffect);
381384
} else {
385+
auto ifuncOp = mlir::cast<cir::IFuncOp>(directCalleeOp);
382386
callOpWithExceptions = builder.createTryCallOp(
383-
callLoc, directFuncOp, CIRCallArgs, callingConv, sideEffect);
387+
callLoc, ifuncOp, CIRCallArgs, callingConv, sideEffect);
384388
}
385389
callOpWithExceptions->setAttr("extra_attrs", extraFnAttrs);
386390
CGF.mayThrow = true;
@@ -405,7 +409,12 @@ emitCallLikeOp(CIRGenFunction &CGF, mlir::Location callLoc,
405409
callLoc, indirectFuncVal, indirectFuncTy, CIRCallArgs,
406410
cir::CallingConv::C, sideEffect, extraFnAttrs);
407411
}
408-
return builder.createCallOp(callLoc, directFuncOp, CIRCallArgs, callingConv,
412+
if (auto funcOp = mlir::dyn_cast<cir::FuncOp>(directCalleeOp)) {
413+
return builder.createCallOp(callLoc, funcOp, CIRCallArgs, callingConv,
414+
sideEffect, extraFnAttrs);
415+
}
416+
auto ifuncOp = mlir::cast<cir::IFuncOp>(directCalleeOp);
417+
return builder.createCallOp(callLoc, ifuncOp, CIRCallArgs, callingConv,
409418
sideEffect, extraFnAttrs);
410419
}
411420

@@ -620,19 +629,21 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &CallInfo,
620629
cir::CIRCallOpInterface theCall = [&]() {
621630
cir::FuncType indirectFuncTy;
622631
mlir::Value indirectFuncVal;
623-
cir::FuncOp directFuncOp;
632+
mlir::Operation *directCalleeOp = nullptr;
624633

625634
if (auto fnOp = dyn_cast<cir::FuncOp>(CalleePtr)) {
626-
directFuncOp = fnOp;
635+
directCalleeOp = fnOp;
636+
} else if (auto ifuncOp = dyn_cast<cir::IFuncOp>(CalleePtr)) {
637+
directCalleeOp = ifuncOp;
627638
} else if (auto getGlobalOp = dyn_cast<cir::GetGlobalOp>(CalleePtr)) {
628639
// FIXME(cir): This peephole optimization to avoids indirect calls for
629640
// builtins. This should be fixed in the builting declaration instead by
630641
// not emitting an unecessary get_global in the first place.
631642
auto *globalOp = mlir::SymbolTable::lookupSymbolIn(CGM.getModule(),
632643
getGlobalOp.getName());
633644
assert(getGlobalOp && "undefined global function");
634-
directFuncOp = llvm::dyn_cast<cir::FuncOp>(globalOp);
635-
assert(directFuncOp && "operation is not a function");
645+
directCalleeOp = llvm::dyn_cast<cir::FuncOp>(globalOp);
646+
assert(directCalleeOp && "operation is not a function");
636647
} else {
637648
[[maybe_unused]] auto resultTypes = CalleePtr->getResultTypes();
638649
[[maybe_unused]] auto FuncPtrTy =
@@ -648,7 +659,7 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &CallInfo,
648659
Attrs.getDictionary(&getMLIRContext()));
649660

650661
cir::CIRCallOpInterface callLikeOp = emitCallLikeOp(
651-
*this, callLoc, indirectFuncTy, indirectFuncVal, directFuncOp,
662+
*this, callLoc, indirectFuncTy, indirectFuncVal, directCalleeOp,
652663
CIRCallArgs, isInvoke, callingConv, sideEffect, extraFnAttrs);
653664

654665
if (E)

clang/lib/CIR/CodeGen/CIRGenExpr.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,15 @@ static CIRGenCallee emitDirectCallee(CIRGenModule &CGM, GlobalDecl GD) {
553553
return CIRGenCallee::forBuiltin(builtinID, FD);
554554
}
555555

556+
// Handle ifunc specially - get the IFuncOp directly
557+
if (FD->hasAttr<IFuncAttr>()) {
558+
llvm::StringRef mangledName = CGM.getMangledName(GD);
559+
mlir::Operation *ifuncOp = CGM.getGlobalValue(mangledName);
560+
assert(ifuncOp && isa<cir::IFuncOp>(ifuncOp) &&
561+
"Expected IFuncOp for ifunc");
562+
return CIRGenCallee::forDirect(ifuncOp, GD);
563+
}
564+
556565
mlir::Operation *CalleePtr = emitFunctionDeclPointer(CGM, GD);
557566

558567
if ((CGM.getLangOpts().HIP || CGM.getLangOpts().CUDA) &&

clang/lib/CIR/CodeGen/CIRGenModule.cpp

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,10 @@ void CIRGenModule::emitGlobal(GlobalDecl gd) {
598598

599599
const auto *global = cast<ValueDecl>(gd.getDecl());
600600

601-
assert(!global->hasAttr<IFuncAttr>() && "NYI");
601+
// IFunc like an alias whose value is resolved at runtime by calling resolver.
602+
if (global->hasAttr<IFuncAttr>())
603+
return emitIFuncDefinition(gd);
604+
602605
assert(!global->hasAttr<CPUDispatchAttr>() && "NYI");
603606

604607
if (langOpts.CUDA || langOpts.HIP) {
@@ -722,6 +725,77 @@ void CIRGenModule::emitGlobal(GlobalDecl gd) {
722725
}
723726
}
724727

728+
void CIRGenModule::emitIFuncDefinition(GlobalDecl globalDecl) {
729+
const auto *d = cast<FunctionDecl>(globalDecl.getDecl());
730+
const IFuncAttr *ifa = d->getAttr<IFuncAttr>();
731+
assert(ifa && "Not an ifunc?");
732+
733+
llvm::StringRef mangledName = getMangledName(globalDecl);
734+
735+
if (ifa->getResolver() == mangledName) {
736+
getDiags().Report(ifa->getLocation(), diag::err_cyclic_alias) << 1;
737+
return;
738+
}
739+
740+
// Get function type for the ifunc.
741+
mlir::Type declTy = getTypes().convertTypeForMem(d->getType());
742+
auto funcTy = mlir::dyn_cast<cir::FuncType>(declTy);
743+
assert(funcTy && "IFunc must have function type");
744+
745+
// The resolver might not be visited yet. Create a forward declaration for it.
746+
mlir::Type resolverRetTy = builder.getPointerTo(funcTy);
747+
auto resolverFuncTy =
748+
cir::FuncType::get(llvm::ArrayRef<mlir::Type>{}, resolverRetTy);
749+
750+
// Ensure the resolver function is created.
751+
GetOrCreateCIRFunction(ifa->getResolver(), resolverFuncTy, GlobalDecl(),
752+
/*ForVTable=*/false);
753+
754+
mlir::OpBuilder::InsertionGuard guard(builder);
755+
builder.setInsertionPointToStart(theModule.getBody());
756+
757+
// Report an error if some definition overrides ifunc.
758+
mlir::Operation *entry = getGlobalValue(mangledName);
759+
if (entry) {
760+
// Check if this is a non-declaration (an actual definition).
761+
bool isDeclaration = false;
762+
if (auto func = mlir::dyn_cast<cir::FuncOp>(entry))
763+
isDeclaration = func.isDeclaration();
764+
765+
if (!isDeclaration) {
766+
GlobalDecl otherGd;
767+
if (lookupRepresentativeDecl(mangledName, otherGd) &&
768+
DiagnosedConflictingDefinitions.insert(globalDecl).second) {
769+
getDiags().Report(d->getLocation(), diag::err_duplicate_mangled_name)
770+
<< mangledName;
771+
getDiags().Report(otherGd.getDecl()->getLocation(),
772+
diag::note_previous_definition);
773+
}
774+
return;
775+
}
776+
777+
// This is just a forward declaration, remove it.
778+
if (auto func = mlir::dyn_cast<cir::FuncOp>(entry)) {
779+
func.erase();
780+
}
781+
}
782+
783+
// Get linkage
784+
GVALinkage linkage = astContext.GetGVALinkageForFunction(d);
785+
cir::GlobalLinkageKind cirLinkage =
786+
getCIRLinkageForDeclarator(d, linkage, /*IsConstantVariable=*/false);
787+
788+
// Get visibility
789+
cir::VisibilityAttr visibilityAttr = getGlobalVisibilityAttrFromDecl(d);
790+
cir::VisibilityKind visibilityKind = visibilityAttr.getValue();
791+
792+
auto ifuncOp = builder.create<cir::IFuncOp>(
793+
theModule.getLoc(), mangledName, visibilityKind, funcTy,
794+
ifa->getResolver(), cirLinkage, /*sym_visibility=*/mlir::StringAttr{});
795+
796+
setCommonAttributes(globalDecl, ifuncOp);
797+
}
798+
725799
void CIRGenModule::emitGlobalFunctionDefinition(GlobalDecl gd,
726800
mlir::Operation *op) {
727801
auto const *d = cast<FunctionDecl>(gd.getDecl());
@@ -2424,6 +2498,10 @@ void CIRGenModule::ReplaceUsesOfNonProtoTypeWithRealFunction(
24242498
// Replace type
24252499
getGlobalOp.getAddr().setType(
24262500
cir::PointerType::get(newFn.getFunctionType()));
2501+
} else if (auto ifuncOp = dyn_cast<cir::IFuncOp>(use.getUser())) {
2502+
// IFuncOp references the resolver function by symbol.
2503+
// The symbol reference doesn't need updating - it's name-based.
2504+
// The resolver's signature is validated when the IFuncOp is created.
24272505
} else {
24282506
llvm_unreachable("NIY");
24292507
}
@@ -3111,6 +3189,11 @@ cir::FuncOp CIRGenModule::GetOrCreateCIRFunction(
31113189
// Lookup the entry, lazily creating it if necessary.
31123190
mlir::Operation *entry = getGlobalValue(mangledName);
31133191
if (entry) {
3192+
// If this is an ifunc, we can't create a FuncOp for it. Just return nullptr
3193+
// and let the caller handle calling through the ifunc.
3194+
if (isa<cir::IFuncOp>(entry))
3195+
return nullptr;
3196+
31143197
assert(isa<cir::FuncOp>(entry) &&
31153198
"not implemented, only supports FuncOp for now");
31163199

clang/lib/CIR/CodeGen/CIRGenModule.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,7 @@ class CIRGenModule : public CIRGenTypeCache {
773773
cir::FuncOp func);
774774

775775
void emitGlobalDefinition(clang::GlobalDecl D, mlir::Operation *Op = nullptr);
776+
void emitIFuncDefinition(clang::GlobalDecl globalDecl);
776777
void emitGlobalFunctionDefinition(clang::GlobalDecl D, mlir::Operation *Op);
777778
void emitGlobalVarDefinition(const clang::VarDecl *D,
778779
bool IsTentative = false);

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

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -360,8 +360,8 @@ void cir::ConditionOp::getSuccessorRegions(
360360
regions.emplace_back(&await.getSuspend(), await.getSuspend().getArguments());
361361
}
362362

363-
MutableOperandRange cir::ConditionOp::getMutableSuccessorOperands(
364-
RegionSuccessor /*successor*/) {
363+
MutableOperandRange
364+
cir::ConditionOp::getMutableSuccessorOperands(RegionSuccessor /*successor*/) {
365365
// No values are yielded to the successor region.
366366
return MutableOperandRange(getOperation(), 0, 0);
367367
}
@@ -1525,8 +1525,7 @@ void cir::ScopeOp::getSuccessorRegions(
15251525
mlir::RegionBranchPoint point, SmallVectorImpl<RegionSuccessor> &regions) {
15261526
// The only region always branch back to the parent operation.
15271527
if (!point.isParent()) {
1528-
regions.push_back(
1529-
RegionSuccessor(getOperation(), this->getODSResults(0)));
1528+
regions.push_back(RegionSuccessor(getOperation(), this->getODSResults(0)));
15301529
return;
15311530
}
15321531

@@ -1787,8 +1786,8 @@ void cir::TernaryOp::build(
17871786
// YieldOp
17881787
//===----------------------------------------------------------------------===//
17891788

1790-
MutableOperandRange cir::YieldOp::getMutableSuccessorOperands(
1791-
RegionSuccessor successor) {
1789+
MutableOperandRange
1790+
cir::YieldOp::getMutableSuccessorOperands(RegionSuccessor successor) {
17921791
Operation *op = getOperation();
17931792
if (auto loop = dyn_cast<LoopOpInterface>(op->getParentOp())) {
17941793
if (op->getParentRegion() == &loop.getCond())
@@ -3113,17 +3112,26 @@ verifyCallCommInSymbolUses(Operation *op, SymbolTableCollection &symbolTable) {
31133112
if (!fnAttr)
31143113
return success();
31153114

3115+
// Look up the callee - it can be either a FuncOp or an IFuncOp
31163116
cir::FuncOp fn = symbolTable.lookupNearestSymbolFrom<cir::FuncOp>(op, fnAttr);
3117-
if (!fn)
3117+
cir::IFuncOp ifn =
3118+
symbolTable.lookupNearestSymbolFrom<cir::IFuncOp>(op, fnAttr);
3119+
3120+
if (!fn && !ifn)
31183121
return op->emitOpError() << "'" << fnAttr.getValue()
31193122
<< "' does not reference a valid function";
3123+
31203124
auto callIf = dyn_cast<cir::CIRCallOpInterface>(op);
31213125
assert(callIf && "expected CIR call interface to be always available");
31223126

3127+
// Get function type from either FuncOp or IFuncOp
3128+
cir::FuncType fnType = fn ? fn.getFunctionType() : ifn.getFunctionType();
3129+
31233130
// Verify that the operand and result types match the callee. Note that
31243131
// argument-checking is disabled for functions without a prototype.
3125-
auto fnType = fn.getFunctionType();
3126-
if (!fn.getNoProto()) {
3132+
// IFuncs are always considered to have a prototype.
3133+
bool hasProto = ifn || !fn.getNoProto();
3134+
if (hasProto) {
31273135
unsigned numCallOperands = callIf.getNumArgOperands();
31283136
unsigned numFnOpOperands = fnType.getNumInputs();
31293137

@@ -3140,8 +3148,9 @@ verifyCallCommInSymbolUses(Operation *op, SymbolTableCollection &symbolTable) {
31403148
<< op->getOperand(i).getType() << " for operand number " << i;
31413149
}
31423150

3143-
// Calling convention must match.
3144-
if (callIf.getCallingConv() != fn.getCallingConv())
3151+
// Calling convention must match (only check for FuncOp; IFuncOp uses the
3152+
// type's convention)
3153+
if (fn && callIf.getCallingConv() != fn.getCallingConv())
31453154
return op->emitOpError("calling convention mismatch: expected ")
31463155
<< stringifyCallingConv(fn.getCallingConv()) << ", but provided "
31473156
<< stringifyCallingConv(callIf.getCallingConv());

0 commit comments

Comments
 (0)