[clang] [llvm] [RISCV] Add partial support for -fzero-call-used-regs (PR #194883)

via cfe-commits cfe-commits at lists.llvm.org
Wed Apr 29 08:23:48 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-backend-risc-v

@llvm/pr-subscribers-clang-driver

Author: Lucas Chollet (LucasChollet)

<details>
<summary>Changes</summary>

This implements the "-fzero-call-used-regs" option on RISCV for the "skip" and "*gpr*" arguments. Zeroing floating points and vector registers will be implemented later.

---
Full diff: https://github.com/llvm/llvm-project/pull/194883.diff


10 Files Affected:

- (modified) clang/include/clang/Options/Options.td (+1-1) 
- (modified) clang/lib/Driver/ToolChains/Clang.cpp (+6-4) 
- (modified) llvm/lib/Target/RISCV/RISCVFrameLowering.cpp (+21) 
- (modified) llvm/lib/Target/RISCV/RISCVFrameLowering.h (+4) 
- (modified) llvm/lib/Target/RISCV/RISCVInstrInfo.cpp (+16) 
- (modified) llvm/lib/Target/RISCV/RISCVInstrInfo.h (+4) 
- (modified) llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp (+10) 
- (modified) llvm/lib/Target/RISCV/RISCVRegisterInfo.h (+3) 
- (modified) llvm/lib/Target/RISCV/RISCVRegisterInfo.td (+8) 
- (added) llvm/test/CodeGen/RISCV/zero-call-used-regs.ll (+225) 


``````````diff
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index c16c41ad4057d..12edf04797616 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -4977,7 +4977,7 @@ defm raw_string_literals : BoolFOption<"raw-string-literals",
 def fzero_call_used_regs_EQ
     : Joined<["-"], "fzero-call-used-regs=">, Group<f_Group>,
     Visibility<[ClangOption, CC1Option]>,
-      HelpText<"Clear call-used registers upon function return (AArch64/x86 only)">,
+      HelpText<"Clear call-used registers upon function return (AArch64/RISCV/x86 only)">,
       Values<"skip,used-gpr-arg,used-gpr,used-arg,used,all-gpr-arg,all-gpr,all-arg,all">,
       NormalizedValues<["Skip", "UsedGPRArg", "UsedGPR", "UsedArg", "Used",
                         "AllGPRArg", "AllGPR", "AllArg", "All"]>,
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index cf5a29f19aaff..00348c3f5ba6e 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -6802,10 +6802,12 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
                     options::OPT_fno_check_new);
 
   if (Arg *A = Args.getLastArg(options::OPT_fzero_call_used_regs_EQ)) {
-    // FIXME: There's no reason for this to be restricted to X86. The backend
-    // code needs to be changed to include the appropriate function calls
-    // automatically.
-    if (!Triple.isX86() && !Triple.isAArch64())
+    // FIXME: There's no reason for this to be restricted to some backend.
+    // The backend code needs to be changed to include the appropriate function
+    // calls automatically.
+    StringRef Value = A->getValue();
+    if (!Triple.isX86() && !Triple.isAArch64() &&
+        !(Triple.isRISCV() && (Value == "skip" || Value.contains("gpr"))))
       D.Diag(diag::err_drv_unsupported_opt_for_target)
           << A->getAsString(Args) << TripleStr;
   }
diff --git a/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp b/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp
index 1be81ea93129a..207ffde5cbf23 100644
--- a/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp
@@ -1433,6 +1433,27 @@ void RISCVFrameLowering::emitEpilogue(MachineFunction &MF,
   emitSiFiveCLICStackSwap(MF, MBB, MBBI, DL);
 }
 
+void RISCVFrameLowering::emitZeroCallUsedRegs(BitVector RegsToZero,
+                                              MachineBasicBlock &MBB) const {
+  // Insertion point.
+  MachineBasicBlock::iterator MBBI = MBB.getFirstTerminator();
+
+  // Fake a debug loc.
+  DebugLoc DL;
+  if (MBBI != MBB.end())
+    DL = MBBI->getDebugLoc();
+
+  const MachineFunction &MF = *MBB.getParent();
+  const RISCVSubtarget &STI = MF.getSubtarget<RISCVSubtarget>();
+  const RISCVRegisterInfo &TRI = STI.getTargetABI();
+  const RISCVInstrInfo &TII = *STI.getInstrInfo();
+
+  for (MCRegister Reg : RegsToZero.set_bits()) {
+    if (TRI.isGeneralPurposeRegister(MF, Reg))
+      TII.buildClearRegister(Reg, MBB, MBBI, DL);
+  }
+}
+
 StackOffset
 RISCVFrameLowering::getFrameIndexReference(const MachineFunction &MF, int FI,
                                            Register &FrameReg) const {
diff --git a/llvm/lib/Target/RISCV/RISCVFrameLowering.h b/llvm/lib/Target/RISCV/RISCVFrameLowering.h
index 84e48dbc05d67..496af9b9fd9cc 100644
--- a/llvm/lib/Target/RISCV/RISCVFrameLowering.h
+++ b/llvm/lib/Target/RISCV/RISCVFrameLowering.h
@@ -115,6 +115,10 @@ class RISCVFrameLowering : public TargetFrameLowering {
                                    const DebugLoc &DL, int64_t Amount,
                                    MachineInstr::MIFlag Flag, bool EmitCFI,
                                    bool DynAllocation) const;
+
+  /// Emit target zero call-used regs.
+  void emitZeroCallUsedRegs(BitVector RegsToZero,
+                            MachineBasicBlock &MBB) const override;
 };
 } // namespace llvm
 #endif
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
index 15024404b05ca..94722a28b2241 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
@@ -3939,6 +3939,22 @@ MachineBasicBlock::iterator RISCVInstrInfo::insertOutlinedCall(
   return It;
 }
 
+void RISCVInstrInfo::buildClearRegister(Register Reg, MachineBasicBlock &MBB,
+                                        MachineBasicBlock::iterator Iter,
+                                        DebugLoc &DL,
+                                        bool AllowSideEffects) const {
+
+  const MachineFunction &MF = *MBB.getParent();
+  const RISCVSubtarget &STI = MF.getSubtarget<RISCVSubtarget>();
+  const RISCVRegisterInfo &TRI = *STI.getRegisterInfo();
+
+  if (TRI.isGeneralPurposeRegister(MF, Reg)) {
+    BuildMI(MBB, Iter, DL, get(RISCV::PseudoLI), Reg).addImm(0);
+  } else {
+    llvm_unreachable("Implement buildClearRegister for non-GPR registers");
+  }
+}
+
 std::optional<RegImmPair> RISCVInstrInfo::isAddImmediate(const MachineInstr &MI,
                                                          Register Reg) const {
   // TODO: Handle cases where Reg is a super- or sub-register of the
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfo.h b/llvm/lib/Target/RISCV/RISCVInstrInfo.h
index b11f290c8c107..e816f862301a0 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfo.h
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfo.h
@@ -256,6 +256,10 @@ class RISCVInstrInfo : public RISCVGenInstrInfo {
                      MachineBasicBlock::iterator &It, MachineFunction &MF,
                      outliner::Candidate &C) const override;
 
+  void buildClearRegister(Register Reg, MachineBasicBlock &MBB,
+                          MachineBasicBlock::iterator Iter, DebugLoc &DL,
+                          bool AllowSideEffects = true) const override;
+
   std::optional<RegImmPair> isAddImmediate(const MachineInstr &MI,
                                            Register Reg) const override;
 
diff --git a/llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp b/llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp
index 6baa30cf9e6f6..520a893b5fe23 100644
--- a/llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp
@@ -809,6 +809,16 @@ Register RISCVRegisterInfo::getFrameRegister(const MachineFunction &MF) const {
   return TFI->hasFP(MF) ? RISCV::X8 : RISCV::X2;
 }
 
+bool RISCVRegisterInfo::isArgumentRegister(const MachineFunction &MF,
+                                           MCRegister Reg) const {
+  auto const &STI = MF.getSubtarget<RISCVSubtarget>();
+  if (!STI.getRegisterInfo()->isGeneralPurposeRegister(MF, Reg))
+    llvm_unreachable("Implement isArgumentRegister for non-GPR registers");
+
+  return llvm::any_of(RISCV::getArgGPRs(STI.getTargetABI()),
+                      [&](MCPhysReg R) { return Reg == R; });
+}
+
 StringRef RISCVRegisterInfo::getRegAsmName(MCRegister Reg) const {
   if (Reg == RISCV::SF_VCIX_STATE)
     return "sf.vcix_state";
diff --git a/llvm/lib/Target/RISCV/RISCVRegisterInfo.h b/llvm/lib/Target/RISCV/RISCVRegisterInfo.h
index 3a77820d28bbd..df203dc073786 100644
--- a/llvm/lib/Target/RISCV/RISCVRegisterInfo.h
+++ b/llvm/lib/Target/RISCV/RISCVRegisterInfo.h
@@ -125,6 +125,9 @@ struct RISCVRegisterInfo : public RISCVGenRegisterInfo {
 
   Register getFrameRegister(const MachineFunction &MF) const override;
 
+  bool isArgumentRegister(const MachineFunction &MF,
+                          MCRegister Reg) const override;
+
   StringRef getRegAsmName(MCRegister Reg) const override;
 
   bool requiresRegisterScavenging(const MachineFunction &MF) const override {
diff --git a/llvm/lib/Target/RISCV/RISCVRegisterInfo.td b/llvm/lib/Target/RISCV/RISCVRegisterInfo.td
index d3a387d57b338..7338edca1947a 100644
--- a/llvm/lib/Target/RISCV/RISCVRegisterInfo.td
+++ b/llvm/lib/Target/RISCV/RISCVRegisterInfo.td
@@ -1012,3 +1012,11 @@ def MR : RISCVRegisterClass<[i8, v8i1], 8,
 }
 
 def MR0 : RISCVRegisterClass<[i8, v8i1], 8, (add M0)>;
+
+//===----------------------------------------------------------------------===//
+// Register categories.
+//
+
+def GeneralPurposeRegisters : RegisterCategory<[GPR]>;
+
+def FixedRegisters : RegisterCategory<[GPRX0]>;
diff --git a/llvm/test/CodeGen/RISCV/zero-call-used-regs.ll b/llvm/test/CodeGen/RISCV/zero-call-used-regs.ll
new file mode 100644
index 0000000000000..e962f3fe6724c
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/zero-call-used-regs.ll
@@ -0,0 +1,225 @@
+; RUN: llc < %s -verify-machineinstrs -mtriple=riscv64-unknown-unknown | FileCheck %s
+; RUN: llc < %s -verify-machineinstrs -mtriple=riscv32-unknown-unknown | FileCheck %s
+
+target triple = "riscv64-unknown-linux-gnu"
+
+define dso_local i32 @skip(i32 noundef %a, i32 noundef %b, i32 noundef %c) local_unnamed_addr #0 "zero-call-used-regs"="skip" {
+; CHECK-LABEL: skip:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    mul{{w?}} a0, a1, a0
+; CHECK-NEXT:    or a0, a0, a2
+; CHECK-NEXT:    ret
+
+entry:
+  %mul = mul nsw i32 %b, %a
+  %or = or i32 %mul, %c
+  ret i32 %or
+}
+
+define dso_local i32 @used_gpr_arg(i32 noundef %a, i32 noundef %b, i32 noundef %c) local_unnamed_addr #0 noinline optnone "zero-call-used-regs"="used-gpr-arg" {
+; CHECK-LABEL: used_gpr_arg:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    mul{{w?}} a0, a1, a0
+; CHECK-NEXT:    or a0, a0, a2
+; CHECK-NEXT:    li a1, 0
+; CHECK-NEXT:    li a2, 0
+; CHECK-NEXT:    ret
+
+entry:
+  %mul = mul nsw i32 %b, %a
+  %or = or i32 %mul, %c
+  ret i32 %or
+}
+
+define dso_local i32 @used_gpr(i32 noundef %a, i32 noundef %b, i32 noundef %c) local_unnamed_addr #0 noinline optnone "zero-call-used-regs"="used-gpr" {
+; CHECK-LABEL: used_gpr:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    mul{{w?}} a0, a1, a0
+; CHECK-NEXT:    or a0, a0, a2
+; CHECK-NEXT:    li a1, 0
+; CHECK-NEXT:    li a2, 0
+; CHECK-NEXT:    ret
+
+entry:
+  %mul = mul nsw i32 %b, %a
+  %or = or i32 %mul, %c
+  ret i32 %or
+}
+
+define dso_local i32 @used_arg(i32 noundef %a, i32 noundef %b, i32 noundef %c) local_unnamed_addr #0 noinline optnone "zero-call-used-regs"="used-arg" {
+; CHECK-LABEL: used_arg:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    mul{{w?}} a0, a1, a0
+; CHECK-NEXT:    or a0, a0, a2
+; CHECK-NEXT:    li a1, 0
+; CHECK-NEXT:    li a2, 0
+; CHECK-NEXT:    ret
+
+entry:
+  %mul = mul nsw i32 %b, %a
+  %or = or i32 %mul, %c
+  ret i32 %or
+}
+
+define dso_local i32 @used(i32 noundef %a, i32 noundef %b, i32 noundef %c) local_unnamed_addr #0 noinline optnone "zero-call-used-regs"="used" {
+; CHECK-LABEL: used:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    mul{{w?}} a0, a1, a0
+; CHECK-NEXT:    or a0, a0, a2
+; CHECK-NEXT:    li a1, 0
+; CHECK-NEXT:    li a2, 0
+; CHECK-NEXT:    ret
+
+entry:
+  %mul = mul nsw i32 %b, %a
+  %or = or i32 %mul, %c
+  ret i32 %or
+}
+
+define dso_local i32 @all_gpr_arg(i32 noundef %a, i32 noundef %b, i32 noundef %c) local_unnamed_addr #0 "zero-call-used-regs"="all-gpr-arg" {
+; CHECK-LABEL: all_gpr_arg:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    mul{{w?}} a0, a1, a0
+; CHECK-NEXT:    or a0, a0, a2
+; CHECK-NEXT:    li a1, 0
+; CHECK-NEXT:    li a2, 0
+; CHECK-NEXT:    li a3, 0
+; CHECK-NEXT:    li a4, 0
+; CHECK-NEXT:    li a5, 0
+; CHECK-NEXT:    li a6, 0
+; CHECK-NEXT:    li a7, 0
+; CHECK-NEXT:    ret
+
+entry:
+  %mul = mul nsw i32 %b, %a
+  %or = or i32 %mul, %c
+  ret i32 %or
+}
+
+define dso_local i32 @all_gpr(i32 noundef %a, i32 noundef %b, i32 noundef %c) local_unnamed_addr #0 "zero-call-used-regs"="all-gpr" {
+; CHECK-LABEL: all_gpr:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    mul{{w?}} a0, a1, a0
+; CHECK-NEXT:    or a0, a0, a2
+; CHECK-NEXT:    li t0, 0
+; CHECK-NEXT:    li t1, 0
+; CHECK-NEXT:    li t2, 0
+; CHECK-NEXT:    li a1, 0
+; CHECK-NEXT:    li a2, 0
+; CHECK-NEXT:    li a3, 0
+; CHECK-NEXT:    li a4, 0
+; CHECK-NEXT:    li a5, 0
+; CHECK-NEXT:    li a6, 0
+; CHECK-NEXT:    li a7, 0
+; CHECK-NEXT:    li t3, 0
+; CHECK-NEXT:    li t4, 0
+; CHECK-NEXT:    li t5, 0
+; CHECK-NEXT:    li t6, 0
+; CHECK-NEXT:    ret
+
+entry:
+  %mul = mul nsw i32 %b, %a
+  %or = or i32 %mul, %c
+  ret i32 %or
+}
+
+define dso_local double @skip_float(double noundef %a, float noundef %b) local_unnamed_addr #0 "zero-call-used-regs"="skip" {
+; CHECK-LABEL: skip_float:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    fcvt.d.s fa5, fa1
+; CHECK-NEXT:    fmul.d	fa0, fa5, fa0
+; CHECK-NEXT:    ret
+
+entry:
+  %conv = fpext float %b to double
+  %mul = fmul double %conv, %a
+  ret double %mul
+}
+
+define dso_local double @used_gpr_arg_float(double noundef %a, float noundef %b) local_unnamed_addr #0 noinline optnone "zero-call-used-regs"="used-gpr-arg" {
+; CHECK-LABEL: used_gpr_arg_float:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    fcvt.d.s fa5, fa1
+; CHECK-NEXT:    fmul.d	fa0, fa5, fa0
+; CHECK-NEXT:    ret
+
+entry:
+  %conv = fpext float %b to double
+  %mul = fmul double %conv, %a
+  ret double %mul
+}
+
+define dso_local double @used_gpr_float(double noundef %a, float noundef %b) local_unnamed_addr #0 noinline optnone "zero-call-used-regs"="used-gpr" {
+; CHECK-LABEL: used_gpr_float:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    fcvt.d.s fa5, fa1
+; CHECK-NEXT:    fmul.d	fa0, fa5, fa0
+; CHECK-NEXT:    ret
+
+entry:
+  %conv = fpext float %b to double
+  %mul = fmul double %conv, %a
+  ret double %mul
+}
+
+define dso_local double @all_gpr_arg_float(double noundef %a, float noundef %b) local_unnamed_addr #0 noinline optnone "zero-call-used-regs"="all-gpr-arg" {
+; CHECK-LABEL: all_gpr_arg_float:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    fcvt.d.s fa5, fa1
+; CHECK-NEXT:    fmul.d	fa0, fa5, fa0
+; CHECK-NEXT:    li a0, 0
+; CHECK-NEXT:    li a1, 0
+; CHECK-NEXT:    li a2, 0
+; CHECK-NEXT:    li a3, 0
+; CHECK-NEXT:    li a4, 0
+; CHECK-NEXT:    li a5, 0
+; CHECK-NEXT:    li a6, 0
+; CHECK-NEXT:    li a7, 0
+; CHECK-NEXT:    ret
+
+entry:
+  %conv = fpext float %b to double
+  %mul = fmul double %conv, %a
+  ret double %mul
+}
+
+define dso_local double @all_gpr_float(double noundef %a, float noundef %b) local_unnamed_addr #0 noinline optnone "zero-call-used-regs"="all-gpr" {
+; CHECK-LABEL: all_gpr_float:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    fcvt.d.s fa5, fa1
+; CHECK-NEXT:    fmul.d	fa0, fa5, fa0
+; CHECK-NEXT:    li t0, 0
+; CHECK-NEXT:    li t1, 0
+; CHECK-NEXT:    li t2, 0
+; CHECK-NEXT:    li a0, 0
+; CHECK-NEXT:    li a1, 0
+; CHECK-NEXT:    li a2, 0
+; CHECK-NEXT:    li a3, 0
+; CHECK-NEXT:    li a4, 0
+; CHECK-NEXT:    li a5, 0
+; CHECK-NEXT:    li a6, 0
+; CHECK-NEXT:    li a7, 0
+; CHECK-NEXT:    li t3, 0
+; CHECK-NEXT:    li t4, 0
+; CHECK-NEXT:    li t5, 0
+; CHECK-NEXT:    li t6, 0
+; CHECK-NEXT:    ret
+
+entry:
+  %conv = fpext float %b to double
+  %mul = fmul double %conv, %a
+  ret double %mul
+}
+
+; Don't emit zeroing registers in "main" function.
+define dso_local i32 @main() local_unnamed_addr #0 {
+; CHECK-LABEL: main:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    li a0, 0
+; CHECK-NEXT:    ret
+
+entry:
+  ret i32 0
+}
+
+attributes #0 = { mustprogress nofree norecurse nosync nounwind readnone willreturn uwtable "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+m,+f,+d" }

``````````

</details>


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


More information about the cfe-commits mailing list