Skip to content

Commit 822fc44

Browse files
authored
[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.
1 parent 1f35b52 commit 822fc44

File tree

4 files changed

+275
-26
lines changed

4 files changed

+275
-26
lines changed

llvm/include/llvm/IR/Intrinsics.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,21 @@ namespace Intrinsic {
109109
LLVM_ABI Function *getOrInsertDeclaration(Module *M, ID id,
110110
ArrayRef<Type *> Tys = {});
111111

112+
/// Look up the Function declaration of the intrinsic \p IID in the Module
113+
/// \p M. If it does not exist, add a declaration and return it. Otherwise,
114+
/// return the existing declaration.
115+
///
116+
/// This overload automatically resolves overloaded intrinsics based on the
117+
/// provided return type and argument types. For non-overloaded intrinsics,
118+
/// the return type and argument types are ignored.
119+
///
120+
/// \param M - The module to get or insert the intrinsic declaration.
121+
/// \param IID - The intrinsic ID.
122+
/// \param RetTy - The return type of the intrinsic.
123+
/// \param ArgTys - The argument types of the intrinsic.
124+
LLVM_ABI Function *getOrInsertDeclaration(Module *M, ID IID, Type *RetTy,
125+
ArrayRef<Type *> ArgTys);
126+
112127
/// Look up the Function declaration of the intrinsic \p id in the Module
113128
/// \p M and return it if it exists. Otherwise, return nullptr. This version
114129
/// supports non-overloaded intrinsics.

llvm/lib/IR/IRBuilder.cpp

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -858,24 +858,12 @@ CallInst *IRBuilderBase::CreateIntrinsic(Type *RetTy, Intrinsic::ID ID,
858858
const Twine &Name) {
859859
Module *M = BB->getModule();
860860

861-
SmallVector<Intrinsic::IITDescriptor> Table;
862-
Intrinsic::getIntrinsicInfoTableEntries(ID, Table);
863-
ArrayRef<Intrinsic::IITDescriptor> TableRef(Table);
864-
865861
SmallVector<Type *> ArgTys;
866862
ArgTys.reserve(Args.size());
867863
for (auto &I : Args)
868864
ArgTys.push_back(I->getType());
869-
FunctionType *FTy = FunctionType::get(RetTy, ArgTys, false);
870-
SmallVector<Type *> OverloadTys;
871-
Intrinsic::MatchIntrinsicTypesResult Res =
872-
matchIntrinsicSignature(FTy, TableRef, OverloadTys);
873-
(void)Res;
874-
assert(Res == Intrinsic::MatchIntrinsicTypes_Match && TableRef.empty() &&
875-
"Wrong types for intrinsic!");
876-
// TODO: Handle varargs intrinsics.
877-
878-
Function *Fn = Intrinsic::getOrInsertDeclaration(M, ID, OverloadTys);
865+
866+
Function *Fn = Intrinsic::getOrInsertDeclaration(M, ID, RetTy, ArgTys);
879867
return createCallHelper(Fn, Args, Name, FMFSource);
880868
}
881869

llvm/lib/IR/Intrinsics.cpp

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -727,14 +727,14 @@ Intrinsic::ID Intrinsic::lookupIntrinsicID(StringRef Name) {
727727
#include "llvm/IR/IntrinsicImpl.inc"
728728
#undef GET_INTRINSIC_ATTRIBUTES
729729

730-
Function *Intrinsic::getOrInsertDeclaration(Module *M, ID id,
731-
ArrayRef<Type *> Tys) {
732-
// There can never be multiple globals with the same name of different types,
733-
// because intrinsics must be a specific type.
734-
auto *FT = getType(M->getContext(), id, Tys);
730+
static Function *getOrInsertIntrinsicDeclarationImpl(Module *M,
731+
Intrinsic::ID id,
732+
ArrayRef<Type *> Tys,
733+
FunctionType *FT) {
735734
Function *F = cast<Function>(
736-
M->getOrInsertFunction(
737-
Tys.empty() ? getName(id) : getName(id, Tys, M, FT), FT)
735+
M->getOrInsertFunction(Tys.empty() ? Intrinsic::getName(id)
736+
: Intrinsic::getName(id, Tys, M, FT),
737+
FT)
738738
.getCallee());
739739
if (F->getFunctionType() == FT)
740740
return F;
@@ -746,11 +746,49 @@ Function *Intrinsic::getOrInsertDeclaration(Module *M, ID id,
746746
// invalid declaration will get upgraded later.
747747
F->setName(F->getName() + ".invalid");
748748
return cast<Function>(
749-
M->getOrInsertFunction(
750-
Tys.empty() ? getName(id) : getName(id, Tys, M, FT), FT)
749+
M->getOrInsertFunction(Tys.empty() ? Intrinsic::getName(id)
750+
: Intrinsic::getName(id, Tys, M, FT),
751+
FT)
751752
.getCallee());
752753
}
753754

755+
Function *Intrinsic::getOrInsertDeclaration(Module *M, ID id,
756+
ArrayRef<Type *> Tys) {
757+
// There can never be multiple globals with the same name of different types,
758+
// because intrinsics must be a specific type.
759+
FunctionType *FT = getType(M->getContext(), id, Tys);
760+
return getOrInsertIntrinsicDeclarationImpl(M, id, Tys, FT);
761+
}
762+
763+
Function *Intrinsic::getOrInsertDeclaration(Module *M, ID id, Type *RetTy,
764+
ArrayRef<Type *> ArgTys) {
765+
// If the intrinsic is not overloaded, use the non-overloaded version.
766+
if (!Intrinsic::isOverloaded(id))
767+
return getOrInsertDeclaration(M, id);
768+
769+
// Get the intrinsic signature metadata.
770+
SmallVector<Intrinsic::IITDescriptor, 8> Table;
771+
getIntrinsicInfoTableEntries(id, Table);
772+
ArrayRef<Intrinsic::IITDescriptor> TableRef = Table;
773+
774+
FunctionType *FTy = FunctionType::get(RetTy, ArgTys, /*isVarArg=*/false);
775+
776+
// Automatically determine the overloaded types.
777+
SmallVector<Type *, 4> OverloadTys;
778+
[[maybe_unused]] Intrinsic::MatchIntrinsicTypesResult Res =
779+
matchIntrinsicSignature(FTy, TableRef, OverloadTys);
780+
assert(Res == Intrinsic::MatchIntrinsicTypes_Match &&
781+
"intrinsic signature mismatch");
782+
783+
// If intrinsic requires vararg, recreate the FunctionType accordingly.
784+
if (!matchIntrinsicVarArg(/*isVarArg=*/true, TableRef))
785+
FTy = FunctionType::get(RetTy, ArgTys, /*isVarArg=*/true);
786+
787+
assert(TableRef.empty() && "Unprocessed descriptors remain");
788+
789+
return getOrInsertIntrinsicDeclarationImpl(M, id, OverloadTys, FTy);
790+
}
791+
754792
Function *Intrinsic::getDeclarationIfExists(const Module *M, ID id) {
755793
return M->getFunction(getName(id));
756794
}

llvm/unittests/IR/IntrinsicsTest.cpp

Lines changed: 211 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,12 @@
3030
using namespace llvm;
3131

3232
namespace {
33-
3433
class IntrinsicsTest : public ::testing::Test {
34+
protected:
3535
LLVMContext Context;
3636
std::unique_ptr<Module> M;
3737
BasicBlock *BB = nullptr;
3838

39-
void TearDown() override { M.reset(); }
40-
4139
void SetUp() override {
4240
M = std::make_unique<Module>("Test", Context);
4341
auto F = M->getOrInsertFunction(
@@ -46,6 +44,8 @@ class IntrinsicsTest : public ::testing::Test {
4644
EXPECT_NE(BB, nullptr);
4745
}
4846

47+
void TearDown() override { M.reset(); }
48+
4949
public:
5050
Instruction *makeIntrinsic(Intrinsic::ID ID) const {
5151
IRBuilder<> Builder(BB);
@@ -197,4 +197,212 @@ TEST(IntrinsicAttributes, TestGetFnAttributesBug) {
197197
AttributeSet AS = getFnAttributes(Context, experimental_guard);
198198
EXPECT_FALSE(AS.hasAttributes());
199199
}
200+
201+
// Tests non-overloaded intrinsic declaration.
202+
TEST_F(IntrinsicsTest, NonOverloadedIntrinsic) {
203+
Type *RetTy = Type::getVoidTy(Context);
204+
SmallVector<Type *, 1> ArgTys;
205+
ArgTys.push_back(Type::getInt1Ty(Context));
206+
207+
Function *F = Intrinsic::getOrInsertDeclaration(M.get(), Intrinsic::assume,
208+
RetTy, ArgTys);
209+
210+
ASSERT_NE(F, nullptr);
211+
EXPECT_EQ(F->getIntrinsicID(), Intrinsic::assume);
212+
EXPECT_EQ(F->getReturnType(), RetTy);
213+
EXPECT_EQ(F->arg_size(), 1u);
214+
EXPECT_FALSE(F->isVarArg());
215+
EXPECT_EQ(F->getName(), "llvm.assume");
216+
}
217+
218+
// Tests overloaded intrinsic with automatic type resolution for scalar types.
219+
TEST_F(IntrinsicsTest, OverloadedIntrinsicScalar) {
220+
Type *RetTy = Type::getInt32Ty(Context);
221+
SmallVector<Type *, 2> ArgTys;
222+
ArgTys.push_back(Type::getInt32Ty(Context));
223+
ArgTys.push_back(Type::getInt32Ty(Context));
224+
225+
Function *F = Intrinsic::getOrInsertDeclaration(M.get(), Intrinsic::umax,
226+
RetTy, ArgTys);
227+
228+
ASSERT_NE(F, nullptr);
229+
EXPECT_EQ(F->getIntrinsicID(), Intrinsic::umax);
230+
EXPECT_EQ(F->getReturnType(), RetTy);
231+
EXPECT_EQ(F->arg_size(), 2u);
232+
EXPECT_FALSE(F->isVarArg());
233+
EXPECT_EQ(F->getName(), "llvm.umax.i32");
234+
}
235+
236+
// Tests overloaded intrinsic with automatic type resolution for vector types.
237+
TEST_F(IntrinsicsTest, OverloadedIntrinsicVector) {
238+
Type *RetTy = FixedVectorType::get(Type::getInt32Ty(Context), 4);
239+
SmallVector<Type *, 2> ArgTys;
240+
ArgTys.push_back(RetTy);
241+
ArgTys.push_back(RetTy);
242+
243+
Function *F = Intrinsic::getOrInsertDeclaration(M.get(), Intrinsic::umax,
244+
RetTy, ArgTys);
245+
246+
ASSERT_NE(F, nullptr);
247+
EXPECT_EQ(F->getIntrinsicID(), Intrinsic::umax);
248+
EXPECT_EQ(F->getReturnType(), RetTy);
249+
EXPECT_EQ(F->arg_size(), 2u);
250+
EXPECT_FALSE(F->isVarArg());
251+
EXPECT_EQ(F->getName(), "llvm.umax.v4i32");
252+
}
253+
254+
// Tests overloaded intrinsic with automatic type resolution for addrspace.
255+
TEST_F(IntrinsicsTest, OverloadedIntrinsicAddressSpace) {
256+
Type *RetTy = Type::getVoidTy(Context);
257+
SmallVector<Type *, 4> ArgTys;
258+
ArgTys.push_back(PointerType::get(Context, 1)); // ptr addrspace(1)
259+
ArgTys.push_back(Type::getInt32Ty(Context)); // rw
260+
ArgTys.push_back(Type::getInt32Ty(Context)); // locality
261+
ArgTys.push_back(Type::getInt32Ty(Context)); // cache type
262+
263+
Function *F = Intrinsic::getOrInsertDeclaration(M.get(), Intrinsic::prefetch,
264+
RetTy, ArgTys);
265+
266+
ASSERT_NE(F, nullptr);
267+
EXPECT_EQ(F->getIntrinsicID(), Intrinsic::prefetch);
268+
EXPECT_EQ(F->getReturnType(), RetTy);
269+
EXPECT_EQ(F->arg_size(), 4u);
270+
EXPECT_FALSE(F->isVarArg());
271+
EXPECT_EQ(F->getName(), "llvm.prefetch.p1");
272+
}
273+
274+
// Tests vararg intrinsic declaration.
275+
TEST_F(IntrinsicsTest, VarArgIntrinsicStatepoint) {
276+
Type *RetTy = Type::getTokenTy(Context);
277+
SmallVector<Type *, 5> ArgTys;
278+
ArgTys.push_back(Type::getInt64Ty(Context)); // ID
279+
ArgTys.push_back(Type::getInt32Ty(Context)); // NumPatchBytes
280+
ArgTys.push_back(PointerType::get(Context, 0)); // Target
281+
ArgTys.push_back(Type::getInt32Ty(Context)); // NumCallArgs
282+
ArgTys.push_back(Type::getInt32Ty(Context)); // Flags
283+
284+
Function *F = Intrinsic::getOrInsertDeclaration(
285+
M.get(), Intrinsic::experimental_gc_statepoint, RetTy, ArgTys);
286+
287+
ASSERT_NE(F, nullptr);
288+
EXPECT_EQ(F->getIntrinsicID(), Intrinsic::experimental_gc_statepoint);
289+
EXPECT_EQ(F->getReturnType(), RetTy);
290+
EXPECT_EQ(F->arg_size(), 5u);
291+
EXPECT_TRUE(F->isVarArg()) << "experimental_gc_statepoint must be vararg";
292+
EXPECT_EQ(F->getName(), "llvm.experimental.gc.statepoint.p0");
293+
}
294+
295+
// Tests that different overloads create different declarations.
296+
TEST_F(IntrinsicsTest, DifferentOverloads) {
297+
// i32 version
298+
Type *RetTy32 = Type::getInt32Ty(Context);
299+
SmallVector<Type *, 2> ArgTys32;
300+
ArgTys32.push_back(Type::getInt32Ty(Context));
301+
ArgTys32.push_back(Type::getInt32Ty(Context));
302+
303+
Function *Func32 = Intrinsic::getOrInsertDeclaration(M.get(), Intrinsic::umax,
304+
RetTy32, ArgTys32);
305+
306+
// i64 version
307+
Type *RetTy64 = Type::getInt64Ty(Context);
308+
SmallVector<Type *, 2> ArgTys64;
309+
ArgTys64.push_back(Type::getInt64Ty(Context));
310+
ArgTys64.push_back(Type::getInt64Ty(Context));
311+
312+
Function *Func64 = Intrinsic::getOrInsertDeclaration(M.get(), Intrinsic::umax,
313+
RetTy64, ArgTys64);
314+
315+
EXPECT_NE(Func32, Func64)
316+
<< "Different overloads should be different functions";
317+
EXPECT_EQ(Func32->getName(), "llvm.umax.i32");
318+
EXPECT_EQ(Func64->getName(), "llvm.umax.i64");
319+
}
320+
321+
// Tests IRBuilder::CreateIntrinsic with overloaded scalar type.
322+
TEST_F(IntrinsicsTest, IRBuilderCreateIntrinsicScalar) {
323+
IRBuilder<> Builder(BB);
324+
325+
Type *RetTy = Type::getInt32Ty(Context);
326+
SmallVector<Value *, 2> Args;
327+
Args.push_back(ConstantInt::get(Type::getInt32Ty(Context), 10));
328+
Args.push_back(ConstantInt::get(Type::getInt32Ty(Context), 20));
329+
330+
CallInst *CI = Builder.CreateIntrinsic(RetTy, Intrinsic::umax, Args);
331+
332+
ASSERT_NE(CI, nullptr);
333+
EXPECT_EQ(CI->getIntrinsicID(), Intrinsic::umax);
334+
EXPECT_EQ(CI->getType(), RetTy);
335+
EXPECT_EQ(CI->arg_size(), 2u);
336+
EXPECT_FALSE(CI->getCalledFunction()->isVarArg());
337+
}
338+
339+
// Tests IRBuilder::CreateIntrinsic with overloaded vector type.
340+
TEST_F(IntrinsicsTest, IRBuilderCreateIntrinsicVector) {
341+
IRBuilder<> Builder(BB);
342+
343+
Type *RetTy = FixedVectorType::get(Type::getInt32Ty(Context), 4);
344+
SmallVector<Value *, 2> Args;
345+
Args.push_back(Constant::getNullValue(RetTy));
346+
Args.push_back(Constant::getNullValue(RetTy));
347+
348+
CallInst *CI = Builder.CreateIntrinsic(RetTy, Intrinsic::umax, Args);
349+
350+
ASSERT_NE(CI, nullptr);
351+
EXPECT_EQ(CI->getIntrinsicID(), Intrinsic::umax);
352+
EXPECT_EQ(CI->getType(), RetTy);
353+
EXPECT_EQ(CI->arg_size(), 2u);
354+
EXPECT_FALSE(CI->getCalledFunction()->isVarArg());
355+
}
356+
357+
// Tests IRBuilder::CreateIntrinsic with overloaded address space.
358+
TEST_F(IntrinsicsTest, IRBuilderCreateIntrinsicAddressSpace) {
359+
IRBuilder<> Builder(BB);
360+
361+
Type *RetTy = Type::getVoidTy(Context);
362+
SmallVector<Value *, 4> Args;
363+
Args.push_back(Constant::getNullValue(
364+
PointerType::get(Context, 1))); // ptr addrspace(1) null
365+
Args.push_back(ConstantInt::get(Type::getInt32Ty(Context), 0)); // rw
366+
Args.push_back(ConstantInt::get(Type::getInt32Ty(Context), 3)); // locality
367+
Args.push_back(ConstantInt::get(Type::getInt32Ty(Context), 1)); // cache type
368+
369+
CallInst *CI = Builder.CreateIntrinsic(RetTy, Intrinsic::prefetch, Args);
370+
371+
ASSERT_NE(CI, nullptr);
372+
EXPECT_EQ(CI->getIntrinsicID(), Intrinsic::prefetch);
373+
EXPECT_EQ(CI->getType(), RetTy);
374+
EXPECT_EQ(CI->arg_size(), 4u);
375+
EXPECT_FALSE(CI->getCalledFunction()->isVarArg());
376+
EXPECT_EQ(CI->getCalledFunction()->getName(), "llvm.prefetch.p1");
377+
}
378+
379+
// Tests IRBuilder::CreateIntrinsic with vararg intrinsic.
380+
TEST_F(IntrinsicsTest, IRBuilderCreateIntrinsicVarArg) {
381+
IRBuilder<> Builder(BB);
382+
383+
// Create a dummy function to call through statepoint
384+
FunctionType *DummyFnTy = FunctionType::get(Type::getVoidTy(Context), false);
385+
Function *DummyFn = Function::Create(DummyFnTy, GlobalValue::ExternalLinkage,
386+
"dummy", M.get());
387+
388+
Type *RetTy = Type::getTokenTy(Context);
389+
SmallVector<Value *, 5> Args;
390+
Args.push_back(ConstantInt::get(Type::getInt64Ty(Context), 0)); // ID
391+
Args.push_back(
392+
ConstantInt::get(Type::getInt32Ty(Context), 0)); // NumPatchBytes
393+
Args.push_back(DummyFn); // Target
394+
Args.push_back(ConstantInt::get(Type::getInt32Ty(Context), 0)); // NumCallArgs
395+
Args.push_back(ConstantInt::get(Type::getInt32Ty(Context), 0)); // Flags
396+
397+
CallInst *CI = Builder.CreateIntrinsic(
398+
RetTy, Intrinsic::experimental_gc_statepoint, Args);
399+
400+
ASSERT_NE(CI, nullptr);
401+
EXPECT_EQ(CI->getIntrinsicID(), Intrinsic::experimental_gc_statepoint);
402+
EXPECT_EQ(CI->getType(), RetTy);
403+
EXPECT_EQ(CI->arg_size(), 5u);
404+
EXPECT_TRUE(CI->getCalledFunction()->isVarArg())
405+
<< "experimental_gc_statepoint must be vararg";
406+
}
407+
200408
} // end namespace

0 commit comments

Comments
 (0)