Skip to content

[RISC-V][MC] Introduce initial support for RVY (CHERI) - #176871

Closed
arichardson wants to merge 10000 commits into
users/arichardson/spr/main.risc-vmc-support-rvy-auipc-instructionfrom
users/arichardson/spr/risc-vmc-introduce-initial-support-rvy-cheri
Closed

arichardson wants to merge 10000 commits into
users/arichardson/spr/main.risc-vmc-support-rvy-auipc-instructionfrom
users/arichardson/spr/risc-vmc-introduce-initial-support-rvy-cheri

Conversation

@arichardson

@arichardson arichardson commented Jan 20, 2026 •

Copy link
Copy Markdown
Member

This adds MC-level support for most of the base Y extension instructions
using version 0.9.8.2 of the specification. This initial pull request is
restricted to the execution-mode-independent subset. The Y extension
(CHERI for RISC-V) also introduces an execution mode that determines
whether certain register operands use the full extended register or only
the address subset (the current XLEN registers). The instructions that
depend on execution mode (loads/stores/jumps + AUIPC) will be added in
the following commits in this stack of changes.

Specification: https://github.com/riscv/riscv-cheri/releases/tag/v0.9.8.1
Co-authored-by: Jessica Clarke jrtc27@jrtc27.com
Co-authored-by: Alexander Richardson alexrichardson@google.com
Co-authored-by: Petr Vesely petr.vesely@codasip.com

@llvmbot

llvmbot commented Jan 20, 2026

Copy link
Copy Markdown
Member

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

Author: Alexander Richardson (arichardson)

Changes

This adds MC-level support for most of the base Y extension instructions,
restricted to the execution-mode-independent subset. The Y extension
(CHERI for RISC-V) also introduces an execution mode that determines
whether certain register operands use the full extended register or only
the address subset (the current XLEN registers). The instructions that
depend on execution mode (loads/stores/jumps + AUIPC) will be added in
the following commits in this stack of changes.

Co-authored-by: Jessica Clarke <jrtc27@jrtc27.com>
Co-authored-by: Alexander Richardson <alexrichardson@google.com>
Co-authored-by: Petr Vesely <petr.vesely@codasip.com>


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

9 Files Affected:

  • (modified) llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp (+55)
  • (modified) llvm/lib/Target/RISCV/Disassembler/RISCVDisassembler.cpp (+39)
  • (modified) llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h (+1)
  • (modified) llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp (+29)
  • (added) llvm/lib/Target/RISCV/RISCVInstrFormatsY.td (+77)
  • (modified) llvm/lib/Target/RISCV/RISCVInstrInfo.td (+3)
  • (added) llvm/lib/Target/RISCV/RISCVInstrInfoY.td (+108)
  • (added) llvm/test/MC/RISCV/rvy/rvy-invalid-mode-independent.s (+42)
  • (added) llvm/test/MC/RISCV/rvy/rvy-valid-mode-independent.s (+242)
diff --git a/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp b/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp
index e9d72aceb0e88..6edc942cdb280 100644
--- a/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp
+++ b/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp
@@ -488,6 +488,11 @@ struct RISCVOperand final : public MCParsedAsmOperand {
            RISCVMCRegisterClasses[RISCV::GPRRegClassID].contains(Reg.Reg);
   }
 
+  bool isYGPR() const {
+    return Kind == KindTy::Register &&
+           RISCVMCRegisterClasses[RISCV::YGPRRegClassID].contains(Reg.Reg);
+  }
+
   bool isGPRPair() const {
     return Kind == KindTy::Register &&
            RISCVMCRegisterClasses[RISCV::GPRPairRegClassID].contains(Reg.Reg);
@@ -762,6 +767,11 @@ struct RISCVOperand final : public MCParsedAsmOperand {
     });
   }
 
+  bool isUImm7Srliy() const {
+    return isUImmPred(
+        [this](int64_t Imm) { return isRV64Expr() ? Imm == 64 : Imm == 32; });
+  }
+
   bool isUImm8GE32() const {
     return isUImmPred([](int64_t Imm) { return isUInt<8>(Imm) && Imm >= 32; });
   }
@@ -794,6 +804,29 @@ struct RISCVOperand final : public MCParsedAsmOperand {
     return IsConstantImm && isInt<N>(fixImmediateForRV32(Imm, isRV64Expr()));
   }
 
+  bool isYBNDSWImm() const {
+    if (!isExpr())
+      return false;
+
+    int64_t Imm;
+    bool IsConstantImm = evaluateConstantExpr(getExpr(), Imm);
+    if (!IsConstantImm)
+      return false;
+    // The immediate is encoded using `((imm[7:0] + 257) << imm[9:8]) - 256`
+    // which gives the following valid ranges:
+    if (Imm < 1)
+      return false;
+    if (Imm <= 256)
+      return true;
+    if (Imm <= 768)
+      return (Imm % 2) == 0;
+    if (Imm <= 1792)
+      return (Imm % 4) == 0;
+    if (Imm <= 3840)
+      return (Imm % 8) == 0;
+    return false;
+  }
+
   template <class Pred> bool isSImmPred(Pred p) const {
     int64_t Imm;
     if (!isExpr())
@@ -1301,6 +1334,11 @@ static MCRegister convertFPR64ToFPR128(MCRegister Reg) {
   return Reg - RISCV::F0_D + RISCV::F0_Q;
 }
 
+static MCRegister convertGPRToYGPR(MCRegister Reg) {
+  assert(Reg >= RISCV::X0 && Reg <= RISCV::X31 && "Invalid register");
+  return Reg - RISCV::X0 + RISCV::X0_Y;
+}
+
 static MCRegister convertVRToVRMx(const MCRegisterInfo &RI, MCRegister Reg,
                                   unsigned Kind) {
   unsigned RegClassID;
@@ -1334,6 +1372,11 @@ unsigned RISCVAsmParser::validateTargetOperandClass(MCParsedAsmOperand &AsmOp,
       RISCVMCRegisterClasses[RISCV::FPR64CRegClassID].contains(Reg);
   bool IsRegVR = RISCVMCRegisterClasses[RISCV::VRRegClassID].contains(Reg);
 
+  if (Op.isGPR() && Kind == MCK_YGPR) {
+    // GPR and capability GPR use the same register names, convert if required.
+    Op.Reg.Reg = convertGPRToYGPR(Reg);
+    return Match_Success;
+  }
   if (IsRegFPR64 && Kind == MCK_FPR256) {
     Op.Reg.Reg = convertFPR64ToFPR256(Reg);
     return Match_Success;
@@ -1712,6 +1755,18 @@ bool RISCVAsmParser::matchAndEmitInstruction(SMLoc IDLoc, unsigned &Opcode,
         ErrorLoc,
         "stack adjustment is invalid for this instruction and register list");
   }
+  case Match_InvalidYBNDSWImm: {
+    const SMLoc ErrorLoc = ((RISCVOperand &)*Operands[ErrorInfo]).getStartLoc();
+    return Error(ErrorLoc, "immediate must be an integer in the range "
+                           "[1, 256], a multiple of 2 in the range [258, 768], "
+                           "a multiple of 4 in the range [772, 1792], or "
+                           "a multiple of 8 in the range [1800, 3840]");
+  }
+  case Match_InvalidUImm7Srliy: {
+    const SMLoc ErrorLoc = ((RISCVOperand &)*Operands[ErrorInfo]).getStartLoc();
+    return Error(ErrorLoc, "immediate must be an integer equal to XLEN (" +
+                               Twine(isRV64() ? "64" : "32") + ")");
+  }
   }
 
   if (const char *MatchDiag = getMatchKindDiag((RISCVMatchResultTy)Result)) {
diff --git a/llvm/lib/Target/RISCV/Disassembler/RISCVDisassembler.cpp b/llvm/lib/Target/RISCV/Disassembler/RISCVDisassembler.cpp
index cacb07fb65501..d894714b03773 100644
--- a/llvm/lib/Target/RISCV/Disassembler/RISCVDisassembler.cpp
+++ b/llvm/lib/Target/RISCV/Disassembler/RISCVDisassembler.cpp
@@ -91,6 +91,19 @@ static DecodeStatus DecodeGPRRegisterClass(MCInst &Inst, uint32_t RegNo,
   return MCDisassembler::Success;
 }
 
+static DecodeStatus DecodeYGPRRegisterClass(MCInst &Inst, uint32_t RegNo,
+                                            uint64_t Address,
+                                            const MCDisassembler *Decoder) {
+  bool IsRVE = Decoder->getSubtargetInfo().hasFeature(RISCV::FeatureStdExtE);
+
+  if (RegNo >= 32 || (IsRVE && RegNo >= 16))
+    return MCDisassembler::Fail;
+
+  MCRegister Reg = RISCV::X0_Y + RegNo;
+  Inst.addOperand(MCOperand::createReg(Reg));
+  return MCDisassembler::Success;
+}
+
 static DecodeStatus DecodeGPRF16RegisterClass(MCInst &Inst, uint32_t RegNo,
                                               uint64_t Address,
                                               const MCDisassembler *Decoder) {
@@ -438,6 +451,15 @@ static DecodeStatus DecodeTRM4RegisterClass(MCInst &Inst, uint32_t RegNo,
   return MCDisassembler::Success;
 }
 
+static DecodeStatus DecodeYBNDSWImm(MCInst &Inst, uint64_t Imm, int64_t Address,
+                                    const MCDisassembler *Decoder) {
+  assert(isUInt<10>(Imm) && "Invalid immediate");
+  const uint32_t Shift = Imm >> 8;
+  uint64_t Result = (((Imm & maxUIntN(8)) + 257) << Shift) - 256;
+  Inst.addOperand(MCOperand::createImm(Result));
+  return MCDisassembler::Success;
+}
+
 static DecodeStatus decodeVMaskReg(MCInst &Inst, uint32_t RegNo,
                                    uint64_t Address,
                                    const MCDisassembler *Decoder) {
@@ -519,6 +541,20 @@ static DecodeStatus decodeUImmLog2XLenOperand(MCInst &Inst, uint32_t Imm,
   return MCDisassembler::Success;
 }
 
+static DecodeStatus decodeUImm7SrliyOperand(MCInst &Inst, uint32_t Imm,
+                                            int64_t Address,
+                                            const MCDisassembler *Decoder) {
+  assert(isUInt<7>(Imm) && "Invalid immediate");
+
+  uint32_t ExpectedValue =
+      Decoder->getSubtargetInfo().hasFeature(RISCV::Feature64Bit) ? 64 : 32;
+  if (Imm != ExpectedValue)
+    return MCDisassembler::Fail;
+
+  Inst.addOperand(MCOperand::createImm(Imm));
+  return MCDisassembler::Success;
+}
+
 template <unsigned N>
 static DecodeStatus decodeUImmNonZeroOperand(MCInst &Inst, uint32_t Imm,
                                              int64_t Address,
@@ -727,6 +763,9 @@ static constexpr DecoderListEntry DecoderList32[]{
     {DecoderTableXSMT32, XSMTGroup, "SpacemiT extensions"},
     {DecoderTableXAIF32, XAIFGroup, "AI Foundry extensions"},
     // Standard Extensions
+    {DecoderTableRVYOnly32,
+     {RISCV::FeatureStdExtY},
+     "RVY-only standard 32-bit instructions"},
     {DecoderTable32, {}, "standard 32-bit instructions"},
     {DecoderTableRV32Only32, {}, "RV32-only standard 32-bit instructions"},
     {DecoderTableZfinx32, {}, "Zfinx (Float in Integer)"},
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h
index cda2f95bbef78..e2b6df1e8c1d5 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h
@@ -380,6 +380,7 @@ enum OperandType : unsigned {
   OPERAND_UIMM7,
   OPERAND_UIMM7_LSB00,
   OPERAND_UIMM7_LSB000,
+  OPERAND_UIMM7_SRLIY,
   OPERAND_UIMM8_LSB00,
   OPERAND_UIMM8,
   OPERAND_UIMM8_LSB000,
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
index 5cf1f296d4f72..ef40b8b203ca2 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
@@ -105,6 +105,10 @@ class RISCVMCCodeEmitter : public MCCodeEmitter {
                          SmallVectorImpl<MCFixup> &Fixups,
                          const MCSubtargetInfo &STI) const;
 
+  unsigned getYBNDSWImmOpValue(const MCInst &MI, unsigned OpNo,
+                               SmallVectorImpl<MCFixup> &Fixups,
+                               const MCSubtargetInfo &STI) const;
+
   unsigned getVMaskReg(const MCInst &MI, unsigned OpNo,
                        SmallVectorImpl<MCFixup> &Fixups,
                        const MCSubtargetInfo &STI) const;
@@ -730,6 +734,31 @@ uint64_t RISCVMCCodeEmitter::getImmOpValue(const MCInst &MI, unsigned OpNo,
   return 0;
 }
 
+unsigned
+RISCVMCCodeEmitter::getYBNDSWImmOpValue(const MCInst &MI, unsigned OpNo,
+                                        SmallVectorImpl<MCFixup> &Fixups,
+                                        const MCSubtargetInfo &STI) const {
+  unsigned Imm = getImmOpValue(MI, OpNo, Fixups, STI);
+  // The 10-bit immediate is encoded as `((imm[7:0] + 257) << imm[9:8]) - 256`.
+  if (Imm <= 256) {
+    assert(Imm > 0); // 1, 2, ..., 255, 256
+    return Imm - 1;
+  }
+  if (Imm <= 768) {
+    assert(Imm % 2 == 0); // 258, 260, ..., 766, 768
+    return ((Imm - 258) >> 1) | (1 << 8);
+  }
+  if (Imm <= 1792) {
+    assert(Imm % 4 == 0); // 772, 776, ..., 1788, 1792
+    return ((Imm - 772) >> 2) | (2 << 8);
+  }
+  if (Imm <= 3840) {
+    assert(Imm % 8 == 0); // 1800, 1808, ..., 3832, 3840
+    return ((Imm - 1800) >> 3) | (3 << 8);
+  }
+  llvm_unreachable("Invalid immediate for YBNDSWI");
+}
+
 unsigned RISCVMCCodeEmitter::getVMaskReg(const MCInst &MI, unsigned OpNo,
                                          SmallVectorImpl<MCFixup> &Fixups,
                                          const MCSubtargetInfo &STI) const {
diff --git a/llvm/lib/Target/RISCV/RISCVInstrFormatsY.td b/llvm/lib/Target/RISCV/RISCVInstrFormatsY.td
new file mode 100644
index 0000000000000..cc32d124f515d
--- /dev/null
+++ b/llvm/lib/Target/RISCV/RISCVInstrFormatsY.td
@@ -0,0 +1,77 @@
+//===-- RISCVInstrFormatsY.td --------------------------------*- tablegen -*-=//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+//  This file describes the RISC-V Y extension instruction formats.
+//
+//===----------------------------------------------------------------------===//
+
+//===----------------------------------------------------------------------===//
+// Operand definitions.
+//===----------------------------------------------------------------------===//
+
+def YBNDSWImmOperand : AsmOperandClass {
+  let Name = "YBNDSWImm";
+  let RenderMethod = "addImmOperands";
+  let DiagnosticType = "InvalidYBNDSWImm";
+}
+
+def ybndsw_imm : Operand<XLenVT>, ImmLeaf<XLenVT, [{
+  return (Imm >= 1 && Imm <= 256) ||
+         (Imm >= 258 && Imm <= 768 && (Imm % 2) == 0) ||
+         (Imm >= 772 && Imm <= 1792 && (Imm % 4) == 0) ||
+         (Imm >= 1800 && Imm <= 3840 && (Imm % 8) == 0);
+}]> {
+  let EncoderMethod = "getYBNDSWImmOpValue";
+  let ParserMatchClass = YBNDSWImmOperand;
+  let DecoderMethod = "DecodeYBNDSWImm";
+  let MIOperandInfo = (ops i32imm);
+}
+
+def uimm7_srliy : RISCVUImmOp<7>, ImmLeaf<XLenVT, [{
+  return Subtarget->is64Bit() ? Imm == 64 : Imm == 32;
+}]> {
+  let ParserMatchClass = UImmAsmOperand<7, "Srliy">;
+  let DecoderMethod = "decodeUImm7SrliyOperand";
+  let MCOperandPredicate = [{
+    int64_t Imm;
+    if (!MCOp.evaluateAsConstantImm(Imm))
+      return false;
+    return Subtarget->is64Bit() ? Imm == 64 : Imm == 32;
+  }];
+  let OperandType = "OPERAND_UIMM7_SRLIY";
+}
+
+//===----------------------------------------------------------------------===//
+// Instruction Formats
+//===----------------------------------------------------------------------===//
+
+// Like an RVInstR, except rs2 is now an additional function code.
+class RVYInstSrcDst<bits<7> funct7, bits<5> funct5, bits<3> funct3,
+                    RISCVOpcode opcode, dag outs, dag ins, string opcodestr,
+                    string argstr>
+    : RVInst<outs, ins, opcodestr, argstr, [], InstFormatR> {
+  bits<5> rs1;
+  bits<5> rd;
+
+  let Inst{31 -25} = funct7;
+  let Inst{24 -20} = funct5;
+  let Inst{19 -15} = rs1;
+  let Inst{14 -12} = funct3;
+  let Inst{11 -7} = rd;
+  let Inst{6 -0} = opcode.Value;
+}
+
+class RVYInstSetBoundsImmFmt<dag outs, dag ins, string opcodestr, string argstr>
+    : RVInstIBase<0b011, OPC_OP_IMM_32, outs, ins, opcodestr, argstr> {
+  bits<5> rd;
+  bits<5> rs1;
+  bits<10> imm;
+
+  let Inst{31 -30} = 0b00;
+  let Inst{29 -20} = imm;
+}
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfo.td b/llvm/lib/Target/RISCV/RISCVInstrInfo.td
index c7be5b07c33cc..e8e3691564744 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfo.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfo.td
@@ -2369,6 +2369,9 @@ include "RISCVInstrInfoZicfiss.td"
 // Short Forward Branch
 include "RISCVInstrInfoSFB.td"
 
+// CHERI
+include "RISCVInstrInfoY.td"
+
 //===----------------------------------------------------------------------===//
 // Vendor extensions
 //===----------------------------------------------------------------------===//
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoY.td b/llvm/lib/Target/RISCV/RISCVInstrInfoY.td
new file mode 100644
index 0000000000000..08565a6f906e8
--- /dev/null
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoY.td
@@ -0,0 +1,108 @@
+//===-- RISCVInstrInfoY.td - RISCV instructions -------------*- tblgen-*---===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+//===----------------------------------------------------------------------===//
+// Instruction Formats
+//===----------------------------------------------------------------------===//
+
+include "RISCVInstrFormatsY.td"
+
+//===----------------------------------------------------------------------===//
+// Instruction Class Templates
+//===----------------------------------------------------------------------===//
+
+let hasSideEffects = 0, mayLoad = 0, mayStore = 0 in {
+  class RVY_r<bits<5> funct5, string opcodestr, DAGOperand rdOp = GPR,
+              DAGOperand rs1Op = YGPR>
+      : RVYInstSrcDst<0x8, funct5, 0x0, OPC_OP, (outs rdOp:$rd),
+                      (ins rs1Op:$rs1), opcodestr, "$rd, $rs1">;
+  class RVY_rr<bits<7> funct7, bits<3> funct3, string opcodestr,
+               DAGOperand rdOp = YGPR, DAGOperand rs1Op = YGPR,
+               DAGOperand rs2Op = GPR>
+      : RVInstR<funct7, funct3, OPC_OP, (outs rdOp:$rd),
+                (ins rs1Op:$rs1, rs2Op:$rs2), opcodestr, "$rd, $rs1, $rs2">;
+  class RVY_ri<bits<3> funct3, RISCVOpcode opcode, string opcodestr,
+               DAGOperand rdOp = YGPR, DAGOperand rs1Op = YGPR>
+      : RVInstI<funct3, opcode, (outs rdOp:$rd),
+                (ins rs1Op:$rs1, simm12_lo:$imm12), opcodestr,
+                "$rd, $rs1, $imm12">;
+  class RVY_setboundsimm<string opcodestr>
+      : RVYInstSetBoundsImmFmt<(outs YGPR:$rd),
+                               (ins YGPR:$rs1, ybndsw_imm:$imm), opcodestr,
+                               "$rd, $rs1, $imm">;
+} // hasSideEffects = 0, mayLoad = 0, mayStore = 0
+
+let Predicates = [HasStdExtY] in {
+  //
+  // Instructions to Update The Capability Pointer
+  //
+  let isReMaterializable = true, isAsCheapAsAMove = true in {
+    def ADDY : RVY_rr<0x6, 0x0, "addy", YGPR, YGPR, GPRNoX0>;
+    def ADDIY : RVY_ri<0x2, OPC_OP_IMM_32, "addiy">;
+  }
+  def : InstAlias<"addy $rd, $rs1, $imm12",
+                  (ADDIY YGPR:$rd, YGPR:$rs1, simm12_lo:$imm12), 0>;
+  def YADDRW : RVY_rr<0x6, 0x1, "yaddrw", YGPR, YGPR, GPR>;
+
+  //
+  // Instructions to Manipulate Capabilities
+  //
+  // TODO: should we support acperm/candperm aliases with inverted mask?
+  def YPERMC : RVY_rr<0x6, 0x2, "ypermc", YGPR, YGPR, GPR>;
+  let hasSideEffects = 0, mayLoad = 0, mayStore = 0, isReMaterializable = true,
+      isAsCheapAsAMove = true, isMoveReg = true in {
+    def YMV : RVYInstSrcDst<0x6, 0x0, 0x0, OPC_OP, (outs YGPR:$rd),
+                            (ins YGPR:$rs1), "ymv", "$rd, $rs1">;
+  }
+  def PACKY : RVY_rr<0x6, 0x3, "packy", YGPR, YGPR, GPR>;
+  def : InstAlias<"yhiw $rd, $rs1, $rs2", (PACKY YGPR:$rd, YGPR:$rs1, GPR:$rs2),
+                  0>;
+  def YBNDSW : RVY_rr<0x7, 0x0, "ybndsw", YGPR, YGPR, GPR>;
+  def YBNDSWI : RVY_setboundsimm<"ybndswi">;
+  def : InstAlias<"ybndsw $rd, $rs1, $imm",
+                  (YBNDSWI YGPR:$rd, YGPR:$rs1, ybndsw_imm:$imm), 0>;
+  def YBNDSRW : RVY_rr<0x7, 0x1, "ybndsrw", YGPR, YGPR, GPR>;
+  def YSUNSEAL : RVY_rr<0x7, 0x2, "ysunseal", YGPR, YGPR, YGPR>;
+  //
+  // Instructions to Decode Capability Bounds
+  //
+  def YBASER : RVY_r<0x5, "ybaser", GPR, YGPR>;
+  def YLENR : RVY_r<0x6, "ylenr", GPR, YGPR>;
+
+  //
+  // Instructions to Extract Capability Fields
+  //
+  def YTAGR : RVY_r<0x0, "ytagr", GPR, YGPR>;
+  def YPERMR : RVY_r<0x1, "ypermr", GPR, YGPR>;
+  def YTYPER : RVY_r<0x2, "ytyper", GPR, YGPR>;
+  // The SRLIY instruction uses the encoding of SRLI with shamt==XLEN, other
+  // values are not supported. YHIR is a pseudo that expands to the correct
+  // shift value depending on RV32/RV64.
+  let DecoderNamespace = "RVYOnly", hasSideEffects = 0, mayLoad = 0,
+      mayStore = 0 in {
+    def SRLIY : RVInstIBase<0b101, OPC_OP_IMM, (outs GPR:$rd),
+                            (ins YGPR:$rs1, uimm7_srliy:$shamt), "srliy",
+                            "$rd, $rs1, $shamt"> {
+      bits<7> shamt;
+      let Inst{31 -27} = 0b00000;
+      let Inst{26 -20} = shamt;
+    }
+  }
+  let Predicates = [HasStdExtY, IsRV64] in {
+    def : InstAlias<"yhir $rd, $rs1", (SRLIY GPR:$rd, YGPR:$rs1, 64)>;
+  }
+  let Predicates = [HasStdExtY, IsRV32] in {
+    def : InstAlias<"yhir $rd, $rs1", (SRLIY GPR:$rd, YGPR:$rs1, 32)>;
+  }
+  //
+  // Miscellaneous Instructions to Handle Capability Data
+  //
+  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>;
+} // Predicates = [HasStdExtY]
diff --git a/llvm/test/MC/RISCV/rvy/rvy-invalid-mode-independent.s b/llvm/test/MC/RISCV/rvy/rvy-invalid-mode-independent.s
new file mode 100644
index 0000000000000..608becf94983e
--- /dev/null
+++ b/llvm/test/MC/RISCV/rvy/rvy-invalid-mode-independent.s
@@ -0,0 +1,42 @@
+# RUN: not llvm-mc --triple riscv32 --mattr=+experimental-y <%s 2>&1 \
+# RUN:     | FileCheck %s --check-prefixes=CHECK,CHECK-32 '--implicit-check-not=error:'
+# RUN: not llvm-mc --triple riscv64 --mattr=+experimental-y <%s 2>&1 \
+# RUN:     | FileCheck %s --check-prefixes=CHECK,CHECK-64 '--implicit-check-not=error:'
+
+addy a0, a1, zero
+# CHECK: <stdin>:[[#@LINE-1]]:14: error: operand must be a symbol with %lo/%pcrel_lo/%tprel_lo specifier or an integer in the range [-2048, 2047]
+addy a0, a1, x0
+# CHECK: <stdin>:[[#@LINE-1]]:14: error: operand must be a symbol with %lo/%pcrel_lo/%tprel_lo specifier or an integer in the range [-2048, 2047]
+srliy a0, a1, 65
+# CHECK-32: <stdin>:[[#@LINE-1]]:15: error: immediate must be an integer equal to XLEN (32)
+# CHECK-64: <stdin>:[[#@LINE-2]]:15: error: immediate must be an integer equal to XLEN (64)
+srliy a0, a1, 64
+# CHECK-32: <stdin>:[[#@LINE-1]]:15: error: immediate must be an integer equal to XLEN (32)
+srliy a0, a1, 32
+# CHECK-64: <stdin>:[[#@LINE-1]]:15: error: immediate must be an integer equal to XLEN (64)
+ybndswi a0, a0, 0
+# CHECK: <stdin>:[[#@LINE-1]]:17: error: immediate must be an integer in the range [1, 256], a multiple of 2 in the range [258, 768], a multiple of 4 in the range [772, 1792], or a multiple of 8 in the range [1800, 3840]
+ybndswi a0, a0, 257
+# CHECK: <stdin>:[[#@LINE-1]]:17: error: immediate must be an integer in the range [1, 256], a multiple of 2 in the range [258, 768], a multiple of 4 in the range [772, 1792], or a multiple of 8 in the range [1800, 3840]
+ybndswi a0, a0, 259
+# CHECK: <stdin>:[[#@LINE-1]]:17: error: immediate must be an integer in the range [1, 256], a multiple of 2 in the range [258, 768], a multiple of 4 in the range [772, 1792], or a multiple of 8 i...
[truncated]

// Standard Extensions
{DecoderTableRVYOnly32,
{RISCV::FeatureStdExtY},
"RVY-only standard 32-bit instructions"},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a decoder namespace? They shouldn't conflict with other standard extensions.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree we should not conflict with anything, but it was needed for SRLIY (aka YHIR) which technically overlaps with RV64-SRLI for RV32Y. Not sure if there is a more elegant way to solve this?

// The SRLIY instruction uses the encoding of SRLI with shamt==XLEN, other
  // values are not supported. YHIR is a pseudo that expands to the correct
  // shift value depending on RV32/RV64.
  let DecoderNamespace = "RVYOnly", hasSideEffects = 0, mayLoad = 0,
      mayStore = 0 in {
    def SRLIY : RVInstIBase<0b101, OPC_OP_IMM, (outs GPR:$rd),
                            (ins YGPR:$rs1, uimm7_srliy:$shamt), "srliy",
                            "$rd, $rs1, $shamt"> {
      bits<7> shamt;
      let Inst{31 -27} = 0b00000;
      let Inst{26 -20} = shamt;
    }
  }
  let Predicates = [HasStdExtY, IsRV64] in {
    def : InstAlias<"yhir $rd, $rs1", (SRLIY GPR:$rd, YGPR:$rs1, 64)>;
  }
  let Predicates = [HasStdExtY, IsRV32] in {
    def : InstAlias<"yhir $rd, $rs1", (SRLIY GPR:$rd, YGPR:$rs1, 32)>;
  }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's the RV32Only32 namespace already

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately RV32Only32 one won't work since SRLI claims the full immediate field for both 32 and 64-bit. I guess we could do something like use EncodingByHwMode for SRLI and SRLIY so that SRLIY claims immediates > XLEN, but I'm not sure if that is better or worse than the namespace?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you try it? I don't think we check for RV32 before using RV32Only32.

As far as I know, EncodingByHwMode duplicates the entire disassembler table for every HwMode which sounds expensive for 1 instruction.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this namespace is no longer needed. I started working on this probably around 3-4 months ago, so it's possible it predates the recent tablegen decoder changes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like I spoke too soon. Tablegen no longer complains, but we are now unable to disassemble the instruction. Will try to debug this a bit more today.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New workaround: split it into SRLIY64 and SRLIY32 which now has fixed bits in these positions so the decoder checks them before checking SRLI:

  5, 132, 1,                                 //  2089:  case 0x5: {
  OPC_SwitchField, 26, 6,                    //  2092:   switch Inst[31:26] {
  0, 16,                                     //  2095:   case 0x0: {
  OPC_Scope, 10,                             //  2097:    try {
  OPC_CheckField, 20, 6, 32,                 //  2099:     check Inst[25:20] == 0x20
  OPC_CheckPredicate, 41,                    //  2103:     check predicate 41
  OPC_Decode, 244, 123, 18,                  //  2105:     decode to SRLIY32 using decoder 18
                                             //  2105:    } else try {
  OPC_Decode, 242, 123, 15,                  //  2109:     decode to SRLI using decoder 15
                                             //  2109:    }
                                             //  2109:   }
  1, 10,                                     //  2113:   case 0x1: {
  OPC_CheckPredicate, 42,                    //  2115:    check predicate 42
  OPC_CheckField, 20, 6, 0,                  //  2117:    check Inst[25:20] == 0x0
  OPC_Decode, 245, 123, 19,                  //  2121:    decode to SRLIY64 using decoder 19
                                             //  2121:   }

def ADDY : RVY_rr<0x6, 0x0, "addy", YGPR, YGPR, GPRNoX0>;
def ADDIY : RVY_ri<0x2, OPC_OP_IMM_32, "addiy">;
}
def : InstAlias<"addy $rd, $rs1, $imm12",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we group the aliases separately from the actual instructions?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure happy to make that change, I just thought keeping them grouped together would make sense since they all share the same Predicate.

//
let isReMaterializable = true, isAsCheapAsAMove = true in {
def ADDY : RVY_rr<0x6, 0x0, "addy", YGPR, YGPR, GPRNoX0>;
def ADDIY : RVY_ri<0x2, OPC_OP_IMM_32, "addiy">;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the spec this conflicts with P. Is tablegen just not catching this because P also specifies specific values in some of bits 31-20?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why tablegen is not complaining about this one.
CADDI use OP-IMM-32 encoding space with [14:12] == 010, which based on looking at https://www.jhauser.us/RISCV/ext-P/RVP-instrEncodings-018.pdf should definitely overlap with all uses of RVPUnary_ri.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This encoding will change for 1.0 (or RVP is moved), so it will only be a temporary encoding conflict.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why tablegen is not complaining about this one. CADDI use OP-IMM-32 encoding space with [14:12] == 010, which based on looking at https://www.jhauser.us/RISCV/ext-P/RVP-instrEncodings-018.pdf should definitely overlap with all uses of RVPUnary_ri.

Tablegen may be conservatively assuming that not all values of the 12-bit immediate for ADDIY are legal. Since P instructions specifies fixed values for some of those bits, I think it just orders the P extension instructions first. Not sure if it will fallthrough to decode ADDIY if the P predicate check fails. Or if the P predicate check will fail the decode. I'll check if you're testing a conflicting case.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just looked at the generated table and it looks like we now check all instructions with some bit positions fixed first and the fall back to one with a wildcard. So if you mix P and Y you would get some P instructions in the disassembly stream. Is this okay for the initial version or should I mark P as conflicting?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just looked at the generated table and it looks like we now check all instructions with some bit positions fixed first and the fall back to one with a wildcard. So if you mix P and Y you would get some P instructions in the disassembly stream. Is this okay for the initial version or should I mark P as conflicting?

I think it's ok for the initial version.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think conflicts are only detected when two instructions have a wildcard bit at the same position, not when one has a fixed bit and the other has a wildcard bit (it's assumed the one with the fixed bit is the more specific decode and used).

We rely on this behaviour in a bunch of places, where we have GPRNoX0 fields (5 bits, all wildcard), and reuse the 0b00000 encoding for another instruction. In these cases, we are getting the correct behaviour.

For more complex cases, we should be setting hasCompleteDecoder = false, so that the decoder can try several operand/instruction decoders and the failure of the first decoder is not treated as a failure of decoding, it will continue trying the rest.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(It's not clear, however, that this is a case where hasCompleteDecoder = false would actually "fix" something. There are more notes on the comment about hasCompleteDecoder in Target.td)

@arichardson

arichardson commented Jan 21, 2026 •

Copy link
Copy Markdown
Member Author

This is a stacked pull request managed by spr. The following pull requests must be merged before this one:

@github-actions

github-actions Bot commented Jan 21, 2026 •

Copy link
Copy Markdown

🐧 Linux x64 Test Results

  • 189205 tests passed
  • 5015 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

  • 130161 tests passed
  • 2881 tests skipped

✅ The build succeeded and all tests passed.

@arichardson arichardson changed the title [RISC-V][MC] Introduce initial support RVY (CHERI) [RISC-V][MC] Introduce initial support for RVY (CHERI) Jan 21, 2026
@github-actions

github-actions Bot commented Jan 21, 2026 •

Copy link
Copy Markdown

✅ With the latest revision this PR passed the C/C++ code formatter.

@arichardson
arichardson requested a review from topperc January 26, 2026 06:06
@github-actions
github-actions Bot deleted the branch users/arichardson/spr/main.risc-vmc-support-rvy-auipc-instruction April 28, 2026 09:08
@github-actions
github-actions Bot deleted the users/arichardson/spr/risc-vmc-introduce-initial-support-rvy-cheri branch April 28, 2026 09:09
Cmake does not properly parse IN_LIST within the if condition, and
treats it as a token.
This is not desired behavior.
The CMP0057 policy supports the new [if() IN_LIST
](https://cmake.org/cmake/help/latest/command/if.html#command:if)
operator.
Enable this policy and resolve the build error.


Fixes #199282
Assisted by: Github Copilot
jwanggit86 and others added 5 commits May 26, 2026 13:20
In the GFX950 documentation, some instructions that should have the
_sdwa suffix were incorrectly given the _dpp suffix.
…ertAll*/) (#197289)

In the context of nested checkpoints the tracker's API was somewhat
inconsistent. Tracker::revert() would revert to the last checkpoint but
accept() would accept all changes.

This patch fixes this, and introduces `accept(bool AcceptAll)` and
`revert(bool RevertAll)`.
When tryToVectorize is called with AllowFMACandidates=true, falling
through to tryToVectorizeList vectorizes the fmul operands of an
FMA-candidate fadd without accounting for the lost FMA opportunity.
canConvertToFMA requires those fmuls to have one use, so vectorizing
them always breaks FMA formation. The cost model for tryToVectorizeList
omits the fadd from the tree and compares "2 fmuls vs 1 vfmul", missing
the scalar FMA savings entirely.
Block tryToVectorizeList when AllowFMACandidates=true. TryToReduce is
safe because computeReductionCost accounts for FMA in the scalar
baseline via canConvertToFMA on the fadd user.
Fixes a 4.5% regression in SPEC17 imagemagick on AArch64 introduced by

Reviewers: sushgokh, bababuck

Pull Request: #199706
…7620)

The second argument to `__builtin_arm_irg()` and `__builtin_arm_gmi()`
is expected to be 64-bit. When a wider type is passed, the compiler
still generates a `zext` instruction, leading to a backend error:

```
> cat test.c
unsigned test(void* a, unsigned __int128 m) {
  return __builtin_arm_gmi(a, m);
}
> clang -target aarch64 -march=armv9+memtag -S test.c
fatal error: error in backend: Cannot select: ... i64,ch = load<... zext from i128>
```

This is fixed by applying the required implicit conversions to the
argument in Sema.
This does not implement CSR spills other than those AMDGPU handles
during PEI. The remaining spills are handled in a subsequent patch.

Change-Id: I5e3a9a62cf9189245011a82a129790d813d49373
Co-authored-by: Scott Linder <scott.linder@amd.com>
Co-authored-by: Venkata Ramanaiah Nalamothu <VenkataRamanaiah.Nalamothu@amd.com>
arichardson added a commit to arichardson/upstream-llvm-project that referenced this pull request May 26, 2026
This adds MC-level support for most of the base Y extension instructions
using version 0.9.8.1 of the specification. This initial pull request is
restricted to the execution-mode-independent subset. The Y extension
(CHERI for RISC-V) also introduces an execution mode that determines
whether certain register operands use the full extended register or only
the address subset (the current XLEN registers). The instructions that
depend on execution mode (loads/stores/jumps + AUIPC) will be added in
the following commits in this stack of changes.

Specification: https://github.com/riscv/riscv-cheri/releases/tag/v0.9.8.1
Co-authored-by: Jessica Clarke <jrtc27@jrtc27.com>
Co-authored-by: Alexander Richardson <alexrichardson@google.com>
Co-authored-by: Petr Vesely <petr.vesely@codasip.com>

Pull Request: llvm#176871
@arichardson
arichardson changed the base branch from users/arichardson/spr/main.risc-vmc-introduce-initial-support-rvy-cheri to main May 26, 2026 21:33
Created using spr 1.3.8-beta.1
@arichardson
arichardson changed the base branch from main to users/arichardson/spr/main.risc-vmc-support-rvy-auipc-instruction May 26, 2026 21:36
timeout-minutes: 60
runs-on: ${{ matrix.os }}
container:
image: ${{ (startsWith(matrix.os, 'ubuntu-24.04-arm') && 'ghcr.io/llvm/arm64v8/libc-ubuntu-24.04') || 'ghcr.io/llvm/libc-ubuntu-24.04'}}
timeout-minutes: 60
runs-on: ${{ matrix.os }}
container:
image: ${{ (startsWith(matrix.os, 'ubuntu-24.04-arm') && 'ghcr.io/llvm/arm64v8/libc-ubuntu-24.04') || 'ghcr.io/llvm/libc-ubuntu-24.04'}}
git config user.name "llvmbot"
git commit -a -m "Add ${{ inputs.release-version }} documentation"
git push --force "https://$GH_TOKEN@github.com/llvmbot/www-releases.git" HEAD:refs/heads/${{ inputs.release-version }}
git push --force "https://$PUSH_TOKEN@github.com/llvmbot/www-releases.git" HEAD:refs/heads/${{ inputs.release-version }}
git config user.name "llvmbot"
git commit -a -m "Add ${{ inputs.release-version }} documentation"
git push --force "https://$GH_TOKEN@github.com/llvmbot/www-releases.git" HEAD:refs/heads/${{ inputs.release-version }}
git push --force "https://$PUSH_TOKEN@github.com/llvmbot/www-releases.git" HEAD:refs/heads/${{ inputs.release-version }}
fi

cmake \
--build ${{ steps.strings.outputs.build-output-dir }} \
repo = github.Github(auth=github.Auth.Token("${{ github.token }}")).get_repo("${{ github.repository }}")
pr = repo.get_pull(${{ github.event.issue.number }})
comment = pr.get_issue_comment(${{ github.event.comment.id }})
add_text = "> _Running benchmarks in ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/job/${{ job.check_run_id }}_"
source .venv/bin/activate && cd repo
./libcxx/utils/test-at-commit --commit ${{ steps.vars.outputs.pr_head }} -B build/candidate -- -sv -j1 --param optimization=speed "$BENCHMARKS"
./libcxx/utils/consolidate-benchmarks build/candidate | tee candidate.lnt
./libcxx/utils/build-at-commit --commit ${{ steps.vars.outputs.pr_head }} --install-dir install/candidate -- -DCMAKE_BUILD_TYPE=RelWithDebInfo
@arichardson

Copy link
Copy Markdown
Member Author

Apologies for the spam here, it seems like SPR got confused by the branch being deleted. I'll create a new PR.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.