Skip to main content

celox_runtime/
vcd.rs

1mod plan;
2
3use celox_state_layout::TRACE_GROUP_BYTES;
4use num_bigint::BigUint;
5use plan::TracePlan;
6use std::fs::File;
7use std::io::{BufWriter, Write};
8use std::mem::MaybeUninit;
9use std::path::Path;
10
11/// Describes a signal for VCD recording.
12///
13/// Self-contained — does not reference any IR types. Can be cached
14/// alongside a shared backend artifact so that VCD
15/// works even on cache-hit paths.
16#[derive(Clone, Debug)]
17pub struct VcdSignalDesc {
18    /// VCD scope name (e.g. instance path).
19    pub scope: String,
20    /// Signal name within the scope.
21    pub name: String,
22    /// Byte offset in JIT memory (stable region).
23    pub offset: usize,
24    /// Bit width.
25    pub width: usize,
26    /// Whether this signal has a 4-state mask region immediately after the value.
27    pub is_4state: bool,
28}
29
30/// Describes a signal whose value is supplied by an external runtime rather
31/// than stored in Celox's flat simulation memory.
32#[derive(Clone, Debug)]
33pub struct VcdExternalSignalDesc {
34    pub scope: String,
35    pub name: String,
36    pub width: usize,
37}
38
39struct VcdHeaderSignal {
40    scope: String,
41    name: String,
42    width: usize,
43}
44
45/// Finalized with the header: an optional space, the ID, and a newline.
46/// Most IDs fit in one fixed-size copy; retain arbitrary-length IDs as well.
47enum VcdRecordSuffix {
48    Inline { bytes: [u8; 8], len: u8 },
49    Long(Box<[u8]>),
50}
51
52impl VcdRecordSuffix {
53    fn new(width: usize, id: &str) -> Self {
54        let prefix = usize::from(width != 1);
55        let len = prefix + id.len() + 1;
56        if len <= 8 {
57            let mut bytes = [b' '; 8];
58            bytes[prefix..len - 1].copy_from_slice(id.as_bytes());
59            bytes[len - 1] = b'\n';
60            Self::Inline {
61                bytes,
62                len: len as u8,
63            }
64        } else {
65            let mut bytes = Vec::with_capacity(len);
66            if prefix != 0 {
67                bytes.push(b' ');
68            }
69            bytes.extend_from_slice(id.as_bytes());
70            bytes.push(b'\n');
71            Self::Long(bytes.into_boxed_slice())
72        }
73    }
74
75    fn capacity(&self) -> usize {
76        match self {
77            Self::Inline { .. } => 8,
78            Self::Long(bytes) => bytes.len(),
79        }
80    }
81
82    /// Initializes the returned number of bytes, plus padding for short IDs.
83    #[inline]
84    fn encode(&self, out: &mut [MaybeUninit<u8>]) -> usize {
85        match self {
86            Self::Inline { bytes, len } => {
87                copy_encoded(out, bytes);
88                *len as usize
89            }
90            Self::Long(bytes) => {
91                copy_encoded(out, bytes);
92                bytes.len()
93            }
94        }
95    }
96}
97
98#[derive(Clone, Copy, Debug, Default)]
99pub struct VcdStatistics {
100    pub comparisons: u64,
101    pub changes: u64,
102    pub value_bytes: u64,
103}
104
105pub struct VcdWriter<W: Write = File> {
106    output: VcdOutput<W>,
107    headers: Vec<VcdHeaderSignal>,
108    plan: TracePlan,
109    groups: fxhash::FxHashMap<usize, Vec<usize>>,
110    selected: Vec<usize>,
111    /// Consumed groups retained after a failed dump; empty after success.
112    activity: Vec<usize>,
113    timestamp: u64,
114    header_written: bool,
115    initial_values_written: bool,
116    external_count: usize,
117}
118
119struct VcdOutput<W: Write> {
120    writer: BufWriter<W>,
121    /// Complete records accepted by the encoder. Retained on I/O errors because
122    /// the trace plan already caches their values. Headers and timestamps also
123    /// use this queue, and are drained before any value records are appended.
124    /// Successful dumps hand the tail to BufWriter so Drop owns pending output.
125    encoded: Vec<u8>,
126    /// Bytes already accepted by BufWriter, including short writes before an error.
127    written: usize,
128    encoded_changes: u64,
129    stats: VcdStatistics,
130}
131
132impl VcdWriter<File> {
133    pub fn new<P: AsRef<Path>>(path: P, descs: &[VcdSignalDesc]) -> std::io::Result<Self> {
134        Ok(Self::from_writer(File::create(path)?, descs))
135    }
136}
137
138impl<W: Write> VcdWriter<W> {
139    pub fn from_writer(writer: W, descs: &[VcdSignalDesc]) -> Self {
140        let mut plan = TracePlan::default();
141        let mut groups: fxhash::FxHashMap<usize, Vec<usize>> = Default::default();
142        let headers = descs
143            .iter()
144            .enumerate()
145            .map(|(index, desc)| {
146                plan.add_memory(desc);
147                let group = desc.offset / TRACE_GROUP_BYTES;
148                groups.entry(group).or_default().push(index);
149                VcdHeaderSignal {
150                    scope: desc.scope.clone(),
151                    name: desc.name.clone(),
152                    width: desc.width,
153                }
154            })
155            .collect::<Vec<_>>();
156        Self {
157            output: VcdOutput {
158                writer: BufWriter::with_capacity(256 * 1024, writer),
159                encoded: Vec::new(),
160                written: 0,
161                encoded_changes: 0,
162                stats: VcdStatistics::default(),
163            },
164            headers,
165            plan,
166            groups,
167            selected: Vec::new(),
168            activity: Vec::new(),
169            timestamp: 0,
170            header_written: false,
171            initial_values_written: false,
172            external_count: 0,
173        }
174    }
175
176    /// Publish buffered output, also reporting errors that Drop cannot report.
177    pub fn flush(&mut self) -> std::io::Result<()> {
178        self.output.write_encoded()?;
179        self.output.writer.flush()
180    }
181
182    pub fn statistics(&self) -> VcdStatistics {
183        self.output.stats
184    }
185    pub fn get_ref(&self) -> &W {
186        self.output.writer.get_ref()
187    }
188
189    /// Dump as the backend's sole incremental waveform observer. Multiple
190    /// writers must consume activity once and share it through
191    /// `dump_with_activity`, since each consumption clears the pending flags.
192    pub fn dump_backend<B: crate::backend::SimBackend>(
193        &mut self,
194        timestamp: u64,
195        backend: &mut B,
196        external: &[(BigUint, BigUint)],
197    ) -> std::io::Result<()> {
198        // Reject recoverable input errors before consuming pending writes.
199        self.validate_external_count(external.len())?;
200        let mut activity = std::mem::take(&mut self.activity);
201        let tracked = if activity.is_empty() {
202            backend.take_vcd_activity(&mut activity)
203        } else {
204            // Consumption clears its destination. Keep failed-dump activity
205            // and merge writes made since that attempt, including duplicates.
206            // Only this retry path needs a second allocation.
207            let mut new_activity = Vec::new();
208            let tracked = backend.take_vcd_activity(&mut new_activity);
209            activity.extend(new_activity);
210            activity.sort_unstable();
211            activity.dedup();
212            tracked
213        };
214        let (ptr, size) = backend.memory_as_ptr();
215        // SAFETY: the backend owns the image and cannot run during this dump.
216        let memory = unsafe { std::slice::from_raw_parts(ptr, size) };
217        let result =
218            self.dump_with_activity(timestamp, memory, external, tracked.then_some(&activity));
219        if result.is_ok() {
220            activity.clear();
221        }
222        self.activity = activity;
223        result
224    }
225
226    pub fn into_inner(mut self) -> std::io::Result<W> {
227        self.flush()?;
228        self.output
229            .writer
230            .into_inner()
231            .map_err(|error| error.into_error())
232    }
233
234    /// Adds externally supplied signals before the first dump. VCD headers
235    /// cannot be extended after value changes have started.
236    pub fn add_external_signals(&mut self, descs: &[VcdExternalSignalDesc]) -> std::io::Result<()> {
237        if descs.is_empty() {
238            return Ok(());
239        }
240        if self.external_count != 0 {
241            let existing = self.headers[self.headers.len() - self.external_count..]
242                .iter()
243                .zip(descs)
244                .all(|(signal, desc)| {
245                    signal.scope == desc.scope
246                        && signal.name == desc.name
247                        && signal.width == desc.width
248                });
249            if existing && self.external_count == descs.len() {
250                return Ok(());
251            }
252        }
253        if self.header_written {
254            return Err(std::io::Error::new(
255                std::io::ErrorKind::InvalidInput,
256                "cannot add external VCD signals after the first dump",
257            ));
258        }
259        for desc in descs {
260            let index = self.external_count;
261            self.external_count += 1;
262            self.headers.push(VcdHeaderSignal {
263                scope: desc.scope.clone(),
264                name: desc.name.clone(),
265                width: desc.width,
266            });
267            self.plan.add_external(index, desc.width);
268        }
269        Ok(())
270    }
271
272    #[cold]
273    fn write_header(&mut self) -> std::io::Result<()> {
274        writeln!(self.output.encoded, "$date")?;
275        writeln!(
276            self.output.encoded,
277            "  {}",
278            chrono::Local::now().format("%Y-%m-%d %H:%M:%S")
279        )?;
280        writeln!(self.output.encoded, "$end")?;
281        writeln!(self.output.encoded, "$version")?;
282        writeln!(self.output.encoded, "  celox")?;
283        writeln!(self.output.encoded, "$end")?;
284        writeln!(self.output.encoded, "$timescale 1ns $end")?;
285
286        let mut scope_order = Vec::<String>::new();
287        let mut scope_groups = Vec::<Vec<usize>>::new();
288        let mut scope_idx = fxhash::FxHashMap::<String, usize>::default();
289        for (signal_index, signal) in self.headers.iter().enumerate() {
290            if let Some(index) = scope_idx.get(&signal.scope).copied() {
291                scope_groups[index].push(signal_index);
292            } else {
293                let index = scope_order.len();
294                scope_idx.insert(signal.scope.clone(), index);
295                scope_order.push(signal.scope.clone());
296                scope_groups.push(vec![signal_index]);
297            }
298        }
299        let mut next_id = 0;
300        for (scope, group) in scope_order.iter().zip(scope_groups) {
301            writeln!(self.output.encoded, "$scope module {} $end", scope)?;
302            for signal_index in group {
303                let signal = &mut self.headers[signal_index];
304                let id = Self::generate_vcd_id(next_id);
305                *self.plan.suffix(signal_index) = VcdRecordSuffix::new(signal.width, &id);
306                next_id += 1;
307                writeln!(
308                    self.output.encoded,
309                    "$var wire {} {} {} $end",
310                    signal.width, id, signal.name
311                )?;
312            }
313            writeln!(self.output.encoded, "$upscope $end")?;
314        }
315        writeln!(self.output.encoded, "$enddefinitions $end")?;
316        writeln!(self.output.encoded, "$dumpvars")?;
317        writeln!(self.output.encoded, "$end")?;
318        let record_capacity = self
319            .headers
320            .iter()
321            .enumerate()
322            .map(|(index, signal)| {
323                signal.width.max(1)
324                    + usize::from(signal.width != 1)
325                    + self.plan.suffix(index).capacity()
326            })
327            .max()
328            .map(|max_record| self.output.writer.capacity() + max_record);
329        if let Some(capacity) = record_capacity {
330            // A block can cross the writer's capacity by one complete record.
331            // Include the short suffix's padding, even for scalar-only traces.
332            // Integer SIMD stores also fit within the full declared width.
333            // Reserve here so record encoding never needs to grow the Vec.
334            self.output
335                .encoded
336                .reserve(capacity.saturating_sub(self.output.encoded.len()));
337        }
338        self.header_written = true;
339        self.output.write_encoded()?;
340        if let Some(capacity) = record_capacity {
341            // A large header must not permanently enlarge the value buffer.
342            self.output.encoded.shrink_to(capacity);
343        }
344        Ok(())
345    }
346
347    fn generate_vcd_id(num: usize) -> String {
348        let mut id = String::new();
349        let mut n = num;
350        loop {
351            let char = ((n % 94) + 33) as u8 as char;
352            id.push(char);
353            if n < 94 {
354                break;
355            }
356            n = (n / 94) - 1;
357        }
358        id.chars().rev().collect()
359    }
360
361    /// Full-scan reference path, also suitable for uninstrumented/raw memory.
362    pub fn dump(&mut self, timestamp: u64, memory: &[u8]) -> std::io::Result<()> {
363        self.dump_with_external(timestamp, memory, &[])
364    }
365
366    pub fn dump_with_external(
367        &mut self,
368        timestamp: u64,
369        memory: &[u8],
370        external: &[(BigUint, BigUint)],
371    ) -> std::io::Result<()> {
372        self.dump_with_activity(timestamp, memory, external, None)
373    }
374
375    fn validate_external_count(&self, count: usize) -> std::io::Result<()> {
376        if count != self.external_count {
377            return Err(std::io::Error::new(
378                std::io::ErrorKind::InvalidInput,
379                format!(
380                    "expected {} external VCD values, got {count}",
381                    self.external_count
382                ),
383            ));
384        }
385        Ok(())
386    }
387
388    /// `activity` contains unique physical group IDs consumed from TraceLayout.
389    /// None requests a full scan. Initial values and external signals are always
390    /// observed, including when no generated store has executed.
391    pub fn dump_with_activity(
392        &mut self,
393        timestamp: u64,
394        memory: &[u8],
395        external: &[(BigUint, BigUint)],
396        activity: Option<&[usize]>,
397    ) -> std::io::Result<()> {
398        self.validate_external_count(external.len())?;
399        // Finish the previous attempt before starting a new timestamp or
400        // consulting caches that include its queued value records.
401        self.output.write_encoded()?;
402        let first_dump = !self.initial_values_written;
403        if !self.header_written {
404            self.write_header()?;
405        }
406        if timestamp > self.timestamp || timestamp == 0 {
407            writeln!(self.output.encoded, "#{}", timestamp)?;
408            self.timestamp = timestamp;
409            self.output.write_encoded()?;
410        }
411        self.selected.clear();
412        // Dense activity is cheaper to walk directly in registration order.
413        let sparse = activity
414            .filter(|activity| !first_dump && activity.len() < self.groups.len().div_ceil(2));
415        if let Some(activity) = sparse {
416            for &group in activity {
417                if let Some(indices) = self.groups.get(&group) {
418                    self.selected.extend_from_slice(indices);
419                }
420            }
421            self.selected
422                .extend(self.headers.len() - self.external_count..self.headers.len());
423            // Preserve registration order even when homes are laid out differently.
424            self.selected.sort_unstable();
425            self.selected.dedup();
426            if self.selected.is_empty() {
427                return Ok(());
428            }
429        }
430        if first_dump {
431            self.plan
432                .dump::<true, W>(memory, external, None, &mut self.output)?;
433            self.initial_values_written = true;
434        } else {
435            self.plan.dump::<false, W>(
436                memory,
437                external,
438                sparse.map(|_| self.selected.as_slice()),
439                &mut self.output,
440            )?;
441        }
442        Ok(())
443    }
444}
445
446impl<W: Write> VcdOutput<W> {
447    // Keep suffix publication with the value encoder so SIMD constants and
448    // output state can stay in registers between records.
449    #[inline(always)]
450    fn finish_record(&mut self, value_len: usize, suffix: &VcdRecordSuffix) -> std::io::Result<()> {
451        let suffix_len = suffix.encode(&mut self.encoded.spare_capacity_mut()[value_len..]);
452        // SAFETY: both encoders initialize their returned lengths in checked
453        // spare capacity. SIMD/suffix padding stays outside the visible length.
454        unsafe {
455            self.encoded
456                .set_len(self.encoded.len() + value_len + suffix_len);
457        }
458        self.encoded_changes += 1;
459        if self.encoded.len() >= self.writer.capacity() {
460            self.write_encoded()?;
461        }
462        Ok(())
463    }
464
465    #[inline]
466    fn write_encoded(&mut self) -> std::io::Result<()> {
467        if !self.encoded.is_empty() {
468            // Full blocks bypass BufWriter's internal copy. A dump's final
469            // partial block stays buffered with the timestamps and header.
470            // Unlike write_all, retain progress if a later short write fails.
471            let remaining = &self.encoded[self.written..];
472            match self.writer.write(remaining) {
473                Ok(count) if count == remaining.len() => {}
474                result => self.finish_short_write(result)?,
475            }
476            if self.encoded_changes != 0 {
477                self.stats.changes += self.encoded_changes;
478                self.stats.value_bytes += self.encoded.len() as u64;
479            }
480            self.encoded.clear();
481            self.written = 0;
482            self.encoded_changes = 0;
483        }
484        Ok(())
485    }
486
487    #[cold]
488    fn finish_short_write(&mut self, mut result: std::io::Result<usize>) -> std::io::Result<()> {
489        loop {
490            match result {
491                Ok(0) => return Err(std::io::ErrorKind::WriteZero.into()),
492                Ok(count) => self.written += count,
493                Err(error) if error.kind() == std::io::ErrorKind::Interrupted => {}
494                Err(error) => return Err(error),
495            }
496            if self.written == self.encoded.len() {
497                return Ok(());
498            }
499            result = self.writer.write(&self.encoded[self.written..]);
500        }
501    }
502}
503
504fn last_mask(width: usize) -> u8 {
505    if width.is_multiple_of(8) {
506        0xff
507    } else {
508        ((1u16 << (width % 8)) - 1) as u8
509    }
510}
511
512fn plane_equal(old: &[u8], value: &[u8], width: usize) -> bool {
513    let Some((&last, prefix)) = old.split_last() else {
514        return true;
515    };
516    if value.len() >= old.len() {
517        prefix == &value[..prefix.len()] && last == value[prefix.len()] & last_mask(width)
518    } else {
519        old.iter().enumerate().all(|(i, &byte)| {
520            byte == value.get(i).copied().unwrap_or(0)
521                & if i + 1 == old.len() {
522                    last_mask(width)
523                } else {
524                    0xff
525                }
526        })
527    }
528}
529
530fn copy_plane(dst: &mut [u8], src: &[u8], width: usize) {
531    let len = dst.len().min(src.len());
532    dst[..len].copy_from_slice(&src[..len]);
533    dst[len..].fill(0);
534    if let Some(last) = dst.last_mut() {
535        *last &= last_mask(width);
536    }
537}
538
539#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
540#[inline]
541fn encode_u64(out: &mut [MaybeUninit<u8>], value: u64) -> usize {
542    use std::arch::x86_64::*;
543
544    let bits = (64 - value.leading_zeros() as usize).max(1);
545    let remaining = value << (64 - bits);
546    let out = &mut out[..65];
547    out[0].write(b'b');
548    // SAFETY: SSE2 is enabled for this target. Each unaligned store initializes
549    // exactly one 16-byte chunk of reserved capacity. Only the significant
550    // digits are published; any extra initialized bytes remain outside len.
551    unsafe {
552        let masks = _mm_set1_epi64x(0x0102_0408_1020_4080);
553        // Duplicate all eight input bytes once. Each chunk then selects two
554        // adjacent bytes and repeats each eight times, high byte first.
555        let word = _mm_cvtsi64_si128(remaining as i64);
556        let pairs = _mm_unpacklo_epi8(word, word);
557        let chunks = out[1..].as_chunks_mut::<16>().0;
558        let emit = |chunk: &mut [MaybeUninit<u8>; 16], bytes| {
559            let ones = _mm_cmpeq_epi8(_mm_and_si128(bytes, masks), masks);
560            let ascii = _mm_sub_epi8(_mm_set1_epi8(b'0' as i8), ones);
561            _mm_storeu_si128(chunk.as_mut_ptr().cast(), ascii);
562        };
563        emit(
564            &mut chunks[0],
565            _mm_shuffle_epi32::<0xfa>(_mm_shufflehi_epi16::<0xaf>(pairs)),
566        );
567        if bits > 16 {
568            emit(
569                &mut chunks[1],
570                _mm_shuffle_epi32::<0xfa>(_mm_shufflehi_epi16::<0x05>(pairs)),
571            );
572        }
573        if bits > 32 {
574            emit(
575                &mut chunks[2],
576                _mm_shuffle_epi32::<0x50>(_mm_shufflelo_epi16::<0xaf>(pairs)),
577            );
578        }
579        if bits > 48 {
580            emit(
581                &mut chunks[3],
582                _mm_shuffle_epi32::<0x50>(_mm_shufflelo_epi16::<0x05>(pairs)),
583            );
584        }
585    }
586    1 + bits
587}
588
589#[cfg(not(all(target_arch = "x86_64", target_feature = "sse2")))]
590#[inline]
591fn encode_u64(out: &mut [MaybeUninit<u8>], value: u64) -> usize {
592    encode_value(out, 64, &value.to_le_bytes(), &[])
593}
594
595/// Initializes and returns the value's encoded length. The caller reserves a
596/// full-width record, including any SIMD and suffix padding, before encoding.
597fn encode_value(out: &mut [MaybeUninit<u8>], width: usize, value: &[u8], mask: &[u8]) -> usize {
598    let prefix = usize::from(width != 1);
599    if width != 1 {
600        out[0].write(b'b');
601    }
602    let four_state = mask.iter().any(|&byte| byte != 0);
603    let bits = if four_state {
604        width
605    } else {
606        value
607            .iter()
608            .rposition(|&byte| byte != 0)
609            .map_or(1, |i| i * 8 + (8 - value[i].leading_zeros() as usize))
610    };
611    let out = &mut out[prefix..prefix + bits];
612    if !four_state {
613        let bytes = bits.div_ceil(8);
614        let high = value.get(bytes - 1).copied().unwrap_or(0);
615        let high_bits = bits - (bytes - 1) * 8;
616        copy_encoded(out, &BINARY[high as usize][8 - high_bits..]);
617        for (chunk, &byte) in out[high_bits..]
618            .as_chunks_mut::<8>()
619            .0
620            .iter_mut()
621            .zip(value[..bytes - 1].iter().rev())
622        {
623            copy_encoded(chunk, &BINARY[byte as usize]);
624        }
625        return prefix + bits;
626    }
627    for (dst, bit) in out.iter_mut().zip((0..bits).rev()) {
628        let v = value.get(bit / 8).copied().unwrap_or(0) >> (bit % 8) & 1;
629        let m = mask.get(bit / 8).copied().unwrap_or(0) >> (bit % 8) & 1;
630        dst.write(match (m, v) {
631            (0, 0) => b'0',
632            (0, _) => b'1',
633            (_, 0) => b'z',
634            _ => b'x',
635        });
636    }
637    prefix + bits
638}
639
640#[inline]
641fn copy_encoded(out: &mut [MaybeUninit<u8>], bytes: &[u8]) {
642    let out = &mut out[..bytes.len()];
643    // SAFETY: the checked destination has enough capacity and the borrowed
644    // source cannot overlap it. This initializes exactly bytes.len() bytes.
645    unsafe {
646        std::ptr::copy_nonoverlapping(bytes.as_ptr(), out.as_mut_ptr().cast(), bytes.len());
647    }
648}
649
650const BINARY: [[u8; 8]; 256] = {
651    let mut table = [[b'0'; 8]; 256];
652    let mut byte = 0;
653    while byte < 256 {
654        let mut bit = 0;
655        while bit < 8 {
656            table[byte][bit] += ((byte >> (7 - bit)) & 1) as u8;
657            bit += 1;
658        }
659        byte += 1;
660    }
661    table
662};
663
664#[cfg(test)]
665mod tests {
666    use super::*;
667
668    #[test]
669    fn external_values_are_masked_to_their_declared_width() {
670        let dir = tempfile::tempdir().unwrap();
671        let path = dir.path().join("external-width.vcd");
672        let mut writer = VcdWriter::new(&path, &[]).unwrap();
673        writer
674            .add_external_signals(&[VcdExternalSignalDesc {
675                scope: "component".into(),
676                name: "state".into(),
677                width: 8,
678            }])
679            .unwrap();
680
681        writer
682            .dump_with_external(
683                0,
684                &[],
685                &[(BigUint::from(0x1ffu16), BigUint::from(0x100u16))],
686            )
687            .unwrap();
688        writer
689            .dump_with_external(1, &[], &[(BigUint::from(0xffu8), BigUint::default())])
690            .unwrap();
691        writer
692            .dump_with_external(2, &[], &[(BigUint::default(), BigUint::from(0xffu8))])
693            .unwrap();
694        writer
695            .dump_with_external(3, &[], &[(BigUint::default(), BigUint::default())])
696            .unwrap();
697
698        writer.flush().unwrap();
699        let dump = std::fs::read_to_string(path).unwrap();
700        assert!(!dump.contains("b111111111"), "{dump}");
701        assert_eq!(dump.matches("b11111111 !").count(), 1, "{dump}");
702        assert_eq!(dump.matches("bzzzzzzzz !").count(), 1, "{dump}");
703        assert_eq!(dump.matches("b0 !").count(), 1, "{dump}");
704    }
705}
706
707#[cfg(test)]
708mod encoding_tests {
709    use super::*;
710
711    fn changes(bytes: &[u8]) -> Vec<(u64, String)> {
712        let mut parser = vcd::Parser::new(bytes);
713        parser.parse_header().unwrap();
714        let mut time = 0;
715        parser
716            .filter_map(|command| match command.unwrap() {
717                vcd::Command::Timestamp(t) => {
718                    time = t;
719                    None
720                }
721                vcd::Command::ChangeScalar(_, value) => Some((time, value.to_string())),
722                vcd::Command::ChangeVector(_, value) => Some((time, value.to_string())),
723                _ => None,
724            })
725            .collect()
726    }
727
728    #[test]
729    fn integer_encoding_matches_binary_format_at_every_bit_length() {
730        let mut values = vec![0, u64::MAX, 0x0123_4567_89ab_cdef, 0xaaaa_5555_aaaa_5555];
731        let mut random = 0x8314_40be_9d6a_2785u64;
732        for bit in 0..64 {
733            let mask = u64::MAX >> (63 - bit);
734            values.extend([1 << bit, mask]);
735            for _ in 0..32 {
736                random ^= random << 13;
737                random ^= random >> 7;
738                random ^= random << 17;
739                values.push((random & mask) | (1 << bit));
740            }
741        }
742        // Exercise unaligned output, capacity growth, and reuse of spare bytes
743        // after truncation, checking that neither prefixes nor lengths change.
744        for prefix in [0, 1, 15, 16, 63] {
745            let mut actual = vec![b'#'; prefix];
746            let mut expected = actual.clone();
747            for &value in &values {
748                actual.reserve(65);
749                let len = encode_u64(actual.spare_capacity_mut(), value);
750                // SAFETY: encode_u64 initialized len bytes of spare capacity.
751                unsafe { actual.set_len(actual.len() + len) };
752                expected.extend_from_slice(format!("b{value:b}").as_bytes());
753                assert_eq!(actual, expected, "prefix={prefix} value={value:#x}");
754                if actual.len() > 4096 {
755                    actual.truncate(prefix);
756                    expected.truncate(prefix);
757                }
758            }
759        }
760    }
761
762    #[test]
763    fn record_suffixes_preserve_ids_and_never_publish_padding() {
764        for (number, expected) in [(0, "!"), (93, "~"), (94, "!!"), (8929, "~~"), (8930, "!!!")] {
765            assert_eq!(VcdWriter::<Vec<u8>>::generate_vcd_id(number), expected);
766        }
767        // Test both sides of the inline boundary without allocating the huge
768        // signal set needed to reach long IDs through normal registration.
769        for id in [
770            "!",
771            "~",
772            "!!",
773            "~~~",
774            "abcdef",
775            "abcdefg",
776            "abcdefgh",
777            "abcdefghi",
778            "abcdefghijklmnop",
779        ] {
780            for width in [1, 64] {
781                let suffix = VcdRecordSuffix::new(width, id);
782                let expected_suffix = format!("{}{id}\n", if width == 1 { "" } else { " " });
783                for prefix in 0..16 {
784                    let mut guarded = [MaybeUninit::new(b'#'); 64];
785                    let end = prefix + suffix.capacity();
786                    let len = suffix.encode(&mut guarded[prefix..end]);
787                    // SAFETY: all guard bytes started initialized, and encoding
788                    // only overwrites them with initialized suffix bytes.
789                    let actual = guarded.map(|byte| unsafe { byte.assume_init() });
790                    assert_eq!(&actual[..prefix], vec![b'#'; prefix]);
791                    assert_eq!(&actual[prefix..prefix + len], expected_suffix.as_bytes());
792                    assert!(actual[end..].iter().all(|&byte| byte == b'#'));
793
794                    let mut records = vec![b'#'; prefix];
795                    let mut expected = records.clone();
796                    for value in [0u64, 1, 1 << 63, u64::MAX, 0] {
797                        let value_capacity = if width == 1 { 1 } else { 65 };
798                        records.reserve(value_capacity + suffix.capacity());
799                        // Bound the spare slice to exactly the reserved record
800                        // space, including fixed-store padding. Later records
801                        // must overwrite that padding at the visible length.
802                        let out =
803                            &mut records.spare_capacity_mut()[..value_capacity + suffix.capacity()];
804                        let value_len = if width == 1 {
805                            out[0].write(b'0' + (value & 1) as u8);
806                            1
807                        } else {
808                            encode_u64(out, value)
809                        };
810                        let suffix_len = suffix.encode(&mut out[value_len..]);
811                        // SAFETY: both encoders initialized their returned lengths.
812                        unsafe { records.set_len(records.len() + value_len + suffix_len) };
813                        let value_text = if width == 1 {
814                            (value & 1).to_string()
815                        } else {
816                            format!("b{value:b}")
817                        };
818                        expected.extend_from_slice(value_text.as_bytes());
819                        expected.extend_from_slice(expected_suffix.as_bytes());
820                        assert_eq!(records, expected, "id={id} width={width} prefix={prefix}");
821                    }
822                }
823            }
824        }
825    }
826
827    #[test]
828    fn complete_records_cross_small_blocks_with_scalar_and_mixed_widths() {
829        for scalar_only in [true, false] {
830            let descs = (0..100)
831                .map(|i| VcdSignalDesc {
832                    scope: format!("scope{}", i % 2),
833                    name: format!("s{i}"),
834                    offset: i * 8,
835                    width: if scalar_only { 1 } else { [1, 9, 64][i % 3] },
836                    is_4state: false,
837                })
838                .collect::<Vec<_>>();
839            for capacity in [1, 3, 7, 8, 9, 16, 64, 71, 72, 73] {
840                let mut writer = VcdWriter::from_writer(Vec::new(), &descs);
841                writer.output.writer = BufWriter::with_capacity(capacity, Vec::new());
842                for (time, value) in [0xff, 0, 0, 0xff].into_iter().enumerate() {
843                    writer.dump(time as u64, &[value; 800]).unwrap();
844                }
845                assert_eq!(writer.statistics().changes, 300);
846                let bytes = writer.into_inner().unwrap();
847                let mut parser = vcd::Parser::new(bytes.as_slice());
848                let header = parser.parse_header().unwrap();
849                let mut names = fxhash::FxHashMap::default();
850                for item in header.items {
851                    if let vcd::ScopeItem::Scope(scope) = item {
852                        for item in scope.items {
853                            if let vcd::ScopeItem::Var(var) = item {
854                                names.insert(var.code, var.reference);
855                            }
856                        }
857                    }
858                }
859                assert_eq!(names.len(), descs.len());
860                let mut time = 0;
861                let actual = parser
862                    .filter_map(|command| {
863                        let (id, value) = match command.unwrap() {
864                            vcd::Command::Timestamp(t) => {
865                                time = t;
866                                return None;
867                            }
868                            vcd::Command::ChangeScalar(id, value) => (id, value.to_string()),
869                            vcd::Command::ChangeVector(id, value) => (id, value.to_string()),
870                            _ => return None,
871                        };
872                        Some((time, names[&id].clone(), value))
873                    })
874                    .collect::<Vec<_>>();
875                let expected = [0, 1, 3]
876                    .into_iter()
877                    .flat_map(|time| {
878                        descs.iter().map(move |desc| {
879                            let value = if time == 1 {
880                                "0".into()
881                            } else {
882                                "1".repeat(desc.width)
883                            };
884                            (time, desc.name.clone(), value)
885                        })
886                    })
887                    .collect::<Vec<_>>();
888                assert_eq!(
889                    actual, expected,
890                    "scalar_only={scalar_only} capacity={capacity}"
891                );
892            }
893        }
894    }
895
896    #[test]
897    fn integer_memory_at_unaligned_buffer_end_preserves_changes_and_aliases() {
898        for offset in 1..=8 {
899            let descs = [
900                VcdSignalDesc {
901                    scope: "top".into(),
902                    name: "prefix".into(),
903                    offset: 0,
904                    width: 7,
905                    is_4state: false,
906                },
907                VcdSignalDesc {
908                    scope: "top".into(),
909                    name: "q".into(),
910                    offset,
911                    width: 64,
912                    is_4state: false,
913                },
914                VcdSignalDesc {
915                    scope: "top".into(),
916                    name: "alias".into(),
917                    offset,
918                    width: 64,
919                    is_4state: false,
920                },
921            ];
922            let mut writer = VcdWriter::from_writer(Vec::new(), &descs);
923            // Both previous values follow a 7-bit signal, so their cached
924            // offsets are unaligned too. No padding follows the memory value.
925            let mut memory = vec![0; offset + 8];
926            memory[0] = 0x45;
927            let mut expected = vec![(0, format!("{:b}", memory[0]))];
928            for (step, value) in std::iter::once(0u64)
929                .chain((0..64).map(|bit| 1 << bit))
930                .chain([u64::MAX, 0])
931                .enumerate()
932            {
933                let time = (step * 2) as u64;
934                memory[offset..].copy_from_slice(&value.to_le_bytes());
935                writer.dump(time, &memory).unwrap();
936                writer.dump(time + 1, &memory).unwrap();
937                expected.extend(std::iter::repeat_n((time, format!("{value:b}")), 2));
938            }
939            assert_eq!(writer.statistics().changes, expected.len() as u64);
940            assert_eq!(
941                changes(&writer.into_inner().unwrap()),
942                expected,
943                "offset={offset}"
944            );
945        }
946    }
947
948    #[test]
949    fn emitted_values_match_independent_biguint_oracle() {
950        for width in [1, 7, 8, 9, 31, 32, 63, 64, 65, 257, 1024] {
951            for four_state in [false, true] {
952                let desc = VcdSignalDesc {
953                    scope: "top".into(),
954                    name: "q".into(),
955                    offset: 0,
956                    width,
957                    is_4state: four_state,
958                };
959                let mut writer = VcdWriter::from_writer(Vec::new(), &[desc]);
960                let size = width.div_ceil(8);
961                let mut memory = vec![0; size * 2];
962                let limit = (BigUint::from(1u8) << width) - 1u8;
963                let mut expected = vec![];
964                let mut previous = None;
965                let mut random = 0x8314_40be_9d6a_2785u64;
966                for step in 0..96u64 {
967                    if step % 4 != 1 {
968                        for byte in &mut memory {
969                            random ^= random << 13;
970                            random ^= random >> 7;
971                            random ^= random << 17;
972                            *byte = random as u8;
973                        }
974                    }
975                    if step % 3 == 0 {
976                        memory[size..].fill(0);
977                    }
978                    if step % 8 == 0 {
979                        memory[..size].fill(0);
980                    }
981                    // Deliberately include nonzero padding above the declared width.
982                    let value = BigUint::from_bytes_le(&memory[..size]) & &limit;
983                    let mask = if four_state {
984                        BigUint::from_bytes_le(&memory[size..]) & &limit
985                    } else {
986                        BigUint::default()
987                    };
988                    let state = (value.clone(), mask.clone());
989                    if previous.as_ref() != Some(&state) {
990                        let text = if mask == BigUint::default() {
991                            value.to_str_radix(2)
992                        } else {
993                            (0..width)
994                                .rev()
995                                .map(|bit| match (mask.bit(bit as u64), value.bit(bit as u64)) {
996                                    (false, false) => '0',
997                                    (false, true) => '1',
998                                    (true, false) => 'z',
999                                    (true, true) => 'x',
1000                                })
1001                                .collect()
1002                        };
1003                        expected.push((step / 2, text));
1004                        previous = Some(state);
1005                    }
1006                    writer.dump(step / 2, &memory).unwrap();
1007                }
1008                assert_eq!(
1009                    changes(&writer.into_inner().unwrap()),
1010                    expected,
1011                    "width={width} four_state={four_state}"
1012                );
1013            }
1014        }
1015    }
1016
1017    #[test]
1018    fn typed_sparse_runs_preserve_aliases_external_values_and_order() {
1019        let descs = [
1020            (512, 64, false),
1021            (0, 1, false),
1022            (128, 64, false),
1023            (256, 64, true),
1024            (512, 64, false),
1025            (640, 1, false),
1026            (768, 33, true),
1027            (896, 64, false),
1028            (0, 1, false),
1029            (1024, 9, false),
1030            (1152, 64, false),
1031        ]
1032        .into_iter()
1033        .enumerate()
1034        .map(|(index, (offset, width, is_4state))| VcdSignalDesc {
1035            scope: format!("scope{}", index % 2),
1036            name: format!("s{index}"),
1037            offset,
1038            width,
1039            is_4state,
1040        })
1041        .collect::<Vec<_>>();
1042        let external_descs = [1, 64, 65]
1043            .into_iter()
1044            .enumerate()
1045            .map(|(index, width)| VcdExternalSignalDesc {
1046                scope: "component".into(),
1047                name: format!("e{index}"),
1048                width,
1049            })
1050            .collect::<Vec<_>>();
1051        let mut sparse = VcdWriter::from_writer(Vec::new(), &descs);
1052        let mut full = VcdWriter::from_writer(Vec::new(), &descs);
1053        sparse.add_external_signals(&external_descs).unwrap();
1054        full.add_external_signals(&external_descs).unwrap();
1055        let mut memory = vec![0; 1216];
1056        for (step, groups) in [
1057            vec![],
1058            vec![8, 0, 8],
1059            vec![4, 2],
1060            vec![12, 10],
1061            vec![16, 14],
1062            vec![18, 0],
1063            vec![],
1064        ]
1065        .into_iter()
1066        .enumerate()
1067        {
1068            for &group in &groups {
1069                for (index, byte) in memory[group * 64..][..64].iter_mut().enumerate() {
1070                    *byte = (step * 19 + index) as u8;
1071                }
1072            }
1073            let external = external_descs
1074                .iter()
1075                .map(|desc| {
1076                    (
1077                        (BigUint::from(step) << desc.width.saturating_sub(1))
1078                            + BigUint::from(step % 2),
1079                        BigUint::from(step % 3) << desc.width.saturating_sub(1),
1080                    )
1081                })
1082                .collect::<Vec<_>>();
1083            sparse
1084                .dump_with_activity((step / 2) as u64, &memory, &external, Some(&groups))
1085                .unwrap();
1086            full.dump_with_external((step / 2) as u64, &memory, &external)
1087                .unwrap();
1088        }
1089        // Re-registering the same external descriptors remains idempotent.
1090        sparse.add_external_signals(&external_descs).unwrap();
1091        assert!(sparse.statistics().comparisons < full.statistics().comparisons);
1092        let parse = |bytes: Vec<u8>| {
1093            let mut parser = vcd::Parser::new(bytes.as_slice());
1094            parser.parse_header().unwrap();
1095            parser.map(Result::unwrap).collect::<Vec<_>>()
1096        };
1097        assert_eq!(
1098            parse(sparse.into_inner().unwrap()),
1099            parse(full.into_inner().unwrap())
1100        );
1101    }
1102
1103    #[test]
1104    fn typed_plan_checks_each_dump_and_only_requires_selected_memory() {
1105        use std::panic::{AssertUnwindSafe, catch_unwind};
1106        let descs = [
1107            (0, 64, false),
1108            (64, 1, false),
1109            (128, 64, false),
1110            (192, 64, true),
1111            (256, 65, false),
1112            (320, 64, false),
1113        ]
1114        .into_iter()
1115        .enumerate()
1116        .map(|(index, (offset, width, is_4state))| VcdSignalDesc {
1117            scope: "top".into(),
1118            name: format!("s{index}"),
1119            offset,
1120            width,
1121            is_4state,
1122        })
1123        .collect::<Vec<_>>();
1124        let mut writer = VcdWriter::from_writer(Vec::new(), &descs);
1125        writer
1126            .dump_with_activity(0, &[0; 328], &[], Some(&[]))
1127            .unwrap();
1128        writer.dump_with_activity(1, &[], &[], Some(&[])).unwrap();
1129        writer
1130            .dump_with_activity(2, &[1; 8], &[], Some(&[0]))
1131            .unwrap();
1132        writer
1133            .dump_with_activity(3, &[1; 65], &[], Some(&[1]))
1134            .unwrap();
1135        assert_eq!(writer.statistics().comparisons, 8);
1136        for (group, short_len) in [(0, 7), (1, 64), (2, 135), (3, 207), (4, 264)] {
1137            assert!(
1138                catch_unwind(AssertUnwindSafe(|| {
1139                    writer
1140                        .dump_with_activity(4, &vec![0; short_len], &[], Some(&[group]))
1141                        .unwrap();
1142                }))
1143                .is_err()
1144            );
1145        }
1146        assert!(catch_unwind(AssertUnwindSafe(|| writer.dump(5, &[0; 327]).unwrap())).is_err());
1147        let overflowing = VcdSignalDesc {
1148            offset: usize::MAX,
1149            ..descs[0].clone()
1150        };
1151        assert!(catch_unwind(|| VcdWriter::from_writer(Vec::new(), &[overflowing])).is_err());
1152    }
1153
1154    #[test]
1155    fn initial_snapshot_is_retried_after_a_record_write_error() {
1156        #[derive(Default)]
1157        struct FailRecordOnce {
1158            bytes: Vec<u8>,
1159            fail: bool,
1160        }
1161        impl Write for FailRecordOnce {
1162            fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
1163                if self.fail
1164                    && self.bytes.last() == Some(&b'\n')
1165                    && matches!(bytes.first(), Some(b'0' | b'1' | b'b'))
1166                {
1167                    self.fail = false;
1168                    return Err(std::io::ErrorKind::BrokenPipe.into());
1169                }
1170                self.bytes.extend_from_slice(bytes);
1171                Ok(bytes.len())
1172            }
1173            fn flush(&mut self) -> std::io::Result<()> {
1174                Ok(())
1175            }
1176        }
1177        let descs = [64, 1, 9, 64]
1178            .into_iter()
1179            .enumerate()
1180            .map(|(index, width)| VcdSignalDesc {
1181                scope: "top".into(),
1182                name: format!("s{index}"),
1183                offset: index * 64,
1184                width,
1185                is_4state: false,
1186            })
1187            .collect::<Vec<_>>();
1188        let mut writer = VcdWriter::from_writer(FailRecordOnce::default(), &descs);
1189        writer.output.writer = BufWriter::with_capacity(1, FailRecordOnce::default());
1190        writer.write_header().unwrap();
1191        writer.flush().unwrap();
1192        writer.output.writer.get_mut().fail = true;
1193        assert!(
1194            writer
1195                .dump_with_activity(1, &[0; 200], &[], Some(&[]))
1196                .is_err()
1197        );
1198        assert!(!writer.initial_values_written);
1199        writer
1200            .dump_with_activity(2, &[0; 200], &[], Some(&[]))
1201            .unwrap();
1202        assert!(writer.initial_values_written);
1203        // Finish the accepted record at its original time, then repeat the
1204        // incomplete initial snapshot in full at the retry's timestamp.
1205        assert_eq!(writer.statistics().changes, 5);
1206        assert_eq!(writer.statistics().comparisons, 5);
1207        assert_eq!(
1208            changes(&writer.into_inner().unwrap().bytes),
1209            [(1, "0".into())]
1210                .into_iter()
1211                .chain(vec![(2, "0".into()); 4])
1212                .collect::<Vec<_>>()
1213        );
1214    }
1215
1216    #[test]
1217    fn comparison_statistics_count_the_visited_prefix_on_io_error() {
1218        #[derive(Default)]
1219        struct FailAfterRecord {
1220            bytes: Vec<u8>,
1221            remaining: Option<usize>,
1222        }
1223        impl Write for FailAfterRecord {
1224            fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
1225                if self.bytes.last() == Some(&b'\n')
1226                    && matches!(bytes.first(), Some(b'0' | b'1' | b'b'))
1227                    && let Some(remaining) = &mut self.remaining
1228                {
1229                    if *remaining == 0 {
1230                        self.remaining = None;
1231                        return Err(std::io::ErrorKind::BrokenPipe.into());
1232                    }
1233                    *remaining -= 1;
1234                }
1235                self.bytes.extend_from_slice(bytes);
1236                Ok(bytes.len())
1237            }
1238            fn flush(&mut self) -> std::io::Result<()> {
1239                Ok(())
1240            }
1241        }
1242        for width in [1, 64, 9] {
1243            let descs = (0..8)
1244                .map(|index| VcdSignalDesc {
1245                    scope: "top".into(),
1246                    name: format!("s{index}"),
1247                    offset: index * 16,
1248                    width,
1249                    is_4state: false,
1250                })
1251                .collect::<Vec<_>>();
1252            let mut writer = VcdWriter::from_writer(FailAfterRecord::default(), &descs);
1253            writer.output.writer = BufWriter::with_capacity(1, FailAfterRecord::default());
1254            let mut memory = [0; 128];
1255            writer.dump(0, &memory).unwrap();
1256            writer.flush().unwrap();
1257            memory[0] = 1;
1258            memory[4 * 16] = 1;
1259            writer.output.writer.get_mut().remaining = Some(1);
1260            assert!(writer.dump(1, &memory).is_err());
1261            // Visit indices 0 through 4, including the three unchanged values.
1262            assert_eq!(writer.statistics().comparisons, 8 + 5, "width={width}");
1263            assert_eq!(writer.statistics().changes, 8 + 1, "width={width}");
1264        }
1265    }
1266
1267    #[test]
1268    fn sparse_selection_preserves_aliases_initial_values_and_registration_order() {
1269        let descs = [128, 0, 129, 0, 512, 640, 768, 896, 1024, 1152]
1270            .into_iter()
1271            .enumerate()
1272            .map(|(index, offset)| VcdSignalDesc {
1273                scope: "top".into(),
1274                name: format!("s{index}"),
1275                offset,
1276                width: 8,
1277                is_4state: false,
1278            })
1279            .collect::<Vec<_>>();
1280        let mut sparse = VcdWriter::from_writer(Vec::new(), &descs);
1281        let mut full = VcdWriter::from_writer(Vec::new(), &descs);
1282        let mut memory = vec![0; 1153];
1283        for time in 0..5 {
1284            memory[0] = time as u8;
1285            memory[129] = (time * 3) as u8;
1286            // First sample ignores an empty candidate set; later sets arrive unsorted.
1287            let groups = if time == 0 { &[][..] } else { &[2, 0, 2][..] };
1288            sparse
1289                .dump_with_activity(time, &memory, &[], Some(groups))
1290                .unwrap();
1291            full.dump(time, &memory).unwrap();
1292        }
1293        assert!(sparse.statistics().comparisons < full.statistics().comparisons);
1294        let parse = |bytes: Vec<u8>| {
1295            let mut parser = vcd::Parser::new(bytes.as_slice());
1296            parser.parse_header().unwrap();
1297            parser.map(Result::unwrap).collect::<Vec<_>>()
1298        };
1299        assert_eq!(
1300            parse(sparse.into_inner().unwrap()),
1301            parse(full.into_inner().unwrap())
1302        );
1303    }
1304
1305    #[test]
1306    fn padding_and_mask_only_changes_have_exact_semantics() {
1307        let desc = VcdSignalDesc {
1308            scope: "top".into(),
1309            name: "q".into(),
1310            offset: 0,
1311            width: 1,
1312            is_4state: true,
1313        };
1314        let mut writer = VcdWriter::from_writer(Vec::new(), &[desc]);
1315        for (time, bytes) in [[0, 0], [0xfe, 0xfe], [0, 1], [1, 1], [1, 0]]
1316            .iter()
1317            .enumerate()
1318        {
1319            writer.dump(time as u64, bytes).unwrap();
1320        }
1321        assert_eq!(
1322            changes(&writer.into_inner().unwrap()),
1323            [(0, "0"), (2, "z"), (3, "x"), (4, "1")].map(|(t, s)| (t, s.to_string()))
1324        );
1325    }
1326
1327    #[derive(Default)]
1328    struct ShortWrites {
1329        bytes: Vec<u8>,
1330        calls: usize,
1331        fail_after: Option<usize>,
1332    }
1333
1334    impl Write for ShortWrites {
1335        fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
1336            self.calls += 1;
1337            if self.calls.is_multiple_of(7) {
1338                return Err(std::io::ErrorKind::Interrupted.into());
1339            }
1340            let remaining = self
1341                .fail_after
1342                .unwrap_or(usize::MAX)
1343                .saturating_sub(self.bytes.len());
1344            if remaining == 0 && !bytes.is_empty() {
1345                return Err(std::io::ErrorKind::BrokenPipe.into());
1346            }
1347            let len = bytes.len().min(113).min(remaining);
1348            self.bytes.extend_from_slice(&bytes[..len]);
1349            Ok(len)
1350        }
1351
1352        fn flush(&mut self) -> std::io::Result<()> {
1353            Ok(())
1354        }
1355    }
1356
1357    #[test]
1358    fn failed_value_blocks_resume_without_losing_cached_transitions() {
1359        for width in [1usize, 64, 9, 65, 256 * 1024 + 17] {
1360            for four_state in [false, true] {
1361                for capacity in [1, 64] {
1362                    for accepted in [0, 1, 31] {
1363                        let count = if width > 1024 { 2 } else { 80 };
1364                        let stride = width.div_ceil(8) * if four_state { 2 } else { 1 };
1365                        let descs = (0..count)
1366                            .map(|index| VcdSignalDesc {
1367                                scope: "top".into(),
1368                                name: format!("s{index}"),
1369                                offset: index * stride,
1370                                width,
1371                                is_4state: four_state,
1372                            })
1373                            .collect::<Vec<_>>();
1374                        let mut memory = vec![0; count * stride];
1375                        let mut writer = VcdWriter::from_writer(ShortWrites::default(), &descs);
1376                        writer.output.writer =
1377                            BufWriter::with_capacity(capacity, ShortWrites::default());
1378                        let mut reference = VcdWriter::from_writer(Vec::new(), &descs);
1379                        writer.dump(0, &memory).unwrap();
1380                        writer.flush().unwrap();
1381                        reference.dump(0, &memory).unwrap();
1382
1383                        // Fail before accepting a value block, or after a short
1384                        // prefix of it. A block may contain several cache updates.
1385                        let limit = writer.get_ref().bytes.len() + b"#1\n".len() + accepted;
1386                        writer.output.writer.get_mut().fail_after = Some(limit);
1387                        memory.fill(0xff);
1388                        for _ in 0..2 {
1389                            assert_eq!(
1390                                writer.dump(1, &memory).unwrap_err().kind(),
1391                                std::io::ErrorKind::BrokenPipe
1392                            );
1393                        }
1394                        writer.output.writer.get_mut().fail_after = None;
1395                        writer.dump(1, &memory).unwrap();
1396                        reference.dump(1, &memory).unwrap();
1397                        // Also check that recovery leaves the caches current.
1398                        writer.dump(2, &memory).unwrap();
1399                        reference.dump(2, &memory).unwrap();
1400                        assert_eq!(writer.statistics().changes, reference.statistics().changes);
1401                        assert_eq!(
1402                            writer.statistics().value_bytes,
1403                            reference.statistics().value_bytes
1404                        );
1405                        assert_eq!(
1406                            changes(&writer.into_inner().unwrap().bytes),
1407                            changes(&reference.into_inner().unwrap()),
1408                            "width={width}, four_state={four_state}, capacity={capacity}, accepted={accepted}"
1409                        );
1410                    }
1411                }
1412            }
1413        }
1414    }
1415
1416    #[test]
1417    fn pending_value_records_finish_before_the_next_timestamp() {
1418        for flush_first in [false, true] {
1419            let descs = (0..4)
1420                .map(|index| VcdSignalDesc {
1421                    scope: "top".into(),
1422                    name: format!("s{index}"),
1423                    offset: index * 8,
1424                    width: 64,
1425                    is_4state: false,
1426                })
1427                .collect::<Vec<_>>();
1428            let mut writer = VcdWriter::from_writer(ShortWrites::default(), &descs);
1429            // Keep the unwritten suffix larger than BufWriter's capacity so it
1430            // cannot accept another record before surfacing the injected error.
1431            writer.output.writer = BufWriter::with_capacity(1, ShortWrites::default());
1432            writer.dump(0, &[0; 32]).unwrap();
1433            writer.flush().unwrap();
1434            let limit = writer.get_ref().bytes.len() + b"#1\n".len() + 7;
1435            writer.output.writer.get_mut().fail_after = Some(limit);
1436            assert!(writer.dump(1, &[0xff; 32]).is_err());
1437            writer.output.writer.get_mut().fail_after = None;
1438            if flush_first {
1439                writer.flush().unwrap();
1440            }
1441            // The memory has changed again since the failed attempt. The old
1442            // queued record must finish at #1, followed by the new value at #2.
1443            writer.dump(2, &[0; 32]).unwrap();
1444            let mut expected = vec![(0, "0".to_owned()); 4];
1445            expected.extend([(1, "1".repeat(64)), (2, "0".into())]);
1446            assert_eq!(changes(&writer.into_inner().unwrap().bytes), expected);
1447        }
1448    }
1449
1450    #[test]
1451    fn header_and_timestamp_resume_after_partial_writes() {
1452        let desc = VcdSignalDesc {
1453            scope: "top".into(),
1454            name: "q".into(),
1455            offset: 0,
1456            width: 64,
1457            is_4state: false,
1458        };
1459        for accepted in [0, 1, 17] {
1460            let mut writer =
1461                VcdWriter::from_writer(ShortWrites::default(), std::slice::from_ref(&desc));
1462            writer.output.writer = BufWriter::with_capacity(
1463                1,
1464                ShortWrites {
1465                    fail_after: Some(accepted),
1466                    ..Default::default()
1467                },
1468            );
1469            assert!(writer.dump(0, &[0; 8]).is_err());
1470            writer.output.writer.get_mut().fail_after = None;
1471            writer.dump(0, &[0; 8]).unwrap();
1472            writer.flush().unwrap();
1473            let limit = writer.get_ref().bytes.len() + 2;
1474            writer.output.writer.get_mut().fail_after = Some(limit);
1475            assert!(writer.dump(123, &[1; 8]).is_err());
1476            writer.output.writer.get_mut().fail_after = None;
1477            writer.dump(123, &[1; 8]).unwrap();
1478            assert_eq!(
1479                changes(&writer.into_inner().unwrap().bytes),
1480                [
1481                    (0, "0".to_owned()),
1482                    (123, format!("{:b}", u64::from_le_bytes([1; 8])))
1483                ],
1484            );
1485        }
1486    }
1487
1488    #[test]
1489    fn blocks_and_oversized_records_survive_short_writes_and_drop() {
1490        let mut offset = 0;
1491        let descs = (0..1025)
1492            .map(|i| {
1493                let width = if i == 1024 {
1494                    256 * 1024 + 17
1495                } else {
1496                    [1, 9, 65, 1024][i % 4]
1497                };
1498                let is_4state = i % 3 == 0 || i == 1024;
1499                let desc = VcdSignalDesc {
1500                    scope: "top".into(),
1501                    name: format!("s{i}"),
1502                    offset,
1503                    width,
1504                    is_4state,
1505                };
1506                offset += width.div_ceil(8) * if is_4state { 2 } else { 1 };
1507                desc
1508            })
1509            .collect::<Vec<_>>();
1510        let mut memory = vec![0xff; offset];
1511        let mut sink = ShortWrites::default();
1512        let mut expected = Vec::new();
1513        let stats = {
1514            let mut writer = VcdWriter::from_writer(&mut sink, &descs);
1515            writer
1516                .add_external_signals(&[VcdExternalSignalDesc {
1517                    scope: "component".into(),
1518                    name: "state".into(),
1519                    width: 9,
1520                }])
1521                .unwrap();
1522            for time in 0..4 {
1523                if time == 1 {
1524                    memory.fill(0);
1525                } else if time == 3 {
1526                    for desc in &descs {
1527                        let size = desc.width.div_ceil(8);
1528                        if desc.is_4state {
1529                            memory[desc.offset + size..desc.offset + size * 2].fill(0xff);
1530                        } else {
1531                            memory[desc.offset..desc.offset + size].fill(0xff);
1532                        }
1533                    }
1534                }
1535                let external = match time {
1536                    0 => (BigUint::from(0x1ffu16), BigUint::from(0x1ffu16)),
1537                    3 => (BigUint::default(), BigUint::from(0x1ffu16)),
1538                    _ => (BigUint::default(), BigUint::default()),
1539                };
1540                writer
1541                    .dump_with_external(time, &memory, &[external])
1542                    .unwrap();
1543                if time != 2 {
1544                    for desc in &descs {
1545                        let value = if time == 1 {
1546                            "0".into()
1547                        } else if desc.is_4state {
1548                            if time == 0 { "x" } else { "z" }.repeat(desc.width)
1549                        } else {
1550                            "1".repeat(desc.width)
1551                        };
1552                        expected.push((time, value));
1553                    }
1554                    expected.push((
1555                        time,
1556                        match time {
1557                            0 => "x".repeat(9),
1558                            3 => "z".repeat(9),
1559                            _ => "0".into(),
1560                        },
1561                    ));
1562                }
1563            }
1564            // Leave the final partial block buffered and exercise Drop.
1565            writer.statistics()
1566        };
1567        assert_eq!(changes(&sink.bytes), expected);
1568        let text = std::str::from_utf8(&sink.bytes).unwrap();
1569        assert!(text.contains("\n#2\n#3\n"));
1570        assert_eq!(stats.changes, expected.len() as u64);
1571        let values = text.split_once("$dumpvars\n$end\n").unwrap().1;
1572        assert_eq!(
1573            stats.value_bytes,
1574            values
1575                .lines()
1576                .filter(|line| !line.starts_with('#'))
1577                .map(|line| (line.len() + 1) as u64)
1578                .sum::<u64>()
1579        );
1580    }
1581
1582    #[test]
1583    fn block_and_tail_io_errors_are_reported() {
1584        for (width, fail_after, fails_during_dump) in
1585            [(64, 16, false), (512 * 1024, 256 * 1024 + 13, true)]
1586        {
1587            let desc = VcdSignalDesc {
1588                scope: "top".into(),
1589                name: "q".into(),
1590                offset: 0,
1591                width,
1592                is_4state: false,
1593            };
1594            let mut sink = ShortWrites {
1595                fail_after: Some(fail_after),
1596                ..Default::default()
1597            };
1598            {
1599                let mut writer = VcdWriter::from_writer(&mut sink, &[desc]);
1600                let dump = writer.dump(0, &vec![0xff; width.div_ceil(8)]);
1601                assert_eq!(dump.is_err(), fails_during_dump);
1602                let error = dump.and_then(|()| writer.flush()).unwrap_err();
1603                assert_eq!(error.kind(), std::io::ErrorKind::BrokenPipe);
1604            }
1605            assert_eq!(sink.bytes.len(), fail_after);
1606        }
1607    }
1608
1609    #[test]
1610    fn explicit_flush_reports_sink_failure() {
1611        struct BadFlush;
1612        impl Write for BadFlush {
1613            fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
1614                Ok(bytes.len())
1615            }
1616            fn flush(&mut self) -> std::io::Result<()> {
1617                Err(std::io::Error::other("flush failed"))
1618            }
1619        }
1620        let mut writer = VcdWriter::from_writer(BadFlush, &[]);
1621        writer.dump(0, &[]).unwrap();
1622        assert_eq!(writer.flush().unwrap_err().to_string(), "flush failed");
1623    }
1624}