[llvm] [TableGen] Diagnose sub-register indices that overflow their register (PR #206346)

via llvm-commits llvm-commits at lists.llvm.org
Sun Jun 28 09:47:24 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-tablegen

Author: Taimuraz Kaitmazov (atassis)

<details>
<summary>Changes</summary>

# [TableGen] Diagnose sub-register indices that overflow their register

## What

A register defined with `CoveredBySubRegs` derives its sub-register lane masks
from the declared `Size`/`Offset` of its explicit `SubRegIndex`es. If a
`SubRegIndex` `Size` is wrong such that `offset + size` runs past the
register's own size, that sub-register gets an oversized lane mask. Nothing
diagnoses this today, so it surfaces as silently wrong sub-register liveness and
spilling: a wrong-code bug with no ICE and no machine-verifier error.

This patch diagnoses it when the register bank is built.

## Why (a real bug, not theoretical)

A downstream backend (AMD AIEngine) had a 64-bit block-exponent sub-register
given a `SubRegIndex` with `Size` 576 instead of 64. With `CoveredBySubRegs`,
the exponent's lane mask spanned 576 bits instead of 64; once register pressure
forced the value to spill, the reload was corrupted, producing a silent
`-O1`/`-O2` miscompile that took a hardware reproduction to find. TableGen
accepted the malformed definition without a word. The fix was a one-character
size change; this check is what would have caught it at build time.

## Scope / how false positives are avoided

The check only applies to registers whose explicit sub-registers tile a
*contiguous* bit range and whose size is a real bit width. It skips the cases
where a register's nominal size is deliberately not its bit width:

- **strided/spaced register tuples** (e.g. ARM `Tuples3DSpc = [dsub_0, dsub_2,
  dsub_4]`): sub-registers at non-adjacent offsets, intentionally past the
  nominal size;
- **tuple sub-indices with no contiguous bit range** (`offset`/`size == -1`,
  e.g. X86 `KPAIRS`);
- **registers whose only size comes from an `untyped` register class**, where
  the class size is a placeholder rather than a bit width.

I ran `-gen-register-info` with the check over every in-tree target and the full
`llvm/test/TableGen` suite: clean on all of them (the new test below is the only
thing that trips it).

## Test

`llvm/test/TableGen/subreg-index-overflow.td` defines a 64-bit pair whose high
half is mis-sized so the sub-registers cover 96 bits, and checks for the error.

## Note for reviewers

This is a new hard error. It is clean across all in-tree targets, but an
out-of-tree target with this latent bug would now fail to build -- which is the
intent (the alternative is a silent miscompile). If a softer rollout is
preferred I am happy to land it as a warning first. (For context, the same check
is in the downstream fork as
https://github.com/Xilinx/llvm-aie/pull/1060, and the bug it caught was
https://github.com/Xilinx/llvm-aie/pull/1059.)

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


3 Files Affected:

- (added) llvm/test/TableGen/subreg-index-overflow.td (+29) 
- (modified) llvm/utils/TableGen/Common/CodeGenRegisters.cpp (+67) 
- (modified) llvm/utils/TableGen/Common/CodeGenRegisters.h (+5) 


``````````diff
diff --git a/llvm/test/TableGen/subreg-index-overflow.td b/llvm/test/TableGen/subreg-index-overflow.td
new file mode 100644
index 0000000000000..836fc6010266a
--- /dev/null
+++ b/llvm/test/TableGen/subreg-index-overflow.td
@@ -0,0 +1,29 @@
+// RUN: not llvm-tblgen -gen-register-info -I %p/../../include %s -o /dev/null 2>&1 | FileCheck %s
+include "llvm/Target/Target.td"
+
+class MyReg<string n> : Register<n> { let Namespace = "Test"; }
+class MyClass<int size, list<ValueType> types, dag registers>
+  : RegisterClass<"Test", types, size, registers> { let Size = size; }
+
+def sub_lo : SubRegIndex<32, 0>;
+// Erroneous: the high half is declared 64 bits at offset 32, so the pair's
+// sub-registers cover [0, 96) bits, past the end of the 64-bit register.
+def sub_hi : SubRegIndex<64, 32>;
+
+def L0 : MyReg<"l0">;
+def H0 : MyReg<"h0">;
+def L1 : MyReg<"l1">;
+def H1 : MyReg<"h1">;
+
+def LoRegs : MyClass<32, [i32], (add L0, L1)>;
+def HiRegs : MyClass<32, [i32], (add H0, H1)>;
+
+let SubRegIndices = [sub_lo, sub_hi], CoveredBySubRegs = 1 in {
+  def P0 : MyReg<"p0"> { let SubRegs = [L0, H0]; }
+  def P1 : MyReg<"p1"> { let SubRegs = [L1, H1]; }
+}
+def PairRegs : MyClass<64, [i64], (add P0, P1)>;
+
+def TestTarget : Target;
+
+// CHECK: error: register 'P0' has size 64 but its explicit sub-registers cover 96 bits; a SubRegIndex 'Size' is larger than the sub-register it describes
diff --git a/llvm/utils/TableGen/Common/CodeGenRegisters.cpp b/llvm/utils/TableGen/Common/CodeGenRegisters.cpp
index eb6f07d761029..d61316ad1cecc 100644
--- a/llvm/utils/TableGen/Common/CodeGenRegisters.cpp
+++ b/llvm/utils/TableGen/Common/CodeGenRegisters.cpp
@@ -388,6 +388,68 @@ CodeGenRegister::computeSubRegs(CodeGenRegBank &RegBank) {
   return SubRegs;
 }
 
+// Verify that a register's explicit sub-registers fit within it. A SubRegIndex
+// whose offset+size runs past the register's own size gives that sub-register
+// an oversized lane mask, which silently corrupts sub-register liveness and
+// spilling and is not otherwise diagnosed. (This was found via a real
+// wrong-code bug in a downstream target, where a 64-bit exponent sub-register
+// index was mistakenly declared with size 576.) The check is limited to
+// registers whose explicit sub-registers tile a contiguous bit range:
+// strided/spaced register tuples (e.g. ARM's Tuples3DSpc,
+// [dsub_0, dsub_2, dsub_4]) deliberately place sub-registers at non-adjacent
+// offsets that extend past the nominal size, and are not bugs.
+void CodeGenRegister::checkSubRegIndexSizes(CodeGenRegBank &RegBank) const {
+  // This register's own bit size is the largest register class containing it.
+  unsigned ParentSize = 0;
+  for (const auto &RC : RegBank.getRegClasses()) {
+    if (!RC.contains(this) || !RC.RSI.hasDefault())
+      continue;
+    // Only trust a register-class size that is backed by a real value type.
+    // An 'untyped' class carries a placeholder size, not the register's bit
+    // width, so its size cannot be compared against sub-register extents.
+    ArrayRef<ValueTypeByHwMode> VTs = RC.getValueTypes();
+    if (VTs.empty() || !VTs[0].isSimple() || VTs[0].getSimple() == MVT::Untyped)
+      continue;
+    ParentSize = std::max(ParentSize, RC.RSI.get(DefaultMode).RegSize);
+  }
+  if (!ParentSize)
+    return;
+
+  // Collect the explicit sub-register (offset, size) ranges. Bail on any
+  // register with incomplete or non-contiguous range info.
+  SmallVector<std::pair<unsigned, unsigned>, 8> Ranges;
+  for (const CodeGenSubRegIndex *Idx : ExplicitSubRegIndices) {
+    if (!Idx->Range.hasDefault())
+      return;
+    const SubRegRange &R = Idx->Range.get(DefaultMode);
+    if (R.Size == static_cast<uint32_t>(-1) ||
+        R.Offset == static_cast<uint32_t>(-1))
+      return; // tuple sub-index with no contiguous bit range (e.g. X86 KPAIRS,
+              // ARM strided NEON tuples)
+    Ranges.emplace_back(R.Offset, R.Size);
+  }
+  if (Ranges.empty())
+    return;
+
+  // Only contiguously-tiled registers are dense bit containers; anything with a
+  // gap or overlap is a strided/spaced tuple, which is allowed to exceed size.
+  llvm::sort(Ranges);
+  unsigned Pos = 0;
+  for (auto [Off, Sz] : Ranges) {
+    if (Off != Pos)
+      return; // gap or overlap -> strided/sparse, skip
+    Pos += Sz;
+  }
+
+  if (Pos > ParentSize)
+    PrintFatalError(TheDef->getLoc(),
+                    Twine("register '") + getName() + "' has size " +
+                        Twine(ParentSize) +
+                        " but its explicit sub-registers cover " + Twine(Pos) +
+                        " bits; a SubRegIndex 'Size' is larger than the "
+                        "sub-register it describes");
+}
+
 // In a register that is covered by its sub-registers, try to find redundant
 // sub-registers. For example:
 //
@@ -1274,6 +1336,11 @@ CodeGenRegBank::CodeGenRegBank(const RecordKeeper &Records,
   // Read in the register category definitions.
   for (const Record *R : Records.getAllDerivedDefinitions("RegisterCategory"))
     RegCategories.emplace_back(*this, R);
+
+  // PROTOTYPE (issue #847): survey explicit SubRegIndex size mismatches now
+  // that register classes (and their sizes/members) are fully built.
+  for (const auto &Reg : Registers)
+    Reg.checkSubRegIndexSizes(*this);
 }
 
 // Create a synthetic CodeGenSubRegIndex without a corresponding Record.
diff --git a/llvm/utils/TableGen/Common/CodeGenRegisters.h b/llvm/utils/TableGen/Common/CodeGenRegisters.h
index 077294783f8ae..839ae1adef6fb 100644
--- a/llvm/utils/TableGen/Common/CodeGenRegisters.h
+++ b/llvm/utils/TableGen/Common/CodeGenRegisters.h
@@ -206,6 +206,11 @@ class CodeGenRegister {
   // graph has been built.
   void computeSuperRegs(CodeGenRegBank &);
 
+  // Verify that each explicit SubRegIndex's declared size matches the size of
+  // the sub-register it covers. A mismatch silently corrupts subregister lane
+  // masks and spilling (see llvm-aie issue #847). PROTOTYPE: warns only.
+  void checkSubRegIndexSizes(CodeGenRegBank &) const;
+
   const SubRegMap &getSubRegs() const {
     assert(SubRegsComplete && "Must precompute sub-registers");
     return SubRegs;

``````````

</details>


https://github.com/llvm/llvm-project/pull/206346


More information about the llvm-commits mailing list