[llvm] [CodeGen][AArch64] Avoid duplicate hints in register allocation (PR #219007)

Nick Desaulniers via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 28 13:05:53 PDT 2026


https://github.com/nickdesaulniers updated https://github.com/llvm/llvm-project/pull/219007

>From f55333b7225fa5eb330a44ccafc969a8c7cb11b4 Mon Sep 17 00:00:00 2001
From: Nick Desaulniers <ndesaulniers at google.com>
Date: Wed, 29 Jul 2026 14:33:45 -0700
Subject: [PATCH] [CodeGen][AArch64] Avoid duplicate hints in register
 allocation

Duplicate register allocation hints cause AllocationOrder to yield
duplicate physical register candidates. For fixed-capacity eviction
advisors like MLEvictAdvisor (MaxInterferences = 32), this leads to
out-of-bounds array indexing and assertion failures.

In commit 0756e5985f0c, TargetRegisterInfo::getRegAllocationHints was
hoisted to the top of AArch64RegisterInfo::getRegAllocationHints, but
fallthrough paths invoked it a second time on an already-populated Hints
vector, duplicating copy hints for standard GPRs.

This patch:
1. Returns ConsiderOnlyHints on fallthrough in
   AArch64RegisterInfo::getRegAllocationHints instead of calling
   TargetRegisterInfo::getRegAllocationHints a second time.
2. In TargetRegisterInfo::getRegAllocationHints, deduplicates directly
   against Hints instead of using a transient local set.
3. In AllocationOrder::create, deduplicates Hints in O(N) while
   preserving priority order, and asserts that Hints contains no
   duplicates.
4. Adds regression test coverage for AArch64 MLRegAlloc eviction
   advisor.

Assisted-by: Gemini
Fixes: 0756e5985f0c ("[AArch64] Hint regalloc to choose distinct predicate for MATCH/CMP (#190139)")
Fixes: https://github.com/llvm/llvm-project/issues/193011
---
 llvm/lib/CodeGen/AllocationOrder.cpp          | 11 ++++
 llvm/lib/CodeGen/TargetRegisterInfo.cpp       | 14 ++---
 .../Target/AArch64/AArch64RegisterInfo.cpp    |  9 ++--
 .../aarch64-evict-advisor-duplicate-hints.ll  | 54 +++++++++++++++++++
 4 files changed, 76 insertions(+), 12 deletions(-)
 create mode 100644 llvm/test/CodeGen/MLRegAlloc/aarch64-evict-advisor-duplicate-hints.ll

diff --git a/llvm/lib/CodeGen/AllocationOrder.cpp b/llvm/lib/CodeGen/AllocationOrder.cpp
index 183dc8af1b91b..6bbbb7d49bbe0 100644
--- a/llvm/lib/CodeGen/AllocationOrder.cpp
+++ b/llvm/lib/CodeGen/AllocationOrder.cpp
@@ -36,6 +36,14 @@ AllocationOrder AllocationOrder::create(Register VirtReg, const VirtRegMap &VRM,
   bool HardHints =
       TRI->getRegAllocationHints(VirtReg, Order, Hints, MF, &VRM, Matrix);
 
+  // TODO: Consider changing the interface of
+  // TargetRegisterInfo::getRegAllocationHints to take a SetVector to enforce
+  // uniqueness at the API boundary. Ensure hints are unique while preserving
+  // priority order.
+  SmallDenseSet<MCPhysReg, 16> Seen;
+  llvm::erase_if(Hints,
+                 [&](MCPhysReg Reg) { return !Seen.insert(Reg).second; });
+
   LLVM_DEBUG({
     if (!Hints.empty()) {
       dbgs() << "hints:";
@@ -47,5 +55,8 @@ AllocationOrder AllocationOrder::create(Register VirtReg, const VirtRegMap &VRM,
   assert(all_of(Hints,
                 [&](MCPhysReg Hint) { return is_contained(Order, Hint); }) &&
          "Target hint is outside allocation order.");
+  assert(
+      all_of(Hints, [&](MCPhysReg Hint) { return count(Hints, Hint) == 1; }) &&
+      "Target hints contain duplicates.");
   return AllocationOrder(std::move(Hints), Order, HardHints);
 }
diff --git a/llvm/lib/CodeGen/TargetRegisterInfo.cpp b/llvm/lib/CodeGen/TargetRegisterInfo.cpp
index 3b29b0863dd6c..c55c67fd4e7ad 100644
--- a/llvm/lib/CodeGen/TargetRegisterInfo.cpp
+++ b/llvm/lib/CodeGen/TargetRegisterInfo.cpp
@@ -14,7 +14,6 @@
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/BitVector.h"
 #include "llvm/ADT/STLExtras.h"
-#include "llvm/ADT/SmallSet.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/BinaryFormat/Dwarf.h"
 #include "llvm/CodeGen/LiveInterval.h"
@@ -408,7 +407,6 @@ bool TargetRegisterInfo::getRegAllocationHints(
   if (!Hints_MRI)
     return false;
 
-  SmallSet<Register, 32> HintedRegs;
   // First hint may be a target hint.
   bool Skip = (Hints_MRI->first != 0);
   for (auto Reg : Hints_MRI->second) {
@@ -422,10 +420,6 @@ bool TargetRegisterInfo::getRegAllocationHints(
     if (VRM && Phys.isVirtual())
       Phys = VRM->getPhys(Phys);
 
-    // Don't add the same reg twice (Hints_MRI may contain multiple virtual
-    // registers allocated to the same physreg).
-    if (!HintedRegs.insert(Phys).second)
-      continue;
     // Check that Phys is a valid hint in VirtReg's register class.
     if (!Phys.isPhysical())
       continue;
@@ -437,7 +431,15 @@ bool TargetRegisterInfo::getRegAllocationHints(
     if (!is_contained(Order, Phys))
       continue;
 
+    // Don't add the same reg twice (Hints_MRI may contain multiple virtual
+    // registers allocated to the same physreg, or Hints may already contain
+    // it).
+    if (is_contained(Hints, Phys.id()))
+      continue;
+
     // All clear, tell the register allocator to prefer this register.
+    assert(!is_contained(Hints, Phys.id()) &&
+           "TargetRegisterInfo::getRegAllocationHints duplicate hint!");
     Hints.push_back(Phys.id());
   }
   return false;
diff --git a/llvm/lib/Target/AArch64/AArch64RegisterInfo.cpp b/llvm/lib/Target/AArch64/AArch64RegisterInfo.cpp
index 502c91fe3e531..dbc6475899e81 100644
--- a/llvm/lib/Target/AArch64/AArch64RegisterInfo.cpp
+++ b/llvm/lib/Target/AArch64/AArch64RegisterInfo.cpp
@@ -1266,8 +1266,7 @@ bool AArch64RegisterInfo::getRegAllocationHints(
     return ConsiderOnlyHints;
 
   if (!ST.hasSME() || !ST.isStreaming())
-    return TargetRegisterInfo::getRegAllocationHints(VirtReg, Order, Hints, MF,
-                                                     VRM);
+    return ConsiderOnlyHints;
 
   // The SVE calling convention preserves registers Z8-Z23. As a result, there
   // are no ZPR2Strided or ZPR4Strided registers that do not overlap with the
@@ -1409,8 +1408,7 @@ bool AArch64RegisterInfo::getRegAllocationHints(
       }
 
       if (!Hints.empty())
-        return TargetRegisterInfo::getRegAllocationHints(VirtReg, Order, Hints,
-                                                         MF, VRM);
+        return ConsiderOnlyHints;
     }
   }
 
@@ -1441,8 +1439,7 @@ bool AArch64RegisterInfo::getRegAllocationHints(
     }
   }
 
-  return TargetRegisterInfo::getRegAllocationHints(VirtReg, Order, Hints, MF,
-                                                   VRM);
+  return ConsiderOnlyHints;
 }
 
 unsigned AArch64RegisterInfo::getLocalAddressRegister(
diff --git a/llvm/test/CodeGen/MLRegAlloc/aarch64-evict-advisor-duplicate-hints.ll b/llvm/test/CodeGen/MLRegAlloc/aarch64-evict-advisor-duplicate-hints.ll
new file mode 100644
index 0000000000000..67895b3d199cf
--- /dev/null
+++ b/llvm/test/CodeGen/MLRegAlloc/aarch64-evict-advisor-duplicate-hints.ll
@@ -0,0 +1,54 @@
+; REQUIRES: have_tf_aot
+; REQUIRES: aarch64-registered-target
+;
+; Check that AArch64 virtual registers with copy hints do not get duplicate
+; physical register hints added to AllocationOrder, which would otherwise lead
+; to out-of-bounds indexing in MLEvictAdvisor.
+;
+; RUN: llc -mtriple=aarch64-linux-gnu -regalloc=greedy \
+; RUN:   -regalloc-enable-advisor=release < %s -o /dev/null
+
+%struct.Flags = type { i8, i32, i8, i8, i8, i8, i8, i32, i8 }
+
+ at asan_flags_dont_use_directly = external global %struct.Flags
+
+define void @_Z15InitializeFlagsv(ptr %0, ptr %1) {
+  store i32 1, ptr getelementptr inbounds nuw (i8, ptr @asan_flags_dont_use_directly, i64 4), align 4
+  %3 = load i8, ptr getelementptr inbounds nuw (i8, ptr @asan_flags_dont_use_directly, i64 9), align 1
+  store i8 %3, ptr getelementptr inbounds nuw (i8, ptr @asan_flags_dont_use_directly, i64 8), align 4
+  %4 = load i8, ptr getelementptr inbounds nuw (i8, ptr @asan_flags_dont_use_directly, i64 12), align 4
+  store i8 %4, ptr getelementptr inbounds nuw (i8, ptr @asan_flags_dont_use_directly, i64 11), align 1
+  store i8 1, ptr getelementptr inbounds nuw (i8, ptr @asan_flags_dont_use_directly, i64 10), align 2
+  %5 = load i8, ptr getelementptr inbounds nuw (i8, ptr @asan_flags_dont_use_directly, i64 20), align 4
+  %6 = zext i8 %5 to i32
+  store i32 %6, ptr getelementptr inbounds nuw (i8, ptr @asan_flags_dont_use_directly, i64 16), align 4
+  %7 = tail call ptr @_Znwmi(i64 16, i32 0)
+  store ptr null, ptr %0, align 8
+  store ptr null, ptr %7, align 8
+  store ptr null, ptr @_Z26GetGlobalLowLevelAllocatorv, align 8
+  %8 = tail call ptr @_Znwmi(i64 0, i32 0)
+  store ptr @asan_flags_dont_use_directly, ptr %0, align 8
+  %9 = tail call ptr @_Znwmi(i64 0, i32 0)
+  store ptr getelementptr inbounds nuw (i8, ptr @asan_flags_dont_use_directly, i64 4), ptr %9, align 8
+  %10 = load i32, ptr null, align 4
+  %11 = tail call ptr @_Znwmi(i64 0, i32 %10)
+  store ptr getelementptr inbounds nuw (i8, ptr @asan_flags_dont_use_directly, i64 8), ptr %0, align 8
+  %12 = load i32, ptr null, align 4
+  %13 = tail call ptr @_Znwmi(i64 0, i32 %12)
+  store ptr getelementptr inbounds nuw (i8, ptr @asan_flags_dont_use_directly, i64 9), ptr @_Znwmi, align 8
+  store ptr getelementptr inbounds nuw (i8, ptr @asan_flags_dont_use_directly, i64 10), ptr %1, align 8
+  store ptr getelementptr inbounds nuw (i8, ptr @asan_flags_dont_use_directly, i64 11), ptr %0, align 8
+  %14 = tail call ptr @_Znwmi(i64 0, i32 0)
+  store ptr getelementptr inbounds nuw (i8, ptr @asan_flags_dont_use_directly, i64 12), ptr %0, align 8
+  %15 = tail call ptr @_Znwmi(i64 0, i32 0)
+  store ptr getelementptr inbounds nuw (i8, ptr @asan_flags_dont_use_directly, i64 16), ptr %1, align 8
+  store ptr getelementptr inbounds nuw (i8, ptr @asan_flags_dont_use_directly, i64 20), ptr %0, align 8
+  ret void
+}
+
+declare i32 @_Z26GetGlobalLowLevelAllocatorv()
+
+declare ptr @_Znwmi(i64, i32)
+
+; uselistorder directives
+uselistorder ptr @_Znwmi, { 7, 6, 5, 4, 3, 2, 1, 0 }



More information about the llvm-commits mailing list