[llvm] 822fc44 - [LLVM][Intrinsics] Adds an API to automatically resolve overload types (#169007)

via llvm-commits llvm-commits at lists.llvm.org
Tue Dec 2 21:19:21 PST 2025


Author: Rajat Bajpai
Date: 2025-12-03T10:49:17+05:30
New Revision: 822fc449985553c609e44915374f935672c0db50

URL: https://github.com/llvm/llvm-project/commit/822fc449985553c609e44915374f935672c0db50
DIFF: https://github.com/llvm/llvm-project/commit/822fc449985553c609e44915374f935672c0db50.diff

LOG: [LLVM][Intrinsics] Adds an API to automatically resolve overload types (#169007)

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.

Added: 
    

Modified: 
    llvm/include/llvm/IR/Intrinsics.h
    llvm/lib/IR/IRBuilder.cpp
    llvm/lib/IR/Intrinsics.cpp
    llvm/unittests/IR/IntrinsicsTest.cpp

Removed: 
    


################################################################################
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..f46d3e5063e43 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 
diff erent 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 
diff erent 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 id, Type *RetTy,
+                                            ArrayRef<Type *> ArgTys) {
+  // If the intrinsic is not overloaded, use the non-overloaded version.
+  if (!Intrinsic::isOverloaded(id))
+    return getOrInsertDeclaration(M, id);
+
+  // Get the intrinsic signature metadata.
+  SmallVector<Intrinsic::IITDescriptor, 8> Table;
+  getIntrinsicInfoTableEntries(id, 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, id, 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..87d922d22eaac 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,212 @@ 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 overloaded intrinsic with automatic type resolution for addrspace.
+TEST_F(IntrinsicsTest, OverloadedIntrinsicAddressSpace) {
+  Type *RetTy = Type::getVoidTy(Context);
+  SmallVector<Type *, 4> ArgTys;
+  ArgTys.push_back(PointerType::get(Context, 1)); // ptr addrspace(1)
+  ArgTys.push_back(Type::getInt32Ty(Context));    // rw
+  ArgTys.push_back(Type::getInt32Ty(Context));    // locality
+  ArgTys.push_back(Type::getInt32Ty(Context));    // cache type
+
+  Function *F = Intrinsic::getOrInsertDeclaration(M.get(), Intrinsic::prefetch,
+                                                  RetTy, ArgTys);
+
+  ASSERT_NE(F, nullptr);
+  EXPECT_EQ(F->getIntrinsicID(), Intrinsic::prefetch);
+  EXPECT_EQ(F->getReturnType(), RetTy);
+  EXPECT_EQ(F->arg_size(), 4u);
+  EXPECT_FALSE(F->isVarArg());
+  EXPECT_EQ(F->getName(), "llvm.prefetch.p1");
+}
+
+// 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 
diff erent overloads create 
diff erent 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 *Func32 = 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 *Func64 = Intrinsic::getOrInsertDeclaration(M.get(), Intrinsic::umax,
+                                                       RetTy64, ArgTys64);
+
+  EXPECT_NE(Func32, Func64)
+      << "Different overloads should be 
diff erent functions";
+  EXPECT_EQ(Func32->getName(), "llvm.umax.i32");
+  EXPECT_EQ(Func64->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 overloaded address space.
+TEST_F(IntrinsicsTest, IRBuilderCreateIntrinsicAddressSpace) {
+  IRBuilder<> Builder(BB);
+
+  Type *RetTy = Type::getVoidTy(Context);
+  SmallVector<Value *, 4> Args;
+  Args.push_back(Constant::getNullValue(
+      PointerType::get(Context, 1))); // ptr addrspace(1) null
+  Args.push_back(ConstantInt::get(Type::getInt32Ty(Context), 0)); // rw
+  Args.push_back(ConstantInt::get(Type::getInt32Ty(Context), 3)); // locality
+  Args.push_back(ConstantInt::get(Type::getInt32Ty(Context), 1)); // cache type
+
+  CallInst *CI = Builder.CreateIntrinsic(RetTy, Intrinsic::prefetch, Args);
+
+  ASSERT_NE(CI, nullptr);
+  EXPECT_EQ(CI->getIntrinsicID(), Intrinsic::prefetch);
+  EXPECT_EQ(CI->getType(), RetTy);
+  EXPECT_EQ(CI->arg_size(), 4u);
+  EXPECT_FALSE(CI->getCalledFunction()->isVarArg());
+  EXPECT_EQ(CI->getCalledFunction()->getName(), "llvm.prefetch.p1");
+}
+
+// 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


        


More information about the llvm-commits mailing list