[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