[llvm] [Hexagon] Handle truncating COPY from DoubleRegs to IntRegs (PR #181360)

Brian Cain via llvm-commits llvm-commits at lists.llvm.org
Thu Feb 19 14:50:14 PST 2026


https://github.com/androm3da updated https://github.com/llvm/llvm-project/pull/181360

>From 7fc0466628bb9573088d2a39aa60469936d0cc9b Mon Sep 17 00:00:00 2001
From: Brian Cain <brian.cain at oss.qualcomm.com>
Date: Thu, 12 Feb 2026 22:09:42 -0800
Subject: [PATCH 1/2] [Hexagon] Handle truncating COPY from DoubleRegs to
 IntRegs

ISel can generate truncating COPYs from DoubleRegs to IntRegs when a
64-bit C2_mask result is used in a context that only needs 32 bits
(e.g., V6_vinsertwr). Three passes crashed on this pattern:

BitTracker asserted WD >= WS for COPY instructions. Fix by handling
the WD < WS case: extract the low WD bits from the source.

HexagonTfrCleanup asserted that source and destination register
sizes match. Fix by returning false (skipping the optimization)
when sizes don't match.

HexagonInstrInfo::copyPhysReg had no case for IntRegs <- DoubleRegs.
Fix by extracting the isub_lo sub-register and using A2_tfr.
---
 llvm/lib/Target/Hexagon/BitTracker.cpp        | 14 +++++++++---
 llvm/lib/Target/Hexagon/HexagonInstrInfo.cpp  |  7 ++++++
 llvm/lib/Target/Hexagon/HexagonTfrCleanup.cpp |  3 ++-
 .../Hexagon/truncating-copy-double-to-int.ll  | 22 +++++++++++++++++++
 4 files changed, 42 insertions(+), 4 deletions(-)
 create mode 100644 llvm/test/CodeGen/Hexagon/truncating-copy-double-to-int.ll

diff --git a/llvm/lib/Target/Hexagon/BitTracker.cpp b/llvm/lib/Target/Hexagon/BitTracker.cpp
index 80eedabb0d038..193eadb8fb4d4 100644
--- a/llvm/lib/Target/Hexagon/BitTracker.cpp
+++ b/llvm/lib/Target/Hexagon/BitTracker.cpp
@@ -736,16 +736,24 @@ bool BT::MachineEvaluator::evaluate(const MachineInstr &MI,
     case TargetOpcode::COPY: {
       // COPY can transfer a smaller register into a wider one.
       // If that is the case, fill the remaining high bits with 0.
+      // COPY can also transfer a wider register into a narrower one,
+      // in which case the high bits are simply truncated.
       RegisterRef RD = MI.getOperand(0);
       RegisterRef RS = MI.getOperand(1);
       assert(RD.Sub == 0);
       uint16_t WD = getRegBitWidth(RD);
       uint16_t WS = getRegBitWidth(RS);
-      assert(WD >= WS);
       RegisterCell Src = getCell(RS, Inputs);
       RegisterCell Res(WD);
-      Res.insert(Src, BitMask(0, WS-1));
-      Res.fill(WS, WD, BitValue::Zero);
+      if (WD <= WS) {
+        // Truncating copy: extract low WD bits from source.
+        RegisterCell Trunc = Src.extract(BitMask(0, WD - 1));
+        Res.insert(Trunc, BitMask(0, WD - 1));
+      } else {
+        // Widening copy: insert all source bits, zero-fill high bits.
+        Res.insert(Src, BitMask(0, WS - 1));
+        Res.fill(WS, WD, BitValue::Zero);
+      }
       putCell(RD, Res, Outputs);
       break;
     }
diff --git a/llvm/lib/Target/Hexagon/HexagonInstrInfo.cpp b/llvm/lib/Target/Hexagon/HexagonInstrInfo.cpp
index 9f511efdfb461..212629141ccc6 100644
--- a/llvm/lib/Target/Hexagon/HexagonInstrInfo.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonInstrInfo.cpp
@@ -882,6 +882,13 @@ void HexagonInstrInfo::copyPhysReg(MachineBasicBlock &MBB,
       .addReg(SrcReg).addReg(SrcReg, KillFlag);
     return;
   }
+  if (Hexagon::IntRegsRegClass.contains(DestReg) &&
+      Hexagon::DoubleRegsRegClass.contains(SrcReg)) {
+    // Truncating copy: extract the low 32-bit sub-register.
+    Register SrcLo = HRI.getSubReg(SrcReg, Hexagon::isub_lo);
+    BuildMI(MBB, I, DL, get(Hexagon::A2_tfr), DestReg).addReg(SrcLo, KillFlag);
+    return;
+  }
   if (Hexagon::CtrRegsRegClass.contains(DestReg) &&
       Hexagon::IntRegsRegClass.contains(SrcReg)) {
     BuildMI(MBB, I, DL, get(Hexagon::A2_tfrrcr), DestReg)
diff --git a/llvm/lib/Target/Hexagon/HexagonTfrCleanup.cpp b/llvm/lib/Target/Hexagon/HexagonTfrCleanup.cpp
index 5a85f348fdaf7..b97671cf13529 100644
--- a/llvm/lib/Target/Hexagon/HexagonTfrCleanup.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonTfrCleanup.cpp
@@ -186,7 +186,8 @@ bool HexagonTfrCleanup::rewriteIfImm(MachineInstr *MI, ImmediateMap &IMap,
   bool Tmp, Is32;
   if (!isIntReg(DstR, Is32) || !isIntReg(SrcR, Tmp))
     return false;
-  assert(Tmp == Is32 && "Register size mismatch");
+  if (Tmp != Is32)
+    return false;
   uint64_t Val;
   bool Found = getReg(SrcR, Val, IMap);
   if (!Found)
diff --git a/llvm/test/CodeGen/Hexagon/truncating-copy-double-to-int.ll b/llvm/test/CodeGen/Hexagon/truncating-copy-double-to-int.ll
new file mode 100644
index 0000000000000..762163890a6b3
--- /dev/null
+++ b/llvm/test/CodeGen/Hexagon/truncating-copy-double-to-int.ll
@@ -0,0 +1,22 @@
+; RUN: llc -mtriple=hexagon -mcpu=hexagonv73 -mattr=+hvxv73,+hvx-length128b \
+; RUN:   < %s | FileCheck %s
+;
+; Check that a truncating copy from DoubleRegs to IntRegs (generated by
+; C2_mask + truncation) does not crash in BitTracker, HexagonTfrCleanup,
+; or ExpandPostRAPseudo.
+
+target datalayout = "e-m:e-p:32:32:32-a:0-n16:32-i64:64:64-i32:32:32-i16:16:16-i1:8:8-f32:32:32-f64:64:64-v32:32:32-v64:64:64-v512:512:512-v1024:1024:1024-v2048:2048:2048"
+target triple = "hexagon-unknown-linux-musl"
+
+; CHECK-LABEL: endgame:
+; CHECK:       mask(p0)
+; CHECK:       popcount
+; CHECK:       jumpr r31
+define i16 @endgame(<8 x i32> %0) {
+entry:
+  %1 = icmp eq <8 x i32> %0, zeroinitializer
+  %rdx.op150 = shufflevector <8 x i1> zeroinitializer, <8 x i1> %1, <16 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 8, i32 9, i32 10, i32 11, i32 12, i32 13, i32 14, i32 15>
+  %2 = bitcast <16 x i1> %rdx.op150 to i16
+  %3 = call i16 @llvm.ctpop.i16(i16 %2)
+  ret i16 %3
+}

>From 9510d3dec8afee04562b8040ed480fc8074292e1 Mon Sep 17 00:00:00 2001
From: Sergei Larin <slarin at codeaurora.org>
Date: Thu, 19 Feb 2026 14:48:44 -0800
Subject: [PATCH 2/2] [Hexagon] Improve subreg copy handling in GenMux,
 TfrCleanup, and copyPhysReg

Extend the truncating COPY support from the previous commit with more
comprehensive subreg handling across multiple passes:

copyPhysReg: respect the subreg index on the source operand
(isub_lo/isub_hi) instead of always extracting isub_lo, and add the
reverse direction (DoubleRegs.sub <- IntRegs).

HexagonTfrCleanup: properly resolve subreg indices on both source and
destination operands when checking register sizes, and add a
hasNoVRegs() guard since this pass runs post-RA.

HexagonGenMux: preserve subreg information when building mux
instructions and handle subreg operands when fixing kill flags,
avoiding an assertion that assumed no subregs on physical registers.
---
 llvm/lib/Target/Hexagon/HexagonGenMux.cpp     | 42 +++++++++++---
 llvm/lib/Target/Hexagon/HexagonInstrInfo.cpp  | 26 ++++++++-
 llvm/lib/Target/Hexagon/HexagonTfrCleanup.cpp | 55 ++++++++++++++++---
 .../CodeGen/Hexagon/copy-phys-int-dbl.mir     | 32 +++++++++++
 .../Hexagon/tfr-cleanup-subreg-copy.ll        | 20 +++++++
 5 files changed, 157 insertions(+), 18 deletions(-)
 create mode 100644 llvm/test/CodeGen/Hexagon/copy-phys-int-dbl.mir
 create mode 100644 llvm/test/CodeGen/Hexagon/tfr-cleanup-subreg-copy.ll

diff --git a/llvm/lib/Target/Hexagon/HexagonGenMux.cpp b/llvm/lib/Target/Hexagon/HexagonGenMux.cpp
index c6fffde84af58..1029095b27c56 100644
--- a/llvm/lib/Target/Hexagon/HexagonGenMux.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonGenMux.cpp
@@ -324,18 +324,41 @@ bool HexagonGenMux::genMuxInBlock(MachineBasicBlock &B) {
 
     MachineBasicBlock &B = *MX.At->getParent();
     const DebugLoc &DL = B.findDebugLoc(MX.At);
-    auto NewMux = BuildMI(B, MX.At, DL, HII->get(MxOpc), MX.DefR)
-                      .addReg(MX.PredR)
-                      .add(*MX.SrcT)
-                      .add(*MX.SrcF);
-    NewMux->clearKillInfo();
+    MachineInstrBuilder MIB = BuildMI(B, MX.At, DL, HII->get(MxOpc), MX.DefR);
+    MIB.addReg(MX.PredR);
+    auto AddSrc = [&](MachineOperand *SrcOp) {
+      if (!SrcOp->isReg()) {
+        MIB.add(*SrcOp);
+        return;
+      }
+      Register Reg = SrcOp->getReg();
+      unsigned Sub = SrcOp->getSubReg();
+      RegState RS = {};
+      if (SrcOp->isKill())
+        RS |= RegState::Kill;
+      if (SrcOp->isUndef())
+        RS |= RegState::Undef;
+      if (SrcOp->isDebug())
+        RS |= RegState::Debug;
+      if (SrcOp->isInternalRead())
+        RS |= RegState::InternalRead;
+      if (Sub) {
+        // Preserve subregister information instead of resolving to physical
+        // reg.
+        MIB.addReg(Reg, RS, Sub);
+      } else {
+        MIB.addReg(Reg, RS);
+      }
+    };
+    AddSrc(MX.SrcT);
+    AddSrc(MX.SrcF);
+    MIB.getInstr()->clearKillInfo();
     B.remove(MX.Def1);
     B.remove(MX.Def2);
     Changed = true;
   }
 
   // Fix up kill flags.
-
   LiveRegUnits LPR(*HRI);
   LPR.addLiveOuts(B);
   for (MachineInstr &I : llvm::reverse(B)) {
@@ -348,7 +371,12 @@ bool HexagonGenMux::genMuxInBlock(MachineBasicBlock &B) {
     for (MachineOperand &Op : I.operands()) {
       if (!Op.isReg() || !Op.isUse())
         continue;
-      assert(Op.getSubReg() == 0 && "Should have physical registers only");
+      if (Op.getSubReg() != 0) {
+        Register R = HRI->getSubReg(Op.getReg(), Op.getSubReg());
+        bool Live = !LPR.available(R);
+        Op.setIsKill(!Live);
+        continue;
+      }
       bool Live = !LPR.available(Op.getReg());
       Op.setIsKill(!Live);
     }
diff --git a/llvm/lib/Target/Hexagon/HexagonInstrInfo.cpp b/llvm/lib/Target/Hexagon/HexagonInstrInfo.cpp
index 212629141ccc6..2984858439e5f 100644
--- a/llvm/lib/Target/Hexagon/HexagonInstrInfo.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonInstrInfo.cpp
@@ -884,9 +884,29 @@ void HexagonInstrInfo::copyPhysReg(MachineBasicBlock &MBB,
   }
   if (Hexagon::IntRegsRegClass.contains(DestReg) &&
       Hexagon::DoubleRegsRegClass.contains(SrcReg)) {
-    // Truncating copy: extract the low 32-bit sub-register.
-    Register SrcLo = HRI.getSubReg(SrcReg, Hexagon::isub_lo);
-    BuildMI(MBB, I, DL, get(Hexagon::A2_tfr), DestReg).addReg(SrcLo, KillFlag);
+    // Truncating copy: extract the specified or low sub-register.
+    unsigned SrcSub = Hexagon::isub_lo;
+    if (I->isCopy()) {
+      unsigned OpSub = I->getOperand(1).getSubReg();
+      if (OpSub == Hexagon::isub_lo || OpSub == Hexagon::isub_hi)
+        SrcSub = OpSub;
+    }
+    Register SrcHalf = HRI.getSubReg(SrcReg, SrcSub);
+    BuildMI(MBB, I, DL, get(Hexagon::A2_tfr), DestReg)
+        .addReg(SrcHalf, KillFlag);
+    return;
+  }
+  if (Hexagon::DoubleRegsRegClass.contains(DestReg) &&
+      Hexagon::IntRegsRegClass.contains(SrcReg)) {
+    // Inserting copy: write into the specified or low sub-register half.
+    unsigned DstSub = Hexagon::isub_lo;
+    if (I->isCopy()) {
+      unsigned OpSub = I->getOperand(0).getSubReg();
+      if (OpSub == Hexagon::isub_lo || OpSub == Hexagon::isub_hi)
+        DstSub = OpSub;
+    }
+    Register DstHalf = HRI.getSubReg(DestReg, DstSub);
+    BuildMI(MBB, I, DL, get(Hexagon::A2_tfr), DstHalf).addReg(SrcReg, KillFlag);
     return;
   }
   if (Hexagon::CtrRegsRegClass.contains(DestReg) &&
diff --git a/llvm/lib/Target/Hexagon/HexagonTfrCleanup.cpp b/llvm/lib/Target/Hexagon/HexagonTfrCleanup.cpp
index b97671cf13529..528a679dda5ca 100644
--- a/llvm/lib/Target/Hexagon/HexagonTfrCleanup.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonTfrCleanup.cpp
@@ -181,24 +181,59 @@ bool HexagonTfrCleanup::rewriteIfImm(MachineInstr *MI, ImmediateMap &IMap,
     return false;
   }
 
-  unsigned DstR = MI->getOperand(0).getReg();
-  unsigned SrcR = MI->getOperand(1).getReg();
-  bool Tmp, Is32;
-  if (!isIntReg(DstR, Is32) || !isIntReg(SrcR, Tmp))
+  Register DstBase = MI->getOperand(0).getReg();
+  Register SrcBase = MI->getOperand(1).getReg();
+  unsigned DstSub = MI->getOperand(0).getSubReg();
+  unsigned SrcSub = MI->getOperand(1).getSubReg();
+
+  if (!DstBase.isPhysical() || !SrcBase.isPhysical())
     return false;
-  if (Tmp != Is32)
+
+  // Determine effective registers and widths, accounting for subregs.
+  bool Is32Dst = false, Is32Src = false;
+  unsigned DstR = DstBase;
+  unsigned SrcR = SrcBase;
+
+  if (DstSub) {
+    if (DstSub != Hexagon::isub_lo && DstSub != Hexagon::isub_hi)
+      return false;
+    Is32Dst = true;
+    DstR = TRI->getSubReg(DstBase, DstSub);
+  } else {
+    if (!isIntReg(DstBase, Is32Dst))
+      return false;
+  }
+
+  if (SrcSub) {
+    if (SrcSub != Hexagon::isub_lo && SrcSub != Hexagon::isub_hi)
+      return false;
+    if (!Hexagon::DoubleRegsRegClass.contains(SrcBase))
+      return false;
+    Is32Src = true;
+    SrcR = TRI->getSubReg(SrcBase, SrcSub);
+  } else {
+    if (!isIntReg(SrcBase, Is32Src))
+      return false;
+  }
+
+  // After resolving subregs, check that effective register sizes match.
+  bool Is32DstResolved = Hexagon::IntRegsRegClass.contains(DstR);
+  bool Is32SrcResolved = Hexagon::IntRegsRegClass.contains(SrcR);
+  if (Is32DstResolved != Is32SrcResolved)
     return false;
+
   uint64_t Val;
   bool Found = getReg(SrcR, Val, IMap);
   if (!Found)
     return false;
 
   MachineBasicBlock &B = *MI->getParent();
+  MachineFunction *F = B.getParent();
   DebugLoc DL = MI->getDebugLoc();
-  int64_t SVal = Is32 ? int32_t(Val) : Val;
-  auto &HST = B.getParent()->getSubtarget<HexagonSubtarget>();
+  int64_t SVal = Is32Dst ? int32_t(Val) : Val;
+  auto &HST = F->getSubtarget<HexagonSubtarget>();
   MachineInstr *NewMI;
-  if (Is32)
+  if (Is32Dst)
     NewMI = BuildMI(B, MI, DL, HII->get(A2_tfrsi), DstR).addImm(SVal);
   else if (isInt<8>(SVal))
     NewMI = BuildMI(B, MI, DL, HII->get(A2_tfrpi), DstR).addImm(SVal);
@@ -260,6 +295,10 @@ bool HexagonTfrCleanup::eraseIfRedundant(MachineInstr *MI,
 }
 
 bool HexagonTfrCleanup::runOnMachineFunction(MachineFunction &MF) {
+  // This pass is intended to run post-RA. If virtual registers are present,
+  // skip safely to avoid touching non-physical registers.
+  if (!MF.getProperties().hasNoVRegs())
+    return false;
   bool Changed = false;
   // Map: 32-bit register -> immediate value.
   // 64-bit registers are stored through their subregisters.
diff --git a/llvm/test/CodeGen/Hexagon/copy-phys-int-dbl.mir b/llvm/test/CodeGen/Hexagon/copy-phys-int-dbl.mir
new file mode 100644
index 0000000000000..5b4212b31515b
--- /dev/null
+++ b/llvm/test/CodeGen/Hexagon/copy-phys-int-dbl.mir
@@ -0,0 +1,32 @@
+# RUN: llc -mtriple=hexagon -verify-machineinstrs %s -o - | FileCheck %s
+# RUN: llc -mtriple=hexagon -stop-after=tfr-cleanup -verify-machineinstrs %s -o - | FileCheck %s --check-prefix=MIR
+
+# Ensure copyPhysReg handles int<-dbl subregister and dbl subregister<-int,
+# and does not crash on scalar copies.
+
+# CHECK-LABEL: phys_copy:
+# CHECK:       combine(
+
+# MIR-LABEL: name: phys_copy
+# MIR: $r5 = {{A2_tfr|COPY}} $r4
+# MIR: $r6 = COPY {{.*}}$r4
+# MIR: $r1 = COPY {{.*}}$r6
+
+---
+name: phys_copy
+tracksRegLiveness: true
+body: |
+  bb.0:
+    liveins: $r4, $r0
+    ; Int<-Int
+    $r5 = COPY $r4
+    ; Use $r5 so it isn't DCE'd
+    $r0 = COPY $r5
+    ; Dbl subreg<-Int (use virtual double)
+    %0:doubleregs = REG_SEQUENCE $r4, %subreg.isub_lo, $r4, %subreg.isub_hi
+    ; Int<-Dbl subreg
+    $r6 = COPY %0.isub_lo
+    ; Use $r6 to keep it live
+    $r1 = COPY $r6
+    PS_jmpret $r31, implicit-def dead $pc, implicit $r0, implicit $r1
+...
diff --git a/llvm/test/CodeGen/Hexagon/tfr-cleanup-subreg-copy.ll b/llvm/test/CodeGen/Hexagon/tfr-cleanup-subreg-copy.ll
new file mode 100644
index 0000000000000..08a93c0e76689
--- /dev/null
+++ b/llvm/test/CodeGen/Hexagon/tfr-cleanup-subreg-copy.ll
@@ -0,0 +1,20 @@
+; RUN: llc -stop-after=tfr-cleanup -verify-machineinstrs -mtriple=hexagon %s -o - | FileCheck %s
+
+; Create a copy from a 64-bit argument (double regs) to 32-bit intregs via subreg.
+; The tfr-cleanup pass should not assert on size mismatch and should leave the
+; copy intact when sizes differ.
+
+; CHECK: name:            test
+; CHECK: liveins: $d0, $r2
+; CHECK: renamable $r0 = A2_add killed renamable $r0, renamable $r1
+; CHECK: S2_storeri_io
+
+define dso_local void @test(i64 %x, ptr nocapture %out) local_unnamed_addr {
+entry:
+  %lo = trunc i64 %x to i32
+  %hi.shift = lshr i64 %x, 32
+  %hi = trunc i64 %hi.shift to i32
+  %sum = add i32 %lo, %hi
+  store i32 %sum, ptr %out, align 4
+  ret void
+}



More information about the llvm-commits mailing list