dramsim3/
lib.rs

1use std::{
2    cell::UnsafeCell,
3    ffi::{c_void, CString},
4    ptr::null_mut,
5    path::Path,
6};
7
8mod ffi;
9
10// equavilent to `*const dyn FnMut(u64, bool)`,
11// but raw fat pointer is unstable
12#[repr(C)]
13#[derive(Clone, Copy)]
14struct RawCallbackFnMut {
15    fun: unsafe extern "C" fn(data: *mut c_void, addr: u64, is_write: bool),
16    data: *mut c_void,
17}
18
19impl RawCallbackFnMut {
20    pub const fn invalid() -> Self {
21        RawCallbackFnMut {
22            fun: Self::_invalid_fn,
23            data: null_mut(),
24        }
25    }
26
27    pub fn from_fn_mut<F: FnMut(u64, bool)>(f: &mut F) -> Self {
28        RawCallbackFnMut {
29            fun: Self::_invoke_fn_mut::<F>,
30            data: f as *mut F as *mut c_void,
31        }
32    }
33
34    unsafe extern "C" fn _invalid_fn(_data: *mut c_void, _addr: u64, _is_write: bool) {
35        unreachable!("callback is invoked outside tick");
36    }
37
38    unsafe extern "C" fn _invoke_fn_mut<F: FnMut(u64, bool)>(
39        f: *mut c_void,
40        addr: u64,
41        is_write: bool,
42    ) {
43        (*(f as *mut F))(addr, is_write)
44    }
45
46    extern "C" fn _invoke_cb(this: *mut c_void, addr: u64, is_write: bool) {
47        unsafe {
48            let this = *(this as *mut RawCallbackFnMut);
49            (this.fun)(this.data, addr, is_write);
50        }
51    }
52}
53
54#[derive(Clone, Copy, Debug)]
55pub enum MemorySystemCreationError {
56    NulInConfig,
57    NulInDir,
58}
59
60pub struct MemorySystem {
61    ffi_ptr: *mut c_void,
62
63    // _cb is used by both C++ and Rust side,
64    // use UnsafeCell to avoid potential aliasing UB
65    _cb: Box<UnsafeCell<RawCallbackFnMut>>,
66}
67
68// safety: `Send` generally means safe under mutex
69unsafe impl Send for MemorySystem {}
70
71impl MemorySystem {
72    pub fn new(config: impl AsRef<Path>, dir: impl AsRef<Path>) -> Result<MemorySystem, MemorySystemCreationError> {
73        let config_cstr = CString::new(config.as_ref().as_os_str().as_encoded_bytes()).map_err(|_| MemorySystemCreationError::NulInConfig)?;
74        let dir_cstr = CString::new(dir.as_ref().as_os_str().as_encoded_bytes()).map_err(|_| MemorySystemCreationError::NulInDir)?;
75
76        let cb_box: Box<UnsafeCell<RawCallbackFnMut>> =
77            Box::new(UnsafeCell::new(RawCallbackFnMut::invalid()));
78        let cb_ptr: *mut RawCallbackFnMut = cb_box.get();
79
80        let handle = unsafe {
81            ffi::ds3_create(
82                cb_ptr as *mut c_void,
83                config_cstr.as_ptr(),
84                dir_cstr.as_ptr(),
85                RawCallbackFnMut::_invoke_cb,
86            )
87        };
88
89        Ok(MemorySystem {
90            ffi_ptr: handle,
91            _cb: cb_box,
92        })
93    }
94
95    pub fn tick(&mut self, mut f: impl FnMut(u64, bool)) {
96        let cb_ptr: *mut RawCallbackFnMut = self._cb.get();
97        // safety: we hold `&mut self` here, no data races when changing cb_ptr
98        unsafe {
99            *cb_ptr = RawCallbackFnMut::from_fn_mut(&mut f);
100            ffi::ds3_tick(self.ffi_ptr);
101
102            // could be omitted, add as additional safe guard
103            *cb_ptr = RawCallbackFnMut::invalid();
104        }
105    }
106
107    pub fn can_add(&self, addr: u64, is_write: bool) -> bool {
108        unsafe { ffi::ds3_can_add(self.ffi_ptr, addr, is_write) }
109    }
110
111    pub fn add(&mut self, addr: u64, is_write: bool) -> bool {
112        unsafe { ffi::ds3_add(self.ffi_ptr, addr, is_write) }
113    }
114
115    pub fn tck(&self) -> f64 {
116        unsafe { ffi::ds3_get_tck(self.ffi_ptr) }
117    }
118
119    pub fn bus_bits(&self) -> usize {
120        (unsafe { ffi::ds3_get_bus_bits(self.ffi_ptr) }) as usize
121    }
122
123    pub fn burst_length(&self) -> usize {
124        (unsafe { ffi::ds3_get_burst_length(self.ffi_ptr) }) as usize
125    }
126
127    pub fn queue_size(&self) -> usize {
128        (unsafe { ffi::ds3_get_queue_size(self.ffi_ptr) }) as usize
129    }
130}
131
132impl Drop for MemorySystem {
133    fn drop(&mut self) {
134        unsafe {
135            ffi::ds3_drop(self.ffi_ptr);
136        }
137    }
138}
139
140#[test]
141fn test() -> anyhow::Result<()> {
142    use std::cell::RefCell;
143    use std::path::PathBuf;
144    use std::rc::Rc;
145
146    let mut config = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
147    config.push("DRAMsim3/configs/HBM2_4Gb_x128.ini");
148
149    let dir = tempfile::tempdir()?;
150
151    let mut pushed = false;
152    let resolved: Rc<RefCell<bool>> = Rc::new(RefCell::new(false));
153
154    let sys_resolved = resolved.clone();
155
156    let mut sys = MemorySystem::new(&config, &dir).unwrap();
157
158    loop {
159        sys.tick(|addr, is_write| {
160            assert_eq!(addr, 0xdeadbeef);
161            assert_eq!(is_write, false);
162            *sys_resolved.borrow_mut() = true;
163        });
164        if !pushed && sys.can_add(0xdeadbeef, false) {
165            assert!(sys.add(0xdeadbeef, false));
166            pushed = true;
167        }
168
169        {
170            if *resolved.borrow() {
171                break;
172            }
173        }
174    }
175
176    Ok(())
177}