Skip to content

Commit 89f8ba2

Browse files
authored
[AsmParser] Support calling intrinsics without mangling suffix (#89172)
This adds proper support for calling intrinsics without mangling suffix when parsing textual IR. This already worked (mostly by accident) when only a single mangling suffix was in use. This patch extends support to the case where the intrinsic is used with multiple signatures, and as such multiple different intrinsic declarations have to be inserted. The final IR will have intrinsics with mangling suffix as usual. Motivated by the discussion at: https://discourse.llvm.org/t/recent-improvements-to-the-ir-parser/77366
1 parent eb7ad88 commit 89f8ba2

7 files changed

+107
-55
lines changed

llvm/include/llvm/IR/Intrinsics.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,12 @@ namespace Intrinsic {
235235
/// specified by the .td file. The overloaded types are pushed into the
236236
/// AgTys vector.
237237
///
238-
/// Returns false if the given function is not a valid intrinsic call.
238+
/// Returns false if the given ID and function type combination is not a
239+
/// valid intrinsic call.
240+
bool getIntrinsicSignature(Intrinsic::ID, FunctionType *FT,
241+
SmallVectorImpl<Type *> &ArgTys);
242+
243+
/// Same as previous, but accepts a Function instead of ID and FunctionType.
239244
bool getIntrinsicSignature(Function *F, SmallVectorImpl<Type *> &ArgTys);
240245

241246
// Checks if the intrinsic name matches with its signature and if not

llvm/lib/AsmParser/LLParser.cpp

Lines changed: 52 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,42 @@ bool LLParser::validateEndOfModule(bool UpgradeDebugInfo) {
326326
ForwardRefComdats.begin()->first + "'");
327327

328328
for (const auto &[Name, Info] : make_early_inc_range(ForwardRefVals)) {
329+
if (StringRef(Name).starts_with("llvm.")) {
330+
Intrinsic::ID IID = Function::lookupIntrinsicID(Name);
331+
if (IID == Intrinsic::not_intrinsic)
332+
// Don't do anything for unknown intrinsics.
333+
continue;
334+
335+
// Automatically create declarations for intrinsics. Intrinsics can only
336+
// be called directly, so the call function type directly determines the
337+
// declaration function type.
338+
//
339+
// Additionally, automatically add the required mangling suffix to the
340+
// intrinsic name. This means that we may replace a single forward
341+
// declaration with multiple functions here.
342+
for (Use &U : make_early_inc_range(Info.first->uses())) {
343+
auto *CB = dyn_cast<CallBase>(U.getUser());
344+
if (!CB || !CB->isCallee(&U))
345+
return error(Info.second, "intrinsic can only be used as callee");
346+
347+
SmallVector<Type *> OverloadTys;
348+
if (!Intrinsic::getIntrinsicSignature(IID, CB->getFunctionType(),
349+
OverloadTys))
350+
return error(Info.second, "invalid intrinsic signature");
351+
352+
U.set(Intrinsic::getDeclaration(M, IID, OverloadTys));
353+
}
354+
355+
Info.first->eraseFromParent();
356+
ForwardRefVals.erase(Name);
357+
continue;
358+
}
359+
360+
// If incomplete IR is allowed, also add declarations for
361+
// non-intrinsics.
362+
if (!AllowIncompleteIR)
363+
continue;
364+
329365
auto GetCommonFunctionType = [](Value *V) -> FunctionType * {
330366
FunctionType *FTy = nullptr;
331367
for (Use &U : V->uses()) {
@@ -337,40 +373,23 @@ bool LLParser::validateEndOfModule(bool UpgradeDebugInfo) {
337373
return FTy;
338374
};
339375

340-
auto GetDeclarationType = [&](StringRef Name, Value *V) -> Type * {
341-
// Automatically create declarations for intrinsics. Intrinsics can only
342-
// be called directly, so the call function type directly determines the
343-
// declaration function type.
344-
if (Name.starts_with("llvm."))
345-
// Don't do anything if the intrinsic is called with different function
346-
// types. This would result in a verifier error anyway.
347-
return GetCommonFunctionType(V);
348-
349-
if (AllowIncompleteIR) {
350-
// If incomplete IR is allowed, also add declarations for
351-
// non-intrinsics. First check whether this global is only used in
352-
// calls with the same type, in which case we'll insert a function.
353-
if (auto *Ty = GetCommonFunctionType(V))
354-
return Ty;
355-
356-
// Otherwise, fall back to using a dummy i8 type.
357-
return Type::getInt8Ty(Context);
358-
}
359-
return nullptr;
360-
};
376+
// First check whether this global is only used in calls with the same
377+
// type, in which case we'll insert a function. Otherwise, fall back to
378+
// using a dummy i8 type.
379+
Type *Ty = GetCommonFunctionType(Info.first);
380+
if (!Ty)
381+
Ty = Type::getInt8Ty(Context);
361382

362-
if (Type *Ty = GetDeclarationType(Name, Info.first)) {
363-
GlobalValue *GV;
364-
if (auto *FTy = dyn_cast<FunctionType>(Ty))
365-
GV = Function::Create(FTy, GlobalValue::ExternalLinkage, Name, M);
366-
else
367-
GV = new GlobalVariable(*M, Ty, /*isConstant*/ false,
368-
GlobalValue::ExternalLinkage,
369-
/*Initializer*/ nullptr, Name);
370-
Info.first->replaceAllUsesWith(GV);
371-
Info.first->eraseFromParent();
372-
ForwardRefVals.erase(Name);
373-
}
383+
GlobalValue *GV;
384+
if (auto *FTy = dyn_cast<FunctionType>(Ty))
385+
GV = Function::Create(FTy, GlobalValue::ExternalLinkage, Name, M);
386+
else
387+
GV = new GlobalVariable(*M, Ty, /*isConstant*/ false,
388+
GlobalValue::ExternalLinkage,
389+
/*Initializer*/ nullptr, Name);
390+
Info.first->replaceAllUsesWith(GV);
391+
Info.first->eraseFromParent();
392+
ForwardRefVals.erase(Name);
374393
}
375394

376395
if (!ForwardRefVals.empty())

llvm/lib/IR/Function.cpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1742,27 +1742,30 @@ Intrinsic::matchIntrinsicVarArg(bool isVarArg,
17421742
return true;
17431743
}
17441744

1745-
bool Intrinsic::getIntrinsicSignature(Function *F,
1745+
bool Intrinsic::getIntrinsicSignature(Intrinsic::ID ID, FunctionType *FT,
17461746
SmallVectorImpl<Type *> &ArgTys) {
1747-
Intrinsic::ID ID = F->getIntrinsicID();
17481747
if (!ID)
17491748
return false;
17501749

17511750
SmallVector<Intrinsic::IITDescriptor, 8> Table;
17521751
getIntrinsicInfoTableEntries(ID, Table);
17531752
ArrayRef<Intrinsic::IITDescriptor> TableRef = Table;
17541753

1755-
if (Intrinsic::matchIntrinsicSignature(F->getFunctionType(), TableRef,
1756-
ArgTys) !=
1754+
if (Intrinsic::matchIntrinsicSignature(FT, TableRef, ArgTys) !=
17571755
Intrinsic::MatchIntrinsicTypesResult::MatchIntrinsicTypes_Match) {
17581756
return false;
17591757
}
1760-
if (Intrinsic::matchIntrinsicVarArg(F->getFunctionType()->isVarArg(),
1761-
TableRef))
1758+
if (Intrinsic::matchIntrinsicVarArg(FT->isVarArg(), TableRef))
17621759
return false;
17631760
return true;
17641761
}
17651762

1763+
bool Intrinsic::getIntrinsicSignature(Function *F,
1764+
SmallVectorImpl<Type *> &ArgTys) {
1765+
return getIntrinsicSignature(F->getIntrinsicID(), F->getFunctionType(),
1766+
ArgTys);
1767+
}
1768+
17661769
std::optional<Function *> Intrinsic::remangleIntrinsicFunction(Function *F) {
17671770
SmallVector<Type *, 4> ArgTys;
17681771
if (!getIntrinsicSignature(F, ArgTys))
Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
; RUN: not llvm-as < %s 2>&1 | FileCheck %s
22

3-
; Check that intrinsics do not get automatically declared if they are used
4-
; with different function types.
3+
; Use of intrinsic without mangling suffix and invalid signature should
4+
; be rejected.
55

6-
; CHECK: error: use of undefined value '@llvm.umax'
6+
; CHECK: error: invalid intrinsic signature
77
define void @test() {
8-
call i8 @llvm.umax(i8 0, i8 1)
9-
call i16 @llvm.umax(i16 0, i16 1)
8+
call i8 @llvm.umax(i8 0, i16 1)
109
ret void
1110
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
; RUN: not llvm-as < %s 2>&1 | FileCheck %s
2+
3+
; Use of intrinsic as non-callee should be rejected.
4+
5+
; CHECK: error: intrinsic can only be used as callee
6+
define void @test() {
7+
call void @foo(ptr @llvm.umax)
8+
ret void
9+
}
10+
11+
declare void @foo(ptr)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
; RUN: not llvm-as < %s 2>&1 | FileCheck %s
2+
3+
; Use of unknown intrinsic without declaration should be rejected.
4+
5+
; CHECK: error: use of undefined value '@llvm.foobar'
6+
define void @test() {
7+
call i8 @llvm.foobar(i8 0, i16 1)
8+
ret void
9+
}

llvm/test/Assembler/implicit-intrinsic-declaration.ll

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,38 @@
22
; RUN: opt -S < %s | FileCheck %s
33
; RUN: opt -S -passes=instcombine < %s | FileCheck %s --check-prefix=INSTCOMBINE
44

5-
; llvm.umax is intentionally missing the mangling suffix here, to show that
6-
; this works fine with auto-upgrade.
7-
define i16 @test(i8 %x, i8 %y) {
8-
; CHECK-LABEL: define i16 @test(
5+
; Two of the llvm.umax calls are intentionally missing the mangling suffix here,
6+
; to show that it will be added automatically.
7+
define i32 @test(i8 %x, i8 %y) {
8+
; CHECK-LABEL: define i32 @test(
99
; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
1010
; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i8 [[X]], -1
1111
; CHECK-NEXT: call void @llvm.assume(i1 [[CMP]])
1212
; CHECK-NEXT: [[MAX1:%.*]] = call i8 @llvm.umax.i8(i8 [[X]], i8 [[Y]])
1313
; CHECK-NEXT: [[X_EXT:%.*]] = zext i8 [[X]] to i16
1414
; CHECK-NEXT: [[Y_EXT:%.*]] = zext i8 [[Y]] to i16
1515
; CHECK-NEXT: [[MAX2:%.*]] = call i16 @llvm.umax.i16(i16 [[X_EXT]], i16 [[Y_EXT]])
16-
; CHECK-NEXT: ret i16 [[MAX2]]
16+
; CHECK-NEXT: [[X_EXT2:%.*]] = zext i8 [[X]] to i32
17+
; CHECK-NEXT: [[Y_EXT2:%.*]] = zext i8 [[Y]] to i32
18+
; CHECK-NEXT: [[MAX3:%.*]] = call i32 @llvm.umax.i32(i32 [[X_EXT2]], i32 [[Y_EXT2]])
19+
; CHECK-NEXT: ret i32 [[MAX3]]
1720
;
18-
; INSTCOMBINE-LABEL: define i16 @test(
21+
; INSTCOMBINE-LABEL: define i32 @test(
1922
; INSTCOMBINE-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
2023
; INSTCOMBINE-NEXT: [[CMP:%.*]] = icmp sgt i8 [[X]], -1
2124
; INSTCOMBINE-NEXT: call void @llvm.assume(i1 [[CMP]])
2225
; INSTCOMBINE-NEXT: [[TMP1:%.*]] = call i8 @llvm.umax.i8(i8 [[X]], i8 [[Y]])
23-
; INSTCOMBINE-NEXT: [[MAX2:%.*]] = zext i8 [[TMP1]] to i16
24-
; INSTCOMBINE-NEXT: ret i16 [[MAX2]]
26+
; INSTCOMBINE-NEXT: [[MAX3:%.*]] = zext i8 [[TMP1]] to i32
27+
; INSTCOMBINE-NEXT: ret i32 [[MAX3]]
2528
;
2629
%cmp = icmp sgt i8 %x, -1
2730
call void @llvm.assume(i1 %cmp)
2831
%max1 = call i8 @llvm.umax(i8 %x, i8 %y)
2932
%x.ext = zext i8 %x to i16
3033
%y.ext = zext i8 %y to i16
31-
%max2 = call i16 @llvm.umax.i16(i16 %x.ext, i16 %y.ext)
32-
ret i16 %max2
34+
%max2 = call i16 @llvm.umax(i16 %x.ext, i16 %y.ext)
35+
%x.ext2 = zext i8 %x to i32
36+
%y.ext2 = zext i8 %y to i32
37+
%max3 = call i32 @llvm.umax.i32(i32 %x.ext2, i32 %y.ext2)
38+
ret i32 %max3
3339
}

0 commit comments

Comments
 (0)