Fixed Register Being Spill and Restored in Clang

Hi, guys
I found a fixed register will be saved and restored. Look at Compiler Explorer.

I feel confused why x24(s8) was saved and restored, even if give clang “-ffixed-x24”. And gcc don’t. I think gcc is better.

Compiler options:

-S -O2 -ffixed-x24
register long a asm("x24");
int main() {
  a = 123;
  return 0;
}
      

GCC out:

main:
        li      s8,123
        li      a0,0
        ret

Clang Out:

main:
        addi    sp, sp, -16
        sd      s8, 8(sp)
        li      s8, 123
        li      a0, 0
        ld      s8, 8(sp)
        addi    sp, sp, 16
        ret

It looks like it is getting register allocated correctly, without a spill:

body:             |
  bb.0.entry:
    $x24 = ADDI $x0, 123
    $x10 = COPY $x0
    PseudoRET implicit killed $x10

You can see the full godbolt here. I will take a look to see if I can figure out where we are introducing the spill.

It looks like the spills are inserted by the PrologEpilogInserter pass. You can see this on godbolt here.

It looks like the code generated by clang and gcc do different things. On gcc, s8 is updated to 123. On clang, s8 is temporarily updated to 123, only to be restored to its original value. Is there some sort of UB going on or is this a miscompile?

It’s a miscompile. LLVM isn’t removing x24 from the callee saved register list when -ffixed-x24 is passed.

1 Like

The spills are generated because this register is included in as a SavedReg in determineCalleeSaves. TargetFrameLowering::determineCalleeSaves makes a call to getCalleeSavedRegs and this register is part of this list. We could probably avoid adding it to the list in the first place, or have determineCalleeSaves skip over it in this case.

Proposed patch: [RISCV][MRI] Account for fixed registers when determining callee saved regs by michaelmaitland · Pull Request #115756 · llvm/llvm-project · GitHub

gcc doesn’t spill the register when used as a global register variable regardless of -ffixed-x24, but llvm doesn’t allow x24 as a global register variable without -ffixed-x24.

May fixup it by

   const MachineRegisterInfo &MRI = MF.getRegInfo();
   for (unsigned i = 0; CSRegs[i]; ++i) {
     unsigned Reg = CSRegs[i];
-    if (CallsUnwindInit || MRI.isPhysRegModified(Reg))
+    // Fixed register should not be spill and restore.
+    if (!MRI.isReserved(Reg) && 
+        (CallsUnwindInit || MRI.isPhysRegModified(Reg)))
       SavedRegs.set(Reg);

in TargetFrameLowering::determineCalleeSaves

I’m not sure MRI::isReserved approach would be correct. For example, some targets may reserve a register, separate from the user fixing it. such as x4 on RISC-V. This is different than passing -ffixed-x4.

I’m not sure MRI::isReserved approach would be correct. For example, some targets may reserve a register, separate from the user fixing it. such as x4 on RISC-V. This is different than passing -ffixed-x4 .

Why is it different? X4 isn’t part of the callee saved register list because it is always reserved.

Resolved in [RISCV][MRI] Account for fixed registers when determining callee saved regs by michaelmaitland · Pull Request #115756 · llvm/llvm-project · GitHub