[llvm] [LLVM-Tablegen] Explicit Type Constraints for Overloaded LLVM Intrinsics (PR #172442)
Dharuni R Acharya via llvm-commits
llvm-commits at lists.llvm.org
Tue Dec 16 21:04:17 PST 2025
https://github.com/DharuniRAcharya updated https://github.com/llvm/llvm-project/pull/172442
>From 3a66fcb6c2d21fece7e9cb8d37f115f71aad98f9 Mon Sep 17 00:00:00 2001
From: Dharuni R Acharya <dharunira at nvidia.com>
Date: Tue, 16 Dec 2025 08:43:42 +0000
Subject: [PATCH 1/2] [LLVM-Tablegen] Explicit Type Constraints for Overloaded
LLVM Intrinsics
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This patch adds LLVM infrastructure to support explicit type constraints for overloaded intrinsics.
A new constrained type class, AnyTypeOf<[list of LLVMType]>, is added to express allowed
type subsets directly in TableGen, enabling precise IR-level verification of intrinsic type usage.
Type violations are now detected during the LLVM Verifier flow with clear, actionable
diagnostics, improving error reporting and debuggability while keeping verification
consistent with LLVM’s existing pipelines.
Signed-off-by: Dharuni R Acharya <dharunira at nvidia.com>
---
llvm/include/llvm/IR/Intrinsics.h | 12 +
llvm/include/llvm/IR/Intrinsics.td | 19 ++
llvm/include/llvm/IR/IntrinsicsNVVM.td | 12 +
llvm/lib/IR/Intrinsics.cpp | 297 +++++++++++++++++-
llvm/lib/IR/Verifier.cpp | 10 +
.../NVPTX/anytypeof_constraints_invalid.ll | 51 +++
.../NVPTX/anytypeof_constraints_valid.ll | 45 +++
7 files changed, 430 insertions(+), 16 deletions(-)
create mode 100644 llvm/test/CodeGen/NVPTX/anytypeof_constraints_invalid.ll
create mode 100644 llvm/test/CodeGen/NVPTX/anytypeof_constraints_valid.ll
diff --git a/llvm/include/llvm/IR/Intrinsics.h b/llvm/include/llvm/IR/Intrinsics.h
index 2c86a43e114ea..3d2dcd5e0da1a 100644
--- a/llvm/include/llvm/IR/Intrinsics.h
+++ b/llvm/include/llvm/IR/Intrinsics.h
@@ -181,6 +181,7 @@ namespace Intrinsic {
AMX,
PPCQuad,
AArch64Svcount,
+ ArgumentTypeConstraint, // For AnyTypeOf - marks constrained argument types.
} Kind;
union {
@@ -189,6 +190,7 @@ namespace Intrinsic {
unsigned Pointer_AddressSpace;
unsigned Struct_NumElements;
unsigned Argument_Info;
+ unsigned Argument_NumConstraints;
ElementCount Vector_Width;
};
@@ -231,6 +233,12 @@ namespace Intrinsic {
return Argument_Info & 0xFFFF;
}
+ // For ArgumentTypeConstraint: get number of allowed types.
+ unsigned getArgumentNumConstraints() const {
+ assert(Kind == ArgumentTypeConstraint);
+ return Argument_NumConstraints;
+ }
+
static IITDescriptor get(IITDescriptorKind K, unsigned Field) {
IITDescriptor Result = { K, { Field } };
return Result;
@@ -278,6 +286,10 @@ namespace Intrinsic {
LLVM_ABI bool matchIntrinsicVarArg(bool isVarArg,
ArrayRef<IITDescriptor> &Infos);
+ /// Verify type constraints for AnyTypeOf constrained intrinsics.
+ LLVM_ABI bool verifyIntrinsicTypeConstraints(ID id, FunctionType *FTy,
+ std::string &ErrMsg);
+
/// Gets the type arguments of an intrinsic call by matching type contraints
/// specified by the .td file. The overloaded types are pushed into the
/// AgTys vector.
diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td
index 35a4158a56da9..5ff061b255650 100644
--- a/llvm/include/llvm/IR/Intrinsics.td
+++ b/llvm/include/llvm/IR/Intrinsics.td
@@ -372,6 +372,8 @@ def IIT_V6 : IIT_Vec<6, 50>;
def IIT_V10 : IIT_Vec<10, 51>;
def IIT_V2048 : IIT_Vec<2048, 52>;
def IIT_V4096 : IIT_Vec<4096, 53>;
+// Constrained type encoding for overloaded intrinsics.
+def IIT_ANYTYPE : IIT_Base<93>;
}
defvar IIT_all_FixedTypes = !filter(iit, IIT_all,
@@ -448,6 +450,23 @@ class LLVMAnyPointerType : LLVMAnyType<pAny> {
assert isAny, "pAny should have isOverloaded";
}
+// AnyTypeOf: Constrain overloaded type to specific set of allowed types.
+// Encoding follows the pattern: [IIT_ARG, EncAny, IIT_ANYTYPE, count, type_sigs...].
+class AnyTypeOf<list<LLVMType> allowed_types> : LLVMAnyType<Any> {
+ list<LLVMType> AllowedTypes = allowed_types;
+
+ // Validation: specifying maximal 255 number of allowed types.
+ assert !le(!size(allowed_types), 255),
+ "AnyTypeOf cannot exceed 255 allowed types";
+
+ let Sig = !listconcat(
+ [IIT_ARG.Number, EncAnyType<ArgCode>.ret],
+ [IIT_ANYTYPE.Number, !size(allowed_types)],
+ !foldl([]<int>, allowed_types, accum, ty,
+ !listconcat(accum, ty.Sig))
+ );
+}
+
// Match the type of another intrinsic parameter. Number is an index into the
// list of overloaded types for the intrinsic, excluding all the fixed types.
// The Number value must refer to a previously listed type. For example:
diff --git a/llvm/include/llvm/IR/IntrinsicsNVVM.td b/llvm/include/llvm/IR/IntrinsicsNVVM.td
index aab85c2a86373..c7f154255050e 100644
--- a/llvm/include/llvm/IR/IntrinsicsNVVM.td
+++ b/llvm/include/llvm/IR/IntrinsicsNVVM.td
@@ -3312,4 +3312,16 @@ foreach sp = [0, 1] in {
}
}
+// Test intrinsic - TODO: Add the feature to existing intrinsics and remove these test intrinsics.
+// IIT_LongEncodingTable entry: /* 25339 */ 4, 15, 0, 93, 2, 3, 4, 15, 7, 15, 8, 93, 2, 15, 9, 7, 15, 16, 93, 2, 14, 24, 3, 15, 24, 93, 2, 5, 8, 15, 32, 93, 3, 3, 10, 4, 10, 7, 0,
+def int_nvvm_test_anytype : Intrinsic<[llvm_i32_ty],
+ [AnyTypeOf<[llvm_i16_ty, llvm_i32_ty]>, LLVMMatchType<0>, AnyTypeOf<[llvm_anyint_ty, llvm_float_ty]>, AnyTypeOf<[llvm_ptr_ty, llvm_shared_ptr_ty]>,
+ AnyTypeOf<[llvm_i64_ty, llvm_double_ty]>, AnyTypeOf<[llvm_i16_ty, llvm_v4i32_ty, llvm_v4f32_ty]>],
+ [IntrNoMem]>;
+
+// IIT_LongEncodingTable entry: /* 15435 */ 21, 1, 4, 15, 0, 93, 2, 3, 4, 5, 4, 0,
+def int_nvvm_test_return_type : Intrinsic<[llvm_i32_ty, AnyTypeOf<[llvm_i16_ty, llvm_i32_ty]>, llvm_i64_ty],
+[llvm_i32_ty],
+[IntrNoMem]>;
+
} // let TargetPrefix = "nvvm"
diff --git a/llvm/lib/IR/Intrinsics.cpp b/llvm/lib/IR/Intrinsics.cpp
index f46d3e5063e43..88762923fd96d 100644
--- a/llvm/lib/IR/Intrinsics.cpp
+++ b/llvm/lib/IR/Intrinsics.cpp
@@ -353,8 +353,21 @@ DecodeIITType(unsigned &NextElt, ArrayRef<unsigned char> Infos,
case IIT_ARG: {
unsigned ArgInfo = (NextElt == Infos.size() ? 0 : Infos[NextElt++]);
OutputTable.push_back(IITDescriptor::get(IITDescriptor::Argument, ArgInfo));
+
+ if (NextElt < Infos.size() && Infos[NextElt] == IIT_ANYTYPE) {
+ NextElt++;
+
+ unsigned NumTypes = Infos[NextElt++];
+ OutputTable.push_back(IITDescriptor::get(IITDescriptor::ArgumentTypeConstraint, NumTypes));
+
+ for (unsigned i = 0; i < NumTypes; ++i)
+ DecodeIITType(NextElt, Infos, Info, OutputTable);
+ return;
+ }
return;
}
+ case IIT_ANYTYPE:
+ llvm_unreachable("IIT_ANYTYPE must follow IIT_ARG");
case IIT_EXTEND_ARG: {
unsigned ArgInfo = (NextElt == Infos.size() ? 0 : Infos[NextElt++]);
OutputTable.push_back(
@@ -570,6 +583,8 @@ static Type *DecodeFixedType(ArrayRef<Intrinsic::IITDescriptor> &Infos,
case IITDescriptor::VecOfAnyPtrsToElt:
// Return the overloaded type (which determines the pointers address space)
return Tys[D.getOverloadArgNumber()];
+ case IITDescriptor::ArgumentTypeConstraint:
+ llvm_unreachable("ArgumentTypeConstraint should not appear in DecodeFixedType");
}
llvm_unreachable("unhandled");
}
@@ -582,9 +597,26 @@ FunctionType *Intrinsic::getType(LLVMContext &Context, ID id,
ArrayRef<IITDescriptor> TableRef = Table;
Type *ResultTy = DecodeFixedType(TableRef, Tys, Context);
+ if (!TableRef.empty() && TableRef[0].Kind == IITDescriptor::ArgumentTypeConstraint) {
+ unsigned NumConstraints = TableRef[0].getArgumentNumConstraints();
+ TableRef = TableRef.slice(1);
+
+ for (unsigned i = 0; i < NumConstraints; ++i)
+ (void)DecodeFixedType(TableRef, Tys, Context);
+ }
+
SmallVector<Type *, 8> ArgTys;
- while (!TableRef.empty())
+ while (!TableRef.empty()) {
ArgTys.push_back(DecodeFixedType(TableRef, Tys, Context));
+
+ if (!TableRef.empty() && TableRef[0].Kind == IITDescriptor::ArgumentTypeConstraint) {
+ unsigned NumConstraints = TableRef[0].getArgumentNumConstraints();
+ TableRef = TableRef.slice(1);
+
+ for (unsigned i = 0; i < NumConstraints; ++i)
+ (void)DecodeFixedType(TableRef, Tys, Context);
+ }
+ }
// DecodeFixedType returns Void for IITDescriptor::Void and
// IITDescriptor::VarArg If we see void type as the type of the last argument,
@@ -833,6 +865,50 @@ bool Intrinsic::hasConstrainedFPRoundingModeOperand(Intrinsic::ID QID) {
}
}
+// Helper to skip past descriptors for one complete type in AnyTypeOf constraints.
+static unsigned skipDescriptorsForSingleType(ArrayRef<Intrinsic::IITDescriptor> &Infos) {
+ using namespace Intrinsic;
+
+ if (Infos.empty())
+ return 0;
+
+ IITDescriptor D = Infos[0];
+ unsigned Count = 1;
+ Infos = Infos.slice(1);
+
+ switch (D.Kind) {
+ case IITDescriptor::Vector:
+ Count += skipDescriptorsForSingleType(Infos);
+ break;
+
+ case IITDescriptor::Pointer:
+ break;
+
+ case IITDescriptor::Struct:
+ for (unsigned i = 0, e = D.Struct_NumElements; i != e; ++i)
+ Count += skipDescriptorsForSingleType(Infos);
+ break;
+
+ case IITDescriptor::SameVecWidthArgument:
+ Count += skipDescriptorsForSingleType(Infos);
+ break;
+
+ case IITDescriptor::Argument:
+ if (!Infos.empty() && Infos[0].Kind == IITDescriptor::ArgumentTypeConstraint) {
+ unsigned NumConstraints = Infos[0].getArgumentNumConstraints();
+ Count++;
+ Infos = Infos.slice(1);
+ for (unsigned i = 0; i < NumConstraints; ++i)
+ Count += skipDescriptorsForSingleType(Infos);
+ }
+ break;
+
+ default:
+ break;
+ }
+ return Count;
+}
+
using DeferredIntrinsicMatchPair =
std::pair<Type *, ArrayRef<Intrinsic::IITDescriptor>>;
@@ -919,6 +995,29 @@ matchIntrinsicType(Type *Ty, ArrayRef<Intrinsic::IITDescriptor> &Infos,
// verify that the later instance matches the previous instance.
if (D.getArgumentNumber() < ArgTys.size())
return Ty != ArgTys[D.getArgumentNumber()];
+
+ switch (D.getArgumentKind()) {
+ case IITDescriptor::AK_Any:
+ break;
+ case IITDescriptor::AK_AnyInteger:
+ if (!Ty->isIntOrIntVectorTy())
+ return true;
+ break;
+ case IITDescriptor::AK_AnyFloat:
+ if (!Ty->isFPOrFPVectorTy())
+ return true;
+ break;
+ case IITDescriptor::AK_AnyVector:
+ if (!isa<VectorType>(Ty))
+ return true;
+ break;
+ case IITDescriptor::AK_AnyPointer:
+ if (!isa<PointerType>(Ty))
+ return true;
+ break;
+ case IITDescriptor::AK_MatchType:
+ break;
+ }
if (D.getArgumentNumber() > ArgTys.size() ||
D.getArgumentKind() == IITDescriptor::AK_MatchType)
@@ -927,22 +1026,18 @@ matchIntrinsicType(Type *Ty, ArrayRef<Intrinsic::IITDescriptor> &Infos,
assert(D.getArgumentNumber() == ArgTys.size() && !IsDeferredCheck &&
"Table consistency error");
ArgTys.push_back(Ty);
-
- switch (D.getArgumentKind()) {
- case IITDescriptor::AK_Any:
- return false; // Success
- case IITDescriptor::AK_AnyInteger:
- return !Ty->isIntOrIntVectorTy();
- case IITDescriptor::AK_AnyFloat:
- return !Ty->isFPOrFPVectorTy();
- case IITDescriptor::AK_AnyVector:
- return !isa<VectorType>(Ty);
- case IITDescriptor::AK_AnyPointer:
- return !isa<PointerType>(Ty);
- default:
- break;
+
+ if (!Infos.empty() && Infos[0].Kind == IITDescriptor::ArgumentTypeConstraint) {
+ unsigned NumConstraints = Infos[0].getArgumentNumConstraints();
+ Infos = Infos.slice(1);
+
+ for (unsigned i = 0; i < NumConstraints; ++i)
+ skipDescriptorsForSingleType(Infos);
+
+ return false;
}
- llvm_unreachable("all argument kinds not covered");
+
+ return false;
case IITDescriptor::ExtendArgument: {
// If this is a forward reference, defer the check for later.
@@ -1058,6 +1153,8 @@ matchIntrinsicType(Type *Ty, ArrayRef<Intrinsic::IITDescriptor> &Infos,
return true;
return ThisArgVecTy != VectorType::getInteger(ReferenceType);
}
+ case IITDescriptor::ArgumentTypeConstraint:
+ llvm_unreachable("ArgumentTypeConstraint should be handled in Argument case");
}
llvm_unreachable("unhandled");
}
@@ -1088,6 +1185,174 @@ Intrinsic::matchIntrinsicSignature(FunctionType *FTy,
return MatchIntrinsicTypes_Match;
}
+// Helper: Check if a type matches AnyTypeOf constraints using matchIntrinsicType.
+static bool verifyTypeAgainstConstraints(
+ Type *Ty, unsigned NumConstraints,
+ ArrayRef<Intrinsic::IITDescriptor> &Infos) {
+ using namespace Intrinsic;
+
+ bool Matched = false;
+ for (unsigned i = 0; i < NumConstraints && !Matched; ++i) {
+ ArrayRef<IITDescriptor> TypeDesc = Infos;
+ SmallVector<Type *, 4> DummyArgTys;
+ SmallVector<DeferredIntrinsicMatchPair, 2> DummyDeferredChecks;
+
+ if (!matchIntrinsicType(Ty, TypeDesc, DummyArgTys, DummyDeferredChecks, false)) {
+ Matched = true;
+ for (unsigned j = 0; j < NumConstraints - i; ++j)
+ skipDescriptorsForSingleType(Infos);
+ break;
+ }
+ skipDescriptorsForSingleType(Infos);
+ }
+
+ return Matched;
+}
+
+// Helper: Format a type as string for error messages.
+static std::string typeToString(Type *Ty) {
+ std::string Str;
+ raw_string_ostream OS(Str);
+ Ty->print(OS);
+ return Str;
+}
+
+bool Intrinsic::verifyIntrinsicTypeConstraints(
+ ID id, FunctionType *FTy,
+ std::string &ErrMsg) {
+
+ if (id == 0 || id >= Intrinsic::num_intrinsics)
+ return true;
+
+ SmallVector<IITDescriptor, 8> Table;
+ getIntrinsicInfoTableEntries(id, Table);
+
+ if (Table.empty())
+ return true;
+
+ ArrayRef<IITDescriptor> Infos = Table;
+ SmallVector<Type *, 4> ArgTys;
+
+ // Processing return type.
+ Type *RetTy = FTy->getReturnType();
+ if (!Infos.empty() && Infos[0].Kind == IITDescriptor::Argument) {
+ Infos = Infos.slice(1);
+
+ if (!Infos.empty() && Infos[0].Kind == IITDescriptor::ArgumentTypeConstraint) {
+ unsigned NumConstraints = Infos[0].getArgumentNumConstraints();
+ Infos = Infos.slice(1);
+
+ bool Matched = false;
+
+ // Check if a struct type's elements are all present in AnyTypeOf constraint list.
+ if (auto *STy = dyn_cast<StructType>(RetTy)) {
+ SmallVector<Type *, 8> AllowedTypes;
+ ArrayRef<IITDescriptor> TempInfos = Infos;
+ for (unsigned i = 0; i < NumConstraints; ++i) {
+ ArrayRef<IITDescriptor> TypeDesc = TempInfos;
+ SmallVector<Type *, 4> DummyTys;
+ Type *ConstraintTy = DecodeFixedType(TypeDesc, DummyTys, FTy->getContext());
+ AllowedTypes.push_back(ConstraintTy);
+ TempInfos = TypeDesc;
+ }
+
+ Matched = llvm::all_of(STy->elements(), [&](Type *ElemTy) {
+ return llvm::is_contained(AllowedTypes, ElemTy);
+ });
+
+ if (Matched) {
+ for (unsigned i = 0; i < NumConstraints; ++i)
+ skipDescriptorsForSingleType(Infos);
+ Matched = true;
+ }
+ }
+
+ if (!Matched) {
+ for (unsigned i = 0; i < NumConstraints && !Matched; ++i) {
+ ArrayRef<IITDescriptor> TypeDesc = Infos;
+ SmallVector<Type *, 4> DummyArgTys;
+ SmallVector<DeferredIntrinsicMatchPair, 2> DummyDeferredChecks;
+
+ if (!matchIntrinsicType(RetTy, TypeDesc, DummyArgTys,
+ DummyDeferredChecks, false)) {
+ Matched = true;
+ for (unsigned j = 0; j < NumConstraints - i; ++j)
+ skipDescriptorsForSingleType(Infos);
+ break;
+ }
+ skipDescriptorsForSingleType(Infos);
+ }
+ }
+
+ if (!Matched) {
+ ErrMsg = "Return type '" + typeToString(RetTy) + "' not in allowed types";
+ return false;
+ }
+ }
+ } else if (!Infos.empty() && Infos[0].Kind == IITDescriptor::Struct) {
+ // Handle struct return type with AnyTypeOf constrained elements.
+ auto *STy = dyn_cast<StructType>(RetTy);
+ if (!STy)
+ skipDescriptorsForSingleType(Infos);
+ else {
+ unsigned NumElements = Infos[0].Struct_NumElements;
+ Infos = Infos.slice(1);
+
+ for (unsigned ElemIdx = 0; ElemIdx < NumElements; ++ElemIdx) {
+ if (Infos.empty())
+ break;
+
+ if (Infos[0].Kind == IITDescriptor::Argument) {
+ Infos = Infos.slice(1);
+
+ if (!Infos.empty() && Infos[0].Kind == IITDescriptor::ArgumentTypeConstraint) {
+ unsigned NumConstraints = Infos[0].getArgumentNumConstraints();
+ Infos = Infos.slice(1);
+
+ Type *ElemTy = STy->getElementType(ElemIdx);
+ if (!verifyTypeAgainstConstraints(ElemTy, NumConstraints, Infos)) {
+ ErrMsg = "Return type struct element " + std::to_string(ElemIdx) +
+ " type '" + typeToString(ElemTy) + "' not in allowed types";
+ return false;
+ }
+ }
+ } else
+ skipDescriptorsForSingleType(Infos);
+ }
+ }
+ } else if (!Infos.empty())
+ skipDescriptorsForSingleType(Infos);
+
+ // Processing parameters.
+ for (unsigned ParamIdx = 0; ParamIdx < FTy->getNumParams(); ++ParamIdx) {
+ if (Infos.empty())
+ break;
+
+ Type *ParamTy = FTy->getParamType(ParamIdx);
+
+ if (Infos[0].Kind == IITDescriptor::Argument) {
+ unsigned ArgNum = Infos[0].getArgumentNumber();
+ Infos = Infos.slice(1);
+
+ if (!Infos.empty() && Infos[0].Kind == IITDescriptor::ArgumentTypeConstraint) {
+ unsigned NumConstraints = Infos[0].getArgumentNumConstraints();
+ Infos = Infos.slice(1);
+
+ if (!verifyTypeAgainstConstraints(ParamTy, NumConstraints, Infos)) {
+ ErrMsg = "Parameter " + std::to_string(ParamIdx) + " type '" +
+ typeToString(ParamTy) + "' not in allowed types";
+ return false;
+ }
+
+ if (ArgNum == ArgTys.size())
+ ArgTys.push_back(ParamTy);
+ }
+ } else
+ skipDescriptorsForSingleType(Infos);
+ }
+ return true;
+}
+
bool Intrinsic::matchIntrinsicVarArg(
bool isVarArg, ArrayRef<Intrinsic::IITDescriptor> &Infos) {
// If there are no descriptors left, then it can't be a vararg.
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 543c26dfe25e0..ec771ff773223 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -3274,6 +3274,16 @@ void Verifier::visitFunction(const Function &F) {
}
}
+ // Verify AnyTypeOf type constraints for intrinsic declarations.
+ if (F.isIntrinsic()) {
+ std::string ConstraintErrMsg;
+ if (!Intrinsic::verifyIntrinsicTypeConstraints(F.getIntrinsicID(),
+ F.getFunctionType(),
+ ConstraintErrMsg))
+ CheckFailed("Intrinsic declaration '" + F.getName().str() +
+ "' violates type constraint: " + ConstraintErrMsg);
+ }
+
auto *N = F.getSubprogram();
HasDebugInfo = (N != nullptr);
if (!HasDebugInfo)
diff --git a/llvm/test/CodeGen/NVPTX/anytypeof_constraints_invalid.ll b/llvm/test/CodeGen/NVPTX/anytypeof_constraints_invalid.ll
new file mode 100644
index 0000000000000..7647a04898299
--- /dev/null
+++ b/llvm/test/CodeGen/NVPTX/anytypeof_constraints_invalid.ll
@@ -0,0 +1,51 @@
+; RUN: not llvm-as < %s -o /dev/null
+; Test cases for int_nvvm_test_anytype intrinsic - INVALID combinations
+
+; arg0 must be i16 or i32, not i8
+define i32 @invalid_arg0_i8(i8 %a0, i8 %a1, i32 %a2, ptr %a3, i64 %a4, i16 %a5) {
+ %result = call i32 @llvm.nvvm.test.anytype.i8.i8.i32.p0.i64.i16(i8 %a0, i8 %a1, i32 %a2, ptr %a3, i64 %a4, i16 %a5)
+ ret i32 %result
+}
+
+; arg1 must match arg0 (i16), not i32
+define i32 @invalid_arg1_mismatch_i16_i32(i16 %a0, i32 %a1, i32 %a2, ptr %a3, i64 %a4, i16 %a5) {
+ %result = call i32 @llvm.nvvm.test.anytype.i16.i32.i32.p0.i64.i16(i16 %a0, i32 %a1, i32 %a2, ptr %a3, i64 %a4, i16 %a5)
+ ret i32 %result
+}
+
+; arg2 must be anyint or float, not double
+define i32 @invalid_arg2_double(i32 %a0, i32 %a1, double %a2, ptr %a3, i64 %a4, i16 %a5) {
+ %result = call i32 @llvm.nvvm.test.anytype.i32.i32.f64.p0.i64.i16(i32 %a0, i32 %a1, double %a2, ptr %a3, i64 %a4, i16 %a5)
+ ret i32 %result
+}
+
+; arg3 must be ptr or ptr addrspace(3), not ptr addrspace(1) (global)
+define i32 @invalid_arg3_ptr_as1_wrong(i32 %a0, i32 %a1, i32 %a2, ptr addrspace(1) %a3, i64 %a4, i16 %a5) {
+ %result = call i32 @llvm.nvvm.test.anytype.i32.i32.i32.p1.i64.i16(i32 %a0, i32 %a1, i32 %a2, ptr addrspace(1) %a3, i64 %a4, i16 %a5)
+ ret i32 %result
+}
+
+; arg4 must be i64 or double, not i32
+define i32 @invalid_arg4_i32(i32 %a0, i32 %a1, i32 %a2, ptr %a3, i32 %a4, i16 %a5) {
+ %result = call i32 @llvm.nvvm.test.anytype.i32.i32.i32.p0.i32.i16(i32 %a0, i32 %a1, i32 %a2, ptr %a3, i32 %a4, i16 %a5)
+ ret i32 %result
+}
+
+; arg5 must be i16, <4 x i32>, or <4 x f32>, not <2 x i32>
+define i32 @invalid_arg5_v2i32(i32 %a0, i32 %a1, i32 %a2, ptr %a3, i64 %a4, <2 x i32> %a5) {
+ %result = call i32 @llvm.nvvm.test.anytype.i32.i32.i32.p0.i64.v2i32(i32 %a0, i32 %a1, i32 %a2, ptr %a3, i64 %a4, <2 x i32> %a5)
+ ret i32 %result
+}
+
+define {i32, i8, i64} @invalid_return_type_i8(i32 %arg) {
+ %result = call {i32, i8, i64} @llvm.nvvm.test.return.type.i8(i32 %arg)
+ ret {i32, i8, i64} %result
+}
+
+declare i32 @llvm.nvvm.test.anytype.i8.i8.i32.p0.i64.i16(i8, i8, i32, ptr, i64, i16)
+declare i32 @llvm.nvvm.test.anytype.i16.i32.i32.p0.i64.i16(i16, i32, i32, ptr, i64, i16)
+declare i32 @llvm.nvvm.test.anytype.i32.i32.f64.p0.i64.i16(i32, i32, double, ptr, i64, i16)
+declare i32 @llvm.nvvm.test.anytype.i32.i32.i32.p1.i64.i16(i32, i32, i32, ptr addrspace(1), i64, i16)
+declare i32 @llvm.nvvm.test.anytype.i32.i32.i32.p0.i32.i16(i32, i32, i32, ptr, i32, i16)
+declare i32 @llvm.nvvm.test.anytype.i32.i32.i32.p0.i64.v2i32(i32, i32, i32, ptr, i64, <2 x i32>)
+declare {i32, i8, i64} @llvm.nvvm.test.return.type.i8(i32)
diff --git a/llvm/test/CodeGen/NVPTX/anytypeof_constraints_valid.ll b/llvm/test/CodeGen/NVPTX/anytypeof_constraints_valid.ll
new file mode 100644
index 0000000000000..2efa46c04116f
--- /dev/null
+++ b/llvm/test/CodeGen/NVPTX/anytypeof_constraints_valid.ll
@@ -0,0 +1,45 @@
+; RUN: llvm-as < %s -o /dev/null
+; Test cases for int_nvvm_test_anytype intrinsic - VALID combinations
+
+define i32 @test_arg0_i16(i16 %a0, i16 %a1, i32 %a2, ptr %a3, i64 %a4, i16 %a5) {
+ %result = call i32 @llvm.nvvm.test.anytype.i16.i16.i32.p0.i64.i16(i16 %a0, i16 %a1, i32 %a2, ptr %a3, i64 %a4, i16 %a5)
+ ret i32 %result
+}
+
+define i32 @test_arg0_i32(i32 %a0, i32 %a1, i32 %a2, ptr %a3, i64 %a4, i16 %a5) {
+ %result = call i32 @llvm.nvvm.test.anytype.i32.i32.i32.p0.i64.i16(i32 %a0, i32 %a1, i32 %a2, ptr %a3, i64 %a4, i16 %a5)
+ ret i32 %result
+}
+
+define i32 @test_arg2_anyint(i32 %a0, i32 %a1, i32 %a2, ptr %a3, i64 %a4, i16 %a5) {
+ %result = call i32 @llvm.nvvm.test.anytype.i32.i32.i32.p0.i64.i16(i32 %a0, i32 %a1, i32 %a2, ptr %a3, i64 %a4, i16 %a5)
+ ret i32 %result
+}
+
+define i32 @test_arg3_shared_ptr(i32 %a0, i32 %a1, i32 %a2, ptr addrspace(3) %a3, i64 %a4, i16 %a5) {
+ %result = call i32 @llvm.nvvm.test.anytype.i32.i32.i32.p3.i64.i16(i32 %a0, i32 %a1, i32 %a2, ptr addrspace(3) %a3, i64 %a4, i16 %a5)
+ ret i32 %result
+}
+
+define i32 @test_arg4_double(i32 %a0, i32 %a1, i32 %a2, ptr %a3, double %a4, i16 %a5) {
+ %result = call i32 @llvm.nvvm.test.anytype.i32.i32.i32.p0.f64.i16(i32 %a0, i32 %a1, i32 %a2, ptr %a3, double %a4, i16 %a5)
+ ret i32 %result
+}
+
+define i32 @test_arg5_v4i32(i32 %a0, i32 %a1, i32 %a2, ptr %a3, i64 %a4, <4 x i32> %a5) {
+ %result = call i32 @llvm.nvvm.test.anytype.i32.i32.i32.p0.i64.v4i32(i32 %a0, i32 %a1, i32 %a2, ptr %a3, i64 %a4, <4 x i32> %a5)
+ ret i32 %result
+}
+
+define {i32, i32, i64} @test_return_type_valid(i32 %arg) {
+ %result = call {i32, i32, i64} @llvm.nvvm.test.return.type.i32(i32 %arg)
+ ret {i32, i32, i64} %result
+}
+
+declare i32 @llvm.nvvm.test.anytype.i16.i16.i32.p0.i64.i16(i16, i16, i32, ptr, i64, i16)
+declare i32 @llvm.nvvm.test.anytype.i32.i32.i32.p0.i64.i16(i32, i32, i32, ptr, i64, i16)
+declare i32 @llvm.nvvm.test.anytype.i32.i32.f32.p0.i64.i16(i32, i32, i32, ptr, i64, i16)
+declare i32 @llvm.nvvm.test.anytype.i32.i32.i32.p3.i64.i16(i32, i32, i32, ptr addrspace(3), i64, i16)
+declare i32 @llvm.nvvm.test.anytype.i32.i32.i32.p0.f64.i16(i32, i32, i32, ptr, double, i16)
+declare i32 @llvm.nvvm.test.anytype.i32.i32.i32.p0.i64.v4i32(i32, i32, i32, ptr, i64, <4 x i32>)
+declare {i32, i32, i64} @llvm.nvvm.test.return.type.i32(i32)
>From 3857c6239cb4a38c151bba5a885085d321022a9a Mon Sep 17 00:00:00 2001
From: Dharuni R Acharya <dharunira at nvidia.com>
Date: Wed, 17 Dec 2025 05:03:56 +0000
Subject: [PATCH 2/2] Fix formatting
---
llvm/include/llvm/IR/Intrinsics.h | 3 +-
llvm/lib/IR/Intrinsics.cpp | 155 +++++++++++++++++-------------
llvm/lib/IR/Verifier.cpp | 5 +-
3 files changed, 90 insertions(+), 73 deletions(-)
diff --git a/llvm/include/llvm/IR/Intrinsics.h b/llvm/include/llvm/IR/Intrinsics.h
index 3d2dcd5e0da1a..0b1daa102c2a2 100644
--- a/llvm/include/llvm/IR/Intrinsics.h
+++ b/llvm/include/llvm/IR/Intrinsics.h
@@ -181,7 +181,8 @@ namespace Intrinsic {
AMX,
PPCQuad,
AArch64Svcount,
- ArgumentTypeConstraint, // For AnyTypeOf - marks constrained argument types.
+ ArgumentTypeConstraint, // For AnyTypeOf - marks constrained argument
+ // types.
} Kind;
union {
diff --git a/llvm/lib/IR/Intrinsics.cpp b/llvm/lib/IR/Intrinsics.cpp
index 88762923fd96d..0e349925b2c8c 100644
--- a/llvm/lib/IR/Intrinsics.cpp
+++ b/llvm/lib/IR/Intrinsics.cpp
@@ -358,8 +358,9 @@ DecodeIITType(unsigned &NextElt, ArrayRef<unsigned char> Infos,
NextElt++;
unsigned NumTypes = Infos[NextElt++];
- OutputTable.push_back(IITDescriptor::get(IITDescriptor::ArgumentTypeConstraint, NumTypes));
-
+ OutputTable.push_back(
+ IITDescriptor::get(IITDescriptor::ArgumentTypeConstraint, NumTypes));
+
for (unsigned i = 0; i < NumTypes; ++i)
DecodeIITType(NextElt, Infos, Info, OutputTable);
return;
@@ -584,7 +585,8 @@ static Type *DecodeFixedType(ArrayRef<Intrinsic::IITDescriptor> &Infos,
// Return the overloaded type (which determines the pointers address space)
return Tys[D.getOverloadArgNumber()];
case IITDescriptor::ArgumentTypeConstraint:
- llvm_unreachable("ArgumentTypeConstraint should not appear in DecodeFixedType");
+ llvm_unreachable(
+ "ArgumentTypeConstraint should not appear in DecodeFixedType");
}
llvm_unreachable("unhandled");
}
@@ -597,10 +599,11 @@ FunctionType *Intrinsic::getType(LLVMContext &Context, ID id,
ArrayRef<IITDescriptor> TableRef = Table;
Type *ResultTy = DecodeFixedType(TableRef, Tys, Context);
- if (!TableRef.empty() && TableRef[0].Kind == IITDescriptor::ArgumentTypeConstraint) {
+ if (!TableRef.empty() &&
+ TableRef[0].Kind == IITDescriptor::ArgumentTypeConstraint) {
unsigned NumConstraints = TableRef[0].getArgumentNumConstraints();
TableRef = TableRef.slice(1);
-
+
for (unsigned i = 0; i < NumConstraints; ++i)
(void)DecodeFixedType(TableRef, Tys, Context);
}
@@ -608,11 +611,12 @@ FunctionType *Intrinsic::getType(LLVMContext &Context, ID id,
SmallVector<Type *, 8> ArgTys;
while (!TableRef.empty()) {
ArgTys.push_back(DecodeFixedType(TableRef, Tys, Context));
-
- if (!TableRef.empty() && TableRef[0].Kind == IITDescriptor::ArgumentTypeConstraint) {
+
+ if (!TableRef.empty() &&
+ TableRef[0].Kind == IITDescriptor::ArgumentTypeConstraint) {
unsigned NumConstraints = TableRef[0].getArgumentNumConstraints();
TableRef = TableRef.slice(1);
-
+
for (unsigned i = 0; i < NumConstraints; ++i)
(void)DecodeFixedType(TableRef, Tys, Context);
}
@@ -865,36 +869,39 @@ bool Intrinsic::hasConstrainedFPRoundingModeOperand(Intrinsic::ID QID) {
}
}
-// Helper to skip past descriptors for one complete type in AnyTypeOf constraints.
-static unsigned skipDescriptorsForSingleType(ArrayRef<Intrinsic::IITDescriptor> &Infos) {
+// Helper to skip past descriptors for one complete type in AnyTypeOf
+// constraints.
+static unsigned
+skipDescriptorsForSingleType(ArrayRef<Intrinsic::IITDescriptor> &Infos) {
using namespace Intrinsic;
-
+
if (Infos.empty())
return 0;
-
+
IITDescriptor D = Infos[0];
unsigned Count = 1;
Infos = Infos.slice(1);
-
+
switch (D.Kind) {
case IITDescriptor::Vector:
Count += skipDescriptorsForSingleType(Infos);
break;
-
+
case IITDescriptor::Pointer:
break;
-
+
case IITDescriptor::Struct:
for (unsigned i = 0, e = D.Struct_NumElements; i != e; ++i)
Count += skipDescriptorsForSingleType(Infos);
break;
-
+
case IITDescriptor::SameVecWidthArgument:
Count += skipDescriptorsForSingleType(Infos);
break;
-
+
case IITDescriptor::Argument:
- if (!Infos.empty() && Infos[0].Kind == IITDescriptor::ArgumentTypeConstraint) {
+ if (!Infos.empty() &&
+ Infos[0].Kind == IITDescriptor::ArgumentTypeConstraint) {
unsigned NumConstraints = Infos[0].getArgumentNumConstraints();
Count++;
Infos = Infos.slice(1);
@@ -995,7 +1002,7 @@ matchIntrinsicType(Type *Ty, ArrayRef<Intrinsic::IITDescriptor> &Infos,
// verify that the later instance matches the previous instance.
if (D.getArgumentNumber() < ArgTys.size())
return Ty != ArgTys[D.getArgumentNumber()];
-
+
switch (D.getArgumentKind()) {
case IITDescriptor::AK_Any:
break;
@@ -1026,14 +1033,15 @@ matchIntrinsicType(Type *Ty, ArrayRef<Intrinsic::IITDescriptor> &Infos,
assert(D.getArgumentNumber() == ArgTys.size() && !IsDeferredCheck &&
"Table consistency error");
ArgTys.push_back(Ty);
-
- if (!Infos.empty() && Infos[0].Kind == IITDescriptor::ArgumentTypeConstraint) {
+
+ if (!Infos.empty() &&
+ Infos[0].Kind == IITDescriptor::ArgumentTypeConstraint) {
unsigned NumConstraints = Infos[0].getArgumentNumConstraints();
- Infos = Infos.slice(1);
+ Infos = Infos.slice(1);
for (unsigned i = 0; i < NumConstraints; ++i)
skipDescriptorsForSingleType(Infos);
-
+
return false;
}
@@ -1154,7 +1162,8 @@ matchIntrinsicType(Type *Ty, ArrayRef<Intrinsic::IITDescriptor> &Infos,
return ThisArgVecTy != VectorType::getInteger(ReferenceType);
}
case IITDescriptor::ArgumentTypeConstraint:
- llvm_unreachable("ArgumentTypeConstraint should be handled in Argument case");
+ llvm_unreachable(
+ "ArgumentTypeConstraint should be handled in Argument case");
}
llvm_unreachable("unhandled");
}
@@ -1185,19 +1194,21 @@ Intrinsic::matchIntrinsicSignature(FunctionType *FTy,
return MatchIntrinsicTypes_Match;
}
-// Helper: Check if a type matches AnyTypeOf constraints using matchIntrinsicType.
-static bool verifyTypeAgainstConstraints(
- Type *Ty, unsigned NumConstraints,
- ArrayRef<Intrinsic::IITDescriptor> &Infos) {
+// Helper: Check if a type matches AnyTypeOf constraints using
+// matchIntrinsicType.
+static bool
+verifyTypeAgainstConstraints(Type *Ty, unsigned NumConstraints,
+ ArrayRef<Intrinsic::IITDescriptor> &Infos) {
using namespace Intrinsic;
-
+
bool Matched = false;
for (unsigned i = 0; i < NumConstraints && !Matched; ++i) {
ArrayRef<IITDescriptor> TypeDesc = Infos;
SmallVector<Type *, 4> DummyArgTys;
SmallVector<DeferredIntrinsicMatchPair, 2> DummyDeferredChecks;
-
- if (!matchIntrinsicType(Ty, TypeDesc, DummyArgTys, DummyDeferredChecks, false)) {
+
+ if (!matchIntrinsicType(Ty, TypeDesc, DummyArgTys, DummyDeferredChecks,
+ false)) {
Matched = true;
for (unsigned j = 0; j < NumConstraints - i; ++j)
skipDescriptorsForSingleType(Infos);
@@ -1205,7 +1216,7 @@ static bool verifyTypeAgainstConstraints(
}
skipDescriptorsForSingleType(Infos);
}
-
+
return Matched;
}
@@ -1217,63 +1228,65 @@ static std::string typeToString(Type *Ty) {
return Str;
}
-bool Intrinsic::verifyIntrinsicTypeConstraints(
- ID id, FunctionType *FTy,
- std::string &ErrMsg) {
-
+bool Intrinsic::verifyIntrinsicTypeConstraints(ID id, FunctionType *FTy,
+ std::string &ErrMsg) {
+
if (id == 0 || id >= Intrinsic::num_intrinsics)
return true;
-
+
SmallVector<IITDescriptor, 8> Table;
getIntrinsicInfoTableEntries(id, Table);
-
+
if (Table.empty())
return true;
-
+
ArrayRef<IITDescriptor> Infos = Table;
SmallVector<Type *, 4> ArgTys;
-
+
// Processing return type.
Type *RetTy = FTy->getReturnType();
if (!Infos.empty() && Infos[0].Kind == IITDescriptor::Argument) {
Infos = Infos.slice(1);
-
- if (!Infos.empty() && Infos[0].Kind == IITDescriptor::ArgumentTypeConstraint) {
+
+ if (!Infos.empty() &&
+ Infos[0].Kind == IITDescriptor::ArgumentTypeConstraint) {
unsigned NumConstraints = Infos[0].getArgumentNumConstraints();
Infos = Infos.slice(1);
-
+
bool Matched = false;
-
- // Check if a struct type's elements are all present in AnyTypeOf constraint list.
+
+ // Check if a struct type's elements are all present in AnyTypeOf
+ // constraint list.
if (auto *STy = dyn_cast<StructType>(RetTy)) {
SmallVector<Type *, 8> AllowedTypes;
ArrayRef<IITDescriptor> TempInfos = Infos;
for (unsigned i = 0; i < NumConstraints; ++i) {
ArrayRef<IITDescriptor> TypeDesc = TempInfos;
SmallVector<Type *, 4> DummyTys;
- Type *ConstraintTy = DecodeFixedType(TypeDesc, DummyTys, FTy->getContext());
+ Type *ConstraintTy =
+ DecodeFixedType(TypeDesc, DummyTys, FTy->getContext());
AllowedTypes.push_back(ConstraintTy);
TempInfos = TypeDesc;
}
-
+
Matched = llvm::all_of(STy->elements(), [&](Type *ElemTy) {
- return llvm::is_contained(AllowedTypes, ElemTy);
+ return llvm::is_contained(AllowedTypes, ElemTy);
});
-
+
if (Matched) {
for (unsigned i = 0; i < NumConstraints; ++i)
skipDescriptorsForSingleType(Infos);
Matched = true;
}
}
-
+
if (!Matched) {
for (unsigned i = 0; i < NumConstraints && !Matched; ++i) {
ArrayRef<IITDescriptor> TypeDesc = Infos;
SmallVector<Type *, 4> DummyArgTys;
SmallVector<DeferredIntrinsicMatchPair, 2> DummyDeferredChecks;
-
- if (!matchIntrinsicType(RetTy, TypeDesc, DummyArgTys,
+
+ if (!matchIntrinsicType(RetTy, TypeDesc, DummyArgTys,
DummyDeferredChecks, false)) {
Matched = true;
for (unsigned j = 0; j < NumConstraints - i; ++j)
@@ -1283,9 +1296,10 @@ bool Intrinsic::verifyIntrinsicTypeConstraints(
skipDescriptorsForSingleType(Infos);
}
}
-
+
if (!Matched) {
- ErrMsg = "Return type '" + typeToString(RetTy) + "' not in allowed types";
+ ErrMsg =
+ "Return type '" + typeToString(RetTy) + "' not in allowed types";
return false;
}
}
@@ -1297,22 +1311,24 @@ bool Intrinsic::verifyIntrinsicTypeConstraints(
else {
unsigned NumElements = Infos[0].Struct_NumElements;
Infos = Infos.slice(1);
-
+
for (unsigned ElemIdx = 0; ElemIdx < NumElements; ++ElemIdx) {
if (Infos.empty())
break;
-
+
if (Infos[0].Kind == IITDescriptor::Argument) {
Infos = Infos.slice(1);
-
- if (!Infos.empty() && Infos[0].Kind == IITDescriptor::ArgumentTypeConstraint) {
+
+ if (!Infos.empty() &&
+ Infos[0].Kind == IITDescriptor::ArgumentTypeConstraint) {
unsigned NumConstraints = Infos[0].getArgumentNumConstraints();
Infos = Infos.slice(1);
-
+
Type *ElemTy = STy->getElementType(ElemIdx);
if (!verifyTypeAgainstConstraints(ElemTy, NumConstraints, Infos)) {
- ErrMsg = "Return type struct element " + std::to_string(ElemIdx) +
- " type '" + typeToString(ElemTy) + "' not in allowed types";
+ ErrMsg = "Return type struct element " + std::to_string(ElemIdx) +
+ " type '" + typeToString(ElemTy) +
+ "' not in allowed types";
return false;
}
}
@@ -1327,26 +1343,27 @@ bool Intrinsic::verifyIntrinsicTypeConstraints(
for (unsigned ParamIdx = 0; ParamIdx < FTy->getNumParams(); ++ParamIdx) {
if (Infos.empty())
break;
-
+
Type *ParamTy = FTy->getParamType(ParamIdx);
-
+
if (Infos[0].Kind == IITDescriptor::Argument) {
unsigned ArgNum = Infos[0].getArgumentNumber();
Infos = Infos.slice(1);
-
- if (!Infos.empty() && Infos[0].Kind == IITDescriptor::ArgumentTypeConstraint) {
+
+ if (!Infos.empty() &&
+ Infos[0].Kind == IITDescriptor::ArgumentTypeConstraint) {
unsigned NumConstraints = Infos[0].getArgumentNumConstraints();
Infos = Infos.slice(1);
-
+
if (!verifyTypeAgainstConstraints(ParamTy, NumConstraints, Infos)) {
- ErrMsg = "Parameter " + std::to_string(ParamIdx) + " type '" +
+ ErrMsg = "Parameter " + std::to_string(ParamIdx) + " type '" +
typeToString(ParamTy) + "' not in allowed types";
return false;
}
-
+
if (ArgNum == ArgTys.size())
ArgTys.push_back(ParamTy);
- }
+ }
} else
skipDescriptorsForSingleType(Infos);
}
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index ec771ff773223..2cc83e5b2c4c2 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -3277,9 +3277,8 @@ void Verifier::visitFunction(const Function &F) {
// Verify AnyTypeOf type constraints for intrinsic declarations.
if (F.isIntrinsic()) {
std::string ConstraintErrMsg;
- if (!Intrinsic::verifyIntrinsicTypeConstraints(F.getIntrinsicID(),
- F.getFunctionType(),
- ConstraintErrMsg))
+ if (!Intrinsic::verifyIntrinsicTypeConstraints(
+ F.getIntrinsicID(), F.getFunctionType(), ConstraintErrMsg))
CheckFailed("Intrinsic declaration '" + F.getName().str() +
"' violates type constraint: " + ConstraintErrMsg);
}
More information about the llvm-commits
mailing list