[llvm] [AArch64][GlobalISel] Fix invalid subregister copies for truncating stores (PR #213935)

Cullen Rhodes via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 4 06:23:32 PDT 2026


https://github.com/c-rhodes created https://github.com/llvm/llvm-project/pull/213935

The custom truncating store selector always created a subregister copy matching the memory type. When storing an i16 value to i8 both types used GPR32, producing an invalid copy:
```
  *** Bad machine code: Invalid subregister index for virtual register ***
  - function:    truncstores
  - basic block: %bb.0  (0xb1a58e8738c0)
  - instruction: %2:gpr32 = COPY %val16.sub_32:gpr32
  - operand 1:   %val16.sub_32:gpr32
  Register class GPR32 does not support subreg index sub_32
  LLVM ERROR: Found 1 machine code errors.
```
Only create the subregister copy when the value and memory types use different register classes. When the classes match, the store can consume the original register directly.

The existing RegBankSelect avoids this by inserting a G_ANYEXT from i16 to i32, allowing the imported pattern to match. It was exposed by the type-based RegBankSelect prototype in #199040.

Assisted-by: codex

>From 85e859ac3286a42c72663897ad32fb81345e470c Mon Sep 17 00:00:00 2001
From: Cullen Rhodes <cullen.rhodes at arm.com>
Date: Thu, 11 Jun 2026 11:33:30 +0000
Subject: [PATCH] [AArch64][GlobalISel] Fix invalid subregister copies for
 truncating stores

The custom truncating store selector always created a subregister copy
matching the memory type. When storing an i16 value to i8 both types
used GPR32, producing an invalid copy:

  *** Bad machine code: Invalid subregister index for virtual register ***
  - function:    truncstores
  - basic block: %bb.0  (0xb1a58e8738c0)
  - instruction: %2:gpr32 = COPY %val16.sub_32:gpr32
  - operand 1:   %val16.sub_32:gpr32
  Register class GPR32 does not support subreg index sub_32
  LLVM ERROR: Found 1 machine code errors.

Only create the subregister copy when the value and memory types use
different register classes. When the classes match, the store can
consume the original register directly.

The existing RegBankSelect hides this case by inserting a G_ANYEXT from
i16 to i32. It was exposed by the type-based RegBankSelect prototype in

Assisted-by: codex
---
 .../GISel/AArch64InstructionSelector.cpp      | 27 +++++++++++++------
 .../AArch64/GlobalISel/select-store.mir       |  6 +++++
 2 files changed, 25 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp b/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp
index 7e9135b15144a..b854c64d2a984 100644
--- a/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp
+++ b/llvm/lib/Target/AArch64/GISel/AArch64InstructionSelector.cpp
@@ -3035,18 +3035,29 @@ bool AArch64InstructionSelector::select(MachineInstr &I) {
     // The code below doesn't support truncating stores, so we need to split it
     // again.
     if (isa<GStore>(LdSt) && ValTy.getSizeInBits() > MemSizeInBits) {
-      unsigned SubReg;
       LLT MemTy = LdSt.getMMO().getMemoryType();
       auto *RC = getRegClassForTypeOnBank(MemTy, RB);
-      if (!getSubRegForClass(RC, TRI, SubReg))
+      if (!RC)
         return false;
+      auto *ValRC = getRegClassForTypeOnBank(ValTy, RB);
+      if (!ValRC)
+        return false;
+
+      // Only insert a subregister copy when truncating changes register class.
+      // If both types use the same class, the store can consume ValReg
+      // directly.
+      if (ValRC != RC) {
+        unsigned SubReg;
+        if (!getSubRegForClass(RC, TRI, SubReg))
+          return false;
 
-      // Generate a subreg copy.
-      auto Copy = MIB.buildInstr(TargetOpcode::COPY, {MemTy}, {})
-                      .addReg(ValReg, {}, SubReg)
-                      .getReg(0);
-      RBI.constrainGenericRegister(Copy, *RC, MRI);
-      LdSt.getOperand(0).setReg(Copy);
+        // Generate a subreg copy.
+        auto Copy = MIB.buildInstr(TargetOpcode::COPY, {MemTy}, {})
+                        .addReg(ValReg, {}, SubReg)
+                        .getReg(0);
+        RBI.constrainGenericRegister(Copy, *RC, MRI);
+        LdSt.getOperand(0).setReg(Copy);
+      }
     } else if (isa<GLoad>(LdSt) && ValTy.getSizeInBits() > MemSizeInBits) {
       // If this is an any-extending load from the FPR bank, split it into a regular
       // load + extend.
diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/select-store.mir b/llvm/test/CodeGen/AArch64/GlobalISel/select-store.mir
index 3cc6ef4d4cb0b..0d6dfd5da82c1 100644
--- a/llvm/test/CodeGen/AArch64/GlobalISel/select-store.mir
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/select-store.mir
@@ -728,6 +728,8 @@ body:             |
     ; CHECK-NEXT: [[COPY:%[0-9]+]]:gpr64sp = COPY $x0
     ; CHECK-NEXT: %val32:gpr32 = COPY $w1
     ; CHECK-NEXT: %val64:gpr64 = COPY $x2
+    ; CHECK-NEXT: %val16:gpr32 = IMPLICIT_DEF
+    ; CHECK-NEXT: STRBBui %val16, [[COPY]], 0 :: (store (i8))
     ; CHECK-NEXT: STRBBui %val32, [[COPY]], 0 :: (store (i8))
     ; CHECK-NEXT: STRBBui %val32, [[COPY]], 43 :: (store (i8))
     ; CHECK-NEXT: STRHHui %val32, [[COPY]], 0 :: (store (i16))
@@ -743,6 +745,10 @@ body:             |
     %0:gpr(p0) = COPY $x0
     %val32:gpr(i32) = COPY $w1
     %val64:gpr(i64) = COPY $x2
+    %val16:gpr(i16) = G_IMPLICIT_DEF
+
+    G_STORE %val16, %0 :: (store (i8))
+
     G_STORE %val32, %0 :: (store (i8))
     ; unscaled offset:
     %cst:gpr(i64) = G_CONSTANT i64 43



More information about the llvm-commits mailing list