From f0c04f146648bbae791294d9086e832a474cbb0a Mon Sep 17 00:00:00 2001 From: Noratrieb <48135649+Noratrieb@users.noreply.github.com> Date: Sun, 13 Apr 2025 16:17:29 +0200 Subject: [PATCH 1/2] Optimize rvdc tests --- Cargo.lock | 52 ++++++++++++++++++++++ rvdc/Cargo.toml | 1 + rvdc/README.md | 4 +- rvdc/src/lib.rs | 114 ++++++++++++++++++++++++++---------------------- 4 files changed, 118 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c37f38..345bd11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,6 +29,37 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "errno" version = "0.3.10" @@ -133,6 +164,26 @@ version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1036865bb9422d3300cf723f657c2851d0e9ab12567854b1f4eba3d77decf564" +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "rustix" version = "1.0.1" @@ -171,6 +222,7 @@ name = "rvdc" version = "0.1.1" dependencies = [ "object", + "rayon", "tempfile", ] diff --git a/rvdc/Cargo.toml b/rvdc/Cargo.toml index de87c41..420b0c6 100644 --- a/rvdc/Cargo.toml +++ b/rvdc/Cargo.toml @@ -15,4 +15,5 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(slow_tests)'] } [dev-dependencies] object = "0.36.7" +rayon = "1.10.0" tempfile = "3.19.1" diff --git a/rvdc/README.md b/rvdc/README.md index 8494525..5bad4cd 100644 --- a/rvdc/README.md +++ b/rvdc/README.md @@ -38,7 +38,7 @@ More extensions may be implemented in the future. let x = 0x1101_u32; let expected = rvdc::Inst::Addi { imm: rvdc::Imm::new_i32(-0x20), dest: rvdc::Reg::SP, src1: rvdc::Reg::SP }; -let (inst, is_compressed) = rvdc::Inst::decode(x).unwrap(); +let (inst, is_compressed) = rvdc::Inst::decode(x, rvdc::Xlen::Rv32).unwrap(); assert_eq!(inst, expected); assert_eq!(is_compressed, rvdc::IsCompressed::Yes); assert_eq!(format!("{inst}"), "addi sp, sp, -32") @@ -49,7 +49,7 @@ assert_eq!(format!("{inst}"), "addi sp, sp, -32") let x = 0x0000a317; let expected = rvdc::Inst::Auipc { uimm: rvdc::Imm::new_u32(0xa << 12), dest: rvdc::Reg::T1 }; -let (inst, is_compressed) = rvdc::Inst::decode(x).unwrap(); +let (inst, is_compressed) = rvdc::Inst::decode(x, rvdc::Xlen::Rv32).unwrap(); assert_eq!(inst, expected); assert_eq!(is_compressed, rvdc::IsCompressed::No); assert_eq!(format!("{inst}"), "auipc t1, 10") diff --git a/rvdc/src/lib.rs b/rvdc/src/lib.rs index 3eec47c..9664713 100644 --- a/rvdc/src/lib.rs +++ b/rvdc/src/lib.rs @@ -6,7 +6,7 @@ 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)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Xlen { /// 32 bit Rv32, @@ -928,10 +928,10 @@ impl Inst { /// let x = 0x1101_u16; /// let expected = rvdc::Inst::Addi { imm: rvdc::Imm::new_i32(-0x20), dest: rvdc::Reg::SP, src1: rvdc::Reg::SP }; /// - /// let inst = rvdc::Inst::decode_compressed(x).unwrap(); + /// let inst = rvdc::Inst::decode_compressed(x, rvdc::Xlen::Rv32).unwrap(); /// assert_eq!(inst, expected); /// ``` - pub fn decode_compressed(code: u16, xlen: Xlen) -> 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")); @@ -1557,6 +1557,8 @@ impl Inst { #[cfg(test)] mod tests { extern crate std; + use core::sync::atomic::AtomicU32; + use core::sync::atomic::Ordering; use std::prelude::rust_2024::*; use std::fmt::Write as _; @@ -1564,6 +1566,8 @@ mod tests { use object::Object; use object::ObjectSection; + use rayon::iter::IntoParallelRefIterator; + use rayon::iter::ParallelIterator; use crate::Fence; use crate::FenceSet; @@ -1574,18 +1578,17 @@ mod tests { #[test] #[cfg_attr(not(slow_tests), ignore = "cfg(slow_tests) not enabled")] - fn exhaustive_decode_no_panic() { - 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::Rv32); - } - let _ = Inst::decode(u32::MAX, Xlen::Rv32); + fn exhaustive_decode_no_panic_32() { + exhaustive_decode_no_panic(Xlen::Rv32); + } + #[test] + #[cfg_attr(not(slow_tests), ignore = "cfg(slow_tests) not enabled")] + fn exhaustive_decode_no_panic_64() { + exhaustive_decode_no_panic(Xlen::Rv64); + } + + fn exhaustive_decode_no_panic(xlen: Xlen) { for i in 0..u32::MAX { if (i % (2 << 25)) == 0 { let percent = i as f32 / (u32::MAX as f32); @@ -1593,14 +1596,18 @@ mod tests { std::print!("\r{}{}", "#".repeat(done), "-".repeat(100 - done)); std::io::stdout().flush().unwrap(); } - let _ = Inst::decode(i, Xlen::Rv64); + let _ = Inst::decode(i, xlen); } - let _ = Inst::decode(u32::MAX, Xlen::Rv64); + let _ = Inst::decode(u32::MAX, xlen); } #[test] fn size_of_instruction() { - assert!(size_of::() <= 12); + assert!( + size_of::() <= 16, + "size of instruction is too large: {}", + size_of::() + ); } const TEST_SECTION_NAME: &str = ".text.rvdctest"; @@ -1699,49 +1706,54 @@ mod tests { const CHUNKS: u32 = 128; const CHUNK_SIZE: u32 = u32::MAX / CHUNKS; - for (chunk_idx, start) in ((SKIP_CHUNKS * CHUNK_SIZE)..u32::MAX) + let chunks = ((SKIP_CHUNKS * CHUNK_SIZE)..u32::MAX) .step_by(CHUNK_SIZE as usize) - .enumerate() - { - let chunk_idx = chunk_idx + SKIP_CHUNKS as usize; + .collect::>(); - let start_time = std::time::Instant::now(); + let start_time = std::time::Instant::now(); + let completed = AtomicU32::new(0); - let insts = (start..=start.saturating_add(CHUNK_SIZE)) - .filter_map(|code| Some((code, Inst::decode_normal(code, Xlen::Rv32).ok()?))) - .filter(|(_, inst)| is_inst_supposed_to_roundtrip(inst)) - .collect::>(); + chunks + .par_iter() + .for_each(|&start| { + let insts = (start..=start.saturating_add(CHUNK_SIZE)) + .filter_map(|code| Some((code, Inst::decode_normal(code, Xlen::Rv32).ok()?))) + .filter(|(_, inst)| is_inst_supposed_to_roundtrip(inst)) + .collect::>(); - let mut text = std::format!(".section {TEST_SECTION_NAME}\n.globl _start\n_start:\n"); - for (_, inst) in &insts { - writeln!(text, " {inst}").unwrap(); - } + let mut text = + std::format!(".section {TEST_SECTION_NAME}\n.globl _start\n_start:\n"); + for (_, inst) in &insts { + writeln!(text, " {inst}").unwrap(); + } - let data = clang_assemble(&text, "-march=rv32ima_zihintpause"); + let data = clang_assemble(&text, "-march=rv32ima_zihintpause"); - for (i, result_code) in data.chunks(4).enumerate() { - let result_code = u32::from_le_bytes(result_code.try_into().unwrap()); + for (i, result_code) in data.chunks(4).enumerate() { + let result_code = u32::from_le_bytes(result_code.try_into().unwrap()); - assert_eq!( - insts[i].0, result_code, - "failed to rountrip!\n\ - instruction `{:0>32b}` failed to rountrip\n\ - resulted in `{:0>32b}` instead.\n\ - disassembly of original instruction: `{}`", - insts[i].0, result_code, insts[i].1 - ); - } + assert_eq!( + insts[i].0, result_code, + "failed to rountrip!\n\ + instruction `{:0>32b}` failed to rountrip\n\ + resulted in `{:0>32b}` instead.\n\ + disassembly of original instruction: `{}`", + insts[i].0, result_code, insts[i].1 + ); + } - let elapsed = start_time.elapsed(); - let chunk_number = chunk_idx + 1; - let remaining = elapsed * (CHUNKS.saturating_sub(chunk_number as u32)); + let already_completed = completed.fetch_add(1, Ordering::Relaxed); + let already_elapsed = start_time.elapsed(); - writeln!( - std::io::stdout(), - "Completed chunk {chunk_number}/{CHUNKS} (estimated {remaining:?} remaining)", - ) - .unwrap(); - } + let remaining_chunks = CHUNKS.saturating_sub(already_completed); + let remaining = already_elapsed / std::cmp::max(already_completed, 1) * remaining_chunks; + + writeln!( + std::io::stdout(), + "Completed chunk {already_completed}/{CHUNKS} (estimated {remaining:?} remaining)", + ) + .unwrap(); + }); } #[test] From 8d01c9f1d70adbc84c3b39a865ab3468af36d6c8 Mon Sep 17 00:00:00 2001 From: Noratrieb <48135649+Noratrieb@users.noreply.github.com> Date: Sun, 13 Apr 2025 17:53:50 +0200 Subject: [PATCH 2/2] continue RV64 --- rvdc/src/lib.rs | 183 ++++++++++++++++++++++++++++------------- src/emu.rs | 108 ++++++++++++++++++++++-- tests/check.rs | 5 +- tests/check/int_comp.S | 61 +++++++++++++- 4 files changed, 290 insertions(+), 67 deletions(-) diff --git a/rvdc/src/lib.rs b/rvdc/src/lib.rs index 9664713..ff6e41f 100644 --- a/rvdc/src/lib.rs +++ b/rvdc/src/lib.rs @@ -274,10 +274,16 @@ pub enum Inst { /// Add Add { dest: Reg, src1: Reg, src2: Reg }, + /// Add 32-bit (**RV64 only**) + AddW { dest: Reg, src1: Reg, src2: Reg }, /// Subtract Sub { dest: Reg, src1: Reg, src2: Reg }, + /// Subtract 32-bit (**RV64 only**) + SubW { dest: Reg, src1: Reg, src2: Reg }, /// Shift Left Logical Sll { dest: Reg, src1: Reg, src2: Reg }, + /// Shift Left Logical 32-bit (**RV64 only**) + SllW { dest: Reg, src1: Reg, src2: Reg }, /// Set Less Than (signed) Slt { dest: Reg, src1: Reg, src2: Reg }, /// Set Less Than Unsigned @@ -286,8 +292,12 @@ pub enum Inst { Xor { dest: Reg, src1: Reg, src2: Reg }, /// Shift Right Logical (unsigned) Srl { dest: Reg, src1: Reg, src2: Reg }, + /// Shift Right Logical (unsigned) 32-bit (**RV64 only**) + SrlW { dest: Reg, src1: Reg, src2: Reg }, /// Shift Right Arithmetic (unsigned) Sra { dest: Reg, src1: Reg, src2: Reg }, + /// Shift Right Arithmetic (unsigned) 32-bit (**RV64 only**) + SraW { dest: Reg, src1: Reg, src2: Reg }, /// OR Or { dest: Reg, src1: Reg, src2: Reg }, /// AND @@ -303,6 +313,8 @@ pub enum Inst { // ------------- M extension ------------- /// Multiply Mul { dest: Reg, src1: Reg, src2: Reg }, + /// Multiply 32-bit (**RV64 only**) + MulW { dest: Reg, src1: Reg, src2: Reg }, /// Mul Upper Half Signed-Signed Mulh { dest: Reg, src1: Reg, src2: Reg }, /// Mul Upper Half Signed-Unsigned @@ -311,12 +323,20 @@ pub enum Inst { Mulhu { dest: Reg, src1: Reg, src2: Reg }, /// Divide (signed) Div { dest: Reg, src1: Reg, src2: Reg }, + /// Divide (signed) 32-bit (**RV64 only**) + DivW { dest: Reg, src1: Reg, src2: Reg }, /// Divide Unsigned Divu { dest: Reg, src1: Reg, src2: Reg }, + /// Divide Unsigned 32-bit (**RV64 only**) + DivuW { dest: Reg, src1: Reg, src2: Reg }, /// Remainder (signed) Rem { dest: Reg, src1: Reg, src2: Reg }, + /// Remainder (signed) 32-bit (**RV64 only**) + RemW { dest: Reg, src1: Reg, src2: Reg }, /// Remainder Unsigned Remu { dest: Reg, src1: Reg, src2: Reg }, + /// Remainder Unsigned 32-bit (**RV64 only**) + RemuW { dest: Reg, src1: Reg, src2: Reg }, // ------------- A extension ------------- /// Load-Reserved Word @@ -545,7 +565,7 @@ impl Display for Inst { if imm.as_u32() == 0 { write!(f, "sext.w {dest}, {src1}") } else { - write!(f, "addi.w {dest}, {src1}, {}", imm.as_i32()) + write!(f, "addiw {dest}, {src1}, {}", imm.as_i32()) } } Inst::Slti { @@ -606,13 +626,20 @@ impl Display for Inst { Inst::Add { dest, src1, src2 } => { write!(f, "add {dest}, {src1}, {src2}") } + Inst::AddW { dest, src1, src2 } => { + write!(f, "addw {dest}, {src1}, {src2}") + } Inst::Sub { dest, src1, src2 } => write!(f, "sub {dest}, {src1}, {src2}"), + Inst::SubW { dest, src1, src2 } => write!(f, "subw {dest}, {src1}, {src2}"), Inst::Sll { dest, src1, src2 } => write!(f, "sll {dest}, {src1}, {src2}"), + Inst::SllW { dest, src1, src2 } => write!(f, "sllw {dest}, {src1}, {src2}"), Inst::Slt { dest, src1, src2 } => write!(f, "slt {dest}, {src1}, {src2}"), Inst::Sltu { dest, src1, src2 } => write!(f, "sltu {dest}, {src1}, {src2}"), Inst::Xor { dest, src1, src2 } => write!(f, "xor {dest}, {src1}, {src2}"), Inst::Srl { dest, src1, src2 } => write!(f, "srl {dest}, {src1}, {src2}"), + Inst::SrlW { dest, src1, src2 } => write!(f, "srlw {dest}, {src1}, {src2}"), Inst::Sra { dest, src1, src2 } => write!(f, "sra {dest}, {src1}, {src2}"), + Inst::SraW { dest, src1, src2 } => write!(f, "sraw {dest}, {src1}, {src2}"), Inst::Or { dest, src1, src2 } => write!(f, "or {dest}, {src1}, {src2}"), Inst::And { dest, src1, src2 } => write!(f, "and {dest}, {src1}, {src2}"), Inst::Fence { fence } => match fence.fm { @@ -625,13 +652,18 @@ impl Display for Inst { Inst::Ecall => write!(f, "ecall"), Inst::Ebreak => write!(f, "ebreak"), Inst::Mul { dest, src1, src2 } => write!(f, "mul {dest}, {src1}, {src2}"), + Inst::MulW { dest, src1, src2 } => write!(f, "mulw {dest}, {src1}, {src2}"), Inst::Mulh { dest, src1, src2 } => write!(f, "mulh {dest}, {src1}, {src2}"), Inst::Mulhsu { dest, src1, src2 } => write!(f, "mulhsu {dest}, {src1}, {src2}"), Inst::Mulhu { dest, src1, src2 } => write!(f, "mulhu {dest}, {src1}, {src2}"), Inst::Div { dest, src1, src2 } => write!(f, "div {dest}, {src1}, {src2}"), + Inst::DivW { dest, src1, src2 } => write!(f, "divw {dest}, {src1}, {src2}"), Inst::Divu { dest, src1, src2 } => write!(f, "divu {dest}, {src1}, {src2}"), + Inst::DivuW { dest, src1, src2 } => write!(f, "divuw {dest}, {src1}, {src2}"), Inst::Rem { dest, src1, src2 } => write!(f, "rem {dest}, {src1}, {src2}"), + Inst::RemW { dest, src1, src2 } => write!(f, "remw {dest}, {src1}, {src2}"), Inst::Remu { dest, src1, src2 } => write!(f, "remu {dest}, {src1}, {src2}"), + Inst::RemuW { dest, src1, src2 } => write!(f, "remuw {dest}, {src1}, {src2}"), Inst::LrW { order, dest, addr } => write!(f, "lr.w{order} {dest}, ({addr})",), Inst::ScW { order, @@ -737,14 +769,6 @@ impl InstCode { let end_span = 32 - (range.end() + 1); (self.0 << (end_span)) >> (end_span + range.start()) } - fn immediate_u(self, mappings: &[(RangeInclusive, u32)]) -> Imm { - let mut imm = 0; - for (from, to) in mappings { - let value = self.extract(from.clone()); - imm |= value << to; - } - Imm::new_u32(imm) - } fn immediate_s(self, mappings: &[(RangeInclusive, u32)]) -> Imm { let mut imm = 0; let mut size = 0; @@ -775,6 +799,10 @@ impl InstCode { fn rs2_imm(self) -> u32 { self.extract(20..=24) } + // shifts on RV64 have one extra bit + fn rs2_imm_plus(self) -> u32 { + self.extract(20..=25) + } fn rd(self) -> Reg { Reg(self.extract(7..=11) as u8) } @@ -788,7 +816,8 @@ impl InstCode { self.immediate_s(&[(31..=31, 12), (7..=7, 11), (25..=30, 5), (8..=11, 1)]) } fn imm_u(self) -> Imm { - self.immediate_u(&[(12..=31, 12)]) + // Don't be fooled by the "u", LUI/AUIPC immediates are sign-extended on RV64. + self.immediate_s(&[(12..=31, 12)]) } fn imm_j(self) -> Imm { self.immediate_s(&[(31..=31, 20), (21..=30, 1), (20..=20, 11), (12..=19, 12)]) @@ -1096,7 +1125,7 @@ impl Inst { _ => { let uimm = code.immediate_s(&[(2..=6, 12), (12..=12, 17)]); if uimm.as_u32() == 0 { - return Err(decode_error(code, "C.LUI imm")); + return Err(decode_error(code, "C.LUI zero immediate")); } Inst::Lui { uimm, @@ -1352,7 +1381,13 @@ impl Inst { src1: code.rs1(), }, 0b001 => { - if code.funct7() != 0 { + // For RV32, bit 25 must be zero as well. + let left_zeroes = code.funct7() + >> match xlen { + Xlen::Rv32 => 0, + Xlen::Rv64 => 1, + }; + if left_zeroes != 0 { return Err(decode_error(code, "slli shift overflow")); } Inst::Slli { @@ -1361,24 +1396,42 @@ impl Inst { src1: code.rs1(), } } - 0b101 => match code.funct7() { - 0b0000000 => Inst::Srli { - imm: Imm::new_u32(code.rs2_imm()), - dest: code.rd(), - src1: code.rs1(), + 0b101 => match xlen { + Xlen::Rv32 => match code.funct7() { + 0b0000000 => Inst::Srli { + imm: Imm::new_u32(code.rs2_imm()), + dest: code.rd(), + src1: code.rs1(), + }, + 0b0100000 => Inst::Srai { + imm: Imm::new_u32(code.rs2_imm()), + dest: code.rd(), + src1: code.rs1(), + }, + _ => return Err(decode_error(code, "srli shift overflow")), }, - 0b0100000 => Inst::Srai { - imm: Imm::new_u32(code.rs2_imm()), - dest: code.rd(), - src1: code.rs1(), - }, - _ => return Err(decode_error(code, "OP-IMM funct7")), + Xlen::Rv64 => { + let upper = code.funct7() >> 1; + match upper { + 0b010000 => Inst::Srai { + imm: Imm::new_u32(code.rs2_imm_plus()), + dest: code.rd(), + src1: code.rs1(), + }, + 0b000000 => Inst::Srli { + imm: Imm::new_u32(code.rs2_imm_plus()), + dest: code.rd(), + src1: code.rs1(), + }, + _ => return Err(decode_error(code, "srai/srli upper bits")), + } + } }, _ => return Err(decode_error(code, "OP-IMM funct3")), }, // OP-IMM-32 0b0011011 => { - if !xlen.is_64() { + if xlen.is_32() { return Err(decode_error(code, "OP-IMM-32 only on RV64")); } @@ -1443,6 +1496,28 @@ impl Inst { _ => return Err(decode_error(code, "OP funct3/funct7")), } } + // OP-32 + 0b0111011 => { + if xlen.is_32() { + return Err(decode_error(code, "OP-IMM-32 only on RV64")); + } + + let (dest, src1, src2) = (code.rd(), code.rs1(), code.rs2()); + match (code.funct3(), code.funct7()) { + (0b000, 0b0000000) => Inst::AddW { dest, src1, src2 }, + (0b000, 0b0100000) => Inst::SubW { dest, src1, src2 }, + (0b001, 0b0000000) => Inst::SllW { dest, src1, src2 }, + (0b101, 0b0000000) => Inst::SrlW { dest, src1, src2 }, + (0b101, 0b0100000) => Inst::SraW { dest, src1, src2 }, + + (0b000, 0b0000001) => Inst::MulW { dest, src1, src2 }, + (0b100, 0b0000001) => Inst::DivW { dest, src1, src2 }, + (0b101, 0b0000001) => Inst::DivuW { dest, src1, src2 }, + (0b110, 0b0000001) => Inst::RemW { dest, src1, src2 }, + (0b111, 0b0000001) => Inst::RemuW { dest, src1, src2 }, + _ => return Err(decode_error(code, "OP-32 funct3/funct7")), + } + } // MISC-MEM 0b0001111 => { let fm = code.extract(28..=31); @@ -1713,47 +1788,45 @@ mod tests { let start_time = std::time::Instant::now(); let completed = AtomicU32::new(0); - chunks - .par_iter() - .for_each(|&start| { - let insts = (start..=start.saturating_add(CHUNK_SIZE)) - .filter_map(|code| Some((code, Inst::decode_normal(code, Xlen::Rv32).ok()?))) - .filter(|(_, inst)| is_inst_supposed_to_roundtrip(inst)) - .collect::>(); + chunks.par_iter().for_each(|&start| { + let insts = (start..=start.saturating_add(CHUNK_SIZE)) + .filter_map(|code| Some((code, Inst::decode_normal(code, Xlen::Rv32).ok()?))) + .filter(|(_, inst)| is_inst_supposed_to_roundtrip(inst)) + .collect::>(); - let mut text = - std::format!(".section {TEST_SECTION_NAME}\n.globl _start\n_start:\n"); - for (_, inst) in &insts { - writeln!(text, " {inst}").unwrap(); - } + let mut text = std::format!(".section {TEST_SECTION_NAME}\n.globl _start\n_start:\n"); + for (_, inst) in &insts { + writeln!(text, " {inst}").unwrap(); + } - let data = clang_assemble(&text, "-march=rv32ima_zihintpause"); + let data = clang_assemble(&text, "-march=rv32ima_zihintpause"); - for (i, result_code) in data.chunks(4).enumerate() { - let result_code = u32::from_le_bytes(result_code.try_into().unwrap()); + for (i, result_code) in data.chunks(4).enumerate() { + let result_code = u32::from_le_bytes(result_code.try_into().unwrap()); - assert_eq!( - insts[i].0, result_code, - "failed to rountrip!\n\ + assert_eq!( + insts[i].0, result_code, + "failed to rountrip!\n\ instruction `{:0>32b}` failed to rountrip\n\ resulted in `{:0>32b}` instead.\n\ disassembly of original instruction: `{}`", - insts[i].0, result_code, insts[i].1 - ); - } + insts[i].0, result_code, insts[i].1 + ); + } - let already_completed = completed.fetch_add(1, Ordering::Relaxed); - let already_elapsed = start_time.elapsed(); + let already_completed = completed.fetch_add(1, Ordering::Relaxed); + let already_elapsed = start_time.elapsed(); - let remaining_chunks = CHUNKS.saturating_sub(already_completed); - let remaining = already_elapsed / std::cmp::max(already_completed, 1) * remaining_chunks; + let remaining_chunks = CHUNKS.saturating_sub(already_completed); + let remaining = + already_elapsed / std::cmp::max(already_completed, 1) * remaining_chunks; - writeln!( - std::io::stdout(), - "Completed chunk {already_completed}/{CHUNKS} (estimated {remaining:?} remaining)", - ) - .unwrap(); - }); + writeln!( + std::io::stdout(), + "Completed chunk {already_completed}/{CHUNKS} (estimated {remaining:?} remaining)", + ) + .unwrap(); + }); } #[test] diff --git a/src/emu.rs b/src/emu.rs index 2881412..06a1eb9 100644 --- a/src/emu.rs +++ b/src/emu.rs @@ -386,8 +386,33 @@ impl Emulator { Inst::Srli { imm, dest, src1 } => self[dest] = self[src1].unsigned_shr(imm.as_u32()), Inst::Srai { imm, dest, src1 } => self[dest] = self[src1].signed_shr(imm.as_u32()), Inst::Add { dest, src1, src2 } => self[dest] = self[src1].add(self[src2]), + Inst::AddW { dest, src1, src2 } => { + self[dest] = XLEN::switch2( + self[src1], + self[src2], + |_, _| unreachable!(), + |a, b| (a as u32).add(b as u32) as i32 as i64 as u64, + ); + } Inst::Sub { dest, src1, src2 } => self[dest] = self[src1].sub(self[src2]), - Inst::Sll { dest, src1, src2 } => self[dest] = self[src1].shl(self[src2].truncate32()), + Inst::SubW { dest, src1, src2 } => { + self[dest] = XLEN::switch2( + self[src1], + self[src2], + |_, _| unreachable!(), + |a, b| (a as u32).sub(b as u32) as i32 as i64 as u64, + ); + } + Inst::Sll { dest, src1, src2 } => { + self[dest] = self[src1].shl(self[src2].truncate32()); + } + Inst::SllW { dest, src1, src2 } => { + self[dest] = XLEN::switch( + self[src1], + |_| unreachable!(), + |a| (a as u32).shl(self[src2].truncate32()) as i32 as i64 as u64, + ); + } Inst::Slt { dest, src1, src2 } => { self[dest] = XLEN::from_bool(self[src1].signed_lt(self[src2])); } @@ -398,9 +423,23 @@ impl Emulator { Inst::Srl { dest, src1, src2 } => { self[dest] = self[src1].unsigned_shr(self[src2].truncate32()) } + Inst::SrlW { dest, src1, src2 } => { + self[dest] = XLEN::switch( + self[src1], + |_| unreachable!(), + |a| (a as u32).unsigned_shr(self[src2].truncate32()) as i32 as i64 as u64, + ); + } Inst::Sra { dest, src1, src2 } => { self[dest] = self[src1].signed_shr(self[src2].truncate32()) } + Inst::SraW { dest, src1, src2 } => { + self[dest] = XLEN::switch( + self[src1], + |_| unreachable!(), + |a| (a as u32).unsigned_shr(self[src2].truncate32()) as i32 as i64 as u64, + ); + } Inst::Or { dest, src1, src2 } => self[dest] = self[src1].or(self[src2]), Inst::And { dest, src1, src2 } => self[dest] = self[src1].and(self[src2]), Inst::Fence { fence: _ } => { /* dont care */ } @@ -502,7 +541,40 @@ impl Emulator { } self.reservation_set = None; } - _ => return Err(Status::UnsupportedInst(inst)), + Inst::AddiW { imm, dest, src1 } => { + self[dest] = XLEN::switch( + self[src1], + |_| unreachable!(), + |src| ((src as u32).wrapping_add(imm.as_u32())) as i32 as i64 as u64, + ); + } + Inst::SlliW { imm, dest, src1 } => { + self[dest] = XLEN::switch( + self[src1], + |_| unreachable!(), + |src| ((src as u32).shl(imm.as_u32())) as u64, + ); + } + Inst::SrliW { imm, dest, src1 } => { + self[dest] = XLEN::switch( + self[src1], + |_| unreachable!(), + |src| ((src as u32).unsigned_shr(imm.as_u32())) as u64, + ); + } + Inst::SraiW { imm, dest, src1 } => { + self[dest] = XLEN::switch( + self[src1], + |_| unreachable!(), + |src| ((src as u32).signed_shr(imm.as_u32())) as u64, + ); + } + Inst::MulW { dest, src1, src2 } => todo!(), + Inst::DivW { dest, src1, src2 } => todo!(), + Inst::DivuW { dest, src1, src2 } => todo!(), + Inst::RemW { dest, src1, src2 } => todo!(), + Inst::RemuW { dest, src1, src2 } => todo!(), + _ => todo!(), } if !jumped { @@ -676,7 +748,7 @@ impl Emulator { } } -pub trait XLen: Copy + PartialEq + Eq { +pub trait XLen: Copy + PartialEq + Eq + Debug { type Signed; type NextUnsigned; type NextSigned; @@ -687,7 +759,15 @@ pub trait XLen: Copy + PartialEq + Eq { const SIGNED_MIN: Self; const MAX: Self; - fn switch(self, on_32: impl FnOnce(u32) -> R, on_64: impl FnOnce(u64) -> R) -> R; + const IS_64: bool = matches!(Self::XLEN, rvdc::Xlen::Rv64); + + fn switch(self, on_32: impl FnOnce(u32) -> u32, on_64: impl FnOnce(u64) -> u64) -> Self; + fn switch2( + self, + other: Self, + on_32: impl FnOnce(u32, u32) -> u32, + on_64: impl FnOnce(u64, u64) -> u64, + ) -> Self; fn from_bool(v: bool) -> Self { Self::from_32_z(v as u32) @@ -839,9 +919,17 @@ impl XLen for u32 { const XLEN: rvdc::Xlen = rvdc::Xlen::Rv32; - fn switch(self, on_32: impl FnOnce(u32) -> R, _: impl FnOnce(u64) -> R) -> R { + fn switch(self, on_32: impl FnOnce(u32) -> u32, _: impl FnOnce(u64) -> u64) -> Self { on_32(self) } + fn switch2( + self, + other: Self, + on_32: impl FnOnce(u32, u32) -> u32, + _: impl FnOnce(u64, u64) -> u64, + ) -> Self { + on_32(self, other) + } fn from_32_s(v: u32) -> Self { v @@ -863,9 +951,17 @@ impl XLen for u64 { const XLEN: rvdc::Xlen = rvdc::Xlen::Rv64; - fn switch(self, _: impl FnOnce(u32) -> R, on_64: impl FnOnce(u64) -> R) -> R { + fn switch(self, _: impl FnOnce(u32) -> u32, on_64: impl FnOnce(u64) -> u64) -> Self { on_64(self) } + fn switch2( + self, + other: Self, + _: impl FnOnce(u32, u32) -> u32, + on_64: impl FnOnce(u64, u64) -> u64, + ) -> Self { + on_64(self, other) + } fn from_32_s(v: u32) -> Self { v as i32 as i64 as u64 diff --git a/tests/check.rs b/tests/check.rs index 9dd4fc2..91d4380 100644 --- a/tests/check.rs +++ b/tests/check.rs @@ -30,7 +30,7 @@ fn check() -> eyre::Result<()> { 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, "rv64ima")?; // test_case(tmpdir.path(), &file, name, 64, "rv64imac")?; } @@ -70,7 +70,7 @@ fn test_case( } }), Box::new(|_, xreg| { - if xreg[Reg::A7.0 as usize] == u32::MAX as u64 { + if xreg[Reg::A7.0 as usize] == u64::MAX { if xreg[Reg::A0.0 as usize] == 1 { Err(rustv32i::emu::Status::Exit { code: 0 }) } else { @@ -101,6 +101,7 @@ fn build(tmpdir: &Path, src: &Path, size: u8, march: &str) -> eyre::Result