#![allow(clippy::new_without_default)]
use std::{
cell::RefCell,
fmt::Debug,
mem::{self},
num::NonZeroUsize,
rc::Rc,
thread::panicking,
};
use awint::{
awint_dag::{
epoch::{EpochCallback, EpochKey, _get_epoch_stack},
triple_arena::{ptr_struct, Arena},
Lineage, Location, Op, PState,
},
bw, dag,
};
use crate::{
ensemble::{Delay, Ensemble, Value},
Error, EvalAwi,
};
#[derive(Debug)]
pub struct Assertions {
pub bits: Vec<EvalAwi>,
}
impl Assertions {
pub fn new() -> Self {
Self { bits: vec![] }
}
}
impl Default for Assertions {
fn default() -> Self {
Self::new()
}
}
ptr_struct!(PEpochShared);
#[derive(Debug)]
pub struct PerEpochShared {
pub states_inserted: Vec<PState>,
pub assertions: Assertions,
}
impl PerEpochShared {
pub fn new() -> Self {
Self {
states_inserted: vec![],
assertions: Assertions::new(),
}
}
}
pub struct EpochData {
pub epoch_key: Option<EpochKey>,
pub ensemble: Ensemble,
pub responsible_for: Arena<PEpochShared, PerEpochShared>,
}
impl Drop for EpochData {
fn drop(&mut self) {
for (_, mut shared) in self.responsible_for.drain() {
for eval_awi in shared.assertions.bits.drain(..) {
mem::forget(eval_awi);
}
}
}
}
impl Debug for EpochData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EpochData")
.field("epoch_key", &self.epoch_key)
.field("responsible_for.len()", &self.responsible_for.len())
.finish()
}
}
#[derive(Clone)]
pub struct EpochShared {
pub epoch_data: Rc<RefCell<EpochData>>,
pub p_self: PEpochShared,
}
impl Debug for EpochShared {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Ok(epoch_data) = self.epoch_data.try_borrow() {
f.debug_struct("EpochShared")
.field("epoch_data", &epoch_data)
.field("p_self", &self.p_self)
.finish()
} else {
f.debug_struct("EpochShared")
.field(
"epoch_data (already borrowed, cannot display in `Debug` impl)",
&(),
)
.field("p_self", &self.p_self)
.finish()
}
}
}
impl EpochShared {
pub fn new() -> Self {
let mut epoch_data = EpochData {
epoch_key: None,
ensemble: Ensemble::new(),
responsible_for: Arena::new(),
};
let p_self = epoch_data.responsible_for.insert(PerEpochShared::new());
Self {
epoch_data: Rc::new(RefCell::new(epoch_data)),
p_self,
}
}
pub fn shared_with(other: &Self) -> Self {
let p_self = other
.epoch_data
.borrow_mut()
.responsible_for
.insert(PerEpochShared::new());
Self {
epoch_data: Rc::clone(&other.epoch_data),
p_self,
}
}
pub fn set_as_current(&self) {
let mut lock = self.epoch_data.borrow_mut();
if lock.epoch_key.is_none() {
lock.epoch_key = Some(_callback().push_on_epoch_stack());
}
drop(lock);
CURRENT_EPOCH.with(|top| {
let mut current = top.borrow_mut();
if let Some(current) = current.take() {
EPOCH_STACK.with(|top| {
let mut stack = top.borrow_mut();
stack.push(current);
})
}
*current = Some(self.clone());
});
}
pub fn remove_as_current(&self) -> Result<(), Error> {
EPOCH_STACK.with(|top| {
let mut stack = top.borrow_mut();
let next_current = stack.pop();
CURRENT_EPOCH.with(|top| {
let mut current = top.borrow_mut();
if let Some(ref to_drop) = current.take() {
if Rc::ptr_eq(&to_drop.epoch_data, &self.epoch_data) {
*current = next_current;
Ok(())
} else {
Err(Error::OtherStr(
"tried to drop or suspend an `Epoch` out of stacklike order before \
dropping or suspending the current `Epoch`",
))
}
} else {
Err(Error::OtherStr(
"`remove_as_current` encountered no current `EpochShared`, which should \
not be possible if an `Epoch` still exists",
))
}
})
})?;
let mut lock = self.epoch_data.borrow_mut();
if lock.responsible_for.is_empty() {
match lock.epoch_key.take().unwrap().pop_off_epoch_stack() {
Ok(()) => Ok(()),
Err((self_gen, top_gen)) => Err(Error::OtherString(format!(
"The last `starlight::Epoch` or `starlight::SuspendedEpoch` of a group of one \
or more shared `Epoch`s was dropped out of stacklike order, such that an \
`awint_dag::epoch::EpochKey` with generation {} was attempted to be dropped \
before the current key with generation {}. This may be because explicit \
`drop`s of `Epoch`s should be used in a different order.",
self_gen, top_gen
))),
}
} else {
Ok(())
}
}
pub fn drop_associated(&self) -> Result<(), Error> {
let mut lock = self.epoch_data.borrow_mut();
if let Some(mut ours) = lock.responsible_for.remove(self.p_self) {
let assertion_bits = mem::take(&mut ours.assertions.bits);
drop(lock);
drop(assertion_bits);
let mut lock = self.epoch_data.borrow_mut();
for p_state in ours.states_inserted.iter().copied() {
let _ = lock.ensemble.remove_state_if_pruning_allowed(p_state);
}
drop(lock);
drop(ours);
Ok(())
} else {
Err(Error::OtherStr(
"should be unreachable: called `EpochShared::drop_associated` on the same \
`EpochShared`",
))
}
}
pub fn ensemble<O, F: FnMut(&Ensemble) -> O>(&self, mut f: F) -> O {
f(&self.epoch_data.borrow().ensemble)
}
pub fn take_states_added(&mut self) -> Vec<PState> {
let mut epoch_data = self.epoch_data.borrow_mut();
let ours = epoch_data.responsible_for.get_mut(self.p_self).unwrap();
mem::take(&mut ours.states_inserted)
}
pub fn assertions(&self) -> Assertions {
let p_self = self.p_self;
let epoch_data = self.epoch_data.borrow();
let bits = &epoch_data
.responsible_for
.get(p_self)
.unwrap()
.assertions
.bits;
let mut p_externals = vec![];
for bit in bits {
p_externals.push(bit.p_external());
}
drop(epoch_data);
let mut cloned = vec![];
for bit in p_externals {
cloned.push(EvalAwi::try_clone_from(bit).unwrap());
}
Assertions { bits: cloned }
}
pub fn assert_assertions(&self, strict: bool) -> Result<(), Error> {
let p_self = self.p_self;
let epoch_data = self.epoch_data.borrow();
let mut len = epoch_data
.responsible_for
.get(p_self)
.unwrap()
.assertions
.bits
.len();
drop(epoch_data);
let mut unknown = None;
let mut i = 0;
loop {
if i >= len {
break
}
let epoch_data = self.epoch_data.borrow();
let eval_awi = &epoch_data
.responsible_for
.get(p_self)
.unwrap()
.assertions
.bits[i];
let p_external = eval_awi.p_external();
drop(epoch_data);
let val = Ensemble::request_thread_local_rnode_value(p_external, 0)?;
if let Some(val) = val.known_value() {
if !val {
return Err(Error::OtherString(format!(
"an assertion bit evaluated to false, failed on {p_external:#?}"
)))
}
} else if unknown.is_none() {
unknown = Some(p_external);
}
if (val == Value::ConstUnknown) && strict && unknown.is_none() {
unknown = Some(p_external);
}
if val.is_const() {
let mut epoch_data = self.epoch_data.borrow_mut();
let eval_awi = epoch_data
.responsible_for
.get_mut(p_self)
.unwrap()
.assertions
.bits
.swap_remove(i);
drop(epoch_data);
drop(eval_awi);
len -= 1;
} else {
i += 1;
}
}
if strict {
if let Some(p_external) = unknown {
return Err(Error::OtherString(format!(
"an assertion bit could not be evaluated to a known value, failed on \
{p_external:#?}"
)))
}
}
Ok(())
}
fn internal_run_with_lower_capability(&self, time: Delay) -> Result<(), Error> {
Ensemble::handle_states_to_lower(self)?;
let mut lock = self.epoch_data.borrow_mut();
let ensemble = &mut lock.ensemble;
ensemble.run(time)
}
fn internal_run(&self, time: Delay) -> Result<(), Error> {
let mut lock = self.epoch_data.borrow_mut();
let ensemble = &mut lock.ensemble;
ensemble.run(time)
}
}
thread_local!(
static CURRENT_EPOCH: RefCell<Option<EpochShared>> = RefCell::new(None);
static EPOCH_STACK: RefCell<Vec<EpochShared>> = RefCell::new(vec![]);
);
pub fn get_current_epoch() -> Result<EpochShared, Error> {
CURRENT_EPOCH
.with(|top| {
let top = top.borrow();
top.clone()
})
.ok_or(Error::NoCurrentlyActiveEpoch)
}
pub fn debug_epoch_stack() {
println!("awint epoch stack: {:?}", _get_epoch_stack());
CURRENT_EPOCH.with(|top| {
let top = top.borrow();
if let Some(x) = top.as_ref() {
println!("starlight current: {x:?}");
} else {
println!("no current epoch on starlight stack");
}
});
EPOCH_STACK.with(|top| {
let top = top.borrow();
for (i, x) in top.iter().rev().enumerate() {
println!("starlight stack depth {i}+1: {x:?}");
}
});
}
pub fn no_recursive_current_epoch<T, F: FnMut(&EpochShared) -> T>(mut f: F) -> T {
CURRENT_EPOCH.with(|top| {
let top = top.borrow();
if let Some(current) = top.as_ref() {
f(current)
} else {
panic!("There needs to be an `Epoch` in scope for this to work");
}
})
}
pub fn no_recursive_current_epoch_mut<T, F: FnMut(&mut EpochShared) -> T>(mut f: F) -> T {
CURRENT_EPOCH.with(|top| {
let mut top = top.borrow_mut();
if let Some(current) = top.as_mut() {
f(current)
} else {
panic!("There needs to be an `Epoch` in scope for this to work");
}
})
}
#[doc(hidden)]
pub fn _callback() -> EpochCallback {
fn new_pstate(nzbw: NonZeroUsize, op: Op<PState>, location: Option<Location>) -> PState {
no_recursive_current_epoch_mut(|current| {
let mut epoch_data = current.epoch_data.borrow_mut();
let p_state = epoch_data.ensemble.make_state(nzbw, op.clone(), location);
epoch_data
.responsible_for
.get_mut(current.p_self)
.unwrap()
.states_inserted
.push(p_state);
p_state
})
}
fn register_assertion_bit(bit: dag::bool, location: Location) {
let need_register = if let Some(awi) = bit.state().try_get_as_awi() {
assert_eq!(awi.bw(), 1);
awi.is_zero()
} else {
true
};
if need_register {
let new_bit = new_pstate(bw(1), Op::Assert([bit.state()]), Some(location));
let eval_awi = EvalAwi::from_state(new_bit);
CURRENT_EPOCH.with(|top| {
let mut top = top.borrow_mut();
if let Some(current) = top.as_mut() {
let mut epoch_data = current.epoch_data.borrow_mut();
epoch_data
.responsible_for
.get_mut(current.p_self)
.unwrap()
.assertions
.bits
.push(eval_awi);
} else {
panic!(
"there needs to be an `Epoch` in scope for assertion registration to work"
);
}
})
}
}
fn get_nzbw(p_state: PState) -> NonZeroUsize {
no_recursive_current_epoch(|current| {
current
.epoch_data
.borrow()
.ensemble
.stator
.states
.get(p_state)
.expect(
"probably, an `awint_dag`/`starlight` mimicking type was operated on in the \
wrong `Epoch`",
)
.nzbw
})
}
fn get_op(p_state: PState) -> Op<PState> {
no_recursive_current_epoch(|current| {
current
.epoch_data
.borrow()
.ensemble
.stator
.states
.get(p_state)
.expect(
"probably, an `awint_dag`/`starlight` mimicking type was operated on in the \
wrong `Epoch`",
)
.op
.clone()
})
}
EpochCallback {
new_pstate,
register_assertion_bit,
get_nzbw,
get_op,
}
}
#[derive(Debug)]
struct EpochInnerDrop {
epoch_shared: EpochShared,
is_suspended: bool,
}
impl Drop for EpochInnerDrop {
fn drop(&mut self) {
if !panicking() {
if let Err(e) = self.epoch_shared.drop_associated() {
panic!("{e}");
}
if !self.is_suspended {
if let Err(e) = self.epoch_shared.remove_as_current() {
panic!("panicked upon dropping an `Epoch`: {e}");
}
}
}
}
}
#[derive(Debug)]
pub struct Epoch {
inner: EpochInnerDrop,
}
#[derive(Debug)]
pub struct SuspendedEpoch {
inner: EpochInnerDrop,
}
impl SuspendedEpoch {
pub fn resume(mut self) -> Epoch {
self.inner.epoch_shared.set_as_current();
self.inner.is_suspended = false;
Epoch { inner: self.inner }
}
fn shared(&self) -> &EpochShared {
&self.inner.epoch_shared
}
pub fn ensemble<O, F: FnMut(&Ensemble) -> O>(&self, f: F) -> O {
self.shared().ensemble(f)
}
}
impl Epoch {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
let new = EpochShared::new();
new.set_as_current();
Self {
inner: EpochInnerDrop {
epoch_shared: new,
is_suspended: false,
},
}
}
pub fn shared_with(other: &Epoch) -> Self {
let shared = EpochShared::shared_with(other.shared());
shared.set_as_current();
Self {
inner: EpochInnerDrop {
epoch_shared: shared,
is_suspended: false,
},
}
}
fn shared(&self) -> &EpochShared {
&self.inner.epoch_shared
}
fn check_current(&self) -> Result<EpochShared, Error> {
let epoch_shared = get_current_epoch()?;
if Rc::ptr_eq(&epoch_shared.epoch_data, &self.shared().epoch_data) {
Ok(self.shared().clone())
} else {
Err(Error::WrongCurrentlyActiveEpoch)
}
}
#[track_caller]
pub fn suspend(mut self) -> SuspendedEpoch {
self.inner.epoch_shared.remove_as_current().unwrap();
self.inner.is_suspended = true;
SuspendedEpoch { inner: self.inner }
}
pub fn ensemble<O, F: FnMut(&Ensemble) -> O>(&self, f: F) -> O {
self.shared().ensemble(f)
}
pub fn clone_ensemble(&self) -> Ensemble {
self.ensemble(|ensemble| ensemble.clone())
}
pub fn verify_integrity(&self) -> Result<(), Error> {
self.ensemble(|ensemble| ensemble.verify_integrity())
}
pub fn assertions(&self) -> Assertions {
self.shared().assertions()
}
pub fn assert_assertions(&self, strict: bool) -> Result<(), Error> {
let epoch_shared = self.check_current()?;
epoch_shared.assert_assertions(strict)
}
pub fn prune_unused_states(&self) -> Result<(), Error> {
let epoch_shared = self.check_current()?;
let _ = epoch_shared.assert_assertions(false);
let mut lock = epoch_shared.epoch_data.borrow_mut();
lock.ensemble.prune_unused_states()
}
pub fn lower(&self) -> Result<(), Error> {
let epoch_shared = self.check_current()?;
Ensemble::handle_states_to_lower(&epoch_shared)?;
Ensemble::lower_for_rnodes(&epoch_shared)?;
let _ = epoch_shared.assert_assertions(false);
Ok(())
}
pub fn lower_and_prune(&self) -> Result<(), Error> {
let epoch_shared = self.check_current()?;
Ensemble::handle_states_to_lower(&epoch_shared)?;
Ensemble::lower_for_rnodes(&epoch_shared)?;
let _ = epoch_shared.assert_assertions(false);
let mut lock = epoch_shared.epoch_data.borrow_mut();
lock.ensemble.force_remove_all_states()
}
pub fn optimize(&self) -> Result<(), Error> {
let epoch_shared = self.check_current()?;
Ensemble::handle_states_to_lower(&epoch_shared)?;
Ensemble::lower_for_rnodes(&epoch_shared).unwrap();
let mut lock = epoch_shared.epoch_data.borrow_mut();
lock.ensemble.optimize_all().unwrap();
drop(lock);
let _ = epoch_shared.assert_assertions(false);
Ok(())
}
pub fn run<D: Into<Delay>>(&self, time: D) -> Result<(), Error> {
let epoch_shared = self.check_current()?;
if epoch_shared
.epoch_data
.borrow()
.ensemble
.stator
.states
.is_empty()
{
epoch_shared.internal_run(time.into())
} else {
epoch_shared.internal_run_with_lower_capability(time.into())
}
}
pub fn quiesced(&self) -> Result<bool, Error> {
self.run(Delay::zero())?;
self.ensemble(|ensemble| {
Ok(ensemble.delayer.delayed_events.is_empty() && ensemble.evaluator.are_events_empty())
})
}
}