[clang] [llvm] Get intrinsic info table entries refactor 1 1 (PR #196802)
Rahul Joshi via cfe-commits
cfe-commits at lists.llvm.org
Sun May 10 06:58:18 PDT 2026
https://github.com/jurahul created https://github.com/llvm/llvm-project/pull/196802
None
>From 17193f5278366075a7d72f46be48cf1a7037b519 Mon Sep 17 00:00:00 2001
From: Rahul Joshi <rjoshi at nvidia.com>
Date: Fri, 8 May 2026 08:58:06 -0700
Subject: [PATCH 1/3] [LLVM][Intrinsics] Extend `getIntrinsicInfoTableEntries`
to return num args
---
clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp | 12 +--
llvm/include/llvm/IR/Intrinsics.h | 15 +++-
llvm/lib/IR/Intrinsics.cpp | 84 ++++++++++---------
...implicit-intrinsic-declaration-invalid4.ll | 10 +++
4 files changed, 67 insertions(+), 54 deletions(-)
create mode 100644 llvm/test/Assembler/implicit-intrinsic-declaration-invalid4.ll
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
index fe932834e9b55..868bca404949b 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -955,21 +955,13 @@ static cir::FuncType getIntrinsicType(CIRGenFunction &cgf,
using namespace llvm::Intrinsic;
SmallVector<IITDescriptor, 8> table;
- getIntrinsicInfoTableEntries(id, table);
+ auto [tableRef, _, isVarArg] = getIntrinsicInfoTableEntries(id, table);
- ArrayRef<IITDescriptor> tableRef = table;
mlir::Type resultTy = decodeFixedType(cgf, tableRef, context);
SmallVector<mlir::Type, 8> argTypes;
- bool isVarArg = false;
- while (!tableRef.empty()) {
- IITDescriptor::IITDescriptorKind kind = tableRef.front().Kind;
- if (kind == IITDescriptor::VarArg) {
- isVarArg = true;
- break; // VarArg is last
- }
+ while (!tableRef.empty())
argTypes.push_back(decodeFixedType(cgf, tableRef, context));
- }
// CIR convention: no explicit void return type
if (isa<cir::VoidType>(resultTy))
diff --git a/llvm/include/llvm/IR/Intrinsics.h b/llvm/include/llvm/IR/Intrinsics.h
index ae931c7961161..af97285fcee70 100644
--- a/llvm/include/llvm/IR/Intrinsics.h
+++ b/llvm/include/llvm/IR/Intrinsics.h
@@ -20,6 +20,7 @@
#include "llvm/Support/TypeSize.h"
#include <optional>
#include <string>
+#include <tuple>
namespace llvm {
@@ -259,10 +260,16 @@ struct IITDescriptor {
/// Returns true if \p id has a struct return type.
LLVM_ABI bool hasStructReturnType(ID id);
-/// Return the IIT table descriptor for the specified intrinsic into an array
-/// of IITDescriptors.
-LLVM_ABI void getIntrinsicInfoTableEntries(ID id,
- SmallVectorImpl<IITDescriptor> &T);
+/// Fill the IIT table descriptor for the intrinsic \p id into an array
+/// of IITDescriptors. Returns a tuple of 3 values:
+/// - ArrayRef for the descriptor table (for convenience).
+/// - Number of arguments.
+/// - if it's a variable argument intrinsic.
+///
+/// Note that for VarArg intrinsics, the last IIT `VarArg` token will be
+/// consumed and not a part of the returned ArrayRef.
+LLVM_ABI std::tuple<ArrayRef<IITDescriptor>, unsigned, bool>
+getIntrinsicInfoTableEntries(ID id, SmallVectorImpl<IITDescriptor> &T);
/// Returns true if \p FT is a valid function type for intrinsic \p ID. If
/// `ID` is an overloaded intrinsic, the overload types are pushed into the
diff --git a/llvm/lib/IR/Intrinsics.cpp b/llvm/lib/IR/Intrinsics.cpp
index ff57d335c9a13..1ae2fa8b01811 100644
--- a/llvm/lib/IR/Intrinsics.cpp
+++ b/llvm/lib/IR/Intrinsics.cpp
@@ -38,10 +38,9 @@
using namespace llvm;
// Forward declaration of static functions.
-static bool isIntrinsicVarArg(ArrayRef<Intrinsic::IITDescriptor> &Infos,
- bool Consume);
static bool isSignatureValid(FunctionType *FTy,
ArrayRef<Intrinsic::IITDescriptor> &Infos,
+ unsigned NumArgs, bool IsVarArg,
SmallVectorImpl<Type *> &OverloadTys,
raw_ostream &OS);
@@ -398,6 +397,8 @@ DecodeIITType(unsigned &NextElt, ArrayRef<unsigned char> Infos,
unsigned OverloadIndex = Infos[NextElt++];
OutputTable.push_back(
IITDescriptor::get(IITDescriptor::SameVecWidth, OverloadIndex));
+ // IIT_SAME_VEC_WIDTH_ARG entry is followed by the element type.
+ DecodeIITType(NextElt, Infos, OutputTable);
return;
}
case IIT_VEC_OF_ANYPTRS_TO_ELT: {
@@ -451,8 +452,9 @@ DecodeIITType(unsigned &NextElt, ArrayRef<unsigned char> Infos,
#define GET_INTRINSIC_GENERATOR_GLOBAL
#include "llvm/IR/IntrinsicImpl.inc"
-void Intrinsic::getIntrinsicInfoTableEntries(
- ID id, SmallVectorImpl<IITDescriptor> &T) {
+std::tuple<ArrayRef<Intrinsic::IITDescriptor>, unsigned, bool>
+Intrinsic::getIntrinsicInfoTableEntries(ID id,
+ SmallVectorImpl<IITDescriptor> &T) {
// Note that `FixedEncodingTy` is defined in IntrinsicImpl.inc and can be
// uint16_t or uint32_t based on the the value of `Use16BitFixedEncoding` in
// IntrinsicEmitter.cpp.
@@ -497,8 +499,21 @@ void Intrinsic::getIntrinsicInfoTableEntries(
// Okay, decode the table into the output vector of IITDescriptors.
DecodeIITType(NextElt, IITEntries, T);
- while (IITEntries[NextElt] != IIT_Done)
+ unsigned NumArgs = 0;
+ while (IITEntries[NextElt] != IIT_Done) {
DecodeIITType(NextElt, IITEntries, T);
+ ++NumArgs;
+ }
+
+ ArrayRef<IITDescriptor> TableRef = T;
+
+ bool IsVarArg = false;
+ if (TableRef.back().Kind == Intrinsic::IITDescriptor::VarArg) {
+ IsVarArg = true;
+ TableRef.consume_back();
+ --NumArgs;
+ }
+ return {TableRef, NumArgs, IsVarArg};
}
static Type *DecodeFixedType(ArrayRef<Intrinsic::IITDescriptor> &Infos,
@@ -511,8 +526,6 @@ static Type *DecodeFixedType(ArrayRef<Intrinsic::IITDescriptor> &Infos,
switch (D.Kind) {
case IITDescriptor::Void:
return Type::getVoidTy(Context);
- case IITDescriptor::VarArg:
- return Type::getVoidTy(Context);
case IITDescriptor::MMX:
return llvm::FixedVectorType::get(llvm::IntegerType::get(Context, 64), 1);
case IITDescriptor::AMX:
@@ -589,6 +602,10 @@ static Type *DecodeFixedType(ArrayRef<Intrinsic::IITDescriptor> &Infos,
assert(VTy && "Expected overload type to be a Vector Type");
return VectorType::getInteger(VTy);
}
+ case IITDescriptor::VarArg:
+ // VarArg token should be consumed by `getIntrinsicInfoTableEntries`, so we
+ // should never see it here.
+ break;
}
llvm_unreachable("unhandled");
}
@@ -596,10 +613,7 @@ static Type *DecodeFixedType(ArrayRef<Intrinsic::IITDescriptor> &Infos,
FunctionType *Intrinsic::getType(LLVMContext &Context, ID id,
ArrayRef<Type *> OverloadTys) {
SmallVector<IITDescriptor, 8> Table;
- getIntrinsicInfoTableEntries(id, Table);
- ArrayRef<IITDescriptor> TableRef = Table;
-
- bool IsVarArg = isIntrinsicVarArg(TableRef, /*Consume=*/true);
+ auto [TableRef, _, IsVarArg] = getIntrinsicInfoTableEntries(id, Table);
Type *ResultTy = DecodeFixedType(TableRef, OverloadTys, Context);
@@ -777,16 +791,13 @@ Function *Intrinsic::getOrInsertDeclaration(Module *M, ID id, Type *RetTy,
// Get the intrinsic signature metadata.
SmallVector<Intrinsic::IITDescriptor, 8> Table;
- getIntrinsicInfoTableEntries(id, Table);
- ArrayRef<Intrinsic::IITDescriptor> TableRef = Table;
- bool IsVarArg = isIntrinsicVarArg(TableRef, /*Consume=*/false);
-
+ auto [TableRef, NumArgs, IsVarArg] = getIntrinsicInfoTableEntries(id, Table);
FunctionType *FTy = FunctionType::get(RetTy, ArgTys, IsVarArg);
// Automatically determine the overloaded types.
SmallVector<Type *, 4> OverloadTys;
- [[maybe_unused]] bool IsValid =
- ::isSignatureValid(FTy, TableRef, OverloadTys, nulls());
+ [[maybe_unused]] bool IsValid = ::isSignatureValid(
+ FTy, TableRef, NumArgs, IsVarArg, OverloadTys, nulls());
assert(IsValid && "intrinsic signature mismatch");
return getOrInsertIntrinsicDeclarationImpl(M, id, OverloadTys, FTy);
}
@@ -859,8 +870,6 @@ matchIntrinsicType(Type *Ty, ArrayRef<Intrinsic::IITDescriptor> &Infos,
switch (D.Kind) {
case IITDescriptor::Void:
return !Ty->isVoidTy();
- case IITDescriptor::VarArg:
- return true;
case IITDescriptor::MMX: {
FixedVectorType *VT = dyn_cast<FixedVectorType>(Ty);
return !VT || VT->getNumElements() != 1 ||
@@ -1050,44 +1059,40 @@ matchIntrinsicType(Type *Ty, ArrayRef<Intrinsic::IITDescriptor> &Infos,
return true;
return ThisArgVecTy != VectorType::getInteger(ReferenceType);
}
+ case IITDescriptor::VarArg:
+ // VarArg token should be consumed by `getIntrinsicInfoTableEntries`, so we
+ // should never see it here.
+ break;
}
llvm_unreachable("unhandled");
}
-/// Returns true if the intrinsic is a VarArg intrinsics. If \p Consume is true
-/// the IITDescriptor for the VarArg is consumed and removed from \p Infos, else
-/// it stays unchanged.
-static bool isIntrinsicVarArg(ArrayRef<Intrinsic::IITDescriptor> &Infos,
- bool Consume) {
- if (!Infos.empty() && Infos.back().Kind == Intrinsic::IITDescriptor::VarArg) {
- if (Consume)
- Infos.consume_back();
- return true;
- }
- return false;
-}
-
/// Return true if the function type \p FTy is a valid type signature for the
-/// type constraints specified in the .td file, represented by \p Infos.
-/// The overloaded type for the intrinsic are pushed to the OverloadTys vector.
+/// type constraints specified in the .td file, represented by \p Infos and
+/// \p IsVarArg. The overloaded type for the intrinsic are pushed to the
+/// \p OverloadTys vector.
///
/// If the type is not valid, returns false and prints an error message to
/// \p OS.
static bool isSignatureValid(FunctionType *FTy,
ArrayRef<Intrinsic::IITDescriptor> &Infos,
+ unsigned NumArgs, bool IsVarArg,
SmallVectorImpl<Type *> &OverloadTys,
raw_ostream &OS) {
- bool IsVarArg = isIntrinsicVarArg(Infos, /*Consume=*/true);
-
SmallVector<DeferredIntrinsicMatchPair, 2> DeferredChecks;
if (matchIntrinsicType(FTy->getReturnType(), Infos, OverloadTys,
DeferredChecks, false)) {
OS << "intrinsic has incorrect return type!";
return false;
}
-
unsigned NumDeferredReturnChecks = DeferredChecks.size();
+ if (FTy->getNumParams() != NumArgs) {
+ OS << "intrinsic has incorrect number of args. Expected " << NumArgs
+ << ", but got " << FTy->getNumParams();
+ return false;
+ }
+
for (Type *Ty : FTy->params()) {
if (matchIntrinsicType(Ty, Infos, OverloadTys, DeferredChecks, false)) {
OS << "intrinsic has incorrect argument type!";
@@ -1137,10 +1142,9 @@ bool Intrinsic::isSignatureValid(Intrinsic::ID ID, FunctionType *FT,
return false;
SmallVector<Intrinsic::IITDescriptor, 8> Table;
- getIntrinsicInfoTableEntries(ID, Table);
- ArrayRef<Intrinsic::IITDescriptor> TableRef = Table;
+ auto [TableRef, NumArgs, IsVarArg] = getIntrinsicInfoTableEntries(ID, Table);
- return ::isSignatureValid(FT, TableRef, OverloadTys, OS);
+ return ::isSignatureValid(FT, TableRef, NumArgs, IsVarArg, OverloadTys, OS);
}
bool Intrinsic::isSignatureValid(Function *F,
diff --git a/llvm/test/Assembler/implicit-intrinsic-declaration-invalid4.ll b/llvm/test/Assembler/implicit-intrinsic-declaration-invalid4.ll
new file mode 100644
index 0000000000000..6ce373942ac9b
--- /dev/null
+++ b/llvm/test/Assembler/implicit-intrinsic-declaration-invalid4.ll
@@ -0,0 +1,10 @@
+; RUN: not llvm-as < %s 2>&1 | FileCheck %s
+
+; Use of intrinsic without mangling suffix and invalid signature should
+; be rejected.
+
+; CHECK: intrinsic has incorrect number of args. Expected 1, but got 2
+define void @test(float %a) {
+ call float @llvm.ceil.f32(float %a, float %a)
+ ret void
+}
>From 8bb0397b49369edf4ca1833c6bdd47def1c3aa83 Mon Sep 17 00:00:00 2001
From: Rahul Joshi <rjoshi at nvidia.com>
Date: Fri, 8 May 2026 15:15:47 -0700
Subject: [PATCH 2/3] Review feedback
---
llvm/lib/IR/Intrinsics.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/llvm/lib/IR/Intrinsics.cpp b/llvm/lib/IR/Intrinsics.cpp
index 1ae2fa8b01811..89aef32938c75 100644
--- a/llvm/lib/IR/Intrinsics.cpp
+++ b/llvm/lib/IR/Intrinsics.cpp
@@ -605,7 +605,7 @@ static Type *DecodeFixedType(ArrayRef<Intrinsic::IITDescriptor> &Infos,
case IITDescriptor::VarArg:
// VarArg token should be consumed by `getIntrinsicInfoTableEntries`, so we
// should never see it here.
- break;
+ llvm_unreachable("IITDescriptor::VarArg not expected");
}
llvm_unreachable("unhandled");
}
@@ -1062,14 +1062,14 @@ matchIntrinsicType(Type *Ty, ArrayRef<Intrinsic::IITDescriptor> &Infos,
case IITDescriptor::VarArg:
// VarArg token should be consumed by `getIntrinsicInfoTableEntries`, so we
// should never see it here.
- break;
+ llvm_unreachable("IITDescriptor::VarArg not expected");
}
llvm_unreachable("unhandled");
}
/// Return true if the function type \p FTy is a valid type signature for the
/// type constraints specified in the .td file, represented by \p Infos and
-/// \p IsVarArg. The overloaded type for the intrinsic are pushed to the
+/// \p IsVarArg. The overloaded types for the intrinsic are pushed to the
/// \p OverloadTys vector.
///
/// If the type is not valid, returns false and prints an error message to
>From ca799f4be1d747c98367241ce33a8bb47591b462 Mon Sep 17 00:00:00 2001
From: Rahul Joshi <rjoshi at nvidia.com>
Date: Sun, 10 May 2026 06:54:50 -0700
Subject: [PATCH 3/3] More Precise error messages
---
llvm/lib/IR/Intrinsics.cpp | 202 ++++++++++---
llvm/test/Verifier/intrinsic-bad-arg-type1.ll | 277 ++++++++++++++++++
2 files changed, 433 insertions(+), 46 deletions(-)
create mode 100644 llvm/test/Verifier/intrinsic-bad-arg-type1.ll
diff --git a/llvm/lib/IR/Intrinsics.cpp b/llvm/lib/IR/Intrinsics.cpp
index 89aef32938c75..e3ee58668987a 100644
--- a/llvm/lib/IR/Intrinsics.cpp
+++ b/llvm/lib/IR/Intrinsics.cpp
@@ -844,81 +844,182 @@ bool Intrinsic::hasConstrainedFPRoundingModeOperand(Intrinsic::ID QID) {
}
}
-using DeferredIntrinsicMatchPair =
- std::pair<Type *, ArrayRef<Intrinsic::IITDescriptor>>;
+// The function below prints an error message when the type does not match. The
+// error message is of the form: intrinsic <position> type <reason>. The
+// `position` indiciates which type or type component of the intrinsic signature
+// the error is about and can be of the following forms:
+//
+// return -> for return type.
+// return struct element 3 ->
+// return vector element
+// return struct element 3 vector element
+// argument 3
+// argument 3 vector element
+//
+// To support deferred checks also being able to generate these error messages
+// we need to encode the position compactly so that it can be stashed into
+// DeferredIntrinsicMatchPair below.
+//
+namespace {
+struct MatchPosition {
+ unsigned IsRet : 1;
+ unsigned Num : 31; // Argument number (when IsRet = false).
+ struct Index {
+ unsigned IsStruct : 1; // Is this a struct element or vector element?
+ unsigned Num : 31; // Struct element index.
+ };
+ // We expect this to be just 2 levels deep, since nested structs are not
+ // supported.
+ static constexpr unsigned INDEX_TABLE_SIZE = 2;
+ Index Indices[INDEX_TABLE_SIZE];
+ unsigned NumIndices = 0;
+
+ void pop_index() {
+ assert(NumIndices > 0 && "cannot pop from empty indices");
+ --NumIndices;
+ }
+
+ void push_struct_element(unsigned ElementNum) {
+ assert(NumIndices < INDEX_TABLE_SIZE && "index table overflow");
+ Indices[NumIndices].IsStruct = true;
+ Indices[NumIndices++].Num = ElementNum;
+ }
+
+ void push_vector_element() {
+ assert(NumIndices < INDEX_TABLE_SIZE && "index table overflow");
+ Indices[NumIndices].IsStruct = false;
+ Indices[NumIndices++].Num = 0;
+ }
+};
+} // namespace
+
+static raw_ostream &operator<<(raw_ostream &OS, const MatchPosition &Pos) {
+ OS << "intrinsic ";
+
+ if (Pos.IsRet)
+ OS << "return";
+ else
+ OS << "argument " << Pos.Num;
+
+ for (const MatchPosition::Index &Idx :
+ ArrayRef(Pos.Indices).take_front(Pos.NumIndices)) {
+ if (Idx.IsStruct)
+ OS << " struct element " << Idx.Num;
+ else
+ OS << " vector element";
+ }
+ return OS;
+}
+
+using DeferredIntrinsicMatchInfo =
+ std::tuple<Type *, ArrayRef<Intrinsic::IITDescriptor>, MatchPosition>;
static bool
matchIntrinsicType(Type *Ty, ArrayRef<Intrinsic::IITDescriptor> &Infos,
- SmallVectorImpl<Type *> &OverloadTys,
- SmallVectorImpl<DeferredIntrinsicMatchPair> &DeferredChecks,
- bool IsDeferredCheck) {
+ MatchPosition Position, SmallVectorImpl<Type *> &OverloadTys,
+ SmallVectorImpl<DeferredIntrinsicMatchInfo> &DeferredChecks,
+ bool IsDeferredCheck, raw_ostream &OS) {
using namespace Intrinsic;
// If we ran out of descriptors, there are too many arguments.
- if (Infos.empty())
+ if (Infos.empty()) {
+ OS << Position << " too many "
+ << (Position.IsRet ? "returns" : "arguments");
return true;
+ }
// Do this before slicing off the 'front' part
auto InfosRef = Infos;
- auto DeferCheck = [&DeferredChecks, &InfosRef](Type *T) {
- DeferredChecks.emplace_back(T, InfosRef);
+ auto DeferCheck = [&DeferredChecks, &InfosRef, &Position](Type *T) {
+ DeferredChecks.emplace_back(T, InfosRef, Position);
return false;
};
IITDescriptor D = Infos.consume_front();
+ auto PrintMsg = [&OS, &Position, Ty](bool IsValid,
+ const Twine &Expected) -> bool {
+ if (IsValid)
+ return false;
+ OS << Position << " type expected " << Expected << ", but got " << *Ty;
+ return true;
+ };
+
switch (D.Kind) {
case IITDescriptor::Void:
- return !Ty->isVoidTy();
+ assert(Position.IsRet && Position.NumIndices == 0 &&
+ "void descriptor expected only for return type");
+ return PrintMsg(Ty->isVoidTy(), "void");
case IITDescriptor::MMX: {
FixedVectorType *VT = dyn_cast<FixedVectorType>(Ty);
- return !VT || VT->getNumElements() != 1 ||
- !VT->getElementType()->isIntegerTy(64);
+ return PrintMsg(VT && VT->getNumElements() == 1 &&
+ VT->getElementType()->isIntegerTy(64),
+ "x86_mmx (<1 x i64>)");
}
case IITDescriptor::AMX:
- return !Ty->isX86_AMXTy();
+ return PrintMsg(Ty->isX86_AMXTy(), "x86_amx");
case IITDescriptor::Token:
- return !Ty->isTokenTy();
+ return PrintMsg(Ty->isTokenTy(), "token");
case IITDescriptor::Metadata:
- return !Ty->isMetadataTy();
+ return PrintMsg(Ty->isMetadataTy(), "metadata");
case IITDescriptor::Half:
- return !Ty->isHalfTy();
+ return PrintMsg(Ty->isHalfTy(), "half");
case IITDescriptor::BFloat:
- return !Ty->isBFloatTy();
+ return PrintMsg(Ty->isBFloatTy(), "bfloat");
case IITDescriptor::Float:
- return !Ty->isFloatTy();
+ return PrintMsg(Ty->isFloatTy(), "float");
case IITDescriptor::Double:
- return !Ty->isDoubleTy();
+ return PrintMsg(Ty->isDoubleTy(), "double");
case IITDescriptor::Quad:
- return !Ty->isFP128Ty();
+ return PrintMsg(Ty->isFP128Ty(), "fp128");
case IITDescriptor::PPCQuad:
- return !Ty->isPPC_FP128Ty();
+ return PrintMsg(Ty->isPPC_FP128Ty(), "ppc_fp128");
case IITDescriptor::Integer:
- return !Ty->isIntegerTy(D.IntegerWidth);
+ return PrintMsg(Ty->isIntegerTy(D.IntegerWidth),
+ "i" + Twine(D.IntegerWidth));
case IITDescriptor::AArch64Svcount:
- return !isa<TargetExtType>(Ty) ||
- cast<TargetExtType>(Ty)->getName() != "aarch64.svcount";
+ return PrintMsg(isa<TargetExtType>(Ty) &&
+ cast<TargetExtType>(Ty)->getName() == "aarch64.svcount",
+ "aarch64.svcount");
case IITDescriptor::Vector: {
VectorType *VT = dyn_cast<VectorType>(Ty);
- return !VT || VT->getElementCount() != D.VectorWidth ||
- matchIntrinsicType(VT->getElementType(), Infos, OverloadTys,
- DeferredChecks, IsDeferredCheck);
+ StringRef Scalable = D.VectorWidth.isScalable() ? "vscale " : "";
+ bool HasError =
+ PrintMsg(VT && VT->getElementCount() == D.VectorWidth,
+ Twine(Scalable) + "vector with " +
+ Twine(D.VectorWidth.getKnownMinValue()) + " elements");
+ if (HasError)
+ return true;
+ Position.push_vector_element();
+ return matchIntrinsicType(VT->getElementType(), Infos, Position,
+ OverloadTys, DeferredChecks, IsDeferredCheck, OS);
}
case IITDescriptor::Pointer: {
PointerType *PT = dyn_cast<PointerType>(Ty);
- return !PT || PT->getAddressSpace() != D.PointerAddressSpace;
+ unsigned AS = D.PointerAddressSpace;
+ bool IsValid = PT && PT->getAddressSpace() == AS;
+ if (AS == 0)
+ return PrintMsg(IsValid, "ptr");
+ else
+ return PrintMsg(IsValid, "ptr addrspace(" + Twine(AS) + ")");
}
case IITDescriptor::Struct: {
StructType *ST = dyn_cast<StructType>(Ty);
- if (!ST || !ST->isLiteral() || ST->isPacked() ||
- ST->getNumElements() != D.StructNumElements)
+ unsigned EC = D.StructNumElements;
+ bool HasError = PrintMsg(
+ ST && ST->isLiteral() && !ST->isPacked() && ST->getNumElements() == EC,
+ "literal non-packed struct with " + Twine(EC) + " elements");
+ if (HasError)
return true;
- for (unsigned i = 0, e = D.StructNumElements; i != e; ++i)
- if (matchIntrinsicType(ST->getElementType(i), Infos, OverloadTys,
- DeferredChecks, IsDeferredCheck))
+ for (const auto &[Idx, ETy] : llvm::enumerate(ST->elements())) {
+ Position.push_struct_element(Idx);
+ if (matchIntrinsicType(ETy, Infos, Position, OverloadTys, DeferredChecks,
+ IsDeferredCheck, OS))
return true;
+ Position.pop_index();
+ }
return false;
}
@@ -997,9 +1098,10 @@ matchIntrinsicType(Type *Ty, ArrayRef<Intrinsic::IITDescriptor> &Infos,
if (ReferenceType->getElementCount() != ThisArgType->getElementCount())
return true;
EltTy = ThisArgType->getElementType();
+ Position.push_vector_element();
}
- return matchIntrinsicType(EltTy, Infos, OverloadTys, DeferredChecks,
- IsDeferredCheck);
+ return matchIntrinsicType(EltTy, Infos, Position, OverloadTys,
+ DeferredChecks, IsDeferredCheck, OS);
}
case IITDescriptor::VecOfAnyPtrsToElt: {
unsigned RefOverloadIndex = D.getRefOverloadIndex();
@@ -1079,10 +1181,15 @@ static bool isSignatureValid(FunctionType *FTy,
unsigned NumArgs, bool IsVarArg,
SmallVectorImpl<Type *> &OverloadTys,
raw_ostream &OS) {
- SmallVector<DeferredIntrinsicMatchPair, 2> DeferredChecks;
- if (matchIntrinsicType(FTy->getReturnType(), Infos, OverloadTys,
- DeferredChecks, false)) {
- OS << "intrinsic has incorrect return type!";
+ SmallVector<DeferredIntrinsicMatchInfo, 2> DeferredChecks;
+
+ assert(!Infos.empty() && "Table consistency error");
+
+ MatchPosition Pos;
+ Pos.IsRet = true;
+ if (matchIntrinsicType(FTy->getReturnType(), Infos, Pos, OverloadTys,
+ DeferredChecks, false, OS)) {
+ OS << " intrinsic has incorrect return type!";
return false;
}
unsigned NumDeferredReturnChecks = DeferredChecks.size();
@@ -1093,22 +1200,25 @@ static bool isSignatureValid(FunctionType *FTy,
return false;
}
- for (Type *Ty : FTy->params()) {
- if (matchIntrinsicType(Ty, Infos, OverloadTys, DeferredChecks, false)) {
- OS << "intrinsic has incorrect argument type!";
+ Pos.IsRet = false;
+ for (const auto &[Idx, Ty] : llvm::enumerate(FTy->params())) {
+ Pos.Num = Idx;
+ if (matchIntrinsicType(Ty, Infos, Pos, OverloadTys, DeferredChecks, false,
+ OS)) {
+ OS << " intrinsic has incorrect argument type!";
return false;
}
}
for (unsigned I = 0, E = DeferredChecks.size(); I != E; ++I) {
- DeferredIntrinsicMatchPair &Check = DeferredChecks[I];
- if (!matchIntrinsicType(Check.first, Check.second, OverloadTys,
- DeferredChecks, true))
+ auto &[DefTy, DefInfos, DefPosition] = DeferredChecks[I];
+ if (!matchIntrinsicType(DefTy, DefInfos, DefPosition, OverloadTys,
+ DeferredChecks, true, OS))
continue;
if (I < NumDeferredReturnChecks)
- OS << "intrinsic has incorrect return type!";
+ OS << " intrinsic has incorrect return type!";
else
- OS << "intrinsic has incorrect argument type!";
+ OS << " intrinsic has incorrect argument type!";
return false;
}
diff --git a/llvm/test/Verifier/intrinsic-bad-arg-type1.ll b/llvm/test/Verifier/intrinsic-bad-arg-type1.ll
new file mode 100644
index 0000000000000..1221392fc4623
--- /dev/null
+++ b/llvm/test/Verifier/intrinsic-bad-arg-type1.ll
@@ -0,0 +1,277 @@
+; RUN: split-file %s %t
+
+;--- test0.ll
+; RUN: not opt -S -passes=verify 2>&1 < %t/test0.ll | FileCheck %t/test0.ll
+; CHECK: intrinsic return type expected void, but got float
+; CHECK-NEXT: ptr @llvm.set.rounding
+
+declare float @llvm.set.rounding(i32)
+
+define void @test0() {
+entry:
+ %t = call float @llvm.set.rounding(i32 0)
+ ret void
+}
+
+;--- test1.ll
+; RUN: not opt -S -passes=verify 2>&1 < %t/test1.ll | FileCheck %t/test1.ll
+; CHECK: intrinsic return type expected x86_mmx (<1 x i64>), but got x86_amx
+; CHECK-NEXT: ptr @llvm.x86.sse.cvtps2pi
+
+declare x86_amx @llvm.x86.sse.cvtps2pi(<4 x float>)
+
+define void @test1() {
+entry:
+ %t = call x86_amx @llvm.x86.sse.cvtps2pi(<4 x float> undef)
+ ret void
+}
+
+;--- test2.ll
+; RUN: not opt -S -passes=verify 2>&1 < %t/test2.ll | FileCheck %t/test2.ll
+; CHECK: intrinsic argument 1 type expected x86_mmx (<1 x i64>), but got i32
+; CHECK-NEXT: ptr @llvm.x86.sse.cvtpi2ps
+
+declare <4 x float> @llvm.x86.sse.cvtpi2ps(<4 x float>, i32)
+
+define void @test1() {
+entry:
+ %t = call <4 x float> @llvm.x86.sse.cvtpi2ps(<4 x float> undef, i32 0)
+ ret void
+}
+
+;--- test3.ll
+; RUN: not opt -S -passes=verify 2>&1 < %t/test3.ll | FileCheck %t/test3.ll
+; CHECK: intrinsic return type expected x86_amx, but got float
+; CHECK-NEXT: ptr @llvm.x86.tileloadd64.internal
+
+declare float @llvm.x86.tileloadd64.internal(i16, i16, ptr, i64)
+
+define void @test1() {
+entry:
+ %t = call float @llvm.x86.tileloadd64.internal(i16 0, i16 0, ptr undef, i64 0)
+ ret void
+}
+
+;--- test4.ll
+; RUN: not opt -S -passes=verify 2>&1 < %t/test4.ll | FileCheck %t/test4.ll
+; CHECK: intrinsic return type expected token, but got i32
+; CHECK-NEXT: ptr @llvm.call.preallocated.setup
+
+declare i32 @llvm.call.preallocated.setup(i32)
+
+define void @test1() {
+entry:
+ %t = call i32 @llvm.call.preallocated.setup(i32 0)
+ ret void
+}
+
+;--- test5.ll
+; RUN: not opt -S -passes=verify 2>&1 < %t/test5.ll | FileCheck %t/test5.ll
+; CHECK: intrinsic argument 1 type expected metadata, but got i16
+; CHECK-NEXT: ptr @llvm.fptrunc.round.f64.f64
+
+declare double @llvm.fptrunc.round.f64.f64(double, i16)
+
+define void @test1() {
+entry:
+ %t = call double @llvm.fptrunc.round.f64.f64(double 1.0, i16 0)
+ ret void
+}
+
+;--- test6.ll
+; RUN: not opt -S -passes=verify 2>&1 < %t/test6.ll | FileCheck %t/test6.ll
+; CHECK: intrinsic argument 0 type expected half, but got i16
+; CHECK-NEXT: ptr @llvm.nvvm.mul.rn.sat.f16
+
+declare half @llvm.nvvm.mul.rn.sat.f16(i16, half)
+
+define void @test1() {
+entry:
+ %t = call half @llvm.nvvm.mul.rn.sat.f16(i16 0, half 1.0)
+ ret void
+}
+
+;--- test7.ll
+; RUN: not opt -S -passes=verify 2>&1 < %t/test7.ll | FileCheck %t/test7.ll
+; CHECK: intrinsic return type expected bfloat, but got half
+; CHECK-NEXT: ptr @llvm.arm.neon.vcvtbfp2bf
+
+declare half @llvm.arm.neon.vcvtbfp2bf(float)
+
+define void @test1() {
+entry:
+ %t = call half @llvm.arm.neon.vcvtbfp2bf(float undef)
+ ret void
+}
+
+;--- test8.ll
+; RUN: not opt -S -passes=verify 2>&1 < %t/test8.ll | FileCheck %t/test8.ll
+; CHECK: intrinsic argument 2 type expected float, but got half
+; CHECK-NEXT: ptr @llvm.x86.avx512.vfmadd.f32
+
+declare float @llvm.x86.avx512.vfmadd.f32(float, float, half, i32)
+
+define void @test1() {
+entry:
+ %t = call float @llvm.x86.avx512.vfmadd.f32(float 0.0, float 0.0, half 0.0, i32 0)
+ ret void
+}
+
+;--- test9.ll
+; RUN: not opt -S -passes=verify 2>&1 < %t/test9.ll | FileCheck %t/test9.ll
+; CHECK: intrinsic argument 2 type expected double, but got half
+; CHECK-NEXT: ptr @llvm.expect.with.probability.i32
+
+declare i32 @llvm.expect.with.probability.i32(i32, i32, half)
+
+define void @test1() {
+entry:
+ %t = call i32 @llvm.expect.with.probability.i32(i32 0, i32 0, half 0.0)
+ ret void
+}
+
+;--- test10.ll
+; RUN: not opt -S -passes=verify 2>&1 < %t/test10.ll | FileCheck %t/test10.ll
+; CHECK: intrinsic argument 0 type expected fp128, but got double
+; CHECK-NEXT: ptr @llvm.ppc.truncf128.round.to.odd
+
+declare double @llvm.ppc.truncf128.round.to.odd(double)
+
+define void @test1() {
+entry:
+ %t = call double @llvm.ppc.truncf128.round.to.odd(double 0.0)
+ ret void
+}
+
+;--- test11.ll
+; RUN: not opt -S -passes=verify 2>&1 < %t/test11.ll | FileCheck %t/test11.ll
+; CHECK: intrinsic argument 0 type expected ppc_fp128, but got double
+; CHECK-NEXT: ptr @llvm.ppc.unpack.longdouble
+
+declare double @llvm.ppc.unpack.longdouble(double, i32)
+
+define void @test1() {
+entry:
+ %t = call double @llvm.ppc.unpack.longdouble(double 0.0, i32 0)
+ ret void
+}
+
+;--- test12.ll
+; RUN: not opt -S -passes=verify 2>&1 < %t/test12.ll | FileCheck %t/test12.ll
+; CHECK: intrinsic return type expected i64, but got double
+; CHECK-NEXT: ptr @llvm.readcyclecounter
+
+declare double @llvm.readcyclecounter()
+
+define void @test1() {
+entry:
+ %t = call double @llvm.readcyclecounter()
+ ret void
+}
+
+;--- test13.ll
+; RUN: not opt -S -passes=verify 2>&1 < %t/test13.ll | FileCheck %t/test13.ll
+; CHECK: intrinsic argument 0 type expected aarch64.svcount, but got i32
+; CHECK-NEXT: ptr @llvm.aarch64.sve.pext
+
+declare <4 x i32> @llvm.aarch64.sve.pext(i32, i32)
+
+define void @test1() {
+entry:
+ %t = call <4 x i32> @llvm.aarch64.sve.pext(i32 0, i32 0)
+ ret void
+}
+
+;--- test14.ll
+; RUN: not opt -S -passes=verify 2>&1 < %t/test14.ll | FileCheck %t/test14.ll
+; CHECK: intrinsic return type expected vector with 16 elements, but got i32
+; CHECK-NEXT: ptr @llvm.aarch64.neon.pmull64
+
+; Correct return type is <16 x i8>
+declare i32 @llvm.aarch64.neon.pmull64(i64, i64)
+
+define void @test1() {
+entry:
+ %t = call i32 @llvm.aarch64.neon.pmull64(i64 0, i64 0)
+ ret void
+}
+
+;--- test15.ll
+; RUN: not opt -S -passes=verify 2>&1 < %t/test15.ll | FileCheck %t/test15.ll
+; CHECK: intrinsic return vector element type expected i8, but got i32
+; CHECK-NEXT: ptr @llvm.aarch64.neon.pmull64
+
+declare <16 x i32> @llvm.aarch64.neon.pmull64(i64, i64)
+
+define void @test1() {
+entry:
+ %t = call <16 x i32> @llvm.aarch64.neon.pmull64(i64 0, i64 0)
+ ret void
+}
+
+;--- test16.ll
+; RUN: not opt -S -passes=verify 2>&1 < %t/test16.ll | FileCheck %t/test16.ll
+; CHECK: intrinsic return type expected vector with 16 elements, but got <vscale x 16 x i32>
+; CHECK-NEXT: ptr @llvm.aarch64.neon.pmull64
+
+declare <vscale x 16 x i32> @llvm.aarch64.neon.pmull64(i64, i64)
+
+define void @test1() {
+entry:
+ %t = call <vscale x 16 x i32> @llvm.aarch64.neon.pmull64(i64 0, i64 0)
+ ret void
+}
+
+;--- test17.ll
+; RUN: not opt -S -passes=verify 2>&1 < %t/test17.ll | FileCheck %t/test17.ll
+; CHECK: intrinsic argument 1 type expected ptr, but got i32
+; CHECK-NEXT: ptr @llvm.gcroot
+
+declare void @llvm.gcroot(ptr, i32)
+
+define void @test1() {
+entry:
+ call void @llvm.gcroot(ptr null, i32 0)
+ ret void
+}
+
+;--- test18.ll
+; RUN: not opt -S -passes=verify 2>&1 < %t/test18.ll | FileCheck %t/test18.ll
+; CHECK: intrinsic argument 0 type expected ptr addrspace(1), but got ptr
+; CHECK-NEXT: ptr @llvm.nvvm.applypriority.global.L2.evict.normal
+
+declare void @llvm.nvvm.applypriority.global.L2.evict.normal(ptr, i64)
+
+define void @test1() {
+entry:
+ call void @llvm.nvvm.applypriority.global.L2.evict.normal(ptr null, i64 0)
+ ret void
+}
+
+;--- test19.ll
+; RUN: not opt -S -passes=verify 2>&1 < %t/test19.ll | FileCheck %t/test19.ll
+; CHECK: intrinsic return type expected literal non-packed struct with 2 elements, but got void
+; CHECK-NEXT: ptr @llvm.nvvm.elect.sync
+
+; Expected return type is { i32, i1 }.
+declare void @llvm.nvvm.elect.sync(i32)
+
+define void @test1() {
+entry:
+ call void @llvm.nvvm.elect.sync(i32 0)
+ ret void
+}
+
+;--- test20.ll
+; RUN: not opt -S -passes=verify 2>&1 < %t/test20.ll | FileCheck %t/test20.ll
+; CHECK: intrinsic return struct element 1 type expected i1, but got i2
+; CHECK-NEXT: ptr @llvm.nvvm.elect.sync
+
+; Expected return type is { i32, i1 }.
+declare { i32, i2 } @llvm.nvvm.elect.sync(i32)
+
+define void @test1() {
+entry:
+ call { i32, i2 } @llvm.nvvm.elect.sync(i32 0)
+ ret void
+}
More information about the cfe-commits
mailing list