1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! Deterministic per-declaration resource fuel (C16).
//!
//! One declaration must never be able to OOM the process: Lean core's
//! `Int.add_mul_ediv_right` (Init corpus) legally expands to multi-hundred-
//! million-node intermediate terms inside `whnf`/`is_def_eq`, and with a
//! deep-copy `Expr` representation that is a multi-GiB peak. This module
//! provides a *deterministic* budget on the term-duplication work a single
//! declaration may perform, measured in **`Expr` nodes cloned or rebuilt**:
//!
//! * [`Expr::clone`](crate::Expr) charges one unit per node cloned
//! (`instantiate` argument copies, cache result clones, `mk_app` rebuilds);
//! * the substitution/lift builders (`subst::instantiate*`, `shift_bvars`,
//! `abstract_expr`, `parallel_subst`, `instantiate::{instantiate_rev,
//! instantiate_many, instantiate_type_lparams}`,
//! `expr_util::lift_loose_bvars`) charge one unit per node **visited**, so
//! spine nodes rebuilt without passing through `clone` are bounded too
//! (the C16b gap: a `let`-tower duplicating its value at every level
//! allocated multi-GiB while burning almost no clone-fuel).
//!
//! ## Contract
//!
//! * The counter is thread-local; a checking thread calls
//! [`set_budget`](crate::fuel::set_budget)`(Some(n))` before a declaration and inspects
//! [`is_exhausted`](crate::fuel::is_exhausted) after. [`Expr::clone`](crate::Expr) charges one unit
//! per node cloned.
//! * When the budget hits zero the flag latches. Reduction, definitional
//! equality and type inference then *degrade conservatively*: `whnf`
//! returns its input unchanged (a stuck term — always sound), `is_def_eq`
//! decides only syntactic equality (never a wrong accept), and
//! `infer_type` aborts with a typed error (C16b — terms are never
//! truncated; the in-flight substitution completes exactly and the next
//! inference step aborts). Positive judgements reached after exhaustion
//! are therefore still exact; failures are reported by the caller as a
//! **named resource limit**, not as a rejection.
//! * Determinism: the same declaration checked with the same budget always
//! exhausts at the same point — the count depends only on the reduction
//! path taken, never on wall-clock time or allocator state.
//!
//! The default is *unlimited* ([`set_budget`](crate::fuel::set_budget)`(None)`): plain kernel users
//! (tests, embedders) are unaffected unless they opt in.
use Cell;
thread_local!
/// Reset the fuel for a new unit of work (typically: one declaration).
///
/// `Some(n)` sets a budget of `n` cloned nodes; `None` is unlimited. Both
/// clear the exhaustion latch and the usage counter.
/// Charge `n` units of fuel. Saturates at zero and latches [`is_exhausted`].
/// Whether the current thread's per-declaration resource budget is spent —
/// either the deterministic node-construction [`fuel`](self) is exhausted, OR
/// the wall-clock [`deadline`](crate::deadline) has expired.
///
/// Folding the deadline in here means every place the kernel already degrades
/// conservatively on fuel exhaustion (`whnf` returns its input, `is_def_eq`
/// decides syntactically, `infer_type` aborts) automatically also honours the
/// deadline, so a non-fuel-metered runaway loop is cut off and reported as a
/// named resource limit instead of hanging. The deadline check samples the
/// clock only periodically; with no deadline armed it is a single `Cell` read.
/// Total units charged since the last [`set_budget`] (for reporting and
/// budget calibration).