Optimize rvdc tests

This commit is contained in:
nora 2025-04-13 16:17:29 +02:00
parent e9a689aa1a
commit f0c04f1466
4 changed files with 118 additions and 53 deletions

52
Cargo.lock generated
View file

@ -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",
]

View file

@ -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"

View file

@ -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")

View file

@ -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<Inst, DecodeError> {
pub fn decode_compressed(code: u16, _xlen: Xlen) -> Result<Inst, DecodeError> {
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::<Inst>() <= 12);
assert!(
size_of::<Inst>() <= 16,
"size of instruction is too large: {}",
size_of::<Inst>()
);
}
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::<Vec<_>>();
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::<Vec<_>>();
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::<Vec<_>>();
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]