Skip to content

Commit

Permalink
[RISCV] Initial support .insn directive for the assembler.
Browse files Browse the repository at this point in the history
This allows for a custom encoding to be emitted. It can also be
used with inline assembly to allow the custom instruction to be
register allocated like other instructions.

I initially started from SystemZ's implementation, but some of
the formats allow operands to be specified in multiple ways so I
had to add support for matching different operand class lists for
the same format. That implementation is a simplified version of
what is emitted by tablegen for regular instructions.

I've left out the compressed formats. And I haven't supported the
named opcodes like LUI or OP_IMM_32. Those can be added in future
patches.

Documentation can be found here https://sourceware.org/binutils/docs-2.37/as/RISC_002dV_002dFormats.html

Reviewed By: jrtc27, MaskRay

Differential Revision: https://reviews.llvm.org/D108602
  • Loading branch information
topperc committed Sep 12, 2021
1 parent 66a0b34 commit 2838797
Show file tree
Hide file tree
Showing 8 changed files with 407 additions and 3 deletions.
63 changes: 62 additions & 1 deletion llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ class RISCVAsmParser : public MCTargetAsmParser {

bool parseDirectiveOption();
bool parseDirectiveAttribute();
bool parseDirectiveInsn(SMLoc L);

void setFeatureBits(uint64_t Feature, StringRef FeatureString) {
if (!(getSTI().getFeatureBits()[Feature])) {
Expand Down Expand Up @@ -504,6 +505,24 @@ struct RISCVOperand : public MCParsedAsmOperand {
return (isRV64() && isUInt<5>(Imm)) || isUInt<4>(Imm);
}

bool isUImm2() const {
int64_t Imm;
RISCVMCExpr::VariantKind VK = RISCVMCExpr::VK_RISCV_None;
if (!isImm())
return false;
bool IsConstantImm = evaluateConstantImm(getImm(), Imm, VK);
return IsConstantImm && isUInt<2>(Imm) && VK == RISCVMCExpr::VK_RISCV_None;
}

bool isUImm3() const {
int64_t Imm;
RISCVMCExpr::VariantKind VK = RISCVMCExpr::VK_RISCV_None;
if (!isImm())
return false;
bool IsConstantImm = evaluateConstantImm(getImm(), Imm, VK);
return IsConstantImm && isUInt<3>(Imm) && VK == RISCVMCExpr::VK_RISCV_None;
}

bool isUImm5() const {
int64_t Imm;
RISCVMCExpr::VariantKind VK = RISCVMCExpr::VK_RISCV_None;
Expand All @@ -513,6 +532,15 @@ struct RISCVOperand : public MCParsedAsmOperand {
return IsConstantImm && isUInt<5>(Imm) && VK == RISCVMCExpr::VK_RISCV_None;
}

bool isUImm7() const {
int64_t Imm;
RISCVMCExpr::VariantKind VK = RISCVMCExpr::VK_RISCV_None;
if (!isImm())
return false;
bool IsConstantImm = evaluateConstantImm(getImm(), Imm, VK);
return IsConstantImm && isUInt<7>(Imm) && VK == RISCVMCExpr::VK_RISCV_None;
}

bool isSImm5() const {
if (!isImm())
return false;
Expand Down Expand Up @@ -1835,8 +1863,10 @@ bool RISCVAsmParser::ParseDirective(AsmToken DirectiveID) {

if (IDVal == ".option")
return parseDirectiveOption();
else if (IDVal == ".attribute")
if (IDVal == ".attribute")
return parseDirectiveAttribute();
if (IDVal == ".insn")
return parseDirectiveInsn(DirectiveID.getLoc());

return true;
}
Expand Down Expand Up @@ -2200,6 +2230,37 @@ bool RISCVAsmParser::parseDirectiveAttribute() {
return false;
}

/// parseDirectiveInsn
/// ::= .insn [ format encoding, (operands (, operands)*) ]
bool RISCVAsmParser::parseDirectiveInsn(SMLoc L) {
MCAsmParser &Parser = getParser();

// Expect instruction format as identifier.
StringRef Format;
SMLoc ErrorLoc = Parser.getTok().getLoc();
if (Parser.parseIdentifier(Format))
return Error(ErrorLoc, "expected instruction format");

if (Format != "r" && Format != "r4" && Format != "i" && Format != "b" &&
Format != "sb" && Format != "u" && Format != "j" && Format != "uj" &&
Format != "s")
return Error(ErrorLoc, "invalid instruction format");

std::string FormatName = (".insn_" + Format).str();

ParseInstructionInfo Info;
SmallVector<std::unique_ptr<MCParsedAsmOperand>, 8> Operands;

if (ParseInstruction(Info, FormatName, L, Operands))
return true;

unsigned Opcode;
uint64_t ErrorInfo;
return MatchAndEmitInstruction(L, Opcode, Operands, Parser.getStreamer(),
ErrorInfo,
/*MatchingInlineAsm=*/false);
}

void RISCVAsmParser::emitToStreamer(MCStreamer &S, const MCInst &Inst) {
MCInst CInst;
bool Res = compressInst(CInst, Inst, getSTI(), S.getContext());
Expand Down
5 changes: 4 additions & 1 deletion llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,11 @@ enum {
namespace RISCVOp {
enum OperandType : unsigned {
OPERAND_FIRST_RISCV_IMM = MCOI::OPERAND_FIRST_TARGET,
OPERAND_UIMM4 = OPERAND_FIRST_RISCV_IMM,
OPERAND_UIMM2 = OPERAND_FIRST_RISCV_IMM,
OPERAND_UIMM3,
OPERAND_UIMM4,
OPERAND_UIMM5,
OPERAND_UIMM7,
OPERAND_UIMM12,
OPERAND_SIMM12,
OPERAND_UIMM20,
Expand Down
2 changes: 1 addition & 1 deletion llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ unsigned RISCVMCCodeEmitter::getImmOpValue(const MCInst &MI, unsigned OpNo,
}
} else if (Kind == MCExpr::SymbolRef &&
cast<MCSymbolRefExpr>(Expr)->getKind() == MCSymbolRefExpr::VK_None) {
if (Desc.getOpcode() == RISCV::JAL) {
if (MIFrm == RISCVII::InstFormatJ) {
FixupKind = RISCV::fixup_riscv_jal;
} else if (MIFrm == RISCVII::InstFormatB) {
FixupKind = RISCV::fixup_riscv_branch;
Expand Down
132 changes: 132 additions & 0 deletions llvm/lib/Target/RISCV/RISCVInstrFormats.td
Original file line number Diff line number Diff line change
Expand Up @@ -409,3 +409,135 @@ class RVInstJ<RISCVOpcode opcode, dag outs, dag ins, string opcodestr,
let Inst{11-7} = rd;
let Opcode = opcode.Value;
}

//===----------------------------------------------------------------------===//
// Instruction classes for .insn directives
//===----------------------------------------------------------------------===//

class DirectiveInsnR<dag outs, dag ins, string argstr>
: RVInst<outs, ins, "", "", [], InstFormatR> {
bits<7> opcode;
bits<7> funct7;
bits<3> funct3;

bits<5> rs2;
bits<5> rs1;
bits<5> rd;

let Inst{31-25} = funct7;
let Inst{24-20} = rs2;
let Inst{19-15} = rs1;
let Inst{14-12} = funct3;
let Inst{11-7} = rd;
let Opcode = opcode;

let AsmString = ".insn r " # argstr;
}

class DirectiveInsnR4<dag outs, dag ins, string argstr>
: RVInst<outs, ins, "", "", [], InstFormatR4> {
bits<7> opcode;
bits<2> funct2;
bits<3> funct3;

bits<5> rs3;
bits<5> rs2;
bits<5> rs1;
bits<5> rd;

let Inst{31-27} = rs3;
let Inst{26-25} = funct2;
let Inst{24-20} = rs2;
let Inst{19-15} = rs1;
let Inst{14-12} = funct3;
let Inst{11-7} = rd;
let Opcode = opcode;

let AsmString = ".insn r4 " # argstr;
}

class DirectiveInsnI<dag outs, dag ins, string argstr>
: RVInst<outs, ins, "", "", [], InstFormatI> {
bits<7> opcode;
bits<3> funct3;

bits<12> imm12;
bits<5> rs1;
bits<5> rd;

let Inst{31-20} = imm12;
let Inst{19-15} = rs1;
let Inst{14-12} = funct3;
let Inst{11-7} = rd;
let Opcode = opcode;

let AsmString = ".insn i " # argstr;
}

class DirectiveInsnS<dag outs, dag ins, string argstr>
: RVInst<outs, ins, "", "", [], InstFormatS> {
bits<7> opcode;
bits<3> funct3;

bits<12> imm12;
bits<5> rs2;
bits<5> rs1;

let Inst{31-25} = imm12{11-5};
let Inst{24-20} = rs2;
let Inst{19-15} = rs1;
let Inst{14-12} = funct3;
let Inst{11-7} = imm12{4-0};
let Opcode = opcode;

let AsmString = ".insn s " # argstr;
}

class DirectiveInsnB<dag outs, dag ins, string argstr>
: RVInst<outs, ins, "", "", [], InstFormatB> {
bits<7> opcode;
bits<3> funct3;

bits<12> imm12;
bits<5> rs2;
bits<5> rs1;

let Inst{31} = imm12{11};
let Inst{30-25} = imm12{9-4};
let Inst{24-20} = rs2;
let Inst{19-15} = rs1;
let Inst{14-12} = funct3;
let Inst{11-8} = imm12{3-0};
let Inst{7} = imm12{10};
let Opcode = opcode;

let AsmString = ".insn b " # argstr;
}

class DirectiveInsnU<dag outs, dag ins, string argstr>
: RVInst<outs, ins, "", "", [], InstFormatU> {
bits<7> opcode;

bits<20> imm20;
bits<5> rd;

let Inst{31-12} = imm20;
let Inst{11-7} = rd;
let Opcode = opcode;

let AsmString = ".insn u " # argstr;
}

class DirectiveInsnJ<dag outs, dag ins, string argstr>
: RVInst<outs, ins, "", "", [], InstFormatJ> {
bits<7> opcode;

bits<20> imm20;
bits<5> rd;

let Inst{31-12} = imm20;
let Inst{11-7} = rd;
let Opcode = opcode;

let AsmString = ".insn j " # argstr;
}
102 changes: 102 additions & 0 deletions llvm/lib/Target/RISCV/RISCVInstrInfo.td
Original file line number Diff line number Diff line change
Expand Up @@ -153,13 +153,34 @@ def uimmlog2xlen : Operand<XLenVT>, ImmLeaf<XLenVT, [{
let OperandNamespace = "RISCVOp";
}

def uimm2 : Operand<XLenVT> {
let ParserMatchClass = UImmAsmOperand<2>;
let DecoderMethod = "decodeUImmOperand<2>";
let OperandType = "OPERAND_UIMM2";
let OperandNamespace = "RISCVOp";
}

def uimm3 : Operand<XLenVT> {
let ParserMatchClass = UImmAsmOperand<3>;
let DecoderMethod = "decodeUImmOperand<3>";
let OperandType = "OPERAND_UIMM3";
let OperandNamespace = "RISCVOp";
}

def uimm5 : Operand<XLenVT>, ImmLeaf<XLenVT, [{return isUInt<5>(Imm);}]> {
let ParserMatchClass = UImmAsmOperand<5>;
let DecoderMethod = "decodeUImmOperand<5>";
let OperandType = "OPERAND_UIMM5";
let OperandNamespace = "RISCVOp";
}

def uimm7 : Operand<XLenVT> {
let ParserMatchClass = UImmAsmOperand<7>;
let DecoderMethod = "decodeUImmOperand<7>";
let OperandType = "OPERAND_UIMM7";
let OperandNamespace = "RISCVOp";
}

def simm12 : Operand<XLenVT>, ImmLeaf<XLenVT, [{return isInt<12>(Imm);}]> {
let ParserMatchClass = SImmAsmOperand<12>;
let EncoderMethod = "getImmOpValue";
Expand Down Expand Up @@ -849,6 +870,87 @@ def : MnemonicAlias<"sbreak", "ebreak">;
// that don't support this alias.
def : InstAlias<"zext.b $rd, $rs", (ANDI GPR:$rd, GPR:$rs, 0xFF), 0>;

//===----------------------------------------------------------------------===//
// .insn directive instructions
//===----------------------------------------------------------------------===//

// isCodeGenOnly = 1 to hide them from the tablegened assembly parser.
let isCodeGenOnly = 1, hasSideEffects = 1, mayLoad = 1, mayStore = 1,
hasNoSchedulingInfo = 1 in {
def InsnR : DirectiveInsnR<(outs AnyReg:$rd), (ins uimm7:$opcode, uimm3:$funct3,
uimm7:$funct7, AnyReg:$rs1,
AnyReg:$rs2),
"$opcode, $funct3, $funct7, $rd, $rs1, $rs2">;
def InsnR4 : DirectiveInsnR4<(outs AnyReg:$rd), (ins uimm7:$opcode,
uimm3:$funct3,
uimm2:$funct2,
AnyReg:$rs1, AnyReg:$rs2,
AnyReg:$rs3),
"$opcode, $funct3, $funct2, $rd, $rs1, $rs2, $rs3">;
def InsnI : DirectiveInsnI<(outs AnyReg:$rd), (ins uimm7:$opcode, uimm3:$funct3,
AnyReg:$rs1, simm12:$imm12),
"$opcode, $funct3, $rd, $rs1, $imm12">;
def InsnI_Mem : DirectiveInsnI<(outs AnyReg:$rd), (ins uimm7:$opcode,
uimm3:$funct3,
AnyReg:$rs1,
simm12:$imm12),
"$opcode, $funct3, $rd, ${imm12}(${rs1})">;
def InsnB : DirectiveInsnB<(outs), (ins uimm7:$opcode, uimm3:$funct3,
AnyReg:$rs1, AnyReg:$rs2,
simm13_lsb0:$imm12),
"$opcode, $funct3, $rs1, $rs2, $imm12">;
def InsnU : DirectiveInsnU<(outs AnyReg:$rd), (ins uimm7:$opcode,
uimm20_lui:$imm20),
"$opcode, $rd, $imm20">;
def InsnJ : DirectiveInsnJ<(outs AnyReg:$rd), (ins uimm7:$opcode,
simm21_lsb0_jal:$imm20),
"$opcode, $rd, $imm20">;
def InsnS : DirectiveInsnS<(outs), (ins uimm7:$opcode, uimm3:$funct3,
AnyReg:$rs2, AnyReg:$rs1,
simm12:$imm12),
"$opcode, $funct3, $rs2, ${imm12}(${rs1})">;
}

// Use InstAliases to match these so that we can combine the insn and format
// into a mnemonic to use as the key for the tablegened asm matcher table. The
// parser will take care of creating these fake mnemonics and will only do it
// for known formats.
let EmitPriority = 0 in {
def : InstAlias<".insn_r $opcode, $funct3, $funct7, $rd, $rs1, $rs2",
(InsnR AnyReg:$rd, uimm7:$opcode, uimm3:$funct3, uimm7:$funct7,
AnyReg:$rs1, AnyReg:$rs2)>;
// Accept 4 register form of ".insn r" as alias for ".insn r4".
def : InstAlias<".insn_r $opcode, $funct3, $funct7, $rd, $rs1, $rs2, $rs3",
(InsnR4 AnyReg:$rd, uimm7:$opcode, uimm3:$funct3, uimm7:$funct7,
AnyReg:$rs1, AnyReg:$rs2, AnyReg:$rs3)>;
def : InstAlias<".insn_r4 $opcode, $funct3, $funct7, $rd, $rs1, $rs2, $rs3",
(InsnR4 AnyReg:$rd, uimm7:$opcode, uimm3:$funct3, uimm7:$funct7,
AnyReg:$rs1, AnyReg:$rs2, AnyReg:$rs3)>;
def : InstAlias<".insn_i $opcode, $funct3, $rd, $rs1, $imm12",
(InsnI AnyReg:$rd, uimm7:$opcode, uimm3:$funct3, AnyReg:$rs1,
simm12:$imm12)>;
def : InstAlias<".insn_i $opcode, $funct3, $rd, ${imm12}(${rs1})",
(InsnI_Mem AnyReg:$rd, uimm7:$opcode, uimm3:$funct3,
AnyReg:$rs1, simm12:$imm12)>;
def : InstAlias<".insn_b $opcode, $funct3, $rs1, $rs2, $imm12",
(InsnB uimm7:$opcode, uimm3:$funct3, AnyReg:$rs1,
AnyReg:$rs2, simm13_lsb0:$imm12)>;
// Accept sb as an alias for b.
def : InstAlias<".insn_sb $opcode, $funct3, $rs1, $rs2, $imm12",
(InsnB uimm7:$opcode, uimm3:$funct3, AnyReg:$rs1,
AnyReg:$rs2, simm13_lsb0:$imm12)>;
def : InstAlias<".insn_u $opcode, $rd, $imm20",
(InsnU AnyReg:$rd, uimm7:$opcode, uimm20_lui:$imm20)>;
def : InstAlias<".insn_j $opcode, $rd, $imm20",
(InsnJ AnyReg:$rd, uimm7:$opcode, simm21_lsb0_jal:$imm20)>;
// Accept uj as an alias for j.
def : InstAlias<".insn_uj $opcode, $rd, $imm20",
(InsnJ AnyReg:$rd, uimm7:$opcode, simm21_lsb0_jal:$imm20)>;
def : InstAlias<".insn_s $opcode, $funct3, $rs2, ${imm12}(${rs1})",
(InsnS uimm7:$opcode, uimm3:$funct3, AnyReg:$rs2,
AnyReg:$rs1, simm12:$imm12)>;
}

//===----------------------------------------------------------------------===//
// Pseudo-instructions and codegen patterns
//
Expand Down
12 changes: 12 additions & 0 deletions llvm/lib/Target/RISCV/RISCVRegisterInfo.td
Original file line number Diff line number Diff line change
Expand Up @@ -557,3 +557,15 @@ foreach m = LMULList.m in {
def FFLAGS : RISCVReg<0, "fflags">;
def FRM : RISCVReg<0, "frm">;
def FCSR : RISCVReg<0, "fcsr">;

// Any type register. Used for .insn directives when we don't know what the
// register types could be.
// NOTE: The alignment and size are bogus values. The Size needs to be non-zero
// or tablegen will use "untyped" to determine the size which will assert.
let isAllocatable = 0 in
def AnyReg : RegisterClass<"RISCV", [untyped], 32,
(add (sequence "X%u", 0, 31),
(sequence "F%u_D", 0, 31),
(sequence "V%u", 0, 31))> {
let Size = 32;
}
Loading

0 comments on commit 2838797

Please sign in to comment.