diff --git a/src/node/inner.rs b/src/node/inner.rs index d897c6fc..c7960d4b 100644 --- a/src/node/inner.rs +++ b/src/node/inner.rs @@ -298,6 +298,26 @@ impl Inner { } impl, W> Inner, X, W> { + /// Collapse the node information to a `Dag` + pub fn into_dag(self) -> Dag> { + match self { + Inner::Iden + | Inner::Unit + | Inner::Witness(_) + | Inner::Fail(_) + | Inner::Jet(_) + | Inner::Word(_) => Dag::Nullary, + Inner::InjL(c) + | Inner::InjR(c) + | Inner::Take(c) + | Inner::Drop(c) + | Inner::AssertL(c, _) + | Inner::AssertR(_, c) => Dag::Unary(c), + Inner::Comp(cl, cr) | Inner::Case(cl, cr) | Inner::Pair(cl, cr) => Dag::Binary(cl, cr), + Inner::Disconnect(cl, cr) => cr.disconnect_dag_arc(cl), + } + } + /// Collapse the node information to a `Dag` pub fn as_dag(&self) -> Dag<&C> { match self { diff --git a/src/node/mod.rs b/src/node/mod.rs index f0da02f8..3f3c91bc 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -802,6 +802,34 @@ impl> Node { } } +impl Drop for Node { + fn drop(&mut self) { + // Note: this is basically identical to the drop impl for types::Incomplete. + fn push_children( + stack: &mut Vec>>, + inner: Inner>, N::Disconnect, N::Witness>, + ) { + use crate::dag::Dag; + match inner.into_dag() { + Dag::Nullary => {} + Dag::Unary(child) => stack.push(child), + Dag::Binary(left, right) => { + stack.push(left); + stack.push(right); + } + } + } + + let mut stack = Vec::new(); + push_children(&mut stack, std::mem::replace(&mut self.inner, Inner::Unit)); + while let Some(child) = stack.pop() { + if let Some(mut child) = Arc::into_inner(child) { + push_children(&mut stack, std::mem::replace(&mut child.inner, Inner::Unit)); + } + } + } +} + #[cfg(test)] #[cfg(all(feature = "test-utils", feature = "elements"))] mod tests { diff --git a/src/types/context.rs b/src/types/context.rs index 7992d2ae..0497bc6e 100644 --- a/src/types/context.rs +++ b/src/types/context.rs @@ -15,13 +15,14 @@ //! use std::any::TypeId; +use std::collections::HashMap; use std::fmt; use std::marker::PhantomData; use std::sync::{Arc, Mutex, MutexGuard}; use ghost_cell::GhostToken; -use crate::dag::{Dag, DagLike}; +use crate::dag::{Dag, DagLike, SharingTracker}; use crate::jet::Jet; use super::{ @@ -254,7 +255,7 @@ impl<'brand> Context<'brand> { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct BoundRef<'brand> { phantom: InvariantLifetime<'brand>, index: usize, @@ -285,7 +286,9 @@ impl super::PointerLike for BoundRef<'_> { } } -impl<'brand> DagLike for (&'_ Context<'brand>, BoundRef<'brand>) { +pub type CtxAndBoundRef<'ctx, 'brand> = (&'ctx Context<'brand>, BoundRef<'brand>); + +impl<'brand> DagLike for CtxAndBoundRef<'_, 'brand> { type Node = BoundRef<'brand>; fn data(&self) -> &BoundRef<'brand> { &self.1 @@ -303,6 +306,27 @@ impl<'brand> DagLike for (&'_ Context<'brand>, BoundRef<'brand>) { } } +#[derive(Clone, Debug, Default)] +pub struct BoundRefSharing<'brand> { + map: HashMap, usize>, +} + +impl<'brand> SharingTracker> for BoundRefSharing<'brand> { + fn record(&mut self, d: &CtxAndBoundRef<'_, 'brand>, index: usize) -> Option { + use std::collections::hash_map::Entry; + match self.map.entry(d.1.clone()) { + Entry::Occupied(occ) => Some(*occ.get()), + Entry::Vacant(vac) => { + vac.insert(index); + None + } + } + } + fn seen_before(&self, d: &CtxAndBoundRef<'_, 'brand>) -> Option { + self.map.get(&d.1).copied() + } +} + #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub struct OccursCheckId<'brand> { phantom: InvariantLifetime<'brand>, diff --git a/src/types/incomplete.rs b/src/types/incomplete.rs index d66473b4..48f16579 100644 --- a/src/types/incomplete.rs +++ b/src/types/incomplete.rs @@ -11,7 +11,9 @@ use crate::dag::{Dag, DagLike, NoSharing}; use crate::types::union_bound::PointerLike; +use super::context::BoundRefSharing; use super::{Bound, BoundRef, Context}; +use super::{MAX_DISPLAY_DEPTH, MAX_DISPLAY_LENGTH}; use std::fmt; use std::sync::Arc; @@ -31,6 +33,59 @@ pub enum Incomplete { Final(Arc), } +impl Incomplete { + /// Private helper function for `Incomplete::drop`. See `node::Inner::into_dag` which is + /// similar but doesn't require unsafe code. + fn into_dag(mut self) -> Dag> { + use core::{mem, ptr}; + let ret = match &mut self { + Incomplete::Sum(ref mut left, ref mut right) + | Incomplete::Product(ref mut left, ref mut right) => { + // Because Rust is stupid, we cannot just move 'left' and 'right' out of 'self'. + // We get the error "cannot move out of type that implements Drop". This message + // dates to before Rust 1.0, before mem::forget was marked safe, and has no + // justification that stands up to any scrutiny. There have been mulitple RFCs to + // remove it but for some reason they have never gone anywhere. See for example + // + // https://internals.rust-lang.org/t/destructuring-droppable-structs/20993/41 + // + // Anyway, instead we have to use unsafe code here to do this obviously-safe + // operation in an overcomplicated and hard-to-review way. + unsafe { + // SAFETY we are calling `ptr::read` on valid pointers (they come directly + // from references, which are always valid), and we will mem::forget their old + // locations before any early returns or panics. + + let left = ptr::read(left); + let right = ptr::read(right); + Dag::Binary(left, right) + } + } + Incomplete::Cycle => Dag::Nullary, + Incomplete::Free(s) => { + // SAFETY: see above. We are manually dropping `s` here, which we have to do + // by reading it out of &mut self, because Rust is stupid. + unsafe { + ptr::read(s); + } + Dag::Nullary + } + Incomplete::Final(fin) => { + // SAFETY: see above + unsafe { + ptr::read(fin); + } + Dag::Nullary + } + }; + // Because `Incomplete::drop` calls this method, we cannot allow `self` to be dropped + // under any circumstances, or else we will infinitely recurse and stack-overflow. (This + // is why we had to do ptr::read in every branch above). + mem::forget(self); + ret + } +} + impl DagLike for &'_ Incomplete { type Node = Incomplete; fn data(&self) -> &Incomplete { @@ -55,7 +110,18 @@ impl fmt::Debug for Incomplete { impl fmt::Display for Incomplete { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut skip_next = false; - for data in self.verbose_pre_order_iter::(None) { + for data in self.verbose_pre_order_iter::(Some(MAX_DISPLAY_DEPTH)) { + if data.index > MAX_DISPLAY_LENGTH { + write!(f, "... [truncated type after {} nodes]", MAX_DISPLAY_LENGTH)?; + return Ok(()); + } + if data.depth == MAX_DISPLAY_DEPTH { + if data.n_children_yielded == 0 { + f.write_str("...")?; + } + continue; + } + if skip_next { skip_next = false; continue; @@ -92,6 +158,31 @@ impl fmt::Display for Incomplete { } } +impl Drop for Incomplete { + fn drop(&mut self) { + // Note: this is basically identical to the drop impl for node::Node. + fn push_children(stack: &mut Vec>, inner: Incomplete) { + use crate::dag::Dag; + match inner.into_dag() { + Dag::Nullary => {} + Dag::Unary(child) => stack.push(child), + Dag::Binary(left, right) => { + stack.push(left); + stack.push(right); + } + } + } + + let mut stack = Vec::new(); + push_children(&mut stack, std::mem::replace(self, Incomplete::Cycle)); + while let Some(child) = stack.pop() { + if let Some(mut child) = Arc::into_inner(child) { + push_children(&mut stack, std::mem::replace(&mut child, Incomplete::Cycle)); + } + } + } +} + impl Incomplete { /// Whether this "incomplete bound" is the unit type. pub fn is_unit(&self) -> bool { @@ -168,7 +259,7 @@ impl Incomplete { // Now that we know our bound has finite size, we can safely use a // post-order iterator on it. let mut finalized = vec![]; - for data in (ctx, bound_ref).post_order_iter::() { + for data in (ctx, bound_ref).post_order_iter::>() { let bound_get = data.node.0.get(&data.node.1); let final_data = match bound_get { Bound::Free(s) => Incomplete::Free(s),