Skip to content

Commit

Permalink
riscv64: Add compressed addi (bytecodealliance#7057)
Browse files Browse the repository at this point in the history
* riscv64: Add `c.ebreak` instruction

* riscv64: Implement `c.unimp`

* riscv64: Add `c.addi`

* riscv64: Add `c.addiw`

* riscv64: Add `c.addi16sp`

* riscv64: Add `c.slli`

* riscv64: Add `c.addi4spn`

* riscv64: Update `c.addiw` comment

* riscv64: Centralize Zca Check

* riscv64: Avoid double construction in some match arms
  • Loading branch information
afonso360 authored and eduardomourar committed Sep 22, 2023
1 parent 66af4e6 commit 0c57ab5
Show file tree
Hide file tree
Showing 6 changed files with 457 additions and 30 deletions.
16 changes: 16 additions & 0 deletions cranelift/codegen/src/isa/riscv64/inst.isle
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,9 @@
(CAdd)
(CJr)
(CJalr)
;; c.ebreak technically isn't a CR format instruction, but it's encoding
;; lines up with this format.
(CEbreak)
))

;; Opcodes for the CA compressed instruction format
Expand All @@ -732,6 +735,19 @@
(CJ)
))

;; Opcodes for the CI compressed instruction format
(type CiOp (enum
(CAddi)
(CAddiw)
(CAddi16sp)
(CSlli)
))

;; Opcodes for the CIW compressed instruction format
(type CiwOp (enum
(CAddi4spn)
))


(type CsrRegOP (enum
;; Atomic Read/Write CSR
Expand Down
44 changes: 41 additions & 3 deletions cranelift/codegen/src/isa/riscv64/inst/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ use super::*;
use crate::ir::condcodes::CondCode;

use crate::isa::riscv64::inst::{reg_name, reg_to_gpr_num};
use crate::isa::riscv64::lower::isle::generated_code::{COpcodeSpace, CaOp, CjOp, CrOp};

use crate::isa::riscv64::lower::isle::generated_code::{
COpcodeSpace, CaOp, CiOp, CiwOp, CjOp, CrOp,
};
use crate::machinst::isle::WritableReg;

use std::fmt::{Display, Formatter, Result};
Expand Down Expand Up @@ -1916,14 +1919,14 @@ impl CrOp {
match self {
// `c.jr` has the same op/funct4 as C.MV, but RS2 is 0, which is illegal for mv.
CrOp::CMv | CrOp::CJr => 0b1000,
CrOp::CAdd | CrOp::CJalr => 0b1001,
CrOp::CAdd | CrOp::CJalr | CrOp::CEbreak => 0b1001,
}
}

pub fn op(&self) -> COpcodeSpace {
// https://five-embeddev.com/riscv-isa-manual/latest/rvc-opcode-map.html#rvcopcodemap
match self {
CrOp::CMv | CrOp::CAdd | CrOp::CJr | CrOp::CJalr => COpcodeSpace::C2,
CrOp::CMv | CrOp::CAdd | CrOp::CJr | CrOp::CJalr | CrOp::CEbreak => COpcodeSpace::C2,
}
}
}
Expand Down Expand Up @@ -1974,3 +1977,38 @@ impl CjOp {
}
}
}

impl CiOp {
pub fn funct3(&self) -> u32 {
// https://github.com/michaeljclark/riscv-meta/blob/master/opcodes
match self {
CiOp::CAddi | CiOp::CSlli => 0b000,
CiOp::CAddiw => 0b001,
CiOp::CAddi16sp => 0b011,
}
}

pub fn op(&self) -> COpcodeSpace {
// https://five-embeddev.com/riscv-isa-manual/latest/rvc-opcode-map.html#rvcopcodemap
match self {
CiOp::CAddi | CiOp::CAddiw | CiOp::CAddi16sp => COpcodeSpace::C1,
CiOp::CSlli => COpcodeSpace::C2,
}
}
}

impl CiwOp {
pub fn funct3(&self) -> u32 {
// https://github.com/michaeljclark/riscv-meta/blob/master/opcodes
match self {
CiwOp::CAddi4spn => 0b000,
}
}

pub fn op(&self) -> COpcodeSpace {
// https://five-embeddev.com/riscv-isa-manual/latest/rvc-opcode-map.html#rvcopcodemap
match self {
CiwOp::CAddi4spn => COpcodeSpace::C0,
}
}
}
141 changes: 121 additions & 20 deletions cranelift/codegen/src/isa/riscv64/inst/emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use crate::binemit::StackMap;
use crate::ir::{self, LibCall, RelSourceLoc, TrapCode};
use crate::isa::riscv64::inst::*;
use crate::isa::riscv64::lower::isle::generated_code::{CaOp, CrOp};
use crate::isa::riscv64::lower::isle::generated_code::{CaOp, CiOp, CiwOp, CrOp};
use crate::machinst::{AllocationConsumer, Reg, Writable};
use crate::trace;
use cranelift_control::ControlPlane;
Expand Down Expand Up @@ -460,11 +460,17 @@ impl Inst {
&self,
sink: &mut MachBuffer<Inst>,
emit_info: &EmitInfo,
_state: &mut EmitState,
state: &mut EmitState,
start_off: &mut u32,
) -> bool {
let has_zca = emit_info.isa_flags.has_zca();

// Currently all compressed extensions (Zcb, Zcd, Zcmp, Zcmt, etc..) require Zca
// to be enabled, so check it early.
if !has_zca {
return false;
}

fn reg_is_compressible(r: Reg) -> bool {
r.to_real_reg()
.map(|r| r.hw_enc() >= 8 && r.hw_enc() < 16)
Expand All @@ -478,7 +484,7 @@ impl Inst {
rd,
rs1,
rs2,
} if has_zca && rd.to_reg() == rs1 && rs1 != zero_reg() && rs2 != zero_reg() => {
} if rd.to_reg() == rs1 && rs1 != zero_reg() && rs2 != zero_reg() => {
sink.put2(encode_cr_type(CrOp::CAdd, rd, rs2));
}

Expand All @@ -488,8 +494,7 @@ impl Inst {
rd,
rs,
imm12,
} if has_zca
&& rd.to_reg() != rs
} if rd.to_reg() != rs
&& rd.to_reg() != zero_reg()
&& rs != zero_reg()
&& imm12.as_i16() == 0 =>
Expand All @@ -509,11 +514,7 @@ impl Inst {
rd,
rs1,
rs2,
} if has_zca
&& rd.to_reg() == rs1
&& reg_is_compressible(rs1)
&& reg_is_compressible(rs2) =>
{
} if rd.to_reg() == rs1 && reg_is_compressible(rs1) && reg_is_compressible(rs2) => {
let op = match alu_op {
AluOPRRR::And => CaOp::CAnd,
AluOPRRR::Or => CaOp::COr,
Expand All @@ -530,32 +531,129 @@ impl Inst {
// c.j
//
// We don't have a separate JAL as that is only availabile in RV32C
Inst::Jal { label } if has_zca => {
Inst::Jal { label } => {
sink.use_label_at_offset(*start_off, label, LabelUse::RVCJump);
sink.add_uncond_branch(*start_off, *start_off + 2, label);
sink.put2(encode_cj_type(CjOp::CJ, Imm12::ZERO));
}

// c.jr
Inst::Jalr { rd, base, offset }
if has_zca
&& rd.to_reg() == zero_reg()
&& base != zero_reg()
&& offset.as_i16() == 0 =>
if rd.to_reg() == zero_reg() && base != zero_reg() && offset.as_i16() == 0 =>
{
sink.put2(encode_cr2_type(CrOp::CJr, base));
}

// c.jalr
Inst::Jalr { rd, base, offset }
if has_zca
&& rd.to_reg() == link_reg()
&& base != zero_reg()
&& offset.as_i16() == 0 =>
if rd.to_reg() == link_reg() && base != zero_reg() && offset.as_i16() == 0 =>
{
sink.put2(encode_cr2_type(CrOp::CJalr, base));
}

// c.ebreak
Inst::EBreak => {
sink.put2(encode_cr_type(
CrOp::CEbreak,
writable_zero_reg(),
zero_reg(),
));
}

// c.unimp
Inst::Udf { trap_code } => {
sink.add_trap(trap_code);
if let Some(s) = state.take_stack_map() {
sink.add_stack_map(StackMapExtent::UpcomingBytes(2), s);
}
sink.put2(0x0000);
}

// c.addi16sp
//
// c.addi16sp shares the opcode with c.lui, but has a destination field of x2.
// c.addi16sp adds the non-zero sign-extended 6-bit immediate to the value in the stack pointer (sp=x2),
// where the immediate is scaled to represent multiples of 16 in the range (-512,496). c.addi16sp is used
// to adjust the stack pointer in procedure prologues and epilogues. It expands into addi x2, x2, nzimm. c.addi16sp
// is only valid when nzimm≠0; the code point with nzimm=0 is reserved.
Inst::AluRRImm12 {
alu_op: AluOPRRI::Addi,
rd,
rs,
imm12,
} if rd.to_reg() == rs
&& rs == stack_reg()
&& imm12.as_i16() != 0
&& (imm12.as_i16() % 16) == 0
&& Imm6::maybe_from_i16(imm12.as_i16() / 16).is_some() =>
{
let imm6 = Imm6::maybe_from_i16(imm12.as_i16() / 16).unwrap();
sink.put2(encode_c_addi16sp(imm6));
}

// c.addi4spn
//
// c.addi4spn is a CIW-format instruction that adds a zero-extended non-zero
// immediate, scaled by 4, to the stack pointer, x2, and writes the result to
// rd. This instruction is used to generate pointers to stack-allocated variables
// and expands to addi rd, x2, nzuimm. c.addi4spn is only valid when nzuimm≠0;
// the code points with nzuimm=0 are reserved.
Inst::AluRRImm12 {
alu_op: AluOPRRI::Addi,
rd,
rs,
imm12,
} if reg_is_compressible(rd.to_reg())
&& rs == stack_reg()
&& imm12.as_i16() != 0
&& (imm12.as_i16() % 4) == 0
&& u8::try_from(imm12.as_i16() / 4).is_ok() =>
{
let imm = u8::try_from(imm12.as_i16() / 4).unwrap();
sink.put2(encode_ciw_type(CiwOp::CAddi4spn, rd, imm));
}

// c.addi
Inst::AluRRImm12 {
alu_op: AluOPRRI::Addi,
rd,
rs,
imm12,
} if rd.to_reg() == rs && rs != zero_reg() && imm12.as_i16() != 0 => {
let imm6 = match Imm6::maybe_from_imm12(imm12) {
Some(imm6) => imm6,
None => return false,
};

sink.put2(encode_ci_type(CiOp::CAddi, rd, imm6));
}

// c.addiw
Inst::AluRRImm12 {
alu_op: AluOPRRI::Addiw,
rd,
rs,
imm12,
} if rd.to_reg() == rs && rs != zero_reg() => {
let imm6 = match Imm6::maybe_from_imm12(imm12) {
Some(imm6) => imm6,
None => return false,
};
sink.put2(encode_ci_type(CiOp::CAddiw, rd, imm6));
}

// c.slli
Inst::AluRRImm12 {
alu_op: AluOPRRI::Slli,
rd,
rs,
imm12,
} if rd.to_reg() == rs && rs != zero_reg() && imm12.as_i16() != 0 => {
// The shift amount is unsigned, but we encode it as signed.
let shift = imm12.as_i16() & 0x3f;
let imm6 = Imm6::maybe_from_i16(shift << 10 >> 10).unwrap();
sink.put2(encode_ci_type(CiOp::CSlli, rd, imm6));
}
_ => return false,
}

Expand Down Expand Up @@ -2045,7 +2143,10 @@ impl Inst {
&Inst::Udf { trap_code } => {
sink.add_trap(trap_code);
if let Some(s) = state.take_stack_map() {
sink.add_stack_map(StackMapExtent::UpcomingBytes(4), s);
sink.add_stack_map(
StackMapExtent::UpcomingBytes(Inst::TRAP_OPCODE.len() as u32),
s,
);
}
sink.put_data(Inst::TRAP_OPCODE);
}
Expand Down
58 changes: 56 additions & 2 deletions cranelift/codegen/src/isa/riscv64/inst/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
use super::*;
use crate::isa::riscv64::inst::reg_to_gpr_num;
use crate::isa::riscv64::lower::isle::generated_code::{
CaOp, CjOp, CrOp, VecAluOpRImm5, VecAluOpRR, VecAluOpRRImm5, VecAluOpRRR, VecAluOpRRRImm5,
VecAluOpRRRR, VecElementWidth, VecOpCategory, VecOpMasking,
CaOp, CiOp, CiwOp, CjOp, CrOp, VecAluOpRImm5, VecAluOpRR, VecAluOpRRImm5, VecAluOpRRR,
VecAluOpRRRImm5, VecAluOpRRRR, VecElementWidth, VecOpCategory, VecOpMasking,
};
use crate::machinst::isle::WritableReg;
use crate::Reg;
Expand Down Expand Up @@ -388,3 +388,57 @@ pub fn encode_cj_type(op: CjOp, imm: Imm12) -> u16 {
bits |= unsigned_field_width(op.funct3(), 3) << 13;
bits.try_into().unwrap()
}

// Encode a CI type instruction.
//
// The imm field is a 6 bit signed immediate.
//
// 0--1-2-------6-7-------11-12-----12-13-----15
// |op | imm[4:0] | src | imm[5] | funct3 |
pub fn encode_ci_type(op: CiOp, rd: WritableReg, imm: Imm6) -> u16 {
let imm = imm.bits();

let mut bits = 0;
bits |= unsigned_field_width(op.op().bits(), 2);
bits |= unsigned_field_width((imm & 0x1f) as u32, 5) << 2;
bits |= reg_to_gpr_num(rd.to_reg()) << 7;
bits |= unsigned_field_width(((imm >> 5) & 1) as u32, 1) << 12;
bits |= unsigned_field_width(op.funct3(), 3) << 13;
bits.try_into().unwrap()
}

/// c.addi16sp is a regular CI op, but the immediate field is encoded in a weird way
pub fn encode_c_addi16sp(imm: Imm6) -> u16 {
let imm = imm.bits();

// [6|1|3|5:4|2]
let mut enc_imm = 0;
enc_imm |= ((imm >> 5) & 1) << 5;
enc_imm |= ((imm >> 0) & 1) << 4;
enc_imm |= ((imm >> 2) & 1) << 3;
enc_imm |= ((imm >> 3) & 3) << 1;
enc_imm |= ((imm >> 1) & 1) << 0;
let enc_imm = Imm6::maybe_from_i16((enc_imm as i16) << 10 >> 10).unwrap();

encode_ci_type(CiOp::CAddi16sp, writable_stack_reg(), enc_imm)
}

// Encode a CIW type instruction.
//
// 0--1-2------4-5------12-13--------15
// |op | rd | imm | funct3 |
pub fn encode_ciw_type(op: CiwOp, rd: WritableReg, imm: u8) -> u16 {
// [3:2|7:4|0|1]
let mut imm_field = 0;
imm_field |= ((imm >> 1) & 1) << 0;
imm_field |= ((imm >> 0) & 1) << 1;
imm_field |= ((imm >> 4) & 7) << 2;
imm_field |= ((imm >> 2) & 3) << 6;

let mut bits = 0;
bits |= unsigned_field_width(op.op().bits(), 2);
bits |= reg_to_compressed_gpr_num(rd.to_reg()) << 2;
bits |= unsigned_field_width(imm_field as u32, 8) << 5;
bits |= unsigned_field_width(op.funct3(), 3) << 13;
bits.try_into().unwrap()
}
Loading

0 comments on commit 0c57ab5

Please sign in to comment.