[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