Skip to main content

retex_engine/
fingerprint.rs

1//! Fingerprints of the engine state, for `--watch` (see
2//! `docs/design/incremental-rebuilds.md`): after an edit, the rebuild
3//! compares its state at a page with the previous build's at the same
4//! page; when they agree, the rest of the previous build's output is what
5//! the rebuild would produce.
6//!
7//! The fingerprint covers what decides the rest of the run: the input
8//! still to be read (not the line numbers, which an edit shifts), the
9//! table of equivalents and the meanings, the save stack, the semantic
10//! nest, the page builder, marks, conditionals, the PDF state and the
11//! backend's. Node lists are hashed by structure (their nodes are
12//! numbered by allocation, which differs between the builds), hash maps in
13//! key order. Not covered: the Lua state, and what was already written
14//! out (it differs before the edit by construction). A fingerprint can so
15//! agree when the runs would not: the rebuild goes on to the end and
16//! checks.
17
18use std::collections::HashMap;
19use std::fmt::Write as _;
20use std::rc::Rc;
21
22use crate::Engine;
23use crate::eqtb::{CodeTable, Meaning, Page, Val};
24use crate::input::{Level, LineSource};
25use crate::nodes::{NodeData, NodeRef};
26use crate::save::SaveEntry;
27use crate::token::Macro;
28
29/// A 64-bit FNV-1a hash, fed bytes, integers and `Debug` output.
30pub struct Fp(u64);
31
32impl Default for Fp {
33    fn default() -> Self {
34        Fp(0xcbf2_9ce4_8422_2325)
35    }
36}
37
38impl std::fmt::Write for Fp {
39    fn write_str(&mut self, s: &str) -> std::fmt::Result {
40        self.bytes(s.as_bytes());
41        Ok(())
42    }
43}
44
45impl Fp {
46    /// Goes on from a hash taken earlier with [`Fp::get`].
47    pub fn resume(h: u64) -> Fp {
48        Fp(h)
49    }
50
51    /// The hash so far.
52    pub fn get(&self) -> u64 {
53        self.0
54    }
55
56    /// Adds bytes.
57    pub fn bytes(&mut self, b: &[u8]) {
58        for &x in b {
59            self.0 ^= u64::from(x);
60            self.0 = self.0.wrapping_mul(0x0100_0000_01b3);
61        }
62    }
63
64    /// Adds an integer.
65    pub fn u(&mut self, v: u64) {
66        self.0 ^= v;
67        self.0 = self.0.wrapping_mul(0x0100_0000_01b3).rotate_left(29);
68    }
69
70    /// Adds a signed integer.
71    pub fn i(&mut self, v: i64) {
72        self.u(v as u64);
73    }
74
75    /// Adds the `Debug` form of `v`.
76    pub fn debug(&mut self, v: &impl std::fmt::Debug) {
77        let _ = write!(self, "{v:?}");
78    }
79
80    /// Adds glue.
81    pub fn glue(&mut self, g: &crate::glue::Glue) {
82        self.i(g.width.into());
83        self.i(g.stretch.into());
84        self.i(g.shrink.into());
85        self.u(u64::from(g.stretch_order) << 8 | u64::from(g.shrink_order));
86    }
87
88    /// Adds a token list.
89    pub fn tokens(&mut self, t: &[crate::token::Token]) {
90        self.u(t.len() as u64);
91        for x in t {
92            self.u(x.0.into());
93        }
94    }
95
96    /// Adds the entries of a map, in the order of their `Debug` forms
97    /// (iteration order depends on the map's history).
98    pub fn map<K: std::fmt::Debug, V: std::fmt::Debug>(&mut self, m: impl Iterator<Item = (K, V)>) {
99        let mut v: Vec<String> = m.map(|(k, x)| format!("{k:?}\u{0}{x:?}")).collect();
100        v.sort();
101        self.u(v.len() as u64);
102        for e in v {
103            self.bytes(e.as_bytes());
104            self.u(0);
105        }
106    }
107}
108
109/// Hashes of code-table pages and macros, by address. Each entry holds a
110/// reference, so that the object is neither freed (its address reused)
111/// nor changed in place: a code table copies a shared page before
112/// changing it.
113#[derive(Default)]
114pub struct FpCache {
115    pages: HashMap<usize, (Rc<Page>, u64)>,
116    macros: HashMap<usize, (Rc<Macro>, u64)>,
117}
118
119fn page_hash(p: &Page) -> u64 {
120    let mut h = Fp::default();
121    for s in p.iter() {
122        h.i(s.val);
123        h.u(s.level.into());
124    }
125    h.get()
126}
127
128fn macro_hash(m: &Macro) -> u64 {
129    let mut h = Fp::default();
130    h.u(m.protected.into());
131    h.tokens(&m.tokens);
132    h.get()
133}
134
135impl Engine {
136    /// Hashes the code-table pages and macros that exist now, so that
137    /// fingerprints taken later (in this process and the processes it
138    /// forks) need not hash them again: most are the preamble's and never
139    /// change.
140    pub fn prepare_fingerprints(&mut self) {
141        let mut c = FpCache::default();
142        let e = &self.eqtb;
143        let tables = e
144            .catcode_tables
145            .values()
146            .chain([&e.lc, &e.uc, &e.sf, &e.math, &e.del]);
147        for t in tables {
148            for p in t.pages().iter().flatten() {
149                c.pages
150                    .entry(Rc::as_ptr(p) as usize)
151                    .or_insert_with(|| (p.clone(), page_hash(p)));
152            }
153        }
154        for s in &e.cs {
155            if let Some(m) = &s.val.mac {
156                c.macros
157                    .entry(Rc::as_ptr(m) as usize)
158                    .or_insert_with(|| (m.clone(), macro_hash(m)));
159            }
160        }
161        self.fp_cache = Some(Box::new(c));
162    }
163
164    /// Adds a code table.
165    fn fp_code_table(&self, h: &mut Fp, t: &CodeTable) {
166        for (k, p) in t.pages().iter().enumerate() {
167            if let Some(p) = p {
168                h.u(k as u64);
169                let cached = self
170                    .fp_cache
171                    .as_ref()
172                    .and_then(|c| c.pages.get(&(Rc::as_ptr(p) as usize)));
173                h.u(cached.map_or_else(|| page_hash(p), |c| c.1));
174            }
175        }
176        h.u(u64::MAX);
177    }
178
179    /// Adds a meaning.
180    pub(crate) fn fp_meaning(&self, h: &mut Fp, m: &Meaning) {
181        h.u(m.cmd as u64);
182        h.u(m.chr.into());
183        match &m.mac {
184            Some(mac) => {
185                let cached = self
186                    .fp_cache
187                    .as_ref()
188                    .and_then(|c| c.macros.get(&(Rc::as_ptr(mac) as usize)));
189                h.u(cached.map_or_else(|| macro_hash(mac), |c| c.1));
190            }
191            None => h.u(0),
192        }
193    }
194
195    /// The fingerprint of the state, by part (for debugging), and in all.
196    pub fn fingerprint(&mut self) -> (Vec<(&'static str, u64)>, u64) {
197        let backend = self.backend.as_mut().map_or(0, |b| b.fingerprint());
198        let parts: Vec<(&'static str, u64)> = vec![
199            ("input", self.fp_input()),
200            ("meanings", self.fp_meanings()),
201            ("registers", self.fp_registers()),
202            ("save stack", self.fp_save()),
203            ("nest", self.fp_nest()),
204            ("page builder", self.fp_page()),
205            ("conditionals", self.fp_cond()),
206            ("pdf", self.fp_pdf()),
207            ("backend", backend),
208            ("misc", self.fp_misc()),
209        ];
210        let mut all = Fp::default();
211        for (_, h) in &parts {
212            all.u(*h);
213        }
214        (parts, all.get())
215    }
216
217    /// The registers, one per line, and the names of the control sequences
218    /// defined as `\count` and `\dimen` registers: for finding why the
219    /// register part of two fingerprints differs (`RETEX_DEBUG_FINGERPRINT`).
220    pub fn fingerprint_dump(&self) -> String {
221        let mut out = String::new();
222        let e = &self.eqtb;
223        let names = ["int_par", "count", "attr", "dimen_par", "dimen"];
224        for (n, v) in names
225            .iter()
226            .zip([&e.int_par, &e.count, &e.attr, &e.dimen_par, &e.dimen])
227        {
228            for (i, s) in v.iter().enumerate() {
229                let _ = writeln!(out, "{n} {i} {} {}", s.val, s.level);
230            }
231        }
232        for (n, v) in ["glue_par", "skip", "muskip"]
233            .iter()
234            .zip([&e.glue_par, &e.skip, &e.muskip])
235        {
236            for (i, s) in v.iter().enumerate() {
237                let _ = writeln!(out, "{n} {i} {:?} {}", s.val, s.level);
238            }
239        }
240        for (n, v) in ["toks_par", "toks"].iter().zip([&e.toks_par, &e.toks]) {
241            for (i, s) in v.iter().enumerate() {
242                let mut h = Fp::default();
243                h.tokens(&s.val);
244                let _ = writeln!(out, "{n} {i} {:x} {}", h.get(), s.level);
245            }
246        }
247        for (i, s) in e.boxes.iter().enumerate() {
248            let mut h = Fp::default();
249            self.fp_list(&mut h, s.val);
250            let _ = writeln!(out, "box {i} {:x} {}", h.get(), s.level);
251        }
252        let _ = writeln!(
253            out,
254            "misc {:?}",
255            (&e.cur_font, &e.math_font, &e.par_shape, &e.etex_shape)
256        );
257        let _ = writeln!(
258            out,
259            "misc2 {:?}",
260            (&e.dir_par, &e.local_count, e.attrs_in_use)
261        );
262        for ((name, _), slot) in self.hash.entries().zip(&e.cs) {
263            let m = &slot.val;
264            if matches!(
265                m.cmd,
266                crate::cmd::Cmd::AssignInt | crate::cmd::Cmd::AssignDimen
267            ) && m.chr >= crate::scan::REG_OFFSET
268            {
269                let _ = writeln!(
270                    out,
271                    "name {:?} {} = {}",
272                    m.cmd,
273                    m.chr - crate::scan::REG_OFFSET,
274                    String::from_utf8_lossy(name)
275                );
276            }
277        }
278        let mut tables: Vec<(&u32, &crate::eqtb::CodeTable)> = e.catcode_tables.iter().collect();
279        tables.sort_by_key(|(k, _)| **k);
280        for (k, t) in tables {
281            let mut h = Fp::default();
282            for (k, page) in t.dump_pages() {
283                h.u(k as u64);
284                for v in page {
285                    h.i(v);
286                }
287            }
288            let _ = writeln!(out, "catcodes {k} {:x}", h.get());
289        }
290        out
291    }
292
293    /// Adds the node list starting at `p` (null for none).
294    pub(crate) fn fp_list(&self, h: &mut Fp, mut p: NodeRef) {
295        while !p.is_null() {
296            self.fp_node(h, p);
297            p = self.nodes.next(p);
298        }
299        h.u(u64::MAX);
300    }
301
302    /// Adds one node, with the lists it holds.
303    fn fp_node(&self, h: &mut Fp, p: NodeRef) {
304        let n = self.nodes.get(p);
305        h.u(n.id.into());
306        h.u(n.subtype.into());
307        match &n.attr {
308            Some(a) => h.debug(&&a[..]),
309            None => h.u(0),
310        }
311        match &n.data {
312            NodeData::Box(b) => {
313                h.debug(&(b.width, b.depth, b.height, b.dir, b.shift));
314                h.debug(&(b.glue_order, b.glue_sign, b.glue_set.to_bits()));
315                self.fp_list(h, b.head);
316            }
317            NodeData::Unset {
318                b,
319                span_count,
320                stretch,
321                shrink,
322                stretch_order,
323                shrink_order,
324            } => {
325                h.debug(&(b.width, b.depth, b.height, b.dir, b.shift));
326                h.debug(&(span_count, stretch, shrink, stretch_order, shrink_order));
327                self.fp_list(h, b.head);
328            }
329            NodeData::Ins {
330                cost,
331                depth,
332                height,
333                spec,
334                head,
335            } => {
336                h.debug(&(cost, depth, height, spec));
337                self.fp_list(h, *head);
338            }
339            NodeData::Adjust { head } | NodeData::SubList { head } => self.fp_list(h, *head),
340            NodeData::Disc {
341                pre,
342                post,
343                replace,
344                penalty,
345            } => {
346                h.i((*penalty).into());
347                self.fp_list(h, *pre);
348                self.fp_list(h, *post);
349                self.fp_list(h, *replace);
350            }
351            NodeData::LocalPar {
352                pen_inter,
353                pen_broken,
354                dir,
355                box_left,
356                box_left_width,
357                box_right,
358                box_right_width,
359            } => {
360                h.debug(&(pen_inter, pen_broken, dir, box_left_width, box_right_width));
361                self.fp_list(h, *box_left);
362                self.fp_list(h, *box_right);
363            }
364            NodeData::Glue { spec, leader } => {
365                h.debug(spec);
366                self.fp_list(h, *leader);
367            }
368            NodeData::Glyph(g) => {
369                h.debug(&(g.chr, g.font, g.lang, g.left, g.right, g.uchyph));
370                h.debug(&(g.xoffset, g.yoffset, g.expansion_factor, g.data));
371                self.fp_list(h, g.components);
372            }
373            NodeData::MarginKern { width, glyph } => {
374                h.i((*width).into());
375                if !glyph.is_null() {
376                    self.fp_node(h, *glyph);
377                }
378            }
379            NodeData::Noad(d) => {
380                h.debug(&(d.options, d.width, d.fraction));
381                for l in [
382                    d.nucleus,
383                    d.sub,
384                    d.sup,
385                    d.left,
386                    d.degree,
387                    d.accent,
388                    d.bot_accent,
389                    d.new_hlist,
390                ] {
391                    self.fp_list(h, l);
392                }
393            }
394            NodeData::Fraction(d) => {
395                h.debug(&(d.thickness, d.options));
396                for l in [d.num, d.denom, d.left, d.right, d.middle, d.new_hlist] {
397                    self.fp_list(h, l);
398                }
399            }
400            NodeData::Fence(d) => {
401                h.debug(&(d.italic, d.height, d.depth, d.options, d.class));
402                self.fp_list(h, d.delim);
403                self.fp_list(h, d.new_hlist);
404            }
405            NodeData::Choice(c) => {
406                for l in c.iter() {
407                    self.fp_list(h, *l);
408                }
409            }
410            // No node references in the others.
411            other => h.debug(other),
412        }
413    }
414
415    /// Adds a saved or current value.
416    pub(crate) fn fp_val(&self, h: &mut Fp, v: &Val) {
417        match v {
418            Val::Meaning(m) => self.fp_meaning(h, m),
419            Val::Node(p) => self.fp_list(h, *p),
420            Val::Toks(t) => h.tokens(t),
421            other => h.debug(other),
422        }
423    }
424
425    /// The input still to be read. Of a file, what follows the point
426    /// read and the number of lines it has: not the line number, which
427    /// an edit before shifts.
428    fn fp_input(&self) -> u64 {
429        let mut h = Fp::default();
430        let inp = &self.input;
431        h.u(inp.stack.len() as u64);
432        for l in &inp.stack {
433            match l {
434                Level::Text(t) => {
435                    h.u(1);
436                    h.debug(&(t.state, t.loc, t.is_input_file, t.eof_seen));
437                    h.debug(&(t.grp_boundary, t.if_depth, t.catmode));
438                    h.debug(&&t.buf[..]);
439                    h.bytes(&t.name);
440                    match &t.source {
441                        LineSource::File { data, pos } => {
442                            let rest = data.get(*pos..).unwrap_or_default();
443                            h.u(2);
444                            h.bytes(rest);
445                        }
446                        other => h.debug(other),
447                    }
448                }
449                Level::Tokens(t) => {
450                    h.u(3);
451                    h.tokens(&t.list);
452                    h.debug(&(t.loc, t.kind, t.param_start, t.name));
453                }
454            }
455        }
456        for p in &inp.param_stack {
457            h.tokens(p);
458        }
459        h.debug(&(inp.align_state, inp.scanner_status, inp.warning_index));
460        h.debug(&(inp.force_eof, inp.in_open));
461        h.get()
462    }
463
464    /// The control sequences and their meanings.
465    pub(crate) fn fp_meanings(&self) -> u64 {
466        let mut h = Fp::default();
467        h.u(self.hash.len() as u64);
468        for (name, kind) in self.hash.entries() {
469            h.bytes(name);
470            h.debug(kind);
471        }
472        for s in &self.eqtb.cs {
473            h.u(s.level.into());
474            self.fp_meaning(&mut h, &s.val);
475        }
476        h.get()
477    }
478
479    /// Parameters, registers and code tables.
480    pub(crate) fn fp_registers(&self) -> u64 {
481        let mut h = Fp::default();
482        let e = &self.eqtb;
483        let ints = |h: &mut Fp, v: &[crate::eqtb::Slot<i32>]| {
484            h.u(v.len() as u64);
485            for s in v {
486                h.i(s.val.into());
487                h.u(s.level.into());
488            }
489        };
490        for v in [&e.int_par, &e.count, &e.attr, &e.dimen_par, &e.dimen] {
491            ints(&mut h, v);
492        }
493        for v in [&e.glue_par, &e.skip, &e.muskip] {
494            h.u(v.len() as u64);
495            for s in v {
496                h.glue(&s.val);
497                h.u(s.level.into());
498            }
499        }
500        for v in [&e.toks_par, &e.toks] {
501            h.u(v.len() as u64);
502            for s in v {
503                h.tokens(&s.val);
504                h.u(s.level.into());
505            }
506        }
507        h.u(e.boxes.len() as u64);
508        for s in &e.boxes {
509            self.fp_list(&mut h, s.val);
510            h.u(s.level.into());
511        }
512        for s in &e.local_box {
513            self.fp_list(&mut h, s.val);
514            h.u(s.level.into());
515        }
516        h.debug(&(&e.cur_font, &e.math_font, &e.par_shape, &e.etex_shape));
517        h.debug(&(&e.dir_par, &e.local_count, e.attrs_in_use));
518        h.map(e.math_param.iter());
519        let mut tables: Vec<(&u32, &crate::eqtb::CodeTable)> = e.catcode_tables.iter().collect();
520        tables.sort_by_key(|(k, _)| **k);
521        let all = tables
522            .iter()
523            .map(|(_, t)| *t)
524            .chain([&e.lc, &e.uc, &e.sf, &e.math, &e.del]);
525        for (k, _) in &tables {
526            h.u((**k).into());
527        }
528        for t in all {
529            self.fp_code_table(&mut h, t);
530        }
531        h.get()
532    }
533
534    /// The save stack (not the lines where groups began).
535    fn fp_save(&self) -> u64 {
536        let mut h = Fp::default();
537        let s = &self.save;
538        h.debug(&(s.cur_level, s.cur_group, s.cur_boundary));
539        for e in &s.stack {
540            match e {
541                SaveEntry::Restore { loc, old, level } => {
542                    h.u(1);
543                    h.debug(&(loc, level));
544                    self.fp_val(&mut h, old);
545                }
546                SaveEntry::InsertToken(t) => {
547                    h.u(2);
548                    h.u(t.0.into());
549                }
550                SaveEntry::Boundary {
551                    group, boundary, ..
552                } => {
553                    h.u(3);
554                    h.debug(&(group, boundary));
555                }
556                SaveEntry::Value(v) => {
557                    h.u(4);
558                    h.i(*v);
559                }
560                SaveEntry::RestoreCodes { kind, old, upto } => {
561                    h.u(5);
562                    h.debug(&(kind, upto));
563                    self.fp_code_table(&mut h, old);
564                }
565            }
566        }
567        h.get()
568    }
569
570    /// The semantic nest (not the lines where modes began).
571    fn fp_nest(&self) -> u64 {
572        let mut h = Fp::default();
573        for l in &self.nest {
574            h.debug(&(l.mode, l.prev_graf, l.prev_depth, l.space_factor));
575            h.debug(&(l.clang, l.math_style));
576            self.fp_list(&mut h, self.nodes.next(l.head));
577            self.fp_list(&mut h, l.incompleat_noad);
578            self.fp_list(&mut h, l.delim_ptr);
579        }
580        h.debug(&(self.mode, self.local_level));
581        h.get()
582    }
583
584    /// The page builder, marks and the typesetting state.
585    fn fp_page(&self) -> u64 {
586        let mut h = Fp::default();
587        let p = &self.page;
588        self.fp_list(&mut h, self.nodes.next(p.contrib_head));
589        self.fp_list(&mut h, self.nodes.next(p.page_head));
590        h.debug(&(p.so_far, p.fi_stretch, p.max_depth, p.contents));
591        h.debug(&(p.least_cost, p.best_size, p.best_so_far, p.best_infinite));
592        // The best break by its place in the page.
593        let mut q = p.page_head;
594        let mut k = 0u64;
595        while !q.is_null() && q != p.best_break {
596            q = self.nodes.next(q);
597            k += 1;
598        }
599        h.u(if q.is_null() { u64::MAX } else { k });
600        h.u(p.ins.len() as u64);
601        h.debug(&(&p.last_glue, p.last_penalty, p.last_kern, p.last_node_type));
602        h.map(self.marks.classes.iter());
603        let ts = &self.ts;
604        h.debug(&(ts.output_active, ts.last_totals, &ts.box_attrs));
605        for l in ts.disc_ptr {
606            self.fp_list(&mut h, l);
607        }
608        for l in [ts.adjust_head, ts.pre_adjust_head] {
609            if !l.is_null() {
610                self.fp_list(&mut h, self.nodes.next(l));
611            }
612        }
613        h.get()
614    }
615
616    /// Conditionals (not the lines where they began).
617    fn fp_cond(&self) -> u64 {
618        let mut h = Fp::default();
619        let c = &self.cond;
620        h.u(c.stack.len() as u64);
621        h.debug(&(c.if_limit, c.cur_if, c.is_in_csname));
622        h.get()
623    }
624
625    /// The engine's PDF state.
626    fn fp_pdf(&self) -> u64 {
627        let mut h = Fp::default();
628        let p = &self.pdf;
629        h.debug(&(&p.page_images, &p.objects, &p.catalog, &p.open_action));
630        h.debug(&(&p.info, &p.names, &p.trailer, &p.outlines));
631        h.debug(&(
632            &p.glyph_to_unicode,
633            &p.colorstacks,
634            p.last_obj,
635            p.last_annot,
636        ));
637        h.debug(&(p.last_link, p.counter.get(), &p.map_entries, &p.lua));
638        h.debug(&(p.retval, p.pos, &p.page_annots, p.output_opened));
639        h.debug(&(&p.dest_order, &p.threads, &p.pending, p.fatal));
640        h.debug(&(&p.active_links, p.stream_written));
641        h.map(p.kinds.iter());
642        h.map(p.page_refs.iter());
643        h.map(p.fonts.iter());
644        h.map(p.font_attrs.iter());
645        h.map(p.dests.iter());
646        h.map(p.struct_dests.iter());
647        h.debug(&(&self.pending_forms, &self.form_objects));
648        h.debug(&(
649            self.images.len(),
650            self.last_image_index,
651            self.last_image_pages,
652        ));
653        h.debug(&(self.box_resources.len(), self.last_box_index));
654        h.map(self.colorstack_values.iter());
655        h.get()
656    }
657
658    /// Fonts, files and counters.
659    fn fp_misc(&self) -> u64 {
660        let mut h = Fp::default();
661        h.u(self.fonts.fonts.len() as u64);
662        for f in &self.fonts.fonts {
663            h.bytes(&f.name);
664            h.i(f.size.into());
665        }
666        h.debug(&(self.dead_cycles, self.insert_penalties, self.last_badness));
667        h.debug(&(
668            self.random_seed,
669            &self.randoms,
670            self.last_x_pos,
671            self.last_y_pos,
672        ));
673        h.debug(&(self.mag_set, self.total_pages, self.err.interaction));
674        h.debug(&(self.err.history, self.long_state, self.pack_dir));
675        h.debug(&(self.cur_cmd, self.cur_chr, self.cur_cs, self.cur_tok));
676        h.debug(&(&self.deferred_writes, &self.text_dirs, &self.math_glues));
677        h.map(self.lua_functions.iter());
678        h.u(self.languages.len() as u64);
679        h.debug(&self.printer.open_write_streams());
680        h.debug(&self.write_paths);
681        for r in &self.files.read_files {
682            h.debug(r);
683        }
684        h.get()
685    }
686}