1use super::logos_env::{self, Declaration, Replayed};
27use super::tc::Kernel;
28use crate::context::{Context, LogEntry};
29use crate::prelude::StandardLibrary;
30use std::cell::{Cell, RefCell};
31use std::collections::{BTreeSet, HashSet};
32use std::sync::Arc;
33
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum Route {
37 Current,
39 Advanced,
41 Rebased,
44 Replayed,
46}
47
48#[derive(Debug)]
50pub struct Synced {
51 pub route: Route,
52 pub verdicts: Vec<Replayed>,
53 pub downstream: BTreeSet<String>,
56}
57
58#[derive(Clone)]
59struct Mirror {
60 kernel: Kernel,
61 head: Option<Arc<LogEntry>>,
63 held: HashSet<String>,
65 awaited: HashSet<String>,
68 refused: HashSet<String>,
70}
71
72thread_local! {
73 static MIRROR: RefCell<Option<Mirror>> = const { RefCell::new(None) };
75 static BASE: RefCell<Option<Mirror>> = const { RefCell::new(None) };
77 static CORE: RefCell<Option<CoreThread>> = const { RefCell::new(None) };
79 static ON_CORE: Cell<bool> = const { Cell::new(false) };
81}
82
83const CORE_STACK: usize = 1 << 30;
85
86struct CoreThread {
89 queue: std::sync::mpsc::Sender<Box<dyn FnOnce() + Send>>,
90}
91
92fn start_core() -> CoreThread {
93 let (queue, jobs) = std::sync::mpsc::channel::<Box<dyn FnOnce() + Send>>();
94 std::thread::Builder::new()
95 .name("logos-core-mirror".to_string())
96 .stack_size(CORE_STACK)
97 .spawn(move || {
98 ON_CORE.with(|on| on.set(true));
99 for job in jobs {
100 job();
101 }
102 })
103 .expect("a core thread starts");
104 CoreThread { queue }
105}
106
107fn on_core<R: Send + 'static>(job: impl FnOnce() -> R + Send + 'static) -> R {
110 if ON_CORE.with(Cell::get) {
111 return job();
112 }
113 let (answer, answered) = std::sync::mpsc::channel();
114 let job: Box<dyn FnOnce() + Send> = Box::new(move || {
115 let _ = answer.send(std::panic::catch_unwind(std::panic::AssertUnwindSafe(job)));
116 });
117 CORE.with(|cell| {
118 cell.borrow_mut()
119 .get_or_insert_with(start_core)
120 .queue
121 .send(job)
122 .expect("the core thread runs")
123 });
124 match answered.recv().expect("the core thread answers every job") {
125 Ok(result) => result,
126 Err(panic) => std::panic::resume_unwind(panic),
127 }
128}
129
130pub fn sync(ctx: &Context) -> Synced {
132 let ctx = ctx.clone();
133 on_core(move || {
134 let (mirror, synced) = advance(MIRROR.with(|cell| cell.borrow_mut().take()), &ctx);
135 MIRROR.with(|cell| *cell.borrow_mut() = Some(mirror));
136 synced
137 })
138}
139
140pub fn with<R: Send + 'static>(
143 ctx: &Context,
144 f: impl FnOnce(&mut Kernel) -> R + Send + 'static,
145) -> (Synced, R) {
146 let ctx = ctx.clone();
147 on_core(move || {
148 let (mut mirror, synced) = advance(MIRROR.with(|cell| cell.borrow_mut().take()), &ctx);
149 let result = f(&mut mirror.kernel);
150 MIRROR.with(|cell| *cell.borrow_mut() = Some(mirror));
151 (synced, result)
152 })
153}
154
155fn same(a: Option<&Arc<LogEntry>>, b: Option<&Arc<LogEntry>>) -> bool {
156 match (a, b) {
157 (Some(a), Some(b)) => Arc::ptr_eq(a, b),
158 (None, None) => true,
159 _ => false,
160 }
161}
162
163fn walk<'a>(
166 mut at: Option<&'a Arc<LogEntry>>,
167 suffix: &mut Vec<&'a LogEntry>,
168 stop: impl Fn(Option<&Arc<LogEntry>>) -> bool,
169) -> Option<Option<&'a Arc<LogEntry>>> {
170 loop {
171 if stop(at) {
172 return Some(at);
173 }
174 let entry = at?;
175 suffix.push(entry);
176 at = entry.prev.as_ref();
177 }
178}
179
180fn advance(mirror: Option<Mirror>, ctx: &Context) -> (Mirror, Synced) {
181 let base_head = StandardLibrary::registered().map(Context::admissions);
182 let at_base = |at: Option<&Arc<LogEntry>>| base_head.is_some_and(|h| same(at, h));
183 let mut suffix: Vec<&LogEntry> = Vec::new();
184 let mut at = ctx.admissions();
185 if let Some(mut mirror) = mirror {
186 let Some(stopped) = walk(at, &mut suffix, |at| {
187 same(at, mirror.head.as_ref()) || at_base(at)
188 }) else {
189 return replay(ctx);
190 };
191 if same(stopped, mirror.head.as_ref()) {
192 if suffix.is_empty() {
193 mirror.kernel.follow(ctx);
194 return (
195 mirror,
196 Synced {
197 route: Route::Current,
198 verdicts: Vec::new(),
199 downstream: BTreeSet::new(),
200 },
201 );
202 }
203 if let Ok((mirror, verdicts, downstream)) = extend(mirror, ctx, &suffix) {
204 return (
205 mirror,
206 Synced {
207 route: Route::Advanced,
208 verdicts,
209 downstream,
210 },
211 );
212 }
213 }
214 at = stopped;
215 }
216 if walk(at, &mut suffix, at_base).is_none() {
217 return replay(ctx);
218 }
219 let (base, built) = base();
220 let route = if built.is_some() {
221 Route::Replayed
222 } else {
223 Route::Rebased
224 };
225 match extend(base, ctx, &suffix) {
226 Ok((mirror, more, more_downstream)) => {
227 let (mut verdicts, mut downstream) = built.unwrap_or_default();
228 verdicts.extend(more);
229 downstream.extend(more_downstream);
230 (
231 mirror,
232 Synced {
233 route,
234 verdicts,
235 downstream,
236 },
237 )
238 }
239 Err(()) => replay(ctx),
240 }
241}
242
243fn base() -> (Mirror, Option<(Vec<Replayed>, BTreeSet<String>)>) {
245 BASE.with(|cell| {
246 let mut slot = cell.borrow_mut();
247 if let Some(base) = slot.as_ref() {
248 return (base.clone(), None);
249 }
250 let ctx = StandardLibrary::registered().expect("a context met the standard library's log");
251 let (base, synced) = replay(ctx);
252 *slot = Some(base.clone());
253 (base, Some((synced.verdicts, synced.downstream)))
254 })
255}
256
257fn replay(ctx: &Context) -> (Mirror, Synced) {
258 let (kernel, items, verdicts) = logos_env::replay_items(ctx);
259 let mut mirror = Mirror {
260 kernel,
261 head: ctx.admissions().cloned(),
262 held: HashSet::new(),
263 awaited: HashSet::new(),
264 refused: HashSet::new(),
265 };
266 let downstream = mirror.hold(&items, &verdicts);
267 (
268 mirror,
269 Synced {
270 route: Route::Replayed,
271 verdicts,
272 downstream,
273 },
274 )
275}
276
277fn extend(
280 mut mirror: Mirror,
281 ctx: &Context,
282 suffix: &[&LogEntry],
283) -> Result<(Mirror, Vec<Replayed>, BTreeSet<String>), ()> {
284 if ctx.canonical_numerals() != mirror.kernel.numerals() {
285 return Err(());
286 }
287 let mut changed: BTreeSet<String> = BTreeSet::new();
288 for entry in suffix.iter().rev() {
289 if mirror.held.contains(&entry.name) || mirror.awaited.contains(&entry.name) {
290 return Err(());
291 }
292 if entry.removed {
293 continue;
294 }
295 let owner = ctx
296 .constructor_inductive(&entry.name)
297 .unwrap_or(&entry.name);
298 if mirror.held.contains(owner) {
299 return Err(());
300 }
301 changed.insert(owner.to_string());
302 }
303 let items = logos_env::items_named(ctx, &changed);
304 mirror.kernel.follow(ctx);
305 let verdicts = logos_env::admit_items(&mut mirror.kernel, ctx, &items);
306 let downstream = mirror.hold(&items, &verdicts);
307 mirror.head = ctx.admissions().cloned();
308 Ok((mirror, verdicts, downstream))
309}
310
311impl Mirror {
312 fn hold(&mut self, items: &[Declaration], verdicts: &[Replayed]) -> BTreeSet<String> {
316 for item in items {
317 self.held
318 .extend(item.names().into_iter().map(str::to_string));
319 }
320 let refused: HashSet<&str> = verdicts
321 .iter()
322 .filter(|v| v.verdict.is_err())
323 .map(|v| v.name.as_str())
324 .collect();
325 let mut downstream = BTreeSet::new();
326 for item in items {
327 let names = item.names();
328 if names.iter().any(|n| refused.contains(n)) {
329 let globals = logos_env::item_globals(item);
330 if globals
331 .iter()
332 .any(|g| self.refused.contains(g) && !names.contains(&g.as_str()))
333 {
334 downstream.extend(names.iter().map(|n| n.to_string()));
335 }
336 self.refused.extend(names.iter().map(|n| n.to_string()));
337 self.awaited
338 .extend(globals.into_iter().filter(|n| !self.held.contains(n)));
339 }
340 }
341 self.awaited.retain(|n| !self.held.contains(n));
342 downstream
343 }
344}
345
346#[cfg(feature = "shadow")]
349pub(crate) fn shadow_enter(ctx: &Context) -> Entry {
350 if compares(ctx) {
351 sync(ctx);
352 }
353 Entry(ctx.admissions().cloned())
354}
355
356#[cfg(feature = "shadow")]
359pub(crate) struct Entry(Option<Arc<LogEntry>>);
360
361#[cfg(feature = "shadow")]
363fn registered_since<'a>(ctx: &'a Context, entry: &Entry) -> HashSet<&'a str> {
364 let mut suffix = Vec::new();
365 walk(ctx.admissions(), &mut suffix, |at| {
366 same(at, entry.0.as_ref())
367 });
368 suffix
369 .into_iter()
370 .filter(|e| !e.removed)
371 .map(|e| e.name.as_str())
372 .collect()
373}
374
375#[cfg(feature = "shadow")]
379fn compares(ctx: &Context) -> bool {
380 !crate::prelude::registering() && !ctx.has_local_bindings()
381}
382
383#[cfg(feature = "shadow")]
388fn admission_disagreements<'a>(
389 verdicts: impl IntoIterator<Item = &'a Replayed>,
390 pending: &HashSet<&str>,
391 downstream: &BTreeSet<String>,
392) -> Vec<String> {
393 verdicts
394 .into_iter()
395 .filter_map(|v| match (&v.verdict, pending.contains(v.name.as_str())) {
396 (Ok(()), false) | (Err(_), true) => None,
397 (Err(_), false) if downstream.contains(&v.name) => None,
398 (Err(e), false) => Some(format!(
399 "`{}`: the Term kernel admits it, the core refuses it: {e}",
400 v.name
401 )),
402 (Ok(()), true) => Some(format!(
403 "`{}`: the Term kernel holds it pending, the core admits it",
404 v.name
405 )),
406 })
407 .collect()
408}
409
410#[cfg(feature = "shadow")]
418pub(crate) fn shadow(
419 ctx: &Context,
420 entry: &Entry,
421 refused: Option<&crate::error::KernelError>,
422 candidate: impl FnOnce() -> Vec<Declaration>,
423) {
424 if !compares(ctx) {
425 return;
426 }
427 match refused {
428 None => {
429 let synced = sync(ctx);
430 let registered = registered_since(ctx, entry);
431 let pending: HashSet<&str> = ctx
432 .pending_obligations()
433 .into_iter()
434 .map(|(n, _)| n)
435 .collect();
436 let decided = synced
437 .verdicts
438 .iter()
439 .filter(|v| registered.contains(v.name.as_str()));
440 let disagreements = admission_disagreements(decided, &pending, &synced.downstream);
441 if !disagreements.is_empty() {
442 panic!(
443 "shadow mode: the kernels disagree ({:?}):\n{}",
444 synced.route,
445 disagreements.join("\n")
446 );
447 }
448 }
449 Some(error) if theory_refusal(error) => {
450 let block = candidate();
451 let names: Vec<String> = block
452 .iter()
453 .flat_map(|d| d.names().into_iter().map(str::to_string))
454 .collect();
455 let judged = ctx.clone();
456 let (_, verdict) = with(ctx, move |k| logos_env::judge(k, &judged, &block));
457 if verdict.is_ok() {
458 panic!(
459 "shadow mode: the Term kernel refuses {}, the core admits it.\nThe Term kernel: {error}",
460 names.join(", ")
461 );
462 }
463 }
464 Some(_) => {}
465 }
466}
467
468#[cfg(feature = "shadow")]
470fn theory_refusal(error: &crate::error::KernelError) -> bool {
471 use crate::error::KernelError::*;
472 matches!(
473 error,
474 UnboundVariable(_)
475 | NotAFunction(_)
476 | NotAType(_)
477 | TypeMismatch { .. }
478 | NotAnInductive(_)
479 | InvalidMotive(_)
480 | WrongNumberOfCases { .. }
481 | InvalidDeclaration(_)
482 | TerminationViolation { .. }
483 | PositivityViolation { .. }
484 )
485}
486
487#[cfg(all(test, feature = "shadow"))]
488#[path = "../../../logicaffeine_tests/unit/logicaffeine_kernel/dag/mirror/shadow_controls.rs"]
489mod shadow_controls;