Repository: llvm/llvm-project
Pull request #133188: [RISCV][Xqccmp] Correctly Parse/Disassemble pushfp
Author: login: lenary
Labels: name: backend:RISC-V
name: llvm:mc
description: Machine (object) code
Created: 2025-03-27T01:04:57Z | Merged: 2025-03-28T00:43:26Z

In the `qc.cm.pushfp` instruction, it is like `cm.pushfp` except in one important way - `qc.cm.pushfp {ra}, -N*16` is not a valid encoding, because this would update `s0`/`fp`/`x8` without saving it.

This change now correctly rejects this variant of the instruction, both during parsing and during disassembly. I also implemented validation for immediates that represent register lists (both kinds), which may help to catch bugs in the future.


Diff:
diff --git a/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp b/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp
index 02cb1b5c9a12b..3c225fb38cb19 100644
--- a/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp
+++ b/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp
@@ -215,7 +215,14 @@ class RISCVAsmParser : public MCTargetAsmParser {
   ParseStatus parseGPRPair(OperandVector &Operands, bool IsRV64Inst);
   ParseStatus parseFRMArg(OperandVector &Operands);
   ParseStatus parseFenceArg(OperandVector &Operands);
-  ParseStatus parseReglist(OperandVector &Operands);
+  ParseStatus parseReglist(OperandVector &Operands) {
+    return parseRegListCommon(Operands, /*MustIncludeS0=*/false);
+  }
+  ParseStatus parseReglistS0(OperandVector &Operands) {
+    return parseRegListCommon(Operands, /*MustIncludeS0=*/true);
+  }
+  ParseStatus parseRegListCommon(OperandVector &Operands, bool MustIncludeS0);
+
   ParseStatus parseRegReg(OperandVector &Operands);
   ParseStatus parseRetval(OperandVector &Operands);
   ParseStatus parseZcmpStackAdj(OperandVector &Operands,
@@ -474,6 +481,9 @@ struct RISCVOperand final : public MCParsedAsmOperand {
   bool isSystemRegister() const { return Kind == KindTy::SystemRegister; }
   bool isRegReg() const { return Kind == KindTy::RegReg; }
   bool isRlist() const { return Kind == KindTy::Rlist; }
+  bool isRlistS0() const {
+    return Kind == KindTy::Rlist && Rlist.Val != RISCVZC::RA;
+  }
   bool isSpimm() const { return Kind == KindTy::Spimm; }
 
   bool isGPR() const {
@@ -2794,9 +2804,15 @@ ParseStatus RISCVAsmParser::parseRegReg(OperandVector &Operands) {
   return ParseStatus::Success;
 }
 
-ParseStatus RISCVAsmParser::parseReglist(OperandVector &Operands) {
+ParseStatus RISCVAsmParser::parseRegListCommon(OperandVector &Operands,
+                                               bool MustIncludeS0) {
   // Rlist: {ra [, s0[-sN]]}
   // XRlist: {x1 [, x8[-x9][, x18[-xN]]]}
+
+  // When MustIncludeS0 = true (not the default) (used for `qc.cm.pushfp`) which
+  // must include `fp`/`s0` in the list:
+  // Rlist: {ra, s0[-sN]}
+  // XRlist: {x1, x8[-x9][, x18[-xN]]}
   SMLoc S = getLoc();
 
   if (parseToken(AsmToken::LCurly, "register list must start with '{'"))
@@ -2814,8 +2830,20 @@ ParseStatus RISCVAsmParser::parseReglist(OperandVector &Operands) {
     return Error(getLoc(), "register list must start from 'ra' or 'x1'");
   getLexer().Lex();
 
-  // parse case like ,s0
-  if (parseOptionalToken(AsmToken::Comma)) {
+  bool SeenComma = parseOptionalToken(AsmToken::Comma);
+
+  // There are two choices here:
+  // - `s0` is not required (usual case), so only try to parse `s0` if there is
+  //   a comma
+  // - `s0` is required (qc.cm.pushfp), and so we must see the comma between
+  //   `ra` and `s0` and must always try to parse `s0`, below
+  if (MustIncludeS0 && !SeenComma) {
+    Error(getLoc(), "register list must include 's0' or 'x8'");
+    return ParseStatus::Failure;
+  }
+
+  // parse case like ,s0 (knowing the comma must be there if required)
+  if (SeenComma) {
     if (getLexer().isNot(AsmToken::Identifier))
       return Error(getLoc(), "invalid register");
     StringRef RegName = getLexer().getTok().getIdentifier();
@@ -2884,6 +2912,8 @@ ParseStatus RISCVAsmParser::parseReglist(OperandVector &Operands) {
 
   auto Encode = RISCVZC::encodeRlist(RegEnd, IsEABI);
   assert(Encode != RISCVZC::INVALID_RLIST);
+  if (MustIncludeS0)
+    assert(Encode != RISCVZC::RA);
   Operands.push_back(RISCVOperand::createRlist(Encode, S));
 
   return ParseStatus::Success;
diff --git a/llvm/lib/Target/RISCV/Disassembler/RISCVDisassembler.cpp b/llvm/lib/Target/RISCV/Disassembler/RISCVDisassembler.cpp
index 46b01417a1ea1..15f9ea0bb86bd 100644
--- a/llvm/lib/Target/RISCV/Disassembler/RISCVDisassembler.cpp
+++ b/llvm/lib/Target/RISCV/Disassembler/RISCVDisassembler.cpp
@@ -507,6 +507,9 @@ static DecodeStatus decodeXTHeadMemPair(MCInst &Inst, uint32_t Insn,
 static DecodeStatus decodeZcmpRlist(MCInst &Inst, uint32_t Imm,
                                     uint64_t Address, const void *Decoder);
 
+static DecodeStatus decodeXqccmpRlistS0(MCInst &Inst, uint32_t Imm,
+                                        uint64_t Address, const void *Decoder);
+
 static DecodeStatus decodeZcmpSpimm(MCInst &Inst, uint32_t Imm,
                                     uint64_t Address, const void *Decoder);
 
@@ -612,7 +615,15 @@ static DecodeStatus decodeXTHeadMemPair(MCInst &Inst, uint32_t Insn,
 
 static DecodeStatus decodeZcmpRlist(MCInst &Inst, uint32_t Imm,
                                     uint64_t Address, const void *Decoder) {
-  if (Imm <= 3)
+  if (Imm < RISCVZC::RA)
+    return MCDisassembler::Fail;
+  Inst.addOperand(MCOperand::createImm(Imm));
+  return MCDisassembler::Success;
+}
+
+static DecodeStatus decodeXqccmpRlistS0(MCInst &Inst, uint32_t Imm,
+                                        uint64_t Address, const void *Decoder) {
+  if (Imm < RISCVZC::RA_S0)
     return MCDisassembler::Fail;
   Inst.addOperand(MCOperand::createImm(Imm));
   return MCDisassembler::Success;
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.cpp b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.cpp
index 1829291cd0348..d5f08ac05f82b 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.cpp
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.cpp
@@ -242,12 +242,12 @@ float RISCVLoadFPImm::getFPImm(unsigned Imm) {
 
 void RISCVZC::printRlist(unsigned SlistEncode, raw_ostream &OS) {
   OS << "{ra";
-  if (SlistEncode > 4) {
+  if (SlistEncode > RISCVZC::RA) {
     OS << ", s0";
-    if (SlistEncode == 15)
+    if (SlistEncode == RISCVZC::RA_S0_S11)
       OS << "-s11";
-    else if (SlistEncode > 5 && SlistEncode <= 14)
-      OS << "-s" << (SlistEncode - 5);
+    else if (SlistEncode > RISCVZC::RA_S0 && SlistEncode <= RISCVZC::RA_S0_S11)
+      OS << "-s" << (SlistEncode - RISCVZC::RA_S0);
   }
   OS << "}";
 }
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h
index db305b0083415..0ede9ba7cf712 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h
@@ -343,6 +343,8 @@ enum OperandType : unsigned {
   OPERAND_RVKRNUM_0_7,
   OPERAND_RVKRNUM_1_10,
   OPERAND_RVKRNUM_2_14,
+  OPERAND_RLIST,
+  OPERAND_RLIST_S0,
   OPERAND_SPIMM,
   // Operand is a 3-bit rounding mode, '111' indicates FRM register.
   // Represents 'frm' argument passing to floating-point operations.
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
index 77d33f2e06871..f333ffe36cbf7 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
@@ -103,6 +103,10 @@ class RISCVMCCodeEmitter : public MCCodeEmitter {
   unsigned getRlistOpValue(const MCInst &MI, unsigned OpNo,
                            SmallVectorImpl<MCFixup> &Fixups,
                            const MCSubtargetInfo &STI) const;
+
+  unsigned getRlistS0OpValue(const MCInst &MI, unsigned OpNo,
+                             SmallVectorImpl<MCFixup> &Fixups,
+                             const MCSubtargetInfo &STI) const;
 };
 } // end anonymous namespace
 
@@ -616,5 +620,16 @@ unsigned RISCVMCCodeEmitter::getRlistOpValue(const MCInst &MI, unsigned OpNo,
   assert(Imm >= 4 && "EABI is currently not implemented");
   return Imm;
 }
+unsigned
+RISCVMCCodeEmitter::getRlistS0OpValue(const MCInst &MI, unsigned OpNo,
+                                      SmallVectorImpl<MCFixup> &Fixups,
+                                      const MCSubtargetInfo &STI) const {
+  const MCOperand &MO = MI.getOperand(OpNo);
+  assert(MO.isImm() && "Rlist operand must be immediate");
+  auto Imm = MO.getImm();
+  assert(Imm >= 4 && "EABI is currently not implemented");
+  assert(Imm != RISCVZC::RA && "Rlist operand must include s0");
+  return Imm;
+}
 
 #include "RISCVGenMCCodeEmitter.inc"
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
index 84e44571a451a..ccb0e8802fbb9 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
@@ -2679,6 +2679,12 @@ bool RISCVInstrInfo::verifyInstruction(const MachineInstr &MI,
         case RISCVOp::OPERAND_RVKRNUM_2_14:
           Ok = Imm >= 2 && Imm <= 14;
           break;
+        case RISCVOp::OPERAND_RLIST:
+          Ok = Imm >= RISCVZC::RA && Imm <= RISCVZC::RA_S0_S11;
+          break;
+        case RISCVOp::OPERAND_RLIST_S0:
+          Ok = Imm >= RISCVZC::RA_S0 && Imm <= RISCVZC::RA_S0_S11;
+          break;
         case RISCVOp::OPERAND_SPIMM:
           Ok = (Imm & 0xf) == 0;
           break;
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoXqccmp.td b/llvm/lib/Target/RISCV/RISCVInstrInfoXqccmp.td
index 5bb9c1e4b228b..bee937f91f46c 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoXqccmp.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoXqccmp.td
@@ -25,6 +25,30 @@
 // Operand and SDNode transformation definitions.
 //===----------------------------------------------------------------------===//
 
+def RlistS0AsmOperand : AsmOperandClass {
+  let Name = "RlistS0";
+  let ParserMethod = "parseReglistS0";
+  let RenderMethod = "addRlistOperands";
+  let DiagnosticType = "InvalidRlistS0";
+  let DiagnosticString = "operand must be {ra, s0[-sN]} or {x1, x8[-x9][, x18[-xN]]}";
+}
+
+def rlist_s0 : RISCVOp<OtherVT> {
+   let ParserMatchClass = RlistS0AsmOperand;
+   let PrintMethod = "printRlist";
+   let DecoderMethod = "decodeXqccmpRlistS0";
+   let EncoderMethod = "getRlistS0OpValue";
+   let MCOperandPredicate = [{
+    int64_t Imm;
+    if (!MCOp.evaluateAsConstantImm(Imm))
+      return false;
+    // 0~4 invalid for `qc.cm.pushfp`
+    return isUInt<4>(Imm) && Imm >= RISCVZC::RA_S0;
+  }];
+
+  string OperandType = "OPERAND_RLIST_S0";
+}
+
 //===----------------------------------------------------------------------===//
 // Instruction Formats
 //===----------------------------------------------------------------------===//
@@ -33,6 +57,20 @@
 // Instruction Class Templates
 //===----------------------------------------------------------------------===//
 
+class RVInstXqccmpCPPPFP<bits<5> funct5, string opcodestr,
+                         DAGOperand immtype = stackadj>
+    : RVInst16<(outs), (ins rlist_s0:$rlist, immtype:$stackadj),
+               opcodestr, "$rlist, $stackadj", [], InstFormatOther> {
+  bits<4> rlist;
+  bits<16> stackadj;
+
+  let Inst{1-0} = 0b10;
+  let Inst{3-2} = stackadj{5-4};
+  let Inst{7-4} = rlist;
+  let Inst{12-8} = funct5;
+  let Inst{15-13} = 0b101;
+}
+
 //===----------------------------------------------------------------------===//
 // Instructions
 //===----------------------------------------------------------------------===//
@@ -59,7 +97,7 @@ def QC_CM_PUSH : RVInstZcCPPP<0b11000, "qc.cm.push", negstackadj>,
                         ReadStoreData, ReadStoreData, ReadStoreData]>;
 
 let hasSideEffects = 0, mayLoad = 0, mayStore = 1, Uses = [X2], Defs = [X2, X8] in
-def QC_CM_PUSHFP : RVInstZcCPPP<0b11001, "qc.cm.pushfp", negstackadj>,
+def QC_CM_PUSHFP : RVInstXqccmpCPPPFP<0b11001, "qc.cm.pushfp", negstackadj>,
                    Sched<[WriteIALU, WriteIALU, ReadIALU, ReadStoreData, ReadStoreData,
                           ReadStoreData, ReadStoreData, ReadStoreData, ReadStoreData,
                           ReadStoreData, ReadStoreData, ReadStoreData, ReadStoreData,
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoZc.td b/llvm/lib/Target/RISCV/RISCVInstrInfoZc.td
index efed74ca8c870..3f90714cdbe88 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoZc.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoZc.td
@@ -58,7 +58,7 @@ def NegStackAdjAsmOperand : AsmOperandClass {
   let RenderMethod = "addSpimmOperands";
 }
 
-def rlist : Operand<OtherVT> {
+def rlist : RISCVOp<OtherVT> {
    let ParserMatchClass = RlistAsmOperand;
    let PrintMethod = "printRlist";
    let DecoderMethod = "decodeZcmpRlist";
@@ -68,8 +68,10 @@ def rlist : Operand<OtherVT> {
     if (!MCOp.evaluateAsConstantImm(Imm))
       return false;
     // 0~3 Reserved for EABI
-    return isUInt<4>(Imm) && Imm >= 4;
+    return isUInt<4>(Imm) && Imm >= RISCVZC::RA;
   }];
+
+  let OperandType = "OPERAND_RLIST";
 }
 
 def stackadj : RISCVOp<OtherVT> {
diff --git a/llvm/test/MC/Disassembler/RISCV/xqccmp-invalid-rlist.txt b/llvm/test/MC/Disassembler/RISCV/xqccmp-invalid-rlist.txt
new file mode 100644
index 0000000000000..fe81c01a037af
--- /dev/null
+++ b/llvm/test/MC/Disassembler/RISCV/xqccmp-invalid-rlist.txt
@@ -0,0 +1,11 @@
+# RUN: not llvm-mc -disassemble -triple=riscv32 -mattr=+experimental-xqccmp %s \
+# RUN:   | FileCheck -check-prefixes=CHECK,CHECK-XQCCMP %s
+
+[0x00,0x00]
+# CHECK: unimp
+
+[0x42,0xb9]
+# CHECK-XQCCMP-NOT: qc.cm.pushfp {ra}, -{{[0-9]+}}
+
+[0x00,0x00]
+# CHECK: unimp
diff --git a/llvm/test/MC/RISCV/rv32xqccmp-invalid.s b/llvm/test/MC/RISCV/rv32xqccmp-invalid.s
index 74f96f076756c..059009de3d830 100644
--- a/llvm/test/MC/RISCV/rv32xqccmp-invalid.s
+++ b/llvm/test/MC/RISCV/rv32xqccmp-invalid.s
@@ -33,3 +33,7 @@ qc.cm.pushfp {ra, s0}, -12
 
 # CHECK-ERROR: error: stack adjustment for register list must be a multiple of 16 bytes in the range [16, 64]
 qc.cm.pop {ra, s0-s1}, -40
+
+# CHECK-ERROR: error: register list must include 's0' or 'x8'
+qc.cm.pushfp {ra}, -16
+
diff --git a/llvm/test/MC/RISCV/rv32xqccmp-valid.s b/llvm/test/MC/RISCV/rv32xqccmp-valid.s
index 5827777e524ca..b1f373ede247d 100644
--- a/llvm/test/MC/RISCV/rv32xqccmp-valid.s
+++ b/llvm/test/MC/RISCV/rv32xqccmp-valid.s
@@ -288,14 +288,6 @@ qc.cm.push {ra, s0-s11}, -112
 # CHECK-ASM: encoding: [0xfe,0xb8]
 qc.cm.push {x1, x8-x9, x18-x27}, -112
 
-# CHECK-ASM-AND-OBJ: qc.cm.pushfp {ra}, -16
-# CHECK-ASM: encoding: [0x42,0xb9]
-qc.cm.pushfp {ra}, -16
-
-# CHECK-ASM-AND-OBJ: qc.cm.pushfp {ra}, -16
-# CHECK-ASM: encoding: [0x42,0xb9]
-qc.cm.pushfp {x1}, -16
-
 # CHECK-ASM-AND-OBJ: qc.cm.pushfp {ra, s0}, -32
 # CHECK-ASM: encoding: [0x56,0xb9]
 qc.cm.pushfp {ra, s0}, -32
diff --git a/llvm/test/MC/RISCV/rv64e-xqccmp-valid.s b/llvm/test/MC/RISCV/rv64e-xqccmp-valid.s
index 8f9e3ce7ee533..003f9852b184a 100644
--- a/llvm/test/MC/RISCV/rv64e-xqccmp-valid.s
+++ b/llvm/test/MC/RISCV/rv64e-xqccmp-valid.s
@@ -72,10 +72,6 @@ qc.cm.push {ra, s0}, -32
 # CHECK-ASM: encoding: [0x62,0xb8]
 qc.cm.push {ra, s0-s1}, -32
 
-# CHECK-ASM-AND-OBJ: qc.cm.pushfp {ra}, -16
-# CHECK-ASM: encoding: [0x42,0xb9]
-qc.cm.pushfp {ra}, -16
-
 # CHECK-ASM-AND-OBJ: qc.cm.pushfp {ra, s0}, -32
 # CHECK-ASM: encoding: [0x56,0xb9]
 qc.cm.pushfp {ra, s0}, -32
diff --git a/llvm/test/MC/RISCV/rv64xqccmp-valid.s b/llvm/test/MC/RISCV/rv64xqccmp-valid.s
index 06ba33fe8a495..29cf6f0dfbdac 100644
--- a/llvm/test/MC/RISCV/rv64xqccmp-valid.s
+++ b/llvm/test/MC/RISCV/rv64xqccmp-valid.s
@@ -148,10 +148,6 @@ qc.cm.push {ra, s0-s11}, -112
 # CHECK-ASM: encoding: [0xf6,0xb8]
 qc.cm.push {ra, s0-s11}, -128
 
-# CHECK-ASM-AND-OBJ: qc.cm.pushfp {ra}, -16
-# CHECK-ASM: encoding: [0x42,0xb9]
-qc.cm.pushfp {ra}, -16
-
 # CHECK-ASM-AND-OBJ: qc.cm.pushfp {ra, s0}, -32
 # CHECK-ASM: encoding: [0x56,0xb9]
 qc.cm.pushfp {ra, s0}, -32


Reviews:
user: login: topperc
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-27T01:42:32Z
user: login: lenary
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T02:02:01Z
user: login: topperc
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-27T02:48:13Z
user: login: lenary
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T18:41:03Z
user: login: topperc
body: LGTM
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-27T20:45:24Z

Review comments:
diff_hunk: @@ -621,6 +625,17 @@ unsigned RISCVMCCodeEmitter::getRlistOpValue(const MCInst &MI, unsigned OpNo,
   return Imm;
 }
 
+unsigned
+RISCVMCCodeEmitter::getRlistS0OpValue(const MCInst &MI, unsigned OpNo,
+                                      SmallVectorImpl<MCFixup> &Fixups,
+                                      const MCSubtargetInfo &STI) const {
+  const MCOperand &MO = MI.getOperand(OpNo);
+  assert(MO.isImm() && "Rlist operand must be immediate");
+  auto Imm = MO.getImm();
+  assert(Imm >= 5 && "EABI is currently not implemented");
path: llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
user: login: topperc
body: Probably should split this assert so the error makes sense. The Zcmp has some note that encodings 0-3 are reserved for future EABI version which is why this assert mentions EABI.
created_at: 2025-03-27T01:42:31Z
author_association: COLLABORATOR
diff_hunk: @@ -621,6 +625,17 @@ unsigned RISCVMCCodeEmitter::getRlistOpValue(const MCInst &MI, unsigned OpNo,
   return Imm;
 }
 
+unsigned
+RISCVMCCodeEmitter::getRlistS0OpValue(const MCInst &MI, unsigned OpNo,
+                                      SmallVectorImpl<MCFixup> &Fixups,
+                                      const MCSubtargetInfo &STI) const {
+  const MCOperand &MO = MI.getOperand(OpNo);
+  assert(MO.isImm() && "Rlist operand must be immediate");
+  auto Imm = MO.getImm();
+  assert(Imm >= 5 && "EABI is currently not implemented");
path: llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
user: login: lenary
body: Done
created_at: 2025-03-27T02:02:01Z
author_association: MEMBER
diff_hunk: @@ -242,11 +242,11 @@ float RISCVLoadFPImm::getFPImm(unsigned Imm) {
 
 void RISCVZC::printRlist(unsigned SlistEncode, raw_ostream &OS) {
   OS << "{ra";
-  if (SlistEncode > 4) {
+  if (SlistEncode > RISCVZC::RA) {
     OS << ", s0";
-    if (SlistEncode == 15)
+    if (SlistEncode == RISCVZC::RA_S0_S11)
       OS << "-s11";
-    else if (SlistEncode > 5 && SlistEncode <= 14)
+    else if (SlistEncode > RISCVZC::RA_S0 && SlistEncode <= RISCVZC::RA_S0_S11)
       OS << "-s" << (SlistEncode - 5);
path: llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.cpp
user: login: topperc
body: `SlistEncode - RISCVZC::RA_S0`
created_at: 2025-03-27T02:48:13Z
author_association: COLLABORATOR
diff_hunk: @@ -242,11 +242,11 @@ float RISCVLoadFPImm::getFPImm(unsigned Imm) {
 
 void RISCVZC::printRlist(unsigned SlistEncode, raw_ostream &OS) {
   OS << "{ra";
-  if (SlistEncode > 4) {
+  if (SlistEncode > RISCVZC::RA) {
     OS << ", s0";
-    if (SlistEncode == 15)
+    if (SlistEncode == RISCVZC::RA_S0_S11)
       OS << "-s11";
-    else if (SlistEncode > 5 && SlistEncode <= 14)
+    else if (SlistEncode > RISCVZC::RA_S0 && SlistEncode <= RISCVZC::RA_S0_S11)
       OS << "-s" << (SlistEncode - 5);
path: llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.cpp
user: login: lenary
body: Done
created_at: 2025-03-27T18:41:03Z
author_association: MEMBER

PR comments:
user: login: llvmbot
created_at: 2025-03-27T01:05:30Z
body: 
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-backend-risc-v

@llvm/pr-subscribers-mc

Author: Sam Elliott (lenary)

<details>
<summary>Changes</summary>

In the `qc.cm.pushfp` instruction, it is like `cm.pushfp` except in one important way - `qc.cm.pushfp {ra}, -N*16` is not a valid encoding, because this would update `s0`/`fp`/`x8` without saving it.

This change now correctly rejects this variant of the instruction, both during parsing and during disassembly. I also implemented validation for immediates that represent register lists (both kinds), which may help to catch bugs in the future.

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


12 Files Affected:

- (modified) llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp (+25-2) 
- (modified) llvm/lib/Target/RISCV/Disassembler/RISCVDisassembler.cpp (+11) 
- (modified) llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h (+2) 
- (modified) llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp (+15) 
- (modified) llvm/lib/Target/RISCV/RISCVInstrInfo.cpp (+6) 
- (modified) llvm/lib/Target/RISCV/RISCVInstrInfoXqccmp.td (+39-1) 
- (modified) llvm/lib/Target/RISCV/RISCVInstrInfoZc.td (+3-1) 
- (added) llvm/test/MC/Disassembler/RISCV/xqccmp-invalid-rlist.txt (+11) 
- (modified) llvm/test/MC/RISCV/rv32xqccmp-invalid.s (+4) 
- (modified) llvm/test/MC/RISCV/rv32xqccmp-valid.s (-8) 
- (modified) llvm/test/MC/RISCV/rv64e-xqccmp-valid.s (-4) 
- (modified) llvm/test/MC/RISCV/rv64xqccmp-valid.s (-4) 


``````````diff
diff --git a/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp b/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp
index 0d40fb5614009..19f28a6287060 100644
--- a/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp
+++ b/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp
@@ -215,7 +215,10 @@ class RISCVAsmParser : public MCTargetAsmParser {
   ParseStatus parseGPRPair(OperandVector &Operands, bool IsRV64Inst);
   ParseStatus parseFRMArg(OperandVector &Operands);
   ParseStatus parseFenceArg(OperandVector &Operands);
+
+  template <bool MustIncludeS0 = false>
   ParseStatus parseReglist(OperandVector &Operands);
+
   ParseStatus parseRegReg(OperandVector &Operands);
   ParseStatus parseRetval(OperandVector &Operands);
   ParseStatus parseZcmpStackAdj(OperandVector &Operands,
@@ -474,6 +477,7 @@ struct RISCVOperand final : public MCParsedAsmOperand {
   bool isSystemRegister() const { return Kind == KindTy::SystemRegister; }
   bool isRegReg() const { return Kind == KindTy::RegReg; }
   bool isRlist() const { return Kind == KindTy::Rlist; }
+  bool isRlistS0() const { return Kind == KindTy::Rlist && Rlist.Val != 4; }
   bool isSpimm() const { return Kind == KindTy::Spimm; }
 
   bool isGPR() const {
@@ -2794,9 +2798,15 @@ ParseStatus RISCVAsmParser::parseRegReg(OperandVector &Operands) {
   return ParseStatus::Success;
 }
 
+template <bool MustIncludeS0>
 ParseStatus RISCVAsmParser::parseReglist(OperandVector &Operands) {
   // Rlist: {ra [, s0[-sN]]}
   // XRlist: {x1 [, x8[-x9][, x18[-xN]]]}
+
+  // When MustIncludeS0 = true (not the default) (used for `qc.cm.pushfp`) which
+  // must include `fp`/`s0` in the list:
+  // Rlist: {ra, s0[-sN]}
+  // XRlist: {x1, x8[-x9][, x18[-xN]]}
   SMLoc S = getLoc();
 
   if (parseToken(AsmToken::LCurly, "register list must start with '{'"))
@@ -2814,8 +2824,19 @@ ParseStatus RISCVAsmParser::parseReglist(OperandVector &Operands) {
     return Error(getLoc(), "register list must start from 'ra' or 'x1'");
   getLexer().Lex();
 
-  // parse case like ,s0
-  if (parseOptionalToken(AsmToken::Comma)) {
+  bool SeenComma = false;
+
+  // If you must include S0, then you need the comma. Fail now if it isn't there
+  if constexpr (MustIncludeS0) {
+    if (parseToken(AsmToken::Comma, "register list must include 's0' or 'x8'"))
+      return ParseStatus::Failure;
+    SeenComma = true;
+  } else {
+    SeenComma = parseOptionalToken(AsmToken::Comma);
+  }
+
+  // parse case like ,s0 - we may have already seen the comma in which case skip
+  if (SeenComma) {
     if (getLexer().isNot(AsmToken::Identifier))
       return Error(getLoc(), "invalid register");
     StringRef RegName = getLexer().getTok().getIdentifier();
@@ -2884,6 +2905,8 @@ ParseStatus RISCVAsmParser::parseReglist(OperandVector &Operands) {
 
   auto Encode = RISCVZC::encodeRlist(RegEnd, IsEABI);
   assert(Encode != RISCVZC::INVALID_RLIST);
+  if constexpr (MustIncludeS0)
+    assert(Encode != RISCVZC::RA);
   Operands.push_back(RISCVOperand::createRlist(Encode, S));
 
   return ParseStatus::Success;
diff --git a/llvm/lib/Target/RISCV/Disassembler/RISCVDisassembler.cpp b/llvm/lib/Target/RISCV/Disassembler/RISCVDisassembler.cpp
index 93cbf662bfa32..d03351db5473b 100644
--- a/llvm/lib/Target/RISCV/Disassembler/RISCVDisassembler.cpp
+++ b/llvm/lib/Target/RISCV/Disassembler/RISCVDisassembler.cpp
@@ -507,6 +507,9 @@ static DecodeStatus decodeXTHeadMemPair(MCInst &Inst, uint32_t Insn,
 static DecodeStatus decodeZcmpRlist(MCInst &Inst, uint32_t Imm,
                                     uint64_t Address, const void *Decoder);
 
+static DecodeStatus decodeXqccmpRlistS0(MCInst &Inst, uint32_t Imm,
+                                        uint64_t Address, const void *Decoder);
+
 static DecodeStatus decodeRegReg(MCInst &Inst, uint32_t Insn, uint64_t Address,
                                  const MCDisassembler *Decoder);
 
@@ -621,6 +624,14 @@ static DecodeStatus decodeZcmpRlist(MCInst &Inst, uint32_t Imm,
   return MCDisassembler::Success;
 }
 
+static DecodeStatus decodeXqccmpRlistS0(MCInst &Inst, uint32_t Imm,
+                                        uint64_t Address, const void *Decoder) {
+  if (Imm <= 4)
+    return MCDisassembler::Fail;
+  Inst.addOperand(MCOperand::createImm(Imm));
+  return MCDisassembler::Success;
+}
+
 static DecodeStatus decodeRegReg(MCInst &Inst, uint32_t Insn, uint64_t Address,
                                  const MCDisassembler *Decoder) {
   uint32_t Rs1 = fieldFromInstruction(Insn, 0, 5);
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h
index db305b0083415..0ede9ba7cf712 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h
@@ -343,6 +343,8 @@ enum OperandType : unsigned {
   OPERAND_RVKRNUM_0_7,
   OPERAND_RVKRNUM_1_10,
   OPERAND_RVKRNUM_2_14,
+  OPERAND_RLIST,
+  OPERAND_RLIST_S0,
   OPERAND_SPIMM,
   // Operand is a 3-bit rounding mode, '111' indicates FRM register.
   // Represents 'frm' argument passing to floating-point operations.
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
index e589e6171d010..6163175347ca0 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
@@ -104,6 +104,10 @@ class RISCVMCCodeEmitter : public MCCodeEmitter {
                            SmallVectorImpl<MCFixup> &Fixups,
                            const MCSubtargetInfo &STI) const;
 
+  unsigned getRlistS0OpValue(const MCInst &MI, unsigned OpNo,
+                             SmallVectorImpl<MCFixup> &Fixups,
+                             const MCSubtargetInfo &STI) const;
+
   unsigned getRegReg(const MCInst &MI, unsigned OpNo,
                      SmallVectorImpl<MCFixup> &Fixups,
                      const MCSubtargetInfo &STI) const;
@@ -621,6 +625,17 @@ unsigned RISCVMCCodeEmitter::getRlistOpValue(const MCInst &MI, unsigned OpNo,
   return Imm;
 }
 
+unsigned
+RISCVMCCodeEmitter::getRlistS0OpValue(const MCInst &MI, unsigned OpNo,
+                                      SmallVectorImpl<MCFixup> &Fixups,
+                                      const MCSubtargetInfo &STI) const {
+  const MCOperand &MO = MI.getOperand(OpNo);
+  assert(MO.isImm() && "Rlist operand must be immediate");
+  auto Imm = MO.getImm();
+  assert(Imm >= 5 && "EABI is currently not implemented");
+  return Imm;
+}
+
 unsigned RISCVMCCodeEmitter::getRegReg(const MCInst &MI, unsigned OpNo,
                                        SmallVectorImpl<MCFixup> &Fixups,
                                        const MCSubtargetInfo &STI) const {
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
index 4fb11f278df97..7867fd7d516cc 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
@@ -2642,6 +2642,12 @@ bool RISCVInstrInfo::verifyInstruction(const MachineInstr &MI,
         case RISCVOp::OPERAND_RVKRNUM_2_14:
           Ok = Imm >= 2 && Imm <= 14;
           break;
+        case RISCVOp::OPERAND_RLIST:
+          Ok = Imm >= RISCVZC::RA && Imm <= RISCVZC::RA_S0_S11;
+          break;
+        case RISCVOp::OPERAND_RLIST_S0:
+          Ok = Imm >= RISCVZC::RA_S0 && Imm <= RISCVZC::RA_S0_S11;
+          break;
         case RISCVOp::OPERAND_SPIMM:
           Ok = (Imm & 0xf) == 0;
           break;
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoXqccmp.td b/llvm/lib/Target/RISCV/RISCVInstrInfoXqccmp.td
index 5bb9c1e4b228b..8cb1cf151f1ad 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoXqccmp.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoXqccmp.td
@@ -25,6 +25,30 @@
 // Operand and SDNode transformation definitions.
 //===----------------------------------------------------------------------===//
 
+def RlistS0AsmOperand : AsmOperandClass {
+  let Name = "RlistS0";
+  let ParserMethod = "parseReglist<true>";
+  let RenderMethod = "addRlistOperands";
+  let DiagnosticType = "InvalidRlistS0";
+  let DiagnosticString = "operand must be {ra, s0[-sN]} or {x1, x8[-x9][, x18[-xN]]}";
+}
+
+def rlist_s0 : RISCVOp<OtherVT> {
+   let ParserMatchClass = RlistS0AsmOperand;
+   let PrintMethod = "printRlist";
+   let DecoderMethod = "decodeXqccmpRlistS0";
+   let EncoderMethod = "getRlistS0OpValue";
+   let MCOperandPredicate = [{
+    int64_t Imm;
+    if (!MCOp.evaluateAsConstantImm(Imm))
+      return false;
+    // 0~4 invalid for `qc.cm.pushfp`
+    return isUInt<4>(Imm) && Imm >= 5;
+  }];
+
+  string OperandType = "OPERAND_RLIST_S0";
+}
+
 //===----------------------------------------------------------------------===//
 // Instruction Formats
 //===----------------------------------------------------------------------===//
@@ -33,6 +57,20 @@
 // Instruction Class Templates
 //===----------------------------------------------------------------------===//
 
+class RVInstXqccmpCPPPFP<bits<5> funct5, string opcodestr,
+                         DAGOperand immtype = stackadj>
+    : RVInst16<(outs), (ins rlist_s0:$rlist, immtype:$stackadj),
+               opcodestr, "$rlist, $stackadj", [], InstFormatOther> {
+  bits<4> rlist;
+  bits<16> stackadj;
+
+  let Inst{1-0} = 0b10;
+  let Inst{3-2} = stackadj{5-4};
+  let Inst{7-4} = rlist;
+  let Inst{12-8} = funct5;
+  let Inst{15-13} = 0b101;
+}
+
 //===----------------------------------------------------------------------===//
 // Instructions
 //===----------------------------------------------------------------------===//
@@ -59,7 +97,7 @@ def QC_CM_PUSH : RVInstZcCPPP<0b11000, "qc.cm.push", negstackadj>,
                         ReadStoreData, ReadStoreData, ReadStoreData]>;
 
 let hasSideEffects = 0, mayLoad = 0, mayStore = 1, Uses = [X2], Defs = [X2, X8] in
-def QC_CM_PUSHFP : RVInstZcCPPP<0b11001, "qc.cm.pushfp", negstackadj>,
+def QC_CM_PUSHFP : RVInstXqccmpCPPPFP<0b11001, "qc.cm.pushfp", negstackadj>,
                    Sched<[WriteIALU, WriteIALU, ReadIALU, ReadStoreData, ReadStoreData,
                           ReadStoreData, ReadStoreData, ReadStoreData, ReadStoreData,
                           ReadStoreData, ReadStoreData, ReadStoreData, ReadStoreData,
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoZc.td b/llvm/lib/Target/RISCV/RISCVInstrInfoZc.td
index efed74ca8c870..04c10220f5bf1 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoZc.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoZc.td
@@ -58,7 +58,7 @@ def NegStackAdjAsmOperand : AsmOperandClass {
   let RenderMethod = "addSpimmOperands";
 }
 
-def rlist : Operand<OtherVT> {
+def rlist : RISCVOp<OtherVT> {
    let ParserMatchClass = RlistAsmOperand;
    let PrintMethod = "printRlist";
    let DecoderMethod = "decodeZcmpRlist";
@@ -70,6 +70,8 @@ def rlist : Operand<OtherVT> {
     // 0~3 Reserved for EABI
     return isUInt<4>(Imm) && Imm >= 4;
   }];
+
+  let OperandType = "OPERAND_RLIST";
 }
 
 def stackadj : RISCVOp<OtherVT> {
diff --git a/llvm/test/MC/Disassembler/RISCV/xqccmp-invalid-rlist.txt b/llvm/test/MC/Disassembler/RISCV/xqccmp-invalid-rlist.txt
new file mode 100644
index 0000000000000..fe81c01a037af
--- /dev/null
+++ b/llvm/test/MC/Disassembler/RISCV/xqccmp-invalid-rlist.txt
@@ -0,0 +1,11 @@
+# RUN: not llvm-mc -disassemble -triple=riscv32 -mattr=+experimental-xqccmp %s \
+# RUN:   | FileCheck -check-prefixes=CHECK,CHECK-XQCCMP %s
+
+[0x00,0x00]
+# CHECK: unimp
+
+[0x42,0xb9]
+# CHECK-XQCCMP-NOT: qc.cm.pushfp {ra}, -{{[0-9]+}}
+
+[0x00,0x00]
+# CHECK: unimp
diff --git a/llvm/test/MC/RISCV/rv32xqccmp-invalid.s b/llvm/test/MC/RISCV/rv32xqccmp-invalid.s
index 74f96f076756c..059009de3d830 100644
--- a/llvm/test/MC/RISCV/rv32xqccmp-invalid.s
+++ b/llvm/test/MC/RISCV/rv32xqccmp-invalid.s
@@ -33,3 +33,7 @@ qc.cm.pushfp {ra, s0}, -12
 
 # CHECK-ERROR: error: stack adjustment for register list must be a multiple of 16 bytes in the range [16, 64]
 qc.cm.pop {ra, s0-s1}, -40
+
+# CHECK-ERROR: error: register list must include 's0' or 'x8'
+qc.cm.pushfp {ra}, -16
+
diff --git a/llvm/test/MC/RISCV/rv32xqccmp-valid.s b/llvm/test/MC/RISCV/rv32xqccmp-valid.s
index 5827777e524ca..b1f373ede247d 100644
--- a/llvm/test/MC/RISCV/rv32xqccmp-valid.s
+++ b/llvm/test/MC/RISCV/rv32xqccmp-valid.s
@@ -288,14 +288,6 @@ qc.cm.push {ra, s0-s11}, -112
 # CHECK-ASM: encoding: [0xfe,0xb8]
 qc.cm.push {x1, x8-x9, x18-x27}, -112
 
-# CHECK-ASM-AND-OBJ: qc.cm.pushfp {ra}, -16
-# CHECK-ASM: encoding: [0x42,0xb9]
-qc.cm.pushfp {ra}, -16
-
-# CHECK-ASM-AND-OBJ: qc.cm.pushfp {ra}, -16
-# CHECK-ASM: encoding: [0x42,0xb9]
-qc.cm.pushfp {x1}, -16
-
 # CHECK-ASM-AND-OBJ: qc.cm.pushfp {ra, s0}, -32
 # CHECK-ASM: encoding: [0x56,0xb9]
 qc.cm.pushfp {ra, s0}, -32
diff --git a/llvm/test/MC/RISCV/rv64e-xqccmp-valid.s b/llvm/test/MC/RISCV/rv64e-xqccmp-valid.s
index 8f9e3ce7ee533..003f9852b184a 100644
--- a/llvm/test/MC/RISCV/rv64e-xqccmp-valid.s
+++ b/llvm/test/MC/RISCV/rv64e-xqccmp-valid.s
@@ -72,10 +72,6 @@ qc.cm.push {ra, s0}, -32
 # CHECK-ASM: encoding: [0x62,0xb8]
 qc.cm.push {ra, s0-s1}, -32
 
-# CHECK-ASM-AND-OBJ: qc.cm.pushfp {ra}, -16
-# CHECK-ASM: encoding: [0x42,0xb9]
-qc.cm.pushfp {ra}, -16
-
 # CHECK-ASM-AND-OBJ: qc.cm.pushfp {ra, s0}, -32
 # CHECK-ASM: encoding: [0x56,0xb9]
 qc.cm.pushfp {ra, s0}, -32
diff --git a/llvm/test/MC/RISCV/rv64xqccmp-valid.s b/llvm/test/MC/RISCV/rv64xqccmp-valid.s
index 06ba33fe8a495..29cf6f0dfbdac 100644
--- a/llvm/test/MC/RISCV/rv64xqccmp-valid.s
+++ b/llvm/test/MC/RISCV/rv64xqccmp-valid.s
@@ -148,10 +148,6 @@ qc.cm.push {ra, s0-s11}, -112
 # CHECK-ASM: encoding: [0xf6,0xb8]
 qc.cm.push {ra, s0-s11}, -128
 
-# CHECK-ASM-AND-OBJ: qc.cm.pushfp {ra}, -16
-# CHECK-ASM: encoding: [0x42,0xb9]
-qc.cm.pushfp {ra}, -16
-
 # CHECK-ASM-AND-OBJ: qc.cm.pushfp {ra, s0}, -32
 # CHECK-ASM: encoding: [0x56,0xb9]
 qc.cm.pushfp {ra, s0}, -32

``````````

</details>

author_association: MEMBER
user: login: lenary
created_at: 2025-03-27T01:11:33Z
body: I templated `parseReglist` because the function seemed too long to copy/paste, but I avoided doing the same for most of the other methods - maybe I should have though? I'm not sure, feedback appreciated.

I may need a declaration somewhere that the `parseRegList` template instantiations actually (need to) exist.
author_association: MEMBER
user: login: topperc
created_at: 2025-03-27T01:38:18Z
body: > I templated `parseReglist` because the function seemed too long to copy/paste, but I avoided doing the same for most of the other methods - maybe I should have though? I'm not sure, feedback appreciated.
> 
> I may need a declaration somewhere that the `parseRegList` template instantiations actually (need to) exist.

Can we have an untemplated `parseRegList` that takes the flag as an argument and make the template function call the untemplate function with the template parameter passed as an argument? That will prevent the function from being duplicated in the binary.
author_association: COLLABORATOR
user: login: lenary
created_at: 2025-03-27T01:50:02Z
body: > > I templated `parseReglist` because the function seemed too long to copy/paste, but I avoided doing the same for most of the other methods - maybe I should have though? I'm not sure, feedback appreciated.
> > I may need a declaration somewhere that the `parseRegList` template instantiations actually (need to) exist.
> 
> Can we have an untemplated `parseRegList` that takes the flag as an argument and make the template function call the untemplate function with the template parameter passed as an argument? That will prevent the function from being duplicated in the binary.

Sure, I guess the size savings of this are better than any savings from `if constexpr`
author_association: MEMBER
user: login: github-actions[bot]
created_at: 2025-03-27T02:01:11Z
body: <!--LLVM CODE FORMAT COMMENT: {clang-format}-->

:white_check_mark: With the latest revision this PR passed the C/C++ code formatter.
author_association: NONE