Skip to main content

logicaffeine_kernel/dag/
expr.rs

1//! The kernel core's expression store: a maximally shared DAG with de Bruijn indices.
2//!
3//! Every structurally distinct expression is one [`ExprId`]. Construction goes through
4//! [`Store::intern`], so building the same node twice yields the same id and structural equality
5//! is an integer compare. Each node carries its loose-bound-variable range and whether it contains
6//! free variables or universe parameters, computed once at construction; substitution skips any
7//! subterm those flags prove it cannot touch.
8//!
9//! Binder names and binder annotations are not part of a node: they are irrelevant to typing and
10//! conversion, and dropping them lets `fun (x : A) => x` and `fun (y : A) => y` share one id.
11//!
12//! Nodes created while a declaration is checked live in a separate scratch region, marked by the
13//! top bit of their id. When the declaration is done, [`Store::end_scratch`] drops them in O(1),
14//! so transient terms never accumulate across a hundred thousand declarations — while a result
15//! computed purely from persistent terms (a definition's body at given universe levels) can
16//! still be interned persistently mid-declaration ([`Store::with_persistent`]) and cached for
17//! every later declaration.
18
19use super::level::{LevelId, Levels, LevelsId};
20use super::name::{NameId, Names};
21use super::nat::Nat;
22use rustc_hash::FxHashMap as FxMap;
23use std::rc::Rc;
24
25#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
26pub struct ExprId(pub u32);
27
28/// The arguments of an application spine; most spines fit inline.
29pub type Args = smallvec::SmallVec<[ExprId; 8]>;
30
31#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
32pub struct NatId(pub u32);
33
34#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
35pub struct StrId(pub u32);
36
37#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
38pub struct LitId(pub u32);
39
40/// A LOGOS literal with no Lean counterpart. `Nat` and `Text` literals are [`Node::NatLit`] and
41/// [`Node::StrLit`]. An integer is canonical: one that fits `i64` is `Int`, any other `BigInt`.
42/// A float is held by its bits, so equal literals are one id and `0.0`/`-0.0` stay apart.
43#[derive(Clone, PartialEq, Eq, Hash, Debug)]
44pub enum Lit {
45    Int(i64),
46    BigInt(logicaffeine_base::BigInt),
47    Float(u64),
48    Duration(i64),
49    Date(i32),
50    Moment(i64),
51}
52
53#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
54pub enum Node {
55    BVar(u32),
56    FVar(u32),
57    Sort(LevelId),
58    Const(NameId, LevelsId),
59    App(ExprId, ExprId),
60    Lam(ExprId, ExprId),
61    Pi(ExprId, ExprId),
62    Let(ExprId, ExprId, ExprId),
63    NatLit(NatId),
64    StrLit(StrId),
65    Proj(NameId, u32, ExprId),
66    /// LOGOS: `match disc return motive with cases`, the cases a [`Node::Seq`] chain in
67    /// constructor order.
68    Match(ExprId, ExprId, ExprId),
69    /// LOGOS: `fix f => body`, with `f` bound as `BVar 0` in `body`.
70    Fix(ExprId),
71    /// LOGOS: the `index`-th member of a mutual fixpoint block whose members (a [`Node::Seq`]
72    /// chain) are each bound in every body, the first member outermost.
73    MutualFix(ExprId, u32),
74    /// A non-empty list of expressions: its first element and the rest ([`Node::Nil`] at the end).
75    /// Only [`Node::Match`] and [`Node::MutualFix`] hold one.
76    Seq(ExprId, ExprId),
77    Nil,
78    /// A LOGOS literal with no Lean counterpart.
79    Lit(LitId),
80}
81
82const HAS_FVAR: u8 = 1;
83const HAS_LPARAM: u8 = 2;
84/// A bound variable with index 64 or more occurs (bound or loose): the loose-variable mask may
85/// have lost bits and is not exact.
86const WIDE_BVAR: u8 = 4;
87
88impl Node {
89    /// A well-mixed hash of the node's fields. The table indexes with the TOP bits (Fibonacci
90    /// hashing), which a multiply-xorshift chain fills with entropy from every field.
91    #[inline]
92    fn hash(&self) -> u64 {
93        let (tag, a, b, c) = match *self {
94            Node::BVar(i) => (1u64, i as u64, 0, 0),
95            Node::FVar(i) => (2, i as u64, 0, 0),
96            Node::Sort(l) => (3, l.0 as u64, 0, 0),
97            Node::Const(n, ls) => (4, n.0 as u64, ls.0 as u64, 0),
98            Node::App(f, a) => (5, f.0 as u64, a.0 as u64, 0),
99            Node::Lam(t, b) => (6, t.0 as u64, b.0 as u64, 0),
100            Node::Pi(t, b) => (7, t.0 as u64, b.0 as u64, 0),
101            Node::Let(t, v, b) => (8, t.0 as u64, v.0 as u64, b.0 as u64),
102            Node::NatLit(n) => (9, n.0 as u64, 0, 0),
103            Node::StrLit(s) => (10, s.0 as u64, 0, 0),
104            Node::Proj(n, i, e) => (11, n.0 as u64, i as u64, e.0 as u64),
105            Node::Match(d, m, c) => (12, d.0 as u64, m.0 as u64, c.0 as u64),
106            Node::Fix(b) => (13, b.0 as u64, 0, 0),
107            Node::MutualFix(ds, i) => (14, ds.0 as u64, i as u64, 0),
108            Node::Seq(h, t) => (15, h.0 as u64, t.0 as u64, 0),
109            Node::Nil => (16, 0, 0, 0),
110            Node::Lit(l) => (17, l.0 as u64, 0, 0),
111        };
112        let mut h = (tag ^ (a << 8)).wrapping_mul(0x9E37_79B9_7F4A_7C15);
113        h = (h ^ b ^ (c << 32)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
114        h ^ (h >> 29)
115    }
116}
117
118/// The scratch region's table. Open addressing with linear probing. A slot holds
119/// `(generation << 32) | (id + 1)`; a slot from an older generation reads as empty, so clearing
120/// the table is one increment, not a memset.
121///
122/// (A hash tag in the slot would spare node-array reads on mismatching probes, fewer cache
123/// misses, but measured +1% instructions on `init`; instructions are the Arena's metric.)
124#[derive(Clone)]
125struct Table {
126    slots: Vec<u64>,
127    shift: u32,
128    len: usize,
129    generation: u64,
130}
131
132impl Table {
133    fn new(cap: usize) -> Table {
134        let cap = cap.next_power_of_two().max(16);
135        Table {
136            slots: vec![0; cap],
137            shift: 64 - cap.trailing_zeros(),
138            len: 0,
139            generation: 1,
140        }
141    }
142
143    /// A slot is live exactly when its generation is current (slots are only written whole).
144    #[inline]
145    fn is_live(&self, s: u64) -> bool {
146        s >> 32 == self.generation
147    }
148
149    /// The node's id, or the vacant slot where it would go (valid until the table changes).
150    #[inline]
151    fn find_or_vacant(&self, entries: &[Entry], node: &Node, h: u64) -> Result<ExprId, usize> {
152        let mask = self.slots.len() - 1;
153        let mut i = (h >> self.shift) as usize;
154        loop {
155            let s = self.slots[i];
156            if !self.is_live(s) {
157                return Err(i);
158            }
159            let id = s as u32 - 1;
160            if entries[id as usize].node == *node {
161                return Ok(ExprId(id));
162            }
163            i = (i + 1) & mask;
164        }
165    }
166
167    /// Inserts `id` at a slot `find_or_vacant` returned, unless the table must grow first.
168    #[inline]
169    fn insert_at(&mut self, entries: &[Entry], slot: Option<usize>, id: ExprId, h: u64) {
170        match slot {
171            Some(i) if (self.len + 1) * 2 <= self.slots.len() => {
172                self.slots[i] = (self.generation << 32) | (id.0 as u64 + 1);
173                self.len += 1;
174            }
175            _ => self.insert(entries, id, h),
176        }
177    }
178
179    fn insert(&mut self, entries: &[Entry], id: ExprId, h: u64) {
180        if (self.len + 1) * 2 > self.slots.len() {
181            self.grow(entries);
182        }
183        self.place(id.0, h);
184        self.len += 1;
185    }
186
187    #[inline]
188    fn place(&mut self, id: u32, h: u64) {
189        let mask = self.slots.len() - 1;
190        let mut i = (h >> self.shift) as usize;
191        while self.is_live(self.slots[i]) {
192            i = (i + 1) & mask;
193        }
194        self.slots[i] = (self.generation << 32) | (id as u64 + 1);
195    }
196
197    fn grow(&mut self, entries: &[Entry]) {
198        let cap = (self.slots.len() * 2).max(16);
199        self.shift = 64 - cap.trailing_zeros();
200        let old = std::mem::replace(&mut self.slots, vec![0; cap]);
201        let old_gen = self.generation;
202        self.generation = 1;
203        for s in old {
204            if s >> 32 == old_gen {
205                let id = s as u32 - 1;
206                self.place(id, entries[id as usize].node.hash());
207            }
208        }
209    }
210
211    fn clear(&mut self) {
212        self.len = 0;
213        self.generation += 1;
214        if self.generation >= u32::MAX as u64 {
215            self.slots.iter_mut().for_each(|s| *s = 0);
216            self.generation = 1;
217        }
218    }
219}
220
221/// The persistent region's table. It is never cleared, so it needs no generation: a slot is
222/// `id + 1`, and `0` is vacant — 4 bytes per slot, half the scratch table's.
223#[derive(Clone)]
224struct PermTable {
225    slots: Vec<u32>,
226    shift: u32,
227    len: usize,
228}
229
230impl PermTable {
231    fn new(cap: usize) -> PermTable {
232        let cap = cap.next_power_of_two().max(16);
233        PermTable {
234            slots: vec![0; cap],
235            shift: 64 - cap.trailing_zeros(),
236            len: 0,
237        }
238    }
239
240    fn reserve(&mut self, entries: &[Entry], n: usize) {
241        while (n + 1) * 2 > self.slots.len() {
242            self.grow(entries);
243        }
244    }
245
246    #[inline]
247    fn find_or_vacant(&self, entries: &[Entry], node: &Node, h: u64) -> Result<ExprId, usize> {
248        let mask = self.slots.len() - 1;
249        let mut i = (h >> self.shift) as usize;
250        loop {
251            let s = self.slots[i];
252            if s == 0 {
253                return Err(i);
254            }
255            if entries[(s - 1) as usize].node == *node {
256                return Ok(ExprId(s - 1));
257            }
258            i = (i + 1) & mask;
259        }
260    }
261
262    #[inline]
263    fn insert_at(&mut self, entries: &[Entry], slot: Option<usize>, id: ExprId, h: u64) {
264        match slot {
265            Some(i) if (self.len + 1) * 2 <= self.slots.len() => self.slots[i] = id.0 + 1,
266            _ => {
267                if (self.len + 1) * 2 > self.slots.len() {
268                    self.grow(entries);
269                }
270                self.place(id.0, h);
271            }
272        }
273        self.len += 1;
274    }
275
276    #[inline]
277    fn place(&mut self, id: u32, h: u64) {
278        let mask = self.slots.len() - 1;
279        let mut i = (h >> self.shift) as usize;
280        while self.slots[i] != 0 {
281            i = (i + 1) & mask;
282        }
283        self.slots[i] = id + 1;
284    }
285
286    fn grow(&mut self, entries: &[Entry]) {
287        let cap = (self.slots.len() * 2).max(16);
288        self.shift = 64 - cap.trailing_zeros();
289        let old = std::mem::replace(&mut self.slots, vec![0; cap]);
290        for s in old {
291            if s != 0 {
292                self.place(s - 1, entries[(s - 1) as usize].node.hash());
293            }
294        }
295    }
296}
297
298/// A memo for one traversal (substitution, lifting, abstraction), keyed on `(node, depth)`.
299/// Open addressing with generation stamps: starting a new traversal is one increment, and a
300/// probe is a couple of instructions, where a general hash map pays for hashing, insertion and
301/// clearing on every call.
302#[derive(Clone)]
303struct Memo {
304    keys: Vec<u64>,
305    vals: Vec<u32>,
306    gens: Vec<u32>,
307    generation: u32,
308    len: usize,
309    shift: u32,
310}
311
312impl Memo {
313    fn new() -> Memo {
314        let cap = 1 << 10;
315        Memo {
316            keys: vec![0; cap],
317            vals: vec![0; cap],
318            gens: vec![0; cap],
319            generation: 1,
320            len: 0,
321            shift: 64 - 10,
322        }
323    }
324
325    #[inline]
326    fn slot(&self, key: u64) -> usize {
327        (key.wrapping_mul(0x9E37_79B9_7F4A_7C15) >> self.shift) as usize
328    }
329
330    /// Starts a fresh traversal.
331    #[inline]
332    fn reset(&mut self) {
333        self.len = 0;
334        self.generation = self.generation.wrapping_add(1);
335        if self.generation == 0 {
336            self.gens.iter_mut().for_each(|g| *g = 0);
337            self.generation = 1;
338        }
339    }
340
341    #[inline]
342    fn get(&self, e: ExprId, off: u32) -> Option<ExprId> {
343        let key = ((off as u64) << 32) | e.0 as u64;
344        let mask = self.keys.len() - 1;
345        let mut i = self.slot(key);
346        while self.gens[i] == self.generation {
347            if self.keys[i] == key {
348                return Some(ExprId(self.vals[i]));
349            }
350            i = (i + 1) & mask;
351        }
352        None
353    }
354
355    #[inline]
356    fn insert(&mut self, e: ExprId, off: u32, r: ExprId) {
357        if (self.len + 1) * 2 > self.keys.len() {
358            self.grow();
359        }
360        let key = ((off as u64) << 32) | e.0 as u64;
361        self.place(key, r.0);
362        self.len += 1;
363    }
364
365    #[inline]
366    fn place(&mut self, key: u64, val: u32) {
367        let mask = self.keys.len() - 1;
368        let mut i = self.slot(key);
369        while self.gens[i] == self.generation {
370            i = (i + 1) & mask;
371        }
372        self.keys[i] = key;
373        self.vals[i] = val;
374        self.gens[i] = self.generation;
375    }
376
377    fn grow(&mut self) {
378        let cap = self.keys.len() * 2;
379        let keys = std::mem::replace(&mut self.keys, vec![0; cap]);
380        let vals = std::mem::replace(&mut self.vals, vec![0; cap]);
381        let gens = std::mem::replace(&mut self.gens, vec![0; cap]);
382        self.shift = 64 - cap.trailing_zeros();
383        let g = self.generation;
384        for i in 0..keys.len() {
385            if gens[i] == g {
386                self.place(keys[i], vals[i]);
387            }
388        }
389    }
390}
391
392/// The id bit marking a scratch node.
393const SCRATCH: u32 = 1 << 31;
394
395impl ExprId {
396    #[inline]
397    pub fn is_scratch(self) -> bool {
398        self.0 & SCRATCH != 0
399    }
400}
401
402#[inline]
403fn has_scratch_child(node: &Node) -> bool {
404    match *node {
405        Node::App(a, b) | Node::Lam(a, b) | Node::Pi(a, b) | Node::Seq(a, b) => {
406            (a.0 | b.0) & SCRATCH != 0
407        }
408        Node::Let(a, b, c) | Node::Match(a, b, c) => (a.0 | b.0 | c.0) & SCRATCH != 0,
409        Node::Proj(_, _, a) | Node::Fix(a) | Node::MutualFix(a, _) => a.0 & SCRATCH != 0,
410        _ => false,
411    }
412}
413
414/// A node and its cached metadata, together: whatever reads one usually reads the others.
415#[derive(Copy, Clone)]
416struct Entry {
417    node: Node,
418    /// Which loose de Bruijn indices below 64 occur (exact only without [`WIDE_BVAR`]).
419    mask: u64,
420    lbr: u32,
421    flags: u8,
422    /// How many applications lead down the function side to the head ([`SPINE_UNKNOWN`] once
423    /// that no longer fits): the argument count, known without walking the spine. For a
424    /// [`Node::Seq`], its length.
425    spine: u16,
426}
427
428const SPINE_UNKNOWN: u16 = u16::MAX;
429
430const _: () = assert!(
431    std::mem::size_of::<Entry>() == 32,
432    "an entry stays half a cache line"
433);
434
435/// One region's nodes.
436#[derive(Clone, Default)]
437struct Region {
438    entries: Vec<Entry>,
439}
440
441impl Region {
442    fn clear(&mut self) {
443        self.entries.clear();
444    }
445
446    fn len(&self) -> usize {
447        self.entries.len()
448    }
449}
450
451#[derive(Clone)]
452pub struct Store {
453    pub names: Names,
454    pub levels: Levels,
455    perm: Region,
456    temp: Region,
457    persistent: PermTable,
458    scratch: Table,
459    in_scratch: bool,
460    force_persistent: bool,
461    /// Set when a [`Store::with_persistent`] computation met a node that already lives in scratch.
462    tainted: bool,
463    /// The largest scratch region any one declaration needed, in nodes.
464    temp_peak: usize,
465    /// Persistent nodes created by [`Store::with_persistent`] (cached instantiations), rather than
466    /// read from the input.
467    cached_nodes: usize,
468    nats: Vec<Nat>,
469    nat_index: FxMap<Nat, NatId>,
470    strs: Vec<Rc<str>>,
471    str_index: FxMap<Rc<str>, StrId>,
472    lits: Vec<Lit>,
473    lit_index: FxMap<Lit, LitId>,
474    inst_memo: Memo,
475    lift_memo: Memo,
476    abs_memo: Memo,
477    lp_memo: Memo,
478}
479
480impl Default for Store {
481    fn default() -> Self {
482        Self::new()
483    }
484}
485
486impl Store {
487    pub fn new() -> Store {
488        Store {
489            names: Names::new(),
490            levels: Levels::new(),
491            perm: Region::default(),
492            temp: Region::default(),
493            persistent: PermTable::new(1 << 12),
494            scratch: Table::new(1 << 12),
495            in_scratch: false,
496            force_persistent: false,
497            tainted: false,
498            temp_peak: 0,
499            cached_nodes: 0,
500            nats: Vec::new(),
501            nat_index: FxMap::default(),
502            strs: Vec::new(),
503            str_index: FxMap::default(),
504            lits: Vec::new(),
505            lit_index: FxMap::default(),
506            inst_memo: Memo::new(),
507            lift_memo: Memo::new(),
508            abs_memo: Memo::new(),
509            lp_memo: Memo::new(),
510        }
511    }
512
513    pub fn expr_count(&self) -> usize {
514        self.perm.len() + self.temp.len()
515    }
516
517    pub fn persistent_count(&self) -> usize {
518        self.perm.len()
519    }
520
521    /// Bytes held by each part of the store (capacity, not length: what the process pays).
522    pub fn memory_report(&self) -> Vec<(&'static str, usize)> {
523        let region = |r: &Region| r.entries.capacity() * std::mem::size_of::<Entry>();
524        vec![
525            ("persistent nodes (count)", self.perm.len()),
526            (
527                "persistent nodes from cached instantiations (count)",
528                self.cached_nodes,
529            ),
530            (
531                "largest scratch region (nodes)",
532                self.temp_peak.max(self.temp.len()),
533            ),
534            ("persistent region bytes", region(&self.perm)),
535            ("scratch region bytes", region(&self.temp)),
536            (
537                "persistent table bytes",
538                self.persistent.slots.capacity() * 4,
539            ),
540            ("scratch table bytes", self.scratch.slots.capacity() * 8),
541            ("names (count)", self.names.len()),
542            ("levels (count)", self.levels.len()),
543            ("nat literals (count)", self.nats.len()),
544            ("string literals (count)", self.strs.len()),
545            ("LOGOS literals (count)", self.lits.len()),
546            (
547                "memo bytes",
548                [
549                    &self.inst_memo,
550                    &self.lift_memo,
551                    &self.abs_memo,
552                    &self.lp_memo,
553                ]
554                .iter()
555                .map(|m| m.keys.capacity() * 16)
556                .sum(),
557            ),
558        ]
559    }
560
561    /// Room for `n` more persistent nodes without growing (a reader that knows its input size
562    /// avoids rehashing a table of millions of entries several times over).
563    pub fn reserve_persistent(&mut self, n: usize) {
564        let total = self.perm.len() + n;
565        self.perm.entries.reserve(n);
566        self.persistent.reserve(&self.perm.entries, total);
567    }
568
569    /// Everything interned from now on is transient until [`Store::end_scratch`].
570    pub fn begin_scratch(&mut self) {
571        debug_assert!(!self.in_scratch);
572        self.in_scratch = true;
573    }
574
575    pub fn end_scratch(&mut self) {
576        if self.in_scratch {
577            self.temp_peak = self.temp_peak.max(self.temp.len());
578            self.in_scratch = false;
579            self.temp.clear();
580            self.scratch.clear();
581        }
582    }
583
584    pub fn in_scratch(&self) -> bool {
585        self.in_scratch
586    }
587
588    /// Runs `f`, a computation over persistent terms only, interning what it builds persistently
589    /// so the result can be cached across declarations. Returns the result and whether it is
590    /// persistent.
591    ///
592    /// A structure is only ever created when it is in neither region, so each structure has one
593    /// id at any time. If `f` meets a node that already lives in scratch (built earlier in this
594    /// declaration), the computation continues in scratch with that node rather than duplicate it,
595    /// and the result is reported as not persistent — correct for this declaration, not cacheable.
596    pub fn with_persistent<T>(&mut self, f: impl FnOnce(&mut Store) -> T) -> (T, bool) {
597        let saved_force = std::mem::replace(&mut self.force_persistent, true);
598        let saved_taint = std::mem::replace(&mut self.tainted, false);
599        let r = f(self);
600        let persistent = !self.tainted;
601        self.force_persistent = saved_force;
602        self.tainted = saved_taint || self.tainted;
603        (r, persistent)
604    }
605
606    pub fn intern(&mut self, node: Node) -> ExprId {
607        let h = node.hash();
608        let scratch_child = has_scratch_child(&node);
609        let mut perm_slot = None;
610        if !scratch_child {
611            match self.persistent.find_or_vacant(&self.perm.entries, &node, h) {
612                Ok(id) => return id,
613                Err(slot) => perm_slot = Some(slot),
614            }
615        }
616        let mut temp_slot = None;
617        if self.in_scratch {
618            match self.scratch.find_or_vacant(&self.temp.entries, &node, h) {
619                Ok(i) => {
620                    self.tainted |= self.force_persistent;
621                    return ExprId(i.0 | SCRATCH);
622                }
623                Err(slot) => temp_slot = Some(slot),
624            }
625        }
626        let entry = self.entry_of(node);
627        if self.in_scratch && (!self.force_persistent || scratch_child) {
628            self.tainted |= self.force_persistent;
629            let i = ExprId(self.temp.len() as u32);
630            self.temp.entries.push(entry);
631            self.scratch.insert_at(&self.temp.entries, temp_slot, i, h);
632            ExprId(i.0 | SCRATCH)
633        } else {
634            debug_assert!(!scratch_child);
635            let id = ExprId(self.perm.len() as u32);
636            assert!(id.0 & SCRATCH == 0, "persistent expression store is full");
637            self.cached_nodes += self.force_persistent as usize;
638            self.perm.entries.push(entry);
639            self.persistent
640                .insert_at(&self.perm.entries, perm_slot, id, h);
641            id
642        }
643    }
644
645    #[inline]
646    fn entry(&self, e: ExprId) -> &Entry {
647        if e.0 & SCRATCH != 0 {
648            &self.temp.entries[(e.0 & !SCRATCH) as usize]
649        } else {
650            &self.perm.entries[e.0 as usize]
651        }
652    }
653
654    #[inline]
655    fn lbr_of(&self, e: ExprId) -> u32 {
656        self.entry(e).lbr
657    }
658
659    #[inline]
660    fn flags_of(&self, e: ExprId) -> u8 {
661        self.entry(e).flags
662    }
663
664    #[inline]
665    fn mask_of(&self, e: ExprId) -> u64 {
666        self.entry(e).mask
667    }
668
669    /// The node with its metadata: loose-variable range and mask, flags, spine length — each
670    /// child's entry read once.
671    #[inline(always)]
672    fn entry_of(&self, node: Node) -> Entry {
673        let (lbr, mask, flags, spine) = match node {
674            Node::BVar(i) => (
675                i + 1,
676                if i < 64 { 1 << i } else { 0 },
677                if i < 64 { 0 } else { WIDE_BVAR },
678                0,
679            ),
680            Node::FVar(_) => (0, 0, HAS_FVAR, 0),
681            Node::Sort(l) => (
682                0,
683                0,
684                if self.levels.has_param(l) {
685                    HAS_LPARAM
686                } else {
687                    0
688                },
689                0,
690            ),
691            Node::Const(_, ls) => {
692                let lp = self
693                    .levels
694                    .get_list(ls)
695                    .iter()
696                    .any(|l| self.levels.has_param(*l));
697                (0, 0, if lp { HAS_LPARAM } else { 0 }, 0)
698            }
699            Node::App(f, a) => {
700                let (x, y) = (self.entry(f), self.entry(a));
701                (
702                    x.lbr.max(y.lbr),
703                    x.mask | y.mask,
704                    x.flags | y.flags,
705                    x.spine.saturating_add(1),
706                )
707            }
708            Node::Lam(t, b) | Node::Pi(t, b) => {
709                let (x, y) = (self.entry(t), self.entry(b));
710                (
711                    x.lbr.max(y.lbr.saturating_sub(1)),
712                    x.mask | (y.mask >> 1),
713                    x.flags | y.flags,
714                    0,
715                )
716            }
717            Node::Let(t, v, b) => {
718                let (x, y, z) = (self.entry(t), self.entry(v), self.entry(b));
719                let lbr = x.lbr.max(y.lbr).max(z.lbr.saturating_sub(1));
720                (
721                    lbr,
722                    x.mask | y.mask | (z.mask >> 1),
723                    x.flags | y.flags | z.flags,
724                    0,
725                )
726            }
727            Node::NatLit(_) | Node::StrLit(_) | Node::Lit(_) | Node::Nil => (0, 0, 0, 0),
728            Node::Proj(_, _, e) => {
729                let x = self.entry(e);
730                (x.lbr, x.mask, x.flags, 0)
731            }
732            Node::Match(..) | Node::Fix(_) | Node::MutualFix(..) | Node::Seq(..) => {
733                self.logos_entry(node)
734            }
735        };
736        Entry {
737            node,
738            mask,
739            lbr,
740            flags,
741            spine,
742        }
743    }
744
745    /// [`Store::entry_of`] for LOGOS's nodes, out of line: a Lean term never reaches these arms, and
746    /// inlined into `intern` they would cost every node either theory interns.
747    #[inline(never)]
748    fn logos_entry(&self, node: Node) -> (u32, u64, u8, u16) {
749        match node {
750            Node::Match(d, m, c) => {
751                let (x, y, z) = (self.entry(d), self.entry(m), self.entry(c));
752                (
753                    x.lbr.max(y.lbr).max(z.lbr),
754                    x.mask | y.mask | z.mask,
755                    x.flags | y.flags | z.flags,
756                    0,
757                )
758            }
759            Node::Fix(b) => {
760                let x = self.entry(b);
761                (x.lbr.saturating_sub(1), x.mask >> 1, x.flags, 0)
762            }
763            Node::MutualFix(ds, _) => {
764                // The `spine` of a `Seq` is its length: the binders every member is under.
765                let x = self.entry(ds);
766                let n = x.spine as u32;
767                let mask = if n >= 64 { 0 } else { x.mask >> n };
768                let wide = if n >= 64 && x.lbr > n { WIDE_BVAR } else { 0 };
769                (x.lbr.saturating_sub(n), mask, x.flags | wide, 0)
770            }
771            Node::Seq(h, t) => {
772                let (x, y) = (self.entry(h), self.entry(t));
773                (
774                    x.lbr.max(y.lbr),
775                    x.mask | y.mask,
776                    x.flags | y.flags,
777                    y.spine.saturating_add(1),
778                )
779            }
780            other => unreachable!("not a LOGOS node: {other:?}"),
781        }
782    }
783
784    #[inline]
785    pub fn node(&self, e: ExprId) -> Node {
786        self.entry(e).node
787    }
788
789    /// One more than the largest loose de Bruijn index (0 when closed).
790    #[inline]
791    pub fn loose_bvar_range(&self, e: ExprId) -> u32 {
792        self.lbr_of(e)
793    }
794
795    /// [`Store::loose_bvar_range`] and [`Store::loose_bvar_mask`] in one read.
796    #[inline]
797    pub fn loose_bvars(&self, e: ExprId) -> (u32, Option<u64>) {
798        let x = self.entry(e);
799        (
800            x.lbr,
801            if x.flags & WIDE_BVAR != 0 {
802                None
803            } else {
804                Some(x.mask)
805            },
806        )
807    }
808
809    /// [`Store::loose_bvars`] and [`Store::has_lparam`] in one read: everything outside a term
810    /// that its meaning can depend on.
811    #[inline]
812    pub fn scope_of(&self, e: ExprId) -> (u32, Option<u64>, bool) {
813        let x = self.entry(e);
814        (
815            x.lbr,
816            if x.flags & WIDE_BVAR != 0 {
817                None
818            } else {
819                Some(x.mask)
820            },
821            x.flags & HAS_LPARAM != 0,
822        )
823    }
824
825    /// Which loose de Bruijn indices occur — bit `i` for `BVar(i)` — when that is known exactly:
826    /// `None` if a bound variable with index 64 or more occurs anywhere in `e` (its bit could not
827    /// be recorded, and may have come into range under binders).
828    #[inline]
829    pub fn loose_bvar_mask(&self, e: ExprId) -> Option<u64> {
830        if self.flags_of(e) & WIDE_BVAR != 0 {
831            None
832        } else {
833            Some(self.mask_of(e))
834        }
835    }
836
837    #[inline]
838    pub fn has_loose_bvars(&self, e: ExprId) -> bool {
839        self.lbr_of(e) > 0
840    }
841
842    #[inline]
843    pub fn has_fvar(&self, e: ExprId) -> bool {
844        self.flags_of(e) & HAS_FVAR != 0
845    }
846
847    #[inline]
848    pub fn has_lparam(&self, e: ExprId) -> bool {
849        self.flags_of(e) & HAS_LPARAM != 0
850    }
851
852    // ---- constructors ----
853
854    pub fn bvar(&mut self, i: u32) -> ExprId {
855        self.intern(Node::BVar(i))
856    }
857    pub fn fvar(&mut self, i: u32) -> ExprId {
858        self.intern(Node::FVar(i))
859    }
860    pub fn sort(&mut self, l: LevelId) -> ExprId {
861        self.intern(Node::Sort(l))
862    }
863    pub fn prop(&mut self) -> ExprId {
864        self.sort(LevelId::ZERO)
865    }
866    pub fn cnst(&mut self, n: NameId, ls: LevelsId) -> ExprId {
867        self.intern(Node::Const(n, ls))
868    }
869    pub fn cnst_levels(&mut self, n: NameId, ls: &[LevelId]) -> ExprId {
870        let l = self.levels.list(ls);
871        self.cnst(n, l)
872    }
873    pub fn app(&mut self, f: ExprId, a: ExprId) -> ExprId {
874        self.intern(Node::App(f, a))
875    }
876    pub fn lam(&mut self, t: ExprId, b: ExprId) -> ExprId {
877        self.intern(Node::Lam(t, b))
878    }
879    pub fn pi(&mut self, t: ExprId, b: ExprId) -> ExprId {
880        self.intern(Node::Pi(t, b))
881    }
882    pub fn let_(&mut self, t: ExprId, v: ExprId, b: ExprId) -> ExprId {
883        self.intern(Node::Let(t, v, b))
884    }
885    pub fn proj(&mut self, s: NameId, i: u32, e: ExprId) -> ExprId {
886        self.intern(Node::Proj(s, i, e))
887    }
888    pub fn nat_lit(&mut self, n: Nat) -> ExprId {
889        let id = self.nat_id(n);
890        self.intern(Node::NatLit(id))
891    }
892    /// The interned id of a natural number (the table is append-only: ids never go stale).
893    pub fn nat_id(&mut self, n: Nat) -> NatId {
894        match self.nat_index.get(&n) {
895            Some(id) => *id,
896            None => {
897                let id = NatId(self.nats.len() as u32);
898                self.nats.push(n.clone());
899                self.nat_index.insert(n, id);
900                id
901            }
902        }
903    }
904    pub fn str_lit(&mut self, s: &str) -> ExprId {
905        let id = match self.str_index.get(s) {
906            Some(id) => *id,
907            None => {
908                let rc: Rc<str> = Rc::from(s);
909                let id = StrId(self.strs.len() as u32);
910                self.strs.push(rc.clone());
911                self.str_index.insert(rc, id);
912                id
913            }
914        };
915        self.intern(Node::StrLit(id))
916    }
917    pub fn nat(&self, n: NatId) -> &Nat {
918        &self.nats[n.0 as usize]
919    }
920    pub fn lit(&mut self, l: Lit) -> ExprId {
921        let id = match self.lit_index.get(&l) {
922            Some(id) => *id,
923            None => {
924                let id = LitId(self.lits.len() as u32);
925                self.lits.push(l.clone());
926                self.lit_index.insert(l, id);
927                id
928            }
929        };
930        self.intern(Node::Lit(id))
931    }
932    pub fn lit_value(&self, l: LitId) -> &Lit {
933        &self.lits[l.0 as usize]
934    }
935    pub fn seq(&mut self, items: &[ExprId]) -> ExprId {
936        let nil = self.intern(Node::Nil);
937        items
938            .iter()
939            .rev()
940            .fold(nil, |tail, h| self.intern(Node::Seq(*h, tail)))
941    }
942    /// The elements of a [`Node::Seq`] chain, first to last.
943    pub fn seq_items(&self, mut e: ExprId) -> Args {
944        let mut out = Args::new();
945        while let Node::Seq(h, t) = self.node(e) {
946            out.push(h);
947            e = t;
948        }
949        debug_assert!(matches!(self.node(e), Node::Nil), "a sequence ends in Nil");
950        out
951    }
952    pub fn match_(&mut self, disc: ExprId, motive: ExprId, cases: &[ExprId]) -> ExprId {
953        let cs = self.seq(cases);
954        self.intern(Node::Match(disc, motive, cs))
955    }
956    pub fn fix(&mut self, body: ExprId) -> ExprId {
957        self.intern(Node::Fix(body))
958    }
959    pub fn mutual_fix(&mut self, defs: &[ExprId], index: u32) -> ExprId {
960        let ds = self.seq(defs);
961        self.intern(Node::MutualFix(ds, index))
962    }
963    pub fn string(&self, s: StrId) -> Rc<str> {
964        self.strs[s.0 as usize].clone()
965    }
966
967    pub fn mk_app_n(&mut self, f: ExprId, args: &[ExprId]) -> ExprId {
968        args.iter().fold(f, |acc, a| self.app(acc, *a))
969    }
970
971    /// The head of an application spine.
972    pub fn app_fn(&self, mut e: ExprId) -> ExprId {
973        while let Node::App(f, _) = self.node(e) {
974            e = f;
975        }
976        e
977    }
978
979    /// Head and arguments (in application order), filled last to first into a buffer of the
980    /// recorded spine length.
981    pub fn app_args(&self, e: ExprId) -> (ExprId, Args) {
982        let n = self.entry(e).spine;
983        if n == SPINE_UNKNOWN {
984            return self.app_args_walk(e);
985        }
986        let mut args: Args = smallvec::smallvec![ExprId(0); n as usize];
987        let mut cur = e;
988        for slot in args.iter_mut().rev() {
989            let Node::App(f, a) = self.node(cur) else {
990                unreachable!("a recorded spine is made of applications")
991            };
992            *slot = a;
993            cur = f;
994        }
995        (cur, args)
996    }
997
998    /// [`Store::app_args`] for a spine too long to record.
999    #[cold]
1000    fn app_args_walk(&self, mut e: ExprId) -> (ExprId, Args) {
1001        let mut args = Args::new();
1002        while let Node::App(f, a) = self.node(e) {
1003            args.push(a);
1004            e = f;
1005        }
1006        args.reverse();
1007        (e, args)
1008    }
1009
1010    pub fn app_num_args(&self, mut e: ExprId) -> usize {
1011        let n = self.entry(e).spine;
1012        if n != SPINE_UNKNOWN {
1013            return n as usize;
1014        }
1015        let mut n = 0;
1016        while let Node::App(f, _) = self.node(e) {
1017            n += 1;
1018            e = f;
1019        }
1020        n
1021    }
1022
1023    pub fn const_head(&self, e: ExprId) -> Option<(NameId, LevelsId)> {
1024        match self.node(self.app_fn(e)) {
1025            Node::Const(n, ls) => Some((n, ls)),
1026            _ => None,
1027        }
1028    }
1029
1030    // ---- substitution ----
1031
1032    /// Replaces loose `BVar(i)`, `i < subst.len()`, by `subst[i]` (`rev = false`) or by
1033    /// `subst[len - 1 - i]` (`rev = true`), and lowers the remaining loose indices by `len`.
1034    pub fn instantiate_core(&mut self, e: ExprId, subst: &[ExprId], rev: bool) -> ExprId {
1035        if subst.is_empty() || !self.has_loose_bvars(e) {
1036            return e;
1037        }
1038        self.inst_memo.reset();
1039        self.inst_go(e, 0, subst, rev)
1040    }
1041
1042    pub fn instantiate(&mut self, e: ExprId, subst: &[ExprId]) -> ExprId {
1043        self.instantiate_core(e, subst, false)
1044    }
1045
1046    pub fn instantiate_rev(&mut self, e: ExprId, subst: &[ExprId]) -> ExprId {
1047        self.instantiate_core(e, subst, true)
1048    }
1049
1050    pub fn instantiate1(&mut self, e: ExprId, v: ExprId) -> ExprId {
1051        self.instantiate_core(e, &[v], false)
1052    }
1053
1054    fn inst_go(&mut self, e: ExprId, off: u32, subst: &[ExprId], rev: bool) -> ExprId {
1055        if self.lbr_of(e) <= off {
1056            return e;
1057        }
1058        if let Some(r) = self.inst_memo.get(e, off) {
1059            return r;
1060        }
1061        let n = subst.len() as u32;
1062        let r = match self.node(e) {
1063            Node::BVar(i) => {
1064                if i < off + n {
1065                    let k = (i - off) as usize;
1066                    let v = if rev {
1067                        subst[subst.len() - 1 - k]
1068                    } else {
1069                        subst[k]
1070                    };
1071                    self.lift_loose(v, 0, off)
1072                } else {
1073                    self.bvar(i - n)
1074                }
1075            }
1076            Node::App(f, a) => {
1077                let (f2, a2) = (
1078                    self.inst_go(f, off, subst, rev),
1079                    self.inst_go(a, off, subst, rev),
1080                );
1081                self.app(f2, a2)
1082            }
1083            Node::Lam(t, b) => {
1084                let (t2, b2) = (
1085                    self.inst_go(t, off, subst, rev),
1086                    self.inst_go(b, off + 1, subst, rev),
1087                );
1088                self.lam(t2, b2)
1089            }
1090            Node::Pi(t, b) => {
1091                let (t2, b2) = (
1092                    self.inst_go(t, off, subst, rev),
1093                    self.inst_go(b, off + 1, subst, rev),
1094                );
1095                self.pi(t2, b2)
1096            }
1097            Node::Let(t, v, b) => {
1098                let t2 = self.inst_go(t, off, subst, rev);
1099                let v2 = self.inst_go(v, off, subst, rev);
1100                let b2 = self.inst_go(b, off + 1, subst, rev);
1101                self.let_(t2, v2, b2)
1102            }
1103            Node::Proj(s, i, x) => {
1104                let x2 = self.inst_go(x, off, subst, rev);
1105                self.proj(s, i, x2)
1106            }
1107            Node::Match(..) | Node::Fix(_) | Node::MutualFix(..) | Node::Seq(..) => {
1108                self.inst_logos(e, off, subst, rev)
1109            }
1110            _ => e,
1111        };
1112        self.inst_memo.insert(e, off, r);
1113        r
1114    }
1115
1116    // The walks' arms for LOGOS's nodes, out of line. A Lean term never reaches them, and the closure
1117    // each builds would otherwise sit in the frame of every call of the hottest recursions in the
1118    // checker, for both theories.
1119    #[inline(never)]
1120    fn inst_logos(&mut self, e: ExprId, off: u32, subst: &[ExprId], rev: bool) -> ExprId {
1121        self.map_logos(e, &mut |st, c, k| st.inst_go(c, off + k, subst, rev))
1122    }
1123    #[inline(never)]
1124    fn lift_logos(&mut self, e: ExprId, s: u32, d: u32) -> ExprId {
1125        self.map_logos(e, &mut |st, c, k| st.lift_go(c, s + k, d))
1126    }
1127    #[inline(never)]
1128    fn abs_logos(&mut self, e: ExprId, off: u32, fvars: &[u32]) -> ExprId {
1129        self.map_logos(e, &mut |st, c, k| st.abs_go(c, off + k, fvars))
1130    }
1131    #[inline(never)]
1132    fn lp_logos(&mut self, e: ExprId, ps: &[NameId], us: &[LevelId]) -> ExprId {
1133        self.map_logos(e, &mut |st, c, _| st.lp_go(c, ps, us))
1134    }
1135
1136    /// Rebuilds a LOGOS node ([`Node::Match`], [`Node::Fix`], [`Node::MutualFix`], [`Node::Seq`])
1137    /// from its children, each mapped by `f` given the number of binders the node puts above it.
1138    fn map_logos(
1139        &mut self,
1140        e: ExprId,
1141        f: &mut dyn FnMut(&mut Store, ExprId, u32) -> ExprId,
1142    ) -> ExprId {
1143        match self.node(e) {
1144            Node::Match(d, m, c) => {
1145                let (d2, m2, c2) = (f(self, d, 0), f(self, m, 0), f(self, c, 0));
1146                self.intern(Node::Match(d2, m2, c2))
1147            }
1148            Node::Fix(b) => {
1149                let b2 = f(self, b, 1);
1150                self.intern(Node::Fix(b2))
1151            }
1152            Node::MutualFix(ds, i) => {
1153                let n = self.entry(ds).spine as u32;
1154                let ds2 = f(self, ds, n);
1155                self.intern(Node::MutualFix(ds2, i))
1156            }
1157            Node::Seq(h, t) => {
1158                let (h2, t2) = (f(self, h, 0), f(self, t, 0));
1159                self.intern(Node::Seq(h2, t2))
1160            }
1161            other => unreachable!("not a LOGOS node with children: {other:?}"),
1162        }
1163    }
1164
1165    /// Adds `d` to every loose index `≥ s`.
1166    pub fn lift_loose(&mut self, e: ExprId, s: u32, d: u32) -> ExprId {
1167        if d == 0 || self.lbr_of(e) <= s {
1168            return e;
1169        }
1170        self.lift_memo.reset();
1171        self.lift_go(e, s, d)
1172    }
1173
1174    fn lift_go(&mut self, e: ExprId, s: u32, d: u32) -> ExprId {
1175        if self.lbr_of(e) <= s {
1176            return e;
1177        }
1178        if let Some(r) = self.lift_memo.get(e, s) {
1179            return r;
1180        }
1181        let r = match self.node(e) {
1182            Node::BVar(i) => self.bvar(i + d),
1183            Node::App(f, a) => {
1184                let (f2, a2) = (self.lift_go(f, s, d), self.lift_go(a, s, d));
1185                self.app(f2, a2)
1186            }
1187            Node::Lam(t, b) => {
1188                let (t2, b2) = (self.lift_go(t, s, d), self.lift_go(b, s + 1, d));
1189                self.lam(t2, b2)
1190            }
1191            Node::Pi(t, b) => {
1192                let (t2, b2) = (self.lift_go(t, s, d), self.lift_go(b, s + 1, d));
1193                self.pi(t2, b2)
1194            }
1195            Node::Let(t, v, b) => {
1196                let t2 = self.lift_go(t, s, d);
1197                let v2 = self.lift_go(v, s, d);
1198                let b2 = self.lift_go(b, s + 1, d);
1199                self.let_(t2, v2, b2)
1200            }
1201            Node::Proj(n, i, x) => {
1202                let x2 = self.lift_go(x, s, d);
1203                self.proj(n, i, x2)
1204            }
1205            Node::Match(..) | Node::Fix(_) | Node::MutualFix(..) | Node::Seq(..) => {
1206                self.lift_logos(e, s, d)
1207            }
1208            _ => e,
1209        };
1210        self.lift_memo.insert(e, s, r);
1211        r
1212    }
1213
1214    /// Replaces `FVar(fvars[i])` by the bound variable of the `i`-th of `fvars.len()` enclosing
1215    /// binders (the last free variable becomes `BVar(0)`), as `mkForall`/`mkLambda` require.
1216    pub fn abstract_fvars(&mut self, e: ExprId, fvars: &[u32]) -> ExprId {
1217        if fvars.is_empty() || !self.has_fvar(e) {
1218            return e;
1219        }
1220        self.abs_memo.reset();
1221        self.abs_go(e, 0, fvars)
1222    }
1223
1224    fn abs_go(&mut self, e: ExprId, off: u32, fvars: &[u32]) -> ExprId {
1225        if !self.has_fvar(e) {
1226            return e;
1227        }
1228        if let Some(r) = self.abs_memo.get(e, off) {
1229            return r;
1230        }
1231        let r = match self.node(e) {
1232            Node::FVar(x) => match fvars.iter().rposition(|f| *f == x) {
1233                Some(i) => self.bvar(off + (fvars.len() - 1 - i) as u32),
1234                None => e,
1235            },
1236            Node::App(f, a) => {
1237                let (f2, a2) = (self.abs_go(f, off, fvars), self.abs_go(a, off, fvars));
1238                self.app(f2, a2)
1239            }
1240            Node::Lam(t, b) => {
1241                let (t2, b2) = (self.abs_go(t, off, fvars), self.abs_go(b, off + 1, fvars));
1242                self.lam(t2, b2)
1243            }
1244            Node::Pi(t, b) => {
1245                let (t2, b2) = (self.abs_go(t, off, fvars), self.abs_go(b, off + 1, fvars));
1246                self.pi(t2, b2)
1247            }
1248            Node::Let(t, v, b) => {
1249                let t2 = self.abs_go(t, off, fvars);
1250                let v2 = self.abs_go(v, off, fvars);
1251                let b2 = self.abs_go(b, off + 1, fvars);
1252                self.let_(t2, v2, b2)
1253            }
1254            Node::Proj(n, i, x) => {
1255                let x2 = self.abs_go(x, off, fvars);
1256                self.proj(n, i, x2)
1257            }
1258            Node::Match(..) | Node::Fix(_) | Node::MutualFix(..) | Node::Seq(..) => {
1259                self.abs_logos(e, off, fvars)
1260            }
1261            _ => e,
1262        };
1263        self.abs_memo.insert(e, off, r);
1264        r
1265    }
1266
1267    /// Substitutes universe parameters `ps[i] := us[i]` throughout `e`.
1268    pub fn instantiate_lparams(&mut self, e: ExprId, ps: &[NameId], us: &[LevelId]) -> ExprId {
1269        if ps.is_empty() || !self.has_lparam(e) {
1270            return e;
1271        }
1272        self.lp_memo.reset();
1273        self.lp_go(e, ps, us)
1274    }
1275
1276    fn lp_go(&mut self, e: ExprId, ps: &[NameId], us: &[LevelId]) -> ExprId {
1277        if !self.has_lparam(e) {
1278            return e;
1279        }
1280        if let Some(r) = self.lp_memo.get(e, 0) {
1281            return r;
1282        }
1283        let r = match self.node(e) {
1284            Node::Sort(l) => {
1285                let l2 = self.levels.instantiate(l, ps, us);
1286                self.sort(l2)
1287            }
1288            Node::Const(n, ls) => {
1289                let old: Vec<LevelId> = self.levels.get_list(ls).to_vec();
1290                let new: Vec<LevelId> = old
1291                    .iter()
1292                    .map(|l| self.levels.instantiate(*l, ps, us))
1293                    .collect();
1294                self.cnst_levels(n, &new)
1295            }
1296            Node::App(f, a) => {
1297                let (f2, a2) = (self.lp_go(f, ps, us), self.lp_go(a, ps, us));
1298                self.app(f2, a2)
1299            }
1300            Node::Lam(t, b) => {
1301                let (t2, b2) = (self.lp_go(t, ps, us), self.lp_go(b, ps, us));
1302                self.lam(t2, b2)
1303            }
1304            Node::Pi(t, b) => {
1305                let (t2, b2) = (self.lp_go(t, ps, us), self.lp_go(b, ps, us));
1306                self.pi(t2, b2)
1307            }
1308            Node::Let(t, v, b) => {
1309                let t2 = self.lp_go(t, ps, us);
1310                let v2 = self.lp_go(v, ps, us);
1311                let b2 = self.lp_go(b, ps, us);
1312                self.let_(t2, v2, b2)
1313            }
1314            Node::Proj(n, i, x) => {
1315                let x2 = self.lp_go(x, ps, us);
1316                self.proj(n, i, x2)
1317            }
1318            Node::Match(..) | Node::Fix(_) | Node::MutualFix(..) | Node::Seq(..) => {
1319                self.lp_logos(e, ps, us)
1320            }
1321            _ => e,
1322        };
1323        self.lp_memo.insert(e, 0, r);
1324        r
1325    }
1326
1327    /// Does any subterm satisfy `pred`? Each distinct node is visited once.
1328    pub fn any_node(&self, e: ExprId, pred: &mut dyn FnMut(&Store, ExprId) -> bool) -> bool {
1329        let mut seen = rustc_hash::FxHashSet::default();
1330        let mut stack = vec![e];
1331        while let Some(x) = stack.pop() {
1332            if !seen.insert(x) {
1333                continue;
1334            }
1335            if pred(self, x) {
1336                return true;
1337            }
1338            match self.node(x) {
1339                Node::App(a, b) | Node::Lam(a, b) | Node::Pi(a, b) | Node::Seq(a, b) => {
1340                    stack.push(a);
1341                    stack.push(b);
1342                }
1343                Node::Let(a, b, c) | Node::Match(a, b, c) => {
1344                    stack.push(a);
1345                    stack.push(b);
1346                    stack.push(c);
1347                }
1348                Node::Proj(_, _, a) | Node::Fix(a) | Node::MutualFix(a, _) => stack.push(a),
1349                _ => {}
1350            }
1351        }
1352        false
1353    }
1354
1355    /// Does `e` mention constant `n`?
1356    pub fn mentions_const(&self, e: ExprId, names: &[NameId]) -> bool {
1357        self.any_node(
1358            e,
1359            &mut |s, x| matches!(s.node(x), Node::Const(c, _) if names.contains(&c)),
1360        )
1361    }
1362}
1363
1364#[cfg(test)]
1365#[path = "../../../logicaffeine_tests/unit/logicaffeine_kernel/dag/expr/tests.rs"]
1366mod tests;