[llvm] [LLVM][Intrinsics] Adds an API to automatically resolve overload types (PR #169007)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Nov 20 22:58:22 PST 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-ir
Author: Rajat Bajpai (rajatbajpai)
<details>
<summary>Changes</summary>
Currently, the getOrInsertDeclaration API requires callers to explicitly provide overload types for overloaded intrinsics, placing a significant burden on callers who must determine whether overload types are needed. This typically results in conditional logic at each call site to check if the intrinsic is overloaded and manually match the intrinsic signature.
This patch introduces a new getOrInsertDeclaration overload that automatically deduces overload types from the provided return type and argument types, then uses this API to simplify IRBuilder::CreateIntrinsic. The new API uses Intrinsic::matchIntrinsicSignature internally to resolve overloaded types, eliminating the need for callers to do manual overload detection.
---
Full diff: https://github.com/llvm/llvm-project/pull/169007.diff
4 Files Affected:
- (modified) llvm/include/llvm/IR/Intrinsics.h (+15)
- (modified) llvm/lib/IR/IRBuilder.cpp (+2-14)
- (modified) llvm/lib/IR/Intrinsics.cpp (+47-9)
- (modified) llvm/unittests/IR/IntrinsicsTest.cpp (+168-3)
``````````diff
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<Type *> 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<Type *> 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<Intrinsic::IITDescriptor> Table;
- Intrinsic::getIntrinsicInfoTableEntries(ID, Table);
- ArrayRef<Intrinsic::IITDescriptor> TableRef(Table);
-
SmallVector<Type *> ArgTys;
ArgTys.reserve(Args.size());
for (auto &I : Args)
ArgTys.push_back(I->getType());
- FunctionType *FTy = FunctionType::get(RetTy, ArgTys, false);
- SmallVector<Type *> 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<Type *> 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<Type *> Tys,
+ FunctionType *FT) {
Function *F = cast<Function>(
- 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<Function>(
- 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<Type *> 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<Type *> 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<Intrinsic::IITDescriptor, 8> Table;
+ getIntrinsicInfoTableEntries(IID, Table);
+ ArrayRef<Intrinsic::IITDescriptor> TableRef = Table;
+
+ FunctionType *FTy = FunctionType::get(RetTy, ArgTys, /*isVarArg=*/false);
+
+ // Automatically determine the overloaded types.
+ SmallVector<Type *, 4> 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<Module> M;
BasicBlock *BB = nullptr;
- void TearDown() override { M.reset(); }
-
void SetUp() override {
M = std::make_unique<Module>("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<Type *, 1> 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<Type *, 2> 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<Type *, 2> 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<Type *, 5> 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<Type *, 2> 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<Type *, 2> 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<Value *, 2> 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<Value *, 2> 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<Value *, 5> 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
``````````
</details>
https://github.com/llvm/llvm-project/pull/169007
More information about the llvm-commits
mailing list