Skip to content

Commit f55fe88

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 723365b commit f55fe88

File tree

10 files changed

+327
-16
lines changed

10 files changed

+327
-16
lines changed

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,16 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
749749
callingConv, sideEffect, extraFnAttr);
750750
}
751751

752+
cir::CallOp createCallOp(mlir::Location loc, cir::IFuncOp callee,
753+
mlir::ValueRange operands = mlir::ValueRange(),
754+
cir::CallingConv callingConv = cir::CallingConv::C,
755+
cir::SideEffect sideEffect = cir::SideEffect::All,
756+
cir::ExtraFuncAttributesAttr extraFnAttr = {}) {
757+
return createCallOp(loc, mlir::SymbolRefAttr::get(callee),
758+
callee.getFunctionType().getReturnType(), operands,
759+
callingConv, sideEffect, extraFnAttr);
760+
}
761+
752762
cir::CallOp
753763
createIndirectCallOp(mlir::Location loc, mlir::Value ind_target,
754764
cir::FuncType fn_type,
@@ -805,6 +815,17 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
805815
callingConv, sideEffect, extraFnAttr);
806816
}
807817

818+
cir::CallOp
819+
createTryCallOp(mlir::Location loc, cir::IFuncOp callee,
820+
mlir::ValueRange operands,
821+
cir::CallingConv callingConv = cir::CallingConv::C,
822+
cir::SideEffect sideEffect = cir::SideEffect::All,
823+
cir::ExtraFuncAttributesAttr extraFnAttr = {}) {
824+
return createTryCallOp(loc, mlir::SymbolRefAttr::get(callee),
825+
callee.getFunctionType().getReturnType(), operands,
826+
callingConv, sideEffect, extraFnAttr);
827+
}
828+
808829
cir::CallOp
809830
createIndirectTryCallOp(mlir::Location loc, mlir::Value ind_target,
810831
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
@@ -2598,6 +2598,53 @@ def CIR_GetGlobalOp
25982598
}];
25992599
}
26002600

2601+
def CIR_IFuncOp : CIR_Op<"func.ifunc", [Symbol]> {
2602+
let summary = "Indirect function (ifunc) declaration";
2603+
let description = [{
2604+
The `cir.func.ifunc` operation declares an indirect function, which allows
2605+
runtime selection of function implementations based on CPU features or other
2606+
runtime conditions. The actual function to call is determined by a resolver
2607+
function at runtime.
2608+
2609+
The resolver function must return a pointer to a function with the same
2610+
signature as the ifunc. The resolver typically inspects CPU features or
2611+
other runtime conditions to select the appropriate implementation.
2612+
2613+
This corresponds to the GNU indirect function attribute:
2614+
`__attribute__((ifunc("resolver")))`
2615+
2616+
Example:
2617+
```mlir
2618+
// Resolver function that returns a function pointer
2619+
cir.func internal @resolve_foo() -> !cir.ptr<!cir.func<i32 ()>> {
2620+
...
2621+
cir.return %impl : !cir.ptr<!cir.func<i32 ()>>
2622+
}
2623+
2624+
// IFunc declaration
2625+
cir.func.ifunc @foo resolver(@resolve_foo) : !cir.func<i32 ()>
2626+
2627+
// Usage
2628+
cir.func @use_foo() {
2629+
%result = cir.call @foo() : () -> i32
2630+
cir.return
2631+
}
2632+
```
2633+
}];
2634+
2635+
let arguments = (ins SymbolNameAttr:$sym_name,
2636+
CIR_VisibilityAttr:$global_visibility,
2637+
TypeAttrOf<CIR_FuncType>:$function_type, FlatSymbolRefAttr:$resolver,
2638+
DefaultValuedAttr<CIR_GlobalLinkageKind,
2639+
"GlobalLinkageKind::ExternalLinkage">:$linkage,
2640+
OptionalAttr<StrAttr>:$sym_visibility);
2641+
2642+
let assemblyFormat = [{
2643+
$sym_name `resolver` `(` $resolver `)` attr-dict `:`
2644+
$function_type
2645+
}];
2646+
}
2647+
26012648
//===----------------------------------------------------------------------===//
26022649
// GuardedInitOp
26032650
//===----------------------------------------------------------------------===//

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

@@ -621,19 +630,21 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &CallInfo,
621630
cir::CIRCallOpInterface theCall = [&]() {
622631
cir::FuncType indirectFuncTy;
623632
mlir::Value indirectFuncVal;
624-
cir::FuncOp directFuncOp;
633+
mlir::Operation *directCalleeOp = nullptr;
625634

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

651662
cir::CIRCallOpInterface callLikeOp = emitCallLikeOp(
652-
*this, callLoc, indirectFuncTy, indirectFuncVal, directFuncOp,
663+
*this, callLoc, indirectFuncTy, indirectFuncVal, directCalleeOp,
653664
CIRCallArgs, isInvoke, callingConv, sideEffect, extraFnAttrs);
654665

655666
if (E)

clang/lib/CIR/CodeGen/CIRGenExpr.cpp

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

579+
// Handle ifunc specially - get the IFuncOp directly
580+
if (FD->hasAttr<IFuncAttr>()) {
581+
llvm::StringRef mangledName = CGM.getMangledName(GD);
582+
mlir::Operation *ifuncOp = CGM.getGlobalValue(mangledName);
583+
assert(ifuncOp && isa<cir::IFuncOp>(ifuncOp) &&
584+
"Expected IFuncOp for ifunc");
585+
return CIRGenCallee::forDirect(ifuncOp, GD);
586+
}
587+
579588
mlir::Operation *CalleePtr = emitFunctionDeclPointer(CGM, GD);
580589

581590
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());
@@ -2432,6 +2506,10 @@ void CIRGenModule::ReplaceUsesOfNonProtoTypeWithRealFunction(
24322506
// Replace type
24332507
getGlobalOp.getAddr().setType(
24342508
cir::PointerType::get(newFn.getFunctionType()));
2509+
} else if (auto ifuncOp = dyn_cast<cir::IFuncOp>(use.getUser())) {
2510+
// IFuncOp references the resolver function by symbol.
2511+
// The symbol reference doesn't need updating - it's name-based.
2512+
// The resolver's signature is validated when the IFuncOp is created.
24352513
} else {
24362514
llvm_unreachable("NIY");
24372515
}
@@ -3145,6 +3223,11 @@ cir::FuncOp CIRGenModule::GetOrCreateCIRFunction(
31453223
// Lookup the entry, lazily creating it if necessary.
31463224
mlir::Operation *entry = getGlobalValue(mangledName);
31473225
if (entry) {
3226+
// If this is an ifunc, we can't create a FuncOp for it. Just return nullptr
3227+
// and let the caller handle calling through the ifunc.
3228+
if (isa<cir::IFuncOp>(entry))
3229+
return nullptr;
3230+
31483231
assert(isa<cir::FuncOp>(entry) &&
31493232
"not implemented, only supports FuncOp for now");
31503233

clang/lib/CIR/CodeGen/CIRGenModule.h

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

785785
void emitGlobalDefinition(clang::GlobalDecl D, mlir::Operation *Op = nullptr);
786+
void emitIFuncDefinition(clang::GlobalDecl globalDecl);
786787
void emitGlobalFunctionDefinition(clang::GlobalDecl D, mlir::Operation *Op);
787788
void emitGlobalVarDefinition(const clang::VarDecl *D,
788789
bool IsTentative = false);

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3153,17 +3153,26 @@ verifyCallCommInSymbolUses(Operation *op, SymbolTableCollection &symbolTable) {
31533153
if (!fnAttr)
31543154
return success();
31553155

3156+
// Look up the callee - it can be either a FuncOp or an IFuncOp
31563157
cir::FuncOp fn = symbolTable.lookupNearestSymbolFrom<cir::FuncOp>(op, fnAttr);
3157-
if (!fn)
3158+
cir::IFuncOp ifn =
3159+
symbolTable.lookupNearestSymbolFrom<cir::IFuncOp>(op, fnAttr);
3160+
3161+
if (!fn && !ifn)
31583162
return op->emitOpError() << "'" << fnAttr.getValue()
31593163
<< "' does not reference a valid function";
3164+
31603165
auto callIf = dyn_cast<cir::CIRCallOpInterface>(op);
31613166
assert(callIf && "expected CIR call interface to be always available");
31623167

3168+
// Get function type from either FuncOp or IFuncOp
3169+
cir::FuncType fnType = fn ? fn.getFunctionType() : ifn.getFunctionType();
3170+
31633171
// Verify that the operand and result types match the callee. Note that
31643172
// argument-checking is disabled for functions without a prototype.
3165-
auto fnType = fn.getFunctionType();
3166-
if (!fn.getNoProto()) {
3173+
// IFuncs are always considered to have a prototype.
3174+
bool hasProto = ifn || !fn.getNoProto();
3175+
if (hasProto) {
31673176
unsigned numCallOperands = callIf.getNumArgOperands();
31683177
unsigned numFnOpOperands = fnType.getNumInputs();
31693178

@@ -3180,8 +3189,9 @@ verifyCallCommInSymbolUses(Operation *op, SymbolTableCollection &symbolTable) {
31803189
<< op->getOperand(i).getType() << " for operand number " << i;
31813190
}
31823191

3183-
// Calling convention must match.
3184-
if (callIf.getCallingConv() != fn.getCallingConv())
3192+
// Calling convention must match (only check for FuncOp; IFuncOp uses the
3193+
// type's convention)
3194+
if (fn && callIf.getCallingConv() != fn.getCallingConv())
31853195
return op->emitOpError("calling convention mismatch: expected ")
31863196
<< stringifyCallingConv(fn.getCallingConv()) << ", but provided "
31873197
<< stringifyCallingConv(callIf.getCallingConv());

0 commit comments

Comments
 (0)