From e9a689aa1a67146528b4d89d6659da74253a22f6 Mon Sep 17 00:00:00 2001 From: Noratrieb <48135649+Noratrieb@users.noreply.github.com> Date: Wed, 26 Mar 2025 21:10:48 +0100 Subject: [PATCH] start with 64 bit --- rvdc/src/lib.rs | 145 +++++++++++++++++++++++--- src/elf.rs | 91 +++++++++++----- src/emu.rs | 271 +++++++++++++++++++++++++++++++----------------- src/lib.rs | 38 ++++--- src/main.rs | 64 ++++++------ tests/check.rs | 33 ++++-- 6 files changed, 449 insertions(+), 193 deletions(-) diff --git a/rvdc/src/lib.rs b/rvdc/src/lib.rs index 65c1fd3..3eec47c 100644 --- a/rvdc/src/lib.rs +++ b/rvdc/src/lib.rs @@ -5,6 +5,27 @@ use core::fmt::{self, Debug, Display}; 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. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 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. #[derive(Copy, Clone, PartialEq, Eq, Hash)] -pub struct Imm(u32); +pub struct Imm(u64); impl Imm { /// The immediate `0`. @@ -118,23 +139,33 @@ impl Imm { /// Create a new immediate from the (if necessary) sign-extended value. 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. - pub const fn new_u32(value: u32) -> Self { - Self(value) + pub const fn new_u32(value: u32) -> Self { + Self(value as u64) } /// Get the `u32` (RV32) value of the immediate. pub const fn as_u32(self) -> u32 { - self.0 + self.0 as u32 } /// Get the `i32` (RV32) value of the immediate. pub const fn as_i32(self) -> 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 for Imm { @@ -216,6 +247,8 @@ pub enum Inst { /// Add Immediate 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) Slti { imm: Imm, dest: Reg, src1: Reg }, /// Set Less Than Immediate Unsigned @@ -228,10 +261,16 @@ pub enum Inst { Andi { imm: Imm, dest: Reg, src1: Reg }, /// Shift Left Logical Immediate 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) 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) 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 { dest: Reg, src1: Reg, src2: Reg }, @@ -502,6 +541,13 @@ impl Display for Inst { 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 { imm, dest, @@ -532,16 +578,31 @@ impl Display for Inst { dest, src1: rs1, } => write!(f, "slli {dest}, {rs1}, {}", imm.as_i32()), + Inst::SlliW { + imm, + dest, + src1: rs1, + } => write!(f, "slliw {dest}, {rs1}, {}", imm.as_i32()), Inst::Srli { imm, dest, src1: rs1, } => write!(f, "srli {dest}, {rs1}, {}", imm.as_i32()), + Inst::SrliW { + imm, + dest, + src1: rs1, + } => write!(f, "srliw {dest}, {rs1}, {}", imm.as_i32()), Inst::Srai { imm, dest, src1: rs1, } => 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 } => { 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`] /// can be used to check, read the required bytes, and then call [`Self::decode_compressed`] or /// [`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; if is_compressed { - Ok((Self::decode_compressed(code as u16)?, IsCompressed::Yes)) + Ok(( + Self::decode_compressed(code as u16, xlen)?, + IsCompressed::Yes, + )) } 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(); /// assert_eq!(inst, expected); /// ``` - pub fn decode_compressed(code: u16) -> Result { + pub fn decode_compressed(code: u16, xlen: Xlen) -> Result { let code = InstCodeC(code); if code.0 == 0 { return Err(decode_error(code, "null instruction")); @@ -1146,7 +1210,7 @@ impl Inst { } /// Decode a normal (not compressed) instruction. - pub fn decode_normal(code: u32) -> Result { + pub fn decode_normal(code: u32, xlen: Xlen) -> Result { let code = InstCode(code); let inst = match code.opcode() { // LUI @@ -1312,6 +1376,47 @@ impl Inst { }, _ => 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 0b0110011 => { let (dest, src1, src2) = (code.rd(), code.rs1(), code.rs2()); @@ -1465,6 +1570,7 @@ mod tests { use crate::Imm; use crate::Inst; use crate::Reg; + use crate::Xlen; #[test] #[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::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] @@ -1591,7 +1708,7 @@ mod tests { let start_time = std::time::Instant::now(); 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)) .collect::>(); @@ -1631,7 +1748,7 @@ mod tests { #[ignore = "this doesn't quite work yet because there is often a non-canonical encoding"] fn compressed_clang_roundtrip() { 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)) .collect::>(); diff --git a/src/elf.rs b/src/elf.rs index 4cf23f1..fcf9d06 100644 --- a/src/elf.rs +++ b/src/elf.rs @@ -32,18 +32,30 @@ pub struct Header { } #[derive(Debug)] -pub struct Phdr32 { +pub struct Phdr { pub p_type: u32, - pub p_offset: u32, - pub p_vaddr: u32, - pub p_paddr: u32, - pub p_filesz: u32, - pub p_memsz: u32, + pub p_offset: Offset, + pub p_vaddr: Addr, + pub p_paddr: Addr, + pub p_filesz: u64, + pub p_memsz: u64, pub p_flags: u32, - pub p_align: u32, + pub p_align: u64, } impl Elf<'_> { + fn class(&self) -> Result { + 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
{ let (ident, rest) = self.content.split_bytes(16)?; if ident[..4] != *b"\x7fELF" { @@ -140,8 +152,9 @@ impl Elf<'_> { }) } - pub fn segments_32(&self) -> Result> { + pub fn segments(&self) -> Result> { let header = self.header()?; + let class = self.class()?; 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)?; @@ -152,25 +165,51 @@ impl Elf<'_> { let phdr; (phdr, phdrs) = phdrs.split_bytes(header.e_phentsize as usize)?; - let (p_type, phdr) = phdr.split_u32()?; - let (p_offset, phdr) = phdr.split_u32()?; - let (p_vaddr, phdr) = phdr.split_u32()?; - let (p_paddr, phdr) = phdr.split_u32()?; - let (p_filesz, phdr) = phdr.split_u32()?; - let (p_memsz, phdr) = phdr.split_u32()?; - let (p_flags, phdr) = phdr.split_u32()?; - let (p_align, _) = phdr.split_u32()?; + let phdr = match class { + ElfClass::Elf32 => { + let (p_type, phdr) = phdr.split_u32()?; + let (p_offset, phdr) = phdr.split_u32()?; + let (p_vaddr, phdr) = phdr.split_u32()?; + let (p_paddr, phdr) = phdr.split_u32()?; + let (p_filesz, phdr) = phdr.split_u32()?; + let (p_memsz, phdr) = phdr.split_u32()?; + let (p_flags, phdr) = phdr.split_u32()?; + let (p_align, _) = phdr.split_u32()?; - parsed_phdrs.push(Phdr32 { - p_type, - p_offset, - p_vaddr, - p_paddr, - p_filesz, - p_memsz, - p_flags, - p_align, - }); + Phdr { + p_type, + p_offset: Offset(p_offset as u64), + p_vaddr: Addr(p_vaddr as u64), + 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_memsz, + p_flags, + p_align, + } + } + }; + parsed_phdrs.push(phdr); } Ok(parsed_phdrs) diff --git a/src/emu.rs b/src/emu.rs index 6bfaa6b..2881412 100644 --- a/src/emu.rs +++ b/src/emu.rs @@ -20,44 +20,50 @@ impl Memory { Ok(()) } } - pub fn slice(&self, addr: XLEN, len: u32) -> Result<&[u8], Status> { + pub fn slice(&self, addr: XLEN, len: XLEN) -> Result<&[u8], Status> { self.mem .get((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())) } - pub fn slice_mut(&mut self, addr: XLEN, len: u32) -> Result<&mut [u8], Status> { + pub fn slice_mut(&mut self, addr: XLEN, len: XLEN) -> Result<&mut [u8], Status> { self.mem .get_mut((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())) } pub fn load_u8(&self, addr: XLEN) -> Result { - 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(&self, addr: XLEN) -> Result { - 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(&self, addr: XLEN) -> Result { - 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(&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()); Ok(()) } pub fn store_u16(&mut self, addr: XLEN, value: u16) -> Result<(), Status> { 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()); Ok(()) } pub fn store_u32(&mut self, addr: XLEN, value: u32) -> Result<(), Status> { 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()); Ok(()) } @@ -139,6 +145,28 @@ fn hash_color(value: usize) -> impl Display { } impl Emulator { + pub fn new( + mem: Memory, + start: XLEN, + break_addr: XLEN, + debug: bool, + ecall_handler: Box 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 { self.setup_linux_stack().unwrap(); @@ -210,7 +238,7 @@ impl Emulator { 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 { println!( @@ -649,11 +677,21 @@ impl Emulator { } pub trait XLen: Copy + PartialEq + Eq { + type Signed; + type NextUnsigned; + type NextSigned; + + const XLEN: rvdc::Xlen; + const ZERO: Self; const SIGNED_MIN: Self; const MAX: Self; - fn from_bool(v: bool) -> Self; + fn switch(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 { Self::from_32_z(v as u32) } @@ -704,14 +742,107 @@ pub trait XLen: Copy + PartialEq + Eq { fn unsigned_rem(self, other: Self) -> Self; } -impl XLen for u32 { - const ZERO: Self = 0; - const SIGNED_MIN: Self = i32::MIN as u32; - const MAX: Self = u32::MAX; +macro_rules! xlen_impl { + () => { + const ZERO: Self = 0; + const SIGNED_MIN: Self = Self::Signed::MIN as Self; + const MAX: Self = Self::MAX; - fn from_bool(v: bool) -> Self { - v as u32 + fn as_usize(self) -> usize { + self as usize + } + fn truncate32(self) -> u32 { + self as u32 + } + + fn add(self, other: Self) -> Self { + self.wrapping_add(other) + } + fn sub(self, other: Self) -> Self { + self.wrapping_sub(other) + } + fn and(self, other: Self) -> Self { + self & other + } + fn or(self, other: Self) -> Self { + self | other + } + fn xor(self, other: Self) -> Self { + self ^ other + } + fn signed_lt(self, other: Self) -> bool { + (self as Self::Signed) < (other as Self::Signed) + } + fn unsigned_lt(self, other: Self) -> bool { + self < other + } + fn signed_ge(self, other: Self) -> bool { + (self as Self::Signed) >= (other as Self::Signed) + } + fn unsigned_ge(self, other: Self) -> bool { + self >= other + } + fn shl(self, other: u32) -> Self { + self.wrapping_shl(other) + } + fn unsigned_shr(self, other: u32) -> Self { + self.wrapping_shr(other) + } + fn signed_shr(self, other: u32) -> Self { + ((self as Self::Signed).wrapping_shr(other)) as Self + } + fn signed_min(self, other: Self) -> Self { + (self as Self::Signed).min(other as Self::Signed) as Self + } + fn unsigned_min(self, other: Self) -> Self { + self.min(other) + } + fn signed_max(self, other: Self) -> Self { + (self as Self::Signed).max(other as Self::Signed) as Self + } + fn unsigned_max(self, other: Self) -> Self { + self.max(other) + } + fn mul_lower(self, other: Self) -> Self { + (self as Self::Signed).wrapping_mul(other as Self::Signed) as Self + } + fn signed_mul_upper(self, other: Self) -> Self { + let mul_result = (self as Self::Signed as Self::NextSigned) + .wrapping_mul(other as Self::Signed as Self::NextSigned); + let shifted = (mul_result as Self::NextUnsigned) >> Self::BITS; + shifted as Self + } + fn unsigned_mul_upper(self, other: Self) -> Self { + let shifted = ((self as Self::NextUnsigned).wrapping_mul(other as Self::NextUnsigned)) + >> Self::BITS; + shifted as Self + } + fn signed_div(self, other: Self) -> Self { + ((self as Self::Signed) / (other as Self::Signed)) as Self + } + fn unsigned_div(self, other: Self) -> Self { + self / other + } + fn signed_rem(self, other: Self) -> Self { + ((self as Self::Signed) % (other as Self::Signed)) as Self + } + fn unsigned_rem(self, other: Self) -> Self { + self % other + } + }; +} + +impl XLen for u32 { + type Signed = i32; + type NextUnsigned = u64; + type NextSigned = i64; + + const XLEN: rvdc::Xlen = rvdc::Xlen::Rv32; + + fn switch(self, on_32: impl FnOnce(u32) -> R, _: impl FnOnce(u64) -> R) -> R { + on_32(self) } + fn from_32_s(v: u32) -> Self { v } @@ -721,84 +852,30 @@ impl XLen for u32 { fn from_imm(v: Imm) -> Self { v.as_u32() } - fn as_usize(self) -> usize { - self as usize - } - fn truncate32(self) -> u32 { - self - } - - fn add(self, other: Self) -> Self { - self.wrapping_add(other) - } - fn sub(self, other: Self) -> Self { - self.wrapping_sub(other) - } - fn and(self, other: Self) -> Self { - self & other - } - fn or(self, other: Self) -> Self { - self | other - } - fn xor(self, other: Self) -> Self { - self ^ other - } - fn signed_lt(self, other: Self) -> bool { - (self as i32) < (other as i32) - } - fn unsigned_lt(self, other: Self) -> bool { - self < other - } - fn signed_ge(self, other: Self) -> bool { - (self as i32) >= (other as i32) - } - fn unsigned_ge(self, other: Self) -> bool { - self >= other - } - fn shl(self, other: u32) -> Self { - self.wrapping_shl(other) - } - fn unsigned_shr(self, other: u32) -> Self { - self.wrapping_shr(other) - } - fn signed_shr(self, other: u32) -> Self { - ((self as i32).wrapping_shr(other)) as u32 - } - fn signed_min(self, other: Self) -> Self { - (self as i32).min(other as i32) as u32 - } - fn unsigned_min(self, other: Self) -> Self { - self.min(other) - } - fn signed_max(self, other: Self) -> Self { - (self as i32).max(other as i32) as u32 - } - fn unsigned_max(self, other: Self) -> Self { - self.max(other) - } - fn mul_lower(self, other: Self) -> Self { - (self as i32).wrapping_mul(other as i32) as u32 - } - fn signed_mul_upper(self, other: Self) -> Self { - let mul_result = (self as i32 as i64).wrapping_mul(other as i32 as i64); - let shifted = (mul_result as u64) >> 32; - shifted as u32 - } - fn unsigned_mul_upper(self, other: Self) -> Self { - let shifted = ((self as u64).wrapping_mul(other as u64)) >> 32; - shifted as u32 - } - fn signed_div(self, other: Self) -> Self { - ((self as i32) / (other as i32)) as u32 - } - fn unsigned_div(self, other: Self) -> Self { - self / other - } - fn signed_rem(self, other: Self) -> Self { - ((self as i32) % (other as i32)) as u32 - } - fn unsigned_rem(self, other: Self) -> Self { - self % other - } + xlen_impl!(); +} + +impl XLen for u64 { + type Signed = i64; + type NextUnsigned = u128; + type NextSigned = i128; + + const XLEN: rvdc::Xlen = rvdc::Xlen::Rv64; + + fn switch(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!(); } diff --git a/src/lib.rs b/src/lib.rs index 934cf7e..2c8a904 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,13 +9,14 @@ const MEMORY_SIZE: usize = 2 << 21; pub fn execute_linux_elf( elf: &[u8], debug: bool, - break_addr: u32, - ecall_handler: Box Result<(), emu::Status>>, + break_addr: u64, + ecall_handler32: Box Result<(), emu::Status>>, + ecall_handler64: Box Result<(), emu::Status>>, ) -> eyre::Result { let elf = elf::Elf { content: elf }; let header = elf.header()?; - let segments = elf.segments_32()?; + let segments = elf.segments()?; let mut mem = emu::Memory { mem: vec![0; MEMORY_SIZE], @@ -30,13 +31,13 @@ pub fn execute_linux_elf( if phdr.p_filesz > 0 { let contents = &elf .content - .get((phdr.p_offset as usize)..) + .get((phdr.p_offset.0 as usize)..) .ok_or_eyre("invalid offset")? .get(..(phdr.p_filesz as usize)) .ok_or_eyre("invalid offset")?; mem.mem - .get_mut((phdr.p_vaddr as usize)..) + .get_mut((phdr.p_vaddr.0 as usize)..) .ok_or_eyre("invalid offset")? .get_mut(..(phdr.p_filesz as usize)) .ok_or_eyre("invalid offset")? @@ -61,19 +62,16 @@ pub fn execute_linux_elf( let start = header.e_entry; - let mut emu = emu::Emulator { - mem, - xreg: [0; 32], - xreg0_value: 0, - pc: start.0 as u32, - reservation_set: None, - - is_breaking: false, - - break_pc: break_addr, - debug, - ecall_handler, - }; - - Ok(emu.start_linux()) + match header.class { + elf::ElfClass::Elf32 => { + let mut emu = + emu::Emulator::::new(mem, start.0 as u32, break_addr as u32, debug, ecall_handler32); + Ok(emu.start_linux()) + } + elf::ElfClass::Elf64 => { + let mut emu = + emu::Emulator::::new(mem, start.0, break_addr, debug, ecall_handler64); + Ok(emu.start_linux()) + } + } } diff --git a/src/main.rs b/src/main.rs index f70208d..7d845ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ -use std::io::Write; +use std::{cell::RefCell, io::Write, sync::Arc}; use eyre::eyre; -use rustv32i::emu::{self, Memory}; +use rustv32i::emu::{self, Memory, XLen}; use rvdc::Reg; fn main() -> eyre::Result<()> { @@ -12,22 +12,26 @@ fn main() -> eyre::Result<()> { .nth(1) .map(|addr| { if let Some(addr) = addr.strip_prefix("0x") { - u32::from_str_radix(addr, 16) + u64::from_str_radix(addr, 16) } else { - u32::from_str_radix(&addr, 10) + u64::from_str_radix(&addr, 10) } }) .unwrap_or(Ok(0))?; 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( &content, debug, break_addr, - Box::new(move |mem, xreg| ecall_handler(mem, xreg, &mut syscall_state)), + Box::new(move |mem, xreg| ecall_handler::(mem, xreg, &mut syscall_state.borrow_mut())), + Box::new(move |mem, xreg| { + ecall_handler::(mem, xreg, &mut syscall_state64.borrow_mut()) + }), )?; std::io::stdout().flush()?; @@ -44,12 +48,12 @@ fn main() -> eyre::Result<()> { } struct SyscallState { - set_child_tid: u32, + set_child_tid: u64, } -fn ecall_handler( +fn ecall_handler + From + XLen>( mem: &mut Memory, - xreg: &mut [u32; 32], + xreg: &mut [XLEN; 32], syscall_state: &mut SyscallState, ) -> Result<(), emu::Status> { 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://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h - match nr { + match nr.into() { // ioctl 29 => { let fd = arg0; let request = arg1; - match request { + match request.into() { // TIOCGWINSZ 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 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 { mem.store_u16(wsz_ptr, wsz.ws_row)?; 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)?; } } - _ => todo!("unknown ioctl: {request}"), + _ => todo!("unknown ioctl: {}", request.into()), } } // read 63 => { - let fd = arg0; + let fd = arg0.into(); let ptr = xreg[Reg::A1.0 as usize]; let len = xreg[Reg::A2.0 as usize]; - let buf = mem.slice_mut(ptr, len)?; + let buf = mem.slice_mut::(ptr, len)?; let len = unsafe { libc::read(fd as i32, buf.as_mut_ptr().cast(), buf.len()) }; let ret = if len < 0 { @@ -101,11 +105,11 @@ fn ecall_handler( len as u32 }; - xreg[Reg::A0.0 as usize] = ret; + xreg[Reg::A0.0 as usize] = ret.into(); } // write 64 => { - let fd = arg0; + let fd = arg0.into(); let ptr = xreg[Reg::A1.0 as usize]; let len = xreg[Reg::A2.0 as usize]; @@ -118,13 +122,13 @@ fn ecall_handler( 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 66 => { - let fd = arg0; - let iovec = arg1; - let iovcnt = arg2; + let fd = arg0.into(); + let iovec = arg1.into(); + let iovcnt = arg2.into(); let mut written = 0; @@ -142,27 +146,27 @@ fn ecall_handler( }; if (ret as i32) < 0 { - xreg[Reg::A0.0 as usize] = ret; + xreg[Reg::A0.0 as usize] = ret.into(); return Ok(()); } else { written += ret; } } - xreg[Reg::A0.0 as usize] = written; + xreg[Reg::A0.0 as usize] = written.into(); } // exit | exit_group 93 | 94 => { return Err(emu::Status::Exit { - code: xreg[Reg::A0.0 as usize] as i32, + code: xreg[Reg::A0.0 as usize].into() as i32, }); } // 96 => { 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. 414 => { @@ -170,10 +174,10 @@ fn ecall_handler( // and opens /dev/null for them if they are. // 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()); } } diff --git a/tests/check.rs b/tests/check.rs index a0310c6..9dd4fc2 100644 --- a/tests/check.rs +++ b/tests/check.rs @@ -27,21 +27,30 @@ fn check() -> eyre::Result<()> { continue; } - test_case(tmpdir.path(), &file, name, "rv32ima")?; - test_case(tmpdir.path(), &file, name, "rv32imac")?; + test_case(tmpdir.path(), &file, name, 32, "rv32ima")?; + 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(()) } -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})"); write!(std::io::stdout(), "test {name} ...")?; std::io::stdout().flush()?; 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 = 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")) } }), + 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"))?; @@ -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 { +fn build(tmpdir: &Path, src: &Path, size: u8, march: &str) -> eyre::Result { let out_path = tmpdir.join(Path::new(src.file_name().unwrap()).with_extension("")); 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(src); cmd.arg("-o");