Skip to content

[RISC-V][MC][RVY] Add support for RVY loads/stores - #177073

Open
arichardson wants to merge 3 commits into
mainfrom
users/arichardson/spr/risc-vmc-add-support-for-rvy-loadsstores
Open

arichardson wants to merge 3 commits into
mainfrom
users/arichardson/spr/risc-vmc-add-support-for-rvy-loadsstores

Conversation

@arichardson

@arichardson arichardson commented Jan 21, 2026 •

Copy link
Copy Markdown
Member

This adds supports for all new RVY loads/stores (capability-wide
versions: ly/sy instructions). Additionally, existing I/Zc* loads and
stores are updated to be mode-dependent, using either a YLEN register or
a XLEN register as the base.
In the former case loads/stores are authorized by that register,
and in the latter (compatibility mode), the loads/stores keep using an
address but are authorized by the DDC CSR. The assembler mnemonics are
the same in both cases. Prior to the standardization process CHERI
assembly used c-prefixed register names for capabilities, so we had the
following syntax:

lw x4, 0(c3) # capability mode: use new `CLW` instruction
lw x4, 0(x3) # integer mode: use existing `LW` instruction

During the standardization this was changed to keep the same register
name in both modes, so now we use lw x4, 0(x3) in both modes.

This allows using the RegClassByHwMode feature to reuse the same MC
instruction but with a different operand type depending on the HwMode.
The downstream fork had duplicated definitions which meant a lot of
switch statements now needed to handle both MCInsts. This approach
using HwMode should be much more maintainable and only introduces a
minor diff compared to what we had downstream.
This will also make it much easier adding support for RVY versions of
other extensions such as vector, since we just need to change out the
GPRMem operand with PtrMem.

This commit also adds assembler-level support for the RVI-compatible
mode of RVY where all base pointers are interpreted as XLEN values
instead of YLEN values. As noted when introducing Zyhybrid support, this
mode selection is as follows:

  • -y, -zyhybrid: Standard RVI (no RVY support, custom-3 is vendor space).
  • +y, -zyhybrid: RVY in Capability Pointer Mode (cannot switch modes).
  • +y, +zyhybrid: RVY in Capability Pointer Mode with Zyhybrid to switch mode.
  • -y, +zyhybrid: RVY in Integral Pointer Mode (RVI compatible, but custom-
    no longer reserved for vendor extension).

@llvmbot

llvmbot commented Jan 21, 2026 •

Copy link
Copy Markdown
Member

@llvm/pr-subscribers-clang-driver

@llvm/pr-subscribers-backend-risc-v

Author: Alexander Richardson (arichardson)

Changes

This adds supports for all new RVY loads/stores (capability-wide
versions: ly/sy instructions). Additionally, for RVY (CHERI), loads and
stores are mode-dependent, using either a YLEN register or a XLEN
register as the base.
In the former case loads/stores are authorized by that register,
and in the latter (compatibility mode), the loads/stores keep using an
address but are authorized by the DDC CSR. The assembler mnemonics are
the same in both cases. Prior to the standardization process CHERI
assembly used c-prefixed register names for capabilities, so we had the
following syntax:

lw x4, 0(c3) # capability mode: use new CLW instruction
lw x4, 0(x3) # integer mode: use existing LW instruction

During the standardization this was changed to keep the same register
name in both modes, so now we have lw x4, 0(x3) in both modes.

This allows using the RegClassByHwMode feature to reuse the same MC
instruction but with a different operand type depending on the HwMode.
The downstream fork had duplicated definitions which meant a lot of
switch statements now needed to handle both MCInsts. This approach
using HwMode should be much more maintainable and only introduces a
minor diff compared to what we had downstream.
This will also make it much easier adding support for RVY versions of
other extensions such as vector, since we just need to change out the
GPRMem operand with PtrMem.


Patch is 27.04 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/177073.diff

11 Files Affected:

  • (modified) llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp (+9-1)
  • (modified) llvm/lib/Target/RISCV/Disassembler/RISCVDisassembler.cpp (+9)
  • (modified) llvm/lib/Target/RISCV/RISCVFeatures.td (+4-2)
  • (modified) llvm/lib/Target/RISCV/RISCVInstrInfo.td (+25-15)
  • (modified) llvm/lib/Target/RISCV/RISCVInstrInfoY.td (+7)
  • (modified) llvm/lib/Target/RISCV/RISCVInstrInfoZilsd.td (+2-2)
  • (modified) llvm/lib/Target/RISCV/RISCVRegisterInfo.td (+37-25)
  • (modified) llvm/lib/Target/RISCV/RISCVSchedule.td (+2)
  • (added) llvm/test/MC/RISCV/rvy/rvy-invalid-load-store.s (+93)
  • (added) llvm/test/MC/RISCV/rvy/rvy-valid-load-store-64.s (+45)
  • (added) llvm/test/MC/RISCV/rvy/rvy-valid-load-store.s (+118)
diff --git a/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp b/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp
index 6edc942cdb280..556c78dcc5617 100644
--- a/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp
+++ b/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp
@@ -1372,9 +1372,17 @@ unsigned RISCVAsmParser::validateTargetOperandClass(MCParsedAsmOperand &AsmOp,
       RISCVMCRegisterClasses[RISCV::FPR64CRegClassID].contains(Reg);
   bool IsRegVR = RISCVMCRegisterClasses[RISCV::VRRegClassID].contains(Reg);
 
-  if (Op.isGPR() && Kind == MCK_YGPR) {
+  // In RVY mode, the BasePtrRC register class should select capability
+  // registers for the base pointer operands, otherwise we use GPRs.
+  // TODO: Is there any way we could do this in tablegen automatically?
+  if (Kind == MCK_RegByHwMode_BasePtrRC)
+    Kind = STI->hasFeature(RISCV::FeatureCapMode) ? MCK_YGPRNoX0 : MCK_GPR;
+
+  if (Op.isGPR() && (Kind == MCK_YGPR || Kind == MCK_YGPRNoX0)) {
     // GPR and capability GPR use the same register names, convert if required.
     Op.Reg.Reg = convertGPRToYGPR(Reg);
+    if (Kind == MCK_YGPRNoX0 && Op.Reg.Reg == RISCV::X0_Y)
+      return Match_InvalidRegClassYGPRNoNull;
     return Match_Success;
   }
   if (IsRegFPR64 && Kind == MCK_FPR256) {
diff --git a/llvm/lib/Target/RISCV/Disassembler/RISCVDisassembler.cpp b/llvm/lib/Target/RISCV/Disassembler/RISCVDisassembler.cpp
index d894714b03773..9094ef64c7162 100644
--- a/llvm/lib/Target/RISCV/Disassembler/RISCVDisassembler.cpp
+++ b/llvm/lib/Target/RISCV/Disassembler/RISCVDisassembler.cpp
@@ -264,6 +264,15 @@ static DecodeStatus DecodeGPRNoX0RegisterClass(MCInst &Inst, uint32_t RegNo,
   return DecodeGPRRegisterClass(Inst, RegNo, Address, Decoder);
 }
 
+static DecodeStatus DecodeYGPRNoX0RegisterClass(MCInst &Inst, uint32_t RegNo,
+                                                uint64_t Address,
+                                                const MCDisassembler *Decoder) {
+  if (RegNo == 0)
+    return MCDisassembler::Fail;
+
+  return DecodeYGPRRegisterClass(Inst, RegNo, Address, Decoder);
+}
+
 static DecodeStatus DecodeGPRNoX2RegisterClass(MCInst &Inst, uint64_t RegNo,
                                                uint32_t Address,
                                                const MCDisassembler *Decoder) {
diff --git a/llvm/lib/Target/RISCV/RISCVFeatures.td b/llvm/lib/Target/RISCV/RISCVFeatures.td
index b533dfd090ed0..b683662fcf0e2 100644
--- a/llvm/lib/Target/RISCV/RISCVFeatures.td
+++ b/llvm/lib/Target/RISCV/RISCVFeatures.td
@@ -1778,8 +1778,10 @@ def IsRV32 : Predicate<"!Subtarget->is64Bit()">,
              AssemblerPredicate<(all_of (not Feature64Bit)),
                                 "RV32I Base Instruction Set">;
 
-defvar RV32 = DefaultMode;
-def RV64 : HwMode<[IsRV64]>;
+defvar RV32I = DefaultMode;
+def RV64I : HwMode<[IsRV64, NotCapMode]>;
+def RV32Y : HwMode<[IsRV32, IsCapMode]>;
+def RV64Y : HwMode<[IsRV64, IsCapMode]>;
 
 def FeatureRelax
     : SubtargetFeature<"relax", "EnableLinkerRelax", "true",
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfo.td b/llvm/lib/Target/RISCV/RISCVInstrInfo.td
index e8e3691564744..36fe4dfa66610 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfo.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfo.td
@@ -217,7 +217,7 @@ def ZeroOffsetMemOpOperand : AsmOperandClass {
   let ParserMethod = "parseZeroOffsetMemOp";
 }
 
-class MemOperand<RegisterClass regClass> : RegisterOperand<regClass>{
+class MemOperand<RegisterClassLike regClass> : RegisterOperand<regClass> {
   let OperandType = "OPERAND_MEMORY";
 }
 
@@ -227,6 +227,15 @@ def GPRMemZeroOffset : MemOperand<GPR> {
 }
 
 def GPRMem : MemOperand<GPR>;
+// Note: For RVY, the encodings using x0/zero (a null capability) as the base
+// are reserved. Therefore, we use YGPRNoX0 to reject them in the assembler
+// while allowing the encodings for RVI mode.
+def BasePtrRC : RegClassByHwMode<
+  [RV32I, RV64I, RV32Y, RV64Y],
+  [GPR, GPR, YGPRNoX0, YGPRNoX0]>;
+
+def BasePtr : MemOperand<BasePtrRC>;
+
 
 def SPMem : MemOperand<SP>;
 
@@ -644,8 +653,9 @@ class BranchCC_rri<bits<3> funct3, string opcodestr>
 }
 
 let hasSideEffects = 0, mayLoad = 1, mayStore = 0 in {
-class Load_ri<bits<3> funct3, string opcodestr, DAGOperand rty = GPR>
-    : RVInstI<funct3, OPC_LOAD, (outs rty:$rd), (ins GPRMem:$rs1, simm12_lo:$imm12),
+class Load_ri<bits<3> funct3, string opcodestr, DAGOperand rty = GPR,
+              RISCVOpcode opcode = OPC_LOAD>
+    : RVInstI<funct3, opcode, (outs rty:$rd), (ins BasePtr:$rs1, simm12_lo:$imm12),
               opcodestr, "$rd, ${imm12}(${rs1})">;
 
 class HLoad_r<bits<7> funct7, bits<5> funct5, string opcodestr>
@@ -661,7 +671,7 @@ class HLoad_r<bits<7> funct7, bits<5> funct5, string opcodestr>
 let hasSideEffects = 0, mayLoad = 0, mayStore = 1 in {
 class Store_rri<bits<3> funct3, string opcodestr, DAGOperand rty = GPR>
     : RVInstS<funct3, OPC_STORE, (outs),
-              (ins rty:$rs2, GPRMem:$rs1, simm12_lo:$imm12),
+              (ins rty:$rs2, BasePtr:$rs1, simm12_lo:$imm12),
               opcodestr, "$rs2, ${imm12}(${rs1})">;
 
 class HStore_rr<bits<7> funct7, string opcodestr>
@@ -1165,22 +1175,22 @@ let Predicates = [HasStdExtZihintntl] in {
 
 let EmitPriority = 0 in {
 def : InstAlias<"lb $rd, (${rs1})",
-                (LB  GPR:$rd, GPR:$rs1, 0)>;
+                (LB  GPR:$rd, BasePtr:$rs1, 0)>;
 def : InstAlias<"lh $rd, (${rs1})",
-                (LH  GPR:$rd, GPR:$rs1, 0)>;
+                (LH  GPR:$rd, BasePtr:$rs1, 0)>;
 def : InstAlias<"lw $rd, (${rs1})",
-                (LW  GPR:$rd, GPR:$rs1, 0)>;
+                (LW  GPR:$rd, BasePtr:$rs1, 0)>;
 def : InstAlias<"lbu $rd, (${rs1})",
-                (LBU  GPR:$rd, GPR:$rs1, 0)>;
+                (LBU  GPR:$rd, BasePtr:$rs1, 0)>;
 def : InstAlias<"lhu $rd, (${rs1})",
-                (LHU  GPR:$rd, GPR:$rs1, 0)>;
+                (LHU  GPR:$rd, BasePtr:$rs1, 0)>;
 
 def : InstAlias<"sb $rs2, (${rs1})",
-                (SB  GPR:$rs2, GPR:$rs1, 0)>;
+                (SB  GPR:$rs2, BasePtr:$rs1, 0)>;
 def : InstAlias<"sh $rs2, (${rs1})",
-                (SH  GPR:$rs2, GPR:$rs1, 0)>;
+                (SH  GPR:$rs2, BasePtr:$rs1, 0)>;
 def : InstAlias<"sw $rs2, (${rs1})",
-                (SW  GPR:$rs2, GPR:$rs1, 0)>;
+                (SW  GPR:$rs2, BasePtr:$rs1, 0)>;
 
 def : InstAlias<"add $rd, $rs1, $imm12",
                 (ADDI  GPR:$rd, GPR:$rs1, simm12_lo:$imm12)>;
@@ -1198,11 +1208,11 @@ def : InstAlias<"sra $rd, $rs1, $shamt",
                 (SRAI  GPR:$rd, GPR:$rs1, uimmlog2xlen:$shamt)>;
 let Predicates = [IsRV64] in {
 def : InstAlias<"lwu $rd, (${rs1})",
-                (LWU  GPR:$rd, GPR:$rs1, 0)>;
+                (LWU  GPR:$rd, BasePtr:$rs1, 0)>;
 def : InstAlias<"ld $rd, (${rs1})",
-                (LD  GPR:$rd, GPR:$rs1, 0)>;
+                (LD  GPR:$rd, BasePtr:$rs1, 0)>;
 def : InstAlias<"sd $rs2, (${rs1})",
-                (SD  GPR:$rs2, GPR:$rs1, 0)>;
+                (SD  GPR:$rs2, BasePtr:$rs1, 0)>;
 
 def : InstAlias<"addw $rd, $rs1, $imm12",
                 (ADDIW  GPR:$rd, GPR:$rs1, simm12_lo:$imm12)>;
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoY.td b/llvm/lib/Target/RISCV/RISCVInstrInfoY.td
index 08565a6f906e8..bb9b252367b4a 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoY.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoY.td
@@ -105,4 +105,11 @@ let Predicates = [HasStdExtY] in {
   def SYEQ : RVY_rr<0x6, 0x4, "syeq", GPR, YGPR, YGPR>;
   def YLT : RVY_rr<0x6, 0x6, "ylt", GPR, YGPR, YGPR>;
   def YAMASK : RVY_r<0x7, "yamask", GPR, GPR>;
+
+  // Instructions to Load and Store Capability Data
+  def LY : Load_ri<0b100, "ly", YGPR, opcode=OPC_MISC_MEM>,
+           Sched<[/* TODO: WriteLDY, */ ReadMemBase]>;
+  def SY : Store_rri<0b100, "sy", YGPR>,
+           Sched<[/* TODO: WriteSTY, */ ReadStoreData, ReadMemBase]>;
+
 } // Predicates = [HasStdExtY]
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoZilsd.td b/llvm/lib/Target/RISCV/RISCVInstrInfoZilsd.td
index 4fc859f2547c1..0438ff4af6d8d 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoZilsd.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoZilsd.td
@@ -64,6 +64,6 @@ def PseudoSD_RV32_OPT :
   let mayStore = 1;
 }
 
-def : InstAlias<"ld $rd, (${rs1})", (LD_RV32 GPRPairRV32:$rd, GPR:$rs1, 0), 0>;
-def : InstAlias<"sd $rs2, (${rs1})", (SD_RV32 GPRPairRV32:$rs2, GPR:$rs1, 0), 0>;
+def : InstAlias<"ld $rd, (${rs1})", (LD_RV32 GPRPairRV32:$rd, BasePtr:$rs1, 0), 0>;
+def : InstAlias<"sd $rs2, (${rs1})", (SD_RV32 GPRPairRV32:$rs2, BasePtr:$rs1, 0), 0>;
 }
diff --git a/llvm/lib/Target/RISCV/RISCVRegisterInfo.td b/llvm/lib/Target/RISCV/RISCVRegisterInfo.td
index c6be45b8dfaa6..5fb69d7bcf80d 100644
--- a/llvm/lib/Target/RISCV/RISCVRegisterInfo.td
+++ b/llvm/lib/Target/RISCV/RISCVRegisterInfo.td
@@ -55,8 +55,9 @@ class RISCVReg128<RISCVReg64 subreg>
 // A subreg index for the address part of capability registers (this is just the
 // default XLEN-wide X<N> register).
 def sub_cap_addr : SubRegIndex<32> {
-  let SubRegRanges =
-      SubRegRangeByHwMode<[RV32, RV64], [SubRegRange<32>, SubRegRange<64>]>;
+  let SubRegRanges = SubRegRangeByHwMode<
+    [RV32I, RV64I, RV32Y, RV64Y],
+    [SubRegRange<32>, SubRegRange<64>, SubRegRange<32>, SubRegRange<64>]>;
 }
 class RISCVCapReg<RISCVRegWithSubRegs subreg, string n, list<string> alt = []>
     : RISCVRegWithSubRegs<subreg.HWEncoding{4 -0}, n, [subreg], alt> {
@@ -83,12 +84,14 @@ def sub_vrm1_7 : ComposedSubRegIndex<sub_vrm2_3, sub_vrm1_1>;
 
 // GPR sizes change with HwMode.
 def sub_gpr_even : SubRegIndex<32> {
-  let SubRegRanges = SubRegRangeByHwMode<[RV32, RV64],
-                                         [SubRegRange<32>, SubRegRange<64>]>;
+  let SubRegRanges = SubRegRangeByHwMode<
+    [RV32I, RV64I, RV32Y, RV64Y],
+    [SubRegRange<32>, SubRegRange<32>, SubRegRange<64>, SubRegRange<64>]>;
 }
 def sub_gpr_odd  : SubRegIndex<32, 32> {
-  let SubRegRanges = SubRegRangeByHwMode<[RV32, RV64],
-                                         [SubRegRange<32, 32>, SubRegRange<64, 64>]>;
+  let SubRegRanges = SubRegRangeByHwMode<
+    [RV32I, RV64I, RV32Y, RV64Y],
+    [SubRegRange<32, 32>, SubRegRange<32, 32>, SubRegRange<64, 64>, SubRegRange<64, 64>]>;
 }
 } // Namespace = "RISCV"
 
@@ -264,29 +267,34 @@ let RegAltNameIndices = [ABIRegAltName] in {
   }
 }
 
-def XLenVT : ValueTypeByHwMode<[RV32, RV64],
-                               [i32,  i64]>;
+def XLenVT : ValueTypeByHwMode<[RV32I, RV64I, RV32Y, RV64Y],
+                               [i32,   i64,   i32,   i64]>;
 defvar XLenPairVT = untyped;
 
 // Allow f64 in GPR for ZDINX on RV64.
-def XLenFVT : ValueTypeByHwMode<[RV64],
-                                [f64]>;
-def XLenPairFVT : ValueTypeByHwMode<[RV32],
-                                    [f64]>;
+def XLenFVT : ValueTypeByHwMode<[RV64I, RV64Y],
+                                [f64,   f64]>;
+def XLenPairFVT : ValueTypeByHwMode<[RV32I, RV32Y],
+                                    [f64,   f64]>;
 
 // P extension
-def XLenVecI8VT : ValueTypeByHwMode<[RV32, RV64],
-                                    [v4i8, v8i8]>;
-def XLenVecI16VT : ValueTypeByHwMode<[RV32,  RV64],
-                                     [v2i16, v4i16]>;
-def XLenVecI32VT : ValueTypeByHwMode<[RV64],
-                                     [v2i32]>;
+def XLenVecI8VT : ValueTypeByHwMode<[RV32I, RV64I, RV32Y, RV64Y],
+                                    [v4i8,  v8i8,  v4i8,  v8i8]>;
+def XLenVecI16VT : ValueTypeByHwMode<[RV32I, RV64I, RV32Y, RV64Y],
+                                     [v2i16, v4i16, v2i16, v4i16]>;
+def XLenVecI32VT : ValueTypeByHwMode<[RV64I, RV64Y],
+                                     [v2i32, v2i32]>;
 def XLenRI : RegInfoByHwMode<
-      [RV32,              RV64],
-      [RegInfo<32,32,32>, RegInfo<64,64,64>]>;
-def YLenVT : ValueTypeByHwMode<[RV32, RV64], [c64, c128]>;
-def YLenRI : RegInfoByHwMode<[RV32, RV64], [RegInfo<64, 64, 64>,
-                                            RegInfo<128, 128, 128>]>;
+      [RV32I,             RV64I,             RV32Y,             RV64Y],
+      [RegInfo<32,32,32>, RegInfo<64,64,64>, RegInfo<32,32,32>, RegInfo<64,64,64>]>;
+def YLenVT : ValueTypeByHwMode<[RV32I, RV64I, RV32Y, RV64Y],
+                               [c64,   c128,  c64,   c128]>;
+def YLenRI : RegInfoByHwMode<
+      [RV32I,               RV64I,                  RV32Y,               RV64Y],
+      [RegInfo<64, 64, 64>, RegInfo<128, 128, 128>, RegInfo<64, 64, 64>, RegInfo<128, 128, 128>]>;
+
+def PtrLenVT : ValueTypeByHwMode<[RV32I, RV64I, RV32Y, RV64Y],
+                                 [i32,   i64,   c64,   c128]>;
 
 class RISCVRegisterClass<list<ValueType> regTypes, int align, dag regList>
     : RegisterClass<"RISCV", regTypes, align, regList> {
@@ -341,6 +349,10 @@ def GPRNoX0 : GPRRegisterClass<(sub GPR, X0)> {
   let DiagnosticType = "InvalidRegClassGPRNoX0";
   let DiagnosticString = "register must be a GPR excluding zero (x0)";
 }
+def YGPRNoX0 : GPRRegisterClass<(sub YGPR, X0_Y)> {
+  let DiagnosticType = "InvalidRegClassYGPRNoNull";
+  let DiagnosticString = GPRNoX0.DiagnosticString;
+}
 
 def GPRNoX2 : GPRRegisterClass<(sub GPR, X2)> {
   let DiagnosticType = "InvalidRegClassGPRNoX2";
@@ -398,8 +410,8 @@ def GPRNoX31 : GPRRegisterClass<(sub GPR, X31)> {
 //===----------------------------------------------------------------------===//
 
 def XLenPairRI : RegInfoByHwMode<
-      [RV32,                RV64],
-      [RegInfo<64, 64, 32>, RegInfo<128, 128, 64>]>;
+      [RV32I,               RV64I,                 RV32Y,               RV64Y],
+      [RegInfo<64, 64, 32>, RegInfo<128, 128, 64>, RegInfo<64, 64, 32>, RegInfo<128, 128, 64>]>;
 
 // Dummy zero register for use in the register pair containing X0 (as X1 is
 // not read to or written when the X0 register pair is used).
diff --git a/llvm/lib/Target/RISCV/RISCVSchedule.td b/llvm/lib/Target/RISCV/RISCVSchedule.td
index dfb4bade9d59c..459e06db6c4a4 100644
--- a/llvm/lib/Target/RISCV/RISCVSchedule.td
+++ b/llvm/lib/Target/RISCV/RISCVSchedule.td
@@ -33,11 +33,13 @@ def WriteLDB        : SchedWrite;    // Load byte
 def WriteLDH        : SchedWrite;    // Load half-word
 def WriteLDW        : SchedWrite;    // Load word
 def WriteLDD        : SchedWrite;    // Load double-word
+// TODO: def WriteLDY        : SchedWrite;    // Load capability
 def WriteCSR        : SchedWrite;    // CSR instructions
 def WriteSTB        : SchedWrite;    // Store byte
 def WriteSTH        : SchedWrite;    // Store half-word
 def WriteSTW        : SchedWrite;    // Store word
 def WriteSTD        : SchedWrite;    // Store double-word
+// TODO: def WriteSTY        : SchedWrite;    // Store capability
 def WriteAtomicB    : SchedWrite;    //Atomic memory operation byte size
 def WriteAtomicH    : SchedWrite;    //Atomic memory operation halfword size
 def WriteAtomicW    : SchedWrite;    //Atomic memory operation word size
diff --git a/llvm/test/MC/RISCV/rvy/rvy-invalid-load-store.s b/llvm/test/MC/RISCV/rvy/rvy-invalid-load-store.s
new file mode 100644
index 0000000000000..32914c5a556d0
--- /dev/null
+++ b/llvm/test/MC/RISCV/rvy/rvy-invalid-load-store.s
@@ -0,0 +1,93 @@
+## For RVY, the loads and stores with the zero register as the base are reserved
+## since this is an always-trapping encoding that may be reused in the future.
+## 1) Check that the assembler rejects zero reg base for capmode but allows it in integer mode.
+## 2) Check that the disassembler with +cap-mode decodes zero reg base as invalid instructions
+# RUN: llvm-mc --triple=riscv32 -mattr=+experimental-y --riscv-no-aliases --show-encoding < %s \
+# RUN:   | FileCheck --check-prefixes=CHECK-ASM,CHECK %s
+# RUN: not llvm-mc --triple=riscv32 -mattr=+experimental-y,+cap-mode --riscv-no-aliases --show-encoding < %s 2>&1 \
+# RUN:   | FileCheck --check-prefixes=ERR-RVY --implicit-check-not=error: %s
+# RUN: llvm-mc --filetype=obj --triple=riscv32 --mattr=+experimental-y -o %t.rv32i.o < %s
+# RUN: llvm-objdump --mattr=+experimental-y -M no-aliases -d --no-print-imm-hex %t.rv32i.o \
+# RUN:   | FileCheck --check-prefixes=CHECK %s
+# RUN: llvm-objdump --mattr=+experimental-y,+cap-mode -M no-aliases -d --no-print-imm-hex %t.rv32i.o \
+# RUN:   | FileCheck --check-prefixes=CHECK-CAP-MODE-DISASM %s
+
+# RUN: llvm-mc --triple=riscv64 --mattr=+experimental-y --riscv-no-aliases --show-encoding --defsym=RV64=1 < %s \
+# RUN:   | FileCheck --check-prefixes=CHECK-ASM,CHECK-ASM-64,CHECK,CHECK-64 %s
+# RUN: not llvm-mc --triple=riscv64 --mattr=+experimental-y,+cap-mode --riscv-no-aliases --show-encoding --defsym=RV64=1 < %s 2>&1 \
+# RUN:   | FileCheck --check-prefixes=ERR-RVY,ERR-RVY-64 --implicit-check-not=error: %s
+# RUN: llvm-mc --filetype=obj --triple=riscv64 --mattr=+experimental-y --riscv-no-aliases --show-encoding --defsym=RV64=1 -o %t.rv64i.o < %s
+# RUN: llvm-objdump --mattr=+experimental-y -M no-aliases -d --no-print-imm-hex %t.rv64i.o \
+# RUN:   | FileCheck --check-prefixes=CHECK,CHECK-64 %s
+# RUN: llvm-objdump --mattr=+experimental-y,+cap-mode -M no-aliases -d --no-print-imm-hex %t.rv64i.o \
+# RUN:   | FileCheck --check-prefixes=CHECK-CAP-MODE-DISASM,CHECK-CAP-MODE-DISASM-64 %s
+
+# CHECK: lb	a0, 16(zero)
+# CHECK-ASM-SAME: # encoding: [0x03,0x05,0x00,0x01]
+# CHECK-CAP-MODE-DISASM: 01000503 <unknown>
+# ERR-RVY: :[[#@LINE+1]]:11: error: register must be a GPR excluding zero (x0)
+lb a0, 16(zero)
+# CHECK-NEXT: sb	a0, 16(zero)
+# CHECK-ASM-SAME: # encoding: [0x23,0x08,0xa0,0x00]
+# CHECK-CAP-MODE-DISASM-NEXT: 00a00823 <unknown>
+# ERR-RVY: :[[#@LINE+1]]:11: error: register must be a GPR excluding zero (x0)
+sb a0, 16(zero)
+# CHECK-NEXT: lbu	a0, 16(zero)
+# CHECK-ASM-SAME: # encoding: [0x03,0x45,0x00,0x01]
+# CHECK-CAP-MODE-DISASM-NEXT: 01004503 <unknown>
+# ERR-RVY: :[[#@LINE+1]]:12: error: register must be a GPR excluding zero (x0)
+lbu a0, 16(zero)
+# CHECK-NEXT: lh	a0, 16(zero)
+# CHECK-ASM-SAME: # encoding: [0x03,0x15,0x00,0x01]
+# CHECK-CAP-MODE-DISASM-NEXT: 01001503 <unknown>
+# ERR-RVY: :[[#@LINE+1]]:11: error: register must be a GPR excluding zero (x0)
+lh a0, 16(zero)
+# CHECK-NEXT: sh	a0, 16(zero)
+# CHECK-ASM-SAME: # encoding: [0x23,0x18,0xa0,0x00]
+# CHECK-CAP-MODE-DISASM-NEXT: 00a01823 <unknown>
+# ERR-RVY: :[[#@LINE+1]]:11: error: register must be a GPR excluding zero (x0)
+sh a0, 16(zero)
+# CHECK-NEXT: lhu	a0, 16(zero)
+# CHECK-ASM-SAME: # encoding: [0x03,0x55,0x00,0x01]
+# CHECK-CAP-MODE-DISASM-NEXT: 01005503 <unknown>
+# ERR-RVY: :[[#@LINE+1]]:12: error: register must be a GPR excluding zero (x0)
+lhu a0, 16(zero)
+# CHECK-NEXT: lw	a0, 16(zero)
+# CHECK-ASM-SAME: # encoding: [0x03,0x25,0x00,0x01]
+# CHECK-CAP-MODE-DISASM-NEXT: 01002503 <unknown>
+# ERR-RVY: :[[#@LINE+1]]:11: error: register must be a GPR excluding zero (x0)
+lw a0, 16(zero)
+# CHECK-NEXT: sw	a0, 16(zero)
+# CHECK-ASM-SAME: # encoding: [0x23,0x28,0xa0,0x00]
+# CHECK-CAP-MODE-DISASM-NEXT: 00a02823 <unknown>
+# ERR-RVY: :[[#@LINE+1]]:11: error: register must be a GPR excluding zero (x0)
+sw a0, 16(zero)
+#
+.ifdef RV64
+# CHECK-64: lwu	a0, 16(zero)
+# CHECK-ASM-64-SAME: # encoding: [0x03,0x65,0x00,0x01]
+# CHECK-CAP-MODE-DISASM-64-NEXT: 01006503 <unknown>
+# ERR-RVY-64: :[[#@LINE+1]]:12: error: register must be a GPR excluding zero (x0)
+lwu a0, 16(zero)
+# CHECK-64: ld	a0, 16(zero)
+# CHECK-ASM-64-SAME: # encoding:  [0x03,0x35,0x00,0x01]
+# CHECK-CAP-MODE-DISASM-64-NEXT: 01003503 <unknown>
+# ERR-RVY-64: :[[#@LINE+1]]:11: error: register must be a GPR excluding zero (x0)
+ld a0, 16(zero)
+# CHECK-64: sd	a0, 16(zero)
+# CHECK-ASM-64-SAME: # encoding: [0x23,0x38,0xa0,0x00]
+# CHECK-CAP-MODE-DISASM-64-NEXT: 00a03823 <unknown>
+# ERR-RVY-64: :[[#@LINE+1]]:11: error: register must be a GPR excluding zero (x0)
+sd a0, 16(zero)
+.endif
+#
+# CHECK-NEXT: ly	a0, 16(zero)
+# CHECK-ASM-SAME: # encoding: [0x0f,0x45,0x00,0x01]
+# CHECK-CAP-MODE-DISASM-NEXT: 0100450f <unknown>
+# ERR-RVY: :[[#@LINE+1]]:11: error: register must be a GPR excluding zero (x0)
+ly a0, 16(zero)
+# CHECK-NEXT: sy	a0, 16(zero)
+# CHECK-ASM-SAME: # encoding: [0x23,0x48,0xa0,0x00]
+# CHECK-CAP-MODE-DISASM-NEXT: 00a04823 <unknown>
+# ERR-RVY: :[[#@LINE+1]]:11: error: register must be a GPR excluding zero (x0)
+sy a0, 16(zero)
diff --git a/llvm/test/MC/RISCV/rvy/rvy-valid-load-store-64.s b/llvm/test/MC/RISCV/rvy/rvy-valid-load-store-64.s
new file mode 100644
index 0000000000000..5567f2b503fe7
--- /dev/null
+++ b/llvm/test/MC/RISCV/rvy/rvy-valid-load-store-64.s
@@ -0,0 +1,45 @@
+# RUN: ...
[truncated]

@github-actions

github-actions Bot commented Jan 21, 2026 •

Copy link
Copy Markdown

🐧 Linux x64 Test Results

  • 181583 tests passed
  • 4282 tests skipped

✅ The build succeeded and all tests passed.

@github-actions

github-actions Bot commented Jan 21, 2026 •

Copy link
Copy Markdown

🪟 Windows x64 Test Results

  • 147117 tests passed
  • 2297 tests skipped

✅ The build succeeded and all tests passed.

@arichardson

Copy link
Copy Markdown
Member Author

Looks like I need to pull some fixes from later in the stack into this pull request first to resolve the VT error. I think I must have accidentally moved them into another commit.

@topperc

topperc commented Jan 21, 2026 •

Copy link
Copy Markdown
Contributor

What about the compressed load/store instructions?

Are FP and vector loads/store and atomics going to be separate PRs?

@arichardson

Copy link
Copy Markdown
Member Author

What about the compressed load/store instructions?

Are FP and vector loads/store and atomics going to be separate PRs?

Yes, I wanted to do one PR per extension to keep the changes smaller. I have a follow-up change for atomics, and will start working on F/D and C shortly.

That said, I could definitely include the compressed versions in this PR if you prefer?

@topperc

topperc commented Jan 22, 2026

Copy link
Copy Markdown
Contributor

What about the compressed load/store instructions?
Are FP and vector loads/store and atomics going to be separate PRs?

Yes, I wanted to do one PR per extension to keep the changes smaller. I have a follow-up change for atomics, and will start working on F/D and C shortly.

That said, I could definitely include the compressed versions in this PR if you prefer?

Let's do the compressed versions of the integer load/stores in this PR so they are in sync. Why is the CompressEmitter letting them be out of sync anyway?

arichardson added a commit to arichardson/upstream-llvm-project that referenced this pull request May 26, 2026
This adds supports for all new RVY loads/stores (capability-wide
versions: ly/sy instructions). Additionally, existing I and Zc* loads and
stores are updated to be mode-dependent, using either a YLEN register or a
XLEN register as the base.
In the former case loads/stores are authorized by that register,
and in the latter (compatibility mode), the loads/stores keep using an
address but are authorized by the DDC CSR. The assembler mnemonics are
the same in both cases. Prior to the standardization process CHERI
assembly used c-prefixed register names for capabilities, so we had the
following syntax:

```
lw x4, 0(c3) # capability mode: use new `CLW` instruction
lw x4, 0(x3) # integer mode: use existing `LW` instruction
```

During the standardization this was changed to keep the same register
name in both modes, so now we use `lw x4, 0(x3)` in both modes.

This allows using the RegClassByHwMode feature to reuse the same MC
instruction but with a different operand type depending on the HwMode.
The downstream fork had duplicated definitions which meant a lot of
switch statements now needed to handle both MCInsts. This approach
using HwMode should be much more maintainable and only introduces a
minor diff compared to what we had downstream.
This will also make it much easier adding support for RVY versions of
other extensions such as vector, since we just need to change out the
`GPRMem` operand with `PtrMem`.

Pull Request: llvm#177073
@arichardson arichardson changed the title [RISC-V][MC] Add support for RVY loads/stores [RISC-V][MC][RVY] Add support for RVY loads/stores May 29, 2026
Comment thread llvm/lib/Target/RISCV/RISCVFeatures.td Resolved
@arichardson

Copy link
Copy Markdown
Member Author

What about the compressed load/store instructions?
Are FP and vector loads/store and atomics going to be separate PRs?

Yes, I wanted to do one PR per extension to keep the changes smaller. I have a follow-up change for atomics, and will start working on F/D and C shortly.
That said, I could definitely include the compressed versions in this PR if you prefer?

Let's do the compressed versions of the integer load/stores in this PR so they are in sync. Why is the CompressEmitter letting them be out of sync anyway?

Updated to include the compressed instructions.

arichardson added a commit that referenced this pull request Jun 2, 2026
…r GPRSize

Map GPRPair register classes to the GPRB register bank during GlobalISel
instruction selection. This is required because the introduction of HwMode-dependent
base pointer register classes (e.g. via PtrRegClassByHwMode) causes TableGen to
emit register bank checks for GPRPair variants in RISCVGenGlobalISel.inc.
Without this mapping, instruction selection crashes on unsupported classes.

To avoid assertion failures when GPRB's maximum size increases to 128-bit on RV64
due to the register pairs, update RISCVRegisterBankInfo::getInstrMapping to query
Subtarget.getXLen() for the scalar register width instead of relying on the bank's
getMaximumSize(). This matches AArch64's design pattern of mapping register pairs
(XSeqPairsClass) to GPR and resolving scalar register sizes dynamically.

This was fine previously but was exposed by the HwMode changes in
#177073.

Pull Request: #200510
llvm-sync Bot pushed a commit to arm/arm-toolchain that referenced this pull request Jun 2, 2026
…etXLen() for GPRSize

Map GPRPair register classes to the GPRB register bank during GlobalISel
instruction selection. This is required because the introduction of HwMode-dependent
base pointer register classes (e.g. via PtrRegClassByHwMode) causes TableGen to
emit register bank checks for GPRPair variants in RISCVGenGlobalISel.inc.
Without this mapping, instruction selection crashes on unsupported classes.

To avoid assertion failures when GPRB's maximum size increases to 128-bit on RV64
due to the register pairs, update RISCVRegisterBankInfo::getInstrMapping to query
Subtarget.getXLen() for the scalar register width instead of relying on the bank's
getMaximumSize(). This matches AArch64's design pattern of mapping register pairs
(XSeqPairsClass) to GPR and resolving scalar register sizes dynamically.

This was fine previously but was exposed by the HwMode changes in
llvm/llvm-project#177073.

Pull Request: llvm/llvm-project#200510
llvm-upstreamsync Bot pushed a commit to qualcomm/cpullvm-toolchain that referenced this pull request Jun 2, 2026
…etXLen() for GPRSize

Map GPRPair register classes to the GPRB register bank during GlobalISel
instruction selection. This is required because the introduction of HwMode-dependent
base pointer register classes (e.g. via PtrRegClassByHwMode) causes TableGen to
emit register bank checks for GPRPair variants in RISCVGenGlobalISel.inc.
Without this mapping, instruction selection crashes on unsupported classes.

To avoid assertion failures when GPRB's maximum size increases to 128-bit on RV64
due to the register pairs, update RISCVRegisterBankInfo::getInstrMapping to query
Subtarget.getXLen() for the scalar register width instead of relying on the bank's
getMaximumSize(). This matches AArch64's design pattern of mapping register pairs
(XSeqPairsClass) to GPR and resolving scalar register sizes dynamically.

This was fine previously but was exposed by the HwMode changes in
llvm/llvm-project#177073.

Pull Request: llvm/llvm-project#200510
arichardson added a commit that referenced this pull request Jun 3, 2026
…n::create

This resolves the TODO for LookupRegClassByHwMode support in llvm-exegesis by
passing the MCSubtargetInfo to Instruction::create and calling
getOpRegClassID with the active HwMode.

This also updates MemoryUse test expectations for Mips, as the target now
correctly resolve HwMode-dependent memory register operands (MSA128F16 in
Mips and post-#177073 BasePtrRegClass for RISC-V).

Pull Request: #200525
@arichardson

Copy link
Copy Markdown
Member Author

Now supports Floating point and compressed instructions as well.

@arichardson
arichardson requested a review from topperc June 3, 2026 18:22
llvm-sync Bot pushed a commit to arm/arm-toolchain that referenced this pull request Sep 16, 2026
… DecodeSPRegisterClass and similar. (#223889)

This can avoid adding more explicitly written decode functions for
llvm/llvm-project#177073
anilkund pushed a commit to anilkund/llvm-project that referenced this pull request Sep 16, 2026
This is useful to map from a ByHwMode MCK_* value to the actual resolved
MCK_* type. This is needed for llvm#177073 where we would otherwise have to
add the manual logic to do this remapping (which is the approach I
previously used). Move RegClassByHwModeMatchTable to file scope and
provide a remapRegClassByHwMode() helper function in the generated
AsmMatcher. Currently only used for RISC-V, but I assume other targets
could also make use of this in the future. But even if not, I think it's
cleaner to have this as a helper rather than an inline array inside the
matcher function.

This change was created with the help of AI tools.

Reviewed By: arsenm

Pull Request: llvm#223302
@arichardson

Copy link
Copy Markdown
Member Author

Apologies for the delay in getting back to this. I think the latest version has a much cleaner diff and is hopefully okay now.

nekoshirro pushed a commit to nekoshirro/Alchemist-LLVM that referenced this pull request Sep 20, 2026
…gisterClass and similar. (#223889)

This can avoid adding more explicitly written decode functions for
llvm/llvm-project#177073
Signed-off-by: Hafidz Muzakky <ais.muzakky@gmail.com>
arichardson added a commit to arichardson/upstream-llvm-project that referenced this pull request Oct 10, 2026
This adds support for all new RVY loads/stores (capability-wide
versions: ly/sy instructions). Additionally, existing I/Zc* loads and
stores are updated to be mode-dependent, using either a YLEN register or a
XLEN register as the base.
In the former case loads/stores are authorized by that register,
and in the latter (RVI compatibility mode), the loads/stores keep using an
address but are authorized by the DDC CSR. The assembler mnemonics are
the same in both cases. Prior to the standardization process CHERI
assembly used c-prefixed register names for capabilities, so we had the
following syntax:

```
lw x4, 0(c3) # capability mode: use new `CLW` instruction
lw x4, 0(x3) # integer mode: use existing `LW` instruction
```

During the standardization this was changed to keep the same register
name in both modes, so now we use `lw x4, 0(x3)` in both modes.

This allows using the RegClassByHwMode feature to reuse the same MC
instruction but with a different operand type depending on the HwMode.
The downstream fork had duplicated definitions which meant a lot of
switch statements now needed to handle both MCInsts. This approach
using HwMode should be much more maintainable and only introduces a
minor diff compared to what we had downstream.
This will also make it much easier adding support for RVY versions of
other extensions such as vector, since we just need to change `BasePtr`
to use `BasePtrRegClass`.

Pull Request: llvm#177073
@arichardson
arichardson changed the base branch from users/arichardson/spr/main.risc-vmc-add-support-for-rvy-loadsstores to users/arichardson/risc-v-mc-rvy-introduce-support-for-the-zyhybrid-extension October 27, 2026 23:37
@arichardson
arichardson force-pushed the users/arichardson/spr/risc-vmc-add-support-for-rvy-loadsstores branch from ccf2bd8 to 62df10e Compare October 27, 2026 23:37
@arichardson
arichardson added this pull request to stack #236272 October 27, 2026 23:38
@arichardson
arichardson force-pushed the users/arichardson/spr/risc-vmc-add-support-for-rvy-loadsstores branch from 62df10e to fe4aa38 Compare October 27, 2026 23:38
@arichardson
arichardson removed this pull request from stack #236272 November 4, 2026 05:28
@arichardson
arichardson deleted the branch users/arichardson/risc-v-mc-rvy-introduce-support-for-the-zyhybrid-extension November 4, 2026 05:28
Base automatically changed from users/arichardson/risc-v-mc-rvy-introduce-support-for-the-zyhybrid-extension to main November 4, 2026 05:28
@arichardson arichardson closed this Nov 4, 2026
@arichardson arichardson reopened this Nov 4, 2026
@arichardson
arichardson force-pushed the users/arichardson/spr/risc-vmc-add-support-for-rvy-loadsstores branch from fe4aa38 to b0f29d8 Compare November 4, 2026 05:36
@arichardson

This comment was marked as outdated.

@arichardson

Copy link
Copy Markdown
Member Author

With #238648 this change has almost zero impact on the size of the generated tables despite doubling the HwModes. Thanks @topperc!

Updated table-size numbers for this PR rebased on top of #238648

### 1. `RISCVGenGlobalISel.inc`

With #238648 applied, RISCVGenGlobalISel.inc shrinks by ~51% on main, and adding RV32Y/RV64Y to XLenRI in this PR adds 0 rules and only +16 bytes to MatchTable0:

Metric main (unpatched) This PR (unpatched) main + #238648 This PR + #238648 Delta of this PR (with #238648)
MatchTable0 size 2,889,759 B 4,472,585 B (+54.77%) 1,415,034 B 1,415,050 B +16 B (+0.001%)
Emitted rules 34,786 69,417 (+99.55%) 17,927 17,927 +0 rules
GIM_*CheckFeatures 34,782 69,413 (+99.57%) 17,740 17,748 +8
LLTByHwMode indices — — 18 (0..17) 18 (0..17) +0 indices
.inc file size 89.04 MB 151.71 MB (+70.37%) 45.55 MB 47.48 MB +1.93 MB (rule comments only)

2. RISCVGenDAGISel.inc and RISCVGenAsmMatcher.inc

File / Table main This PR Delta
RISCVGenDAGISel.inc MatcherTable 1,224,786 B 1,227,296 B +2,510 B (+0.20%)
RISCVGenDAGISel.inc OperandLists 1,002 B 1,002 B +0 B
RISCVGenAsmMatcher.inc MatchTable0 99,456 B (3,552 entries) 99,736 B (3,562 entries) +280 B (+10 entries, +0.28%)
RISCVGenAsmMatcher.inc OperandMatchTable 12,756 B (2,126 entries) 12,756 B (2,126 entries) +0 B

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend:RISC-V clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl'

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants