[llvm] c739ce6 - [TableGen] Diagnose sub-register indices that overflow their register (#206346)
via llvm-commits
llvm-commits at lists.llvm.org
Sat Jul 18 08:41:27 PDT 2026
Author: Taimuraz Kaitmazov
Date: 2026-07-18T08:41:21-07:00
New Revision: c739ce632b1489dcdbaad72df64a9cec9a8ed359
URL: https://github.com/llvm/llvm-project/commit/c739ce632b1489dcdbaad72df64a9cec9a8ed359
DIFF: https://github.com/llvm/llvm-project/commit/c739ce632b1489dcdbaad72df64a9cec9a8ed359.diff
LOG: [TableGen] Diagnose sub-register indices that overflow their register (#206346)
Added:
llvm/test/TableGen/subreg-index-overflow-allowed.td
llvm/test/TableGen/subreg-index-overflow.td
Modified:
llvm/utils/TableGen/Common/CodeGenRegisters.cpp
llvm/utils/TableGen/Common/CodeGenRegisters.h
Removed:
################################################################################
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..a4887a2c4a866
--- /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
diff --git a/llvm/utils/TableGen/Common/CodeGenRegisters.cpp b/llvm/utils/TableGen/Common/CodeGenRegisters.cpp
index 113edb109551c..b495bcb62969e 100644
--- a/llvm/utils/TableGen/Common/CodeGenRegisters.cpp
+++ b/llvm/utils/TableGen/Common/CodeGenRegisters.cpp
@@ -27,6 +27,7 @@
#include "llvm/ADT/StringSet.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/Debug.h"
+#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/TableGen/Error.h"
#include "llvm/TableGen/Record.h"
@@ -388,6 +389,79 @@ 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 {
+ // Only registers that are covered by their sub-registers promise to be fully
+ // tiled by them; for those an explicit SubRegIndex extending past the
+ // register is a genuine bug. A register that is not covered by its
+ // sub-registers makes no such promise, so don't second-guess its layout.
+ if (!CoveredBySubRegs)
+ return;
+
+ // 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.
+ // getValueTypes() is indexed by HwMode; DefaultMode is the first entry.
+ ArrayRef<ValueTypeByHwMode> VTs = RC.getValueTypes();
+ if (VTs.empty())
+ continue;
+ const ValueTypeByHwMode &VT = VTs[DefaultMode];
+ if (!VT.isSimple() || VT.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>> 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;
+
+ // ExplicitSubRegIndices is in .td declaration order, not offset order, so
+ // sort by offset before scanning. Only a contiguous tiling is a dense bit
+ // container; anything left with a gap or overlap is a strided/spaced tuple
+ // (also covered-by-subregs), which is allowed to exceed the nominal 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,
+ formatv("register '{}' has size {} but its explicit "
+ "sub-registers cover {} bits",
+ getName(), ParentSize, Pos));
+}
+
// In a register that is covered by its sub-registers, try to find redundant
// sub-registers. For example:
//
@@ -1280,6 +1354,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 e733da1971eec..382f15e1e07d6 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