Skip to content

[TableGen][CallingConv] Add CCAssignToRegTuple for synthetic registers. #137826

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 30, 2025

Conversation

nvjle
Copy link
Contributor

@nvjle nvjle commented Apr 29, 2025

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.

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.
@nvjle nvjle requested review from topperc and jurahul April 29, 2025 15:29
@llvmbot
Copy link
Member

llvmbot commented Apr 29, 2025

@llvm/pr-subscribers-tablegen

Author: Jason Eckhardt (nvjle)

Changes

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.


Full diff: https://github.com/llvm/llvm-project/pull/137826.diff

3 Files Affected:

  • (modified) llvm/include/llvm/Target/TargetCallingConv.td (+7)
  • (added) llvm/test/TableGen/cc-assign-to-reg-tuple.td (+78)
  • (modified) llvm/utils/TableGen/CallingConvEmitter.cpp (+28-4)
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

Copy link
Contributor

@jurahul jurahul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@nvjle nvjle merged commit 7d05f67 into llvm:main Apr 30, 2025
11 checks passed
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
…s. (llvm#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.
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
…s. (llvm#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.
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
…s. (llvm#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.
GeorgeARM pushed a commit to GeorgeARM/llvm-project that referenced this pull request May 7, 2025
…s. (llvm#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.
Ankur-0429 pushed a commit to Ankur-0429/llvm-project that referenced this pull request May 9, 2025
…s. (llvm#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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants