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
//! Per-declaration wall-clock deadline (M4 hardening).
//!
//! The deterministic per-declaration [`fuel`](crate::fuel) bounds *node
//! construction*, which caps the memory and time of any declaration whose cost
//! shows up as newly-built `Expr` material. But the Mathlib `CategoryTheory`
//! corpus exposed a reduction/def-eq loop that makes progress WITHOUT
//! constructing nodes (e.g. re-deriving the same universe/def-eq obligations),
//! so it never exhausts fuel and runs unboundedly — a single declaration
//! (`CategoryTheory.FreeBicategory`…) pinned one core at 100 % indefinitely.
//!
//! This module adds a thread-local **wall-clock** deadline as a backstop.
//! [`is_expired`](crate::deadline::is_expired) is OR-ed into [`fuel::is_exhausted`](crate::fuel::is_exhausted),
//! so every place the kernel already degrades on fuel exhaustion — `whnf`
//! returns its input, `is_def_eq` decides syntactically (a conservative
//! `false`), `infer_type` aborts — now ALSO honours the deadline, and the caller
//! reports the declaration as a NAMED resource limit. It never changes a
//! positive or negative verdict; it only moves an otherwise-unbounded
//! declaration into the resource-limit bucket instead of hanging.
//!
//! ## Determinism caveat
//!
//! Unlike fuel (a node count), a wall-clock deadline is machine- and
//! load-dependent: WHICH declarations hit it varies with hardware. That is the
//! price of bounding computations the deterministic meter cannot see, and it is
//! disclosed wherever the time budget is set. Soundness is unaffected — an
//! expired declaration is degraded exactly like a fuel-exhausted one, so it is
//! always a named `unsupported`, never a wrong accept or reject.
//!
//! The default is *no deadline* ([`set_deadline`](crate::deadline::set_deadline)`(None)`): plain kernel users
//! (tests, embedders) are unaffected unless they opt in.
use crateInstant;
use Duration;
use Cell;
thread_local!
/// Sample the clock only every 1024 [`is_expired`](crate::deadline::is_expired) calls. The hot reduction
/// loops call it once per step, so the amortised cost is ~one `Instant::now`
/// per 1024 steps while a runaway loop is still cut off within 1024 steps of
/// the deadline.
const CHECK_MASK: u32 = 0x3FF;
/// Reset the deadline for a new unit of work (typically: one declaration).
///
/// `Some(budget)` arms a wall-clock budget starting now; `None` disarms it.
/// Both clear the expiry latch and the sampling counter.
/// Whether the current thread's wall-clock budget has been exceeded since the
/// last [`set_deadline`]. Latches on the first observed expiry. The clock is
/// sampled only periodically (see `CHECK_MASK`) to keep hot-loop cost
/// negligible; with no deadline armed it is a single `Cell` read.
/// Whether the deadline latched at any point since the last [`set_deadline`]
/// (without sampling the clock). Used by callers to attribute a degraded
/// verdict to the time budget.