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

Taimuraz Kaitmazov via llvm-commits llvm-commits at lists.llvm.org
Sun Jun 28 12:45:52 PDT 2026


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

>From 6db22db56d72edb59612f32277f99745cf3e276d Mon Sep 17 00:00:00 2001
From: Taimuraz Kaitmazov <atassikay38 at gmail.com>
Date: Sun, 28 Jun 2026 19:44:39 +0300
Subject: [PATCH] [TableGen] Diagnose sub-register indices that overflow their
 register

A register defined with CoveredBySubRegs derives its sub-register lane masks
from the declared Size/Offset of its explicit SubRegIndices. 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, and the
result is silently wrong sub-register liveness and spilling: a wrong-code bug
with no ICE and no machine-verifier error.

This was found via exactly such a bug in a downstream target (AMD AIEngine),
where a 64-bit block-exponent sub-register index was declared with size 576 and
corrupted spilled values at -O1/-O2 under register pressure.

Diagnose it when the register bank is built: a register's explicit
sub-registers must tile a contiguous bit range that fits within the register's
size. The check skips the cases where a register's nominal size is not its bit
width: strided/spaced register tuples (e.g. ARM Tuples3DSpc) whose
sub-registers sit at non-adjacent offsets; tuple sub-indices with no contiguous
bit range (offset/size == -1, e.g. X86 KPAIRS); and registers whose only size
comes from an 'untyped' register class.

Clean across all in-tree targets and the TableGen tests; a new test exercises a
register given an oversized sub-register index.
---
 .../TableGen/subreg-index-overflow-allowed.td | 37 ++++++++++
 llvm/test/TableGen/subreg-index-overflow.td   | 29 ++++++++
 .../TableGen/Common/CodeGenRegisters.cpp      | 67 +++++++++++++++++++
 llvm/utils/TableGen/Common/CodeGenRegisters.h |  5 ++
 4 files changed, 138 insertions(+)
 create mode 100644 llvm/test/TableGen/subreg-index-overflow-allowed.td
 create mode 100644 llvm/test/TableGen/subreg-index-overflow.td

diff --git a/llvm/test/TableGen/subreg-index-overflow-allowed.td b/llvm/test/TableGen/subreg-index-overflow-allowed.td
new file mode 100644
index 0000000000000..86afdd800692d
--- /dev/null
+++ b/llvm/test/TableGen/subreg-index-overflow-allowed.td
@@ -0,0 +1,37 @@
+// RUN: llvm-tblgen -gen-register-info -I %p/../../include %s -o - | FileCheck %s
+// Companion to subreg-index-overflow.td: patterns that look like an overflow
+// but are legitimate and must NOT be diagnosed by the size check.
+include "llvm/Target/Target.td"
+
+class MyReg<string n, list<Register> subs = []> : Register<n> {
+  let Namespace = "Test";
+  let SubRegs = subs;
+  let CoveredBySubRegs = 1;
+}
+class MyClass<int sz, list<ValueType> types, dag regs>
+  : RegisterClass<"Test", types, sz, regs> { let Size = sz; }
+
+def lo : SubRegIndex<32, 0>;
+def hi_gap : SubRegIndex<32, 64>; // leaves a [32,64) gap -> non-contiguous
+def lo2 : SubRegIndex<32, 0>;
+def hi2 : SubRegIndex<32, 32>;
+
+foreach i = 0-7 in { def R#i : MyReg<"r"#i>; }
+def GPR : MyClass<32, [i32], (sequence "R%u", 0, 7)>;
+
+// (1) Strided/spaced tuple: sub-registers at [0,32) and [64,96) extend past the
+// 64-bit class size, but are non-contiguous, so this is allowed.
+def StridedPair : RegisterTuples<[lo, hi_gap], [(add R0, R2), (add R1, R3)]>;
+def StridedRC : MyClass<64, [untyped], (add StridedPair)>;
+
+// (2) Untyped class: its size is a placeholder, not a bit width, so a 64-bit
+// pair living in it must not be diagnosed as overflowing.
+def DensePair : RegisterTuples<[lo2, hi2], [(add R4, R6), (add R5, R7)]>;
+def UntypedRC : MyClass<8, [untyped], (add DensePair)>;
+
+def TestTarget : Target;
+
+// Both synthesized tuples must be emitted, i.e. neither was rejected by the
+// sub-register overflow check.
+// CHECK-DAG: R0_R1 =
+// CHECK-DAG: R4_R5 =
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..fcbb2c47ad986 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);
+
+  // Now that register classes (and their sizes) are built, check that no
+  // explicit SubRegIndex makes a sub-register overflow its register.
+  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..2922d18fc31ac 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 &);
 
+  // Diagnose an explicit SubRegIndex whose declared size makes a sub-register
+  // extend past the register that contains it (an oversized lane mask that
+  // silently corrupts sub-register liveness and spilling). See the definition.
+  void checkSubRegIndexSizes(CodeGenRegBank &) const;
+
   const SubRegMap &getSubRegs() const {
     assert(SubRegsComplete && "Must precompute sub-registers");
     return SubRegs;



More information about the llvm-commits mailing list