[llvm] [TableGen][CallingConv] Add CCAssignToRegTuple for synthetic registers. (PR #137826)

Jason Eckhardt via llvm-commits llvm-commits at lists.llvm.org
Tue Apr 29 08:29:18 PDT 2025


https://github.com/nvjle created https://github.com/llvm/llvm-project/pull/137826

Currently CCAssignToReg takes a list<Register>. There are tuple-heavy back-ends where we would like to reference any register-- whether those are singletons or those defined by RegisterTuples. However, the latter are synthesized during tuple expansion and are not visible outside of the register info emitter.

The problem is that the parser will see tuple registers as undefined variables before the calling convention emitter is ever reached. To defer evaluation of the symbol, we introduce CCAssignToRegTuple which takes list<string> instead. This allows us to defer the actual register name lookup until the emitter runs-- where we also validate that the register actually exists.

This is currently used in a downstream back-end which will be upstreamed very soon. In the meantime, a unit test is provided to exercise the feature.

>From 0e8618a06c231a3a436827accc8f2d26ca04ac92 Mon Sep 17 00:00:00 2001
From: Jason Eckhardt <jeckhardt at nvidia.com>
Date: Tue, 29 Apr 2025 09:48:15 -0500
Subject: [PATCH] [TableGen][CallingConv] Add CCAssignToRegTuple for synthetic
 registers.

Currently CCAssignToReg takes a list<Register>. There are tuple-heavy
back-ends where we would like to reference any register-- whether those
are singletons or those defined by RegisterTuples. However, the latter
are synthesized during tuple expansion and are not visible outside of the
register info emitter.

The problem is that the parser will see tuple registers as undefined
variables before the calling convention emitter is ever reached. To defer
evaluation of the symbol, we introduce CCAssignToRegTuple which takes
list<string> instead. This allows us to defer the actual register name
lookup until the emitter runs-- where we also validate that the register
actually exists.

This is currently used in a downstream back-end which will be upstreamed
very soon. In the meantime, a unit test is provided to exercise the feature.
---
 llvm/include/llvm/Target/TargetCallingConv.td |  7 ++
 llvm/test/TableGen/cc-assign-to-reg-tuple.td  | 78 +++++++++++++++++++
 llvm/utils/TableGen/CallingConvEmitter.cpp    | 32 +++++++-
 3 files changed, 113 insertions(+), 4 deletions(-)
 create mode 100644 llvm/test/TableGen/cc-assign-to-reg-tuple.td

diff --git a/llvm/include/llvm/Target/TargetCallingConv.td b/llvm/include/llvm/Target/TargetCallingConv.td
index 18b7ff4aec95f..d0533cad927a7 100644
--- a/llvm/include/llvm/Target/TargetCallingConv.td
+++ b/llvm/include/llvm/Target/TargetCallingConv.td
@@ -113,6 +113,13 @@ class CCAssignToReg<list<Register> regList> : CCAction {
   list<Register> RegList = regList;
 }
 
+/// CCAssignToRegTuple - Same as CCAssignToReg, but with a list of registers as
+/// strings. This is needed because records synthesized during tuple expansion
+/// are not visible outside of the register info emitter.
+class CCAssignToRegTuple<list<string> regList> : CCAction {
+  list<string> RegList = regList;
+}
+
 /// CCAssignToRegWithShadow - Same as CCAssignToReg, but with list of registers
 /// which became shadowed, when some register is used.
 class CCAssignToRegWithShadow<list<Register> regList,
diff --git a/llvm/test/TableGen/cc-assign-to-reg-tuple.td b/llvm/test/TableGen/cc-assign-to-reg-tuple.td
new file mode 100644
index 0000000000000..624aa1d5f5f0b
--- /dev/null
+++ b/llvm/test/TableGen/cc-assign-to-reg-tuple.td
@@ -0,0 +1,78 @@
+// RUN: llvm-tblgen --gen-callingconv -I %p/../../include -I %p/Common %s 2>&1 | FileCheck %s
+// RUN: not llvm-tblgen -DERROR1 --gen-callingconv -I %p/../../include -I %p/Common %s 2>&1 | FileCheck --check-prefix=CHECK-ERROR1 %s
+// RUN: not llvm-tblgen -DERROR2 --gen-callingconv -I %p/../../include -I %p/Common %s 2>&1 | FileCheck --check-prefix=CHECK-ERROR2 %s
+
+include "reg-with-subregs-common.td"
+
+def CC_ABI1 : CallingConv<[
+  // Use singleton definitions directly.
+  CCIfType<[i32, f32],
+      CCAssignToReg<[R8, R9, R10, R11, R12, R13, R14, R15]>>,
+
+  // Use tuple definitions indirectly as strings.
+  CCIfType<[i64, f64],
+      CCAssignToRegTuple<["R8_R9", "R10_R11", "R12_R13", "R14_R15"]>>,
+
+  CCIfType<[i128],
+      CCAssignToRegTuple<["R8_R9_R10_R11", "R12_R13_R14_R15"]>>,
+
+  CCIfType<[v8i32],
+      CCAssignToRegTuple<["R8_R9_R10_R11_R12_R13_R14_R15"]>>,
+]>;
+
+// CHECK: if (LocVT == MVT::i32 ||
+// CHECK:      LocVT == MVT::f32) {
+// CHECK:    static const MCPhysReg RegList1[] = {
+// CHECK:      R8, R9, R10, R11, R12, R13, R14, R15
+// CHECK:    };
+// CHECK:    if (MCRegister Reg = State.AllocateReg(RegList1)) {
+// CHECK:      State.addLoc(CCValAssign::getReg(ValNo, ValVT, Reg, LocVT, LocInfo));
+// CHECK:      return false;
+// CHECK:    }
+// CHECK:  }
+
+// CHECK:  if (LocVT == MVT::i64 ||
+// CHECK:      LocVT == MVT::f64) {
+// CHECK:    static const MCPhysReg RegList2[] = {
+// CHECK:      R8_R9, R10_R11, R12_R13, R14_R15
+// CHECK:    };
+// CHECK:    if (MCRegister Reg = State.AllocateReg(RegList2)) {
+// CHECK:      State.addLoc(CCValAssign::getReg(ValNo, ValVT, Reg, LocVT, LocInfo));
+// CHECK:      return false;
+// CHECK:    }
+// CHECK:  }
+
+// CHECK:  if (LocVT == MVT::i128) {
+// CHECK:    static const MCPhysReg RegList3[] = {
+// CHECK:      R8_R9_R10_R11, R12_R13_R14_R15
+// CHECK:    };
+// CHECK:    if (MCRegister Reg = State.AllocateReg(RegList3)) {
+// CHECK:      State.addLoc(CCValAssign::getReg(ValNo, ValVT, Reg, LocVT, LocInfo));
+// CHECK:      return false;
+// CHECK:    }
+// CHECK:  }
+
+// CHECK:  if (LocVT == MVT::v8i32) {
+// CHECK:    if (MCRegister Reg = State.AllocateReg(R8_R9_R10_R11_R12_R13_R14_R15)) {
+// CHECK:      State.addLoc(CCValAssign::getReg(ValNo, ValVT, Reg, LocVT, LocInfo));
+// CHECK:      return false;
+// CHECK:    }
+// CHECK:  }
+
+#ifdef ERROR1
+def CC_ABI2 : CallingConv<[
+  // Test that referencing an undefined tuple is diagnosed as an error.
+  // CHECK-ERROR1: error: register not defined: "R89_R33"
+  CCIfType<[i64, f64],
+      CCAssignToRegTuple<["R89_R33", "R12_R13", "R14_R15"]>>,
+]>;
+#endif
+
+#ifdef ERROR2
+def CC_ABI3 : CallingConv<[
+  // Currently an error: Use tuple definitions directly.
+  // CHECK-ERROR2: error: Variable not defined: 'R8_R9_R10_R11'
+  CCIfType<[i128],
+      CCAssignToRegTuple<[R8_R9_R10_R11, R12_R13_R14_R15]>>,
+]>;
+#endif
diff --git a/llvm/utils/TableGen/CallingConvEmitter.cpp b/llvm/utils/TableGen/CallingConvEmitter.cpp
index 57b1622752613..fd7280a9f2b42 100644
--- a/llvm/utils/TableGen/CallingConvEmitter.cpp
+++ b/llvm/utils/TableGen/CallingConvEmitter.cpp
@@ -11,6 +11,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "Common/CodeGenRegisters.h"
 #include "Common/CodeGenTarget.h"
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/InterleavedRange.h"
@@ -26,6 +27,7 @@ using namespace llvm;
 namespace {
 class CallingConvEmitter {
   const RecordKeeper &Records;
+  const CodeGenTarget Target;
   unsigned Counter = 0u;
   std::string CurrentAction;
   bool SwiftAction = false;
@@ -35,7 +37,10 @@ class CallingConvEmitter {
   std::map<std::string, std::set<std::string>> DelegateToMap;
 
 public:
-  explicit CallingConvEmitter(const RecordKeeper &R) : Records(R) {}
+  explicit CallingConvEmitter(const RecordKeeper &R) : Records(R), Target(R) {
+    for (const CodeGenRegister &Reg : Target.getRegBank().getRegisters())
+      RegistersByDefName.try_emplace(Reg.getName(), &Reg);
+  }
 
   void run(raw_ostream &O);
 
@@ -43,6 +48,9 @@ class CallingConvEmitter {
   void emitCallingConv(const Record *CC, raw_ostream &O);
   void emitAction(const Record *Action, indent Indent, raw_ostream &O);
   void emitArgRegisterLists(raw_ostream &O);
+
+  StringMap<const CodeGenRegister *> RegistersByDefName;
+  std::string getQualifiedNameFromInit(const Init *I);
 };
 } // End anonymous namespace
 
@@ -125,6 +133,21 @@ void CallingConvEmitter::emitCallingConv(const Record *CC, raw_ostream &O) {
   O << "}\n";
 }
 
+// Return the name of the specified Init (DefInit or StringInit), with a
+// namespace qualifier if the corresponding record contains one.
+std::string CallingConvEmitter::getQualifiedNameFromInit(const Init *I) {
+  if (const auto *DI = dyn_cast<DefInit>(I))
+    return getQualifiedName(DI->getDef());
+
+  const auto *SI = dyn_cast<StringInit>(I);
+  assert(SI && "unexpected Init kind");
+  if (const CodeGenRegister *CGR = RegistersByDefName.lookup(SI->getValue()))
+    return getQualifiedName(CGR->TheDef);
+
+  PrintFatalError("register not defined: " + SI->getAsString());
+  return "";
+}
+
 void CallingConvEmitter::emitAction(const Record *Action, indent Indent,
                                     raw_ostream &O) {
 
@@ -133,7 +156,7 @@ void CallingConvEmitter::emitAction(const Record *Action, indent Indent,
     O << Indent << "  ";
     ListSeparator LS;
     for (const Init *V : RL->getValues())
-      O << LS << getQualifiedName(cast<DefInit>(V)->getDef());
+      O << LS << getQualifiedNameFromInit(V);
     O << "\n" << Indent << "};\n";
   };
 
@@ -142,7 +165,7 @@ void CallingConvEmitter::emitAction(const Record *Action, indent Indent,
     SmallVector<std::string> Parms;
     if (RegLists[0]->size() == 1) {
       for (const ListInit *LI : RegLists)
-        Parms.push_back(getQualifiedName(LI->getElementAsRecord(0)));
+        Parms.push_back(getQualifiedNameFromInit(LI->getElement(0)));
     } else {
       for (const std::string &S : RLNames)
         Parms.push_back(S + utostr(++Counter));
@@ -207,10 +230,11 @@ void CallingConvEmitter::emitAction(const Record *Action, indent Indent,
         << Indent + 2 << "return false;\n";
       DelegateToMap[CurrentAction].insert(CC->getName().str());
     } else if (Action->isSubClassOf("CCAssignToReg") ||
+               Action->isSubClassOf("CCAssignToRegTuple") ||
                Action->isSubClassOf("CCAssignToRegAndStack")) {
       const ListInit *RegList = Action->getValueAsListInit("RegList");
       for (unsigned I = 0, E = RegList->size(); I != E; ++I) {
-        std::string Name = getQualifiedName(RegList->getElementAsRecord(I));
+        std::string Name = getQualifiedNameFromInit(RegList->getElement(I));
         if (SwiftAction)
           AssignedSwiftRegsMap[CurrentAction].insert(std::move(Name));
         else



More information about the llvm-commits mailing list