[llvm] [RISCV] Use ADDD for GPR Pair Move with P (PR #180671)

Sam Elliott via llvm-commits llvm-commits at lists.llvm.org
Mon Feb 9 20:01:48 PST 2026


lenary wrote:

The testing on this one was quite annoying. It seems reasonable to prioritise using `fmv.d` when you have Zdinx, which didn't leave very many existing tests that would reasonably use `ADDD`.

There are lots of places where we get `mv a0, <reg>; mv a1, <reg>` - usually when returning a 64-bit value on rv32. Unfortunately we sort of get the wrong result here, due to register coalescing. For instance, `fold_addi_from_different_bb` in `llvm/test/CodeGen/RISCV/fold-addi-loadstore-zilsd.ll`:

Before Register Coalescer:
```
# Machine code for function fold_addi_from_different_bb: NoPHIs, TracksLiveness, TiedOpsRewritten
Function Live Ins: $x10 in %6, $x11 in %7, $x12 in %8

bb.0.entry:
  successors: %bb.2(0x50000000), %bb.1(0x30000000); %bb.2(62.50%), %bb.1(37.50%)
  liveins: $x10, $x11, $x12
  %8:gpr = COPY $x12
  %7:gpr = COPY $x11
  %6:gpr = COPY $x10
  %10:gpr = COPY $x0
  BLT %10:gpr, %7:gpr, %bb.2

bb.1:
; predecessors: %bb.0
  successors: %bb.3(0x80000000); %bb.3(100.00%)

  %9:gprpair = COPY $x0_pair
  %17:gprpair = COPY %9:gprpair
  PseudoBR %bb.3

bb.2.for.body.lr.ph:
; predecessors: %bb.0
  successors: %bb.4(0x80000000); %bb.4(100.00%)

  %12:gpr = nsw SLLI %6:gpr, 4
  %13:gpr = ADD %8:gpr, %12:gpr
  %11:gprpair = COPY $x0_pair
  %18:gpr = COPY %7:gpr
  %19:gprpair = COPY %11:gprpair
  PseudoBR %bb.4

bb.3.for.cond.cleanup:
; predecessors: %bb.4, %bb.1

  %1:gprpair = COPY %17:gprpair
  %15:gpr = COPY %1.sub_gpr_even:gprpair
  %16:gpr = COPY %1.sub_gpr_odd:gprpair
  $x10 = COPY %15:gpr
  $x11 = COPY %16:gpr
  PseudoRET implicit $x10, implicit $x11

bb.4.for.body:
; predecessors: %bb.2, %bb.4
  successors: %bb.3(0x04000000), %bb.4(0x7c000000); %bb.3(3.12%), %bb.4(96.88%)

  %3:gprpair = COPY %19:gprpair
  %2:gpr = COPY %18:gpr
  ADJCALLSTACKDOWN 0, 0, implicit-def dead $x2, implicit $x2
  $x10 = COPY %8:gpr
  PseudoCALL target-flags(riscv-call) @f, <regmask $vlenb $x0 $x1 $x8 $x9 $x18 $x19 $x20 $x21 $x22 $x23 $x24 $x25 $x26 $x27 $x0_h $x1_h $x8_h $x9_h $x18_h $x19_h $x20_h $x21_h $x22_h $x23_h $x24_h $x25_h $x26_h $x27_h $x0_w $x1_w $x8_w $x9_w and 17 more...>, implicit-def dead $x1, implicit $x10, implicit-def $x2
  ADJCALLSTACKUP 0, 0, implicit-def dead $x2, implicit $x2
  %14:gprpair = LD_RV32 %13:gpr, 8 :: (load (s64) from %ir.y)
  %4:gprpair = nofpexcept FADD_D_IN32X %14:gprpair, %3:gprpair, 7, implicit $frm
  %5:gpr = ADDI %2:gpr, -1
  %17:gprpair = COPY %4:gprpair
  %18:gpr = COPY %5:gpr
  %19:gprpair = COPY %4:gprpair
  BEQ %5:gpr, $x0, %bb.3
  PseudoBR %bb.4
```

In `bb.3.for.cond.cleanup`, the two instructions before the `PseudoRET` become:
```
  $x10 = COPY %19.sub_gpr_even:gprpairnox0
  $x11 = COPY %19.sub_gpr_odd:gprpairnox0
```

Which means we definitely get a 2-instruction move, as seen in the final assembly for that function.

I'm not sure what the right thing to do here is. Obviously ADDD/FMV.D are only valid if the copies are coming from an even-odd register pair. Maybe we should be doing some manual coalescing of our own? One other thought I had is we could be assigning 64-bit values into `$x10_x11` during call lowering when they are in a well-aligned register pair, but I think that runs into problems when the two values aren't coming from even-odd pairs, and I think having some kind of COPY_PAIR_OPT instruction would probably be a bad idea for complexity (like we have for RV32_SD_OPT).

https://github.com/llvm/llvm-project/pull/180671


More information about the llvm-commits mailing list