logicaffeine_kernel/dag/relevance.rs
1//! Argument relevance: which arguments of a definition cannot change what it computes.
2//!
3//! When two applications of the same definition are compared, Lean's algorithm first tries the
4//! arguments pairwise and only unfolds on failure. Comparing an argument the definition never
5//! uses can cost arbitrarily much (Arena `perf/discarded-argument`: two numerals evaluated in
6//! Θ(n²) only to be thrown away). An argument position `i` is *ignorable* when either
7//!
8//! * the definition's value, after its leading `fun`s, never mentions parameter `i`, and the rest
9//! of its type does not depend on it — unfolding substitutes into a body with no occurrence of
10//! the parameter, so `c … aᵢ …` and `c … bᵢ …` unfold to the same term whenever the other
11//! arguments agree; or
12//! * the parameter's type is a proposition — two proofs of definitionally equal propositions are
13//! definitionally equal (proof irrelevance), and the propositions are equal whenever the earlier
14//! arguments are.
15//!
16//! Both facts are properties of the settled constant (and, for proof-ness, its universe
17//! arguments), so they are computed once and shared by every declaration.
18
19use super::error::KResult;
20use super::expr::{ExprId, Node};
21use super::level::LevelsId;
22use super::name::NameId;
23use super::tc::Kernel;
24use rustc_hash::FxHashMap as FxMap;
25
26/// Positions beyond this are always compared.
27pub const MAX_TRACKED: usize = 64;
28
29/// Relevance facts, keyed on settled constants only.
30#[derive(Clone, Default)]
31pub struct Cache {
32 absent: FxMap<NameId, u64>,
33 proof: FxMap<(NameId, LevelsId), u64>,
34 signatures: FxMap<(NameId, LevelsId), Signature>,
35 /// Signatures being computed: one asked for again while its own computation runs (a
36 /// reduction inside the telescope's walk that compares applications of the constant) is
37 /// unknown for that inner question.
38 pub(crate) computing: rustc_hash::FxHashSet<(NameId, LevelsId)>,
39}
40
41/// What a settled constant's type says about its applications, at one universe instantiation.
42///
43/// The type's telescope `Π (x₀ : A₀) … (xₙ₋₁ : Aₙ₋₁), B`, walked once under fresh variables,
44/// gives the universe `uᵢ` of every binder type and `v` of the result. Universes do not depend on
45/// terms, so these are the universes of every application's parts too: an argument at position
46/// `i` is a proof exactly when `uᵢ` is zero, and an application to `k ≤ n` arguments, whose type
47/// is `Π (xₖ : Aₖ) … , B`, lives in `imax(uₖ, … imax(uₙ₋₁, v))` — zero exactly when `v` is. So
48/// whether an application is a proof is read off its head and its argument count, without
49/// inferring a type. Past the telescope — a result type that becomes a Π only once arguments are
50/// substituted — nothing is known.
51#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
52pub struct Signature {
53 proof_args: u64,
54 /// Bit `k`: an application to `k` arguments is a proof (meaningful where `known` has bit `k`).
55 proofs: u64,
56 known: u64,
57 /// The telescope's length when it ends within tracking, and — when its result `B` is a sort —
58 /// whether that sort is `Prop` (the full application is then a proposition).
59 full: Option<(u32, Option<bool>)>,
60}
61
62impl Signature {
63 pub(crate) fn new(
64 proof_args: u64,
65 proofs: u64,
66 known: u64,
67 full: Option<(u32, Option<bool>)>,
68 ) -> Signature {
69 Signature {
70 proof_args,
71 proofs,
72 known,
73 full,
74 }
75 }
76
77 /// The argument positions (below [`MAX_TRACKED`]) whose binder type is a proposition.
78 pub fn proof_args(&self) -> u64 {
79 self.proof_args
80 }
81
82 /// Whether an application to `k` arguments is a proof, if the telescope says.
83 #[inline]
84 pub fn is_proof(&self, k: usize) -> Option<bool> {
85 (k < MAX_TRACKED && (self.known >> k) & 1 == 1).then(|| (self.proofs >> k) & 1 == 1)
86 }
87
88 /// Whether an application to `k` arguments is a proposition, if the telescope says: only a
89 /// full application whose result is a sort is a type at all.
90 #[inline]
91 pub fn is_proposition(&self, k: usize) -> Option<bool> {
92 match self.full {
93 Some((n, p)) if n as usize == k => p,
94 _ => None,
95 }
96 }
97}
98
99impl Kernel {
100 /// The argument positions of `c.{ls}` whose arguments need not be compared (see the module
101 /// documentation). Zero for anything that is not a settled, unfoldable definition.
102 pub fn ignorable_args(&mut self, c: NameId, ls: LevelsId) -> KResult<u64> {
103 let Some(absent) = self.absent_mask(c, ls) else {
104 return Ok(0);
105 };
106 let proof = match self.relevance.proof.get(&(c, ls)) {
107 Some(m) => *m,
108 None => {
109 let k = self.env.get(c).cloned().expect("an unfoldable definition");
110 let ty = self.instantiate_type_lparams(&k, ls);
111 let m = self.proof_args(ty)?;
112 self.relevance.proof.insert((c, ls), m);
113 m
114 }
115 };
116 Ok(absent | proof)
117 }
118
119 /// The positions `c`'s value never mentions, or `None` when `c.{ls}` is not a settled,
120 /// unfoldable definition (whose arguments must then all be compared).
121 pub fn absent_mask(&mut self, c: NameId, ls: LevelsId) -> Option<u64> {
122 if !self.env.is_settled(c) {
123 return None;
124 }
125 let k = self.env.get(c)?.clone();
126 let value = k.delta_value()?;
127 if self.s.levels.get_list(ls).len() != k.lparams.len() {
128 return None;
129 }
130 Some(match self.relevance.absent.get(&c) {
131 Some(m) => *m,
132 None => {
133 let m = self.absent_args(k.ty, value);
134 self.relevance.absent.insert(c, m);
135 m
136 }
137 })
138 }
139
140 /// The signature of `c.{ls}`, if some engine has already computed it.
141 #[inline]
142 pub(crate) fn known_signature(&self, c: NameId, ls: LevelsId) -> Option<Signature> {
143 self.relevance.signatures.get(&(c, ls)).copied()
144 }
145
146 /// Records the signature of the settled constant `c.{ls}` (and its proof positions).
147 pub(crate) fn note_signature(&mut self, c: NameId, ls: LevelsId, s: Signature) {
148 self.relevance.signatures.insert((c, ls), s);
149 self.relevance.proof.entry((c, ls)).or_insert(s.proof_args);
150 }
151
152 /// Parameters the value never mentions and the rest of the type does not depend on.
153 fn absent_args(&mut self, ty: ExprId, value: ExprId) -> u64 {
154 let mut body = value;
155 let mut arity = 0usize;
156 while let Node::Lam(_, b) = self.s.node(body) {
157 if arity == MAX_TRACKED {
158 break;
159 }
160 body = b;
161 arity += 1;
162 }
163 if arity == 0 {
164 return 0;
165 }
166 let used = loose_mask(self, body, 0, &mut FxMap::default());
167 let mut absent = 0u64;
168 let mut rest = ty;
169 for i in 0..arity {
170 let Node::Pi(_, cod) = self.s.node(rest) else {
171 break;
172 };
173 let unused = (used >> (arity - 1 - i)) & 1 == 0;
174 let independent = !mentions_bvar0(self, cod);
175 if unused && independent {
176 absent |= 1 << i;
177 }
178 rest = cod;
179 }
180 absent
181 }
182
183 /// Parameters whose type is a proposition, over the instantiated type's telescope.
184 fn proof_args(&mut self, ty: ExprId) -> KResult<u64> {
185 let mut mask = 0u64;
186 let mut cur = ty;
187 for i in 0..MAX_TRACKED {
188 let w = self.whnf(cur)?;
189 let Node::Pi(d, cod) = self.s.node(w) else {
190 break;
191 };
192 if self.is_prop(d)? {
193 mask |= 1 << i;
194 }
195 let fv = self.push_local(d, None);
196 cur = self.s.instantiate1(cod, fv);
197 }
198 Ok(mask)
199 }
200}
201
202/// The loose de Bruijn indices (relative to `depth`, below 64) occurring in `e`.
203fn loose_mask(k: &Kernel, e: ExprId, depth: u32, memo: &mut FxMap<(ExprId, u32), u64>) -> u64 {
204 if k.s.loose_bvar_range(e) <= depth {
205 return 0;
206 }
207 if let Some(m) = memo.get(&(e, depth)) {
208 return *m;
209 }
210 let m = match k.s.node(e) {
211 Node::BVar(i) => {
212 let j = i - depth;
213 if (j as usize) < MAX_TRACKED {
214 1u64 << j
215 } else {
216 0
217 }
218 }
219 Node::App(a, b) => loose_mask(k, a, depth, memo) | loose_mask(k, b, depth, memo),
220 Node::Lam(a, b) | Node::Pi(a, b) => {
221 loose_mask(k, a, depth, memo) | loose_mask(k, b, depth + 1, memo)
222 }
223 Node::Let(a, b, c) => {
224 loose_mask(k, a, depth, memo)
225 | loose_mask(k, b, depth, memo)
226 | loose_mask(k, c, depth + 1, memo)
227 }
228 Node::Proj(_, _, a) => loose_mask(k, a, depth, memo),
229 _ => 0,
230 };
231 memo.insert((e, depth), m);
232 m
233}
234
235/// Does `e` mention the innermost enclosing binder (`BVar(0)` at depth 0)?
236fn mentions_bvar0(k: &Kernel, e: ExprId) -> bool {
237 loose_mask(k, e, 0, &mut FxMap::default()) & 1 == 1
238}