[llvm] [LLVM][Tablegen] Add Default arguments support for Intrinsics in TableGen (PR #198557)
Varad Rahul Kamthe via llvm-commits
llvm-commits at lists.llvm.org
Tue Jun 30 04:50:04 PDT 2026
https://github.com/varadk27 updated https://github.com/llvm/llvm-project/pull/198557
>From 264e2000cf2fc0066c67f982c7ef027f7d7e135a Mon Sep 17 00:00:00 2001
From: Varad Rahul Kamthe <vkamthe at nvidia.com>
Date: Tue, 30 Jun 2026 10:51:46 +0000
Subject: [PATCH] [LLVM][Intrinsics] Add default argument support for
Intrinsics in TableGen
---
llvm/include/llvm/IR/Intrinsics.h | 9 ++
llvm/include/llvm/IR/Intrinsics.td | 18 +++-
llvm/include/llvm/IR/IntrinsicsNVVM.td | 44 ++++++++
llvm/lib/IR/AutoUpgrade.cpp | 91 ++++++++++++++++
llvm/lib/IR/Intrinsics.cpp | 5 +
.../intrinsic-default-args-upgrade.ll | 100 ++++++++++++++++++
llvm/test/TableGen/intrinsic-default-args.td | 94 ++++++++++++++++
.../TableGen/Basic/CodeGenIntrinsics.cpp | 75 +++++++++++++
llvm/utils/TableGen/Basic/CodeGenIntrinsics.h | 5 +
.../utils/TableGen/Basic/IntrinsicEmitter.cpp | 95 +++++++++++++++++
10 files changed, 534 insertions(+), 2 deletions(-)
create mode 100644 llvm/test/Assembler/intrinsic-default-args-upgrade.ll
create mode 100644 llvm/test/TableGen/intrinsic-default-args.td
diff --git a/llvm/include/llvm/IR/Intrinsics.h b/llvm/include/llvm/IR/Intrinsics.h
index 76269fbcb1412..2f9239aa7b77a 100644
--- a/llvm/include/llvm/IR/Intrinsics.h
+++ b/llvm/include/llvm/IR/Intrinsics.h
@@ -95,6 +95,15 @@ LLVM_ABI bool isTriviallyScalarizable(ID id);
/// Returns true if the intrinsic has pretty printed immediate arguments.
LLVM_ABI bool hasPrettyPrintedArgs(ID id);
+/// Returns the first default argument index and an ArrayRef of all
+/// default values for the trailing parameters of intrinsic IID.
+/// Returns {0, empty} if the intrinsic has no default arguments.
+///
+/// The defaults are stored contiguously starting at FirstDefault and
+/// extending to the last parameter (mirrors C++ default-argument
+/// rules).
+LLVM_ABI std::pair<unsigned, ArrayRef<uint64_t>> getAllDefaultArgValues(ID IID);
+
/// isTargetIntrinsic - Returns true if IID is an intrinsic specific to a
/// certain target. If it is a generic intrinsic false is returned.
LLVM_ABI bool isTargetIntrinsic(ID IID);
diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td
index 007882c492b4c..8ff71055eb29f 100644
--- a/llvm/include/llvm/IR/Intrinsics.td
+++ b/llvm/include/llvm/IR/Intrinsics.td
@@ -136,9 +136,23 @@ class Returned<ArgIndex idx> : IntrinsicProperty {
int ArgNo = idx.Value;
}
-// ImmArg - The specified argument must be an immediate.
-class ImmArg<ArgIndex idx> : IntrinsicProperty {
+// DefaultValue - A value carrier paired with ImmArg to declare a default for
+// a missing trailing argument. AutoUpgrade fills the default when an old
+// .bc / .ll file is loaded.
+class DefaultValue<int val> {
+ int Value = val;
+}
+
+// Sentinel — used as the default for ImmArg's optional DefaultValue parameter.
+def NoDefault : DefaultValue<0> {
+ let Value = ?;
+}
+
+// ImmArg - The specified argument must be an immediate. The optional
+// second template parameter specifies a default value for AutoUpgrade.
+class ImmArg<ArgIndex idx, DefaultValue val = NoDefault> : IntrinsicProperty {
int ArgNo = idx.Value;
+ DefaultValue Default = val;
}
// ReadOnly - The specified argument pointer is not written to through the
diff --git a/llvm/include/llvm/IR/IntrinsicsNVVM.td b/llvm/include/llvm/IR/IntrinsicsNVVM.td
index 51ea934b1c0f4..d86434a69cfd6 100644
--- a/llvm/include/llvm/IR/IntrinsicsNVVM.td
+++ b/llvm/include/llvm/IR/IntrinsicsNVVM.td
@@ -1301,6 +1301,34 @@ let TargetPrefix = "nvvm" in {
DefaultAttrsIntrinsic<[], [llvm_i32_ty],
[IntrConvergent, IntrNoMem, IntrHasSideEffects]>;
+ //
+ // Test intrinsic for default-arg feature: three required i32 args plus a
+ // fourth i32 with DefaultValue = 255.
+ //
+ def int_nvvm_test_add3 :
+ NVVMPureIntrinsic<[llvm_i32_ty],
+ [llvm_i32_ty, llvm_i32_ty, llvm_i32_ty, llvm_i32_ty],
+ [ImmArg<ArgIndex<3>, DefaultValue<255>>]>;
+
+ //
+ // Test intrinsic for default-arg feature: four required i32 args plus two
+ // trailing args (i64, i32) with DefaultValue = 50 and 14.
+ //
+ def int_nvvm_test_add4 :
+ NVVMPureIntrinsic<[llvm_i32_ty],
+ [llvm_i32_ty, llvm_i32_ty, llvm_i32_ty, llvm_i32_ty,
+ llvm_i64_ty, llvm_i32_ty],
+ [ImmArg<ArgIndex<4>, DefaultValue<50>>,
+ ImmArg<ArgIndex<5>, DefaultValue<14>>]>;
+
+ //
+ // Dummy based on int_nvvm_nanosleep. Adds i1 flag with default = false.
+ //
+ def int_nvvm_nanosleep_dummy :
+ DefaultAttrsIntrinsic<[], [llvm_i32_ty, llvm_i1_ty],
+ [IntrConvergent, IntrNoMem, IntrHasSideEffects,
+ ImmArg<ArgIndex<1>, DefaultValue<false>>]>;
+
//
// Performance Monitor Events (pm events) intrinsics
//
@@ -2029,6 +2057,11 @@ def int_nvvm_cp_async_bulk_commit_group : Intrinsic<[]>;
def int_nvvm_cp_async_bulk_wait_group :
Intrinsic<[], [llvm_i32_ty], [ImmArg<ArgIndex<0>>]>;
+// Dummy based on cp_async_bulk_wait_group. Adds i1 flag with default = false.
+def int_nvvm_cp_async_bulk_wait_group_dummy :
+ Intrinsic<[], [llvm_i32_ty, llvm_i1_ty],
+ [ImmArg<ArgIndex<1>, DefaultValue<false>>]>;
+
def int_nvvm_cp_async_bulk_wait_group_read :
Intrinsic<[], [llvm_i32_ty], [ImmArg<ArgIndex<0>>]>;
@@ -2148,6 +2181,11 @@ let IntrProperties = [IntrNoMem] in {
def int_nvvm_move_ptr : DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_anyptr_ty]>;
}
+// Dummy based on int_nvvm_move_i32. Adds i64 second arg with default = 99.
+def int_nvvm_move_i32_dummy :
+ DefaultAttrsIntrinsic<[llvm_i32_ty], [llvm_i32_ty, llvm_i64_ty],
+ [ImmArg<ArgIndex<1>, DefaultValue<99>>]>;
+
// For getting the handle from a texture or surface variable
def int_nvvm_texsurf_handle
: NVVMPureIntrinsic<[llvm_i64_ty], [llvm_metadata_ty, llvm_anyptr_ty]>;
@@ -3130,6 +3168,12 @@ def int_nvvm_tcgen05_fence_before_thread_sync : Intrinsic<[], [],
def int_nvvm_tcgen05_fence_after_thread_sync : Intrinsic<[], [],
[IntrNoMem, IntrHasSideEffects]>;
+// Dummy based on tcgen05_fence. Two i32 args + i1 flag with default = false.
+def int_nvvm_tcgen05_fence_dummy :
+ Intrinsic<[], [llvm_i32_ty, llvm_i32_ty, llvm_i1_ty],
+ [IntrConvergent,
+ ImmArg<ArgIndex<2>, DefaultValue<false>>]>;
+
// Tcgen05 cp intrinsics
foreach cta_group = ["cg1", "cg2"] in {
foreach src_fmt = ["", "b6x16_p32", "b4x16_p64"] in {
diff --git a/llvm/lib/IR/AutoUpgrade.cpp b/llvm/lib/IR/AutoUpgrade.cpp
index 3a823f906b012..dcaf329cd8248 100644
--- a/llvm/lib/IR/AutoUpgrade.cpp
+++ b/llvm/lib/IR/AutoUpgrade.cpp
@@ -1308,6 +1308,36 @@ static bool convertIntrinsicValidType(StringRef Name,
return false;
}
+static bool upgradeIntrinsicDeclWithDefaultArgs(Function *F, Function *&NewFn) {
+ Intrinsic::ID IID = Intrinsic::lookupIntrinsicID(F->getName());
+ if (IID == Intrinsic::not_intrinsic)
+ return false;
+
+ auto [FirstDefault, Defaults] = Intrinsic::getAllDefaultArgValues(IID);
+ if (Defaults.empty())
+ return false;
+
+ // Overloaded intrinsics are out of scope for the default-arg feature
+ // and will be supported in a follow-up.
+ if (Intrinsic::isOverloaded(IID))
+ return false;
+
+ // Get the canonical full declaration for this intrinsic.
+ Function *FullDecl = Intrinsic::getOrInsertDeclaration(F->getParent(), IID);
+
+ // If the existing declaration already has all args, nothing to upgrade
+ if (F->arg_size() >= FullDecl->arg_size())
+ return false;
+
+ // Defaults are a contiguous trailing block, so checking the first missing
+ // argument is enough.
+ if (F->arg_size() < FirstDefault)
+ return false;
+
+ NewFn = FullDecl;
+ return true;
+}
+
static bool upgradeIntrinsicFunction1(Function *F, Function *&NewFn,
bool CanUpgradeDebugIntrinsicsToRecords) {
assert(F && "Illegal to upgrade a non-existent Function.");
@@ -1943,6 +1973,9 @@ static bool upgradeIntrinsicFunction1(Function *F, Function *&NewFn,
// to both detect an intrinsic which needs upgrading, and to provide the
// upgraded form of the intrinsic. We should perhaps have two separate
// functions for this.
+ if (upgradeIntrinsicDeclWithDefaultArgs(F, NewFn))
+ return true;
+
return false;
}
@@ -5073,6 +5106,59 @@ static Value *upgradeConvertIntrinsicCall(StringRef Name, CallBase *CI,
return nullptr;
}
+static bool upgradeIntrinsicCallWithDefaultArgs(CallBase *CI, Function *NewFn,
+ IRBuilder<> &Builder) {
+ Intrinsic::ID IID = NewFn->getIntrinsicID();
+
+ auto [FirstDefault, Defaults] = Intrinsic::getAllDefaultArgValues(IID);
+ if (Defaults.empty())
+ return false;
+
+ unsigned OldArgCount = CI->arg_size();
+ unsigned NewArgCount = NewFn->arg_size();
+
+ // If the caller already supplied all arguments (or more), nothing to do.
+ // This mirrors C++ semantics: an explicitly-passed value is never overridden.
+ if (OldArgCount >= NewArgCount)
+ return false;
+
+ // Start with the existing arguments from the old call.
+ SmallVector<Value *, 8> NewArgs(CI->args());
+
+ // Defaults are a contiguous trailing block, so checking the first missing
+ // argument is enough.
+ if (OldArgCount < FirstDefault)
+ return false;
+
+ // Fill in each missing trailing argument from the table.
+ FunctionType *NewFT = NewFn->getFunctionType();
+ for (unsigned Idx = OldArgCount; Idx < NewArgCount; ++Idx) {
+ assert(Idx >= FirstDefault && Idx - FirstDefault < Defaults.size() &&
+ "missing argument outside the default range");
+ Type *ParamTy = NewFT->getParamType(Idx);
+
+ // Only integer types are supported (i1, i8, i16, i32, i64).
+ if (!ParamTy->isIntegerTy())
+ return false;
+ NewArgs.push_back(ConstantInt::get(ParamTy, Defaults[Idx - FirstDefault]));
+ }
+
+ // Preserve operand bundles by creating the call with them.
+ SmallVector<OperandBundleDef, 1> OpBundles;
+ CI->getOperandBundlesAsDefs(OpBundles);
+ CallInst *NewCall = Builder.CreateCall(NewFn, NewArgs, OpBundles);
+
+ NewCall->takeName(CI);
+ NewCall->setCallingConv(CI->getCallingConv());
+ NewCall->copyMetadata(*CI);
+ if (auto *OldCI = dyn_cast<CallInst>(CI))
+ NewCall->setTailCallKind(OldCI->getTailCallKind());
+
+ CI->replaceAllUsesWith(NewCall);
+ CI->eraseFromParent();
+ return true;
+}
+
/// Upgrade a call to an old intrinsic. All argument and return casting must be
/// provided to seamlessly integrate with existing context.
void llvm::UpgradeIntrinsicCall(CallBase *CI, Function *NewFn) {
@@ -5178,6 +5264,11 @@ void llvm::UpgradeIntrinsicCall(CallBase *CI, Function *NewFn) {
CallInst *NewCall = nullptr;
switch (NewFn->getIntrinsicID()) {
default: {
+ // Last resort: try the data-driven default-arg upgrade.
+ // Handles any intrinsic annotated with ImmArg<..., DefaultValue<...>>
+ // in its .td definition, without needing a dedicated case.
+ if (upgradeIntrinsicCallWithDefaultArgs(CI, NewFn, Builder))
+ return;
DefaultCase();
return;
}
diff --git a/llvm/lib/IR/Intrinsics.cpp b/llvm/lib/IR/Intrinsics.cpp
index 044ee61b10a26..6ddfc18c56501 100644
--- a/llvm/lib/IR/Intrinsics.cpp
+++ b/llvm/lib/IR/Intrinsics.cpp
@@ -1474,3 +1474,8 @@ Intrinsic::ID Intrinsic::getDeinterleaveIntrinsicID(unsigned Factor) {
#define GET_INTRINSIC_PRETTY_PRINT_ARGUMENTS
#include "llvm/IR/IntrinsicImpl.inc"
+
+// Emit the default-argument values table and lookup function
+// (Intrinsic::getAllDefaultArgValues).
+#define GET_INTRINSIC_DEFAULT_ARG_VALUES
+#include "llvm/IR/IntrinsicImpl.inc"
diff --git a/llvm/test/Assembler/intrinsic-default-args-upgrade.ll b/llvm/test/Assembler/intrinsic-default-args-upgrade.ll
new file mode 100644
index 0000000000000..9b7a2f74311f9
--- /dev/null
+++ b/llvm/test/Assembler/intrinsic-default-args-upgrade.ll
@@ -0,0 +1,100 @@
+; Verify that AutoUpgrade fills in missing trailing arguments of intrinsic
+; calls from the default values declared via DefaultValue in TableGen.
+;
+; RUN: rm -rf %t && split-file %s %t
+; RUN: opt -S < %t/single-val.ll | FileCheck %t/single-val.ll
+; RUN: opt -S < %t/explicit-preserved.ll | FileCheck %t/explicit-preserved.ll
+; RUN: opt -S < %t/multiple-trailing.ll | FileCheck %t/multiple-trailing.ll
+; RUN: opt -S < %t/partial-fill.ll | FileCheck %t/partial-fill.ll
+; RUN: opt -S < %t/nanosleep.ll | FileCheck %t/nanosleep.ll
+; RUN: opt -S < %t/cp-async-bulk.ll | FileCheck %t/cp-async-bulk.ll
+; RUN: opt -S < %t/move-i32.ll | FileCheck %t/move-i32.ll
+; RUN: opt -S < %t/tcgen05-fence.ll | FileCheck %t/tcgen05-fence.ll
+
+;--- single-val.ll
+; A single missing trailing argument is filled with its default (255).
+
+define i32 @test_default_single_val(i32 %a, i32 %b, i32 %c) {
+entry:
+ %r = call i32 @llvm.nvvm.test.add3(i32 %a, i32 %b, i32 %c)
+ ret i32 %r
+}
+; CHECK-LABEL: @test_default_single_val
+; CHECK: call i32 @llvm.nvvm.test.add3(i32 %a, i32 %b, i32 %c, i32 255)
+
+;--- explicit-preserved.ll
+; An explicitly-passed value must never be overridden by the default.
+
+define i32 @test_explicit_value_preserved(i32 %a, i32 %b, i32 %c) {
+entry:
+ %r = call i32 @llvm.nvvm.test.add3(i32 %a, i32 %b, i32 %c, i32 42)
+ ret i32 %r
+}
+; CHECK-LABEL: @test_explicit_value_preserved
+; CHECK: call i32 @llvm.nvvm.test.add3(i32 %a, i32 %b, i32 %c, i32 42)
+
+;--- multiple-trailing.ll
+; Two missing trailing arguments are both filled (i64 50, i32 14).
+
+define i32 @test_multiple_trailing_defaults(i32 %a, i32 %b, i32 %c, i32 %d) {
+entry:
+ %r = call i32 @llvm.nvvm.test.add4(i32 %a, i32 %b, i32 %c, i32 %d)
+ ret i32 %r
+}
+; CHECK-LABEL: @test_multiple_trailing_defaults
+; CHECK: call i32 @llvm.nvvm.test.add4(i32 %a, i32 %b, i32 %c, i32 %d, i64 50, i32 14)
+
+;--- partial-fill.ll
+; Arg[4] is passed explicitly (i64 99) and kept; only arg[5] (i32 14) is filled.
+
+define i32 @test_partial_fill(i32 %a, i32 %b, i32 %c, i32 %d) {
+entry:
+ %r = call i32 @llvm.nvvm.test.add4(i32 %a, i32 %b, i32 %c, i32 %d, i64 99)
+ ret i32 %r
+}
+; CHECK-LABEL: @test_partial_fill
+; CHECK: call i32 @llvm.nvvm.test.add4(i32 %a, i32 %b, i32 %c, i32 %d, i64 99, i32 14)
+
+;--- nanosleep.ll
+; Void-returning intrinsic; missing i1 argument defaults to false.
+
+define void @test_nanosleep_dummy(i32 %ns) {
+entry:
+ call void @llvm.nvvm.nanosleep.dummy(i32 %ns)
+ ret void
+}
+; CHECK-LABEL: @test_nanosleep_dummy
+; CHECK: call void @llvm.nvvm.nanosleep.dummy(i32 %ns, i1 false)
+
+;--- cp-async-bulk.ll
+; Void-returning intrinsic; missing i1 argument defaults to false.
+
+define void @test_cp_async_bulk_wait_group_dummy(i32 %n) {
+entry:
+ call void @llvm.nvvm.cp.async.bulk.wait.group.dummy(i32 %n)
+ ret void
+}
+; CHECK-LABEL: @test_cp_async_bulk_wait_group_dummy
+; CHECK: call void @llvm.nvvm.cp.async.bulk.wait.group.dummy(i32 %n, i1 false)
+
+;--- move-i32.ll
+; Value-returning intrinsic; missing i64 argument defaults to 99.
+
+define i32 @test_move_i32_dummy(i32 %a) {
+entry:
+ %r = call i32 @llvm.nvvm.move.i32.dummy(i32 %a)
+ ret i32 %r
+}
+; CHECK-LABEL: @test_move_i32_dummy
+; CHECK: call i32 @llvm.nvvm.move.i32.dummy(i32 %a, i64 99)
+
+;--- tcgen05-fence.ll
+; Missing i1 argument at index 2 defaults to false.
+
+define void @test_tcgen05_fence_dummy(i32 %flags, i32 %token) {
+entry:
+ call void @llvm.nvvm.tcgen05.fence.dummy(i32 %flags, i32 %token)
+ ret void
+}
+; CHECK-LABEL: @test_tcgen05_fence_dummy
+; CHECK: call void @llvm.nvvm.tcgen05.fence.dummy(i32 %flags, i32 %token, i1 false)
diff --git a/llvm/test/TableGen/intrinsic-default-args.td b/llvm/test/TableGen/intrinsic-default-args.td
new file mode 100644
index 0000000000000..e372a870880e7
--- /dev/null
+++ b/llvm/test/TableGen/intrinsic-default-args.td
@@ -0,0 +1,94 @@
+// Verify the default-argument values table and lookup function generated for
+// intrinsics that declare DefaultValue on an ImmArg, and the TableGen-time
+// validation of those defaults.
+
+// RUN: llvm-tblgen -gen-intrinsic-impl -I %p/../../include %s -DTEST_INTRINSICS_SUPPRESS_DEFS | FileCheck %s
+// RUN: not llvm-tblgen -gen-intrinsic-impl -I %p/../../include %s -DTEST_INTRINSICS_SUPPRESS_DEFS -DERROR_NEGATIVE 2>&1 | FileCheck %s --check-prefix=ERR-NEGATIVE
+// RUN: not llvm-tblgen -gen-intrinsic-impl -I %p/../../include %s -DTEST_INTRINSICS_SUPPRESS_DEFS -DERROR_RANGE 2>&1 | FileCheck %s --check-prefix=ERR-RANGE
+// RUN: not llvm-tblgen -gen-intrinsic-impl -I %p/../../include %s -DTEST_INTRINSICS_SUPPRESS_DEFS -DERROR_NONINT 2>&1 | FileCheck %s --check-prefix=ERR-NONINT
+// RUN: not llvm-tblgen -gen-intrinsic-impl -I %p/../../include %s -DTEST_INTRINSICS_SUPPRESS_DEFS -DERROR_OVERLOADED 2>&1 | FileCheck %s --check-prefix=ERR-OVERLOADED
+// RUN: not llvm-tblgen -gen-intrinsic-impl -I %p/../../include %s -DTEST_INTRINSICS_SUPPRESS_DEFS -DERROR_GAP 2>&1 | FileCheck %s --check-prefix=ERR-GAP
+
+include "llvm/IR/Intrinsics.td"
+
+// One trailing default: arg 1 (i32) defaults to 255.
+def int_test_one_default :
+ Intrinsic<[llvm_i32_ty], [llvm_i32_ty, llvm_i32_ty],
+ [ImmArg<ArgIndex<1>, DefaultValue<255>>]>;
+
+// Two trailing defaults: arg 1 (i64) = 50, arg 2 (i32) = 14.
+def int_test_two_defaults :
+ Intrinsic<[llvm_i32_ty], [llvm_i32_ty, llvm_i64_ty, llvm_i32_ty],
+ [ImmArg<ArgIndex<1>, DefaultValue<50>>,
+ ImmArg<ArgIndex<2>, DefaultValue<14>>]>;
+
+// No defaults: must map to the sentinel at offset 0.
+def int_test_no_default :
+ Intrinsic<[llvm_i32_ty], [llvm_i32_ty], []>;
+
+// The values table: offset 0 is the sentinel, then the deduplicated sequences.
+// Each real sequence is [Header = (NumDefaults << 32) | FirstDefault, vals...].
+// 8589934593 = (2 << 32) | 1 -> two_defaults, values 50, 14
+// 4294967297 = (1 << 32) | 1 -> one_default, value 255
+// CHECK: static constexpr uint64_t DefaultArgValuesTable[] = {
+// CHECK-NEXT: 0, // offset 0: sentinel for intrinsics without defaults
+// CHECK-NEXT: {{.*}}8589934593,{{.*}}50,{{.*}}14,{{.*}}0,
+// CHECK-NEXT: {{.*}}4294967297,{{.*}}255,{{.*}}0,
+// CHECK-NEXT: };
+
+// The per-intrinsic offset table (indexed by intrinsic ID, alphabetical):
+// not_intrinsic -> 0
+// test_no_default -> 0 (sentinel)
+// test_one_default -> 5
+// test_two_defaults -> 1
+// CHECK: static constexpr uint32_t DefaultArgValuesTableOffset[] = {
+// CHECK-NEXT: 0, // not_intrinsic
+// CHECK-NEXT: 0,
+// CHECK-NEXT: 5,
+// CHECK-NEXT: 1,
+// CHECK-NEXT: };
+
+// The lookup function.
+// CHECK: Intrinsic::getAllDefaultArgValues(ID IID) {
+// CHECK: if (NumDefaults == 0)
+// CHECK-NEXT: return {0, {}};
+
+// A negative default is rejected (values are the raw unsigned bit pattern).
+#ifdef ERROR_NEGATIVE
+// ERR-NEGATIVE: error: default argument value -1 on parameter 1 must be non-negative
+def int_test_negative :
+ Intrinsic<[llvm_i32_ty], [llvm_i32_ty, llvm_i8_ty],
+ [ImmArg<ArgIndex<1>, DefaultValue<-1>>]>;
+#endif
+
+// A default that does not fit the parameter width is rejected.
+#ifdef ERROR_RANGE
+// ERR-RANGE: error: default argument value 1000 out of range for i8 parameter 1
+def int_test_range :
+ Intrinsic<[llvm_i32_ty], [llvm_i32_ty, llvm_i8_ty],
+ [ImmArg<ArgIndex<1>, DefaultValue<1000>>]>;
+#endif
+
+// A default on a non-integer parameter is rejected.
+#ifdef ERROR_NONINT
+// ERR-NONINT: error: default argument on parameter 1 requires an integer parameter type
+def int_test_nonint :
+ Intrinsic<[llvm_i32_ty], [llvm_i32_ty, llvm_float_ty],
+ [ImmArg<ArgIndex<1>, DefaultValue<1>>]>;
+#endif
+
+// A default on an overloaded intrinsic is rejected (not yet supported).
+#ifdef ERROR_OVERLOADED
+// ERR-OVERLOADED: error: default argument values are not supported for overloaded intrinsics
+def int_test_overloaded :
+ Intrinsic<[llvm_anyint_ty], [llvm_anyint_ty, llvm_i32_ty],
+ [ImmArg<ArgIndex<1>, DefaultValue<5>>]>;
+#endif
+
+// Defaults must form a contiguous trailing block.
+#ifdef ERROR_GAP
+// ERR-GAP: error: missing default argument on parameter 2
+def int_test_gap :
+ Intrinsic<[llvm_i32_ty], [llvm_i32_ty, llvm_i32_ty, llvm_i32_ty],
+ [ImmArg<ArgIndex<1>, DefaultValue<5>>]>;
+#endif
diff --git a/llvm/utils/TableGen/Basic/CodeGenIntrinsics.cpp b/llvm/utils/TableGen/Basic/CodeGenIntrinsics.cpp
index e11f6cb02d5d1..76fc2a4f38811 100644
--- a/llvm/utils/TableGen/Basic/CodeGenIntrinsics.cpp
+++ b/llvm/utils/TableGen/Basic/CodeGenIntrinsics.cpp
@@ -17,6 +17,7 @@
#include "llvm/ADT/Twine.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/MathExtras.h"
#include "llvm/TableGen/Error.h"
#include "llvm/TableGen/Record.h"
#include <algorithm>
@@ -386,6 +387,52 @@ CodeGenIntrinsic::CodeGenIntrinsic(const Record *R,
// Sort the argument attributes for later benefit.
for (auto &Attrs : ArgumentAttributes)
llvm::sort(Attrs);
+
+ // Default values are not yet supported for overloaded intrinsics
+ // (overloaded support will come in a follow-up).
+ if (isOverloaded &&
+ llvm::any_of(ParamDefaultValues, [](const std::optional<uint64_t> &DV) {
+ return DV.has_value();
+ }))
+ PrintFatalError(TheDef->getLoc(),
+ "default argument values are not supported for "
+ "overloaded intrinsics");
+
+ // Validate: defaults must form a contiguous trailing block ending at
+ // the last parameter (mirrors C++ default-argument rules).
+ unsigned NumParams = IS.ParamTys.size();
+ bool SeenDefault = false;
+ for (unsigned i = 0; i < NumParams; ++i) {
+ bool HasDefault =
+ (i < ParamDefaultValues.size() && ParamDefaultValues[i].has_value());
+ if (HasDefault) {
+ SeenDefault = true;
+ } else if (SeenDefault) {
+ PrintFatalError(TheDef->getLoc(),
+ "missing default argument on parameter " + Twine(i));
+ }
+ }
+
+ // Validate each declared default: the parameter must be an integer type and
+ // the value (an unsigned bit pattern) must fit in the declared width.
+ for (unsigned i = 0; i < ParamDefaultValues.size(); ++i) {
+ if (!ParamDefaultValues[i].has_value())
+ continue;
+ const Record *VT = IS.ParamTys[i]->getValueAsDef("VT");
+ if (!VT->getValueAsBit("isInteger")) {
+ PrintFatalError(TheDef->getLoc(),
+ "default argument on parameter " + Twine(i) +
+ " requires an integer parameter type");
+ }
+ unsigned Width = VT->getValueAsInt("Size");
+ uint64_t Value = *ParamDefaultValues[i];
+ if (!isUIntN(Width, Value)) {
+ PrintFatalError(TheDef->getLoc(),
+ "default argument value " + Twine(Value) +
+ " out of range for i" + Twine(Width) + " parameter " +
+ Twine(i));
+ }
+ }
}
void CodeGenIntrinsic::setDefaultProperties(
@@ -490,6 +537,22 @@ void CodeGenIntrinsic::setProperty(const Record *R) {
} else if (R->isSubClassOf("ImmArg")) {
unsigned ArgNo = R->getValueAsInt("ArgNo");
addArgAttribute(ArgNo, ImmArg);
+
+ // If a DefaultValue (not the NoDefault sentinel) was supplied, record it.
+ // NoDefault is recognized by its Value field being unset (?).
+ const Record *DefaultField = R->getValueAsDef("Default");
+ const RecordVal *ValueField = DefaultField->getValue("Value");
+ if (ValueField && !isa<UnsetInit>(ValueField->getValue())) {
+ int64_t Value = DefaultField->getValueAsInt("Value");
+ // Defaults are stored as an unsigned bit pattern; a negative literal
+ // would silently wrap, so reject it with a clear message.
+ if (Value < 0)
+ PrintFatalError(TheDef->getLoc(), "default argument value " +
+ Twine(Value) + " on parameter " +
+ Twine(ArgNo - 1) +
+ " must be non-negative");
+ addDefaultArgValue(ArgNo - 1, Value);
+ }
} else if (R->isSubClassOf("Align")) {
unsigned ArgNo = R->getValueAsInt("ArgNo");
uint64_t Align = R->getValueAsInt("Align");
@@ -583,3 +646,15 @@ void CodeGenIntrinsic::addPrettyPrintFunction(unsigned ArgIdx,
It->FuncName + "'");
PrettyPrintFunctions.emplace_back(ArgIdx, ArgName, FuncName);
}
+
+void CodeGenIntrinsic::addDefaultArgValue(unsigned ArgIdx, uint64_t Value) {
+ if (ArgIdx >= ParamDefaultValues.size())
+ ParamDefaultValues.resize(ArgIdx + 1, std::nullopt);
+
+ if (ParamDefaultValues[ArgIdx].has_value())
+ PrintFatalError(TheDef->getLoc(), "Default value for argument " +
+ Twine(ArgIdx) +
+ " is already defined");
+
+ ParamDefaultValues[ArgIdx] = Value;
+}
diff --git a/llvm/utils/TableGen/Basic/CodeGenIntrinsics.h b/llvm/utils/TableGen/Basic/CodeGenIntrinsics.h
index 8ae6f41aae254..f17149dea6b07 100644
--- a/llvm/utils/TableGen/Basic/CodeGenIntrinsics.h
+++ b/llvm/utils/TableGen/Basic/CodeGenIntrinsics.h
@@ -169,9 +169,14 @@ struct CodeGenIntrinsic {
/// Vector that stores ArgInfo (ArgIndex, ArgName, FunctionName).
SmallVector<PrettyPrintArgInfo> PrettyPrintFunctions;
+ /// Default values for parameters. Index = param index.
+ SmallVector<std::optional<uint64_t>> ParamDefaultValues;
+
void addPrettyPrintFunction(unsigned ArgIdx, StringRef ArgName,
StringRef FuncName);
+ void addDefaultArgValue(unsigned ArgIdx, uint64_t Value);
+
bool hasProperty(enum SDNP Prop) const { return Properties & (1 << Prop); }
/// Goes through all IntrProperties that have IsDefault value set and sets
diff --git a/llvm/utils/TableGen/Basic/IntrinsicEmitter.cpp b/llvm/utils/TableGen/Basic/IntrinsicEmitter.cpp
index 4cf88b22c5e14..330de1063d666 100644
--- a/llvm/utils/TableGen/Basic/IntrinsicEmitter.cpp
+++ b/llvm/utils/TableGen/Basic/IntrinsicEmitter.cpp
@@ -75,6 +75,8 @@ class IntrinsicEmitter {
void EmitAttributes(const CodeGenIntrinsicTable &Ints, raw_ostream &OS);
void EmitPrettyPrintArguments(const CodeGenIntrinsicTable &Ints,
raw_ostream &OS);
+ void EmitDefaultArgValuesTable(const CodeGenIntrinsicTable &Ints,
+ raw_ostream &OS);
void EmitIntrinsicToBuiltinMap(const CodeGenIntrinsicTable &Ints,
bool IsClang, raw_ostream &OS);
};
@@ -134,6 +136,9 @@ void IntrinsicEmitter::run(raw_ostream &OS, bool Enums) {
// Emit Pretty Print attribute.
EmitPrettyPrintArguments(Ints, OS);
+ // Emit the default-argument values table and lookup function.
+ EmitDefaultArgValuesTable(Ints, OS);
+
// Emit code to translate Clang builtins into LLVM intrinsics.
EmitIntrinsicToBuiltinMap(Ints, true, OS);
@@ -925,6 +930,96 @@ void Intrinsic::printImmArg(ID IID, unsigned ArgIdx, raw_ostream &OS, const Cons
})";
}
+void IntrinsicEmitter::EmitDefaultArgValuesTable(
+ const CodeGenIntrinsicTable &Ints, raw_ostream &OS) {
+ // Build the per-intrinsic default-value sequences:
+ // [Header = (NumDefaults << 32) | FirstDefault, val0, val1, ...]
+ // Each value is the (non-negative) default for one parameter, stored as a
+ // uint64_t bit pattern.
+ //
+ // Offset 0 of the values table is reserved as the "no defaults" sentinel
+ // (a single 0 word, decoding to NumDefaults = 0). Intrinsics without
+ // defaults point to offset 0; real sequences are emitted after it.
+ // SequenceToOffsetTable deduplicates the real sequences.
+
+ using Sequence = SmallVector<uint64_t, 8>;
+
+ SequenceToOffsetTable<Sequence> Table;
+ // An empty Sequence means "no defaults" (maps to the reserved offset 0);
+ // otherwise it holds the intrinsic's value sequence.
+ SmallVector<Sequence> PerIntrinsic;
+ PerIntrinsic.reserve(Ints.size());
+
+ for (const CodeGenIntrinsic &Int : Ints) {
+ if (Int.ParamDefaultValues.empty()) {
+ PerIntrinsic.push_back({});
+ continue;
+ }
+
+ // Find the first parameter with a default.
+ unsigned FirstDefault = 0;
+ for (size_t j = 0U, N = Int.ParamDefaultValues.size(); j < N; ++j) {
+ if (Int.ParamDefaultValues[j].has_value()) {
+ FirstDefault = j;
+ break;
+ }
+ }
+ unsigned NumDefaults =
+ static_cast<unsigned>(Int.ParamDefaultValues.size()) - FirstDefault;
+
+ Sequence Seq;
+ Seq.push_back((static_cast<uint64_t>(NumDefaults) << 32) | FirstDefault);
+ for (size_t j = FirstDefault, N = Int.ParamDefaultValues.size(); j < N;
+ ++j) {
+ assert(Int.ParamDefaultValues[j].has_value() &&
+ "Default block must be contiguous");
+ Seq.push_back(*Int.ParamDefaultValues[j]);
+ }
+ Table.add(Seq);
+ PerIntrinsic.push_back(std::move(Seq));
+ }
+
+ Table.layout();
+
+ IfDefEmitter IfDef(OS, "GET_INTRINSIC_DEFAULT_ARG_VALUES");
+
+ // Emit the flat values table. Offset 0 is the reserved "no defaults"
+ // sentinel; the deduplicated real sequences follow it.
+ OS << "static constexpr uint64_t DefaultArgValuesTable[] = {\n";
+ OS << " 0, // offset 0: sentinel for intrinsics without defaults\n";
+ Table.emit(OS, [](raw_ostream &OS, uint64_t Val) { OS << " " << Val; });
+ OS << "};\n\n";
+
+ // Emit the per-intrinsic offset table. Entry #0 is for the invalid
+ // Intrinsic::not_intrinsic (IID 0); it and every intrinsic without defaults
+ // point to the reserved sentinel at offset 0. Real sequences are shifted by
+ // +1 to skip past the sentinel slot.
+ OS << "static constexpr uint32_t DefaultArgValuesTableOffset[] = {\n";
+ OS << " 0, // not_intrinsic\n";
+ for (const Sequence &Seq : PerIntrinsic) {
+ if (!Seq.empty())
+ OS << " " << (Table.get(Seq) + 1) << ",\n";
+ else
+ OS << " 0,\n";
+ }
+ OS << "};\n\n";
+
+ // Emit the lookup function body.
+ OS << R"(
+std::pair<unsigned, ArrayRef<uint64_t>>
+Intrinsic::getAllDefaultArgValues(ID IID) {
+ uint32_t Offset = DefaultArgValuesTableOffset[IID];
+ uint64_t Header = DefaultArgValuesTable[Offset];
+ uint32_t FirstDefault = Header & 0xFFFFFFFFu;
+ uint32_t NumDefaults = (Header >> 32) & 0xFFFFFFFFu;
+ if (NumDefaults == 0)
+ return {0, {}};
+ return {FirstDefault,
+ ArrayRef(&DefaultArgValuesTable[Offset + 1], NumDefaults)};
+}
+)";
+}
+
void IntrinsicEmitter::EmitIntrinsicToBuiltinMap(
const CodeGenIntrinsicTable &Ints, bool IsClang, raw_ostream &OS) {
StringRef CompilerName = IsClang ? "Clang" : "MS";
More information about the llvm-commits
mailing list