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