diff --git a/llvm/include/llvm/IR/Intrinsics.h b/llvm/include/llvm/IR/Intrinsics.h index c91fc254ebe11..2c86a43e114ea 100644 --- a/llvm/include/llvm/IR/Intrinsics.h +++ b/llvm/include/llvm/IR/Intrinsics.h @@ -109,6 +109,21 @@ namespace Intrinsic { LLVM_ABI Function *getOrInsertDeclaration(Module *M, ID id, ArrayRef Tys = {}); + /// Look up the Function declaration of the intrinsic \p IID in the Module + /// \p M. If it does not exist, add a declaration and return it. Otherwise, + /// return the existing declaration. + /// + /// This overload automatically resolves overloaded intrinsics based on the + /// provided return type and argument types. For non-overloaded intrinsics, + /// the return type and argument types are ignored. + /// + /// \param M - The module to get or insert the intrinsic declaration. + /// \param IID - The intrinsic ID. + /// \param RetTy - The return type of the intrinsic. + /// \param ArgTys - The argument types of the intrinsic. + LLVM_ABI Function *getOrInsertDeclaration(Module *M, ID IID, Type *RetTy, + ArrayRef ArgTys); + /// Look up the Function declaration of the intrinsic \p id in the Module /// \p M and return it if it exists. Otherwise, return nullptr. This version /// supports non-overloaded intrinsics. diff --git a/llvm/lib/IR/IRBuilder.cpp b/llvm/lib/IR/IRBuilder.cpp index 95edb2e8e56d8..8e1707ac98a51 100644 --- a/llvm/lib/IR/IRBuilder.cpp +++ b/llvm/lib/IR/IRBuilder.cpp @@ -858,24 +858,12 @@ CallInst *IRBuilderBase::CreateIntrinsic(Type *RetTy, Intrinsic::ID ID, const Twine &Name) { Module *M = BB->getModule(); - SmallVector Table; - Intrinsic::getIntrinsicInfoTableEntries(ID, Table); - ArrayRef TableRef(Table); - SmallVector ArgTys; ArgTys.reserve(Args.size()); for (auto &I : Args) ArgTys.push_back(I->getType()); - FunctionType *FTy = FunctionType::get(RetTy, ArgTys, false); - SmallVector OverloadTys; - Intrinsic::MatchIntrinsicTypesResult Res = - matchIntrinsicSignature(FTy, TableRef, OverloadTys); - (void)Res; - assert(Res == Intrinsic::MatchIntrinsicTypes_Match && TableRef.empty() && - "Wrong types for intrinsic!"); - // TODO: Handle varargs intrinsics. - - Function *Fn = Intrinsic::getOrInsertDeclaration(M, ID, OverloadTys); + + Function *Fn = Intrinsic::getOrInsertDeclaration(M, ID, RetTy, ArgTys); return createCallHelper(Fn, Args, Name, FMFSource); } diff --git a/llvm/lib/IR/Intrinsics.cpp b/llvm/lib/IR/Intrinsics.cpp index 859689b9cf168..fe1ccf4a0e051 100644 --- a/llvm/lib/IR/Intrinsics.cpp +++ b/llvm/lib/IR/Intrinsics.cpp @@ -727,14 +727,14 @@ Intrinsic::ID Intrinsic::lookupIntrinsicID(StringRef Name) { #include "llvm/IR/IntrinsicImpl.inc" #undef GET_INTRINSIC_ATTRIBUTES -Function *Intrinsic::getOrInsertDeclaration(Module *M, ID id, - ArrayRef Tys) { - // There can never be multiple globals with the same name of different types, - // because intrinsics must be a specific type. - auto *FT = getType(M->getContext(), id, Tys); +static Function *getOrInsertIntrinsicDeclarationImpl(Module *M, + Intrinsic::ID id, + ArrayRef Tys, + FunctionType *FT) { Function *F = cast( - M->getOrInsertFunction( - Tys.empty() ? getName(id) : getName(id, Tys, M, FT), FT) + M->getOrInsertFunction(Tys.empty() ? Intrinsic::getName(id) + : Intrinsic::getName(id, Tys, M, FT), + FT) .getCallee()); if (F->getFunctionType() == FT) return F; @@ -746,11 +746,49 @@ Function *Intrinsic::getOrInsertDeclaration(Module *M, ID id, // invalid declaration will get upgraded later. F->setName(F->getName() + ".invalid"); return cast( - M->getOrInsertFunction( - Tys.empty() ? getName(id) : getName(id, Tys, M, FT), FT) + M->getOrInsertFunction(Tys.empty() ? Intrinsic::getName(id) + : Intrinsic::getName(id, Tys, M, FT), + FT) .getCallee()); } +Function *Intrinsic::getOrInsertDeclaration(Module *M, ID id, + ArrayRef Tys) { + // There can never be multiple globals with the same name of different types, + // because intrinsics must be a specific type. + FunctionType *FT = getType(M->getContext(), id, Tys); + return getOrInsertIntrinsicDeclarationImpl(M, id, Tys, FT); +} + +Function *Intrinsic::getOrInsertDeclaration(Module *M, ID IID, Type *RetTy, + ArrayRef ArgTys) { + // If the intrinsic is not overloaded, use the non-overloaded version. + if (!Intrinsic::isOverloaded(IID)) + return getOrInsertDeclaration(M, IID); + + // Get the intrinsic signature metadata. + SmallVector Table; + getIntrinsicInfoTableEntries(IID, Table); + ArrayRef TableRef = Table; + + FunctionType *FTy = FunctionType::get(RetTy, ArgTys, /*isVarArg=*/false); + + // Automatically determine the overloaded types. + SmallVector OverloadTys; + [[maybe_unused]] Intrinsic::MatchIntrinsicTypesResult Res = + matchIntrinsicSignature(FTy, TableRef, OverloadTys); + assert(Res == Intrinsic::MatchIntrinsicTypes_Match && + "intrinsic signature mismatch"); + + // If intrinsic requires vararg, recreate the FunctionType accordingly. + if (!matchIntrinsicVarArg(/*isVarArg=*/true, TableRef)) + FTy = FunctionType::get(RetTy, ArgTys, /*isVarArg=*/true); + + assert(TableRef.empty() && "Unprocessed descriptors remain"); + + return getOrInsertIntrinsicDeclarationImpl(M, IID, OverloadTys, FTy); +} + Function *Intrinsic::getDeclarationIfExists(const Module *M, ID id) { return M->getFunction(getName(id)); } diff --git a/llvm/unittests/IR/IntrinsicsTest.cpp b/llvm/unittests/IR/IntrinsicsTest.cpp index cfd99ed542162..9767b34582f98 100644 --- a/llvm/unittests/IR/IntrinsicsTest.cpp +++ b/llvm/unittests/IR/IntrinsicsTest.cpp @@ -30,14 +30,12 @@ using namespace llvm; namespace { - class IntrinsicsTest : public ::testing::Test { +protected: LLVMContext Context; std::unique_ptr M; BasicBlock *BB = nullptr; - void TearDown() override { M.reset(); } - void SetUp() override { M = std::make_unique("Test", Context); auto F = M->getOrInsertFunction( @@ -46,6 +44,8 @@ class IntrinsicsTest : public ::testing::Test { EXPECT_NE(BB, nullptr); } + void TearDown() override { M.reset(); } + public: Instruction *makeIntrinsic(Intrinsic::ID ID) const { IRBuilder<> Builder(BB); @@ -197,4 +197,169 @@ TEST(IntrinsicAttributes, TestGetFnAttributesBug) { AttributeSet AS = getFnAttributes(Context, experimental_guard); EXPECT_FALSE(AS.hasAttributes()); } + +// Tests non-overloaded intrinsic declaration. +TEST_F(IntrinsicsTest, NonOverloadedIntrinsic) { + Type *RetTy = Type::getVoidTy(Context); + SmallVector ArgTys; + ArgTys.push_back(Type::getInt1Ty(Context)); + + Function *F = Intrinsic::getOrInsertDeclaration(M.get(), Intrinsic::assume, + RetTy, ArgTys); + + ASSERT_NE(F, nullptr); + EXPECT_EQ(F->getIntrinsicID(), Intrinsic::assume); + EXPECT_EQ(F->getReturnType(), RetTy); + EXPECT_EQ(F->arg_size(), 1u); + EXPECT_FALSE(F->isVarArg()); + EXPECT_EQ(F->getName(), "llvm.assume"); +} + +// Tests overloaded intrinsic with automatic type resolution for scalar types. +TEST_F(IntrinsicsTest, OverloadedIntrinsicScalar) { + Type *RetTy = Type::getInt32Ty(Context); + SmallVector ArgTys; + ArgTys.push_back(Type::getInt32Ty(Context)); + ArgTys.push_back(Type::getInt32Ty(Context)); + + Function *F = Intrinsic::getOrInsertDeclaration(M.get(), Intrinsic::umax, + RetTy, ArgTys); + + ASSERT_NE(F, nullptr); + EXPECT_EQ(F->getIntrinsicID(), Intrinsic::umax); + EXPECT_EQ(F->getReturnType(), RetTy); + EXPECT_EQ(F->arg_size(), 2u); + EXPECT_FALSE(F->isVarArg()); + EXPECT_EQ(F->getName(), "llvm.umax.i32"); +} + +// Tests overloaded intrinsic with automatic type resolution for vector types. +TEST_F(IntrinsicsTest, OverloadedIntrinsicVector) { + Type *RetTy = FixedVectorType::get(Type::getInt32Ty(Context), 4); + SmallVector ArgTys; + ArgTys.push_back(RetTy); + ArgTys.push_back(RetTy); + + Function *F = Intrinsic::getOrInsertDeclaration(M.get(), Intrinsic::umax, + RetTy, ArgTys); + + ASSERT_NE(F, nullptr); + EXPECT_EQ(F->getIntrinsicID(), Intrinsic::umax); + EXPECT_EQ(F->getReturnType(), RetTy); + EXPECT_EQ(F->arg_size(), 2u); + EXPECT_FALSE(F->isVarArg()); + EXPECT_EQ(F->getName(), "llvm.umax.v4i32"); +} + +// Tests vararg intrinsic declaration. +TEST_F(IntrinsicsTest, VarArgIntrinsicStatepoint) { + Type *RetTy = Type::getTokenTy(Context); + SmallVector ArgTys; + ArgTys.push_back(Type::getInt64Ty(Context)); // ID + ArgTys.push_back(Type::getInt32Ty(Context)); // NumPatchBytes + ArgTys.push_back(PointerType::get(Context, 0)); // Target + ArgTys.push_back(Type::getInt32Ty(Context)); // NumCallArgs + ArgTys.push_back(Type::getInt32Ty(Context)); // Flags + + Function *F = Intrinsic::getOrInsertDeclaration( + M.get(), Intrinsic::experimental_gc_statepoint, RetTy, ArgTys); + + ASSERT_NE(F, nullptr); + EXPECT_EQ(F->getIntrinsicID(), Intrinsic::experimental_gc_statepoint); + EXPECT_EQ(F->getReturnType(), RetTy); + EXPECT_EQ(F->arg_size(), 5u); + EXPECT_TRUE(F->isVarArg()) << "experimental_gc_statepoint must be vararg"; + EXPECT_EQ(F->getName(), "llvm.experimental.gc.statepoint.p0"); +} + +// Tests that different overloads create different declarations. +TEST_F(IntrinsicsTest, DifferentOverloads) { + // i32 version + Type *RetTy32 = Type::getInt32Ty(Context); + SmallVector ArgTys32; + ArgTys32.push_back(Type::getInt32Ty(Context)); + ArgTys32.push_back(Type::getInt32Ty(Context)); + + Function *F32 = Intrinsic::getOrInsertDeclaration(M.get(), Intrinsic::umax, + RetTy32, ArgTys32); + + // i64 version + Type *RetTy64 = Type::getInt64Ty(Context); + SmallVector ArgTys64; + ArgTys64.push_back(Type::getInt64Ty(Context)); + ArgTys64.push_back(Type::getInt64Ty(Context)); + + Function *F64 = Intrinsic::getOrInsertDeclaration(M.get(), Intrinsic::umax, + RetTy64, ArgTys64); + + EXPECT_NE(F32, F64) << "Different overloads should be different functions"; + EXPECT_EQ(F32->getName(), "llvm.umax.i32"); + EXPECT_EQ(F64->getName(), "llvm.umax.i64"); +} + +// Tests IRBuilder::CreateIntrinsic with overloaded scalar type. +TEST_F(IntrinsicsTest, IRBuilderCreateIntrinsicScalar) { + IRBuilder<> Builder(BB); + + Type *RetTy = Type::getInt32Ty(Context); + SmallVector Args; + Args.push_back(ConstantInt::get(Type::getInt32Ty(Context), 10)); + Args.push_back(ConstantInt::get(Type::getInt32Ty(Context), 20)); + + CallInst *CI = Builder.CreateIntrinsic(RetTy, Intrinsic::umax, Args); + + ASSERT_NE(CI, nullptr); + EXPECT_EQ(CI->getIntrinsicID(), Intrinsic::umax); + EXPECT_EQ(CI->getType(), RetTy); + EXPECT_EQ(CI->arg_size(), 2u); + EXPECT_FALSE(CI->getCalledFunction()->isVarArg()); +} + +// Tests IRBuilder::CreateIntrinsic with overloaded vector type. +TEST_F(IntrinsicsTest, IRBuilderCreateIntrinsicVector) { + IRBuilder<> Builder(BB); + + Type *RetTy = FixedVectorType::get(Type::getInt32Ty(Context), 4); + SmallVector Args; + Args.push_back(Constant::getNullValue(RetTy)); + Args.push_back(Constant::getNullValue(RetTy)); + + CallInst *CI = Builder.CreateIntrinsic(RetTy, Intrinsic::umax, Args); + + ASSERT_NE(CI, nullptr); + EXPECT_EQ(CI->getIntrinsicID(), Intrinsic::umax); + EXPECT_EQ(CI->getType(), RetTy); + EXPECT_EQ(CI->arg_size(), 2u); + EXPECT_FALSE(CI->getCalledFunction()->isVarArg()); +} + +// Tests IRBuilder::CreateIntrinsic with vararg intrinsic. +TEST_F(IntrinsicsTest, IRBuilderCreateIntrinsicVarArg) { + IRBuilder<> Builder(BB); + + // Create a dummy function to call through statepoint + FunctionType *DummyFnTy = FunctionType::get(Type::getVoidTy(Context), false); + Function *DummyFn = Function::Create(DummyFnTy, GlobalValue::ExternalLinkage, + "dummy", M.get()); + + Type *RetTy = Type::getTokenTy(Context); + SmallVector Args; + Args.push_back(ConstantInt::get(Type::getInt64Ty(Context), 0)); // ID + Args.push_back( + ConstantInt::get(Type::getInt32Ty(Context), 0)); // NumPatchBytes + Args.push_back(DummyFn); // Target + Args.push_back(ConstantInt::get(Type::getInt32Ty(Context), 0)); // NumCallArgs + Args.push_back(ConstantInt::get(Type::getInt32Ty(Context), 0)); // Flags + + CallInst *CI = Builder.CreateIntrinsic( + RetTy, Intrinsic::experimental_gc_statepoint, Args); + + ASSERT_NE(CI, nullptr); + EXPECT_EQ(CI->getIntrinsicID(), Intrinsic::experimental_gc_statepoint); + EXPECT_EQ(CI->getType(), RetTy); + EXPECT_EQ(CI->arg_size(), 5u); + EXPECT_TRUE(CI->getCalledFunction()->isVarArg()) + << "experimental_gc_statepoint must be vararg"; +} + } // end namespace