Skip to content

Commit d752485

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 26ae7b1 commit d752485

File tree

10 files changed

+330
-16
lines changed

10 files changed

+330
-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
@@ -763,6 +763,16 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
763763
callingConv, sideEffect, extraFnAttr);
764764
}
765765

766+
cir::CallOp createCallOp(mlir::Location loc, cir::IFuncOp callee,
767+
mlir::ValueRange operands = mlir::ValueRange(),
768+
cir::CallingConv callingConv = cir::CallingConv::C,
769+
cir::SideEffect sideEffect = cir::SideEffect::All,
770+
cir::ExtraFuncAttributesAttr extraFnAttr = {}) {
771+
return createCallOp(loc, mlir::SymbolRefAttr::get(callee),
772+
callee.getFunctionType().getReturnType(), operands,
773+
callingConv, sideEffect, extraFnAttr);
774+
}
775+
766776
cir::CallOp
767777
createIndirectCallOp(mlir::Location loc, mlir::Value ind_target,
768778
cir::FuncType fn_type,
@@ -819,6 +829,17 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
819829
callingConv, sideEffect, extraFnAttr);
820830
}
821831

832+
cir::CallOp
833+
createTryCallOp(mlir::Location loc, cir::IFuncOp callee,
834+
mlir::ValueRange operands,
835+
cir::CallingConv callingConv = cir::CallingConv::C,
836+
cir::SideEffect sideEffect = cir::SideEffect::All,
837+
cir::ExtraFuncAttributesAttr extraFnAttr = {}) {
838+
return createTryCallOp(loc, mlir::SymbolRefAttr::get(callee),
839+
callee.getFunctionType().getReturnType(), operands,
840+
callingConv, sideEffect, extraFnAttr);
841+
}
842+
822843
cir::CallOp
823844
createIndirectTryCallOp(mlir::Location loc, mlir::Value ind_target,
824845
cir::FuncType fn_type, mlir::ValueRange operands,

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2610,6 +2610,56 @@ def CIR_GetGlobalOp
26102610
}];
26112611
}
26122612

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

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
@@ -577,6 +577,15 @@ static CIRGenCallee emitDirectCallee(CIRGenModule &CGM, GlobalDecl GD) {
577577
return CIRGenCallee::forBuiltin(builtinID, FD);
578578
}
579579

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

582591
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
@@ -600,7 +600,10 @@ void CIRGenModule::emitGlobal(GlobalDecl gd) {
600600

601601
const auto *global = cast<ValueDecl>(gd.getDecl());
602602

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

606609
if (langOpts.CUDA || langOpts.HIP) {
@@ -724,6 +727,77 @@ void CIRGenModule::emitGlobal(GlobalDecl gd) {
724727
}
725728
}
726729

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

clang/lib/CIR/CodeGen/CIRGenModule.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,7 @@ class CIRGenModule : public CIRGenTypeCache {
816816
void setKCFIType(const FunctionDecl *fd, cir::FuncOp func);
817817

818818
void emitGlobalDefinition(clang::GlobalDecl D, mlir::Operation *Op = nullptr);
819+
void emitIFuncDefinition(clang::GlobalDecl globalDecl);
819820
void emitGlobalFunctionDefinition(clang::GlobalDecl D, mlir::Operation *Op);
820821
void emitGlobalVarDefinition(const clang::VarDecl *D,
821822
bool IsTentative = false);

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

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

3166+
// Look up the callee - it can be either a FuncOp or an IFuncOp
31663167
cir::FuncOp fn = symbolTable.lookupNearestSymbolFrom<cir::FuncOp>(op, fnAttr);
3167-
if (!fn)
3168+
cir::IFuncOp ifn =
3169+
symbolTable.lookupNearestSymbolFrom<cir::IFuncOp>(op, fnAttr);
3170+
3171+
if (!fn && !ifn)
31683172
return op->emitOpError() << "'" << fnAttr.getValue()
31693173
<< "' does not reference a valid function";
3174+
31703175
auto callIf = dyn_cast<cir::CIRCallOpInterface>(op);
31713176
assert(callIf && "expected CIR call interface to be always available");
31723177

3178+
// Get function type from either FuncOp or IFuncOp
3179+
cir::FuncType fnType = fn ? fn.getFunctionType() : ifn.getFunctionType();
3180+
31733181
// Verify that the operand and result types match the callee. Note that
31743182
// argument-checking is disabled for functions without a prototype.
3175-
auto fnType = fn.getFunctionType();
3176-
if (!fn.getNoProto()) {
3183+
// IFuncs are always considered to have a prototype.
3184+
bool hasProto = ifn || !fn.getNoProto();
3185+
if (hasProto) {
31773186
unsigned numCallOperands = callIf.getNumArgOperands();
31783187
unsigned numFnOpOperands = fnType.getNumInputs();
31793188

@@ -3190,8 +3199,9 @@ verifyCallCommInSymbolUses(Operation *op, SymbolTableCollection &symbolTable) {
31903199
<< op->getOperand(i).getType() << " for operand number " << i;
31913200
}
31923201

3193-
// Calling convention must match.
3194-
if (callIf.getCallingConv() != fn.getCallingConv())
3202+
// Calling convention must match (only check for FuncOp; IFuncOp uses the
3203+
// type's convention)
3204+
if (fn && callIf.getCallingConv() != fn.getCallingConv())
31953205
return op->emitOpError("calling convention mismatch: expected ")
31963206
<< stringifyCallingConv(fn.getCallingConv()) << ", but provided "
31973207
<< stringifyCallingConv(callIf.getCallingConv());

0 commit comments

Comments
 (0)