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] 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]