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