add riscv64 backend for cranelift. (#4271)

Add a RISC-V 64 (`riscv64`, RV64GC) backend.

Co-authored-by: yuyang <756445638@qq.com>
Co-authored-by: Chris Fallin <chris@cfallin.org>
Co-authored-by: Afonso Bordado <afonsobordado@az8.co>
This commit is contained in:
yuyang-ok
2022-09-28 08:30:31 +08:00
committed by GitHub
parent 9715d91c50
commit cdecc858b4
182 changed files with 21024 additions and 36 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,218 @@
//! Riscv64 ISA definitions: immediate constants.
// Some variants are never constructed, but we still want them as options in the future.
use super::Inst;
#[allow(dead_code)]
use std::fmt::{Debug, Display, Formatter, Result};
#[derive(Copy, Clone, Debug, Default)]
pub struct Imm12 {
pub bits: i16,
}
impl Imm12 {
pub(crate) const FALSE: Self = Self { bits: 0 };
pub(crate) const TRUE: Self = Self { bits: -1 };
pub fn maybe_from_u64(val: u64) -> Option<Imm12> {
let sign_bit = 1 << 11;
if val == 0 {
Some(Imm12 { bits: 0 })
} else if (val & sign_bit) != 0 && (val >> 12) == 0xffff_ffff_ffff_f {
Some(Imm12 {
bits: (val & 0xffff) as i16,
})
} else if (val & sign_bit) == 0 && (val >> 12) == 0 {
Some(Imm12 {
bits: (val & 0xffff) as i16,
})
} else {
None
}
}
#[inline]
pub fn from_bits(bits: i16) -> Self {
Self { bits: bits & 0xfff }
}
/// Create a zero immediate of this format.
#[inline]
pub fn zero() -> Self {
Imm12 { bits: 0 }
}
#[inline]
pub fn as_i16(self) -> i16 {
self.bits
}
#[inline]
pub fn as_u32(&self) -> u32 {
(self.bits as u32) & 0xfff
}
}
impl Into<i64> for Imm12 {
fn into(self) -> i64 {
self.bits as i64
}
}
impl Display for Imm12 {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{:+}", self.bits)
}
}
impl std::ops::Neg for Imm12 {
type Output = Self;
fn neg(self) -> Self::Output {
Self { bits: -self.bits }
}
}
// singed
#[derive(Clone, Copy, Default)]
pub struct Imm20 {
/// The immediate bits.
pub bits: i32,
}
impl Imm20 {
#[inline]
pub fn from_bits(bits: i32) -> Self {
Self {
bits: bits & 0xf_ffff,
}
}
#[inline]
pub fn as_u32(&self) -> u32 {
(self.bits as u32) & 0xf_ffff
}
}
impl Debug for Imm20 {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{}", self.bits)
}
}
impl Display for Imm20 {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{}", self.bits)
}
}
#[derive(Clone, Copy)]
pub struct Uimm5 {
bits: u8,
}
impl Uimm5 {
pub fn from_bits(bits: u8) -> Self {
Self { bits }
}
/// Create a zero immediate of this format.
pub fn zero() -> Self {
Self { bits: 0 }
}
pub fn as_u32(&self) -> u32 {
(self.bits as u32) & 0b1_1111
}
}
impl Debug for Uimm5 {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{}", self.bits)
}
}
impl Display for Uimm5 {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{}", self.bits)
}
}
impl Inst {
pub(crate) fn imm_min() -> i64 {
let imm20_max: i64 = (1 << 19) << 12;
let imm12_max = 1 << 11;
-imm20_max - imm12_max
}
pub(crate) fn imm_max() -> i64 {
let imm20_max: i64 = ((1 << 19) - 1) << 12;
let imm12_max = (1 << 11) - 1;
imm20_max + imm12_max
}
/// An imm20 immediate and an Imm12 immediate can generate a 32-bit immediate.
/// This helper produces an imm12, imm20, or both to generate the value.
///
/// `value` must be between `imm_min()` and `imm_max()`, or else
/// this helper returns `None`.
pub(crate) fn generate_imm<R>(
value: u64,
mut handle_imm: impl FnMut(Option<Imm20>, Option<Imm12>) -> R,
) -> Option<R> {
if let Some(imm12) = Imm12::maybe_from_u64(value) {
// can be load using single imm12.
let r = handle_imm(None, Some(imm12));
return Some(r);
}
let value = value as i64;
if !(value >= Self::imm_min() && value <= Self::imm_max()) {
// not in range, return None.
return None;
}
const MOD_NUM: i64 = 4096;
let (imm20, imm12) = if value > 0 {
let mut imm20 = value / MOD_NUM;
let mut imm12 = value % MOD_NUM;
if imm12 >= 2048 {
imm12 -= MOD_NUM;
imm20 += 1;
}
assert!(imm12 >= -2048 && imm12 <= 2047);
(imm20, imm12)
} else {
// this is the abs value.
let value_abs = value.abs();
let imm20 = value_abs / MOD_NUM;
let imm12 = value_abs % MOD_NUM;
let mut imm20 = -imm20;
let mut imm12 = -imm12;
if imm12 < -2048 {
imm12 += MOD_NUM;
imm20 -= 1;
}
(imm20, imm12)
};
assert!(imm20 >= -(0x7_ffff + 1) && imm20 <= 0x7_ffff);
assert!(imm20 != 0 || imm12 != 0);
Some(handle_imm(
if imm20 != 0 {
Some(Imm20::from_bits(imm20 as i32))
} else {
None
},
if imm12 != 0 {
Some(Imm12::from_bits(imm12 as i16))
} else {
None
},
))
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_imm12() {
let x = Imm12::zero();
assert_eq!(0, x.as_u32());
Imm12::maybe_from_u64(0xffff_ffff_ffff_ffff).unwrap();
}
#[test]
fn imm20_and_imm12() {
assert!(Inst::imm_max() == (i32::MAX - 2048) as i64);
assert!(Inst::imm_min() == i32::MIN as i64 - 2048);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,220 @@
//! Riscv64 ISA definitions: registers.
//!
use crate::settings;
use crate::machinst::{Reg, Writable};
use crate::machinst::RealReg;
use alloc::vec;
use alloc::vec::Vec;
use regalloc2::VReg;
use regalloc2::{MachineEnv, PReg, RegClass};
// first argument of function call
#[inline]
pub fn a0() -> Reg {
x_reg(10)
}
// second argument of function call
#[inline]
pub fn a1() -> Reg {
x_reg(11)
}
// third argument of function call
#[inline]
pub fn a2() -> Reg {
x_reg(12)
}
#[inline]
pub fn writable_a0() -> Writable<Reg> {
Writable::from_reg(a0())
}
#[inline]
pub fn writable_a1() -> Writable<Reg> {
Writable::from_reg(a1())
}
#[inline]
pub fn writable_a2() -> Writable<Reg> {
Writable::from_reg(a2())
}
#[inline]
pub fn fa0() -> Reg {
f_reg(10)
}
#[inline]
pub fn writable_fa0() -> Writable<Reg> {
Writable::from_reg(fa0())
}
#[inline]
pub fn writable_fa1() -> Writable<Reg> {
Writable::from_reg(fa1())
}
#[inline]
pub fn fa1() -> Reg {
f_reg(11)
}
#[inline]
pub fn fa7() -> Reg {
f_reg(17)
}
/// Get a reference to the zero-register.
#[inline]
pub fn zero_reg() -> Reg {
x_reg(0)
}
/// Get a writable reference to the zero-register (this discards a result).
#[inline]
pub fn writable_zero_reg() -> Writable<Reg> {
Writable::from_reg(zero_reg())
}
#[inline]
pub fn stack_reg() -> Reg {
x_reg(2)
}
/// Get a writable reference to the stack-pointer register.
#[inline]
pub fn writable_stack_reg() -> Writable<Reg> {
Writable::from_reg(stack_reg())
}
/// Get a reference to the link register (x1).
pub fn link_reg() -> Reg {
x_reg(1)
}
/// Get a writable reference to the link register.
#[inline]
pub fn writable_link_reg() -> Writable<Reg> {
Writable::from_reg(link_reg())
}
/// Get a reference to the frame pointer (x29).
#[inline]
pub fn fp_reg() -> Reg {
x_reg(8)
}
/// Get a writable reference to the frame pointer.
#[inline]
pub fn writable_fp_reg() -> Writable<Reg> {
Writable::from_reg(fp_reg())
}
/// Get a reference to the first temporary, sometimes "spill temporary",
/// register. This register is used in various ways as a temporary.
#[inline]
pub fn spilltmp_reg() -> Reg {
x_reg(31)
}
/// Get a writable reference to the spilltmp reg.
#[inline]
pub fn writable_spilltmp_reg() -> Writable<Reg> {
Writable::from_reg(spilltmp_reg())
}
///spilltmp2
#[inline]
pub fn spilltmp_reg2() -> Reg {
x_reg(30)
}
/// Get a writable reference to the spilltmp2 reg.
#[inline]
pub fn writable_spilltmp_reg2() -> Writable<Reg> {
Writable::from_reg(spilltmp_reg2())
}
pub fn crate_reg_eviroment(_flags: &settings::Flags) -> MachineEnv {
let preferred_regs_by_class: [Vec<PReg>; 2] = {
let mut x_register: Vec<PReg> = vec![];
x_register.push(PReg::new(5, RegClass::Int));
for i in 6..=7 {
x_register.push(PReg::new(i, RegClass::Int));
}
for i in 10..=17 {
x_register.push(PReg::new(i, RegClass::Int));
}
for i in 28..=29 {
x_register.push(PReg::new(i, RegClass::Int));
}
let mut f_register: Vec<PReg> = vec![];
for i in 0..=7 {
f_register.push(PReg::new(i, RegClass::Float));
}
for i in 10..=17 {
f_register.push(PReg::new(i, RegClass::Float));
}
for i in 28..=31 {
f_register.push(PReg::new(i, RegClass::Float));
}
[x_register, f_register]
};
let non_preferred_regs_by_class: [Vec<PReg>; 2] = {
let mut x_register: Vec<PReg> = vec![];
x_register.push(PReg::new(9, RegClass::Int));
for i in 18..=27 {
x_register.push(PReg::new(i, RegClass::Int));
}
let mut f_register: Vec<PReg> = vec![];
for i in 8..=9 {
f_register.push(PReg::new(i, RegClass::Float));
}
for i in 18..=27 {
f_register.push(PReg::new(i, RegClass::Float));
}
[x_register, f_register]
};
MachineEnv {
preferred_regs_by_class,
non_preferred_regs_by_class,
fixed_stack_slots: vec![],
}
}
#[inline]
pub fn x_reg(enc: usize) -> Reg {
let p_reg = PReg::new(enc, RegClass::Int);
let v_reg = VReg::new(p_reg.index(), p_reg.class());
Reg::from(v_reg)
}
pub fn px_reg(enc: usize) -> PReg {
PReg::new(enc, RegClass::Int)
}
#[inline]
pub fn f_reg(enc: usize) -> Reg {
let p_reg = PReg::new(enc, RegClass::Float);
let v_reg = VReg::new(p_reg.index(), p_reg.class());
Reg::from(v_reg)
}
pub const fn pf_reg(enc: usize) -> PReg {
PReg::new(enc, RegClass::Float)
}
#[inline]
pub(crate) fn real_reg_to_reg(x: RealReg) -> Reg {
let v_reg = VReg::new(x.hw_enc() as usize, x.class());
Reg::from(v_reg)
}
#[allow(dead_code)]
pub(crate) fn x_reg_range(start: usize, end: usize) -> Vec<Writable<Reg>> {
let mut regs = vec![];
for i in start..=end {
regs.push(Writable::from_reg(x_reg(i)));
}
regs
}

View File

@@ -0,0 +1,2 @@
#[cfg(feature = "unwind")]
pub(crate) mod systemv;

View File

@@ -0,0 +1,173 @@
//! Unwind information for System V ABI (Riscv64).
use crate::isa::riscv64::inst::regs;
use crate::isa::unwind::systemv::RegisterMappingError;
use crate::machinst::Reg;
use gimli::{write::CommonInformationEntry, Encoding, Format, Register};
use regalloc2::RegClass;
/// Creates a new riscv64 common information entry (CIE).
pub fn create_cie() -> CommonInformationEntry {
use gimli::write::CallFrameInstruction;
let mut entry = CommonInformationEntry::new(
Encoding {
address_size: 8,
format: Format::Dwarf32,
version: 1,
},
4, // Code alignment factor
-8, // Data alignment factor
Register(regs::link_reg().to_real_reg().unwrap().hw_enc() as u16),
);
// Every frame will start with the call frame address (CFA) at SP
let sp = Register(regs::stack_reg().to_real_reg().unwrap().hw_enc().into());
entry.add_instruction(CallFrameInstruction::Cfa(sp, 0));
entry
}
/// Map Cranelift registers to their corresponding Gimli registers.
pub fn map_reg(reg: Reg) -> Result<Register, RegisterMappingError> {
match reg.class() {
RegClass::Int => {
let reg = reg.to_real_reg().unwrap().hw_enc() as u16;
Ok(Register(reg))
}
RegClass::Float => {
let reg = reg.to_real_reg().unwrap().hw_enc() as u16;
Ok(Register(32 + reg))
}
}
}
pub(crate) struct RegisterMapper;
impl crate::isa::unwind::systemv::RegisterMapper<Reg> for RegisterMapper {
fn map(&self, reg: Reg) -> Result<u16, RegisterMappingError> {
Ok(map_reg(reg)?.0)
}
fn sp(&self) -> u16 {
regs::stack_reg().to_real_reg().unwrap().hw_enc() as u16
}
fn fp(&self) -> Option<u16> {
Some(regs::fp_reg().to_real_reg().unwrap().hw_enc() as u16)
}
fn lr(&self) -> Option<u16> {
Some(regs::link_reg().to_real_reg().unwrap().hw_enc() as u16)
}
fn lr_offset(&self) -> Option<u32> {
Some(8)
}
}
#[cfg(test)]
mod tests {
use crate::cursor::{Cursor, FuncCursor};
use crate::ir::{
types, AbiParam, Function, InstBuilder, Signature, StackSlotData, StackSlotKind,
UserFuncName,
};
use crate::isa::{lookup, CallConv};
use crate::settings::{builder, Flags};
use crate::Context;
use gimli::write::Address;
use std::str::FromStr;
use target_lexicon::triple;
#[test]
fn test_simple_func() {
let isa = lookup(triple!("riscv64"))
.expect("expect riscv64 ISA")
.finish(Flags::new(builder()))
.expect("Creating compiler backend");
let mut context = Context::for_function(create_function(
CallConv::SystemV,
Some(StackSlotData::new(StackSlotKind::ExplicitSlot, 64)),
));
context.compile(&*isa).expect("expected compilation");
let fde = match context
.create_unwind_info(isa.as_ref())
.expect("can create unwind info")
{
Some(crate::isa::unwind::UnwindInfo::SystemV(info)) => {
info.to_fde(Address::Constant(1234))
}
_ => panic!("expected unwind information"),
};
assert_eq!(format!("{:?}", fde), "FrameDescriptionEntry { address: Constant(1234), length: 40, lsda: None, instructions: [(12, CfaOffset(16)), (12, Offset(Register(8), -16)), (12, Offset(Register(1), -8)), (16, CfaRegister(Register(8)))] }");
}
fn create_function(call_conv: CallConv, stack_slot: Option<StackSlotData>) -> Function {
let mut func =
Function::with_name_signature(UserFuncName::user(0, 0), Signature::new(call_conv));
let block0 = func.dfg.make_block();
let mut pos = FuncCursor::new(&mut func);
pos.insert_block(block0);
pos.ins().return_(&[]);
if let Some(stack_slot) = stack_slot {
func.sized_stack_slots.push(stack_slot);
}
func
}
#[test]
fn test_multi_return_func() {
let isa = lookup(triple!("riscv64"))
.expect("expect riscv64 ISA")
.finish(Flags::new(builder()))
.expect("Creating compiler backend");
let mut context = Context::for_function(create_multi_return_function(CallConv::SystemV));
context.compile(&*isa).expect("expected compilation");
let fde = match context
.create_unwind_info(isa.as_ref())
.expect("can create unwind info")
{
Some(crate::isa::unwind::UnwindInfo::SystemV(info)) => {
info.to_fde(Address::Constant(4321))
}
_ => panic!("expected unwind information"),
};
assert_eq!(
format!("{:?}", fde),
"FrameDescriptionEntry { address: Constant(4321), length: 12, lsda: None, instructions: [] }"
);
}
fn create_multi_return_function(call_conv: CallConv) -> Function {
let mut sig = Signature::new(call_conv);
sig.params.push(AbiParam::new(types::I32));
let mut func = Function::with_name_signature(UserFuncName::user(0, 0), sig);
let block0 = func.dfg.make_block();
let v0 = func.dfg.append_block_param(block0, types::I32);
let block1 = func.dfg.make_block();
let block2 = func.dfg.make_block();
let mut pos = FuncCursor::new(&mut func);
pos.insert_block(block0);
pos.ins().brnz(v0, block2, &[]);
pos.ins().jump(block1, &[]);
pos.insert_block(block1);
pos.ins().return_(&[]);
pos.insert_block(block2);
pos.ins().return_(&[]);
func
}
}