diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h index a533bea6f1e6..17b0ccc0279b 100644 --- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h +++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h @@ -719,6 +719,16 @@ class CIRBaseBuilderTy : public mlir::OpBuilder { callingConv, sideEffect, extraFnAttr); } + cir::CallOp createCallOp(mlir::Location loc, cir::IFuncOp callee, + mlir::ValueRange operands = mlir::ValueRange(), + cir::CallingConv callingConv = cir::CallingConv::C, + cir::SideEffect sideEffect = cir::SideEffect::All, + cir::ExtraFuncAttributesAttr extraFnAttr = {}) { + return createCallOp(loc, mlir::SymbolRefAttr::get(callee), + callee.getFunctionType().getReturnType(), operands, + callingConv, sideEffect, extraFnAttr); + } + cir::CallOp createIndirectCallOp(mlir::Location loc, mlir::Value ind_target, cir::FuncType fn_type, @@ -775,6 +785,17 @@ class CIRBaseBuilderTy : public mlir::OpBuilder { callingConv, sideEffect, extraFnAttr); } + cir::CallOp + createTryCallOp(mlir::Location loc, cir::IFuncOp callee, + mlir::ValueRange operands, + cir::CallingConv callingConv = cir::CallingConv::C, + cir::SideEffect sideEffect = cir::SideEffect::All, + cir::ExtraFuncAttributesAttr extraFnAttr = {}) { + return createTryCallOp(loc, mlir::SymbolRefAttr::get(callee), + callee.getFunctionType().getReturnType(), operands, + callingConv, sideEffect, extraFnAttr); + } + cir::CallOp createIndirectTryCallOp(mlir::Location loc, mlir::Value ind_target, cir::FuncType fn_type, mlir::ValueRange operands, diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 328781881d76..7159d1002a2f 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -2596,6 +2596,53 @@ def CIR_GetGlobalOp }]; } +def CIR_IFuncOp : CIR_Op<"func.ifunc", [Symbol]> { + let summary = "Indirect function (ifunc) declaration"; + let description = [{ + The `cir.func.ifunc` operation declares an indirect function, which allows + runtime selection of function implementations based on CPU features or other + runtime conditions. The actual function to call is determined by a resolver + function at runtime. + + The resolver function must return a pointer to a function with the same + signature as the ifunc. The resolver typically inspects CPU features or + other runtime conditions to select the appropriate implementation. + + This corresponds to the GNU indirect function attribute: + `__attribute__((ifunc("resolver")))` + + Example: + ```mlir + // Resolver function that returns a function pointer + cir.func internal @resolve_foo() -> !cir.ptr> { + ... + cir.return %impl : !cir.ptr> + } + + // IFunc declaration + cir.func.ifunc @foo resolver(@resolve_foo) : !cir.func + + // Usage + cir.func @use_foo() { + %result = cir.call @foo() : () -> i32 + cir.return + } + ``` + }]; + + let arguments = (ins SymbolNameAttr:$sym_name, + CIR_VisibilityAttr:$global_visibility, + TypeAttrOf:$function_type, FlatSymbolRefAttr:$resolver, + DefaultValuedAttr:$linkage, + OptionalAttr:$sym_visibility); + + let assemblyFormat = [{ + $sym_name `resolver` `(` $resolver `)` attr-dict `:` + $function_type + }]; +} + //===----------------------------------------------------------------------===// // GuardedInitOp //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp index 20a596e1fe9c..050067405e89 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp @@ -321,7 +321,7 @@ void CIRGenModule::constructAttributeList( static cir::CIRCallOpInterface emitCallLikeOp(CIRGenFunction &CGF, mlir::Location callLoc, cir::FuncType indirectFuncTy, mlir::Value indirectFuncVal, - cir::FuncOp directFuncOp, + mlir::Operation *directCalleeOp, SmallVectorImpl &CIRCallArgs, bool isInvoke, cir::CallingConv callingConv, cir::SideEffect sideEffect, cir::ExtraFuncAttributesAttr extraFnAttrs) { @@ -378,9 +378,13 @@ emitCallLikeOp(CIRGenFunction &CGF, mlir::Location callLoc, callOpWithExceptions = builder.createIndirectTryCallOp( callLoc, indirectFuncVal, indirectFuncTy, CIRCallArgs, callingConv, sideEffect); + } else if (auto funcOp = mlir::dyn_cast(directCalleeOp)) { + callOpWithExceptions = builder.createTryCallOp( + callLoc, funcOp, CIRCallArgs, callingConv, sideEffect); } else { + auto ifuncOp = mlir::cast(directCalleeOp); callOpWithExceptions = builder.createTryCallOp( - callLoc, directFuncOp, CIRCallArgs, callingConv, sideEffect); + callLoc, ifuncOp, CIRCallArgs, callingConv, sideEffect); } callOpWithExceptions->setAttr("extra_attrs", extraFnAttrs); CGF.mayThrow = true; @@ -405,7 +409,12 @@ emitCallLikeOp(CIRGenFunction &CGF, mlir::Location callLoc, callLoc, indirectFuncVal, indirectFuncTy, CIRCallArgs, cir::CallingConv::C, sideEffect, extraFnAttrs); } - return builder.createCallOp(callLoc, directFuncOp, CIRCallArgs, callingConv, + if (auto funcOp = mlir::dyn_cast(directCalleeOp)) { + return builder.createCallOp(callLoc, funcOp, CIRCallArgs, callingConv, + sideEffect, extraFnAttrs); + } + auto ifuncOp = mlir::cast(directCalleeOp); + return builder.createCallOp(callLoc, ifuncOp, CIRCallArgs, callingConv, sideEffect, extraFnAttrs); } @@ -620,10 +629,12 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &CallInfo, cir::CIRCallOpInterface theCall = [&]() { cir::FuncType indirectFuncTy; mlir::Value indirectFuncVal; - cir::FuncOp directFuncOp; + mlir::Operation *directCalleeOp = nullptr; if (auto fnOp = dyn_cast(CalleePtr)) { - directFuncOp = fnOp; + directCalleeOp = fnOp; + } else if (auto ifuncOp = dyn_cast(CalleePtr)) { + directCalleeOp = ifuncOp; } else if (auto getGlobalOp = dyn_cast(CalleePtr)) { // FIXME(cir): This peephole optimization to avoids indirect calls for // builtins. This should be fixed in the builting declaration instead by @@ -631,8 +642,8 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &CallInfo, auto *globalOp = mlir::SymbolTable::lookupSymbolIn(CGM.getModule(), getGlobalOp.getName()); assert(getGlobalOp && "undefined global function"); - directFuncOp = llvm::dyn_cast(globalOp); - assert(directFuncOp && "operation is not a function"); + directCalleeOp = llvm::dyn_cast(globalOp); + assert(directCalleeOp && "operation is not a function"); } else { [[maybe_unused]] auto resultTypes = CalleePtr->getResultTypes(); [[maybe_unused]] auto FuncPtrTy = @@ -648,7 +659,7 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &CallInfo, Attrs.getDictionary(&getMLIRContext())); cir::CIRCallOpInterface callLikeOp = emitCallLikeOp( - *this, callLoc, indirectFuncTy, indirectFuncVal, directFuncOp, + *this, callLoc, indirectFuncTy, indirectFuncVal, directCalleeOp, CIRCallArgs, isInvoke, callingConv, sideEffect, extraFnAttrs); if (E) diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp index 66e4b132c6c5..dcb3eccb139c 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -576,6 +576,15 @@ static CIRGenCallee emitDirectCallee(CIRGenModule &CGM, GlobalDecl GD) { return CIRGenCallee::forBuiltin(builtinID, FD); } + // Handle ifunc specially - get the IFuncOp directly + if (FD->hasAttr()) { + llvm::StringRef mangledName = CGM.getMangledName(GD); + mlir::Operation *ifuncOp = CGM.getGlobalValue(mangledName); + assert(ifuncOp && isa(ifuncOp) && + "Expected IFuncOp for ifunc"); + return CIRGenCallee::forDirect(ifuncOp, GD); + } + mlir::Operation *CalleePtr = emitFunctionDeclPointer(CGM, GD); if ((CGM.getLangOpts().HIP || CGM.getLangOpts().CUDA) && diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index b108a89a87b0..c148a3f503c7 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -598,7 +598,10 @@ void CIRGenModule::emitGlobal(GlobalDecl gd) { const auto *global = cast(gd.getDecl()); - assert(!global->hasAttr() && "NYI"); + // IFunc like an alias whose value is resolved at runtime by calling resolver. + if (global->hasAttr()) + return emitIFuncDefinition(gd); + assert(!global->hasAttr() && "NYI"); if (langOpts.CUDA || langOpts.HIP) { @@ -722,6 +725,77 @@ void CIRGenModule::emitGlobal(GlobalDecl gd) { } } +void CIRGenModule::emitIFuncDefinition(GlobalDecl globalDecl) { + const auto *d = cast(globalDecl.getDecl()); + const IFuncAttr *ifa = d->getAttr(); + assert(ifa && "Not an ifunc?"); + + llvm::StringRef mangledName = getMangledName(globalDecl); + + if (ifa->getResolver() == mangledName) { + getDiags().Report(ifa->getLocation(), diag::err_cyclic_alias) << 1; + return; + } + + // Get function type for the ifunc. + mlir::Type declTy = getTypes().convertTypeForMem(d->getType()); + auto funcTy = mlir::dyn_cast(declTy); + assert(funcTy && "IFunc must have function type"); + + // The resolver might not be visited yet. Create a forward declaration for it. + mlir::Type resolverRetTy = builder.getPointerTo(funcTy); + auto resolverFuncTy = + cir::FuncType::get(llvm::ArrayRef{}, resolverRetTy); + + // Ensure the resolver function is created. + GetOrCreateCIRFunction(ifa->getResolver(), resolverFuncTy, GlobalDecl(), + /*ForVTable=*/false); + + mlir::OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToStart(theModule.getBody()); + + // Report an error if some definition overrides ifunc. + mlir::Operation *entry = getGlobalValue(mangledName); + if (entry) { + // Check if this is a non-declaration (an actual definition). + bool isDeclaration = false; + if (auto func = mlir::dyn_cast(entry)) + isDeclaration = func.isDeclaration(); + + if (!isDeclaration) { + GlobalDecl otherGd; + if (lookupRepresentativeDecl(mangledName, otherGd) && + DiagnosedConflictingDefinitions.insert(globalDecl).second) { + getDiags().Report(d->getLocation(), diag::err_duplicate_mangled_name) + << mangledName; + getDiags().Report(otherGd.getDecl()->getLocation(), + diag::note_previous_definition); + } + return; + } + + // This is just a forward declaration, remove it. + if (auto func = mlir::dyn_cast(entry)) { + func.erase(); + } + } + + // Get linkage + GVALinkage linkage = astContext.GetGVALinkageForFunction(d); + cir::GlobalLinkageKind cirLinkage = + getCIRLinkageForDeclarator(d, linkage, /*IsConstantVariable=*/false); + + // Get visibility + cir::VisibilityAttr visibilityAttr = getGlobalVisibilityAttrFromDecl(d); + cir::VisibilityKind visibilityKind = visibilityAttr.getValue(); + + auto ifuncOp = builder.create( + theModule.getLoc(), mangledName, visibilityKind, funcTy, + ifa->getResolver(), cirLinkage, /*sym_visibility=*/mlir::StringAttr{}); + + setCommonAttributes(globalDecl, ifuncOp); +} + void CIRGenModule::emitGlobalFunctionDefinition(GlobalDecl gd, mlir::Operation *op) { auto const *d = cast(gd.getDecl()); @@ -2432,6 +2506,10 @@ void CIRGenModule::ReplaceUsesOfNonProtoTypeWithRealFunction( // Replace type getGlobalOp.getAddr().setType( cir::PointerType::get(newFn.getFunctionType())); + } else if (auto ifuncOp = dyn_cast(use.getUser())) { + // IFuncOp references the resolver function by symbol. + // The symbol reference doesn't need updating - it's name-based. + // The resolver's signature is validated when the IFuncOp is created. } else { llvm_unreachable("NIY"); } @@ -3139,6 +3217,11 @@ cir::FuncOp CIRGenModule::GetOrCreateCIRFunction( // Lookup the entry, lazily creating it if necessary. mlir::Operation *entry = getGlobalValue(mangledName); if (entry) { + // If this is an ifunc, we can't create a FuncOp for it. Just return nullptr + // and let the caller handle calling through the ifunc. + if (isa(entry)) + return nullptr; + assert(isa(entry) && "not implemented, only supports FuncOp for now"); diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index f05dab8b7658..844fff10abf3 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -783,6 +783,7 @@ class CIRGenModule : public CIRGenTypeCache { cir::FuncOp func); void emitGlobalDefinition(clang::GlobalDecl D, mlir::Operation *Op = nullptr); + void emitIFuncDefinition(clang::GlobalDecl globalDecl); void emitGlobalFunctionDefinition(clang::GlobalDecl D, mlir::Operation *Op); void emitGlobalVarDefinition(const clang::VarDecl *D, bool IsTentative = false); diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index 37152e0acab7..26777da2e67f 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -3134,17 +3134,26 @@ verifyCallCommInSymbolUses(Operation *op, SymbolTableCollection &symbolTable) { if (!fnAttr) return success(); + // Look up the callee - it can be either a FuncOp or an IFuncOp cir::FuncOp fn = symbolTable.lookupNearestSymbolFrom(op, fnAttr); - if (!fn) + cir::IFuncOp ifn = + symbolTable.lookupNearestSymbolFrom(op, fnAttr); + + if (!fn && !ifn) return op->emitOpError() << "'" << fnAttr.getValue() << "' does not reference a valid function"; + auto callIf = dyn_cast(op); assert(callIf && "expected CIR call interface to be always available"); + // Get function type from either FuncOp or IFuncOp + cir::FuncType fnType = fn ? fn.getFunctionType() : ifn.getFunctionType(); + // Verify that the operand and result types match the callee. Note that // argument-checking is disabled for functions without a prototype. - auto fnType = fn.getFunctionType(); - if (!fn.getNoProto()) { + // IFuncs are always considered to have a prototype. + bool hasProto = ifn || !fn.getNoProto(); + if (hasProto) { unsigned numCallOperands = callIf.getNumArgOperands(); unsigned numFnOpOperands = fnType.getNumInputs(); @@ -3161,8 +3170,9 @@ verifyCallCommInSymbolUses(Operation *op, SymbolTableCollection &symbolTable) { << op->getOperand(i).getType() << " for operand number " << i; } - // Calling convention must match. - if (callIf.getCallingConv() != fn.getCallingConv()) + // Calling convention must match (only check for FuncOp; IFuncOp uses the + // type's convention) + if (fn && callIf.getCallingConv() != fn.getCallingConv()) return op->emitOpError("calling convention mismatch: expected ") << stringifyCallingConv(fn.getCallingConv()) << ", but provided " << stringifyCallingConv(callIf.getCallingConv()); diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index 6ef0f0181f00..13acc411b54d 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -1615,7 +1615,7 @@ rewriteToCallOrInvoke(mlir::Operation *op, mlir::ValueRange callOperands, if (auto fn = dyn_cast(callee)) { llvmFnTy = cast( converter->convertType(fn.getFunctionType())); - } else if (auto alias = cast(callee)) { + } else if (auto alias = dyn_cast(callee)) { // If the callee wasan alias. In that case, // we need to prepend the address of the alias to the operands. The // way aliases work in the LLVM dialect is a little counter-intuitive. @@ -1638,8 +1638,11 @@ rewriteToCallOrInvoke(mlir::Operation *op, mlir::ValueRange callOperands, // Clear the callee attribute because we're calling an alias. calleeAttr = {}; llvmFnTy = cast(alias.getType()); + } else if (auto ifunc = dyn_cast(callee)) { + // IFunc can be called directly like a regular function. + // The ifunc resolves to the actual implementation at runtime. + llvmFnTy = cast(ifunc.getIFuncType()); } else { - // Was this an ifunc? return op->emitError("Unexpected callee type!"); } } else { // indirect call @@ -2560,6 +2563,40 @@ mlir::LogicalResult CIRToLLVMGetGlobalOpLowering::matchAndRewrite( return mlir::success(); } +mlir::LogicalResult CIRToLLVMIFuncOpLowering::matchAndRewrite( + cir::IFuncOp op, OpAdaptor adaptor, + mlir::ConversionPatternRewriter &rewriter) const { + // Get the function type for the ifunc + auto funcTy = mlir::cast( + getTypeConverter()->convertType(op.getFunctionType())); + + // Get the linkage + auto linkage = convertLinkage(op.getLinkage()); + + // Get the resolver symbol name + auto resolverName = op.getResolver(); + + // The resolver_type for IFuncOp should be a pointer type (!llvm.ptr). + // This represents the return type of the resolver function, not the function + // type itself. The verifier checks that resolver_type is a pointer matching + // the ifunc's address space. + auto ptrTy = mlir::LLVM::LLVMPointerType::get(rewriter.getContext()); + + // Convert visibility + auto visibility = lowerCIRVisibilityToLLVMVisibility( + op.getGlobalVisibilityAttr().getValue()); + + // Create the LLVM IFunc + // Note: The 4th parameter is the resolver_type, which must be a pointer type + rewriter.create( + op.getLoc(), op.getSymName(), funcTy, resolverName, ptrTy, linkage, + /*dso_local=*/false, + /*address_space=*/0, mlir::LLVM::UnnamedAddr::None, visibility); + + rewriter.eraseOp(op); + return mlir::success(); +} + mlir::LogicalResult CIRToLLVMComplexCreateOpLowering::matchAndRewrite( cir::ComplexCreateOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { @@ -4947,6 +4984,7 @@ void populateCIRToLLVMConversionPatterns( CIRToLLVMFuncOpLowering, CIRToLLVMGetBitfieldOpLowering, CIRToLLVMGetGlobalOpLowering, + CIRToLLVMIFuncOpLowering, CIRToLLVMGetMemberOpLowering, CIRToLLVMInsertMemberOpLowering, CIRToLLVMDeleteArrayOpLowering, diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h index 338bde99b5d3..8f3e53a43368 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h @@ -599,6 +599,16 @@ class CIRToLLVMGetGlobalOpLowering mlir::ConversionPatternRewriter &) const override; }; +class CIRToLLVMIFuncOpLowering + : public mlir::OpConversionPattern { +public: + using mlir::OpConversionPattern::OpConversionPattern; + + mlir::LogicalResult + matchAndRewrite(cir::IFuncOp op, OpAdaptor, + mlir::ConversionPatternRewriter &) const override; +}; + class CIRToLLVMComplexCreateOpLowering : public mlir::OpConversionPattern { public: diff --git a/clang/test/CIR/CodeGen/ifunc.c b/clang/test/CIR/CodeGen/ifunc.c new file mode 100644 index 000000000000..6c9972e3c07b --- /dev/null +++ b/clang/test/CIR/CodeGen/ifunc.c @@ -0,0 +1,81 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o - | FileCheck %s --check-prefix=CIR +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o - | FileCheck %s --check-prefix=LLVM +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o - | FileCheck %s --check-prefix=OGCG + +// Basic ifunc test: CPU feature detection +static void *resolve_foo(void) { + return (void *)0; +} + +int foo(void) __attribute__((ifunc("resolve_foo"))); + +void use_foo(void) { + foo(); +} + +// CIR-DAG: cir.func.ifunc @foo resolver(@resolve_foo) {{.*}} +// CIR-DAG: cir.func {{.*}}@resolve_foo() +// CIR-DAG: cir.func {{.*}}@use_foo() +// CIR-DAG: cir.call @foo() + +// LLVM-DAG: @foo = ifunc i32 (), ptr @resolve_foo +// LLVM-DAG: define {{.*}}void @use_foo() +// LLVM-DAG: call i32 @foo() +// LLVM-DAG: define internal ptr @resolve_foo() + +// OGCG-DAG: @foo = ifunc i32 (), ptr @resolve_foo +// OGCG-DAG: define{{.*}} void @use_foo() +// OGCG-DAG: call i32 @foo() +// OGCG-DAG: define internal ptr @resolve_foo() + +// Test with multiple implementations +static int foo_impl_v1(void) { return 1; } +static int foo_impl_v2(void) { return 2; } + +typedef int (*foo_func_t)(void); + +static foo_func_t resolve_foo_multi(void) { + return foo_impl_v1; +} + +int foo_multi(void) __attribute__((ifunc("resolve_foo_multi"))); + +// CIR-DAG: cir.func.ifunc @foo_multi resolver(@resolve_foo_multi) {{.*}} +// CIR-DAG: cir.func {{.*}}@foo_impl_v1() +// CIR-DAG: cir.func {{.*}}@resolve_foo_multi() + +// LLVM-DAG: @foo_multi = ifunc i32 (), ptr @resolve_foo_multi +// LLVM-DAG: define internal i32 @foo_impl_v1() +// LLVM-DAG: define internal ptr @resolve_foo_multi() + +// OGCG-DAG: @foo_multi = ifunc i32 (), ptr @resolve_foo_multi +// OGCG-DAG: define internal i32 @foo_impl_v1() +// OGCG-DAG: define internal ptr @resolve_foo_multi() + +// Test with extern declaration followed by ifunc +extern int bar(void); + +void use_bar_before_def(void) { + bar(); +} + +static void *resolve_bar(void) { + return (void *)0; +} + +int bar(void) __attribute__((ifunc("resolve_bar"))); + +// CIR-DAG: cir.func.ifunc @bar resolver(@resolve_bar) {{.*}} +// CIR-DAG: cir.func {{.*}}@use_bar_before_def() +// CIR-DAG: cir.call @bar() +// CIR-DAG: cir.func {{.*}}@resolve_bar() + +// LLVM-DAG: @bar = ifunc i32 (), ptr @resolve_bar +// LLVM-DAG: define {{.*}}void @use_bar_before_def() +// LLVM-DAG: call i32 @bar() +// LLVM-DAG: define internal ptr @resolve_bar() + +// OGCG-DAG: @bar = ifunc i32 (), ptr @resolve_bar +// OGCG-DAG: define{{.*}} void @use_bar_before_def() +// OGCG-DAG: call i32 @bar() +// OGCG-DAG: define internal ptr @resolve_bar()