start with 64 bit
Some checks failed
Rust / build (push) Has been cancelled

This commit is contained in:
nora 2025-03-26 21:10:48 +01:00
parent 0144740228
commit e9a689aa1a
6 changed files with 449 additions and 193 deletions

View file

@ -5,6 +5,27 @@
use core::fmt::{self, Debug, Display}; use core::fmt::{self, Debug, Display};
use core::ops::RangeInclusive; use core::ops::RangeInclusive;
/// The register size of the ISA, RV32 or RV64.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Xlen {
/// 32 bit
Rv32,
/// 64 bit
Rv64,
}
impl Xlen {
/// Whether this is [`Xlen::Rv32`].
pub fn is_32(self) -> bool {
matches!(self, Self::Rv32)
}
/// Whether this is [`Xlen::Rv64`].
pub fn is_64(self) -> bool {
matches!(self, Self::Rv64)
}
}
/// A decoded RISC-V integer register. /// A decoded RISC-V integer register.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Reg(pub u8); pub struct Reg(pub u8);
@ -109,7 +130,7 @@ impl Display for Reg {
/// ///
/// This type is XLEN-agnostic, use the XLEN-specific accessors to get the correct value. /// This type is XLEN-agnostic, use the XLEN-specific accessors to get the correct value.
#[derive(Copy, Clone, PartialEq, Eq, Hash)] #[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct Imm(u32); pub struct Imm(u64);
impl Imm { impl Imm {
/// The immediate `0`. /// The immediate `0`.
@ -118,23 +139,33 @@ impl Imm {
/// Create a new immediate from the (if necessary) sign-extended value. /// Create a new immediate from the (if necessary) sign-extended value.
pub const fn new_i32(value: i32) -> Self { pub const fn new_i32(value: i32) -> Self {
Self(value as u32) Self(value as i64 as u64)
} }
/// Create a new immediate from the (if necessary) zero-extended value. /// Create a new immediate from the (if necessary) zero-extended value.
pub const fn new_u32(value: u32) -> Self { pub const fn new_u32(value: u32) -> Self {
Self(value) Self(value as u64)
} }
/// Get the `u32` (RV32) value of the immediate. /// Get the `u32` (RV32) value of the immediate.
pub const fn as_u32(self) -> u32 { pub const fn as_u32(self) -> u32 {
self.0 self.0 as u32
} }
/// Get the `i32` (RV32) value of the immediate. /// Get the `i32` (RV32) value of the immediate.
pub const fn as_i32(self) -> i32 { pub const fn as_i32(self) -> i32 {
self.0 as i32 self.0 as i32
} }
/// Get the `u64` (RV64) value of the immediate.
pub const fn as_u64(self) -> u64 {
self.0 as u64
}
/// Get the `i64` (RV64) value of the immediate.
pub const fn as_i64(self) -> i64 {
self.0 as i64
}
} }
impl From<i32> for Imm { impl From<i32> for Imm {
@ -216,6 +247,8 @@ pub enum Inst {
/// Add Immediate /// Add Immediate
Addi { imm: Imm, dest: Reg, src1: Reg }, Addi { imm: Imm, dest: Reg, src1: Reg },
/// Add Immediate 32-bit (**RV64 only**)
AddiW { imm: Imm, dest: Reg, src1: Reg },
/// Set Less Than Immediate (signed) /// Set Less Than Immediate (signed)
Slti { imm: Imm, dest: Reg, src1: Reg }, Slti { imm: Imm, dest: Reg, src1: Reg },
/// Set Less Than Immediate Unsigned /// Set Less Than Immediate Unsigned
@ -228,10 +261,16 @@ pub enum Inst {
Andi { imm: Imm, dest: Reg, src1: Reg }, Andi { imm: Imm, dest: Reg, src1: Reg },
/// Shift Left Logical Immediate /// Shift Left Logical Immediate
Slli { imm: Imm, dest: Reg, src1: Reg }, Slli { imm: Imm, dest: Reg, src1: Reg },
/// Shift Left Logical Immediate 32-bit (**RV64 only**)
SlliW { imm: Imm, dest: Reg, src1: Reg },
/// Shift Right Logical Immediate (unsigned) /// Shift Right Logical Immediate (unsigned)
Srli { imm: Imm, dest: Reg, src1: Reg }, Srli { imm: Imm, dest: Reg, src1: Reg },
/// Shift Right Logical Immediate (unsigned) 32-bit (**RV64 only**)
SrliW { imm: Imm, dest: Reg, src1: Reg },
/// Shift Right Arithmetic Immediate (signed) /// Shift Right Arithmetic Immediate (signed)
Srai { imm: Imm, dest: Reg, src1: Reg }, Srai { imm: Imm, dest: Reg, src1: Reg },
/// Shift Right Arithmetic Immediate (signed) 32-bit (**RV64 only**)
SraiW { imm: Imm, dest: Reg, src1: Reg },
/// Add /// Add
Add { dest: Reg, src1: Reg, src2: Reg }, Add { dest: Reg, src1: Reg, src2: Reg },
@ -502,6 +541,13 @@ impl Display for Inst {
write!(f, "addi {dest}, {src1}, {}", imm.as_i32()) write!(f, "addi {dest}, {src1}, {}", imm.as_i32())
} }
} }
Inst::AddiW { imm, dest, src1 } => {
if imm.as_u32() == 0 {
write!(f, "sext.w {dest}, {src1}")
} else {
write!(f, "addi.w {dest}, {src1}, {}", imm.as_i32())
}
}
Inst::Slti { Inst::Slti {
imm, imm,
dest, dest,
@ -532,16 +578,31 @@ impl Display for Inst {
dest, dest,
src1: rs1, src1: rs1,
} => write!(f, "slli {dest}, {rs1}, {}", imm.as_i32()), } => write!(f, "slli {dest}, {rs1}, {}", imm.as_i32()),
Inst::SlliW {
imm,
dest,
src1: rs1,
} => write!(f, "slliw {dest}, {rs1}, {}", imm.as_i32()),
Inst::Srli { Inst::Srli {
imm, imm,
dest, dest,
src1: rs1, src1: rs1,
} => write!(f, "srli {dest}, {rs1}, {}", imm.as_i32()), } => write!(f, "srli {dest}, {rs1}, {}", imm.as_i32()),
Inst::SrliW {
imm,
dest,
src1: rs1,
} => write!(f, "srliw {dest}, {rs1}, {}", imm.as_i32()),
Inst::Srai { Inst::Srai {
imm, imm,
dest, dest,
src1: rs1, src1: rs1,
} => write!(f, "srai {dest}, {rs1}, {}", imm.as_i32()), } => write!(f, "srai {dest}, {rs1}, {}", imm.as_i32()),
Inst::SraiW {
imm,
dest,
src1: rs1,
} => write!(f, "sraiw {dest}, {rs1}, {}", imm.as_i32()),
Inst::Add { dest, src1, src2 } => { Inst::Add { dest, src1, src2 } => {
write!(f, "add {dest}, {src1}, {src2}") write!(f, "add {dest}, {src1}, {src2}")
} }
@ -847,12 +908,15 @@ impl Inst {
/// If the caller wants to avoid reading more bytes than necessary, [`Self::first_byte_is_compressed`] /// If the caller wants to avoid reading more bytes than necessary, [`Self::first_byte_is_compressed`]
/// can be used to check, read the required bytes, and then call [`Self::decode_compressed`] or /// can be used to check, read the required bytes, and then call [`Self::decode_compressed`] or
/// [`Self::decode_normal`] directly. /// [`Self::decode_normal`] directly.
pub fn decode(code: u32) -> Result<(Inst, IsCompressed), DecodeError> { pub fn decode(code: u32, xlen: Xlen) -> Result<(Inst, IsCompressed), DecodeError> {
let is_compressed = (code & 0b11) != 0b11; let is_compressed = (code & 0b11) != 0b11;
if is_compressed { if is_compressed {
Ok((Self::decode_compressed(code as u16)?, IsCompressed::Yes)) Ok((
Self::decode_compressed(code as u16, xlen)?,
IsCompressed::Yes,
))
} else { } else {
Ok((Self::decode_normal(code)?, IsCompressed::No)) Ok((Self::decode_normal(code, xlen)?, IsCompressed::No))
} }
} }
@ -867,7 +931,7 @@ impl Inst {
/// let inst = rvdc::Inst::decode_compressed(x).unwrap(); /// let inst = rvdc::Inst::decode_compressed(x).unwrap();
/// assert_eq!(inst, expected); /// assert_eq!(inst, expected);
/// ``` /// ```
pub fn decode_compressed(code: u16) -> Result<Inst, DecodeError> { pub fn decode_compressed(code: u16, xlen: Xlen) -> Result<Inst, DecodeError> {
let code = InstCodeC(code); let code = InstCodeC(code);
if code.0 == 0 { if code.0 == 0 {
return Err(decode_error(code, "null instruction")); return Err(decode_error(code, "null instruction"));
@ -1146,7 +1210,7 @@ impl Inst {
} }
/// Decode a normal (not compressed) instruction. /// Decode a normal (not compressed) instruction.
pub fn decode_normal(code: u32) -> Result<Inst, DecodeError> { pub fn decode_normal(code: u32, xlen: Xlen) -> Result<Inst, DecodeError> {
let code = InstCode(code); let code = InstCode(code);
let inst = match code.opcode() { let inst = match code.opcode() {
// LUI // LUI
@ -1312,6 +1376,47 @@ impl Inst {
}, },
_ => return Err(decode_error(code, "OP-IMM funct3")), _ => return Err(decode_error(code, "OP-IMM funct3")),
}, },
// OP-IMM-32
0b0011011 => {
if !xlen.is_64() {
return Err(decode_error(code, "OP-IMM-32 only on RV64"));
}
match code.funct3() {
0b000 => Inst::AddiW {
imm: code.imm_i(),
dest: code.rd(),
src1: code.rs1(),
},
// SLLIW
0b001 => {
if code.funct7() != 0 {
return Err(decode_error(code, "SLLIW funct7"));
}
Inst::SlliW {
imm: Imm::new_u32(code.rs2_imm()),
dest: code.rd(),
src1: code.rs1(),
}
}
0b101 => match code.funct7() {
0b0000000 => Inst::SrliW {
imm: Imm::new_u32(code.rs2_imm()),
dest: code.rd(),
src1: code.rs1(),
},
0b0100000 => Inst::SraiW {
imm: Imm::new_u32(code.rs2_imm()),
dest: code.rd(),
src1: code.rs1(),
},
_ => return Err(decode_error(code, "OP-IMM-32 funct7")),
},
_ => return Err(decode_error(code, "OP-IMM-32 funct3")),
}
}
// OP // OP
0b0110011 => { 0b0110011 => {
let (dest, src1, src2) = (code.rd(), code.rs1(), code.rs2()); let (dest, src1, src2) = (code.rd(), code.rs1(), code.rs2());
@ -1465,6 +1570,7 @@ mod tests {
use crate::Imm; use crate::Imm;
use crate::Inst; use crate::Inst;
use crate::Reg; use crate::Reg;
use crate::Xlen;
#[test] #[test]
#[cfg_attr(not(slow_tests), ignore = "cfg(slow_tests) not enabled")] #[cfg_attr(not(slow_tests), ignore = "cfg(slow_tests) not enabled")]
@ -1476,9 +1582,20 @@ mod tests {
std::print!("\r{}{}", "#".repeat(done), "-".repeat(100 - done)); std::print!("\r{}{}", "#".repeat(done), "-".repeat(100 - done));
std::io::stdout().flush().unwrap(); std::io::stdout().flush().unwrap();
} }
let _ = Inst::decode(i); let _ = Inst::decode(i, Xlen::Rv32);
} }
let _ = Inst::decode(u32::MAX); let _ = Inst::decode(u32::MAX, Xlen::Rv32);
for i in 0..u32::MAX {
if (i % (2 << 25)) == 0 {
let percent = i as f32 / (u32::MAX as f32);
let done = (100.0 * percent) as usize;
std::print!("\r{}{}", "#".repeat(done), "-".repeat(100 - done));
std::io::stdout().flush().unwrap();
}
let _ = Inst::decode(i, Xlen::Rv64);
}
let _ = Inst::decode(u32::MAX, Xlen::Rv64);
} }
#[test] #[test]
@ -1591,7 +1708,7 @@ mod tests {
let start_time = std::time::Instant::now(); let start_time = std::time::Instant::now();
let insts = (start..=start.saturating_add(CHUNK_SIZE)) let insts = (start..=start.saturating_add(CHUNK_SIZE))
.filter_map(|code| Some((code, Inst::decode_normal(code).ok()?))) .filter_map(|code| Some((code, Inst::decode_normal(code, Xlen::Rv32).ok()?)))
.filter(|(_, inst)| is_inst_supposed_to_roundtrip(inst)) .filter(|(_, inst)| is_inst_supposed_to_roundtrip(inst))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -1631,7 +1748,7 @@ mod tests {
#[ignore = "this doesn't quite work yet because there is often a non-canonical encoding"] #[ignore = "this doesn't quite work yet because there is often a non-canonical encoding"]
fn compressed_clang_roundtrip() { fn compressed_clang_roundtrip() {
let insts = (0..=u16::MAX) let insts = (0..=u16::MAX)
.filter_map(|code| Some((code, Inst::decode_compressed(code).ok()?))) .filter_map(|code| Some((code, Inst::decode_compressed(code, Xlen::Rv32).ok()?)))
.filter(|(_, inst)| is_compressed_inst_supposed_to_roundtrip(inst)) .filter(|(_, inst)| is_compressed_inst_supposed_to_roundtrip(inst))
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View file

@ -32,18 +32,30 @@ pub struct Header {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct Phdr32 { pub struct Phdr {
pub p_type: u32, pub p_type: u32,
pub p_offset: u32, pub p_offset: Offset,
pub p_vaddr: u32, pub p_vaddr: Addr,
pub p_paddr: u32, pub p_paddr: Addr,
pub p_filesz: u32, pub p_filesz: u64,
pub p_memsz: u32, pub p_memsz: u64,
pub p_flags: u32, pub p_flags: u32,
pub p_align: u32, pub p_align: u64,
} }
impl Elf<'_> { impl Elf<'_> {
fn class(&self) -> Result<ElfClass> {
let (_, class) = self.content.split_u32()?;
let (class, _) = class.split_bytes(1)?;
Ok(match class[0] {
// ELFCLASS32
1 => ElfClass::Elf32,
// ELFCLASS64
2 => ElfClass::Elf64,
_ => bail!("not a ELF32 or ELF64 file (EI_CLASS={})", class[0]),
})
}
pub fn header(&self) -> Result<Header> { pub fn header(&self) -> Result<Header> {
let (ident, rest) = self.content.split_bytes(16)?; let (ident, rest) = self.content.split_bytes(16)?;
if ident[..4] != *b"\x7fELF" { if ident[..4] != *b"\x7fELF" {
@ -140,8 +152,9 @@ impl Elf<'_> {
}) })
} }
pub fn segments_32(&self) -> Result<Vec<Phdr32>> { pub fn segments(&self) -> Result<Vec<Phdr>> {
let header = self.header()?; let header = self.header()?;
let class = self.class()?;
let (_, phdrs) = self.content.split_bytes(header.e_phoff.0 as usize)?; let (_, phdrs) = self.content.split_bytes(header.e_phoff.0 as usize)?;
let (mut phdrs, _) = phdrs.split_bytes((header.e_phentsize * header.e_phnum) as usize)?; let (mut phdrs, _) = phdrs.split_bytes((header.e_phentsize * header.e_phnum) as usize)?;
@ -152,6 +165,8 @@ impl Elf<'_> {
let phdr; let phdr;
(phdr, phdrs) = phdrs.split_bytes(header.e_phentsize as usize)?; (phdr, phdrs) = phdrs.split_bytes(header.e_phentsize as usize)?;
let phdr = match class {
ElfClass::Elf32 => {
let (p_type, phdr) = phdr.split_u32()?; let (p_type, phdr) = phdr.split_u32()?;
let (p_offset, phdr) = phdr.split_u32()?; let (p_offset, phdr) = phdr.split_u32()?;
let (p_vaddr, phdr) = phdr.split_u32()?; let (p_vaddr, phdr) = phdr.split_u32()?;
@ -161,16 +176,40 @@ impl Elf<'_> {
let (p_flags, phdr) = phdr.split_u32()?; let (p_flags, phdr) = phdr.split_u32()?;
let (p_align, _) = phdr.split_u32()?; let (p_align, _) = phdr.split_u32()?;
parsed_phdrs.push(Phdr32 { Phdr {
p_type, p_type,
p_offset, p_offset: Offset(p_offset as u64),
p_vaddr, p_vaddr: Addr(p_vaddr as u64),
p_paddr, p_paddr: Addr(p_paddr as u64),
p_filesz: p_filesz as u64,
p_memsz: p_memsz as u64,
p_flags,
p_align: p_align as u64,
}
}
ElfClass::Elf64 => {
let (p_type, phdr) = phdr.split_u32()?;
let (p_flags, phdr) = phdr.split_u32()?;
let (p_offset, phdr) = phdr.split_u64()?;
let (p_vaddr, phdr) = phdr.split_u64()?;
let (p_paddr, phdr) = phdr.split_u64()?;
let (p_filesz, phdr) = phdr.split_u64()?;
let (p_memsz, phdr) = phdr.split_u64()?;
let (p_align, _) = phdr.split_u64()?;
Phdr {
p_type,
p_offset: Offset(p_offset),
p_vaddr: Addr(p_vaddr),
p_paddr: Addr(p_paddr),
p_filesz, p_filesz,
p_memsz, p_memsz,
p_flags, p_flags,
p_align, p_align,
}); }
}
};
parsed_phdrs.push(phdr);
} }
Ok(parsed_phdrs) Ok(parsed_phdrs)

View file

@ -20,44 +20,50 @@ impl Memory {
Ok(()) Ok(())
} }
} }
pub fn slice<XLEN: XLen>(&self, addr: XLEN, len: u32) -> Result<&[u8], Status> { pub fn slice<XLEN: XLen>(&self, addr: XLEN, len: XLEN) -> Result<&[u8], Status> {
self.mem self.mem
.get((addr.as_usize())..) .get((addr.as_usize())..)
.ok_or(Status::InvalidMemoryAccess(addr.as_usize()))? .ok_or(Status::InvalidMemoryAccess(addr.as_usize()))?
.get(..(len as usize)) .get(..(len.as_usize()))
.ok_or(Status::InvalidMemoryAccess(addr.as_usize())) .ok_or(Status::InvalidMemoryAccess(addr.as_usize()))
} }
pub fn slice_mut<XLEN: XLen>(&mut self, addr: XLEN, len: u32) -> Result<&mut [u8], Status> { pub fn slice_mut<XLEN: XLen>(&mut self, addr: XLEN, len: XLEN) -> Result<&mut [u8], Status> {
self.mem self.mem
.get_mut((addr.as_usize())..) .get_mut((addr.as_usize())..)
.ok_or(Status::InvalidMemoryAccess(addr.as_usize()))? .ok_or(Status::InvalidMemoryAccess(addr.as_usize()))?
.get_mut(..(len as usize)) .get_mut(..(len.as_usize()))
.ok_or(Status::InvalidMemoryAccess(addr.as_usize())) .ok_or(Status::InvalidMemoryAccess(addr.as_usize()))
} }
pub fn load_u8<XLEN: XLen>(&self, addr: XLEN) -> Result<u8, Status> { pub fn load_u8<XLEN: XLen>(&self, addr: XLEN) -> Result<u8, Status> {
Ok(u8::from_le_bytes(self.slice(addr, 1)?.try_into().unwrap())) Ok(u8::from_le_bytes(
self.slice(addr, XLEN::from_32_z(1))?.try_into().unwrap(),
))
} }
pub fn load_u16<XLEN: XLen>(&self, addr: XLEN) -> Result<u16, Status> { pub fn load_u16<XLEN: XLen>(&self, addr: XLEN) -> Result<u16, Status> {
Ok(u16::from_le_bytes(self.slice(addr, 2)?.try_into().unwrap())) Ok(u16::from_le_bytes(
self.slice(addr, XLEN::from_32_z(2))?.try_into().unwrap(),
))
} }
pub fn load_u32<XLEN: XLen>(&self, addr: XLEN) -> Result<u32, Status> { pub fn load_u32<XLEN: XLen>(&self, addr: XLEN) -> Result<u32, Status> {
Ok(u32::from_le_bytes(self.slice(addr, 4)?.try_into().unwrap())) Ok(u32::from_le_bytes(
self.slice(addr, XLEN::from_32_z(4))?.try_into().unwrap(),
))
} }
pub fn store_u8<XLEN: XLen>(&mut self, addr: XLEN, value: u8) -> Result<(), Status> { pub fn store_u8<XLEN: XLen>(&mut self, addr: XLEN, value: u8) -> Result<(), Status> {
self.slice_mut(addr, 1)? self.slice_mut(addr, XLEN::from_32_z(1))?
.copy_from_slice(&value.to_le_bytes()); .copy_from_slice(&value.to_le_bytes());
Ok(()) Ok(())
} }
pub fn store_u16<XLEN: XLen>(&mut self, addr: XLEN, value: u16) -> Result<(), Status> { pub fn store_u16<XLEN: XLen>(&mut self, addr: XLEN, value: u16) -> Result<(), Status> {
self.check_align(addr, 2)?; self.check_align(addr, 2)?;
self.slice_mut(addr, 2)? self.slice_mut(addr, XLEN::from_32_z(2))?
.copy_from_slice(&value.to_le_bytes()); .copy_from_slice(&value.to_le_bytes());
Ok(()) Ok(())
} }
pub fn store_u32<XLEN: XLen>(&mut self, addr: XLEN, value: u32) -> Result<(), Status> { pub fn store_u32<XLEN: XLen>(&mut self, addr: XLEN, value: u32) -> Result<(), Status> {
self.check_align(addr, 4)?; self.check_align(addr, 4)?;
self.slice_mut(addr, 4)? self.slice_mut(addr, XLEN::from_32_z(4))?
.copy_from_slice(&value.to_le_bytes()); .copy_from_slice(&value.to_le_bytes());
Ok(()) Ok(())
} }
@ -139,6 +145,28 @@ fn hash_color(value: usize) -> impl Display {
} }
impl<XLEN: XLen> Emulator<XLEN> { impl<XLEN: XLen> Emulator<XLEN> {
pub fn new(
mem: Memory,
start: XLEN,
break_addr: XLEN,
debug: bool,
ecall_handler: Box<dyn FnMut(&mut Memory, &mut [XLEN; 32]) -> Result<(), Status>>,
) -> Self {
Self {
mem,
xreg: [XLEN::ZERO; 32],
xreg0_value: XLEN::ZERO,
pc: start,
reservation_set: None,
is_breaking: false,
break_pc: break_addr,
debug,
ecall_handler,
}
}
pub fn start_linux(&mut self) -> Status { pub fn start_linux(&mut self) -> Status {
self.setup_linux_stack().unwrap(); self.setup_linux_stack().unwrap();
@ -210,7 +238,7 @@ impl<XLEN: XLen> Emulator<XLEN> {
print!("0x{:x} ", self.pc.as_usize()); print!("0x{:x} ", self.pc.as_usize());
} }
let (inst, was_compressed) = Inst::decode(code)?; let (inst, was_compressed) = Inst::decode(code, XLEN::XLEN)?;
if self.debug { if self.debug {
println!( println!(
@ -649,11 +677,21 @@ impl<XLEN: XLen> Emulator<XLEN> {
} }
pub trait XLen: Copy + PartialEq + Eq { pub trait XLen: Copy + PartialEq + Eq {
type Signed;
type NextUnsigned;
type NextSigned;
const XLEN: rvdc::Xlen;
const ZERO: Self; const ZERO: Self;
const SIGNED_MIN: Self; const SIGNED_MIN: Self;
const MAX: Self; const MAX: Self;
fn from_bool(v: bool) -> Self; fn switch<R>(self, on_32: impl FnOnce(u32) -> R, on_64: impl FnOnce(u64) -> R) -> R;
fn from_bool(v: bool) -> Self {
Self::from_32_z(v as u32)
}
fn from_8_z(v: u8) -> Self { fn from_8_z(v: u8) -> Self {
Self::from_32_z(v as u32) Self::from_32_z(v as u32)
} }
@ -704,29 +742,17 @@ pub trait XLen: Copy + PartialEq + Eq {
fn unsigned_rem(self, other: Self) -> Self; fn unsigned_rem(self, other: Self) -> Self;
} }
impl XLen for u32 { macro_rules! xlen_impl {
() => {
const ZERO: Self = 0; const ZERO: Self = 0;
const SIGNED_MIN: Self = i32::MIN as u32; const SIGNED_MIN: Self = Self::Signed::MIN as Self;
const MAX: Self = u32::MAX; const MAX: Self = Self::MAX;
fn from_bool(v: bool) -> Self {
v as u32
}
fn from_32_s(v: u32) -> Self {
v
}
fn from_32_z(v: u32) -> Self {
v
}
fn from_imm(v: Imm) -> Self {
v.as_u32()
}
fn as_usize(self) -> usize { fn as_usize(self) -> usize {
self as usize self as usize
} }
fn truncate32(self) -> u32 { fn truncate32(self) -> u32 {
self self as u32
} }
fn add(self, other: Self) -> Self { fn add(self, other: Self) -> Self {
@ -745,13 +771,13 @@ impl XLen for u32 {
self ^ other self ^ other
} }
fn signed_lt(self, other: Self) -> bool { fn signed_lt(self, other: Self) -> bool {
(self as i32) < (other as i32) (self as Self::Signed) < (other as Self::Signed)
} }
fn unsigned_lt(self, other: Self) -> bool { fn unsigned_lt(self, other: Self) -> bool {
self < other self < other
} }
fn signed_ge(self, other: Self) -> bool { fn signed_ge(self, other: Self) -> bool {
(self as i32) >= (other as i32) (self as Self::Signed) >= (other as Self::Signed)
} }
fn unsigned_ge(self, other: Self) -> bool { fn unsigned_ge(self, other: Self) -> bool {
self >= other self >= other
@ -763,42 +789,93 @@ impl XLen for u32 {
self.wrapping_shr(other) self.wrapping_shr(other)
} }
fn signed_shr(self, other: u32) -> Self { fn signed_shr(self, other: u32) -> Self {
((self as i32).wrapping_shr(other)) as u32 ((self as Self::Signed).wrapping_shr(other)) as Self
} }
fn signed_min(self, other: Self) -> Self { fn signed_min(self, other: Self) -> Self {
(self as i32).min(other as i32) as u32 (self as Self::Signed).min(other as Self::Signed) as Self
} }
fn unsigned_min(self, other: Self) -> Self { fn unsigned_min(self, other: Self) -> Self {
self.min(other) self.min(other)
} }
fn signed_max(self, other: Self) -> Self { fn signed_max(self, other: Self) -> Self {
(self as i32).max(other as i32) as u32 (self as Self::Signed).max(other as Self::Signed) as Self
} }
fn unsigned_max(self, other: Self) -> Self { fn unsigned_max(self, other: Self) -> Self {
self.max(other) self.max(other)
} }
fn mul_lower(self, other: Self) -> Self { fn mul_lower(self, other: Self) -> Self {
(self as i32).wrapping_mul(other as i32) as u32 (self as Self::Signed).wrapping_mul(other as Self::Signed) as Self
} }
fn signed_mul_upper(self, other: Self) -> Self { fn signed_mul_upper(self, other: Self) -> Self {
let mul_result = (self as i32 as i64).wrapping_mul(other as i32 as i64); let mul_result = (self as Self::Signed as Self::NextSigned)
let shifted = (mul_result as u64) >> 32; .wrapping_mul(other as Self::Signed as Self::NextSigned);
shifted as u32 let shifted = (mul_result as Self::NextUnsigned) >> Self::BITS;
shifted as Self
} }
fn unsigned_mul_upper(self, other: Self) -> Self { fn unsigned_mul_upper(self, other: Self) -> Self {
let shifted = ((self as u64).wrapping_mul(other as u64)) >> 32; let shifted = ((self as Self::NextUnsigned).wrapping_mul(other as Self::NextUnsigned))
shifted as u32 >> Self::BITS;
shifted as Self
} }
fn signed_div(self, other: Self) -> Self { fn signed_div(self, other: Self) -> Self {
((self as i32) / (other as i32)) as u32 ((self as Self::Signed) / (other as Self::Signed)) as Self
} }
fn unsigned_div(self, other: Self) -> Self { fn unsigned_div(self, other: Self) -> Self {
self / other self / other
} }
fn signed_rem(self, other: Self) -> Self { fn signed_rem(self, other: Self) -> Self {
((self as i32) % (other as i32)) as u32 ((self as Self::Signed) % (other as Self::Signed)) as Self
} }
fn unsigned_rem(self, other: Self) -> Self { fn unsigned_rem(self, other: Self) -> Self {
self % other self % other
} }
};
}
impl XLen for u32 {
type Signed = i32;
type NextUnsigned = u64;
type NextSigned = i64;
const XLEN: rvdc::Xlen = rvdc::Xlen::Rv32;
fn switch<R>(self, on_32: impl FnOnce(u32) -> R, _: impl FnOnce(u64) -> R) -> R {
on_32(self)
}
fn from_32_s(v: u32) -> Self {
v
}
fn from_32_z(v: u32) -> Self {
v
}
fn from_imm(v: Imm) -> Self {
v.as_u32()
}
xlen_impl!();
}
impl XLen for u64 {
type Signed = i64;
type NextUnsigned = u128;
type NextSigned = i128;
const XLEN: rvdc::Xlen = rvdc::Xlen::Rv64;
fn switch<R>(self, _: impl FnOnce(u32) -> R, on_64: impl FnOnce(u64) -> R) -> R {
on_64(self)
}
fn from_32_s(v: u32) -> Self {
v as i32 as i64 as u64
}
fn from_32_z(v: u32) -> Self {
v as u64
}
fn from_imm(v: Imm) -> Self {
v.as_u64()
}
xlen_impl!();
} }

View file

@ -9,13 +9,14 @@ const MEMORY_SIZE: usize = 2 << 21;
pub fn execute_linux_elf( pub fn execute_linux_elf(
elf: &[u8], elf: &[u8],
debug: bool, debug: bool,
break_addr: u32, break_addr: u64,
ecall_handler: Box<dyn FnMut(&mut emu::Memory, &mut [u32; 32]) -> Result<(), emu::Status>>, ecall_handler32: Box<dyn FnMut(&mut emu::Memory, &mut [u32; 32]) -> Result<(), emu::Status>>,
ecall_handler64: Box<dyn FnMut(&mut emu::Memory, &mut [u64; 32]) -> Result<(), emu::Status>>,
) -> eyre::Result<emu::Status> { ) -> eyre::Result<emu::Status> {
let elf = elf::Elf { content: elf }; let elf = elf::Elf { content: elf };
let header = elf.header()?; let header = elf.header()?;
let segments = elf.segments_32()?; let segments = elf.segments()?;
let mut mem = emu::Memory { let mut mem = emu::Memory {
mem: vec![0; MEMORY_SIZE], mem: vec![0; MEMORY_SIZE],
@ -30,13 +31,13 @@ pub fn execute_linux_elf(
if phdr.p_filesz > 0 { if phdr.p_filesz > 0 {
let contents = &elf let contents = &elf
.content .content
.get((phdr.p_offset as usize)..) .get((phdr.p_offset.0 as usize)..)
.ok_or_eyre("invalid offset")? .ok_or_eyre("invalid offset")?
.get(..(phdr.p_filesz as usize)) .get(..(phdr.p_filesz as usize))
.ok_or_eyre("invalid offset")?; .ok_or_eyre("invalid offset")?;
mem.mem mem.mem
.get_mut((phdr.p_vaddr as usize)..) .get_mut((phdr.p_vaddr.0 as usize)..)
.ok_or_eyre("invalid offset")? .ok_or_eyre("invalid offset")?
.get_mut(..(phdr.p_filesz as usize)) .get_mut(..(phdr.p_filesz as usize))
.ok_or_eyre("invalid offset")? .ok_or_eyre("invalid offset")?
@ -61,19 +62,16 @@ pub fn execute_linux_elf(
let start = header.e_entry; let start = header.e_entry;
let mut emu = emu::Emulator { match header.class {
mem, elf::ElfClass::Elf32 => {
xreg: [0; 32], let mut emu =
xreg0_value: 0, emu::Emulator::<u32>::new(mem, start.0 as u32, break_addr as u32, debug, ecall_handler32);
pc: start.0 as u32,
reservation_set: None,
is_breaking: false,
break_pc: break_addr,
debug,
ecall_handler,
};
Ok(emu.start_linux()) Ok(emu.start_linux())
}
elf::ElfClass::Elf64 => {
let mut emu =
emu::Emulator::<u64>::new(mem, start.0, break_addr, debug, ecall_handler64);
Ok(emu.start_linux())
}
}
} }

View file

@ -1,7 +1,7 @@
use std::io::Write; use std::{cell::RefCell, io::Write, sync::Arc};
use eyre::eyre; use eyre::eyre;
use rustv32i::emu::{self, Memory}; use rustv32i::emu::{self, Memory, XLen};
use rvdc::Reg; use rvdc::Reg;
fn main() -> eyre::Result<()> { fn main() -> eyre::Result<()> {
@ -12,22 +12,26 @@ fn main() -> eyre::Result<()> {
.nth(1) .nth(1)
.map(|addr| { .map(|addr| {
if let Some(addr) = addr.strip_prefix("0x") { if let Some(addr) = addr.strip_prefix("0x") {
u32::from_str_radix(addr, 16) u64::from_str_radix(addr, 16)
} else { } else {
u32::from_str_radix(&addr, 10) u64::from_str_radix(&addr, 10)
} }
}) })
.unwrap_or(Ok(0))?; .unwrap_or(Ok(0))?;
let debug = std::env::args().any(|arg| arg == "--debug"); let debug = std::env::args().any(|arg| arg == "--debug");
let mut syscall_state = SyscallState { set_child_tid: 0 }; let syscall_state = Arc::new(RefCell::new(SyscallState { set_child_tid: 0 }));
let syscall_state64 = syscall_state.clone();
let status = rustv32i::execute_linux_elf( let status = rustv32i::execute_linux_elf(
&content, &content,
debug, debug,
break_addr, break_addr,
Box::new(move |mem, xreg| ecall_handler(mem, xreg, &mut syscall_state)), Box::new(move |mem, xreg| ecall_handler::<u32>(mem, xreg, &mut syscall_state.borrow_mut())),
Box::new(move |mem, xreg| {
ecall_handler::<u64>(mem, xreg, &mut syscall_state64.borrow_mut())
}),
)?; )?;
std::io::stdout().flush()?; std::io::stdout().flush()?;
@ -44,12 +48,12 @@ fn main() -> eyre::Result<()> {
} }
struct SyscallState { struct SyscallState {
set_child_tid: u32, set_child_tid: u64,
} }
fn ecall_handler( fn ecall_handler<XLEN: Into<u64> + From<u32> + XLen>(
mem: &mut Memory, mem: &mut Memory,
xreg: &mut [u32; 32], xreg: &mut [XLEN; 32],
syscall_state: &mut SyscallState, syscall_state: &mut SyscallState,
) -> Result<(), emu::Status> { ) -> Result<(), emu::Status> {
let nr = xreg[Reg::A7.0 as usize]; let nr = xreg[Reg::A7.0 as usize];
@ -60,22 +64,22 @@ fn ecall_handler(
// https://jborza.com/post/2021-05-11-riscv-linux-syscalls/ // https://jborza.com/post/2021-05-11-riscv-linux-syscalls/
// https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h // https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h
match nr { match nr.into() {
// ioctl // ioctl
29 => { 29 => {
let fd = arg0; let fd = arg0;
let request = arg1; let request = arg1;
match request { match request.into() {
// TIOCGWINSZ // TIOCGWINSZ
0x5413 => { 0x5413 => {
let wsz_ptr = xreg[Reg::A2.0 as usize]; let wsz_ptr = xreg[Reg::A2.0 as usize].into();
let mut wsz: libc::winsize = unsafe { std::mem::zeroed() }; let mut wsz: libc::winsize = unsafe { std::mem::zeroed() };
let r = unsafe { libc::ioctl(fd as i32, libc::TIOCGWINSZ, &mut wsz) }; let r = unsafe { libc::ioctl(fd.into() as i32, libc::TIOCGWINSZ, &mut wsz) };
xreg[Reg::A0.0 as usize] = r as u32; xreg[Reg::A0.0 as usize] = (r as u32).into();
if r >= 0 { if r >= 0 {
mem.store_u16(wsz_ptr, wsz.ws_row)?; mem.store_u16(wsz_ptr, wsz.ws_row)?;
mem.store_u16(wsz_ptr + 2, wsz.ws_col)?; mem.store_u16(wsz_ptr + 2, wsz.ws_col)?;
@ -83,16 +87,16 @@ fn ecall_handler(
mem.store_u16(wsz_ptr + 6, wsz.ws_ypixel)?; mem.store_u16(wsz_ptr + 6, wsz.ws_ypixel)?;
} }
} }
_ => todo!("unknown ioctl: {request}"), _ => todo!("unknown ioctl: {}", request.into()),
} }
} }
// read // read
63 => { 63 => {
let fd = arg0; let fd = arg0.into();
let ptr = xreg[Reg::A1.0 as usize]; let ptr = xreg[Reg::A1.0 as usize];
let len = xreg[Reg::A2.0 as usize]; let len = xreg[Reg::A2.0 as usize];
let buf = mem.slice_mut(ptr, len)?; let buf = mem.slice_mut::<XLEN>(ptr, len)?;
let len = unsafe { libc::read(fd as i32, buf.as_mut_ptr().cast(), buf.len()) }; let len = unsafe { libc::read(fd as i32, buf.as_mut_ptr().cast(), buf.len()) };
let ret = if len < 0 { let ret = if len < 0 {
@ -101,11 +105,11 @@ fn ecall_handler(
len as u32 len as u32
}; };
xreg[Reg::A0.0 as usize] = ret; xreg[Reg::A0.0 as usize] = ret.into();
} }
// write // write
64 => { 64 => {
let fd = arg0; let fd = arg0.into();
let ptr = xreg[Reg::A1.0 as usize]; let ptr = xreg[Reg::A1.0 as usize];
let len = xreg[Reg::A2.0 as usize]; let len = xreg[Reg::A2.0 as usize];
@ -118,13 +122,13 @@ fn ecall_handler(
len as u32 len as u32
}; };
xreg[Reg::A0.0 as usize] = ret; xreg[Reg::A0.0 as usize] = ret.into();
} }
// https://man7.org/linux/man-pages/man3/writev.3p.html // https://man7.org/linux/man-pages/man3/writev.3p.html
66 => { 66 => {
let fd = arg0; let fd = arg0.into();
let iovec = arg1; let iovec = arg1.into();
let iovcnt = arg2; let iovcnt = arg2.into();
let mut written = 0; let mut written = 0;
@ -142,27 +146,27 @@ fn ecall_handler(
}; };
if (ret as i32) < 0 { if (ret as i32) < 0 {
xreg[Reg::A0.0 as usize] = ret; xreg[Reg::A0.0 as usize] = ret.into();
return Ok(()); return Ok(());
} else { } else {
written += ret; written += ret;
} }
} }
xreg[Reg::A0.0 as usize] = written; xreg[Reg::A0.0 as usize] = written.into();
} }
// exit | exit_group // exit | exit_group
93 | 94 => { 93 | 94 => {
return Err(emu::Status::Exit { return Err(emu::Status::Exit {
code: xreg[Reg::A0.0 as usize] as i32, code: xreg[Reg::A0.0 as usize].into() as i32,
}); });
} }
// <https://man7.org/linux/man-pages/man2/set_tid_address.2.html> // <https://man7.org/linux/man-pages/man2/set_tid_address.2.html>
96 => { 96 => {
let tidptr = arg0; let tidptr = arg0;
syscall_state.set_child_tid = tidptr; syscall_state.set_child_tid = tidptr.into();
xreg[Reg::A0.0 as usize] = 1; // thread ID xreg[Reg::A0.0 as usize] = 1.into(); // thread ID
} }
// ppoll - called for some stdin/stdout/stderr check. // ppoll - called for some stdin/stdout/stderr check.
414 => { 414 => {
@ -170,10 +174,10 @@ fn ecall_handler(
// and opens /dev/null for them if they are. // and opens /dev/null for them if they are.
// They're always valid here, so just get out. // They're always valid here, so just get out.
xreg[Reg::A0.0 as usize] = 0; xreg[Reg::A0.0 as usize] = 0.into();
} }
_ => { _ => {
todo!("unkonwn syscall: {nr}"); todo!("unkonwn syscall: {}", nr.into());
} }
} }

View file

@ -27,21 +27,30 @@ fn check() -> eyre::Result<()> {
continue; continue;
} }
test_case(tmpdir.path(), &file, name, "rv32ima")?; test_case(tmpdir.path(), &file, name, 32, "rv32ima")?;
test_case(tmpdir.path(), &file, name, "rv32imac")?; test_case(tmpdir.path(), &file, name, 32, "rv32imac")?;
// test_case(tmpdir.path(), &file, name, 64, "rv64ima")?;
// test_case(tmpdir.path(), &file, name, 64, "rv64imac")?;
} }
Ok(()) Ok(())
} }
fn test_case(tmpdir: &Path, file: &DirEntry, name: &str, march: &str) -> eyre::Result<()> { fn test_case(
tmpdir: &Path,
file: &DirEntry,
name: &str,
size: u8,
march: &str,
) -> eyre::Result<()> {
let name = format!("{name} ({march})"); let name = format!("{name} ({march})");
write!(std::io::stdout(), "test {name} ...")?; write!(std::io::stdout(), "test {name} ...")?;
std::io::stdout().flush()?; std::io::stdout().flush()?;
eprintln!("---- START TEST {name} -----"); eprintln!("---- START TEST {name} -----");
let output = build(&tmpdir, &file.path(), march).wrap_err(format!("building {name}"))?; let output = build(&tmpdir, &file.path(), size, march).wrap_err(format!("building {name}"))?;
let content = let content =
std::fs::read(&output).wrap_err(format!("reading output from {}", output.display()))?; std::fs::read(&output).wrap_err(format!("reading output from {}", output.display()))?;
@ -60,6 +69,17 @@ fn test_case(tmpdir: &Path, file: &DirEntry, name: &str, march: &str) -> eyre::R
Err(rustv32i::emu::Status::Trap("wrong syscall")) Err(rustv32i::emu::Status::Trap("wrong syscall"))
} }
}), }),
Box::new(|_, xreg| {
if xreg[Reg::A7.0 as usize] == u32::MAX as u64 {
if xreg[Reg::A0.0 as usize] == 1 {
Err(rustv32i::emu::Status::Exit { code: 0 })
} else {
Err(rustv32i::emu::Status::Trap("fail"))
}
} else {
Err(rustv32i::emu::Status::Trap("wrong syscall"))
}
}),
) )
.wrap_err(format!("{name} failed"))?; .wrap_err(format!("{name} failed"))?;
@ -71,11 +91,12 @@ fn test_case(tmpdir: &Path, file: &DirEntry, name: &str, march: &str) -> eyre::R
} }
} }
fn build(tmpdir: &Path, src: &Path, march: &str) -> eyre::Result<PathBuf> { fn build(tmpdir: &Path, src: &Path, size: u8, march: &str) -> eyre::Result<PathBuf> {
let out_path = tmpdir.join(Path::new(src.file_name().unwrap()).with_extension("")); let out_path = tmpdir.join(Path::new(src.file_name().unwrap()).with_extension(""));
let mut cmd = std::process::Command::new("clang"); let mut cmd = std::process::Command::new("clang");
cmd.args(["-target", "riscv32-unknown-none-elf", "-nostdlib"]); cmd.args(["-target", &format!("riscv{size}-unknown-none-elf")]);
cmd.arg("-nostdlib");
cmd.arg(format!("-march={march}")); cmd.arg(format!("-march={march}"));
cmd.arg(src); cmd.arg(src);
cmd.arg("-o"); cmd.arg("-o");