1use std::ffi::CString;
13use std::fmt;
14use std::os::raw::{c_char, c_int};
15use std::path::Path;
16
17const ABI_VERSION: u32 = 1;
18const COMMAND_STATUS: u32 = 0;
19const COMMAND_START: u32 = 1;
20const COMMAND_STOP: u32 = 2;
21const COMMAND_FLUSH: u32 = 3;
22const ERROR_CAPACITY: usize = 512;
23
24#[repr(C)]
25#[derive(Default)]
26struct RawTraceStatus {
27 abi_version: u32,
28 capability: u32,
29 state: u32,
30 reserved: u32,
31 start_time_steps: u64,
32 end_time_steps: u64,
33 dump_count: u64,
34}
35
36type TraceControl = unsafe extern "C" fn(
37 command: u32,
38 path: *const c_char,
39 status: *mut RawTraceStatus,
40 error: *mut c_char,
41 error_capacity: usize,
42) -> c_int;
43
44#[cfg(unix)]
45type TraceFormatProbe = unsafe extern "C" fn() -> u32;
46
47#[derive(Clone, Copy, Debug, Eq, PartialEq)]
52pub enum TraceFormat {
53 Fst,
54 Other(u32),
55}
56
57#[derive(Clone, Copy, Debug, Eq, PartialEq)]
59pub enum TraceState {
60 Unavailable,
62 Idle,
64 Active,
66}
67
68#[derive(Clone, Copy, Debug, Eq, PartialEq)]
70pub struct TraceStatus {
71 pub state: TraceState,
72 pub start_time_steps: u64,
73 pub end_time_steps: u64,
74 pub dump_count: u64,
75}
76
77#[derive(Clone, Debug, Eq, PartialEq)]
79pub enum TraceError {
80 Unavailable(String),
81 InvalidPath(String),
82 WrongState(String),
83 Host(String),
84}
85
86impl fmt::Display for TraceError {
87 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
88 match self {
89 Self::Unavailable(message) => write!(formatter, "{message}"),
90 Self::InvalidPath(message) => write!(formatter, "{message}"),
91 Self::WrongState(message) => write!(formatter, "{message}"),
92 Self::Host(message) => write!(formatter, "{message}"),
93 }
94 }
95}
96
97impl std::error::Error for TraceError {}
98
99pub fn status() -> Result<TraceStatus, TraceError> {
101 control(COMMAND_STATUS, None)
102}
103
104pub fn host_present() -> bool {
110 load_control().is_ok()
111}
112
113pub fn host_format() -> Option<TraceFormat> {
119 #[cfg(unix)]
120 {
121 let library = libloading::os::unix::Library::this();
122 unsafe {
125 if let Ok(probe) = library.get::<TraceFormatProbe>(b"rustdv_simulator_trace_format\0") {
126 return Some(match probe() {
127 1 => TraceFormat::Fst,
128 other => TraceFormat::Other(other),
129 });
130 }
131 if library
132 .get::<TraceControl>(b"rustdv_verilator_trace_control\0")
133 .is_ok()
134 {
135 return Some(TraceFormat::Fst);
136 }
137 }
138 }
139 None
140}
141
142pub fn start(path: &Path) -> Result<TraceStatus, TraceError> {
145 let path = path_to_c_string(path)?;
146 control(COMMAND_START, Some(&path))
147}
148
149pub fn flush() -> Result<TraceStatus, TraceError> {
151 control(COMMAND_FLUSH, None)
152}
153
154pub fn stop() -> Result<TraceStatus, TraceError> {
156 control(COMMAND_STOP, None)
157}
158
159fn control(command: u32, path: Option<&CString>) -> Result<TraceStatus, TraceError> {
160 let function = load_control()?;
161 if crate::phase::current_phase_if_initialized() != Some(crate::phase::SimPhase::ReadOnly) {
162 return Err(TraceError::WrongState(
163 "simulator trace control must run on the simulator thread at settled ReadOnly"
164 .to_owned(),
165 ));
166 }
167 let mut raw = RawTraceStatus::default();
168 let mut error = [0_u8; ERROR_CAPACITY];
169 let path = path.map_or(std::ptr::null(), |value| value.as_ptr());
170
171 let result = unsafe {
175 function(
176 command,
177 path,
178 &mut raw,
179 error.as_mut_ptr().cast(),
180 error.len(),
181 )
182 };
183 let message = error_message(&error);
184
185 match result {
186 0 => decode_status(raw),
187 1 => Err(TraceError::Unavailable(message)),
188 3 => Err(TraceError::InvalidPath(message)),
189 4 => Err(TraceError::WrongState(message)),
190 _ => Err(TraceError::Host(message)),
191 }
192}
193
194fn decode_status(raw: RawTraceStatus) -> Result<TraceStatus, TraceError> {
195 if raw.abi_version != ABI_VERSION {
196 return Err(TraceError::Host(format!(
197 "unsupported simulator trace ABI {} (expected {ABI_VERSION})",
198 raw.abi_version
199 )));
200 }
201 let state = match (raw.capability, raw.state) {
202 (0, _) => TraceState::Unavailable,
203 (1, 0) => TraceState::Idle,
204 (1, 1) => TraceState::Active,
205 (capability, state) => {
206 return Err(TraceError::Host(format!(
207 "invalid simulator trace status capability={capability} state={state}"
208 )));
209 }
210 };
211 Ok(TraceStatus {
212 state,
213 start_time_steps: raw.start_time_steps,
214 end_time_steps: raw.end_time_steps,
215 dump_count: raw.dump_count,
216 })
217}
218
219fn error_message(buffer: &[u8]) -> String {
220 let terminator = buffer
221 .iter()
222 .position(|byte| *byte == 0)
223 .unwrap_or(buffer.len());
224 let message = String::from_utf8_lossy(&buffer[..terminator]).into_owned();
225 if message.is_empty() {
226 "simulator trace host returned an unspecified error".to_owned()
227 } else {
228 message
229 }
230}
231
232#[cfg(unix)]
233fn path_to_c_string(path: &Path) -> Result<CString, TraceError> {
234 use std::os::unix::ffi::OsStrExt;
235 CString::new(path.as_os_str().as_bytes())
236 .map_err(|_| TraceError::InvalidPath("trace path contains an interior NUL byte".to_owned()))
237}
238
239#[cfg(not(unix))]
240fn path_to_c_string(path: &Path) -> Result<CString, TraceError> {
241 CString::new(path.to_string_lossy().as_bytes())
242 .map_err(|_| TraceError::InvalidPath("trace path contains an interior NUL byte".to_owned()))
243}
244
245#[cfg(unix)]
246fn load_control() -> Result<TraceControl, TraceError> {
247 let library = libloading::os::unix::Library::this();
248 resolve_control(|name| unsafe { library.get::<TraceControl>(name).ok().map(|symbol| *symbol) })
252 .ok_or_else(|| {
253 TraceError::Unavailable(
254 "the current simulator host does not provide runtime waveform capture".to_owned(),
255 )
256 })
257}
258
259#[cfg(unix)]
260fn resolve_control(mut lookup: impl FnMut(&[u8]) -> Option<TraceControl>) -> Option<TraceControl> {
261 lookup(b"rustdv_simulator_trace_control\0")
262 .or_else(|| lookup(b"rustdv_verilator_trace_control\0"))
263}
264
265#[cfg(not(unix))]
266fn load_control() -> Result<TraceControl, TraceError> {
267 Err(TraceError::Unavailable(
268 "runtime simulator trace control is currently supported on Unix hosts only".to_owned(),
269 ))
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275
276 #[cfg(unix)]
277 unsafe extern "C" fn legacy_control(
278 _: u32,
279 _: *const c_char,
280 _: *mut RawTraceStatus,
281 _: *mut c_char,
282 _: usize,
283 ) -> c_int {
284 0
285 }
286
287 #[cfg(unix)]
288 #[test]
289 fn legacy_verilator_host_symbol_remains_supported() {
290 let mut looked_up = Vec::new();
291 let control = resolve_control(|name| {
292 looked_up.push(name.to_vec());
293 (name == b"rustdv_verilator_trace_control\0").then_some(legacy_control as TraceControl)
294 });
295 assert!(std::ptr::fn_addr_eq(
296 control.expect("legacy symbol must resolve"),
297 legacy_control as TraceControl
298 ));
299 assert_eq!(
300 looked_up,
301 [
302 b"rustdv_simulator_trace_control\0".to_vec(),
303 b"rustdv_verilator_trace_control\0".to_vec(),
304 ]
305 );
306 }
307
308 #[test]
309 fn ordinary_rust_test_reports_missing_trace_host() {
310 assert!(!host_present());
311 assert_eq!(host_format(), None);
312 let error = status().unwrap_err();
313 assert!(matches!(error, TraceError::Unavailable(_)));
314 }
315}