pure-borrow 0.0.0.0 → 0.1.0.0
raw patch · 95 files changed
+23083/−1510 lines, 95 filesdep +atomic-primopsdep +filepathdep +fmlistdep −file-embeddep −processdep −template-haskelldep ~basenew-component:exe:fftPVP ok
version bump matches the API change (PVP)
Dependencies added: atomic-primops, filepath, fmlist, hashable, integer-logarithms, primitive, stm-chans, tasty-inspection-testing
Dependencies removed: file-embed, process, template-haskell, temporary, transformers
Dependency ranges changed: base
API changes (from Hackage documentation)
- Control.Concurrent.DivideConquer.Linear: instance Data.Unrestricted.Linear.Internal.Consumable.Consumable Control.Concurrent.DivideConquer.Linear.Thread
- Control.Concurrent.STM.TMDeque: closeTMDeque :: TMDeque a -> STM ()
- Control.Concurrent.STM.TMDeque: countTMDequeIO :: TMDeque a -> IO Int
- Control.Concurrent.STM.TMDeque: data TMDeque a
- Control.Concurrent.STM.TMDeque: isClosedTMDeque :: TMDeque a -> STM Bool
- Control.Concurrent.STM.TMDeque: isClosedTMDequeIO :: TMDeque a -> IO Bool
- Control.Concurrent.STM.TMDeque: isEmptyTMDeque :: TMDeque a -> STM Bool
- Control.Concurrent.STM.TMDeque: newTMDeque :: STM (TMDeque a)
- Control.Concurrent.STM.TMDeque: newTMDequeIO :: IO (TMDeque a)
- Control.Concurrent.STM.TMDeque: popBackTMDeque :: TMDeque a -> STM (Maybe a)
- Control.Concurrent.STM.TMDeque: popFrontTMDeque :: TMDeque a -> STM (Maybe a)
- Control.Concurrent.STM.TMDeque: pushFrontTMDeque :: TMDeque a -> a -> STM ()
- Control.Concurrent.STM.TMDeque: sizeTMDeque :: TMDeque a -> STM Int
- Control.Concurrent.STM.TMDeque: tryPopBackTMDeque :: TMDeque a -> STM (Maybe (Maybe a))
- Control.Concurrent.STM.TMDeque: tryPopFrontTMDeque :: TMDeque a -> STM (Maybe (Maybe a))
- Control.Concurrent.STM.TMDequeRingBuffer: closeTMDeque :: TMDeque a -> STM ()
- Control.Concurrent.STM.TMDequeRingBuffer: data TMDeque a
- Control.Concurrent.STM.TMDequeRingBuffer: estimateSizeTMDequeIO :: TMDeque a -> IO Int
- Control.Concurrent.STM.TMDequeRingBuffer: isClosedTMDeque :: TMDeque a -> STM Bool
- Control.Concurrent.STM.TMDequeRingBuffer: isClosedTMDequeIO :: TMDeque a -> IO Bool
- Control.Concurrent.STM.TMDequeRingBuffer: isEmptyTMDeque :: TMDeque a -> STM Bool
- Control.Concurrent.STM.TMDequeRingBuffer: newTMDeque :: STM (TMDeque a)
- Control.Concurrent.STM.TMDequeRingBuffer: newTMDequeIO :: IO (TMDeque a)
- Control.Concurrent.STM.TMDequeRingBuffer: popBackTMDeque :: TMDeque a -> STM (Maybe a)
- Control.Concurrent.STM.TMDequeRingBuffer: popFrontTMDeque :: TMDeque a -> STM (Maybe a)
- Control.Concurrent.STM.TMDequeRingBuffer: pushFrontTMDeque :: TMDeque a -> a -> STM ()
- Control.Concurrent.STM.TMDequeRingBuffer: sizeTMDeque :: TMDeque a -> STM Int
- Control.Concurrent.STM.TMDequeRingBuffer: tryPopBackTMDeque :: TMDeque a -> STM (Maybe (Maybe a))
- Control.Concurrent.STM.TMDequeRingBuffer: tryPopFrontTMDeque :: TMDeque a -> STM (Maybe (Maybe a))
- Control.Monad.Borrow.Pure.Experimental.Borrows: data Borrows (bk :: BorrowKind) (α :: Lifetime) (xs :: [Type])
- Control.Monad.Borrow.Pure.Experimental.Borrows: instance (β Control.Monad.Borrow.Pure.Lifetime.Internal.<= α) => Control.Monad.Borrow.Pure.Experimental.Borrows.Borrows bk α xs Data.Coerce.Directed.Internal.<: Control.Monad.Borrow.Pure.Experimental.Borrows.Borrows bk' β xs
- Control.Monad.Borrow.Pure.Experimental.Borrows: instance Control.Monad.Borrow.Pure.Affine.Internal.Affine (Control.Monad.Borrow.Pure.Experimental.Borrows.Borrows bk α xs)
- Control.Monad.Borrow.Pure.Experimental.Borrows: instance Control.Monad.Borrow.Pure.Experimental.Reborrowable.Reborrowable (Control.Monad.Borrow.Pure.Experimental.Borrows.Borrows bk)
- Control.Monad.Borrow.Pure.Experimental.Borrows: instance Data.Unrestricted.Linear.Internal.Consumable.Consumable (Control.Monad.Borrow.Pure.Experimental.Borrows.Borrows bk α xs)
- Control.Monad.Borrow.Pure.Experimental.Loop: [:-] :: forall (bk :: BorrowKind) (α :: Lifetime) x (xs1 :: [Type]). !Borrow bk α x -> !Borrows bk α xs1 -> Borrows bk α (x ': xs1)
- Control.Monad.Borrow.Pure.Experimental.Loop: [BNil] :: forall (bk :: BorrowKind) (α :: Lifetime). Borrows bk α ('[] :: [Type])
- Control.Monad.Borrow.Pure.Experimental.Loop: data Borrows (bk :: BorrowKind) (α :: Lifetime) (xs :: [Type])
- Control.Monad.Borrow.Pure.Experimental.Loop: infixr 5 :-
- Control.Monad.Borrow.Pure.Experimental.Reborrowable: instance Control.Monad.Borrow.Pure.Experimental.Reborrowable.Reborrowable Control.Monad.Borrow.Pure.BO.Internal.Mut
- Control.Monad.Borrow.Pure.Experimental.Reborrowable: instance Control.Monad.Borrow.Pure.Experimental.Reborrowable.Reborrowable Control.Monad.Borrow.Pure.BO.Internal.Share
- Data.Vector.Mutable.Linear.Borrow: inplace :: forall (α :: Lifetime) (β :: Lifetime) a. α >= β => (forall s. () => MVector s a -> ST s ()) %1 -> Mut α (Vector a) %1 -> BO β (Mut α (Vector a))
- Data.Vector.Mutable.Linear.Borrow: instance Control.Monad.Borrow.Pure.Lifetime.Token.Internal.LinearOnly (Data.Vector.Mutable.Linear.Borrow.Vector a)
- Data.Vector.Mutable.Linear.Borrow: instance Data.Unrestricted.Linear.Internal.Dupable.Dupable a => Control.Monad.Borrow.Pure.Clone.Clone (Data.Vector.Mutable.Linear.Borrow.Vector a)
- Data.Vector.Mutable.Linear.Borrow: instance GHC.Internal.TypeError.Unsatisfiable ('GHC.Internal.TypeError.ShowType (Data.Vector.Mutable.Linear.Borrow.Vector a) 'GHC.Internal.TypeError.:<>: 'GHC.Internal.TypeError.Text " cannot be copied!") => Control.Monad.Borrow.Pure.Copyable.Copyable (Data.Vector.Mutable.Linear.Borrow.Vector a)
+ Control.Concurrent.DivideConquer.Linear: Continue :: !t (Ur c, Mut β a) -> Result c (β :: Lifetime) (t :: Type -> Type) a r
+ Control.Concurrent.DivideConquer.Linear: Done :: !r -> Result c (β :: Lifetime) (t :: Type -> Type) a r
+ Control.Concurrent.DivideConquer.Linear: FftCoe :: {-# UNPACK #-} !Double -> {-# UNPACK #-} !Double -> {-# UNPACK #-} !Int -> FftCoe
+ Control.Concurrent.DivideConquer.Linear: [Conquer] :: forall (α :: Lifetime) c a (t :: Type -> Type) r. (forall (β :: Lifetime). α >= β => c -> Mut β a %1 -> t r %1 -> BO β r) -> Conquer c α t a r
+ Control.Concurrent.DivideConquer.Linear: [NoConquer] :: forall c (α :: Lifetime) (t :: Type -> Type) a. Conquer c α t a ()
+ Control.Concurrent.DivideConquer.Linear: [Pair] :: forall a. !a -> !a -> Pair a
+ Control.Concurrent.DivideConquer.Linear: [conquer] :: DivideConquer c (α :: Lifetime) (t :: Type -> Type) a r -> Conquer c α t a r
+ Control.Concurrent.DivideConquer.Linear: [cosθ] :: FftCoe -> {-# UNPACK #-} !Double
+ Control.Concurrent.DivideConquer.Linear: [initialise] :: DivideConquer c (α :: Lifetime) (t :: Type -> Type) a r -> forall (β :: Lifetime). α >= β => Mut β a %1 -> BO β (Ur c)
+ Control.Concurrent.DivideConquer.Linear: [sinθ] :: FftCoe -> {-# UNPACK #-} !Double
+ Control.Concurrent.DivideConquer.Linear: [size] :: FftCoe -> {-# UNPACK #-} !Int
+ Control.Concurrent.DivideConquer.Linear: combineLoop :: forall (v :: Type -> Type) (α :: Lifetime). Vector v (Complex Double) => Int -> Complex Double -> Int -> Complex Double -> Mut α (Vector v (Complex Double)) %1 -> BO α ()
+ Control.Concurrent.DivideConquer.Linear: data Conquer c (α :: Lifetime) (t :: Type -> Type) a r
+ Control.Concurrent.DivideConquer.Linear: data FftCoe
+ Control.Concurrent.DivideConquer.Linear: data Pair a
+ Control.Concurrent.DivideConquer.Linear: data Result c (β :: Lifetime) (t :: Type -> Type) a r
+ Control.Concurrent.DivideConquer.Linear: divideAndConquer' :: forall c (α :: Lifetime) (β :: Lifetime) (t :: Type -> Type) a r g. (Traversable t, α >= β, RandomGen g) => g -> Int -> DivideConquer c α t a r -> Mut α a %1 -> BO β (r, Mut α a)
+ Control.Concurrent.DivideConquer.Linear: fftDC :: forall (v :: Type -> Type) (α :: Lifetime) (β :: Lifetime) g. (Vector v (Complex Double), α >= β, RandomGen g, HasCallStack) => g -> Int -> Int -> Mut α (Vector v (Complex Double)) %1 -> BO β (Mut α (Vector v (Complex Double)))
+ Control.Concurrent.DivideConquer.Linear: fftDC' :: forall (v :: Type -> Type) (α :: Lifetime). Vector v (Complex Double) => Int -> DivideConquer FftCoe α Pair (Vector v (Complex Double)) ()
+ Control.Concurrent.DivideConquer.Linear: instance GHC.Internal.Show.Show Control.Concurrent.DivideConquer.Linear.FftCoe
+ Control.Concurrent.DivideConquer.Linear: naiveDivideAndConquer :: forall c (α :: Lifetime) (t :: Type -> Type) a. (Traversable t, Consumable (t ())) => DivideConquer c α t a () -> Mut α a %1 -> BO α (Mut α a)
+ Control.Concurrent.DivideConquer.Linear: naiveDivideAndConquer' :: forall c (α :: Lifetime) (t :: Type -> Type) a r. (Traversable t, Consumable (t ())) => DivideConquer c α t a r -> Mut α a %1 -> BO α (r, Mut α a)
+ Control.Concurrent.DivideConquer.Linear: qsort :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (Vector v a, Ord a, α >= β) => Word -> Mut α (Vector v a) %1 -> BO β ()
+ Control.Concurrent.DivideConquer.Linear: qsortDC' :: forall (v :: Type -> Type) a (α :: Lifetime). (Vector v a, Ord a) => Int -> DivideConquer () α Pair (Vector v a) ()
+ Control.Concurrent.DivideConquer.Linear: sequentialDivideAndConquer :: forall c (α :: Lifetime) (t :: Type -> Type) a. (Traversable t, Consumable (t ())) => DivideConquer c α t a () -> Mut α a %1 -> BO α (Mut α a)
+ Control.Concurrent.DivideConquer.Linear: sequentialDivideAndConquer' :: forall c (α :: Lifetime) (t :: Type -> Type) a r. (Traversable t, Consumable (t ())) => DivideConquer c α t a r -> Mut α a %1 -> BO α (r, Mut α a)
+ Control.Concurrent.Queue.ChaseLev: Empty :: StealResult a
+ Control.Concurrent.Queue.ChaseLev: Found :: a -> StealResult a
+ Control.Concurrent.Queue.ChaseLev: Race :: StealResult a
+ Control.Concurrent.Queue.ChaseLev: capacity :: ChaseLevDeq a -> IO Int
+ Control.Concurrent.Queue.ChaseLev: close :: ChaseLevDeq a -> IO ()
+ Control.Concurrent.Queue.ChaseLev: data ChaseLevDeq a
+ Control.Concurrent.Queue.ChaseLev: data StealResult a
+ Control.Concurrent.Queue.ChaseLev: estimateSize :: ChaseLevDeq a -> IO Int
+ Control.Concurrent.Queue.ChaseLev: instance GHC.Classes.Eq a => GHC.Classes.Eq (Control.Concurrent.Queue.ChaseLev.StealResult a)
+ Control.Concurrent.Queue.ChaseLev: instance GHC.Classes.Ord a => GHC.Classes.Ord (Control.Concurrent.Queue.ChaseLev.StealResult a)
+ Control.Concurrent.Queue.ChaseLev: instance GHC.Internal.Show.Show a => GHC.Internal.Show.Show (Control.Concurrent.Queue.ChaseLev.StealResult a)
+ Control.Concurrent.Queue.ChaseLev: isClosed :: ChaseLevDeq a -> IO Bool
+ Control.Concurrent.Queue.ChaseLev: newDeq :: IO (ChaseLevDeq a)
+ Control.Concurrent.Queue.ChaseLev: pushFront :: ChaseLevDeq a -> a -> IO ()
+ Control.Concurrent.Queue.ChaseLev: pushFronts :: ChaseLevDeq a -> [a] -> IO ()
+ Control.Concurrent.Queue.ChaseLev: stealHalf :: ChaseLevDeq a -> IO (Maybe (StealResult (NonEmpty a)))
+ Control.Concurrent.Queue.ChaseLev: tryPopBack :: ChaseLevDeq a -> IO (Maybe (StealResult a))
+ Control.Concurrent.Queue.ChaseLev: tryPopFront :: ChaseLevDeq a -> IO (Maybe (Maybe a))
+ Control.Monad.Borrow.Pure: Par :: BO α a -> Par (α :: Lifetime) a
+ Control.Monad.Borrow.Pure: forConcurrentlyOf :: forall s t a b (α :: Lifetime). Traversal s t a b -> s %1 -> (a %1 -> BO α b) -> BO α t
+ Control.Monad.Borrow.Pure: mapConcurrentlyOf :: forall s t a b (α :: Lifetime). Traversal s t a b -> (a %1 -> BO α b) -> s %1 -> BO α t
+ Control.Monad.Borrow.Pure: newtype Par (α :: Lifetime) a
+ Control.Monad.Borrow.Pure: runPar :: forall (α :: Lifetime) a. Par α a %1 -> BO α a
+ Control.Monad.Borrow.Pure: subShare :: forall (α :: Lifetime) (β :: Lifetime) a. α >= β => Share α a -> Share β a
+ Control.Monad.Borrow.Pure.BO: Par :: BO α a -> Par (α :: Lifetime) a
+ Control.Monad.Borrow.Pure.BO: instance Control.Functor.Linear.Internal.Class.Applicative (Control.Monad.Borrow.Pure.BO.Par α)
+ Control.Monad.Borrow.Pure.BO: instance Control.Functor.Linear.Internal.Class.Functor (Control.Monad.Borrow.Pure.BO.Par α)
+ Control.Monad.Borrow.Pure.BO: instance Data.Functor.Linear.Internal.Applicative.Applicative (Control.Monad.Borrow.Pure.BO.Par α)
+ Control.Monad.Borrow.Pure.BO: instance Data.Functor.Linear.Internal.Functor.Functor (Control.Monad.Borrow.Pure.BO.Par α)
+ Control.Monad.Borrow.Pure.BO: newtype Par (α :: Lifetime) a
+ Control.Monad.Borrow.Pure.BO: runPar :: forall (α :: Lifetime) a. Par α a %1 -> BO α a
+ Control.Monad.Borrow.Pure.BO: subShare :: forall (α :: Lifetime) (β :: Lifetime) a. α >= β => Share α a -> Share β a
+ Control.Monad.Borrow.Pure.BO.Unsafe: reviveAlias :: forall (ak :: AliasKind) a (α :: Lifetime). Alias ak a %1 -> BO α (Alias ak a)
+ Control.Monad.Borrow.Pure.BO.Unsafe: unsafeCastAlias :: forall (ak :: AliasKind) a (ak' :: AliasKind). Alias ak a %1 -> Alias ak' a
+ Control.Monad.Borrow.Pure.Copyable: instance Control.Monad.Borrow.Pure.Copyable.Copyable a => Control.Monad.Borrow.Pure.Copyable.Copyable (Data.Complex.Complex a)
+ Control.Monad.Borrow.Pure.Copyable: instance Control.Monad.Borrow.Pure.Copyable.Copyable1 Data.Complex.Complex
+ Control.Monad.Borrow.Pure.Copyable: instance Control.Monad.Borrow.Pure.Copyable.Copyable1 GHC.Internal.Generics.U1
+ Control.Monad.Borrow.Pure.Copyable: instance Control.Monad.Borrow.Pure.Copyable.Copyable1 GHC.Internal.Generics.V1
+ Control.Monad.Borrow.Pure.Copyable: instance Data.Unrestricted.Linear.Internal.Consumable.Consumable (Data.Complex.Complex GHC.Types.Double)
+ Control.Monad.Borrow.Pure.Copyable: instance Data.Unrestricted.Linear.Internal.Dupable.Dupable (Data.Complex.Complex GHC.Types.Double)
+ Control.Monad.Borrow.Pure.Copyable: instance Data.Unrestricted.Linear.Internal.Movable.Movable (Data.Complex.Complex GHC.Types.Double)
+ Control.Monad.Borrow.Pure.Experimental.Borrows: data Aliases (k :: AliasKind) (xs :: [Type])
+ Control.Monad.Borrow.Pure.Experimental.Borrows: instance (k GHC.Types.~ 'Control.Monad.Borrow.Pure.BO.Internal.Borrow 'Control.Monad.Borrow.Pure.BO.Internal.Share α) => Data.Unrestricted.Linear.Internal.Dupable.Dupable (Control.Monad.Borrow.Pure.Experimental.Borrows.Aliases k xs)
+ Control.Monad.Borrow.Pure.Experimental.Borrows: instance (k GHC.Types.~ 'Control.Monad.Borrow.Pure.BO.Internal.Borrow 'Control.Monad.Borrow.Pure.BO.Internal.Share α) => Data.Unrestricted.Linear.Internal.Movable.Movable (Control.Monad.Borrow.Pure.Experimental.Borrows.Aliases k xs)
+ Control.Monad.Borrow.Pure.Experimental.Borrows: instance (k GHC.Types.~ 'Control.Monad.Borrow.Pure.BO.Internal.Borrow bk α) => Control.Monad.Borrow.Pure.Affine.Internal.Affine (Control.Monad.Borrow.Pure.Experimental.Borrows.Aliases k xs)
+ Control.Monad.Borrow.Pure.Experimental.Borrows: instance (k GHC.Types.~ 'Control.Monad.Borrow.Pure.BO.Internal.Borrow bk α) => Data.Unrestricted.Linear.Internal.Consumable.Consumable (Control.Monad.Borrow.Pure.Experimental.Borrows.Aliases k xs)
+ Control.Monad.Borrow.Pure.Experimental.Borrows: instance (α Control.Monad.Borrow.Pure.Lifetime.Internal.<= β, a Data.Coerce.Directed.Internal.<: b) => Control.Monad.Borrow.Pure.Experimental.Borrows.Lends α a Data.Coerce.Directed.Internal.<: Control.Monad.Borrow.Pure.Experimental.Borrows.Lends β b
+ Control.Monad.Borrow.Pure.Experimental.Borrows: instance (α Control.Monad.Borrow.Pure.Lifetime.Internal.>= β, xs Data.Coerce.Directed.Internal.<: ys) => Control.Monad.Borrow.Pure.Experimental.Borrows.Shares α xs Data.Coerce.Directed.Internal.<: Control.Monad.Borrow.Pure.Experimental.Borrows.Shares β ys
+ Control.Monad.Borrow.Pure.Experimental.Borrows: instance (α Control.Monad.Borrow.Pure.Lifetime.Internal.>= β, xs Data.Coerce.Directed.Internal.<: ys, ys Data.Coerce.Directed.Internal.<: xs) => Control.Monad.Borrow.Pure.Experimental.Borrows.Muts α xs Data.Coerce.Directed.Internal.<: Control.Monad.Borrow.Pure.Experimental.Borrows.Muts β ys
+ Control.Monad.Borrow.Pure.Experimental.Borrows: instance Control.Monad.Borrow.Pure.Experimental.Reborrowable.Reborrowable (Control.Monad.Borrow.Pure.Experimental.Borrows.Muts α)
+ Control.Monad.Borrow.Pure.Experimental.Borrows: reborrowings :: forall (α :: Lifetime) (a :: [Type]) (α' :: Lifetime) r. Muts α a %1 -> (forall (β :: Lifetime). () => Muts (β /\ α) a %1 -> BO (β /\ α') r) %1 -> BO α' (r, Muts α a)
+ Control.Monad.Borrow.Pure.Experimental.Borrows: reborrowings' :: forall (α :: Lifetime) (a :: [Type]) (α' :: Lifetime) r. Muts α a %1 -> (forall (β :: Lifetime). () => Muts (β /\ α) a %1 -> BO (β /\ α') (After β r)) %1 -> BO α' (r, Muts α a)
+ Control.Monad.Borrow.Pure.Experimental.Borrows: reborrowings_ :: forall r (α :: Lifetime) (a :: [Type]) (α' :: Lifetime). Consumable r => Muts α a %1 -> (forall (β :: Lifetime). () => Muts (β /\ α) a %1 -> BO (β /\ α') r) %1 -> BO α' (Muts α a)
+ Control.Monad.Borrow.Pure.Experimental.Borrows: reborrows :: forall (β :: Lifetime) (α :: Lifetime) (a :: [Type]). α >= β => Muts α a %1 -> (Muts β a, Lend β (Muts α a))
+ Control.Monad.Borrow.Pure.Experimental.Borrows: reviveAliases :: forall (k :: AliasKind) (xs :: [Type]) (α :: Lifetime). Aliases k xs %1 -> BO α (Aliases k xs)
+ Control.Monad.Borrow.Pure.Experimental.Borrows: type Borrows (bk :: BorrowKind) (α :: Lifetime) = Aliases 'Borrow bk α
+ Control.Monad.Borrow.Pure.Experimental.Borrows: type Lends (α :: Lifetime) = Aliases 'Lend α
+ Control.Monad.Borrow.Pure.Experimental.Borrows: type Muts (α :: Lifetime) = Borrows 'Mut α
+ Control.Monad.Borrow.Pure.Experimental.Borrows: type Shares (α :: Lifetime) = Borrows 'Share α
+ Control.Monad.Borrow.Pure.Experimental.Loop: iterReborrowing_ :: forall {k} bor (α :: Lifetime) (xs :: k). Reborrowable bor => Int -> bor xs %1 -> (forall (β :: Lifetime). () => Int -> WithLifetime bor (β /\ LifetimeOf bor) xs %1 -> BO (β /\ α) ()) -> BO α (bor xs)
+ Control.Monad.Borrow.Pure.Experimental.Reborrowable: instance Control.Monad.Borrow.Pure.Experimental.Reborrowable.Reborrowable (Control.Monad.Borrow.Pure.BO.Internal.Mut α)
+ Control.Monad.Borrow.Pure.Experimental.Reborrowable: instance Control.Monad.Borrow.Pure.Experimental.Reborrowable.Reborrowable (Control.Monad.Borrow.Pure.BO.Internal.Share α)
+ Control.Monad.Borrow.Pure.Experimental.Reborrowable: type LifetimeOf (bor :: k -> Type) :: Lifetime;
+ Control.Monad.Borrow.Pure.Experimental.Reborrowable: type WithLifetime (bor :: k -> Type) (α :: Lifetime) :: k1 -> Type;
+ Control.Monad.Borrow.Pure.Experimental.Reborrowable: }
+ Data.HashMap.RobinHood.Mutable.Linear: alter :: Hashable k => (Maybe v -> Maybe v) -> k -> HashMap k v %1 -> HashMap k v
+ Data.HashMap.RobinHood.Mutable.Linear: alterF :: (Hashable k, Functor f) => (Maybe v -> f (Ur (Maybe v))) %1 -> k -> HashMap k v %1 -> f (HashMap k v)
+ Data.HashMap.RobinHood.Mutable.Linear: capacity :: HashMap k v %1 -> (Ur Int, HashMap k v)
+ Data.HashMap.RobinHood.Mutable.Linear: class Eq a => Hashable a
+ Data.HashMap.RobinHood.Mutable.Linear: data HashMap k v
+ Data.HashMap.RobinHood.Mutable.Linear: data InsertPlan k
+ Data.HashMap.RobinHood.Mutable.Linear: delete :: Hashable k => k -> HashMap k v %1 -> (Ur (Maybe v), HashMap k v)
+ Data.HashMap.RobinHood.Mutable.Linear: foldMapWithKey :: Monoid w => (k -> v -> w) -> HashMap k v %1 -> w
+ Data.HashMap.RobinHood.Mutable.Linear: fromList :: Hashable k => [(k, v)] -> Linearly %1 -> HashMap k v
+ Data.HashMap.RobinHood.Mutable.Linear: insert :: Hashable k => k -> v -> HashMap k v %1 -> (Ur (Maybe v), HashMap k v)
+ Data.HashMap.RobinHood.Mutable.Linear: insertMany :: Hashable k => [(k, v)] -> HashMap k v %1 -> HashMap k v
+ Data.HashMap.RobinHood.Mutable.Linear: lookup :: Hashable k => k -> HashMap k v %1 -> (Ur (Maybe v), HashMap k v)
+ Data.HashMap.RobinHood.Mutable.Linear: lookupForInsert :: Hashable k => k -> HashMap k v %1 -> (Ur (Either v (InsertPlan k)), HashMap k v)
+ Data.HashMap.RobinHood.Mutable.Linear: member :: Hashable k => k -> HashMap k v %1 -> (Ur Bool, HashMap k v)
+ Data.HashMap.RobinHood.Mutable.Linear: new :: Int -> Linearly %1 -> HashMap k v
+ Data.HashMap.RobinHood.Mutable.Linear: size :: HashMap k v %1 -> (Ur Int, HashMap k v)
+ Data.HashMap.RobinHood.Mutable.Linear: toList :: HashMap k v %1 -> Ur [(k, v)]
+ Data.HashMap.RobinHood.Mutable.Linear: union :: Hashable k => HashMap k v %1 -> HashMap k v %1 -> HashMap k v
+ Data.HashMap.RobinHood.Mutable.Linear: unsafeInsertPrepared :: InsertPlan k -> v -> HashMap k v %1 -> HashMap k v
+ Data.HashMap.RobinHood.Mutable.Linear.Borrow: alter :: forall k v (α :: Lifetime). Hashable k => (Maybe v -> Maybe v) -> k -> Mut α (HashMap k v) %1 -> BO α (Mut α (HashMap k v))
+ Data.HashMap.RobinHood.Mutable.Linear.Borrow: alterF :: forall k v (α :: Lifetime). Hashable k => (Maybe v -> BO α (Ur (Maybe v))) -> k -> Mut α (HashMap k v) %1 -> BO α (Mut α (HashMap k v))
+ Data.HashMap.RobinHood.Mutable.Linear.Borrow: class Eq a => Hashable a
+ Data.HashMap.RobinHood.Mutable.Linear.Borrow: data HashMap k v
+ Data.HashMap.RobinHood.Mutable.Linear.Borrow: data InsertPlan k
+ Data.HashMap.RobinHood.Mutable.Linear.Borrow: delete :: forall k (α :: Lifetime) v. Hashable k => k -> Mut α (HashMap k v) %1 -> BO α (Ur (Maybe v), Mut α (HashMap k v))
+ Data.HashMap.RobinHood.Mutable.Linear.Borrow: empty :: Int -> Linearly %1 -> HashMap k v
+ Data.HashMap.RobinHood.Mutable.Linear.Borrow: extend :: forall k v (α :: Lifetime). Hashable k => HashMap k v %1 -> Mut α (HashMap k v) %1 -> BO α (Mut α (HashMap k v))
+ Data.HashMap.RobinHood.Mutable.Linear.Borrow: fromList :: Hashable k => [(k, v)] -> Linearly %1 -> HashMap k v
+ Data.HashMap.RobinHood.Mutable.Linear.Borrow: insert :: forall k v (α :: Lifetime). Hashable k => k -> v -> Mut α (HashMap k v) %1 -> BO α (Ur (Maybe v), Mut α (HashMap k v))
+ Data.HashMap.RobinHood.Mutable.Linear.Borrow: lookup :: forall k (bk :: BorrowKind) (α :: Lifetime) v. Hashable k => k -> Borrow bk α (HashMap k v) %1 -> BO α (Ur (Maybe v), Borrow bk α (HashMap k v))
+ Data.HashMap.RobinHood.Mutable.Linear.Borrow: lookupForInsert :: forall k v (bk :: BorrowKind) (α :: Lifetime). Hashable k => k -> Borrow bk α (HashMap k v) %1 -> BO α (Ur (Either v (InsertPlan k)), Borrow bk α (HashMap k v))
+ Data.HashMap.RobinHood.Mutable.Linear.Borrow: member :: forall k (bk :: BorrowKind) (α :: Lifetime) v. Hashable k => k -> Borrow bk α (HashMap k v) %1 -> BO α (Ur Bool, Borrow bk α (HashMap k v))
+ Data.HashMap.RobinHood.Mutable.Linear.Borrow: size :: forall (bk :: BorrowKind) (α :: Lifetime) k v. Borrow bk α (HashMap k v) %1 -> BO α (Ur Int, Borrow bk α (HashMap k v))
+ Data.HashMap.RobinHood.Mutable.Linear.Borrow: swap :: forall k v (α :: Lifetime). HashMap k v %1 -> Mut α (HashMap k v) %1 -> BO α (HashMap k v, Mut α (HashMap k v))
+ Data.HashMap.RobinHood.Mutable.Linear.Borrow: take :: forall k v (α :: Lifetime). Mut α (HashMap k v) %1 -> BO α (HashMap k v, Mut α (HashMap k v))
+ Data.HashMap.RobinHood.Mutable.Linear.Borrow: take_ :: forall k v (α :: Lifetime). Mut α (HashMap k v) %1 -> BO α (HashMap k v)
+ Data.HashMap.RobinHood.Mutable.Linear.Borrow: toList :: forall (bk :: BorrowKind) (α :: Lifetime) k v. Borrow bk α (HashMap k v) %1 -> BO α (Ur [(k, v)], Borrow bk α (HashMap k v))
+ Data.HashMap.RobinHood.Mutable.Linear.Borrow: union :: Hashable k => HashMap k v %1 -> HashMap k v %1 -> HashMap k v
+ Data.HashMap.RobinHood.Mutable.Linear.Borrow: unsafeInsertPrepared :: forall k v (α :: Lifetime). InsertPlan k -> v -> Mut α (HashMap k v) %1 -> BO α (Mut α (HashMap k v))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: capacity :: forall (v :: Type -> Type) a (bk :: BorrowKind) (α :: Lifetime). Vector v a => Borrow bk α (GrowableVector v a) %1 -> (Ur Int, Borrow bk α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: constant :: forall (v :: Type -> Type) a. Vector v a => Int -> a -> Linearly %1 -> GrowableVector v a
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: copyAt :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Vector v a, α >= β) => Int -> Share α (GrowableVector v a) -> BO β (Ur a)
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: copyAtMut :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Vector v a, α >= β) => Int -> Mut α (GrowableVector v a) %1 -> BO β (Ur a, Mut α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: copyToVector :: forall v a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (Vector v a, α >= β) => Borrow bk α (GrowableVector v a) %1 -> BO β (Ur (v a), Borrow bk α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: data GrowableVector (v :: Type -> Type) a
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: empty :: forall (v :: Type -> Type) a. Vector v a => Linearly %1 -> GrowableVector v a
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: extend :: forall v a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Vector v a, α >= β) => v a -> Mut α (GrowableVector v a) %1 -> BO β (Mut α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: fromList :: forall (v :: Type -> Type) a. Vector v a => [a] -> Linearly %1 -> GrowableVector v a
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: fromVector :: Vector v a => v a -> Linearly %1 -> GrowableVector v a
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: get :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (HasCallStack, Vector v a, α >= β) => Int -> Borrow bk α (GrowableVector v a) %1 -> BO β (Ur a, Borrow bk α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: getContents :: forall (v :: Type -> Type) a (bk :: BorrowKind) (α :: Lifetime). Vector v a => Borrow bk α (GrowableVector v a) %1 -> Borrow bk α (Vector v a)
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: head :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (HasCallStack, Vector v a, α >= β) => Borrow bk α (GrowableVector v a) %1 -> BO β (Ur a, Borrow bk α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: last :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (HasCallStack, Vector v a, α >= β) => Borrow bk α (GrowableVector v a) %1 -> BO β (Ur a, Borrow bk α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: modify :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Vector v a, α >= β) => Int -> (a -> a) -> Mut α (GrowableVector v a) %1 -> BO β (Mut α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: push :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Vector v a, α >= β) => a -> Mut α (GrowableVector v a) %1 -> BO β (Mut α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: reserve :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Vector v a, α >= β) => Int -> Mut α (GrowableVector v a) %1 -> BO β (Mut α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: reserveAdditional :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Vector v a, α >= β) => Int -> Mut α (GrowableVector v a) %1 -> BO β (Mut α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: set :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Vector v a, α >= β) => Int -> a -> Mut α (GrowableVector v a) %1 -> BO β (Ur a, Mut α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: size :: forall (bk :: BorrowKind) (α :: Lifetime) (v :: Type -> Type) a. Borrow bk α (GrowableVector v a) %1 -> (Ur Int, Borrow bk α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: swap :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Vector v a, α >= β) => Mut α (GrowableVector v a) %1 -> Int -> Int -> BO β (Mut α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: toList :: forall (v :: Type -> Type) a. Vector v a => GrowableVector v a %1 -> Ur [a]
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: toVector :: Vector v a => GrowableVector v a %1 -> Ur (v a)
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: unsafeCopyAt :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (Vector v a, α >= β) => Int -> Share α (GrowableVector v a) -> BO β (Ur a)
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: unsafeCopyAtMut :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (Vector v a, α >= β) => Int -> Mut α (GrowableVector v a) %1 -> BO β (Ur a, Mut α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: unsafeFromMutable :: forall (v :: Type -> Type) a state. Vector v a => Mutable v state a %1 -> Linearly %1 -> GrowableVector v a
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: unsafeFromVector :: Vector v a => v a %1 -> Linearly %1 -> GrowableVector v a
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: unsafeGet :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (Vector v a, α >= β) => Int -> Borrow bk α (GrowableVector v a) %1 -> BO β (Ur a, Borrow bk α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: unsafeHead :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (Vector v a, α >= β) => Borrow bk α (GrowableVector v a) %1 -> BO β (Ur a, Borrow bk α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: unsafeLast :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (Vector v a, α >= β) => Borrow bk α (GrowableVector v a) %1 -> BO β (Ur a, Borrow bk α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: unsafeSet :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (Vector v a, α >= β) => Int -> a -> Mut α (GrowableVector v a) %1 -> BO β (Ur a, Mut α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: unsafeSwap :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (Vector v a, α >= β) => Mut α (GrowableVector v a) %1 -> Int -> Int -> BO β (Mut α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: unsafeUpdate :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) result. (Vector v a, α >= β) => Int -> (a -> BO β (Ur result, Ur a)) -> Mut α (GrowableVector v a) %1 -> BO β (Ur result, Mut α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: unsafeWrite :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (Vector v a, α >= β) => Int -> a -> Mut α (GrowableVector v a) %1 -> BO β (Mut α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: update :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) result. (HasCallStack, Vector v a, α >= β) => Int -> (a -> BO β (Ur result, Ur a)) -> Mut α (GrowableVector v a) %1 -> BO β (Ur result, Mut α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: withCapacity :: forall (v :: Type -> Type) a. (HasCallStack, Vector v a) => Int -> Linearly %1 -> GrowableVector v a
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: withContent :: forall (v :: Type -> Type) a (bk :: BorrowKind) (α :: Lifetime) result. Vector v a => Borrow bk α (GrowableVector v a) %1 -> (forall (β :: Lifetime). () => Borrow bk (β /\ α) (Vector v a) %1 -> BO (β /\ α) result) %1 -> BO α (result, Borrow bk α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: withContent_ :: forall (v :: Type -> Type) a result (bk :: BorrowKind) (α :: Lifetime). (Vector v a, Consumable result) => Borrow bk α (GrowableVector v a) %1 -> (forall (β :: Lifetime). () => Borrow bk (β /\ α) (Vector v a) %1 -> BO (β /\ α) result) %1 -> BO α (Borrow bk α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted: write :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Vector v a, α >= β) => Int -> a -> Mut α (GrowableVector v a) %1 -> BO β (Mut α (GrowableVector v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: class KnownMultiplicity (p :: Multiplicity)
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: constant :: forall (v :: Type -> Type) a. Vector v a => Int -> a -> Linearly %1 -> Vector 'Many v a
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: copyAt :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (p :: Multiplicity). (HasCallStack, Vector v a, α >= β, KnownMultiplicity p, PossiblyCopyable p a) => Int -> Share α (Vector p v a) -> BO β (Bound p a)
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: copyAtMut :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (p :: Multiplicity). (HasCallStack, Vector v a, α >= β, KnownMultiplicity p, PossiblyCopyable p a) => Int -> Mut α (Vector p v a) %1 -> BO β (Bound p a, Mut α (Vector p v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: copyToVector :: forall v a (α :: Lifetime) (β :: Lifetime) (p :: Multiplicity) (bk :: BorrowKind). (Vector v a, α >= β, KnownMultiplicity p, PossiblyCopyable p a) => Borrow bk α (Vector p v a) %1 -> BO β (Ur (v a), Borrow bk α (Vector p v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: data Vector (p :: Multiplicity) (v :: Type -> Type) a
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: empty :: forall (v :: Type -> Type) a (p :: Multiplicity). Vector v a => Linearly %1 -> Vector p v a
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: fromList :: forall (v :: Type -> Type) a (p :: Multiplicity). Vector v a => [a] %p -> Linearly %1 -> Vector p v a
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: fromVector :: Vector v a => v a -> Linearly %1 -> Vector 'Many v a
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: get :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (p :: Multiplicity) (bk :: BorrowKind). (HasCallStack, Vector v a, α >= β, KnownMultiplicity p) => Int -> Borrow bk α (Vector p v a) %1 -> BO β (GetResult p bk α v a)
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: head :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (p :: Multiplicity) (bk :: BorrowKind). (HasCallStack, Vector v a, α >= β, KnownMultiplicity p) => Borrow bk α (Vector p v a) %1 -> BO β (GetResult p bk α v a)
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: last :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (p :: Multiplicity) (bk :: BorrowKind). (HasCallStack, Vector v a, α >= β, KnownMultiplicity p) => Borrow bk α (Vector p v a) %1 -> BO β (GetResult p bk α v a)
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: modify :: forall (p :: Multiplicity) (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Vector v a, α >= β, KnownMultiplicity p) => Int -> (a %p -> a) %p -> Mut α (Vector p v a) %1 -> BO β (Mut α (Vector p v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: set :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (p :: Multiplicity). (HasCallStack, Vector v a, α >= β, KnownMultiplicity p) => Int -> a %p -> Mut α (Vector p v a) %1 -> BO β (Bound p a, Mut α (Vector p v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: size :: forall (v :: Type -> Type) a (bk :: BorrowKind) (α :: Lifetime) (p :: Multiplicity). Vector v a => Borrow bk α (Vector p v a) %1 -> (Ur Int, Borrow bk α (Vector p v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: splitAt :: forall (v :: Type -> Type) a (bk :: BorrowKind) (α :: Lifetime) (p :: Multiplicity). Vector v a => Int -> Borrow bk α (Vector p v a) %1 -> (Borrow bk α (Vector p v a), Borrow bk α (Vector p v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: swap :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (p :: Multiplicity). (HasCallStack, Vector v a, α >= β) => Mut α (Vector p v a) %1 -> Int -> Int -> BO β (Mut α (Vector p v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: toList :: forall (p :: Multiplicity) (v :: Type -> Type) a. (KnownMultiplicity p, Vector v a) => Vector p v a %1 -> Bound p [a]
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: toVector :: (KnownMultiplicity p, Vector v a, PossiblyMovable p a) => Vector p v a %1 -> Ur (v a)
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: type UpdateAction (p :: Multiplicity) (β :: Lifetime) result a = a %p -> BO β (Bound p result, Bound p a)
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: type family PossiblyCopyable (p :: Multiplicity) a
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: unsafeFromMutable :: forall (v :: Type -> Type) a state (p :: Multiplicity). Vector v a => Mutable v state a %1 -> Linearly %1 -> Vector p v a
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: unsafeFromVector :: forall v a (p :: Multiplicity). Vector v a => v a %1 -> Linearly %1 -> Vector p v a
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: unsafeGet :: forall (p :: Multiplicity) (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (Vector v a, α >= β, KnownMultiplicity p) => Int -> Borrow bk α (Vector p v a) %1 -> BO β (GetResult p bk α v a)
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: unsafeHead :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (p :: Multiplicity) (bk :: BorrowKind). (Vector v a, α >= β, KnownMultiplicity p) => Borrow bk α (Vector p v a) %1 -> BO β (GetResult p bk α v a)
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: unsafeLast :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (p :: Multiplicity) (bk :: BorrowKind). (Vector v a, α >= β, KnownMultiplicity p) => Borrow bk α (Vector p v a) %1 -> BO β (GetResult p bk α v a)
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: unsafeSet :: forall (p :: Multiplicity) (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (Vector v a, α >= β, KnownMultiplicity p) => Int -> a %p -> Mut α (Vector p v a) %1 -> BO β (Bound p a, Mut α (Vector p v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: unsafeSwap :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (p :: Multiplicity). (Vector v a, α >= β) => Mut α (Vector p v a) %1 -> Int -> Int -> BO β (Mut α (Vector p v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: unsafeUpdate :: forall (p :: Multiplicity) (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) result. (Vector v a, α >= β, KnownMultiplicity p) => Int -> UpdateAction p β result a %p -> Mut α (Vector p v a) %1 -> BO β (Bound p result, Mut α (Vector p v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: unsafeWrite :: forall (p :: Multiplicity) (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (Vector v a, α >= β, KnownMultiplicity p, PossiblyConsumable p a) => Int -> a %p -> Mut α (Vector p v a) %1 -> BO β (Mut α (Vector p v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: update :: forall (p :: Multiplicity) (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) result. (HasCallStack, Vector v a, α >= β, KnownMultiplicity p) => Int -> UpdateAction p β result a %p -> Mut α (Vector p v a) %1 -> BO β (Bound p result, Mut α (Vector p v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity: write :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (p :: Multiplicity). (HasCallStack, Vector v a, α >= β, KnownMultiplicity p, PossiblyConsumable p a) => Int -> a %p -> Mut α (Vector p v a) %1 -> BO β (Mut α (Vector p v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: constant :: forall (v :: Type -> Type) a. Vector v a => Int -> a -> Linearly %1 -> Vector v a
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: copyAt :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Vector v a, α >= β) => Int -> Share α (Vector v a) -> BO β (Ur a)
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: copyAtMut :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Vector v a, α >= β) => Int -> Mut α (Vector v a) %1 -> BO β (Ur a, Mut α (Vector v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: copyToVector :: forall v a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (Vector v a, α >= β) => Borrow bk α (Vector v a) %1 -> BO β (Ur (v a), Borrow bk α (Vector v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: data Vector (v :: Type -> Type) a
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: empty :: forall (v :: Type -> Type) a. Vector v a => Linearly %1 -> Vector v a
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: fromList :: forall (v :: Type -> Type) a. Vector v a => [a] -> Linearly %1 -> Vector v a
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: fromVector :: Vector v a => v a -> Linearly %1 -> Vector v a
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: get :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (HasCallStack, Vector v a, α >= β) => Int -> Borrow bk α (Vector v a) %1 -> BO β (Ur a, Borrow bk α (Vector v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: head :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (HasCallStack, Vector v a, α >= β) => Borrow bk α (Vector v a) %1 -> BO β (Ur a, Borrow bk α (Vector v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: last :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (HasCallStack, Vector v a, α >= β) => Borrow bk α (Vector v a) %1 -> BO β (Ur a, Borrow bk α (Vector v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: modify :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Vector v a, α >= β) => Int -> (a -> a) -> Mut α (Vector v a) %1 -> BO β (Mut α (Vector v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: set :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Vector v a, α >= β) => Int -> a -> Mut α (Vector v a) %1 -> BO β (Ur a, Mut α (Vector v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: size :: forall (v :: Type -> Type) a (bk :: BorrowKind) (α :: Lifetime). Vector v a => Borrow bk α (Vector v a) %1 -> (Ur Int, Borrow bk α (Vector v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: splitAt :: forall (v :: Type -> Type) a (bk :: BorrowKind) (α :: Lifetime). Vector v a => Int -> Borrow bk α (Vector v a) %1 -> (Borrow bk α (Vector v a), Borrow bk α (Vector v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: swap :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Vector v a, α >= β) => Mut α (Vector v a) %1 -> Int -> Int -> BO β (Mut α (Vector v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: toList :: forall (v :: Type -> Type) a. Vector v a => Vector v a %1 -> Ur [a]
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: toVector :: Vector v a => Vector v a %1 -> Ur (v a)
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: unsafeFromMutable :: forall (v :: Type -> Type) a state. Vector v a => Mutable v state a %1 -> Linearly %1 -> Vector v a
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: unsafeFromVector :: Vector v a => v a %1 -> Linearly %1 -> Vector v a
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: unsafeGet :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (Vector v a, α >= β) => Int -> Borrow bk α (Vector v a) %1 -> BO β (Ur a, Borrow bk α (Vector v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: unsafeHead :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (Vector v a, α >= β) => Borrow bk α (Vector v a) %1 -> BO β (Ur a, Borrow bk α (Vector v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: unsafeLast :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (Vector v a, α >= β) => Borrow bk α (Vector v a) %1 -> BO β (Ur a, Borrow bk α (Vector v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: unsafeSet :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (Vector v a, α >= β) => Int -> a -> Mut α (Vector v a) %1 -> BO β (Ur a, Mut α (Vector v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: unsafeSwap :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (Vector v a, α >= β) => Mut α (Vector v a) %1 -> Int -> Int -> BO β (Mut α (Vector v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: unsafeUpdate :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) result. (Vector v a, α >= β) => Int -> (a -> BO β (Ur result, Ur a)) -> Mut α (Vector v a) %1 -> BO β (Ur result, Mut α (Vector v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: unsafeWrite :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (Vector v a, α >= β) => Int -> a -> Mut α (Vector v a) %1 -> BO β (Mut α (Vector v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: update :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) result. (HasCallStack, Vector v a, α >= β) => Int -> (a -> BO β (Ur result, Ur a)) -> Mut α (Vector v a) %1 -> BO β (Ur result, Mut α (Vector v a))
+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted: write :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Vector v a, α >= β) => Int -> a -> Mut α (Vector v a) %1 -> BO β (Mut α (Vector v a))
+ Data.Vector.Mutable.Growable.Linear.Borrow: capacity :: forall (bk :: BorrowKind) (α :: Lifetime) a. Borrow bk α (GrowableVector a) %1 -> (Ur Int, Borrow bk α (GrowableVector a))
+ Data.Vector.Mutable.Growable.Linear.Borrow: constant :: Int -> a -> Linearly %1 -> GrowableVector a
+ Data.Vector.Mutable.Growable.Linear.Borrow: copyAt :: forall a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Copyable a, α >= β) => Int -> Share α (GrowableVector a) -> BO β (Ur a)
+ Data.Vector.Mutable.Growable.Linear.Borrow: copyAtMut :: forall a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Copyable a, α >= β) => Int -> Mut α (GrowableVector a) %1 -> BO β (Ur a, Mut α (GrowableVector a))
+ Data.Vector.Mutable.Growable.Linear.Borrow: data GrowableVector a
+ Data.Vector.Mutable.Growable.Linear.Borrow: empty :: Linearly %1 -> GrowableVector a
+ Data.Vector.Mutable.Growable.Linear.Borrow: extend :: forall (α :: Lifetime) (β :: Lifetime) a. (HasCallStack, α >= β) => Vector a -> Mut α (GrowableVector a) %1 -> BO β (Mut α (GrowableVector a))
+ Data.Vector.Mutable.Growable.Linear.Borrow: fromList :: [a] -> Linearly %1 -> GrowableVector a
+ Data.Vector.Mutable.Growable.Linear.Borrow: fromVector :: Vector a -> Linearly %1 -> GrowableVector a
+ Data.Vector.Mutable.Growable.Linear.Borrow: get :: forall (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind) a. (HasCallStack, α >= β) => Int -> Borrow bk α (GrowableVector a) %1 -> BO β (Borrow bk α a)
+ Data.Vector.Mutable.Growable.Linear.Borrow: getContents :: forall (bk :: BorrowKind) (α :: Lifetime) a. Borrow bk α (GrowableVector a) %1 -> Borrow bk α (Vector a)
+ Data.Vector.Mutable.Growable.Linear.Borrow: head :: forall (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind) a. (HasCallStack, α >= β) => Borrow bk α (GrowableVector a) %1 -> BO β (Borrow bk α a)
+ Data.Vector.Mutable.Growable.Linear.Borrow: indicesMut :: forall (α :: Lifetime) (β :: Lifetime) a. (HasCallStack, α >= β) => Mut α (GrowableVector a) %1 -> [Int] %1 -> BO β [Mut α a]
+ Data.Vector.Mutable.Growable.Linear.Borrow: last :: forall (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind) a. (HasCallStack, α >= β) => Borrow bk α (GrowableVector a) %1 -> BO β (Borrow bk α a)
+ Data.Vector.Mutable.Growable.Linear.Borrow: modify :: forall (α :: Lifetime) (β :: Lifetime) a. (HasCallStack, α >= β) => Int -> (a %1 -> a) %1 -> Mut α (GrowableVector a) %1 -> BO β (Mut α (GrowableVector a))
+ Data.Vector.Mutable.Growable.Linear.Borrow: push :: forall (α :: Lifetime) (β :: Lifetime) a. (HasCallStack, α >= β) => a %1 -> Mut α (GrowableVector a) %1 -> BO β (Mut α (GrowableVector a))
+ Data.Vector.Mutable.Growable.Linear.Borrow: reserve :: forall (α :: Lifetime) (β :: Lifetime) a. (HasCallStack, α >= β) => Int -> Mut α (GrowableVector a) %1 -> BO β (Mut α (GrowableVector a))
+ Data.Vector.Mutable.Growable.Linear.Borrow: reserveAdditional :: forall (α :: Lifetime) (β :: Lifetime) a. (HasCallStack, α >= β) => Int -> Mut α (GrowableVector a) %1 -> BO β (Mut α (GrowableVector a))
+ Data.Vector.Mutable.Growable.Linear.Borrow: set :: forall (α :: Lifetime) (β :: Lifetime) a. (HasCallStack, α >= β) => Int -> a %1 -> Mut α (GrowableVector a) %1 -> BO β (a, Mut α (GrowableVector a))
+ Data.Vector.Mutable.Growable.Linear.Borrow: size :: forall (bk :: BorrowKind) (α :: Lifetime) a. Borrow bk α (GrowableVector a) %1 -> (Ur Int, Borrow bk α (GrowableVector a))
+ Data.Vector.Mutable.Growable.Linear.Borrow: swap :: forall (α :: Lifetime) (β :: Lifetime) a. (HasCallStack, α >= β) => Mut α (GrowableVector a) %1 -> Int -> Int -> BO β (Mut α (GrowableVector a))
+ Data.Vector.Mutable.Growable.Linear.Borrow: toList :: Movable a => GrowableVector a %1 -> Ur [a]
+ Data.Vector.Mutable.Growable.Linear.Borrow: toVector :: Movable a => GrowableVector a %1 -> Ur (Vector a)
+ Data.Vector.Mutable.Growable.Linear.Borrow: unsafeCopyAt :: forall a (α :: Lifetime) (β :: Lifetime). (Copyable a, α >= β) => Int -> Share α (GrowableVector a) -> BO β (Ur a)
+ Data.Vector.Mutable.Growable.Linear.Borrow: unsafeCopyAtMut :: forall a (α :: Lifetime) (β :: Lifetime). (Copyable a, α >= β) => Int -> Mut α (GrowableVector a) %1 -> BO β (Ur a, Mut α (GrowableVector a))
+ Data.Vector.Mutable.Growable.Linear.Borrow: unsafeFromMutable :: MVector state a %1 -> Linearly %1 -> GrowableVector a
+ Data.Vector.Mutable.Growable.Linear.Borrow: unsafeFromVector :: Vector a %1 -> Linearly %1 -> GrowableVector a
+ Data.Vector.Mutable.Growable.Linear.Borrow: unsafeGet :: forall (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind) a. α >= β => Int -> Borrow bk α (GrowableVector a) %1 -> BO β (Borrow bk α a)
+ Data.Vector.Mutable.Growable.Linear.Borrow: unsafeHead :: forall (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind) a. α >= β => Borrow bk α (GrowableVector a) %1 -> BO β (Borrow bk α a)
+ Data.Vector.Mutable.Growable.Linear.Borrow: unsafeIndicesMut :: forall (α :: Lifetime) (β :: Lifetime) a. α >= β => Mut α (GrowableVector a) %1 -> [Int] %1 -> BO β [Mut α a]
+ Data.Vector.Mutable.Growable.Linear.Borrow: unsafeLast :: forall (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind) a. α >= β => Borrow bk α (GrowableVector a) %1 -> BO β (Borrow bk α a)
+ Data.Vector.Mutable.Growable.Linear.Borrow: unsafeSet :: forall (α :: Lifetime) (β :: Lifetime) a. α >= β => Int -> a %1 -> Mut α (GrowableVector a) %1 -> BO β (a, Mut α (GrowableVector a))
+ Data.Vector.Mutable.Growable.Linear.Borrow: unsafeSwap :: forall (α :: Lifetime) (β :: Lifetime) a. α >= β => Mut α (GrowableVector a) %1 -> Int -> Int -> BO β (Mut α (GrowableVector a))
+ Data.Vector.Mutable.Growable.Linear.Borrow: unsafeUpdate :: forall (α :: Lifetime) (β :: Lifetime) a result. α >= β => Int -> (a %1 -> BO β (result, a)) %1 -> Mut α (GrowableVector a) %1 -> BO β (result, Mut α (GrowableVector a))
+ Data.Vector.Mutable.Growable.Linear.Borrow: update :: forall (α :: Lifetime) (β :: Lifetime) a result. (HasCallStack, α >= β) => Int -> (a %1 -> BO β (result, a)) %1 -> Mut α (GrowableVector a) %1 -> BO β (result, Mut α (GrowableVector a))
+ Data.Vector.Mutable.Growable.Linear.Borrow: withCapacity :: HasCallStack => Int -> Linearly %1 -> GrowableVector a
+ Data.Vector.Mutable.Growable.Linear.Borrow: withContent :: forall (bk :: BorrowKind) (α :: Lifetime) a result. Borrow bk α (GrowableVector a) %1 -> (forall (β :: Lifetime). () => Borrow bk (β /\ α) (Vector a) %1 -> BO (β /\ α) result) %1 -> BO α (result, Borrow bk α (GrowableVector a))
+ Data.Vector.Mutable.Growable.Linear.Borrow: withContent_ :: forall result (bk :: BorrowKind) (α :: Lifetime) a. Consumable result => Borrow bk α (GrowableVector a) %1 -> (forall (β :: Lifetime). () => Borrow bk (β /\ α) (Vector a) %1 -> BO (β /\ α) result) %1 -> BO α (Borrow bk α (GrowableVector a))
+ Data.Vector.Mutable.Linear.Borrow: modifyBoxedMVector :: (forall (α :: Lifetime). () => Mut α (Vector a) %1 -> BO α ()) %1 -> MVector s a %1 -> ST s ()
+ Data.Vector.Mutable.Linear.Borrow: modifyBoxedVector :: (forall (α :: Lifetime). () => Mut α (Vector a) %1 -> BO α ()) -> Vector a -> Vector a
+ Data.Vector.Mutable.Linear.Borrow: unsafeInplace :: forall (α :: Lifetime) (β :: Lifetime) a. α >= β => (forall s. () => MVector s a -> ST s ()) %1 -> Mut α (Vector a) %1 -> BO β (Mut α (Vector a))
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: capacity :: forall a (bk :: BorrowKind) (α :: Lifetime). Unbox a => Borrow bk α (GrowableVector a) %1 -> (Ur Int, Borrow bk α (GrowableVector a))
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: constant :: Unbox a => Int -> a -> Linearly %1 -> GrowableVector a
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: copyAt :: forall a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Unbox a, Copyable a, α >= β) => Int -> Share α (GrowableVector a) -> BO β (Ur a)
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: copyAtMut :: forall a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Unbox a, Copyable a, α >= β) => Int -> Mut α (GrowableVector a) %1 -> BO β (Ur a, Mut α (GrowableVector a))
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: data GrowableVector a
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: empty :: Unbox a => Linearly %1 -> GrowableVector a
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: extend :: forall a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Unbox a, α >= β) => Vector a -> Mut α (GrowableVector a) %1 -> BO β (Mut α (GrowableVector a))
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: fromList :: Unbox a => [a] %1 -> Linearly %1 -> GrowableVector a
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: fromVector :: Unbox a => Vector a -> Linearly %1 -> GrowableVector a
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: get :: forall a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (HasCallStack, Unbox a, α >= β) => Int -> Borrow bk α (GrowableVector a) %1 -> BO β (Borrow bk α a)
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: getContents :: forall a (bk :: BorrowKind) (α :: Lifetime). Unbox a => Borrow bk α (GrowableVector a) %1 -> Borrow bk α (Vector a)
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: head :: forall a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (HasCallStack, Unbox a, α >= β) => Borrow bk α (GrowableVector a) %1 -> BO β (Borrow bk α a)
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: last :: forall a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (HasCallStack, Unbox a, α >= β) => Borrow bk α (GrowableVector a) %1 -> BO β (Borrow bk α a)
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: modify :: forall a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Unbox a, α >= β) => Int -> (a %1 -> a) %1 -> Mut α (GrowableVector a) %1 -> BO β (Mut α (GrowableVector a))
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: push :: forall a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Unbox a, α >= β) => a %1 -> Mut α (GrowableVector a) %1 -> BO β (Mut α (GrowableVector a))
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: reserve :: forall a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Unbox a, α >= β) => Int -> Mut α (GrowableVector a) %1 -> BO β (Mut α (GrowableVector a))
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: reserveAdditional :: forall a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Unbox a, α >= β) => Int -> Mut α (GrowableVector a) %1 -> BO β (Mut α (GrowableVector a))
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: set :: forall a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Unbox a, α >= β) => Int -> a %1 -> Mut α (GrowableVector a) %1 -> BO β (a, Mut α (GrowableVector a))
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: size :: forall a (bk :: BorrowKind) (α :: Lifetime). Unbox a => Borrow bk α (GrowableVector a) %1 -> (Ur Int, Borrow bk α (GrowableVector a))
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: swap :: forall a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Unbox a, α >= β) => Mut α (GrowableVector a) %1 -> Int -> Int -> BO β (Mut α (GrowableVector a))
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: toList :: (Unbox a, Movable a) => GrowableVector a %1 -> Ur [a]
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: toVector :: (Unbox a, Movable a) => GrowableVector a %1 -> Ur (Vector a)
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: unsafeFromMutable :: Unbox a => MVector state a %1 -> Linearly %1 -> GrowableVector a
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: unsafeFromVector :: Unbox a => Vector a %1 -> Linearly %1 -> GrowableVector a
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: unsafeGet :: forall a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (Unbox a, α >= β) => Int -> Borrow bk α (GrowableVector a) %1 -> BO β (Borrow bk α a)
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: unsafeHead :: forall a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (Unbox a, α >= β) => Borrow bk α (GrowableVector a) %1 -> BO β (Borrow bk α a)
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: unsafeLast :: forall a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (Unbox a, α >= β) => Borrow bk α (GrowableVector a) %1 -> BO β (Borrow bk α a)
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: unsafeSet :: forall a (α :: Lifetime) (β :: Lifetime). (Unbox a, α >= β) => Int -> a %1 -> Mut α (GrowableVector a) %1 -> BO β (a, Mut α (GrowableVector a))
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: unsafeSwap :: forall a (α :: Lifetime) (β :: Lifetime). (Unbox a, α >= β) => Mut α (GrowableVector a) %1 -> Int -> Int -> BO β (Mut α (GrowableVector a))
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: unsafeUpdate :: forall a (α :: Lifetime) (β :: Lifetime) result. (Unbox a, α >= β) => Int -> (a %1 -> BO β (result, a)) %1 -> Mut α (GrowableVector a) %1 -> BO β (result, Mut α (GrowableVector a))
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: update :: forall a (α :: Lifetime) (β :: Lifetime) result. (HasCallStack, Unbox a, α >= β) => Int -> (a %1 -> BO β (result, a)) %1 -> Mut α (GrowableVector a) %1 -> BO β (result, Mut α (GrowableVector a))
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: withCapacity :: (HasCallStack, Unbox a) => Int -> Linearly %1 -> GrowableVector a
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: withContent :: forall a (bk :: BorrowKind) (α :: Lifetime) result. Unbox a => Borrow bk α (GrowableVector a) %1 -> (forall (β :: Lifetime). () => Borrow bk (β /\ α) (Vector a) %1 -> BO (β /\ α) result) %1 -> BO α (result, Borrow bk α (GrowableVector a))
+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow: withContent_ :: forall a result (bk :: BorrowKind) (α :: Lifetime). (Unbox a, Consumable result) => Borrow bk α (GrowableVector a) %1 -> (forall (β :: Lifetime). () => Borrow bk (β /\ α) (Vector a) %1 -> BO (β /\ α) result) %1 -> BO α (Borrow bk α (GrowableVector a))
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: constant :: Unbox a => Int -> a -> Linearly %1 -> Vector a
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: copyAt :: forall a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Unbox a, Copyable a, α >= β) => Int -> Share α (Vector a) -> BO β (Ur a)
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: copyAtMut :: forall a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Unbox a, Copyable a, α >= β) => Int -> Mut α (Vector a) %1 -> BO β (Ur a, Mut α (Vector a))
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: copyToVector :: forall a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (Unbox a, Copyable a, α >= β) => Borrow bk α (Vector a) %1 -> BO β (Ur (Vector a), Borrow bk α (Vector a))
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: data Vector a
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: empty :: Unbox a => Linearly %1 -> Vector a
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: fromList :: Unbox a => [a] %1 -> Linearly %1 -> Vector a
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: fromVector :: Unbox a => Vector a -> Linearly %1 -> Vector a
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: get :: forall a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (HasCallStack, Unbox a, α >= β) => Int -> Borrow bk α (Vector a) %1 -> BO β (Borrow bk α a)
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: head :: forall a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (HasCallStack, Unbox a, α >= β) => Borrow bk α (Vector a) %1 -> BO β (Borrow bk α a)
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: last :: forall a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (HasCallStack, Unbox a, α >= β) => Borrow bk α (Vector a) %1 -> BO β (Borrow bk α a)
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: modify :: forall a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Unbox a, α >= β) => Int -> (a %1 -> a) %1 -> Mut α (Vector a) %1 -> BO β (Mut α (Vector a))
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: set :: forall a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Unbox a, α >= β) => Int -> a %1 -> Mut α (Vector a) %1 -> BO β (a, Mut α (Vector a))
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: size :: forall a (bk :: BorrowKind) (α :: Lifetime). Unbox a => Borrow bk α (Vector a) %1 -> (Ur Int, Borrow bk α (Vector a))
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: splitAt :: forall a (bk :: BorrowKind) (α :: Lifetime). Unbox a => Int %1 -> Borrow bk α (Vector a) %1 -> (Borrow bk α (Vector a), Borrow bk α (Vector a))
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: swap :: forall a (α :: Lifetime) (β :: Lifetime). (HasCallStack, Unbox a, α >= β) => Mut α (Vector a) %1 -> Int -> Int -> BO β (Mut α (Vector a))
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: toList :: (Unbox a, Movable a) => Vector a %1 -> Ur [a]
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: toVector :: (Unbox a, Movable a) => Vector a %1 -> Ur (Vector a)
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: unsafeFromMutable :: Unbox a => MVector state a %1 -> Linearly %1 -> Vector a
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: unsafeFromVector :: Unbox a => Vector a %1 -> Linearly %1 -> Vector a
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: unsafeGet :: forall a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (Unbox a, α >= β) => Int -> Borrow bk α (Vector a) %1 -> BO β (Borrow bk α a)
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: unsafeHead :: forall a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (Unbox a, α >= β) => Borrow bk α (Vector a) %1 -> BO β (Borrow bk α a)
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: unsafeLast :: forall a (α :: Lifetime) (β :: Lifetime) (bk :: BorrowKind). (Unbox a, α >= β) => Borrow bk α (Vector a) %1 -> BO β (Borrow bk α a)
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: unsafeSet :: forall a (α :: Lifetime) (β :: Lifetime). (Unbox a, α >= β) => Int -> a %1 -> Mut α (Vector a) %1 -> BO β (a, Mut α (Vector a))
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: unsafeSwap :: forall a (α :: Lifetime) (β :: Lifetime). (Unbox a, α >= β) => Mut α (Vector a) %1 -> Int -> Int -> BO β (Mut α (Vector a))
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: unsafeUpdate :: forall a (α :: Lifetime) (β :: Lifetime) result. (Unbox a, α >= β) => Int -> (a %1 -> BO β (result, a)) %1 -> Mut α (Vector a) %1 -> BO β (result, Mut α (Vector a))
+ Data.Vector.Unboxed.Mutable.Linear.Borrow: update :: forall a (α :: Lifetime) (β :: Lifetime) result. (HasCallStack, Unbox a, α >= β) => Int -> (a %1 -> BO β (result, a)) %1 -> Mut α (Vector a) %1 -> BO β (result, Mut α (Vector a))
- Control.Concurrent.DivideConquer.Linear: DivideConquer :: (forall (β :: Lifetime). α >= β => Mut β a %1 -> BO β (Result β t a)) -> DivideConquer (α :: Lifetime) (t :: Type -> Type) a
+ Control.Concurrent.DivideConquer.Linear: DivideConquer :: (forall (β :: Lifetime). α >= β => Mut β a %1 -> BO β (Ur c)) -> (forall (β :: Lifetime). α >= β => c -> Mut β a %1 -> BO β (Result c β t a r)) -> Conquer c α t a r -> DivideConquer c (α :: Lifetime) (t :: Type -> Type) a r
- Control.Concurrent.DivideConquer.Linear: [divide] :: DivideConquer (α :: Lifetime) (t :: Type -> Type) a -> forall (β :: Lifetime). α >= β => Mut β a %1 -> BO β (Result β t a)
+ Control.Concurrent.DivideConquer.Linear: [divide] :: DivideConquer c (α :: Lifetime) (t :: Type -> Type) a r -> forall (β :: Lifetime). α >= β => c -> Mut β a %1 -> BO β (Result c β t a r)
- Control.Concurrent.DivideConquer.Linear: data DivideConquer (α :: Lifetime) (t :: Type -> Type) a
+ Control.Concurrent.DivideConquer.Linear: data DivideConquer c (α :: Lifetime) (t :: Type -> Type) a r
- Control.Concurrent.DivideConquer.Linear: divideAndConquer :: forall (α :: Lifetime) (β :: Lifetime) (t :: Type -> Type) a. (Traversable t, Consumable (t ()), α >= β) => Int -> DivideConquer α t a -> Mut α a %1 -> BO β (Mut α a)
+ Control.Concurrent.DivideConquer.Linear: divideAndConquer :: forall c (α :: Lifetime) (β :: Lifetime) (t :: Type -> Type) a g. (Traversable t, α >= β, RandomGen g) => g -> Int -> DivideConquer c α t a () -> Mut α a %1 -> BO β (Mut α a)
- Control.Concurrent.DivideConquer.Linear: qsortDC :: forall a (α :: Lifetime) (β :: Lifetime). (Ord a, Copyable a, α >= β) => Int -> Int -> Mut α (Vector a) %1 -> BO β (Mut α (Vector a))
+ Control.Concurrent.DivideConquer.Linear: qsortDC :: forall (v :: Type -> Type) a (α :: Lifetime) (β :: Lifetime) g. (Vector v a, Ord a, α >= β, RandomGen g) => g -> Int -> Int -> Mut α (Vector v a) %1 -> BO β (Mut α (Vector v a))
- Control.Monad.Borrow.Pure: data Alias (ak :: AliasKind) (α :: Lifetime) a
+ Control.Monad.Borrow.Pure: data Alias (ak :: AliasKind) a
- Control.Monad.Borrow.Pure: genericSplit :: forall f x (ak :: AliasKind) (α :: Lifetime). GenericDistributesAlias f => Alias ak α (f x) %1 -> f (Alias ak α x)
+ Control.Monad.Borrow.Pure: genericSplit :: forall f x (ak :: AliasKind). GenericDistributesAlias f => Alias ak (f x) %1 -> f (Alias ak x)
- Control.Monad.Borrow.Pure: split :: forall f x (ak :: AliasKind) (α :: Lifetime). DistributesAlias f => Alias ak α (f x) %1 -> f (Alias ak α x)
+ Control.Monad.Borrow.Pure: split :: forall f x (ak :: AliasKind). DistributesAlias f => Alias ak (f x) %1 -> f (Alias ak x)
- Control.Monad.Borrow.Pure: splitEither :: forall (ak :: AliasKind) (α :: Lifetime) a b. Alias ak α (Either a b) %1 -> Either (Alias ak α a) (Alias ak α b)
+ Control.Monad.Borrow.Pure: splitEither :: forall (ak :: AliasKind) a b. Alias ak (Either a b) %1 -> Either (Alias ak a) (Alias ak b)
- Control.Monad.Borrow.Pure: splitPair :: forall (ak :: AliasKind) (α :: Lifetime) a b. Alias ak α (a, b) %1 -> (Alias ak α a, Alias ak α b)
+ Control.Monad.Borrow.Pure: splitPair :: forall (ak :: AliasKind) a b. Alias ak (a, b) %1 -> (Alias ak a, Alias ak b)
- Control.Monad.Borrow.Pure: type Borrow (bk :: BorrowKind) = Alias 'Borrow bk
+ Control.Monad.Borrow.Pure: type Borrow (bk :: BorrowKind) (α :: Lifetime) = Alias 'Borrow bk α
- Control.Monad.Borrow.Pure: type Lend = Alias 'Lend
+ Control.Monad.Borrow.Pure: type Lend (α :: Lifetime) = Alias 'Lend α
- Control.Monad.Borrow.Pure: type Mut = Borrow 'Mut
+ Control.Monad.Borrow.Pure: type Mut (α :: Lifetime) = Borrow 'Mut α
- Control.Monad.Borrow.Pure: type Share = Borrow 'Share
+ Control.Monad.Borrow.Pure: type Share (α :: Lifetime) = Borrow 'Share α
- Control.Monad.Borrow.Pure.BO: data Alias (ak :: AliasKind) (α :: Lifetime) a
+ Control.Monad.Borrow.Pure.BO: data Alias (ak :: AliasKind) a
- Control.Monad.Borrow.Pure.BO: genericSplit :: forall f x (ak :: AliasKind) (α :: Lifetime). GenericDistributesAlias f => Alias ak α (f x) %1 -> f (Alias ak α x)
+ Control.Monad.Borrow.Pure.BO: genericSplit :: forall f x (ak :: AliasKind). GenericDistributesAlias f => Alias ak (f x) %1 -> f (Alias ak x)
- Control.Monad.Borrow.Pure.BO: split :: forall f x (ak :: AliasKind) (α :: Lifetime). DistributesAlias f => Alias ak α (f x) %1 -> f (Alias ak α x)
+ Control.Monad.Borrow.Pure.BO: split :: forall f x (ak :: AliasKind). DistributesAlias f => Alias ak (f x) %1 -> f (Alias ak x)
- Control.Monad.Borrow.Pure.BO: splitEither :: forall (ak :: AliasKind) (α :: Lifetime) a b. Alias ak α (Either a b) %1 -> Either (Alias ak α a) (Alias ak α b)
+ Control.Monad.Borrow.Pure.BO: splitEither :: forall (ak :: AliasKind) a b. Alias ak (Either a b) %1 -> Either (Alias ak a) (Alias ak b)
- Control.Monad.Borrow.Pure.BO: splitPair :: forall (ak :: AliasKind) (α :: Lifetime) a b. Alias ak α (a, b) %1 -> (Alias ak α a, Alias ak α b)
+ Control.Monad.Borrow.Pure.BO: splitPair :: forall (ak :: AliasKind) a b. Alias ak (a, b) %1 -> (Alias ak a, Alias ak b)
- Control.Monad.Borrow.Pure.BO: type Borrow (bk :: BorrowKind) = Alias 'Borrow bk
+ Control.Monad.Borrow.Pure.BO: type Borrow (bk :: BorrowKind) (α :: Lifetime) = Alias 'Borrow bk α
- Control.Monad.Borrow.Pure.BO: type Lend = Alias 'Lend
+ Control.Monad.Borrow.Pure.BO: type Lend (α :: Lifetime) = Alias 'Lend α
- Control.Monad.Borrow.Pure.BO: type Mut = Borrow 'Mut
+ Control.Monad.Borrow.Pure.BO: type Mut (α :: Lifetime) = Borrow 'Mut α
- Control.Monad.Borrow.Pure.BO: type Share = Borrow 'Share
+ Control.Monad.Borrow.Pure.BO: type Share (α :: Lifetime) = Borrow 'Share α
- Control.Monad.Borrow.Pure.BO.Unsafe: UnsafeAlias :: a -> Alias (ak :: AliasKind) (α :: Lifetime) a
+ Control.Monad.Borrow.Pure.BO.Unsafe: UnsafeAlias :: a -> Alias (ak :: AliasKind) a
- Control.Monad.Borrow.Pure.BO.Unsafe: newtype Alias (ak :: AliasKind) (α :: Lifetime) a
+ Control.Monad.Borrow.Pure.BO.Unsafe: newtype Alias (ak :: AliasKind) a
- Control.Monad.Borrow.Pure.BO.Unsafe: unsafeMapAlias :: forall a b (ak :: AliasKind) (α :: Lifetime). (a %1 -> b) %1 -> Alias ak α a %1 -> Alias ak α b
+ Control.Monad.Borrow.Pure.BO.Unsafe: unsafeMapAlias :: forall a b (ak :: AliasKind). (a %1 -> b) %1 -> Alias ak a %1 -> Alias ak b
- Control.Monad.Borrow.Pure.BO.Unsafe: unsafeUnalias :: forall (ak :: AliasKind) (α :: Lifetime) a. Alias ak α a %1 -> a
+ Control.Monad.Borrow.Pure.BO.Unsafe: unsafeUnalias :: forall (ak :: AliasKind) a. Alias ak a %1 -> a
- Control.Monad.Borrow.Pure.Copyable: genericLiftCopy :: forall f (bk :: BorrowKind) a b (α :: Lifetime). GenericCopyable1 f => (Borrow bk α a %1 -> b) -> Borrow bk α (f a) %1 -> f b
+ Control.Monad.Borrow.Pure.Copyable: genericLiftCopy :: forall f (bk :: BorrowKind) (α :: Lifetime) a b. GenericCopyable1 f => (Borrow bk α a %1 -> b) -> Borrow bk α (f a) %1 -> f b
- Control.Monad.Borrow.Pure.Experimental.Borrows: [:-] :: forall (bk :: BorrowKind) (α :: Lifetime) x (xs1 :: [Type]). !Borrow bk α x -> !Borrows bk α xs1 -> Borrows bk α (x ': xs1)
+ Control.Monad.Borrow.Pure.Experimental.Borrows: [:-] :: forall (k :: AliasKind) x (xs1 :: [Type]). !Alias k x -> !Aliases k xs1 -> Aliases k (x ': xs1)
- Control.Monad.Borrow.Pure.Experimental.Borrows: [BNil] :: forall (bk :: BorrowKind) (α :: Lifetime). Borrows bk α ('[] :: [Type])
+ Control.Monad.Borrow.Pure.Experimental.Borrows: [BNil] :: forall (k :: AliasKind). Aliases k ('[] :: [Type])
- Control.Monad.Borrow.Pure.Experimental.Loop: forReborrowing :: forall {k} t bor (α :: Lifetime) (xs :: k) b c. (Traversable t, Reborrowable bor) => bor α xs %1 -> t b %1 -> (forall (β :: Lifetime). () => bor (β /\ α) xs %1 -> b %1 -> BO (β /\ α) c) -> BO α (t c, bor α xs)
+ Control.Monad.Borrow.Pure.Experimental.Loop: forReborrowing :: forall {k} t bor (xs :: k) b (α :: Lifetime) c. (Traversable t, Reborrowable bor) => bor xs %1 -> t b %1 -> (forall (β :: Lifetime). () => WithLifetime bor (β /\ LifetimeOf bor) xs %1 -> b %1 -> BO (β /\ α) c) -> BO α (t c, bor xs)
- Control.Monad.Borrow.Pure.Experimental.Loop: forReborrowingOf_ :: forall {k} bor s a (α :: Lifetime) (xs :: k). Reborrowable bor => Fold s a %1 -> bor α xs %1 -> s %1 -> (forall (β :: Lifetime). () => bor (β /\ α) xs %1 -> a %1 -> BO (β /\ α) ()) -> BO α (bor α xs)
+ Control.Monad.Borrow.Pure.Experimental.Loop: forReborrowingOf_ :: forall {k} bor s a (xs :: k) (α :: Lifetime). Reborrowable bor => Fold s a %1 -> bor xs %1 -> s %1 -> (forall (β :: Lifetime). () => WithLifetime bor (β /\ LifetimeOf bor) xs %1 -> a %1 -> BO (β /\ α) ()) -> BO α (bor xs)
- Control.Monad.Borrow.Pure.Experimental.Loop: forReborrowing_ :: forall {k} t bor (α :: Lifetime) (xs :: k) a. (Foldable t, Reborrowable bor) => bor α xs %1 -> t a %1 -> (forall (β :: Lifetime). () => bor (β /\ α) xs %1 -> a %1 -> BO (β /\ α) ()) -> BO α (bor α xs)
+ Control.Monad.Borrow.Pure.Experimental.Loop: forReborrowing_ :: forall {k} t bor (xs :: k) a (α :: Lifetime). (Foldable t, Reborrowable bor) => bor xs %1 -> t a %1 -> (forall (β :: Lifetime). () => WithLifetime bor (β /\ LifetimeOf bor) xs %1 -> a %1 -> BO (β /\ α) ()) -> BO α (bor xs)
- Control.Monad.Borrow.Pure.Experimental.Loop: iforReborrowingOf_ :: forall {k} bor i s a (α :: Lifetime) (xs :: k). Reborrowable bor => IndexedFold i s a %1 -> bor α xs %1 -> s %1 -> (forall (β :: Lifetime). () => bor (β /\ α) xs %1 -> i %1 -> a %1 -> BO (β /\ α) ()) -> BO α (bor α xs)
+ Control.Monad.Borrow.Pure.Experimental.Loop: iforReborrowingOf_ :: forall {k} bor i s a (xs :: k) (α :: Lifetime). Reborrowable bor => IndexedFold i s a %1 -> bor xs %1 -> s %1 -> (forall (β :: Lifetime). () => WithLifetime bor (β /\ LifetimeOf bor) xs %1 -> i %1 -> a %1 -> BO (β /\ α) ()) -> BO α (bor xs)
- Control.Monad.Borrow.Pure.Experimental.Loop: iforReborrowing_ :: forall {k} i t bor (α :: Lifetime) (xs :: k) a. (FoldableWithIndex i t, Reborrowable bor) => bor α xs %1 -> t a %1 -> (forall (β :: Lifetime). () => bor (β /\ α) xs %1 -> i %1 -> a %1 -> BO (β /\ α) ()) -> BO α (bor α xs)
+ Control.Monad.Borrow.Pure.Experimental.Loop: iforReborrowing_ :: forall {k} i t bor (xs :: k) a (α :: Lifetime). (FoldableWithIndex i t, Reborrowable bor) => bor xs %1 -> t a %1 -> (forall (β :: Lifetime). () => WithLifetime bor (β /\ LifetimeOf bor) xs %1 -> i %1 -> a %1 -> BO (β /\ α) ()) -> BO α (bor xs)
- Control.Monad.Borrow.Pure.Experimental.Reborrowable: class Reborrowable (bor :: Lifetime -> k -> Type)
+ Control.Monad.Borrow.Pure.Experimental.Reborrowable: class bor ~ WithLifetime bor LifetimeOf bor :: k -> Type => Reborrowable (bor :: k -> Type) where {
- Control.Monad.Borrow.Pure.Experimental.Reborrowable: locally :: forall {k} bor (α :: Lifetime) (a :: k) (α' :: Lifetime) r. Reborrowable bor => bor α a %1 -> (forall (β :: Lifetime). () => bor (β /\ α) a %1 -> BO (β /\ α') r) %1 -> BO α' (r, bor α a)
+ Control.Monad.Borrow.Pure.Experimental.Reborrowable: locally :: forall (a :: k) (α' :: Lifetime) r. Reborrowable bor => bor a %1 -> (forall (β :: Lifetime). () => WithLifetime bor (β /\ LifetimeOf bor) a %1 -> BO (β /\ α') r) %1 -> BO α' (r, bor a)
- Control.Monad.Borrow.Pure.Experimental.Reborrowable: locally' :: forall (α :: Lifetime) (a :: k) (α' :: Lifetime) r. Reborrowable bor => bor α a %1 -> (forall (β :: Lifetime). () => bor (β /\ α) a %1 -> BO (β /\ α') (After β r)) %1 -> BO α' (r, bor α a)
+ Control.Monad.Borrow.Pure.Experimental.Reborrowable: locally' :: forall (a :: k) (α' :: Lifetime) r. Reborrowable bor => bor a %1 -> (forall (β :: Lifetime). () => WithLifetime bor (β /\ LifetimeOf bor) a %1 -> BO (β /\ α') (After β r)) %1 -> BO α' (r, bor a)
- Control.Monad.Borrow.Pure.Experimental.Reborrowable: locally_ :: forall {k} bor r (α :: Lifetime) (a :: k) (α' :: Lifetime). (Reborrowable bor, Consumable r) => bor α a %1 -> (forall (β :: Lifetime). () => bor (β /\ α) a %1 -> BO (β /\ α') r) %1 -> BO α' (bor α a)
+ Control.Monad.Borrow.Pure.Experimental.Reborrowable: locally_ :: forall r (a :: k) (α' :: Lifetime). (Reborrowable bor, Consumable r) => bor a %1 -> (forall (β :: Lifetime). () => WithLifetime bor (β /\ LifetimeOf bor) a %1 -> BO (β /\ α') r) %1 -> BO α' (bor a)
- Data.Record.Linear.Borrow.Experimental.Split: (.#) :: forall (field :: Symbol) r a (k :: BorrowKind) (α :: Lifetime). Borrow k α r %1 -> RecordLabel r field a -> Borrow k α a
+ Data.Record.Linear.Borrow.Experimental.Split: (.#) :: forall (field :: Symbol) r a (bk :: BorrowKind) (α :: Lifetime). Borrow bk α r %1 -> RecordLabel r field a -> Borrow bk α a
- Data.Vector.Mutable.Linear.Borrow: toList :: Copyable a => Vector a %1 -> Ur [a]
+ Data.Vector.Mutable.Linear.Borrow: toList :: Movable a => Vector a %1 -> Ur [a]
- Data.Vector.Mutable.Linear.Borrow: toVector :: Copyable a => Vector a %1 -> Ur (Vector a)
+ Data.Vector.Mutable.Linear.Borrow: toVector :: Movable a => Vector a %1 -> Ur (Vector a)
Files
- CHANGELOG.md +65/−0
- app/artifact-runner.hs +0/−238
- app/convert-qsort-bench-csv.hs +66/−66
- app/fft.hs +17/−0
- bench/fft.hs +139/−0
- bench/qsort.hs +118/−2
- bench/suite/Main.hs +5/−0
- bench/suite/PureBorrow/Bench/CopyAt.hs +176/−0
- bench/suite/PureBorrow/Bench/Growable.hs +97/−0
- bench/suite/PureBorrow/Bench/HashMap.hs +270/−0
- bench/suite/PureBorrow/Bench/Ingredients.hs +21/−0
- bench/suite/PureBorrow/Bench/MultiStoreScan.hs +12/−0
- bench/suite/PureBorrow/Bench/ScopeDensity.hs +478/−0
- bench/suite/PureBorrow/Bench/Unboxed.hs +11/−0
- bench/suite/PureBorrow/Bench/Worklist/Resume.hs +12/−0
- dockerfiles/artifact/Dockerfile +0/−44
- internal-src/demo-impl/PureBorrow/Demo/Fft.hs +158/−0
- internal-src/demo-impl/PureBorrow/Demo/QSort.hs +121/−0
- internal-src/qsort-bench-suites/PureBorrow/Internal/Bench/QSort.hs +0/−169
- internal-src/qsort-demo-impl/PureBorrow/Demo/QSort.hs +0/−111
- internal-src/test-bench-common/PureBorrow/Internal/Bench/MultiStoreScan.hs +1866/−0
- internal-src/test-bench-common/PureBorrow/Internal/Bench/Unboxed.hs +421/−0
- internal-src/test-bench-common/PureBorrow/Internal/Bench/Worklist/Resume.hs +2688/−0
- pure-borrow.cabal +242/−70
- src/Control/Concurrent/DivideConquer/Linear.hs +590/−102
- src/Control/Concurrent/DivideConquer/Linear/Internal.hs +67/−0
- src/Control/Concurrent/DivideConquer/Utils/QueuePool.hs +160/−80
- src/Control/Concurrent/DivideConquer/Utils/Semaphore.hs +58/−0
- src/Control/Concurrent/Queue/ChaseLev.hs +226/−0
- src/Control/Concurrent/STM/TMDeque.hs +0/−229
- src/Control/Concurrent/STM/TMDequeRingBuffer.hs +0/−218
- src/Control/Monad/Borrow/Pure.hs +45/−0
- src/Control/Monad/Borrow/Pure/BO.hs +96/−0
- src/Control/Monad/Borrow/Pure/BO/Internal.hs +225/−38
- src/Control/Monad/Borrow/Pure/BO/Unsafe.hs +2/−0
- src/Control/Monad/Borrow/Pure/Clone.hs +1/−1
- src/Control/Monad/Borrow/Pure/Copyable.hs +83/−20
- src/Control/Monad/Borrow/Pure/Experimental/Borrows.hs +242/−12
- src/Control/Monad/Borrow/Pure/Experimental/Loop.hs +44/−17
- src/Control/Monad/Borrow/Pure/Experimental/Reborrowable.hs +95/−26
- src/Control/Monad/Borrow/Pure/Lifetime/Token/Internal.hs +6/−0
- src/Control/Monad/Borrow/Pure/Utils.hs +12/−0
- src/Data/Coerce/Directed/Internal.hs +6/−0
- src/Data/HashMap/RobinHood/Mutable/Linear.hs +58/−0
- src/Data/HashMap/RobinHood/Mutable/Linear/Borrow.hs +274/−0
- src/Data/HashMap/RobinHood/Mutable/Linear/Borrow/Internal.hs +144/−0
- src/Data/HashMap/RobinHood/Mutable/Linear/Internal.hs +778/−0
- src/Data/Record/Linear/Borrow/Experimental/PatternMatch.hs +6/−2
- src/Data/Record/Linear/Borrow/Experimental/Split.hs +4/−4
- src/Data/Ref/Linear/Borrow.hs +8/−0
- src/Data/Vector/Generic/Mutable/Growable/Linear/Borrow/Unrestricted.hs +79/−0
- src/Data/Vector/Generic/Mutable/Growable/Linear/Borrow/Unrestricted/Internal.hs +867/−0
- src/Data/Vector/Generic/Mutable/Linear/Borrow/Experimental/Multiplicity.hs +58/−0
- src/Data/Vector/Generic/Mutable/Linear/Borrow/Experimental/Multiplicity/Internal.hs +710/−0
- src/Data/Vector/Generic/Mutable/Linear/Borrow/Unrestricted.hs +47/−0
- src/Data/Vector/Generic/Mutable/Linear/Borrow/Unrestricted/Internal.hs +477/−0
- src/Data/Vector/Mutable/Growable/Linear/Borrow.hs +75/−0
- src/Data/Vector/Mutable/Growable/Linear/Borrow/Internal.hs +894/−0
- src/Data/Vector/Mutable/Linear/Borrow.hs +75/−47
- src/Data/Vector/Mutable/Linear/Borrow/Internal.hs +61/−0
- src/Data/Vector/Unboxed/Mutable/Growable/Linear/Borrow.hs +45/−0
- src/Data/Vector/Unboxed/Mutable/Growable/Linear/Borrow/Internal.hs +797/−0
- src/Data/Vector/Unboxed/Mutable/Linear/Borrow.hs +36/−0
- src/Data/Vector/Unboxed/Mutable/Linear/Borrow/Internal.hs +535/−0
- test-inspection/Main.hs +22/−0
- test-inspection/PureBorrow/Inspection/Fft.hs +77/−0
- test-inspection/PureBorrow/Inspection/Flags.hs +29/−0
- test-inspection/PureBorrow/Inspection/GenericGrowableUnrestricted.hs +68/−0
- test-inspection/PureBorrow/Inspection/MultiStoreScan.hs +122/−0
- test-inspection/PureBorrow/Inspection/QSort.hs +75/−0
- test-inspection/PureBorrow/Inspection/Sublifetime.hs +387/−0
- test-inspection/PureBorrow/Inspection/Worklist/Resume.hs +130/−0
- test/Control/Concurrent/DivideConquer/LinearSpec.hs +256/−13
- test/Control/Monad/Borrow/Pure/BOSpec.hs +570/−0
- test/Control/Monad/Borrow/Pure/CopyableSpec.hs +157/−0
- test/Control/Monad/Borrow/Pure/Experimental/Borrows/TypingCases.hs +77/−0
- test/Control/Monad/Borrow/Pure/Experimental/BorrowsSpec.hs +90/−0
- test/Data/HashMap/RobinHood/Mutable/Linear/BorrowSpec.hs +237/−0
- test/Data/HashMap/RobinHood/Mutable/LinearSpec.hs +231/−0
- test/Data/HashMap/RobinHood/Mutable/LinearSpec/Cases.hs +113/−0
- test/Data/Vector/Generic/Mutable/Growable/Linear/Borrow/Unrestricted/TypingCases.hs +229/−0
- test/Data/Vector/Generic/Mutable/Growable/Linear/Borrow/UnrestrictedSpec.hs +668/−0
- test/Data/Vector/Generic/Mutable/Linear/Borrow/Experimental/Multiplicity/TypingCases.hs +103/−0
- test/Data/Vector/Generic/Mutable/Linear/Borrow/Experimental/MultiplicitySpec.hs +317/−0
- test/Data/Vector/Generic/Mutable/Linear/Borrow/Unrestricted/TypingCases.hs +105/−0
- test/Data/Vector/Generic/Mutable/Linear/Borrow/UnrestrictedSpec.hs +446/−0
- test/Data/Vector/Mutable/Growable/Linear/BorrowSpec.hs +908/−0
- test/Data/Vector/Mutable/Growable/Linear/TypingCases.hs +172/−0
- test/Data/Vector/Mutable/Linear/BorrowSpec.hs +171/−1
- test/Data/Vector/Unboxed/Mutable/Growable/Linear/BorrowSpec.hs +948/−0
- test/Data/Vector/Unboxed/Mutable/Growable/Linear/TypingCases.hs +205/−0
- test/Data/Vector/Unboxed/Mutable/Linear/BorrowSpec.hs +733/−0
- test/Data/Vector/Unboxed/Mutable/Linear/TypingCases.hs +124/−0
- test/PureBorrow/Internal/Bench/MultiStoreScanSpec.hs +96/−0
- test/PureBorrow/Internal/Bench/Worklist/ResumeSpec.hs +248/−0
CHANGELOG.md view
@@ -1,5 +1,70 @@ # Revision history for pure-borrow +## 0.1.0.0 - 2026-09-19++### Breaking changes++- `Alias` carries its lifetime in the alias kind instead of as its own parameter, so a kind-polymorphic function can abstract over an alias including its lifetime:++ ```haskell+ -- before+ newtype Alias ak α a = UnsafeAlias a+ data AliasKind = Borrow BorrowKind | Lend+ -- after+ newtype Alias ak a = UnsafeAlias a+ data AliasKind = Borrow BorrowKind Lifetime | Lend Lifetime+ ```++ `Mut α`, `Share α` and `Lend α` are unchanged for users.+ The bundle is now `Aliases k xs`, with `Borrows bk α`, `Muts α`, `Shares α` and `Lends α` as synonyms; `Experimental.Loop` no longer re-exports its constructors, and `Experimental.Borrows` absorbs the reborrow instances from `Experimental.Reborrowable`.+- `Reborrowable` takes the lifetime through associated types, so an instance is written for the applied type:++ ```haskell+ -- before+ class Reborrowable bor where+ locally' :: bor α a %1 -> (forall β. bor (β /\ α) a %1 -> BO (β /\ α') (After β r)) %1 -> BO α' (r, bor α a)+ -- after+ class (bor ~ WithLifetime bor (LifetimeOf bor)) => Reborrowable bor where+ type LifetimeOf bor :: Lifetime+ type WithLifetime bor (β :: Lifetime) :: k -> Type+ locally' :: bor a %1 -> (forall β. WithLifetime bor (β /\ LifetimeOf bor) a %1 -> BO (β /\ α') (After β r)) %1 -> BO α' (r, bor a)+ ```++- `Data.Vector.Mutable.Linear.Borrow`'s `toVector` and `toList` require `Movable` rather than `Copyable`, and are now \(O(n)\): each element goes through `move`, which is what hands a linearly owned buffer to a GC-owned result.+- `Affine (Aliases k xs)` is constrained to borrow kinds, so a `Lends` bundle can no longer be discarded in safe code, matching the scalar `Lend`.+- `Control.Concurrent.STM.TMDeque` and `TMDequeRingBuffer` are removed; the scheduler runs on `Control.Concurrent.Queue.ChaseLev`.+- `DivideConquer` is now generalized to run on generic vector and qsort benchmarks are now run against unboxed vectors.++### Clarified++- `Copyable.copy` must complete the copy and return it in WHNF.+ This was always the contract — a `copy` returning a thunk that still reaches into the borrowed structure was never sound — but nothing stated or enforced it.+ The class Haddock now says so, and the generic machinery forces each component copy, so derived instances discharge it for free.++### New++- Five borrow-aware mutable vector families beside the existing boxed `Data.Vector.Mutable.Linear.Borrow`.+ Element-owning: fixed unboxed, and growable boxed and unboxed.+ Backend-generic and *not* element-owning, with GC-owned entries and \(O(1)\) consuming freeze: `Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted` and its growable counterpart.+ `…Borrow.Experimental.Multiplicity` parameterizes the same storage by element multiplicity.+- The growable families keep their length and backing buffer behind a stable header, and project the initialized prefix through `getContents` or the rank-2 `withContent`/`withContent_` as a fixed borrow of the same kind and lifetime.+ Growth replaces the buffer, so it is unavailable while a content borrow is live.+- Added a Robin Hood hash table with backward-shift deletion.+ `Data.HashMap.RobinHood.Mutable.Linear` is the owned table, whose operations are ordinary linear functions; `Data.HashMap.RobinHood.Mutable.Linear.Borrow` keeps one behind a linear `Ref` so that it can be mutated, and grown, through a `Mut` borrow.+ Its keys and values are GC-owned, and it caches a fingerprint per slot so that a key with a cheap hash and an expensive equality is rejected without a full comparison.+ This adds a dependency on `hashable`.+- `subShare` shortens a `Share` without opening a scope, and `Par` is an applicative for parallel composition inside `BO`, with directly inlinable methods.+- `BO.Unsafe` exports `unsafeCastAlias`, a `coerceLin` retagging that replaces a bare `unsafeCoerce`.+- The divide-and-conquer scheduler now correctly exposes `Result` to allow users to write their own algorithm.+ Now it further supports non-trivial "conquer" phase and now comes with FFT example.++### Performance++- Delimiting a sublifetime got much cheaper in `srunBO`/`srunBO_`, `sharing`/`sharing'`/`sharing_`, `reborrowing`/`reborrowing'`/`reborrowing_` and `reborrowings`/`reborrowings'`/`reborrowings_`.+ The `slow` flag restores the old, safe-but-slow implementation.+ Measured on a scope-dominated microbenchmark, the plural scopes went from 40 bytes and 4.5 ns per crossing to 0 bytes and 1.2–1.6 ns.+ The FFT example picks this up through `iterReborrowing_`, which crosses a plural scope per iteration of its setup phase: −4.8% allocation and −5.6% wall at 2^20 points.+ ## 0.0.0.0 -- 2026-05-05 This is the first release on Hackage :tada:
− app/artifact-runner.hs
@@ -1,238 +0,0 @@-{-# LANGUAGE GHC2021 #-}-{-# LANGUAGE BlockArguments #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE DerivingVia #-}-{-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE OverloadedRecordDot #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE NoFieldSelectors #-}-{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}--module Main (main) where--import Control.Applicative-import Control.Concurrent (getNumCapabilities, setNumCapabilities)-import Control.Exception (throwIO, try)-import Control.Monad (forM_)-import Control.Monad.Trans.Writer.CPS (execWriter, tell)-import Data.ByteString qualified as BS-import Data.ByteString.Builder qualified as BB-import Data.ByteString.Lazy qualified as LBS-import Data.Csv (FromNamedRecord (..), decodeByName, (.:))-import Data.FileEmbed-import Data.Foldable (fold)-import Data.Foldable1 (fold1)-import Data.Functor-import Data.IntMap.Monoidal.Strict (MonoidalIntMap)-import Data.IntMap.Monoidal.Strict qualified as MIM-import Data.IntSet qualified as IS-import Data.List qualified as List-import Data.List.NonEmpty (NonEmpty (..))-import Data.Map.Monoidal.Strict (MonoidalMap)-import Data.Map.Monoidal.Strict qualified as MonoidalMap-import Data.Monoid (First (..))-import Data.Semigroup qualified as Semi-import Data.Set qualified as Set-import Data.Text qualified as T-import Data.Text.Encoding qualified as TE-import GHC.Generics-import Options.Applicative qualified as Opts-import PureBorrow.Demo.QSort qualified as QS-import PureBorrow.Internal.Bench.QSort (BenchOpts)-import PureBorrow.Internal.Bench.QSort qualified as Bench-import System.Directory (canonicalizePath, findExecutable)-import System.Environment (withArgs)-import System.Exit (ExitCode)-import System.IO (hClose, hFlush)-import System.IO.Temp (withSystemTempFile)-import System.Process (readProcess)-import Text.Read (readEither)--data Cmd = Bench BenchOpts | QuickBench | QSortDemo QS.CLIOpts- deriving (Show, Eq, Ord, Generic)--optionsP :: Int -> Opts.ParserInfo Cmd-optionsP numCapa =- Opts.info (p <**> Opts.helper) $- Opts.fullDesc- <> Opts.progDesc "Artifact runner for qsort benchmarks and demos"- where- p = cmds <|> Bench <$> Bench.rawOptsP- cmds =- Opts.hsubparser $- fold1 $- Opts.command "bench" (Bench <$> Bench.optionsP)- :| [ Opts.command "demo" $ QSortDemo <$> QS.optionsP numCapa- , Opts.command "quick" $- Opts.info (pure QuickBench) $- Opts.progDesc $- "Run quick benchmarks with numcpu = 4 for sizes 0 and " <> show Bench.kMAX_SIZE- ]--main :: IO ()-main = do- numCap <- getNumCapabilities- Opts.customExecParser (Opts.prefs Opts.subparserInline) (optionsP numCap) >>= \case- Bench benchOpts -> runBench benchOpts- QuickBench -> runBench Bench.BenchOpts {numThreads = 4, sampleSize = 2}- QSortDemo cliOpts -> QS.defaultMainWith cliOpts--runBench :: BenchOpts -> IO ()-runBench benchOpts = do- let rawDest = "qsort-raw.csv"- void $ try @ExitCode $ withArgs ["--csv", rawDest, "-j1", "--time-mode=wall", "-t", "10s"] do- setNumCapabilities benchOpts.numThreads- Bench.defaultMainWith benchOpts-- putStrLn "Processing results..."- (_, rawRows) <- either (throwIO . userError) pure . decodeByName =<< LBS.readFile rawDest- let sd = foldMap fromRawRow rawRows- builder = buildOutput sd- csvDest <- canonicalizePath "qsort.csv"- BB.writeFile csvDest builder-- mgp <- findExecutable "gnuplot"- forM_ mgp \gnuplot -> withSystemTempFile "plot.gp" \tmp h -> do- putStrLn $ "Gnuplot found: " <> gnuplot- pngDest <- canonicalizePath "qsort.png"- BS.hPutStr h gnuplotScript- hFlush h- hClose h- !_ <- readProcess gnuplot ["-e", "input='" <> csvDest <> "'; output='" <> pngDest <> "'", tmp] ""- putStrLn $ "Plot generated: " <> pngDest--gnuplotScript :: BS.ByteString-gnuplotScript =- $(embedFile "scripts/genplot.gnuplot")--buildOutput :: Statistics -> BB.Builder-buildOutput sd = execWriter do- let (hdrs, targets) = toHeaders sd-- putLine $- fold $- List.intersperse "," $- map (BB.byteString . TE.encodeUtf8) hdrs- forM_ (MIM.toAscList sd) \(size, ps) -> do- let row =- BB.intDec size- : concatMap- ( \t ->- maybe (replicate 5 mempty) (\p -> map BB.doubleDec [p.mean, p.stddev, p.alloc, p.copied, p.peak]) $- lookupStat t ps- )- targets- putLine $ fold $ List.intersperse "," row- where- crlf = tell "\r\n"- putLine = (>> crlf) . tell--data RawRow = RawRow- { size :: !Int- , name :: !T.Text- , mean :: !Int- , stddev :: !Int- , alloc :: !Int- , copied :: !Int- , peak :: !Int- }- deriving (Show, Eq, Ord, Generic)--instance FromNamedRecord RawRow where- parseNamedRecord r = do- fullName <- r .: "Name"- let ~(sz : name : _) = drop 2 $ T.splitOn "." fullName- size <- either fail pure $ readEither $ T.unpack sz- mean <- r .: "Mean (ps)"- stddev <- r .: "2*Stdev (ps)" <&> (`quot` 2)- alloc <- r .: "Allocated"- copied <- r .: "Copied"- peak <- r .: "Peak Memory"- pure RawRow {..}--data Performance = Performance- { mean :: !Double- , stddev :: !Double- , alloc :: !Double- , copied :: !Double- , peak :: !Double- }- deriving (Show, Eq, Ord, Generic)- deriving (Semigroup) via Semi.First Performance--toPerformance :: RawRow -> Performance-toPerformance RawRow {..} =- Performance- { mean = fromIntegral mean * 1e-9- , stddev = fromIntegral stddev * 1e-9- , alloc = fromIntegral alloc * 1e-6- , copied = fromIntegral copied * 1e-6- , peak = fromIntegral peak * 1e-6- }--data PerformanceSet = PerformanceSet- { intro :: !(First Performance)- , sequential :: !(First Performance)- , parallel :: !(MonoidalIntMap Performance)- , worksteal :: !(MonoidalIntMap Performance)- , others :: !(MonoidalMap T.Text Performance)- }- deriving (Show, Eq, Ord, Generic)- deriving (Semigroup, Monoid) via Generically PerformanceSet--type Statistics = MonoidalIntMap PerformanceSet--data Target = Intro | Sequential | Parallel Int | Worksteal Int | Other T.Text- deriving (Show, Eq, Ord, Generic)--lookupStat :: Target -> PerformanceSet -> Maybe Performance-lookupStat t ps =- case t of- Intro -> getFirst ps.intro- Sequential -> getFirst ps.sequential- Parallel n -> MIM.lookup n ps.parallel- Worksteal n -> MIM.lookup n ps.worksteal- Other name -> MonoidalMap.lookup name ps.others--toHeaders :: Statistics -> ([T.Text], [Target])-toHeaders stats =- (headers, targets)- where- headers = "size" : [cat <> metric | cat <- categories, metric <- metrics]- targets = Intro : Sequential : [Parallel n | n <- IS.toList parallels] ++ [Worksteal n | n <- IS.toList worksteals] ++ [Other name | name <- Set.toList miscs]- (parallels, worksteals, miscs) =- foldMap- ( \ps ->- ( MIM.keysSet ps.parallel- , MIM.keysSet ps.worksteal- , MonoidalMap.keysSet ps.others- )- )- stats- categories =- "intro"- : "sequential"- : [T.pack $ "parallel" <> show n | n <- IS.toList parallels]- ++ [T.pack $ "workSteal" <> show n | n <- IS.toList worksteals]- ++ [name | name <- Set.toList miscs]-- metrics = ["Mean", "Stddev", "Alloc", "Copied", "Peak"]--fromRawRow :: RawRow -> Statistics-fromRawRow r@RawRow {..} = MIM.singleton size $- case name of- "intro" -> mempty {intro = First (Just $ toPerformance r)}- "sequential" -> mempty {sequential = First (Just $ perf)}- inp- | Just rest <- T.stripPrefix "parallel (budget =" inp- , [(n, _)] <- reads (T.unpack rest) ->- mempty {parallel = MIM.singleton n perf}- | Just rest <- T.stripPrefix "worksteal (workers =" inp- , [(n, _)] <- reads (T.unpack rest) ->- mempty {worksteal = MIM.singleton n perf}- _ -> mempty {others = MonoidalMap.singleton name perf}- where- perf = toPerformance r
app/convert-qsort-bench-csv.hs view
@@ -85,76 +85,76 @@ fromRawRow :: RawRow -> SizeDataMap fromRawRow RawRow {..} = fromMaybe mempty do dat <- case name of- "intro" -> pure mempty {introMean = Sum (fromIntegral mean * 1e-9), introStddev = Sum (fromIntegral stddev * 1e-9), introAlloc = Sum (fromIntegral alloc * 1e-6), introCopied = Sum (fromIntegral copied * 1e-6), introPeak = Sum (fromIntegral peak * 1e-6)}- "sequential" -> pure mempty {sequentialMean = Sum (fromIntegral mean * 1e-9), sequentialStddev = Sum (fromIntegral stddev * 1e-9), sequentialAlloc = Sum (fromIntegral alloc * 1e-6), sequentialCopied = Sum (fromIntegral copied * 1e-6), sequentialPeak = Sum (fromIntegral peak * 1e-6)}- "parallel (budget = 4)" -> pure mempty {parallel4Mean = Sum (fromIntegral mean * 1e-9), parallel4Stddev = Sum (fromIntegral stddev * 1e-9), parallel4Alloc = Sum (fromIntegral alloc * 1e-6), parallel4Copied = Sum (fromIntegral copied * 1e-6), parallel4Peak = Sum (fromIntegral peak * 1e-6)}- "parallel (budget = 8)" -> pure mempty {parallel8Mean = Sum (fromIntegral mean * 1e-9), parallel8Stddev = Sum (fromIntegral stddev * 1e-9), parallel8Alloc = Sum (fromIntegral alloc * 1e-6), parallel8Copied = Sum (fromIntegral copied * 1e-6), parallel8Peak = Sum (fromIntegral peak * 1e-6)}- "parallel (budget = 16)" -> pure mempty {parallel16Mean = Sum (fromIntegral mean * 1e-9), parallel16Stddev = Sum (fromIntegral stddev * 1e-9), parallel16Alloc = Sum (fromIntegral alloc * 1e-6), parallel16Copied = Sum (fromIntegral copied * 1e-6), parallel16Peak = Sum (fromIntegral peak * 1e-6)}- "parallel (budget = 32)" -> pure mempty {parallel32Mean = Sum (fromIntegral mean * 1e-9), parallel32Stddev = Sum (fromIntegral stddev * 1e-9), parallel32Alloc = Sum (fromIntegral alloc * 1e-6), parallel32Copied = Sum (fromIntegral copied * 1e-6), parallel32Peak = Sum (fromIntegral peak * 1e-6)}- "worksteal (workers = 2)" -> pure mempty {workSteal2Mean = Sum (fromIntegral mean * 1e-9), workSteal2Stddev = Sum (fromIntegral stddev * 1e-9), workSteal2Alloc = Sum (fromIntegral alloc * 1e-6), workSteal2Copied = Sum (fromIntegral copied * 1e-6), workSteal2Peak = Sum (fromIntegral peak * 1e-6)}- "worksteal (workers = 4)" -> pure mempty {workSteal4Mean = Sum (fromIntegral mean * 1e-9), workSteal4Stddev = Sum (fromIntegral stddev * 1e-9), workSteal4Alloc = Sum (fromIntegral alloc * 1e-6), workSteal4Copied = Sum (fromIntegral copied * 1e-6), workSteal4Peak = Sum (fromIntegral peak * 1e-6)}- "worksteal (workers = 6)" -> pure mempty {workSteal6Mean = Sum (fromIntegral mean * 1e-9), workSteal6Stddev = Sum (fromIntegral stddev * 1e-9), workSteal6Alloc = Sum (fromIntegral alloc * 1e-6), workSteal6Copied = Sum (fromIntegral copied * 1e-6), workSteal6Peak = Sum (fromIntegral peak * 1e-6)}- "worksteal (workers = 8)" -> pure mempty {workSteal8Mean = Sum (fromIntegral mean * 1e-9), workSteal8Stddev = Sum (fromIntegral stddev * 1e-9), workSteal8Alloc = Sum (fromIntegral alloc * 1e-6), workSteal8Copied = Sum (fromIntegral copied * 1e-6), workSteal8Peak = Sum (fromIntegral peak * 1e-6)}- "worksteal (workers = 10)" -> pure mempty {workSteal10Mean = Sum (fromIntegral mean * 1e-9), workSteal10Stddev = Sum (fromIntegral stddev * 1e-9), workSteal10Alloc = Sum (fromIntegral alloc * 1e-6), workSteal10Copied = Sum (fromIntegral copied * 1e-6), workSteal10Peak = Sum (fromIntegral peak * 1e-6)}+ "intro" -> pure mempty {introMean = Just $ Sum (fromIntegral mean * 1e-9), introStddev = Just $ Sum (fromIntegral stddev * 1e-9), introAlloc = Just $ Sum (fromIntegral alloc * 1e-6), introCopied = Just $ Sum (fromIntegral copied * 1e-6), introPeak = Just $ Sum (fromIntegral peak * 1e-6)}+ "sequential" -> pure mempty {sequentialMean = Just $ Sum (fromIntegral mean * 1e-9), sequentialStddev = Just $ Sum (fromIntegral stddev * 1e-9), sequentialAlloc = Just $ Sum (fromIntegral alloc * 1e-6), sequentialCopied = Just $ Sum (fromIntegral copied * 1e-6), sequentialPeak = Just $ Sum (fromIntegral peak * 1e-6)}+ "parallel (budget = 4)" -> pure mempty {parallel4Mean = Just $ Sum (fromIntegral mean * 1e-9), parallel4Stddev = Just $ Sum (fromIntegral stddev * 1e-9), parallel4Alloc = Just $ Sum (fromIntegral alloc * 1e-6), parallel4Copied = Just $ Sum (fromIntegral copied * 1e-6), parallel4Peak = Just $ Sum (fromIntegral peak * 1e-6)}+ "parallel (budget = 8)" -> pure mempty {parallel8Mean = Just $ Sum (fromIntegral mean * 1e-9), parallel8Stddev = Just $ Sum (fromIntegral stddev * 1e-9), parallel8Alloc = Just $ Sum (fromIntegral alloc * 1e-6), parallel8Copied = Just $ Sum (fromIntegral copied * 1e-6), parallel8Peak = Just $ Sum (fromIntegral peak * 1e-6)}+ "parallel (budget = 16)" -> pure mempty {parallel16Mean = Just $ Sum (fromIntegral mean * 1e-9), parallel16Stddev = Just $ Sum (fromIntegral stddev * 1e-9), parallel16Alloc = Just $ Sum (fromIntegral alloc * 1e-6), parallel16Copied = Just $ Sum (fromIntegral copied * 1e-6), parallel16Peak = Just $ Sum (fromIntegral peak * 1e-6)}+ "parallel (budget = 32)" -> pure mempty {parallel32Mean = Just $ Sum (fromIntegral mean * 1e-9), parallel32Stddev = Just $ Sum (fromIntegral stddev * 1e-9), parallel32Alloc = Just $ Sum (fromIntegral alloc * 1e-6), parallel32Copied = Just $ Sum (fromIntegral copied * 1e-6), parallel32Peak = Just $ Sum (fromIntegral peak * 1e-6)}+ "worksteal (workers = 2)" -> pure mempty {workSteal2Mean = Just $ Sum (fromIntegral mean * 1e-9), workSteal2Stddev = Just $ Sum (fromIntegral stddev * 1e-9), workSteal2Alloc = Just $ Sum (fromIntegral alloc * 1e-6), workSteal2Copied = Just $ Sum (fromIntegral copied * 1e-6), workSteal2Peak = Just $ Sum (fromIntegral peak * 1e-6)}+ "worksteal (workers = 4)" -> pure mempty {workSteal4Mean = Just $ Sum (fromIntegral mean * 1e-9), workSteal4Stddev = Just $ Sum (fromIntegral stddev * 1e-9), workSteal4Alloc = Just $ Sum (fromIntegral alloc * 1e-6), workSteal4Copied = Just $ Sum (fromIntegral copied * 1e-6), workSteal4Peak = Just $ Sum (fromIntegral peak * 1e-6)}+ "worksteal (workers = 6)" -> pure mempty {workSteal6Mean = Just $ Sum (fromIntegral mean * 1e-9), workSteal6Stddev = Just $ Sum (fromIntegral stddev * 1e-9), workSteal6Alloc = Just $ Sum (fromIntegral alloc * 1e-6), workSteal6Copied = Just $ Sum (fromIntegral copied * 1e-6), workSteal6Peak = Just $ Sum (fromIntegral peak * 1e-6)}+ "worksteal (workers = 8)" -> pure mempty {workSteal8Mean = Just $ Sum (fromIntegral mean * 1e-9), workSteal8Stddev = Just $ Sum (fromIntegral stddev * 1e-9), workSteal8Alloc = Just $ Sum (fromIntegral alloc * 1e-6), workSteal8Copied = Just $ Sum (fromIntegral copied * 1e-6), workSteal8Peak = Just $ Sum (fromIntegral peak * 1e-6)}+ "worksteal (workers = 10)" -> pure mempty {workSteal10Mean = Just $ Sum (fromIntegral mean * 1e-9), workSteal10Stddev = Just $ Sum (fromIntegral stddev * 1e-9), workSteal10Alloc = Just $ Sum (fromIntegral alloc * 1e-6), workSteal10Copied = Just $ Sum (fromIntegral copied * 1e-6), workSteal10Peak = Just $ Sum (fromIntegral peak * 1e-6)} _ -> Nothing pure (MIM.singleton size dat) data SizeData = SizeData- { introMean :: !(Sum Double)- , introStddev :: !(Sum Double)- , introAlloc :: !(Sum Double)- , introCopied :: !(Sum Double)- , introPeak :: !(Sum Double)- , sequentialMean :: !(Sum Double)- , sequentialStddev :: !(Sum Double)- , sequentialAlloc :: !(Sum Double)- , sequentialCopied :: !(Sum Double)- , sequentialPeak :: !(Sum Double)- , parallel4Mean :: !(Sum Double)- , parallel4Stddev :: !(Sum Double)- , parallel4Alloc :: !(Sum Double)- , parallel4Copied :: !(Sum Double)- , parallel4Peak :: !(Sum Double)- , parallel8Mean :: !(Sum Double)- , parallel8Stddev :: !(Sum Double)- , parallel8Alloc :: !(Sum Double)- , parallel8Copied :: !(Sum Double)- , parallel8Peak :: !(Sum Double)- , parallel16Mean :: !(Sum Double)- , parallel16Stddev :: !(Sum Double)- , parallel16Alloc :: !(Sum Double)- , parallel16Copied :: !(Sum Double)- , parallel16Peak :: !(Sum Double)- , parallel32Mean :: !(Sum Double)- , parallel32Stddev :: !(Sum Double)- , parallel32Alloc :: !(Sum Double)- , parallel32Copied :: !(Sum Double)- , parallel32Peak :: !(Sum Double)- , workSteal2Mean :: !(Sum Double)- , workSteal2Stddev :: !(Sum Double)- , workSteal2Alloc :: !(Sum Double)- , workSteal2Copied :: !(Sum Double)- , workSteal2Peak :: !(Sum Double)- , workSteal4Mean :: !(Sum Double)- , workSteal4Stddev :: !(Sum Double)- , workSteal4Alloc :: !(Sum Double)- , workSteal4Copied :: !(Sum Double)- , workSteal4Peak :: !(Sum Double)- , workSteal6Mean :: !(Sum Double)- , workSteal6Stddev :: !(Sum Double)- , workSteal6Alloc :: !(Sum Double)- , workSteal6Copied :: !(Sum Double)- , workSteal6Peak :: !(Sum Double)- , workSteal8Mean :: !(Sum Double)- , workSteal8Stddev :: !(Sum Double)- , workSteal8Alloc :: !(Sum Double)- , workSteal8Copied :: !(Sum Double)- , workSteal8Peak :: !(Sum Double)- , workSteal10Mean :: !(Sum Double)- , workSteal10Stddev :: !(Sum Double)- , workSteal10Alloc :: !(Sum Double)- , workSteal10Copied :: !(Sum Double)- , workSteal10Peak :: !(Sum Double)+ { introMean :: !(Maybe (Sum Double))+ , introStddev :: !(Maybe (Sum Double))+ , introAlloc :: !(Maybe (Sum Double))+ , introCopied :: !(Maybe (Sum Double))+ , introPeak :: !(Maybe (Sum Double))+ , sequentialMean :: !(Maybe (Sum Double))+ , sequentialStddev :: !(Maybe (Sum Double))+ , sequentialAlloc :: !(Maybe (Sum Double))+ , sequentialCopied :: !(Maybe (Sum Double))+ , sequentialPeak :: !(Maybe (Sum Double))+ , parallel4Mean :: !(Maybe (Sum Double))+ , parallel4Stddev :: !(Maybe (Sum Double))+ , parallel4Alloc :: !(Maybe (Sum Double))+ , parallel4Copied :: !(Maybe (Sum Double))+ , parallel4Peak :: !(Maybe (Sum Double))+ , parallel8Mean :: !(Maybe (Sum Double))+ , parallel8Stddev :: !(Maybe (Sum Double))+ , parallel8Alloc :: !(Maybe (Sum Double))+ , parallel8Copied :: !(Maybe (Sum Double))+ , parallel8Peak :: !(Maybe (Sum Double))+ , parallel16Mean :: !(Maybe (Sum Double))+ , parallel16Stddev :: !(Maybe (Sum Double))+ , parallel16Alloc :: !(Maybe (Sum Double))+ , parallel16Copied :: !(Maybe (Sum Double))+ , parallel16Peak :: !(Maybe (Sum Double))+ , parallel32Mean :: !(Maybe (Sum Double))+ , parallel32Stddev :: !(Maybe (Sum Double))+ , parallel32Alloc :: !(Maybe (Sum Double))+ , parallel32Copied :: !(Maybe (Sum Double))+ , parallel32Peak :: !(Maybe (Sum Double))+ , workSteal2Mean :: !(Maybe (Sum Double))+ , workSteal2Stddev :: !(Maybe (Sum Double))+ , workSteal2Alloc :: !(Maybe (Sum Double))+ , workSteal2Copied :: !(Maybe (Sum Double))+ , workSteal2Peak :: !(Maybe (Sum Double))+ , workSteal4Mean :: !(Maybe (Sum Double))+ , workSteal4Stddev :: !(Maybe (Sum Double))+ , workSteal4Alloc :: !(Maybe (Sum Double))+ , workSteal4Copied :: !(Maybe (Sum Double))+ , workSteal4Peak :: !(Maybe (Sum Double))+ , workSteal6Mean :: !(Maybe (Sum Double))+ , workSteal6Stddev :: !(Maybe (Sum Double))+ , workSteal6Alloc :: !(Maybe (Sum Double))+ , workSteal6Copied :: !(Maybe (Sum Double))+ , workSteal6Peak :: !(Maybe (Sum Double))+ , workSteal8Mean :: !(Maybe (Sum Double))+ , workSteal8Stddev :: !(Maybe (Sum Double))+ , workSteal8Alloc :: !(Maybe (Sum Double))+ , workSteal8Copied :: !(Maybe (Sum Double))+ , workSteal8Peak :: !(Maybe (Sum Double))+ , workSteal10Mean :: !(Maybe (Sum Double))+ , workSteal10Stddev :: !(Maybe (Sum Double))+ , workSteal10Alloc :: !(Maybe (Sum Double))+ , workSteal10Copied :: !(Maybe (Sum Double))+ , workSteal10Peak :: !(Maybe (Sum Double)) } deriving (Show, Eq, Ord, Generic) deriving anyclass (ToNamedRecord, DefaultOrdered)
+ app/fft.hs view
@@ -0,0 +1,17 @@+{-# LANGUAGE ApplicativeDo #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE PartialTypeSignatures #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}++module Main (main) where++import PureBorrow.Demo.Fft (defaultMain)++main :: IO ()+main = defaultMain
+ bench/fft.hs view
@@ -0,0 +1,139 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeApplications #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}++module Main (main) where++import Control.Concurrent (getNumCapabilities)+import Control.Concurrent.DivideConquer.Linear (+ DivideConquer,+ FftCoe,+ Pair,+ combineLoop,+ fftDC,+ fftDC',+ naiveDivideAndConquer,+ sequentialDivideAndConquer,+ )+import Control.Exception (evaluate)+import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.BO+import Control.Syntax.DataFlow qualified as DataFlow+import Data.Complex (Complex (..))+import Data.Proxy (Proxy (..))+import Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted qualified as VL+import Data.Vector.Unboxed qualified as V+import Prelude.Linear (dup, unur)+import Prelude.Linear qualified as PL+import System.Random.Stateful+import Test.Tasty (askOption, defaultMainWithIngredients)+import Test.Tasty.Bench+import Test.Tasty.Ingredients.Basic (includingOptions)+import Test.Tasty.Options+import Text.Read (readMaybe)+import Prelude as P++data Mode = NaiveDC | Worksteal Int | Sequential+ deriving (Show, Eq, Ord)++{-# SPECIALIZE fftDC' ::+ Int ->+ DivideConquer+ FftCoe+ α+ Pair+ (VL.Vector V.Vector (Complex Double))+ ()+ #-}++{-# SPECIALIZE combineLoop ::+ Int ->+ Complex Double ->+ Int ->+ Complex Double ->+ Mut α (VL.Vector V.Vector (Complex Double)) %1 ->+ BO α ()+ #-}++data BenchOpts = BenchOpts {numThreads :: !Int, sampleSize :: !Int}+ deriving (Show, Eq, Ord)++fun :: Double -> Double+fun x = sin (2 * pi * x) + 2 * cos (pi * x) + 3 * sin (0.5 * pi * x) + 5++sample :: Int -> (Double -> Double) -> V.Vector (Complex Double)+sample n f = V.generate n \i -> f (-4 + 8 * fromIntegral i / fromIntegral n) :+ 0++fftWith :: Mode -> V.Vector (Complex Double) -> V.Vector (Complex Double)+fftWith Sequential v =+ unur PL.$ linearly \lin ->+ DataFlow.do+ (lin, l2) <- dup lin+ runBO lin Control.do+ (v, lend) <- borrowM (VL.fromVector v l2)+ Control.void PL.$ sequentialDivideAndConquer (fftDC' 128) v+ pureAfter (VL.toVector PL.$ reclaim lend)+fftWith NaiveDC v =+ unur PL.$ linearly \lin ->+ DataFlow.do+ (lin, l2) <- dup lin+ runBO lin Control.do+ (v, lend) <- borrowM (VL.fromVector v l2)+ Control.void PL.$ naiveDivideAndConquer (fftDC' 128) v+ pureAfter (VL.toVector PL.$ reclaim lend)+fftWith (Worksteal p) v =+ unur PL.$ linearly \lin ->+ DataFlow.do+ (lin, l2) <- dup lin+ runBO lin Control.do+ (v, lend) <- borrowM (VL.fromVector v l2)+ Control.void PL.$ fftDC (mkStdGen 42) p 128 v+ pureAfter (VL.toVector PL.$ reclaim lend)++data SampleSize = SampleSize Int+ deriving (Show, Eq, Ord)++instance IsOption SampleSize where+ defaultValue = SampleSize 10+ parseValue s =+ case readMaybe s of+ Just n | n > 0, kMAX_SIZE `rem` n == 0 -> Just (SampleSize n)+ _ -> Nothing+ optionName = return "size"+ optionHelp = return "Number of logarithmic size steps (positive divisor of 10)"++main :: IO ()+main = do+ numThreads <- getNumCapabilities+ let customOpts = [Option (Proxy :: Proxy SampleSize)]+ ingredients = includingOptions customOpts : benchIngredients+ defaultMainWithIngredients ingredients $ askOption \(SampleSize sampleSize) ->+ bgroup "All" $ benches BenchOpts {..}++benches :: BenchOpts -> [Benchmark]+benches BenchOpts {..} =+ [ bgroup+ "fft"+ [ env+ (evaluate $ sample size fun)+ \vec ->+ bgroup+ (show size)+ ( bench "sequential" (nf (fftWith Sequential) vec)+ : bench+ ("parallel-dc (thresh = 128)")+ (nf (fftWith NaiveDC) vec)+ : [ bench ("worksteal (workers = " <> show n <> ")") $+ nf (fftWith $ Worksteal n) vec+ | n <- [2, 4 .. numThreads]+ ]+ )+ | i <- [0 .. sampleSize]+ , let size = 2 ^ (10 + i * kMAX_SIZE `quot` sampleSize)+ ]+ ]++kMAX_SIZE :: Int+kMAX_SIZE = 10
bench/qsort.hs view
@@ -1,11 +1,127 @@ {-# LANGUAGE BlockArguments #-} {-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeApplications #-} {-# OPTIONS_GHC -Wno-name-shadowing #-} module Main (main) where -import PureBorrow.Internal.Bench.QSort (defaultMain)+import Control.Concurrent (getNumCapabilities)+import Control.Concurrent.DivideConquer.Linear (+ naiveDivideAndConquer,+ qsort,+ qsortDC,+ qsortDC',+ )+import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.BO+import Control.Syntax.DataFlow qualified as DataFlow+import Data.Proxy (Proxy (..))+import Data.Vector.Algorithms.Intro qualified as AI+import Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted qualified as VL+import Data.Vector.Unboxed qualified as V+import Prelude.Linear (dup, unur)+import Prelude.Linear qualified as PL+import System.Random.Stateful+import Test.Tasty (askOption, defaultMainWithIngredients)+import Test.Tasty.Bench+import Test.Tasty.Ingredients.Basic (includingOptions)+import Test.Tasty.Options+import Text.Read (readMaybe)+import Prelude as P +data Mode = Parallel Word | NaiveDC Int | Worksteal Int | Sequential | IntroSort+ deriving (Show, Eq, Ord)++data BenchOpts = BenchOpts {numThreads :: !Int, sampleSize :: !Int}+ deriving (Show, Eq, Ord)++qsortWith :: Mode -> V.Vector Int -> V.Vector Int+qsortWith IntroSort v = V.modify AI.sort v+qsortWith (Parallel budget) v =+ unur PL.$ linearly \lin ->+ DataFlow.do+ (lin, l2) <- dup lin+ runBO lin Control.do+ (v, lend) <- borrowM (VL.fromVector v l2)+ qsort budget v+ Control.pure PL.$ VL.toVector Control.<$> reclaim' lend+qsortWith Sequential v =+ unur PL.$ linearly \lin ->+ DataFlow.do+ (lin, l2) <- dup lin+ runBO lin Control.do+ (v, lend) <- borrowM (VL.fromVector v l2)+ qsort 0 v+ pureAfter (VL.toVector PL.$ reclaim lend)+qsortWith (NaiveDC p) v =+ unur PL.$ linearly \lin ->+ DataFlow.do+ (lin, l2) <- dup lin+ runBO lin Control.do+ (v, lend) <- borrowM (VL.fromVector v l2)+ Control.void PL.$ naiveDivideAndConquer (qsortDC' p) v+ pureAfter (VL.toVector PL.$ reclaim lend)+qsortWith (Worksteal p) v =+ unur PL.$ linearly \lin ->+ DataFlow.do+ (lin, l2) <- dup lin+ runBO lin Control.do+ (v, lend) <- borrowM (VL.fromVector v l2)+ Control.void PL.$ qsortDC (mkStdGen 42) p 128 v+ pureAfter (VL.toVector PL.$ reclaim lend)++data SampleSize = SampleSize Int+ deriving (Show, Eq, Ord)++instance IsOption SampleSize where+ defaultValue = SampleSize 32+ parseValue s =+ case readMaybe s of+ Just n | n > 0, kMAX_SIZE `rem` n == 0 -> Just (SampleSize n)+ _ -> Nothing+ optionName = return "size"+ optionHelp =+ return "Number of linear size steps (positive divisor of 32768)"+ main :: IO ()-main = defaultMain+main = do+ numThreads <- getNumCapabilities+ let customOpts = [Option (Proxy :: Proxy SampleSize)]+ ingredients = includingOptions customOpts : benchIngredients+ defaultMainWithIngredients ingredients $ askOption \(SampleSize sampleSize) ->+ bgroup "All" $ benches BenchOpts {..}++benches :: BenchOpts -> [Benchmark]+benches BenchOpts {..} =+ [ bgroup+ "qsort"+ [ env+ ( pure $ runStateGen_ (mkStdGen 42) \g -> do+ V.replicateM size (uniformM g)+ )+ \vec ->+ bgroup+ (show size)+ ( [ bench "intro" $ nf (qsortWith IntroSort) vec+ , bench "sequential" $ nf (qsortWith Sequential) vec+ ]+ <> [ bench ("parallel (budget = " <> show n <> ")") $+ nf (qsortWith $ Parallel n) vec+ | n <- [4, 8, 16, 32]+ ]+ <> [ bench ("parallel-dc (thresh = 128)") $+ nf (qsortWith $ NaiveDC 128) vec+ ]+ <> [ bench ("worksteal (workers = " <> show n <> ")") $+ nf (qsortWith $ Worksteal n) vec+ | n <- [2, 4 .. numThreads]+ ]+ )+ | i <- [0 .. sampleSize]+ , let size = i * kMAX_SIZE `quot` sampleSize+ ]+ ]++kMAX_SIZE :: Int+kMAX_SIZE = 32 * 1024
+ bench/suite/Main.hs view
@@ -0,0 +1,5 @@+-- The two @--ingredient@ flags rebuild 'Test.Tasty.Bench.benchIngredients':+-- @tasty-discover@ prepends each ingredient to 'Test.Tasty.defaultIngredients'+-- in reverse flag order, so @listingTests@ ends up first (keeping+-- @--list-tests@ working) and the composed bench reporter second.+{-# OPTIONS_GHC -F -pgmF tasty-discover -optF --tree-display -optF --ingredient=PureBorrow.Bench.Ingredients.benchReporter -optF --ingredient=Test.Tasty.Ingredients.Basic.listingTests #-}
+ bench/suite/PureBorrow/Bench/CopyAt.hs view
@@ -0,0 +1,176 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoImplicitPrelude #-}++module PureBorrow.Bench.CopyAt (+ test_copyAt,+ composedPureBorrowCopiedReadLoop,+ directCopiedReadLoop,+ pureBorrowCopiedReadLoop,+ pureBorrowUpdateLoop,+ reborrowingUpdateLoop,+) where++import Control.Exception (evaluate)+import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure+import Control.Monad.ST.Strict (runST)+import Control.Syntax.DataFlow qualified as DataFlow+import Data.Vector qualified as V+import Data.Vector.Mutable qualified as MV+import Data.Vector.Mutable.Linear.Borrow qualified as VL+import Prelude.Linear+import Test.Tasty.Bench hiding (defaultMain)+import Prelude qualified as NonLinear++directCopiedReadLoop :: V.Vector Int -> (Int, V.Vector Int)+{-# NOINLINE directCopiedReadLoop #-}+directCopiedReadLoop input = runST do+ mutable <- V.thaw input+ total <- go mutable 0 0+ frozen <- V.unsafeFreeze mutable+ NonLinear.pure (total, frozen)+ where+ go mutable !i !total+ | i >= MV.length mutable = NonLinear.pure total+ | otherwise = do+ value <- MV.read mutable i+ go mutable (i + 1) (total + value)++pureBorrowCopiedReadLoop :: V.Vector Int -> (Int, V.Vector Int)+{-# NOINLINE pureBorrowCopiedReadLoop #-}+pureBorrowCopiedReadLoop input = unur $ linearly \linearlyToken -> DataFlow.do+ (runToken, vectorToken) <- dup linearlyToken+ runBO runToken Control.do+ (initialMutable, lend) <- borrowM (VL.fromVector input vectorToken)+ VL.size initialMutable & \(Ur length_, sizedMutable) ->+ go length_ 0 0 sizedMutable lend+ where+ go ::+ forall α.+ Int ->+ Int ->+ Int ->+ Mut α (VL.Vector Int) %1 ->+ Lend α (VL.Vector Int) %1 ->+ BO α (After α (Ur (Int, V.Vector Int)))+ go !length_ !i !total mutable lend+ | i >= length_ =+ consume mutable `lseq`+ Control.pure+ ( ( \vector -> case VL.toVector vector of+ Ur frozen -> Ur (total, frozen)+ )+ Control.<$> reclaim' lend+ )+ | otherwise = Control.do+ (Ur value, nextMutable) <- VL.copyAtMut i mutable+ go length_ (i + 1) (total + value) nextMutable lend++composedPureBorrowCopiedReadLoop :: V.Vector Int -> (Int, V.Vector Int)+{-# NOINLINE composedPureBorrowCopiedReadLoop #-}+composedPureBorrowCopiedReadLoop input = unur $ linearly \linearlyToken -> DataFlow.do+ (runToken, vectorToken) <- dup linearlyToken+ runBO runToken Control.do+ (initialMutable, lend) <- borrowM (VL.fromVector input vectorToken)+ VL.size initialMutable & \(Ur length_, sizedMutable) ->+ go length_ 0 0 sizedMutable lend+ where+ go ::+ forall α.+ Int ->+ Int ->+ Int ->+ Mut α (VL.Vector Int) %1 ->+ Lend α (VL.Vector Int) %1 ->+ BO α (After α (Ur (Int, V.Vector Int)))+ go !length_ !i !total mutable lend+ | i >= length_ =+ consume mutable `lseq`+ Control.pure+ ( ( \vector -> case VL.toVector vector of+ Ur frozen -> Ur (total, frozen)+ )+ Control.<$> reclaim' lend+ )+ | otherwise = Control.do+ (Ur value, nextMutable) <-+ sharing @α @α mutable \shared -> VL.copyAt i shared+ go length_ (i + 1) (total + value) nextMutable lend++pureBorrowUpdateLoop :: V.Vector Int -> V.Vector Int+{-# NOINLINE pureBorrowUpdateLoop #-}+pureBorrowUpdateLoop input = unur $ linearly \linearlyToken -> DataFlow.do+ (runToken, vectorToken) <- dup linearlyToken+ runBO runToken Control.do+ (initialMutable, lend) <- borrowM (VL.fromVector input vectorToken)+ VL.size initialMutable & \(Ur length_, sizedMutable) ->+ go length_ 0 sizedMutable lend+ where+ go ::+ forall α.+ Int ->+ Int ->+ Mut α (VL.Vector Int) %1 ->+ Lend α (VL.Vector Int) %1 ->+ BO α (After α (Ur (V.Vector Int)))+ go !length_ !i mutable lend+ | i >= length_ =+ consume mutable `lseq`+ Control.pure (VL.toVector Control.<$> reclaim' lend)+ | otherwise = Control.do+ nextMutable <- VL.modify i (+ 1) mutable+ go length_ (i + 1) nextMutable lend++reborrowingUpdateLoop :: V.Vector Int -> V.Vector Int+{-# NOINLINE reborrowingUpdateLoop #-}+reborrowingUpdateLoop input = unur $ linearly \linearlyToken -> DataFlow.do+ (runToken, vectorToken) <- dup linearlyToken+ runBO runToken Control.do+ (initialMutable, lend) <- borrowM (VL.fromVector input vectorToken)+ VL.size initialMutable & \(Ur length_, sizedMutable) ->+ go length_ 0 sizedMutable lend+ where+ go ::+ forall α.+ Int ->+ Int ->+ Mut α (VL.Vector Int) %1 ->+ Lend α (VL.Vector Int) %1 ->+ BO α (After α (Ur (V.Vector Int)))+ go !length_ !i mutable lend+ | i >= length_ =+ consume mutable `lseq`+ Control.pure (VL.toVector Control.<$> reclaim' lend)+ | otherwise = Control.do+ nextMutable <-+ reborrowing_ mutable \scopedMutable -> Control.do+ modifiedScoped <- VL.modify i (+ 1) scopedMutable+ Control.pure (consume modifiedScoped)+ go length_ (i + 1) nextMutable lend++test_copyAt :: [Benchmark]+test_copyAt =+ [ bgroup+ "copy-at"+ [ env+ (evaluate $ V.generate size (`NonLinear.rem` 1024))+ \input ->+ bgroup+ (NonLinear.show size)+ [ bench "direct-vector" $ nf directCopiedReadLoop input+ , bench "pure-borrow/direct-copy" $ nf pureBorrowCopiedReadLoop input+ , bench "pure-borrow/composed-copy" $ nf composedPureBorrowCopiedReadLoop input+ ]+ | size <- [4 * 1024, 64 * 1024, 1024 * 1024]+ ]+ , env+ (evaluate $ V.generate (1024 * 1024) (`NonLinear.rem` 1024))+ \input ->+ bgroup+ "discarding-scope/update"+ [ bench "pure-borrow/no-scope" $ nf pureBorrowUpdateLoop input+ , bench "reborrowing_" $ nf reborrowingUpdateLoop input+ ]+ ]
+ bench/suite/PureBorrow/Bench/Growable.hs view
@@ -0,0 +1,97 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoImplicitPrelude #-}++module PureBorrow.Bench.Growable (+ test_growable,+ directContentUpdateLoop,+ fixedContentUpdateLoop,+ growableContentUpdateLoop,+) where++import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure+import Control.Syntax.DataFlow qualified as DataFlow+import Data.STRef (newSTRef, readSTRef)+import Data.Vector qualified as V+import Data.Vector.Mutable qualified as MV+import Data.Vector.Mutable.Growable.Linear.Borrow qualified as Growable+import Data.Vector.Mutable.Linear.Borrow qualified as Fixed+import Prelude.Linear+import Test.Tasty.Bench hiding (defaultMain)+import Prelude qualified as NonLinear++directContentUpdateLoop :: V.Vector Int -> V.Vector Int+{-# NOINLINE directContentUpdateLoop #-}+directContentUpdateLoop input =+ V.modify+ ( \mutable -> do+ -- The header ref mirrors the growable vector's indirection, so the+ -- baseline pays for the same extra load the measured variants do.+ header <- newSTRef (V.length input, mutable)+ (logicalSize, contents) <- readSTRef header+ go logicalSize 0 contents+ )+ input+ where+ go !logicalSize !index contents+ | index >= logicalSize = NonLinear.pure ()+ | otherwise = do+ value <- MV.unsafeRead contents index+ MV.unsafeWrite contents index (value + 1)+ go logicalSize (index + 1) contents++fixedContentUpdateLoop :: V.Vector Int -> V.Vector Int+{-# NOINLINE fixedContentUpdateLoop #-}+fixedContentUpdateLoop input =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (initialVector, lend) <- borrowM (Fixed.fromVector input ownerLinear)+ updatedVector <- updateContents (V.length input) 0 initialVector+ let !() = consume updatedVector+ pureAfter $ Fixed.toVector (reclaim lend)++growableContentUpdateLoop :: V.Vector Int -> V.Vector Int+{-# NOINLINE growableContentUpdateLoop #-}+growableContentUpdateLoop input =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (initialVector, lend) <- borrowM (Growable.fromVector input ownerLinear)+ updatedVector <- Growable.withContent_ initialVector \contents ->+ updateContents (V.length input) 0 contents+ let !() = consume updatedVector+ pureAfter $ Growable.toVector (reclaim lend)++updateContents ::+ forall α.+ Int ->+ Int ->+ Mut α (Fixed.Vector Int) %1 ->+ BO α ()+{-# INLINE updateContents #-}+updateContents !logicalSize !index contents+ | index >= logicalSize = Control.pure (consume contents)+ | otherwise = Control.do+ ((), nextContents) <-+ Fixed.unsafeUpdate+ index+ (\ !value -> Control.pure ((), value + 1))+ contents+ updateContents logicalSize (index + 1) nextContents++test_growable :: [Benchmark]+test_growable =+ [ env+ (NonLinear.pure $ V.generate (1024 * 1024) (`NonLinear.rem` 1024))+ \input ->+ bgroup+ "growable/content-update"+ [ bench "direct/header-open-once" $ nf directContentUpdateLoop input+ , bench "pure-borrow/fixed" $ nf fixedContentUpdateLoop input+ , bench "pure-borrow/withContent_" $ nf growableContentUpdateLoop input+ ]+ ]
+ bench/suite/PureBorrow/Bench/HashMap.hs view
@@ -0,0 +1,270 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}++{- | Benchmarks of the Robin Hood hash table.++Each group measures the same workload three ways: this package's table (owned,+and through its borrow-aware wrapper), @linear-base@'s own linear hash map, and+the unrestricted @unordered-containers@ map. The expensive-key groups exist+because the table caches a fingerprint per slot: a key whose hash is cheap but+whose equality is not is exactly where that cache pays off, and the colliding+group is the extreme of that, where every key lands in one bucket and only the+fingerprint can reject a candidate without a full comparison.+-}+module PureBorrow.Bench.HashMap (+ test_hashMap,+ ExpensiveKey (..),+ benchInsertRobinHood,+ benchInsertRobinHoodBorrow,+ benchInsertLinearBase,+ benchInsertUnordered,+ benchFromListRobinHood,+ benchFromListLinearBase,+ benchFromListUnordered,+ benchLookupRobinHood,+ benchLookupRobinHoodBorrow,+ benchLookupLinearBase,+ benchLookupUnordered,+ testData,+ expensiveTestData,+ collidingExpensiveTestData,+) where++import Control.DeepSeq (NFData, force)+import Control.Exception (evaluate)+import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure+import Control.Syntax.DataFlow qualified as DataFlow+import Data.HashMap.Mutable.Linear qualified as LB+import Data.HashMap.RobinHood.Mutable.Linear qualified as RH+import Data.HashMap.RobinHood.Mutable.Linear.Borrow qualified as RHB+import Data.HashMap.Strict qualified as UCHM+import Data.Hashable (Hashable (..))+import GHC.Generics (Generic)+import Prelude.Linear (lseq, unur, (&))+import Prelude.Linear qualified as PL+import Test.Tasty.Bench++{- | A key with a cheap hash and an expensive equality.++The hash reads only the prefix, so two keys sharing a prefix collide and force+the payloads to be compared.+-}+data ExpensiveKey = ExpensiveKey+ { ekPrefix :: {-# UNPACK #-} !Int+ , ekPayload :: ![Int]+ }+ deriving (Show, Generic, NFData)++instance Eq ExpensiveKey where+ ExpensiveKey p1 payload1 == ExpensiveKey p2 payload2 =+ p1 == p2 && payload1 == payload2+ {-# INLINE (==) #-}++instance Hashable ExpensiveKey where+ hashWithSalt s (ExpensiveKey p _) = hashWithSalt s p+ {-# INLINE hashWithSalt #-}++-- * Insertion into an empty table++benchInsertRobinHood :: (Hashable k) => [(k, v)] -> [(k, v)]+benchInsertRobinHood kvs = unur PL.$ linearly \lin ->+ let %1 !hm = RH.new (length kvs) lin+ in RH.toList (RH.insertMany kvs hm)++benchInsertRobinHoodBorrow :: (Hashable k) => [(k, v)] -> [(k, v)]+benchInsertRobinHoodBorrow kvs = unur PL.$ linearly \lin -> DataFlow.do+ (ownerLinear, runLinear) <- PL.dup lin+ runBO runLinear Control.do+ (table, lend) <- borrowM (RHB.empty (length kvs) ownerLinear)+ table <- insertAllBorrow kvs table+ (Ur entries, table) <- RHB.toList table+ PL.consume table `lseq`+ pureAfter (PL.consume (reclaim lend) `lseq` Ur entries)++insertAllBorrow ::+ (Hashable k) =>+ [(k, v)] ->+ Mut α (RHB.HashMap k v) %1 ->+ BO α (Mut α (RHB.HashMap k v))+insertAllBorrow [] table = Control.pure table+insertAllBorrow ((k, v) : rest) table = Control.do+ (Ur _, table) <- RHB.insert k v table+ insertAllBorrow rest table++benchInsertLinearBase :: (LB.Keyed k) => [(k, v)] -> [(k, v)]+benchInsertLinearBase kvs = unur PL.$ LB.empty (length kvs) \hm ->+ LB.toList (insertManyLinearBase kvs hm)++insertManyLinearBase :: (LB.Keyed k) => [(k, v)] -> LB.HashMap k v %1 -> LB.HashMap k v+insertManyLinearBase [] hm = hm+insertManyLinearBase ((k, v) : rest) hm = insertManyLinearBase rest (LB.insert k v hm)++benchInsertUnordered :: (Hashable k) => [(k, v)] -> [(k, v)]+benchInsertUnordered kvs = UCHM.toList (foldr (uncurry UCHM.insert) UCHM.empty kvs)++-- * Bulk construction++benchFromListRobinHood :: (Hashable k) => [(k, v)] -> [(k, v)]+benchFromListRobinHood kvs = unur PL.$ linearly \lin ->+ RH.toList (RH.fromList kvs lin)++benchFromListLinearBase :: (LB.Keyed k) => [(k, v)] -> [(k, v)]+benchFromListLinearBase kvs = unur PL.$ LB.fromList kvs LB.toList++benchFromListUnordered :: (Hashable k) => [(k, v)] -> [(k, v)]+benchFromListUnordered kvs = UCHM.toList (UCHM.fromList kvs)++-- * Lookup after a bulk insertion++benchLookupRobinHood :: forall k v. (Hashable k) => [(k, v)] -> [k] -> Int+benchLookupRobinHood kvs keys = unur PL.$ linearly \lin ->+ let %1 !hm = RH.insertMany kvs (RH.new (length kvs) lin)+ in go 0 keys hm+ where+ go :: Int -> [k] -> RH.HashMap k v %1 -> Ur Int+ go !acc [] hm = hm `lseq` Ur acc+ go !acc (k : ks) hm =+ RH.lookup k hm & \(Ur mv, hm') ->+ go (acc + maybe 0 (const 1) mv) ks hm'++benchLookupRobinHoodBorrow :: forall k v. (Hashable k) => [(k, v)] -> [k] -> Int+benchLookupRobinHoodBorrow kvs keys = unur PL.$ linearly \lin -> DataFlow.do+ (ownerLinear, runLinear) <- PL.dup lin+ runBO runLinear Control.do+ (table, lend) <- borrowM (RHB.fromList kvs ownerLinear)+ (Ur found, table) <- go 0 keys table+ PL.consume table `lseq`+ pureAfter (PL.consume (reclaim lend) `lseq` Ur found)+ where+ go ::+ Int ->+ [k] ->+ Mut α (RHB.HashMap k v) %1 ->+ BO α (Ur Int, Mut α (RHB.HashMap k v))+ go !acc [] table = Control.pure (Ur acc, table)+ go !acc (k : ks) table = Control.do+ (Ur mv, table) <- RHB.lookup k table+ go (acc + maybe 0 (const 1) mv) ks table++benchLookupLinearBase :: forall k v. (LB.Keyed k) => [(k, v)] -> [k] -> Int+benchLookupLinearBase kvs keys = unur PL.$ LB.empty (length kvs) \hm ->+ let %1 !hm' = insertManyLinearBase kvs hm+ in go 0 keys hm'+ where+ go :: Int -> [k] -> LB.HashMap k v %1 -> Ur Int+ go !acc [] hm = hm `lseq` Ur acc+ go !acc (k : ks) hm =+ LB.lookup k hm & \(Ur mv, hm') ->+ go (acc + maybe 0 (const 1) mv) ks hm'++benchLookupUnordered :: forall k v. (Hashable k) => [(k, v)] -> [k] -> Int+benchLookupUnordered kvs keys = go 0 keys (foldr (uncurry UCHM.insert) UCHM.empty kvs)+ where+ go !acc [] _ = acc+ go !acc (k : ks) hm =+ go (acc + maybe 0 (const 1) (UCHM.lookup k hm)) ks hm++-- * Inputs++-- | Distinct integer keys.+testData :: Int -> [(Int, Int)]+testData n = [(i, i) | i <- [1 .. n]]++-- | Distinct expensive keys: unique prefixes, hundred-element payloads.+expensiveTestData :: Int -> [(ExpensiveKey, Int)]+expensiveTestData n =+ [ (ExpensiveKey i [i .. i + 99], i)+ | i <- [1 .. n]+ ]++-- | Expensive keys that all hash to one bucket.+collidingExpensiveTestData :: Int -> [(ExpensiveKey, Int)]+collidingExpensiveTestData n =+ [ (ExpensiveKey 42 [i .. i + 99], i)+ | i <- [1 .. n]+ ]++test_hashMap :: [Benchmark]+test_hashMap =+ [ bgroup+ "hashmap/insert"+ [ env (evaluate (force (testData n))) \kvs ->+ bgroup+ (show n)+ [ bench "robin-hood" $ nf benchInsertRobinHood kvs+ , bench "robin-hood/borrow" $ nf benchInsertRobinHoodBorrow kvs+ , bench "linear-base" $ nf benchInsertLinearBase kvs+ , bench "unordered-containers" $ nf benchInsertUnordered kvs+ ]+ | n <- sizes+ ]+ , bgroup+ "hashmap/from-list"+ [ env (evaluate (force (testData n))) \kvs ->+ bgroup+ (show n)+ [ bench "robin-hood" $ nf benchFromListRobinHood kvs+ , bench "linear-base" $ nf benchFromListLinearBase kvs+ , bench "unordered-containers" $ nf benchFromListUnordered kvs+ ]+ | n <- sizes+ ]+ , bgroup+ "hashmap/lookup"+ [ env (evaluate (force (testData n))) \kvs ->+ bgroup+ (show n)+ [ bench "robin-hood" $ nf (benchLookupRobinHood kvs) (map fst kvs)+ , bench "robin-hood/borrow" $ nf (benchLookupRobinHoodBorrow kvs) (map fst kvs)+ , bench "linear-base" $ nf (benchLookupLinearBase kvs) (map fst kvs)+ , bench "unordered-containers" $ nf (benchLookupUnordered kvs) (map fst kvs)+ ]+ | n <- sizes+ ]+ , bgroup+ "hashmap/expensive-key-insert"+ [ env (evaluate (force (expensiveTestData n))) \kvs ->+ bgroup+ (show n)+ [ bench "robin-hood" $ nf benchInsertRobinHood kvs+ , bench "linear-base" $ nf benchInsertLinearBase kvs+ , bench "unordered-containers" $ nf benchInsertUnordered kvs+ ]+ | n <- sizes+ ]+ , bgroup+ "hashmap/expensive-key-lookup"+ [ env (evaluate (force (expensiveTestData n))) \kvs ->+ bgroup+ (show n)+ [ bench "robin-hood" $ nf (benchLookupRobinHood kvs) (map fst kvs)+ , bench "linear-base" $ nf (benchLookupLinearBase kvs) (map fst kvs)+ , bench "unordered-containers" $ nf (benchLookupUnordered kvs) (map fst kvs)+ ]+ | n <- sizes+ ]+ , bgroup+ "hashmap/colliding-expensive-key-lookup"+ [ env (evaluate (force (collidingExpensiveTestData n))) \kvs ->+ bgroup+ (show n)+ [ bench "robin-hood" $ nf (benchLookupRobinHood kvs) (map fst kvs)+ , bench "linear-base" $ nf (benchLookupLinearBase kvs) (map fst kvs)+ , bench "unordered-containers" $ nf (benchLookupUnordered kvs) (map fst kvs)+ ]+ | -- Every key lands in one bucket, so the workload is quadratic; the+ -- large size would dominate the whole suite's wall clock.+ n <- collidingSizes+ ]+ ]++sizes :: [Int]+sizes = [100, 1000, 10000]++collidingSizes :: [Int]+collidingSizes = [100, 1000]
+ bench/suite/PureBorrow/Bench/Ingredients.hs view
@@ -0,0 +1,21 @@+module PureBorrow.Bench.Ingredients (benchReporter) where++import Test.Tasty.Bench (consoleBenchReporter, csvReporter, svgReporter)+import Test.Tasty.Ingredients (Ingredient, composeReporters)++{- | The reporting half of 'Test.Tasty.Bench.benchIngredients', as a single+composed ingredient.++Composition matters: 'Test.Tasty.defaultMainWithIngredients' runs the first+ingredient that accepts the given options, and 'consoleBenchReporter' accepts+every invocation. Listing the reporters separately would therefore make+@--csv@ and @--svg@ unreachable.++This is a separate ingredient rather than the whole 'benchIngredients' list so+that it can be named by @tasty-discover@'s @--ingredient@ flag; see+@bench/suite/Main.hs@. Discovery skips this module: @tasty-discover@ imports+only the modules that export a discovered binding.+-}+benchReporter :: Ingredient+benchReporter =+ consoleBenchReporter `composeReporters` (csvReporter `composeReporters` svgReporter)
+ bench/suite/PureBorrow/Bench/MultiStoreScan.hs view
@@ -0,0 +1,12 @@+{- | Exposes the benchmarks of "PureBorrow.Internal.Bench.MultiStoreScan" to+@tasty-discover@. The kernels themselves stay in the @bench-suites@ internal+library because @pure-borrow-test@ and @pure-borrow-inspection@ exercise them+too.+-}+module PureBorrow.Bench.MultiStoreScan (test_multiStoreScan) where++import PureBorrow.Internal.Bench.MultiStoreScan qualified as MultiStoreScan+import Test.Tasty.Bench (Benchmark)++test_multiStoreScan :: [Benchmark]+test_multiStoreScan = MultiStoreScan.benches
+ bench/suite/PureBorrow/Bench/ScopeDensity.hs view
@@ -0,0 +1,478 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE NoImplicitPrelude #-}+-- Rebinding a threaded linear borrow under its own name is the idiom these+-- loops are written in, as it is in the library itself.+{-# OPTIONS_GHC -Wno-name-shadowing #-}++{- |+R4 --- the scope-density regression.++Every other benchmark in this package is dominated by what happens /inside/ a+borrow scope. This one is dominated by the scopes themselves: one owner, one+@'Ref.Ref' 'Int'@, and a loop whose body is a single read-modify-write, crossed+by exactly one scope per iteration. The arms differ only in which delimiter+that crossing goes through, so the difference between an arm and the+scope-free control is the cost of one crossing, and the sweep over iteration+counts turns that difference into a slope rather than a single number.++The fixture exists because nothing here could see the two things that matter+most about the delimiters:++ * @c35dace@ removed the runtime construction of the sublifetime and a+ downstream e-graph engine measured 6.9--10.3% less allocation across+ 12 cases, while this package measured nothing at all; and+ * @ebba572@ then put one out-of-line @reviveAlias@ on every scope exit, and+ recorded that no benchmark shipped here resolves it --- @copy-at@ is+ byte-identical with a timing difference inside machine noise,+ @qsort-bench@ takes one @reborrowing'@ per @divideAndConquer'@ call rather+ than per node, and the FFT recursion amortises one exit over+ @O(log n)@ butterfly work.++So the loop below is deliberately the artificial worst case: an L1-resident+counter whose body is small enough that a per-exit call is a visible fraction+of it. That is the only shape in which a per-scope cost is observable, and it+is not a claim about any real workload. Read it as a lower bound on how much a+scope can cost and an early-warning gate on that cost changing, never as a+prediction of what a scope costs an application.++Two conventions keep the arms comparable.++Every arm performs exactly the same mutation the same number of times: one+@'Ref.modify' (+1)@ per iteration for the single-owner arms, and one per owner+for the two-owner arms. The plural arms therefore have their own matched+control rather than being compared against the single-owner one.++The plural arms build their @'Muts'@ bundle once, outside the loop, and thread+it. A bundle rebuilt per iteration would measure @(':-')@ allocation, which is+a real cost of some call sites but not a cost of the delimiter.+-}+module PureBorrow.Bench.ScopeDensity (+ test_scopeDensity,++ -- * Single-owner mutable arms+ scopeFreeLoop,+ reborrowingLoop,+ reborrowingDiscardingLoop,+ reborrowingFinalizingLoop,+ locallyDiscardingLoop,++ -- * Single-owner shared arms+ sharingHoistedLoop,+ sharingPerIterationLoop,++ -- * Two-owner arms+ scopeFreePairLoop,+ bundleThreadedPairLoop,+ reborrowingsDiscardingLoop,+ reborrowingsRespineLoop,+ reborrowingsLoop,+) where++import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure+import Control.Monad.Borrow.Pure.Experimental.Borrows+import Control.Monad.Borrow.Pure.Experimental.Reborrowable (locally_)+import Control.Syntax.DataFlow qualified as DataFlow+import Data.Ref.Linear qualified as Ref+import Data.Ref.Linear.Borrow qualified as RefBorrow+import Prelude.Linear+import Test.Tasty.Bench hiding (defaultMain)+import Prelude qualified as NonLinear++{- $setup+Each arm is 'NOINLINE' and takes its iteration count as an argument, so that a+count chosen by the benchmark driver cannot be constant-folded into the body.+-}++-- | Allocate one owner, borrow it, run @k@, and return the final counter.+withCounter ::+ (forall α. Mut α (Ref.Ref Int) %1 -> Lend α (Ref.Ref Int) %1 -> BO α (After α (Ur Int))) %1 ->+ Int+{-# INLINE withCounter #-}+withCounter k = unur $ linearly \linear -> DataFlow.do+ (runToken, refToken) <- dup linear+ runBO runToken Control.do+ (mut, lend) <- borrowM (Ref.new (0 :: Int) refToken)+ k mut lend++-- | The same with two independent owners.+withCounterPair ::+ ( forall α.+ Mut α (Ref.Ref Int) %1 ->+ Mut α (Ref.Ref Int) %1 ->+ Lend α (Ref.Ref Int) %1 ->+ Lend α (Ref.Ref Int) %1 ->+ BO α (After α (Ur Int))+ ) %1 ->+ Int+{-# INLINE withCounterPair #-}+withCounterPair k = unur $ linearly \linear -> DataFlow.do+ (runToken, ownerToken) <- dup linear+ (leftToken, rightToken) <- dup ownerToken+ runBO runToken Control.do+ (leftMut, leftLend) <- borrowM (Ref.new (0 :: Int) leftToken)+ (rightMut, rightLend) <- borrowM (Ref.new (0 :: Int) rightToken)+ k leftMut rightMut leftLend rightLend++-- | Reclaim one owner and move its final counter out.+finishCounter ::+ Lend α (Ref.Ref Int) %1 ->+ After α (Ur Int)+{-# INLINE finishCounter #-}+finishCounter lend = (move . Ref.free) Control.<$> reclaim' lend++{- | Reclaim both owners and move the sum of their counters out.++The sum, rather than one of the two, so that neither owner's updates can be+dropped as dead.+-}+finishCounterPair ::+ Lend α (Ref.Ref Int) %1 ->+ Lend α (Ref.Ref Int) %1 ->+ After α (Ur Int)+{-# INLINE finishCounterPair #-}+finishCounterPair leftLend rightLend =+ ( \left right -> case (move (Ref.free left), move (Ref.free right)) of+ (Ur left, Ur right) -> Ur (left + right)+ )+ Control.<$> reclaim' leftLend+ Control.<*> reclaim' rightLend++-- | The control: the mutation without a scope, with the borrow threaded by hand.+scopeFreeLoop :: Int -> Int+{-# NOINLINE scopeFreeLoop #-}+scopeFreeLoop iterations = withCounter (go iterations)+ where+ go ::+ forall α.+ Int ->+ Mut α (Ref.Ref Int) %1 ->+ Lend α (Ref.Ref Int) %1 ->+ BO α (After α (Ur Int))+ go !i mut lend+ | i <= 0 = consume mut `lseq` Control.pure (finishCounter lend)+ | otherwise = Control.do+ mut <- RefBorrow.modify (+ 1) mut+ go (i - 1) mut lend++-- | One result-discarding mutable scope per iteration.+reborrowingDiscardingLoop :: Int -> Int+{-# NOINLINE reborrowingDiscardingLoop #-}+reborrowingDiscardingLoop iterations = withCounter (go iterations)+ where+ go ::+ forall α.+ Int ->+ Mut α (Ref.Ref Int) %1 ->+ Lend α (Ref.Ref Int) %1 ->+ BO α (After α (Ur Int))+ go !i mut lend+ | i <= 0 = consume mut `lseq` Control.pure (finishCounter lend)+ | otherwise = Control.do+ mut <- reborrowing_ mut \scoped ->+ consume Control.<$> RefBorrow.modify (+ 1) scoped+ go (i - 1) mut lend++-- | One result-returning mutable scope per iteration.+reborrowingLoop :: Int -> Int+{-# NOINLINE reborrowingLoop #-}+reborrowingLoop iterations = withCounter (go iterations)+ where+ go ::+ forall α.+ Int ->+ Mut α (Ref.Ref Int) %1 ->+ Lend α (Ref.Ref Int) %1 ->+ BO α (After α (Ur Int))+ go !i mut lend+ | i <= 0 = consume mut `lseq` Control.pure (finishCounter lend)+ | otherwise = Control.do+ ((), mut) <- reborrowing mut \scoped ->+ consume Control.<$> RefBorrow.modify (+ 1) scoped+ go (i - 1) mut lend++-- | One finalizing mutable scope per iteration, whose continuation returns an 'After'.+reborrowingFinalizingLoop :: Int -> Int+{-# NOINLINE reborrowingFinalizingLoop #-}+reborrowingFinalizingLoop iterations = withCounter (go iterations)+ where+ go ::+ forall α.+ Int ->+ Mut α (Ref.Ref Int) %1 ->+ Lend α (Ref.Ref Int) %1 ->+ BO α (After α (Ur Int))+ go !i mut lend+ | i <= 0 = consume mut `lseq` Control.pure (finishCounter lend)+ | otherwise = Control.do+ ((), mut) <- reborrowing' mut \scoped -> Control.do+ scoped <- RefBorrow.modify (+ 1) scoped+ -- The inner 'Control.pure' is the @'After' β@ applicative's.+ Control.pure (Control.pure (consume scoped))+ go (i - 1) mut lend++{- | The shared control: @n@ reads under a single hoisted 'sharing' scope.++A shared borrow cannot mutate, so the shared arms read rather than write, and+their control cannot be scope-free: 'share' consumes the mutable occurrence, so+the only way to read through a 'Share' and still get the owner back is a scope.+The honest comparison is therefore against the share-once idiom the library+documents — one scope hoisted out of the loop, the 'Share' moved to an+unrestricted occurrence, and @n@ reads inside — which is what this arm is.+-}+sharingHoistedLoop :: Int -> Int+{-# NOINLINE sharingHoistedLoop #-}+sharingHoistedLoop iterations = withCounter \mut lend -> Control.do+ (Ur seen, mut) <- sharing mut \shared ->+ move shared & \(Ur shared) -> go iterations 0 shared+ consume mut `lseq`+ Control.pure ((\(Ur final) -> Ur (final + seen)) Control.<$> finishCounter lend)+ where+ go :: Int -> Int -> Share β (Ref.Ref Int) -> BO β (Ur Int)+ go !i !seen shared+ | i <= 0 = Control.pure (Ur seen)+ | otherwise = Control.do+ Ur observed <- move Control.<$> RefBorrow.copyRef shared+ go (i - 1) (seen + observed) shared++-- | The same @n@ reads, each under its own 'sharing' scope.+sharingPerIterationLoop :: Int -> Int+{-# NOINLINE sharingPerIterationLoop #-}+sharingPerIterationLoop iterations = withCounter (go iterations 0)+ where+ go ::+ forall α.+ Int ->+ Int ->+ Mut α (Ref.Ref Int) %1 ->+ Lend α (Ref.Ref Int) %1 ->+ BO α (After α (Ur Int))+ go !i !seen mut lend+ | i <= 0 =+ consume mut `lseq`+ Control.pure ((\(Ur final) -> Ur (final + seen)) Control.<$> finishCounter lend)+ | otherwise = Control.do+ (Ur observed, mut) <- sharing mut \shared ->+ move Control.<$> RefBorrow.copyRef shared+ go (i - 1) (seen + observed) mut lend++-- | One result-discarding scope per iteration, through the generic 'locally_'.+locallyDiscardingLoop :: Int -> Int+{-# NOINLINE locallyDiscardingLoop #-}+locallyDiscardingLoop iterations = withCounter (go iterations)+ where+ go ::+ forall α.+ Int ->+ Mut α (Ref.Ref Int) %1 ->+ Lend α (Ref.Ref Int) %1 ->+ BO α (After α (Ur Int))+ go !i mut lend+ | i <= 0 = consume mut `lseq` Control.pure (finishCounter lend)+ | otherwise = Control.do+ mut <- locally_ mut \scoped ->+ consume Control.<$> RefBorrow.modify (+ 1) scoped+ go (i - 1) mut lend++-- | The two-owner control: two mutations per iteration, no scope, no bundle.+scopeFreePairLoop :: Int -> Int+{-# NOINLINE scopeFreePairLoop #-}+scopeFreePairLoop iterations = withCounterPair (go iterations)+ where+ go ::+ forall α.+ Int ->+ Mut α (Ref.Ref Int) %1 ->+ Mut α (Ref.Ref Int) %1 ->+ Lend α (Ref.Ref Int) %1 ->+ Lend α (Ref.Ref Int) %1 ->+ BO α (After α (Ur Int))+ go !i leftMut rightMut leftLend rightLend+ | i <= 0 =+ consume leftMut `lseq`+ consume rightMut `lseq`+ Control.pure (finishCounterPair leftLend rightLend)+ | otherwise = Control.do+ leftMut <- RefBorrow.modify (+ 1) leftMut+ rightMut <- RefBorrow.modify (+ 1) rightMut+ go (i - 1) leftMut rightMut leftLend rightLend++{- | A bundle threaded and rebuilt each iteration, crossing no scope.++This was written to separate the spine cost from the delimiter cost, and it+does not do that — the arithmetic does not work, and saying so is cheaper than+letting a reader rederive it. It allocates about 80 bytes per iteration over+'scopeFreePairLoop' where the delimiter arms allocate 40 before erasure and 0+after, because its rebuilt spine is loop-carried and genuinely allocated, while+a delimiter hands back the bundle it was given and never rebuilds one. It is+not a point between 'scopeFreePairLoop' and the delimiter arms, so+@delimiter = reborrowings_ − bundled@ would come out negative.++What it does measure is a caller-side cost: what threading and rebuilding a+bundle per iteration costs someone who writes that. Keep it for that, and+compare the delimiter arms against 'scopeFreePairLoop' only. That comparison+charges the delimiter for the bundle destructure as well, so it is an upper+bound on the delimiter's own cost rather than an estimate — which is the+conservative direction.+-}+bundleThreadedPairLoop :: Int -> Int+{-# NOINLINE bundleThreadedPairLoop #-}+bundleThreadedPairLoop iterations =+ withCounterPair \leftMut rightMut leftLend rightLend ->+ go iterations (leftMut :- rightMut :- BNil) leftLend rightLend+ where+ go ::+ forall α.+ Int ->+ Muts α '[Ref.Ref Int, Ref.Ref Int] %1 ->+ Lend α (Ref.Ref Int) %1 ->+ Lend α (Ref.Ref Int) %1 ->+ BO α (After α (Ur Int))+ go !i bundle leftLend rightLend+ | i <= 0 =+ consume bundle `lseq`+ Control.pure (finishCounterPair leftLend rightLend)+ | otherwise = case bundle of+ leftMut :- rightMut :- BNil -> Control.do+ leftMut <- RefBorrow.modify (+ 1) leftMut+ rightMut <- RefBorrow.modify (+ 1) rightMut+ go (i - 1) (leftMut :- rightMut :- BNil) leftLend rightLend++{- | One result-discarding plural scope per iteration, over a two-member bundle.++The continuation consumes its two members individually rather than rebuilding a+bundle to consume, so this arm measures the delimiter and not the caller's+@(':-')@ cells. Compare it against 'scopeFreePairLoop'; see+'bundleThreadedPairLoop' for why the bundled arm is not the comparator.+-}+reborrowingsDiscardingLoop :: Int -> Int+{-# NOINLINE reborrowingsDiscardingLoop #-}+reborrowingsDiscardingLoop iterations =+ withCounterPair \leftMut rightMut leftLend rightLend ->+ go iterations (leftMut :- rightMut :- BNil) leftLend rightLend+ where+ go ::+ forall α.+ Int ->+ Muts α '[Ref.Ref Int, Ref.Ref Int] %1 ->+ Lend α (Ref.Ref Int) %1 ->+ Lend α (Ref.Ref Int) %1 ->+ BO α (After α (Ur Int))+ go !i bundle leftLend rightLend+ | i <= 0 =+ consume bundle `lseq`+ Control.pure (finishCounterPair leftLend rightLend)+ | otherwise = Control.do+ bundle <- reborrowings_ bundle \case+ leftScoped :- rightScoped :- BNil -> Control.do+ leftScoped <- RefBorrow.modify (+ 1) leftScoped+ rightScoped <- RefBorrow.modify (+ 1) rightScoped+ Control.pure (consume leftScoped `lseq` consume rightScoped)+ go (i - 1) bundle leftLend rightLend++{- | 'reborrowingsDiscardingLoop' with the shortened bundle rebuilt before it is consumed.++Measured, this arm is byte-identical to 'reborrowingsDiscardingLoop' in every+configuration: building a bundle only to consume it is case-of-known-constructor+and GHC removes it entirely. So it does not measure "the rebuild put back" —+there is nothing to put back. It is retained as a regression on that+elimination, since a caller who pattern-matches and rebuilds is the shape most+plural call sites actually have, and it would be worth knowing if it ever+started costing something.+-}+reborrowingsRespineLoop :: Int -> Int+{-# NOINLINE reborrowingsRespineLoop #-}+reborrowingsRespineLoop iterations =+ withCounterPair \leftMut rightMut leftLend rightLend ->+ go iterations (leftMut :- rightMut :- BNil) leftLend rightLend+ where+ go ::+ forall α.+ Int ->+ Muts α '[Ref.Ref Int, Ref.Ref Int] %1 ->+ Lend α (Ref.Ref Int) %1 ->+ Lend α (Ref.Ref Int) %1 ->+ BO α (After α (Ur Int))+ go !i bundle leftLend rightLend+ | i <= 0 =+ consume bundle `lseq`+ Control.pure (finishCounterPair leftLend rightLend)+ | otherwise = Control.do+ bundle <- reborrowings_ bundle \case+ leftScoped :- rightScoped :- BNil -> Control.do+ leftScoped <- RefBorrow.modify (+ 1) leftScoped+ rightScoped <- RefBorrow.modify (+ 1) rightScoped+ Control.pure (consume (leftScoped :- rightScoped :- BNil))+ go (i - 1) bundle leftLend rightLend++-- | One result-returning plural scope per iteration, over a two-member bundle.+reborrowingsLoop :: Int -> Int+{-# NOINLINE reborrowingsLoop #-}+reborrowingsLoop iterations =+ withCounterPair \leftMut rightMut leftLend rightLend ->+ go iterations (leftMut :- rightMut :- BNil) leftLend rightLend+ where+ go ::+ forall α.+ Int ->+ Muts α '[Ref.Ref Int, Ref.Ref Int] %1 ->+ Lend α (Ref.Ref Int) %1 ->+ Lend α (Ref.Ref Int) %1 ->+ BO α (After α (Ur Int))+ go !i bundle leftLend rightLend+ | i <= 0 =+ consume bundle `lseq`+ Control.pure (finishCounterPair leftLend rightLend)+ | otherwise = Control.do+ ((), bundle) <- reborrowings bundle \case+ leftScoped :- rightScoped :- BNil -> Control.do+ leftScoped <- RefBorrow.modify (+ 1) leftScoped+ rightScoped <- RefBorrow.modify (+ 1) rightScoped+ Control.pure (consume leftScoped `lseq` consume rightScoped)+ go (i - 1) bundle leftLend rightLend++{- | The iteration counts swept.++Three points, an order of magnitude apart, so that the per-crossing cost comes+out as a slope. One point could not tell a per-crossing cost from the fixture's+own setup.+-}+iterationCounts :: [Int]+iterationCounts = [1024, 16384, 262144]++test_scopeDensity :: [Benchmark]+test_scopeDensity =+ [ bgroup+ "scope-density"+ [ bgroup+ (NonLinear.show iterations)+ [ bgroup+ "mutable"+ [ bench "direct" $ nf scopeFreeLoop iterations+ , bench "reborrowing_" $ nf reborrowingDiscardingLoop iterations+ , bench "reborrowing" $ nf reborrowingLoop iterations+ , bench "reborrowing'" $ nf reborrowingFinalizingLoop iterations+ , bench "locally_" $ nf locallyDiscardingLoop iterations+ ]+ , bgroup+ "shared"+ [ bench "share-once" $ nf sharingHoistedLoop iterations+ , bench "sharing" $ nf sharingPerIterationLoop iterations+ ]+ , bgroup+ "plural"+ [ bench "direct" $ nf scopeFreePairLoop iterations+ , bench "bundled" $ nf bundleThreadedPairLoop iterations+ , bench "reborrowings_" $ nf reborrowingsDiscardingLoop iterations+ , bench "reborrowings_/respine" $ nf reborrowingsRespineLoop iterations+ , bench "reborrowings" $ nf reborrowingsLoop iterations+ ]+ ]+ | iterations <- iterationCounts+ ]+ ]
+ bench/suite/PureBorrow/Bench/Unboxed.hs view
@@ -0,0 +1,11 @@+{- | Exposes the benchmarks of "PureBorrow.Internal.Bench.Unboxed" to+@tasty-discover@. The kernels themselves stay in the @bench-suites@ internal+library because @pure-borrow-test@ exercises them too.+-}+module PureBorrow.Bench.Unboxed (test_unboxed) where++import PureBorrow.Internal.Bench.Unboxed qualified as Unboxed+import Test.Tasty.Bench (Benchmark)++test_unboxed :: [Benchmark]+test_unboxed = Unboxed.benches
+ bench/suite/PureBorrow/Bench/Worklist/Resume.hs view
@@ -0,0 +1,12 @@+{- | Exposes the benchmarks of "PureBorrow.Internal.Bench.Worklist.Resume" to+@tasty-discover@. The kernels themselves stay in the @bench-suites@ internal+library because @pure-borrow-test@ and @pure-borrow-inspection@ exercise them+too.+-}+module PureBorrow.Bench.Worklist.Resume (test_worklistResume) where++import PureBorrow.Internal.Bench.Worklist.Resume qualified as Resume+import Test.Tasty.Bench (Benchmark)++test_worklistResume :: [Benchmark]+test_worklistResume = Resume.benches
− dockerfiles/artifact/Dockerfile
@@ -1,44 +0,0 @@-FROM debian:bookworm AS build--RUN apt-get update && apt-get install -y build-essential curl libffi-dev libffi8ubuntu1 libgmp-dev libgmp10 libncurses-dev gnupg2 git--# install ghcup-RUN echo ${ARCH}-RUN curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | BOOTSTRAP_HASKELL_NONINTERACTIVE=1 BOOTSTRAP_HASKELL_MINIMAL=1 sh-RUN cp ~/.ghcup/bin/ghcup /usr/local/bin/ghcup--ARG GHC=9.10.3-RUN ghcup install ghc --isolate /usr/local --force ${GHC}-ARG CABAL=3.14.2.0-RUN ghcup install cabal --isolate /usr/local/bin --force ${CABAL}--RUN mkdir -p /workspace-ARG PURE_BORROW_COMMIT=e5b027fb84663d2fc2b92956bbc32a09dfeda272-RUN git clone https://github.com/SoftwareFoundationGroupAtKyotoU/pure-borrow.git /workspace/pure-borrow && \- cd /workspace/pure-borrow && \- git fetch --all && \- git checkout ${PURE_BORROW_COMMIT}--WORKDIR /workspace/pure-borrow-ENV PATH="/usr/local/bin:${PATH}"-ENV LANG=C.UTF-8--ARG CABAL_INDEX_STATE=2026-02-16T11:32:42Z-RUN cabal update --index-state=${CABAL_INDEX_STATE}-ARG NUM_CPUS=8-RUN cabal configure --enable-tests --enable-benchmarks --semaphore -j${NUM_CPUS} -fartifact-RUN cabal build all --only-dependencies-RUN cabal build all-RUN mkdir -p /opt/pure-borrow-RUN cp "$(cabal list-bin artifact-runner)" /opt/pure-borrow-RUN apt-get update && apt-get install -y gnuplot--FROM debian:bookworm-slim--ENV PATH="/opt/pure-borrow:${PATH}"-RUN mkdir -p /workspace-WORKDIR /workspace-COPY --from=build /opt/pure-borrow /opt/pure-borrow-RUN apt-get update && apt-get install -y gnuplot--ENTRYPOINT ["/opt/pure-borrow/artifact-runner"]
+ internal-src/demo-impl/PureBorrow/Demo/Fft.hs view
@@ -0,0 +1,158 @@+{-# LANGUAGE ApplicativeDo #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE PartialTypeSignatures #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE ViewPatterns #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}++module PureBorrow.Demo.Fft (+ defaultMain,+ defaultMainWith,+ CLIOpts (..),+ optionsP,+) where++import Control.Applicative ((<**>))+import Control.Concurrent (getNumCapabilities)+import Control.Concurrent.DivideConquer.Linear (fftDC)+import Control.DeepSeq (NFData (..), force)+import Control.Exception (evaluate)+import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.BO+import Control.Syntax.DataFlow qualified as DataFlow+import Data.Bits (popCount)+import Data.Complex+import Data.FMList qualified as FML+import Data.Vector qualified as V+import Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted qualified as UV+import Data.Vector.Unboxed qualified as U+import Options.Applicative qualified as Opts+import Prelude.Linear (dup, unur)+import Prelude.Linear qualified as PL+import System.Directory (createDirectoryIfMissing)+import System.FilePath (takeDirectory)+import System.Random+import Text.Read (readEither)+import Prelude++data CLIOpts = CLIOpts+ { threshold :: !Int+ , size :: !Int+ , seed :: !(Maybe Int)+ , output :: !(Maybe FilePath)+ }+ deriving (Show, Eq, Ord)++optionsP :: Opts.ParserInfo CLIOpts+optionsP = Opts.info (p <**> Opts.helper) $ Opts.progDesc "Parallel FFT"+ where+ p = do+ threshold <-+ Opts.option Opts.auto $+ Opts.short 'w'+ <> Opts.long "workstreal"+ <> Opts.value 1024+ <> Opts.showDefault+ <> Opts.help "Worksteal threshold to calculate sequentially below this length."+ size <-+ Opts.option power2 $+ Opts.short 'n'+ <> Opts.long "size"+ <> Opts.value kN+ <> Opts.showDefault+ <> Opts.help "Sample Size (must be a power of 2)"+ output <-+ Opts.optional $+ Opts.strOption $+ Opts.short 'o'+ <> Opts.metavar "FILE"+ <> Opts.help "Output TSV path"+ seed <-+ Opts.optional $+ Opts.option Opts.auto $+ Opts.short 's'+ <> Opts.long "seed"+ <> Opts.metavar "INT"+ <> Opts.help "Random seed"+ pure CLIOpts {..}++power2 :: Opts.ReadM Int+power2 = Opts.eitherReader \s ->+ case readEither s of+ Right n+ | n > 0 && popCount n == 1 -> Right n+ | otherwise -> Left $ "Must be a positive power of 2, but got: " <> s+ Left err -> Left err++sample :: Int -> (Double -> Double) -> V.Vector Double+sample n f = V.generate n \i -> f (-4 + 8 * fromIntegral i / fromIntegral n)++-- | Convert to the vector of complex numbers, with real part even element and imaginary part odd.+compress :: V.Vector Double -> V.Vector (Complex Double)+compress v =+ V.generate (V.length v `quot` 2) \i ->+ let re = v V.! (2 * i)+ im = v V.! (2 * i + 1)+ in re :+ im++kN :: Int+kN = 2 ^ (20 :: Int)++fun :: Double -> Double+fun x = sin (2 * pi * x) + 2 * cos (pi * x) + 3 * sin (0.5 * pi * x) + 5++defaultMain :: IO ()+defaultMain = do+ opts <- Opts.execParser optionsP+ defaultMainWith opts++defaultMainWith :: CLIOpts -> IO ()+defaultMainWith CLIOpts {..} = do+ numCap <- getNumCapabilities+ !v <- evaluate $ force $ compress $ sample size fun+ g <- maybe newStdGen (pure . mkStdGen) seed+ let !kM = size `quot` 2+ toFreq i = fromIntegral ((i + kM) `rem` size - kM) / 8+ decodeComp !i (!c :: Complex Double)+ | i == (0 :: Int) = (realPart c / fromIntegral size, 0)+ | otherwise =+ let re :+ im = 2 * c / fromIntegral size+ in (re, im)+ retrv = case output of+ Nothing -> evaluate . rnf+ Just fp -> \vs -> do+ createDirectoryIfMissing True $ takeDirectory fp+ writeFile fp+ $ unlines+ $ FML.toList+ $ FML.cons "Frequency\tcos\tsin"+ $ U.foldMap+ ( \(i, c) ->+ let (co, si) = decodeComp i c+ in FML.singleton $ show (toFreq i :: Double) <> "\t" <> show co <> "\t" <> show si+ )+ $ U.indexed+ $ V.convert vs+ putStrLn $ "Written to: " <> fp+ retrv $+ postprocess size $+ unur PL.$ linearly \lin -> DataFlow.do+ (lin, l2) <- dup lin+ runBO lin Control.do+ (vec, lend) <- borrowM (UV.fromVector v l2)+ Control.void PL.$ fftDC g numCap threshold vec+ pureAfter (UV.toVector PL.$ reclaim lend)++postprocess :: Int -> V.Vector (Complex Double) -> V.Vector (Complex Double)+postprocess kN hs =+ let !kM = kN `quot` 2+ in V.generate (kM + 1) \((`rem` kM) -> k) ->+ let !m = (kM - k) `rem` kM+ in 0.5 * ((hs V.! k) + conjugate (hs V.! m))+ - (0 :+ 0.5) * (hs V.! k - conjugate (hs V.! m)) * exp (0 :+ (2 * pi * fromIntegral k / fromIntegral kN))
+ internal-src/demo-impl/PureBorrow/Demo/QSort.hs view
@@ -0,0 +1,121 @@+{-# LANGUAGE ApplicativeDo #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE PartialTypeSignatures #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}++module PureBorrow.Demo.QSort (+ defaultMain,+ defaultMainWith,+ CLIOpts (..),+ optionsP,+) where++import Control.Applicative ((<**>), (<|>))+import Control.Concurrent (getNumCapabilities)+import Control.Concurrent.DivideConquer.Linear (naiveDivideAndConquer, qsortDC, qsortDC')+import Control.DeepSeq (force)+import Control.Exception (evaluate)+import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.BO+import Control.Syntax.DataFlow qualified as DataFlow+import Data.Functor (void)+import Data.Vector qualified as V+import Data.Vector.Algorithms.Intro qualified as AI+import Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted qualified as UV+import Data.Vector.Mutable.Linear.Borrow qualified as VL+import GHC.Generics (Generic)+import Options.Applicative qualified as Opts+import Prelude.Linear hiding (Eq, Ord, Semigroup (..), ($), ($!))+import Prelude.Linear qualified as PL hiding (($!))+import System.Mem (performGC)+import System.Random+import System.Random.Stateful (runStateGen_, uniformM)++data Mode = Parallel Word | Naive Int | Worksteal Int Int | Sequential | IntroSort+ deriving (Show, Eq, Ord, Generic)++data CLIOpts = CLIOpts {mode :: Mode, size :: Int, seed :: Maybe Int}+ deriving (Show, Eq, Ord, Generic)++optionsP :: Int -> Opts.ParserInfo CLIOpts+optionsP numCap = Opts.info (p <**> Opts.helper) $ Opts.progDesc "Parallel quicksort with linear borrows"+ where+ p = do+ mode <-+ Parallel <$> Opts.option Opts.auto (Opts.long "parallel" <> Opts.short 'p' <> Opts.help "Use parallel quicksort with specified capacity (default: 8)")+ <|> Opts.flag' Sequential (Opts.long "sequential" <> Opts.short 'S' <> Opts.help "Use sequential quicksort")+ <|> Opts.flag' (Worksteal numCap 512) (Opts.long "worksteal" <> Opts.short 'w' <> Opts.help "Use work-stealing quicksort")+ <|> Opts.flag' (Naive 512) (Opts.long "naive" <> Opts.short 'Z' <> Opts.help "Use naive divide-and-conquer quicksort")+ <|> Opts.flag (Parallel 8) IntroSort (Opts.long "intro" <> Opts.short 'i' <> Opts.help "Use intro sort")+ size <-+ Opts.option+ Opts.auto+ ( Opts.long "size"+ <> Opts.short 'n'+ <> Opts.value 256+ <> Opts.showDefault+ <> Opts.help "Size of the vector to sort"+ )+ seed <- Opts.optional $ Opts.option Opts.auto (Opts.long "seed" <> Opts.short 's' <> Opts.help "Random seed for vector generation (default: random)")+ pure CLIOpts {..}++qsortWith :: Mode -> StdGen -> V.Vector Int -> V.Vector Int+qsortWith IntroSort _ v = V.modify AI.sort v+qsortWith (Parallel bud) _ v =+ unur PL.$ linearly \lin ->+ DataFlow.do+ (lin, l2) <- dup lin+ runBO lin Control.do+ (v, lend) <- borrowM (VL.fromVector v l2)+ VL.qsort bud v+ pureAfter (VL.toVector PL.$ reclaim lend)+qsortWith Sequential _ v =+ unur PL.$ linearly \lin ->+ DataFlow.do+ (lin, l2) <- dup lin+ runBO lin Control.do+ (v, lend) <- borrowM (VL.fromVector v l2)+ VL.qsort 0 v+ pureAfter (VL.toVector PL.$ reclaim lend)+qsortWith (Worksteal workers thresh) g v =+ unur PL.$ linearly \lin ->+ DataFlow.do+ (lin, l2) <- dup lin+ runBO lin Control.do+ (v, lend) <- borrowM (UV.fromVector v l2)+ Control.void PL.$ qsortDC g workers thresh v+ pureAfter (UV.toVector PL.$ reclaim lend)+qsortWith (Naive thresh) _ v =+ unur PL.$ linearly \lin ->+ DataFlow.do+ (lin, l2) <- dup lin+ runBO lin Control.do+ (v, lend) <- borrowM (UV.fromVector v l2)+ Control.void PL.$ naiveDivideAndConquer (qsortDC' thresh) v+ pureAfter (UV.toVector PL.$ reclaim lend)++defaultMainWith :: CLIOpts -> IO ()+defaultMainWith CLIOpts {..} = do+ putStrLn $ "Sorting " <> show size <> " elements with mode: " <> show mode+ gen <- case seed of+ Just s -> return $ mkStdGen s+ Nothing -> newStdGen+ let !vec =+ runStateGen_ gen \g -> do+ V.replicateM size (uniformM g)+ gen <- newStdGen+ performGC+ void $ evaluate $ force $ qsortWith mode gen vec++defaultMain :: IO ()+defaultMain = do+ numCap <- getNumCapabilities+ opts <- Opts.execParser $ optionsP numCap+ defaultMainWith opts
− internal-src/qsort-bench-suites/PureBorrow/Internal/Bench/QSort.hs
@@ -1,169 +0,0 @@-{-# LANGUAGE BlockArguments #-}-{-# LANGUAGE QualifiedDo #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeApplications #-}-{-# OPTIONS_GHC -Wno-name-shadowing #-}--module PureBorrow.Internal.Bench.QSort (- defaultMain,- defaultMainWith,- optionsP,- rawOptsP,- BenchOpts (..),- benches,- kMAX_SIZE,-) where--import Control.Applicative-import Control.Concurrent (getNumCapabilities)-import Control.Concurrent.DivideConquer.Linear (qsortDC)-import Control.Functor.Linear qualified as Control-import Control.Monad.Borrow.Pure.BO-import Control.Syntax.DataFlow qualified as DataFlow-import Data.Proxy (Proxy (..))-import Data.Vector qualified as V-import Data.Vector.Algorithms.Intro qualified as AI-import Data.Vector.Mutable.Linear.Borrow qualified as VL-import Options.Applicative qualified as Opts-import Prelude.Linear (dup, unur)-import Prelude.Linear qualified as PL-import System.Random.Stateful-import Test.Tasty (askOption, defaultMainWithIngredients)-import Test.Tasty.Bench hiding (defaultMain)-import Test.Tasty.Bench qualified as Bench-import Test.Tasty.Ingredients.Basic (includingOptions)-import Test.Tasty.Options-import Text.Read (readMaybe)-import Prelude as P--data Mode = Parallel Word | Worksteal Int | Sequential | IntroSort- deriving (Show, Eq, Ord)--data BenchOpts = BenchOpts {numThreads :: !Int, sampleSize :: !Int}- deriving (Show, Eq, Ord)--optionsP :: Opts.ParserInfo BenchOpts-optionsP =- Opts.info (p <**> Opts.helper) $- Opts.fullDesc- <> Opts.progDesc "Options for qsort benchmark"- where- p :: Opts.Parser BenchOpts- p =- BenchOpts- <$> Opts.option- Opts.auto- ( Opts.long "threads"- <> Opts.short 'N'- <> Opts.metavar "NUM_THREADS"- <> Opts.help "Number of threads to use for parallel benchmarks"- )- <*> Opts.option- Opts.auto- ( Opts.long "size"- <> Opts.short 's'- <> Opts.metavar "SAMPLE_SIZE"- <> Opts.help "Number of samples to take (must divide 32768)"- )--rawOptsP :: Opts.Parser BenchOpts-rawOptsP =- BenchOpts- <$> Opts.option- Opts.auto- ( Opts.long "threads"- <> Opts.short 'N'- <> Opts.value 10- <> Opts.metavar "NUM_THREADS"- <> Opts.help "Number of threads to use for parallel benchmarks"- )- <*> Opts.option- Opts.auto- ( Opts.long "size"- <> Opts.short 's'- <> Opts.metavar "SAMPLE_SIZE"- <> Opts.value 32- <> Opts.help "Number of samples to take (must divide 32768)"- )--qsortWith :: Mode -> V.Vector Int -> V.Vector Int-qsortWith IntroSort v = V.modify AI.sort v-qsortWith (Parallel budget) v =- unur PL.$ linearly \lin ->- DataFlow.do- (lin, l2) <- dup lin- runBO lin Control.do- (v, lend) <- borrowM (VL.fromVector v l2)- VL.qsort budget v- Control.pure PL.$ VL.toVector Control.<$> reclaim' lend-qsortWith Sequential v =- unur PL.$ linearly \lin ->- DataFlow.do- (lin, l2) <- dup lin- runBO lin Control.do- (v, lend) <- borrowM (VL.fromVector v l2)- VL.qsort 0 v- pureAfter (VL.toVector PL.$ reclaim lend)-qsortWith (Worksteal p) v =- unur PL.$ linearly \lin ->- DataFlow.do- (lin, l2) <- dup lin- runBO lin Control.do- (v, lend) <- borrowM (VL.fromVector v l2)- Control.void PL.$ qsortDC p 128 v- pureAfter (VL.toVector PL.$ reclaim lend)--data SampleSize = SampleSize Int- deriving (Show, Eq, Ord)--instance IsOption SampleSize where- defaultValue = SampleSize 32- parseValue s =- case readMaybe s of- Just n | kMAX_SIZE `rem` n == 0 -> Just (SampleSize n)- _ -> Nothing- optionName = return "size"- optionHelp = return "Step size to take a sample (must divide 32768)"--defaultMain :: IO ()-defaultMain = do- numThreads <- getNumCapabilities- let customOpts = [Option (Proxy :: Proxy SampleSize)]- ingredients = includingOptions customOpts : benchIngredients- defaultMainWithIngredients ingredients $ askOption \(SampleSize sampleSize) ->- bgroup "All" $ benches BenchOpts {..}--defaultMainWith :: BenchOpts -> IO ()-defaultMainWith opts = do- Bench.defaultMain $ benches opts--benches :: BenchOpts -> [Benchmark]-benches BenchOpts {..} =- [ bgroup- "qsort"- [ env- ( pure $ runStateGen_ (mkStdGen 42) \g -> do- V.replicateM size (uniformM g)- )- \vec ->- bgroup- (show size)- ( [ bench "intro" $ nf (qsortWith IntroSort) vec- , bench "sequential" $ nf (qsortWith Sequential) vec- ]- ++ [ bench ("parallel (budget = " <> show n <> ")") $- nf (qsortWith $ Parallel n) vec- | n <- [4, 8, 16, 32]- ]- ++ [ bench ("worksteal (workers = " <> show n <> ")") $- nf (qsortWith $ Worksteal n) vec- | n <- [2, 4 .. numThreads]- ]- )- | i <- [0 .. sampleSize]- , let size = i * kMAX_SIZE `quot` sampleSize- ]- ]--kMAX_SIZE :: Int-kMAX_SIZE = 32 * 1024
− internal-src/qsort-demo-impl/PureBorrow/Demo/QSort.hs
@@ -1,111 +0,0 @@-{-# LANGUAGE ApplicativeDo #-}-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE BlockArguments #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE PartialTypeSignatures #-}-{-# LANGUAGE QualifiedDo #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-}-{-# OPTIONS_GHC -Wno-name-shadowing #-}--module PureBorrow.Demo.QSort (- defaultMain,- defaultMainWith,- CLIOpts (..),- optionsP,-) where--import Control.Applicative ((<**>), (<|>))-import Control.Concurrent (getNumCapabilities)-import Control.Concurrent.DivideConquer.Linear (qsortDC)-import Control.DeepSeq (force)-import Control.Exception (evaluate)-import Control.Functor.Linear qualified as Control-import Control.Monad.Borrow.Pure.BO-import Control.Syntax.DataFlow qualified as DataFlow-import Data.Functor (void)-import Data.Vector qualified as V-import Data.Vector.Algorithms.Intro qualified as AI-import Data.Vector.Mutable.Linear.Borrow qualified as VL-import GHC.Generics (Generic)-import Options.Applicative qualified as Opts-import Prelude.Linear hiding (Eq, Ord, Semigroup (..), ($), ($!))-import Prelude.Linear qualified as PL hiding (($!))-import System.Mem (performGC)-import System.Random-import System.Random.Stateful (runStateGen_, uniformM)--data Mode = Parallel Word | Worksteal Int Int | Sequential | IntroSort- deriving (Show, Eq, Ord, Generic)--data CLIOpts = CLIOpts {mode :: Mode, size :: Int, seed :: Maybe Int}- deriving (Show, Eq, Ord, Generic)--optionsP :: Int -> Opts.ParserInfo CLIOpts-optionsP numCap = Opts.info (p <**> Opts.helper) $ Opts.progDesc "Parallel quicksort with linear borrows"- where- p = do- mode <-- Parallel <$> Opts.option Opts.auto (Opts.long "parallel" <> Opts.short 'p' <> Opts.help "Use parallel quicksort with specified capacity (default: 8)")- <|> Opts.flag' Sequential (Opts.long "sequential" <> Opts.short 'S' <> Opts.help "Use sequential quicksort")- <|> Opts.flag' (Worksteal numCap 512) (Opts.long "worksteal" <> Opts.short 'w' <> Opts.help "Use work-stealing quicksort")- <|> Opts.flag (Parallel 8) IntroSort (Opts.long "intro" <> Opts.short 'i' <> Opts.help "Use intro sort")- size <-- Opts.option- Opts.auto- ( Opts.long "size"- <> Opts.short 'n'- <> Opts.value 256- <> Opts.showDefault- <> Opts.help "Size of the vector to sort"- )- seed <- Opts.optional $ Opts.option Opts.auto (Opts.long "seed" <> Opts.short 's' <> Opts.help "Random seed for vector generation (default: random)")- pure CLIOpts {..}--qsortWith :: Mode -> StdGen -> V.Vector Int -> V.Vector Int-qsortWith IntroSort _ v = V.modify AI.sort v-qsortWith (Parallel bud) _ v =- unur PL.$ linearly \lin ->- DataFlow.do- (lin, l2) <- dup lin- runBO lin Control.do- (v, lend) <- borrowM (VL.fromVector v l2)- VL.qsort bud v- pureAfter (VL.toVector PL.$ reclaim lend)-qsortWith Sequential _ v =- unur PL.$ linearly \lin ->- DataFlow.do- (lin, l2) <- dup lin- runBO lin Control.do- (v, lend) <- borrowM (VL.fromVector v l2)- VL.qsort 0 v- pureAfter (VL.toVector PL.$ reclaim lend)-qsortWith (Worksteal workers thresh) _ v =- unur PL.$ linearly \lin ->- DataFlow.do- (lin, l2) <- dup lin- runBO lin Control.do- (v, lend) <- borrowM (VL.fromVector v l2)- Control.void PL.$ qsortDC workers thresh v- pureAfter (VL.toVector PL.$ reclaim lend)--defaultMainWith :: CLIOpts -> IO ()-defaultMainWith CLIOpts {..} = do- putStrLn $ "Sorting " <> show size <> " elements with mode: " <> show mode- gen <- case seed of- Just s -> return $ mkStdGen s- Nothing -> newStdGen- let !vec =- runStateGen_ gen \g -> do- V.replicateM size (uniformM g)- gen <- newStdGen- performGC- void $ evaluate $ force $ qsortWith mode gen vec--defaultMain :: IO ()-defaultMain = do- numCap <- getNumCapabilities- opts <- Opts.execParser $ optionsP numCap- defaultMainWith opts
+ internal-src/test-bench-common/PureBorrow/Internal/Bench/MultiStoreScan.hs view
@@ -0,0 +1,1866 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE LinearTypes #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE OverloadedLabels #-}+{-# LANGUAGE QualifiedDo #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}++module PureBorrow.Internal.Bench.MultiStoreScan (+ MultiStoreScanInput (..),+ MultiStoreScanOutput (..),+ MultiStoreScanResult (..),+ MultiStoreScanSummary (..),+ TraceEvent,+ multiStoreScanDirectInput,+ multiStoreScanDirectBenchmarkRoot,+ multiStoreScanDirectHeaderMatchedBenchmarkRoot,+ multiStoreScanDirectRoot,+ multiStoreScanNodeCount,+ multiStoreScanBoxedContentProjection,+ multiStoreScanPureBorrowDirectBenchmarkRoot,+ multiStoreScanPureBorrowDirectRoot,+ multiStoreScanPureBorrowFixedUnrestrictedBenchmarkRoot,+ multiStoreScanPureBorrowFixedUnrestrictedWorker,+ multiStoreScanPureBorrowNestedBenchmarkRoot,+ multiStoreScanPureBorrowNestedRoot,+ multiStoreScanPureBorrowOwningBenchmarkRoot,+ multiStoreScanPureBorrowOwningWorker,+ multiStoreScanPureBorrowWorker,+ multiStoreScanUnboxedContentProjection,+ benches,+ defaultMain,+) where++import Control.DeepSeq (NFData)+import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure+import Control.Monad.Borrow.Pure.Experimental.Borrows (+ Aliases (..),+ reborrowings,+ )+import Control.Monad.ST.Strict (ST, runST)+import Control.Syntax.DataFlow qualified as DataFlow+import Data.List qualified as List+import Data.Record.Linear.Borrow.Experimental.PatternMatch (+ RecordLabel,+ (.@),+ )+import Data.STRef (STRef, newSTRef, readSTRef)+import Data.Vector qualified as V+import Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted qualified as Growable+import Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted qualified as Fixed+import Data.Vector.Mutable qualified as MV+import Data.Vector.Mutable.Growable.Linear.Borrow qualified as OwningBoxedGrowable+import Data.Vector.Mutable.Linear.Borrow qualified as OwningBoxedFixed+import Data.Vector.Unboxed qualified as U+import Data.Vector.Unboxed.Mutable qualified as UM+import Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow qualified as OwningUnboxedGrowable+import Data.Vector.Unboxed.Mutable.Linear.Borrow qualified as OwningUnboxedFixed+import GHC.Exts qualified as GHC+import GHC.Generics (Generic)+import GHC.Int (Int64 (I64#))+import Prelude.Linear (+ lseq,+ unur,+ (&),+ )+import Test.Tasty.Bench (Benchmark, bench, bgroup, env, nf)+import Test.Tasty.Bench qualified as Bench++data MultiStoreScanInput = MultiStoreScanInput+ { inputNext :: !(U.Vector Int)+ , inputWeight :: !(U.Vector Int)+ , inputMark :: !(U.Vector Int)+ , inputPayload :: !(V.Vector (Int, Int))+ , inputScore :: !(U.Vector Int)+ , inputLink :: !(U.Vector Int)+ }+ deriving stock (Generic)+ deriving anyclass (NFData)++data MultiStoreScanSummary = MultiStoreScanSummary+ { visitedNodes :: !Int+ , elementReads :: !Int+ , elementWrites :: !Int+ , headerReads :: !Int+ , validationReads :: !Int+ , finalDigest :: !Int64+ }+ deriving stock (Eq, Generic, Show)+ deriving anyclass (NFData)++data MultiStoreScanResult = MultiStoreScanResult+ { resultSummary :: !MultiStoreScanSummary+ , resultVisitedIndices :: !(U.Vector Int)+ , resultEvents :: !(V.Vector TraceEvent)+ , resultEventDigest :: !Int64+ , resultMarks :: !(U.Vector Int)+ , resultScores :: !(U.Vector Int)+ }+ deriving stock (Eq, Generic, Show)+ deriving anyclass (NFData)++data MultiStoreScanOutput = MultiStoreScanOutput+ { outputDigest :: !Int64+ , outputMarks :: !(U.Vector Int)+ , outputScores :: !(U.Vector Int)+ }+ deriving stock (Eq, Generic, Show)+ deriving anyclass (NFData)++data TraceStore+ = NextStore+ | WeightStore+ | MarkStore+ | ScoreStore+ | LinkStore+ deriving stock (Eq, Generic, Show)+ deriving anyclass (NFData)++data TraceEvent+ = ReadIntEvent !TraceStore !Int !Int+ | ReadPayloadEvent !Int !Int !Int+ | WriteIntEvent !TraceStore !Int !Int+ deriving stock (Eq, Generic, Show)+ deriving anyclass (NFData)++data AccessTrace = AccessTrace+ { traceVisitedIndicesRev :: ![Int]+ , traceEventsRev :: ![TraceEvent]+ , traceEventDigest :: !Int64+ , traceVisitedNodes :: !Int+ , traceElementReads :: !Int+ , traceElementWrites :: !Int+ , traceHeaderReads :: !Int+ , traceReadDigest :: !Int64+ }++data FixedRoots = FixedRoots+ { next :: !(Fixed.Vector U.Vector Int)+ , weight :: !(Fixed.Vector U.Vector Int)+ , mark :: !(Fixed.Vector U.Vector Int)+ }++data GrowableRoots = GrowableRoots+ { payload :: !(Growable.GrowableVector V.Vector (Int, Int))+ , score :: !(Growable.GrowableVector U.Vector Int)+ , link :: !(Growable.GrowableVector U.Vector Int)+ }++data MultiStore = MultiStore+ { fixedRoots :: !FixedRoots+ , growableRoots :: !GrowableRoots+ }++data OwningFixedRoots = OwningFixedRoots+ { owningNext :: !(OwningUnboxedFixed.Vector Int)+ , owningWeight :: !(OwningUnboxedFixed.Vector Int)+ , owningMark :: !(OwningUnboxedFixed.Vector Int)+ }++data OwningGrowableRoots = OwningGrowableRoots+ { owningPayload :: !(OwningBoxedGrowable.GrowableVector (Int, Int))+ , owningScore :: !(OwningUnboxedGrowable.GrowableVector Int)+ , owningLink :: !(OwningUnboxedGrowable.GrowableVector Int)+ }++data OwningMultiStore = OwningMultiStore+ { owningFixedRoots :: !OwningFixedRoots+ , owningGrowableRoots :: !OwningGrowableRoots+ }++data FixedUnrestrictedStore = FixedUnrestrictedStore+ { fixedUnrestrictedRoots :: !FixedRoots+ , fixedUnrestrictedGrowableRoots :: !OwningGrowableRoots+ }++multiStoreScanBoxedContentProjection ::+ Mut α (Growable.GrowableVector V.Vector (Int, Int)) %1 ->+ Mut α (Fixed.Vector V.Vector (Int, Int))+{-# INLINE multiStoreScanBoxedContentProjection #-}+multiStoreScanBoxedContentProjection = Growable.getContents++multiStoreScanUnboxedContentProjection ::+ Mut α (Growable.GrowableVector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int)+{-# INLINE multiStoreScanUnboxedContentProjection #-}+multiStoreScanUnboxedContentProjection = Growable.getContents++multiStoreScanNodeCount :: Int+multiStoreScanNodeCount = 4096++multiStoreScanDirectInput :: MultiStoreScanInput+multiStoreScanDirectInput =+ MultiStoreScanInput+ { inputNext = U.generate multiStoreScanNodeCount \index -> (index + 1) `rem` multiStoreScanNodeCount+ , inputWeight = U.generate multiStoreScanNodeCount \index -> (index * 17 + 3) `rem` 101+ , inputMark = U.replicate multiStoreScanNodeCount 0+ , inputPayload =+ V.generate multiStoreScanNodeCount \index ->+ (index `rem` 7, index `rem` 13)+ , inputScore = U.generate multiStoreScanNodeCount \index -> (index * 5 + 11) `rem` 97+ , inputLink = U.replicate multiStoreScanNodeCount 0+ }++validateInput :: MultiStoreScanInput -> Int+{-# NOINLINE validateInput #-}+validateInput input+ | U.length (inputNext input)+ == multiStoreScanNodeCount+ && U.length (inputWeight input)+ == multiStoreScanNodeCount+ && U.length (inputMark input)+ == multiStoreScanNodeCount+ && V.length (inputPayload input)+ == multiStoreScanNodeCount+ && U.length (inputScore input)+ == multiStoreScanNodeCount+ && U.length (inputLink input)+ == multiStoreScanNodeCount+ && nextReads+ == multiStoreScanNodeCount+ && linkReads+ == multiStoreScanNodeCount =+ 6 + nextReads + linkReads+ | otherwise =+ error+ "multi-store scan requires six 4096-element vectors, in-range next indices, and zero links"+ where+ !nextReads =+ U.foldl'+ ( \count value ->+ if value >= 0 && value < multiStoreScanNodeCount+ then count + 1+ else -multiStoreScanNodeCount+ )+ 0+ (inputNext input)+ !linkReads =+ U.foldl'+ ( \count value ->+ if value == 0+ then count + 1+ else -multiStoreScanNodeCount+ )+ 0+ (inputLink input)++multiStoreScanDirectRoot :: MultiStoreScanInput -> MultiStoreScanResult+{-# NOINLINE multiStoreScanDirectRoot #-}+multiStoreScanDirectRoot input =+ let !inputValidationReads = validateInput input+ in runST do+ next <- U.thaw (inputNext input)+ weight <- U.thaw (inputWeight input)+ mark <- U.thaw (inputMark input)+ payloadBuffer <- V.thaw (inputPayload input)+ scoreBuffer <- U.thaw (inputScore input)+ linkBuffer <- U.thaw (inputLink input)++ payloadHeader <- newSTRef (multiStoreScanNodeCount, payloadBuffer)+ scoreHeader <- newSTRef (multiStoreScanNodeCount, scoreBuffer)+ linkHeader <- newSTRef (multiStoreScanNodeCount, linkBuffer)+ (_, payload) <- readSTRef payloadHeader+ (_, score) <- readSTRef scoreHeader+ (_, link) <- readSTRef linkHeader++ trace <-+ multiStoreScanTraceWorker+ multiStoreScanNodeCount+ 0+ 0+ emptyAccessTrace {traceHeaderReads = 3}+ next+ weight+ mark+ payload+ score+ link+ frozenMarks <- U.unsafeFreeze mark+ frozenScores <- U.unsafeFreeze score+ let !digest =+ digestVectors+ (traceReadDigest trace)+ frozenMarks+ frozenScores+ pure+ MultiStoreScanResult+ { resultSummary =+ MultiStoreScanSummary+ { visitedNodes = traceVisitedNodes trace+ , elementReads = traceElementReads trace+ , elementWrites = traceElementWrites trace+ , headerReads = traceHeaderReads trace+ , validationReads = inputValidationReads+ , finalDigest = digest+ }+ , resultVisitedIndices =+ U.fromListN+ multiStoreScanNodeCount+ (reverse (traceVisitedIndicesRev trace))+ , resultEvents =+ V.fromListN+ (traceElementReads trace + traceElementWrites trace)+ (reverse (traceEventsRev trace))+ , resultEventDigest = traceEventDigest trace+ , resultMarks = frozenMarks+ , resultScores = frozenScores+ }++emptyAccessTrace :: AccessTrace+emptyAccessTrace =+ AccessTrace+ { traceVisitedIndicesRev = []+ , traceEventsRev = []+ , traceEventDigest = 1_469_598_103_934_665_603+ , traceVisitedNodes = 0+ , traceElementReads = 0+ , traceElementWrites = 0+ , traceHeaderReads = 0+ , traceReadDigest = 0+ }++multiStoreScanTraceWorker ::+ Int ->+ Int ->+ Int ->+ AccessTrace ->+ UM.MVector s Int ->+ UM.MVector s Int ->+ UM.MVector s Int ->+ MV.MVector s (Int, Int) ->+ UM.MVector s Int ->+ UM.MVector s Int ->+ ST s AccessTrace+multiStoreScanTraceWorker !remaining !index !visits trace next weight mark payload score link+ | remaining <= 0 = pure trace+ | otherwise = do+ nextIndex <- UM.unsafeRead next index+ weightValue <- UM.unsafeRead weight index+ markValue <- UM.unsafeRead mark index+ (payloadTag, payloadDelta) <- MV.unsafeRead payload index+ scoreValue <- UM.unsafeRead score index+ linkValue <- UM.unsafeRead link index+ let !shouldWrite =+ (weightValue + scoreValue + payloadTag + visits) `rem` 5 == 0+ !nextTrace =+ recordTraceVisit+ index+ nextIndex+ weightValue+ markValue+ payloadTag+ payloadDelta+ scoreValue+ linkValue+ shouldWrite+ trace+ if shouldWrite+ then do+ UM.unsafeWrite mark index (markValue + 1)+ UM.unsafeWrite score index (scoreValue + payloadDelta + 1)+ else pure ()+ multiStoreScanTraceWorker+ (remaining - 1)+ ((nextIndex + linkValue) `rem` multiStoreScanNodeCount)+ (visits + 1)+ nextTrace+ next+ weight+ mark+ payload+ score+ link++recordTraceVisit ::+ Int ->+ Int ->+ Int ->+ Int ->+ Int ->+ Int ->+ Int ->+ Int ->+ Bool ->+ AccessTrace ->+ AccessTrace+recordTraceVisit index nextIndex weightValue markValue payloadTag payloadDelta scoreValue linkValue shouldWrite trace =+ let !readEvents =+ [ ReadIntEvent NextStore index nextIndex+ , ReadIntEvent WeightStore index weightValue+ , ReadIntEvent MarkStore index markValue+ , ReadPayloadEvent index payloadTag payloadDelta+ , ReadIntEvent ScoreStore index scoreValue+ , ReadIntEvent LinkStore index linkValue+ ]+ !writeEvents =+ if shouldWrite+ then+ [ WriteIntEvent MarkStore index (markValue + 1)+ , WriteIntEvent+ ScoreStore+ index+ (scoreValue + payloadDelta + 1)+ ]+ else []+ !events = readEvents <> writeEvents+ in AccessTrace+ { traceVisitedIndicesRev =+ index : traceVisitedIndicesRev trace+ , traceEventsRev =+ List.foldl'+ (flip (:))+ (traceEventsRev trace)+ events+ , traceEventDigest =+ List.foldl' hashTraceEvent (traceEventDigest trace) events+ , traceVisitedNodes = traceVisitedNodes trace + 1+ , traceElementReads = traceElementReads trace + 6+ , traceElementWrites =+ traceElementWrites trace + if shouldWrite then 2 else 0+ , traceHeaderReads = traceHeaderReads trace+ , traceReadDigest =+ traceReadDigest trace+ + fromIntegral+ ( nextIndex+ + weightValue+ + markValue+ + payloadTag+ + payloadDelta+ + scoreValue+ + linkValue+ )+ }++hashTraceEvent :: Int64 -> TraceEvent -> Int64+hashTraceEvent digest event =+ List.foldl' hashTraceWord digest case event of+ ReadIntEvent store index value ->+ [1, traceStoreCode store, index, value]+ ReadPayloadEvent index tag delta ->+ [2, index, tag, delta]+ WriteIntEvent store index value ->+ [3, traceStoreCode store, index, value]++hashTraceWord :: Int64 -> Int -> Int64+hashTraceWord digest value =+ digest * 1_099_511_628_211 + fromIntegral value++traceStoreCode :: TraceStore -> Int+traceStoreCode = \case+ NextStore -> 1+ WeightStore -> 2+ MarkStore -> 3+ ScoreStore -> 4+ LinkStore -> 5++multiStoreScanDirectBenchmarkRoot ::+ MultiStoreScanInput ->+ MultiStoreScanOutput+{-# NOINLINE multiStoreScanDirectBenchmarkRoot #-}+multiStoreScanDirectBenchmarkRoot input =+ validateInput input `seq` runST do+ next <- U.thaw (inputNext input)+ weight <- U.thaw (inputWeight input)+ mark <- U.thaw (inputMark input)+ payloadBuffer <- V.thaw (inputPayload input)+ scoreBuffer <- U.thaw (inputScore input)+ linkBuffer <- U.thaw (inputLink input)++ payloadHeader <- newSTRef (multiStoreScanNodeCount, payloadBuffer)+ scoreHeader <- newSTRef (multiStoreScanNodeCount, scoreBuffer)+ linkHeader <- newSTRef (multiStoreScanNodeCount, linkBuffer)+ (_, payload) <- readSTRef payloadHeader+ (_, score) <- readSTRef scoreHeader+ (_, link) <- readSTRef linkHeader++ readDigest <-+ multiStoreScanDirectWorker+ multiStoreScanNodeCount+ 0+ 0+ 0+ next+ weight+ mark+ payload+ score+ link+ frozenMarks <- U.unsafeFreeze mark+ frozenScores <- U.unsafeFreeze score+ pure+ MultiStoreScanOutput+ { outputDigest =+ digestVectors readDigest frozenMarks frozenScores+ , outputMarks = frozenMarks+ , outputScores = frozenScores+ }++multiStoreScanDirectHeaderMatchedBenchmarkRoot ::+ MultiStoreScanInput ->+ MultiStoreScanOutput+{-# NOINLINE multiStoreScanDirectHeaderMatchedBenchmarkRoot #-}+multiStoreScanDirectHeaderMatchedBenchmarkRoot input =+ validateInput input `seq` runST do+ next <- U.thaw (inputNext input)+ weight <- U.thaw (inputWeight input)+ mark <- U.thaw (inputMark input)+ payloadBuffer <- V.thaw (inputPayload input)+ scoreBuffer <- U.thaw (inputScore input)+ linkBuffer <- U.thaw (inputLink input)++ payloadHeader <- newSTRef (multiStoreScanNodeCount, payloadBuffer)+ scoreHeader <- newSTRef (multiStoreScanNodeCount, scoreBuffer)+ linkHeader <- newSTRef (multiStoreScanNodeCount, linkBuffer)+ payload <- readHeaderOpaque payloadHeader+ score <- readHeaderOpaque scoreHeader+ link <- readHeaderOpaque linkHeader++ readDigest <-+ multiStoreScanDirectWorker+ multiStoreScanNodeCount+ 0+ 0+ 0+ next+ weight+ mark+ payload+ score+ link+ frozenMarks <- U.unsafeFreeze mark+ frozenScores <- U.unsafeFreeze score+ pure+ MultiStoreScanOutput+ { outputDigest =+ digestVectors readDigest frozenMarks frozenScores+ , outputMarks = frozenMarks+ , outputScores = frozenScores+ }++readHeaderOpaque :: STRef s (Int, vector) -> ST s vector+{-# NOINLINE readHeaderOpaque #-}+-- Keep the comparator's three header reads observable. If this helper inlines,+-- GHC can cancel each locally allocated STRef against its read and turn the+-- control back into the deliberately retained lower-bound root.+readHeaderOpaque header = do+ (_, vector) <- readSTRef header+ pure vector++multiStoreScanDirectWorker ::+ Int ->+ Int ->+ Int ->+ Int64 ->+ UM.MVector s Int ->+ UM.MVector s Int ->+ UM.MVector s Int ->+ MV.MVector s (Int, Int) ->+ UM.MVector s Int ->+ UM.MVector s Int ->+ ST s Int64+{-# NOINLINE multiStoreScanDirectWorker #-}+multiStoreScanDirectWorker !remaining !index !visits !digest next weight mark payload score link+ | remaining <= 0 = pure digest+ | otherwise = do+ nextIndex <- UM.unsafeRead next index+ weightValue <- UM.unsafeRead weight index+ markValue <- UM.unsafeRead mark index+ (payloadTag, payloadDelta) <- MV.unsafeRead payload index+ scoreValue <- UM.unsafeRead score index+ linkValue <- UM.unsafeRead link index+ let !shouldWrite =+ (weightValue + scoreValue + payloadTag + visits) `rem` 5 == 0+ !nextDigest =+ digest+ + fromIntegral+ ( nextIndex+ + weightValue+ + markValue+ + payloadTag+ + payloadDelta+ + scoreValue+ + linkValue+ )+ if shouldWrite+ then do+ UM.unsafeWrite mark index (markValue + 1)+ UM.unsafeWrite score index (scoreValue + payloadDelta + 1)+ else pure ()+ multiStoreScanDirectWorker+ (remaining - 1)+ ((nextIndex + linkValue) `rem` multiStoreScanNodeCount)+ (visits + 1)+ nextDigest+ next+ weight+ mark+ payload+ score+ link++multiStoreScanPureBorrowOwningBenchmarkRoot ::+ MultiStoreScanInput ->+ MultiStoreScanOutput+{-# NOINLINE multiStoreScanPureBorrowOwningBenchmarkRoot #-}+multiStoreScanPureBorrowOwningBenchmarkRoot input =+ validateInput input `seq`+ unur+ ( linearly \linear -> DataFlow.do+ (allocationLinear, borrowLinear) <- dup linear+ store <- newOwningMultiStore input allocationLinear+ runBO borrowLinear Control.do+ (storeBorrow, lender) <- borrowM store+ (Ur digest, storeBorrow) <-+ reborrowing storeBorrow \local -> Control.do+ let %1 !(fixedRootBorrows, growableRootBorrows) =+ local+ .@ (owningFixedRootsField, owningGrowableRootsField)+ let %1 !(nextBorrow, weightBorrow, markBorrow) =+ fixedRootBorrows+ .@ (owningNextField, owningWeightField, owningMarkField)+ let %1 !(payloadBorrow, scoreBorrow, linkBorrow) =+ growableRootBorrows+ .@ (owningPayloadField, owningScoreField, owningLinkField)+ let %1 !payloadContent =+ OwningBoxedGrowable.getContents payloadBorrow+ let %1 !scoreContent =+ OwningUnboxedGrowable.getContents scoreBorrow+ let %1 !linkContent =+ OwningUnboxedGrowable.getContents linkBorrow+ ( Ur digest+ , nextBorrow+ , weightBorrow+ , markBorrow+ , payloadContent+ , scoreContent+ , linkContent+ ) <-+ multiStoreScanPureBorrowOwningWorker+ multiStoreScanNodeCount+ 0+ 0+ 0+ nextBorrow+ weightBorrow+ markBorrow+ payloadContent+ scoreContent+ linkContent+ let !(Ur _) = share nextBorrow+ let !(Ur _) = share weightBorrow+ let !(Ur _) = share markBorrow+ let !(Ur _) = share payloadContent+ let !(Ur _) = share scoreContent+ let !(Ur _) = share linkContent+ Control.pure (Ur digest)+ let !(Ur _) = share storeBorrow+ pureAfter+ (finishOwningMultiStoreOutput digest (reclaim lender))+ )++multiStoreScanPureBorrowFixedUnrestrictedBenchmarkRoot ::+ MultiStoreScanInput ->+ MultiStoreScanOutput+{-# NOINLINE multiStoreScanPureBorrowFixedUnrestrictedBenchmarkRoot #-}+multiStoreScanPureBorrowFixedUnrestrictedBenchmarkRoot input =+ validateInput input `seq`+ unur+ ( linearly \linear -> DataFlow.do+ (allocationLinear, borrowLinear) <- dup linear+ store <- newFixedUnrestrictedStore input allocationLinear+ runBO borrowLinear Control.do+ (storeBorrow, lender) <- borrowM store+ (Ur digest, storeBorrow) <-+ reborrowing storeBorrow \local -> Control.do+ let %1 !(fixedRootBorrows, growableRootBorrows) =+ local+ .@ ( fixedUnrestrictedRootsField+ , fixedUnrestrictedGrowableRootsField+ )+ let %1 !(nextBorrow, weightBorrow, markBorrow) =+ fixedRootBorrows .@ (nextField, weightField, markField)+ let %1 !(payloadBorrow, scoreBorrow, linkBorrow) =+ growableRootBorrows+ .@ (owningPayloadField, owningScoreField, owningLinkField)+ let %1 !payloadContent =+ OwningBoxedGrowable.getContents payloadBorrow+ let %1 !scoreContent =+ OwningUnboxedGrowable.getContents scoreBorrow+ let %1 !linkContent =+ OwningUnboxedGrowable.getContents linkBorrow+ ( Ur digest+ , nextBorrow+ , weightBorrow+ , markBorrow+ , payloadContent+ , scoreContent+ , linkContent+ ) <-+ multiStoreScanPureBorrowFixedUnrestrictedWorker+ multiStoreScanNodeCount+ 0+ 0+ 0+ nextBorrow+ weightBorrow+ markBorrow+ payloadContent+ scoreContent+ linkContent+ let !(Ur _) = share nextBorrow+ let !(Ur _) = share weightBorrow+ let !(Ur _) = share markBorrow+ let !(Ur _) = share payloadContent+ let !(Ur _) = share scoreContent+ let !(Ur _) = share linkContent+ Control.pure (Ur digest)+ let !(Ur _) = share storeBorrow+ pureAfter+ ( finishFixedUnrestrictedStoreOutput+ digest+ (reclaim lender)+ )+ )++multiStoreScanPureBorrowOwningWorker ::+ Int ->+ Int ->+ Int ->+ Int64 ->+ Mut α (OwningUnboxedFixed.Vector Int) %1 ->+ Mut α (OwningUnboxedFixed.Vector Int) %1 ->+ Mut α (OwningUnboxedFixed.Vector Int) %1 ->+ Mut α (OwningBoxedFixed.Vector (Int, Int)) %1 ->+ Mut α (OwningUnboxedFixed.Vector Int) %1 ->+ Mut α (OwningUnboxedFixed.Vector Int) %1 ->+ BO+ α+ ( Ur Int64+ , Mut α (OwningUnboxedFixed.Vector Int)+ , Mut α (OwningUnboxedFixed.Vector Int)+ , Mut α (OwningUnboxedFixed.Vector Int)+ , Mut α (OwningBoxedFixed.Vector (Int, Int))+ , Mut α (OwningUnboxedFixed.Vector Int)+ , Mut α (OwningUnboxedFixed.Vector Int)+ )+{-# NOINLINE multiStoreScanPureBorrowOwningWorker #-}+multiStoreScanPureBorrowOwningWorker !remaining !index !visits !digest nextBorrow weightBorrow markBorrow payloadContent scoreContent linkContent+ | remaining <= 0 =+ Control.pure+ ( Ur digest+ , nextBorrow+ , weightBorrow+ , markBorrow+ , payloadContent+ , scoreContent+ , linkContent+ )+ | otherwise = Control.do+ (Ur nextIndex, nextBorrow) <-+ OwningUnboxedFixed.copyAtMut index nextBorrow+ (Ur weightValue, weightBorrow) <-+ OwningUnboxedFixed.copyAtMut index weightBorrow+ (Ur markValue, markBorrow) <-+ OwningUnboxedFixed.copyAtMut index markBorrow+ (Ur (payloadTag, payloadDelta), payloadContent) <-+ OwningBoxedFixed.copyAtMut index payloadContent+ (Ur scoreValue, scoreContent) <-+ OwningUnboxedFixed.copyAtMut index scoreContent+ (Ur linkValue, linkContent) <-+ OwningUnboxedFixed.copyAtMut index linkContent+ let !shouldWrite =+ (weightValue + scoreValue + payloadTag + visits) `rem` 5 == 0+ !nextDigest =+ digest+ + fromIntegral+ ( nextIndex+ + weightValue+ + markValue+ + payloadTag+ + payloadDelta+ + scoreValue+ + linkValue+ )+ if shouldWrite+ then Control.do+ (oldMark, markBorrow) <-+ OwningUnboxedFixed.unsafeSet+ index+ (markValue + 1)+ markBorrow+ (oldScore, scoreContent) <-+ OwningUnboxedFixed.unsafeSet+ index+ (scoreValue + payloadDelta + 1)+ scoreContent+ multiStoreScanPureBorrowOwningWorker+ (remaining - 1)+ ((nextIndex + linkValue) `rem` multiStoreScanNodeCount)+ (visits + 1)+ nextDigest+ nextBorrow+ weightBorrow+ (consume oldMark `lseq` markBorrow)+ payloadContent+ (consume oldScore `lseq` scoreContent)+ linkContent+ else+ multiStoreScanPureBorrowOwningWorker+ (remaining - 1)+ ((nextIndex + linkValue) `rem` multiStoreScanNodeCount)+ (visits + 1)+ nextDigest+ nextBorrow+ weightBorrow+ markBorrow+ payloadContent+ scoreContent+ linkContent++multiStoreScanPureBorrowFixedUnrestrictedWorker ::+ Int ->+ Int ->+ Int ->+ Int64 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (OwningBoxedFixed.Vector (Int, Int)) %1 ->+ Mut α (OwningUnboxedFixed.Vector Int) %1 ->+ Mut α (OwningUnboxedFixed.Vector Int) %1 ->+ BO+ α+ ( Ur Int64+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (OwningBoxedFixed.Vector (Int, Int))+ , Mut α (OwningUnboxedFixed.Vector Int)+ , Mut α (OwningUnboxedFixed.Vector Int)+ )+{-# NOINLINE multiStoreScanPureBorrowFixedUnrestrictedWorker #-}+multiStoreScanPureBorrowFixedUnrestrictedWorker !remaining !index !visits !digest nextBorrow weightBorrow markBorrow payloadContent scoreContent linkContent+ | remaining <= 0 =+ Control.pure+ ( Ur digest+ , nextBorrow+ , weightBorrow+ , markBorrow+ , payloadContent+ , scoreContent+ , linkContent+ )+ | otherwise = Control.do+ (Ur nextIndex, nextBorrow) <-+ Fixed.unsafeGet index nextBorrow+ (Ur weightValue, weightBorrow) <-+ Fixed.unsafeGet index weightBorrow+ (Ur markValue, markBorrow) <-+ Fixed.unsafeGet index markBorrow+ (Ur (payloadTag, payloadDelta), payloadContent) <-+ OwningBoxedFixed.copyAtMut index payloadContent+ (Ur scoreValue, scoreContent) <-+ OwningUnboxedFixed.copyAtMut index scoreContent+ (Ur linkValue, linkContent) <-+ OwningUnboxedFixed.copyAtMut index linkContent+ let !shouldWrite =+ (weightValue + scoreValue + payloadTag + visits) `rem` 5 == 0+ !nextDigest =+ digest+ + fromIntegral+ ( nextIndex+ + weightValue+ + markValue+ + payloadTag+ + payloadDelta+ + scoreValue+ + linkValue+ )+ if shouldWrite+ then Control.do+ markBorrow <-+ Fixed.unsafeWrite index (markValue + 1) markBorrow+ (oldScore, scoreContent) <-+ OwningUnboxedFixed.unsafeSet+ index+ (scoreValue + payloadDelta + 1)+ scoreContent+ multiStoreScanPureBorrowFixedUnrestrictedWorker+ (remaining - 1)+ ((nextIndex + linkValue) `rem` multiStoreScanNodeCount)+ (visits + 1)+ nextDigest+ nextBorrow+ weightBorrow+ markBorrow+ payloadContent+ (consume oldScore `lseq` scoreContent)+ linkContent+ else+ multiStoreScanPureBorrowFixedUnrestrictedWorker+ (remaining - 1)+ ((nextIndex + linkValue) `rem` multiStoreScanNodeCount)+ (visits + 1)+ nextDigest+ nextBorrow+ weightBorrow+ markBorrow+ payloadContent+ scoreContent+ linkContent++multiStoreScanPureBorrowDirectBenchmarkRoot ::+ MultiStoreScanInput ->+ MultiStoreScanOutput+{-# NOINLINE multiStoreScanPureBorrowDirectBenchmarkRoot #-}+multiStoreScanPureBorrowDirectBenchmarkRoot input =+ validateInput input `seq`+ unur+ ( linearly \linear -> DataFlow.do+ (allocationLinear, borrowLinear) <- dup linear+ store <- newMultiStore input allocationLinear+ runBO borrowLinear Control.do+ (storeBorrow, lender) <- borrowM store+ (Ur digest, storeBorrow) <-+ reborrowing storeBorrow \local -> Control.do+ let %1 !(fixedRootBorrows, growableRootBorrows) =+ local .@ (fixedRootsField, growableRootsField)+ let %1 !(nextBorrow, weightBorrow, markBorrow) =+ fixedRootBorrows .@ (nextField, weightField, markField)+ let %1 !(payloadBorrow, scoreBorrow, linkBorrow) =+ growableRootBorrows .@ (payloadField, scoreField, linkField)+ let %1 !payloadContent = Growable.getContents payloadBorrow+ let %1 !scoreContent = Growable.getContents scoreBorrow+ let %1 !linkContent = Growable.getContents linkBorrow+ ( Ur digest+ , nextBorrow+ , weightBorrow+ , markBorrow+ , payloadContent+ , scoreContent+ , linkContent+ ) <-+ multiStoreScanPureBorrowWorker+ multiStoreScanNodeCount+ 0+ 0+ 0+ nextBorrow+ weightBorrow+ markBorrow+ payloadContent+ scoreContent+ linkContent+ let !() =+ consumeViews+ nextBorrow+ weightBorrow+ markBorrow+ payloadContent+ scoreContent+ linkContent+ Control.pure (Ur digest)+ let !(Ur _) = share storeBorrow+ pureAfter (finishMultiStoreOutput digest (reclaim lender))+ )++multiStoreScanPureBorrowNestedBenchmarkRoot ::+ MultiStoreScanInput ->+ MultiStoreScanOutput+{-# NOINLINE multiStoreScanPureBorrowNestedBenchmarkRoot #-}+multiStoreScanPureBorrowNestedBenchmarkRoot input =+ validateInput input `seq`+ unur+ ( linearly \linear -> DataFlow.do+ (allocationLinear, borrowLinear) <- dup linear+ store <- newMultiStore input allocationLinear+ runBO borrowLinear Control.do+ (storeBorrow, lender) <- borrowM store+ (Ur digest, storeBorrow) <-+ reborrowing storeBorrow \local -> Control.do+ let %1 !(fixedRootBorrows, growableRootBorrows) =+ local .@ (fixedRootsField, growableRootsField)+ let %1 !(nextBorrow, weightBorrow, markBorrow) =+ fixedRootBorrows .@ (nextField, weightField, markField)+ let %1 !(payloadBorrow, scoreBorrow, linkBorrow) =+ growableRootBorrows .@ (payloadField, scoreField, linkField)+ (Ur digest, fields) <-+ reborrowings+ ( nextBorrow+ :- weightBorrow+ :- markBorrow+ :- payloadBorrow+ :- scoreBorrow+ :- linkBorrow+ :- BNil+ )+ \case+ nextBorrow+ :- weightBorrow+ :- markBorrow+ :- payloadBorrow+ :- scoreBorrow+ :- linkBorrow+ :- BNil -> Control.do+ let %1 !payloadContent =+ Growable.getContents payloadBorrow+ let %1 !scoreContent =+ Growable.getContents scoreBorrow+ let %1 !linkContent =+ Growable.getContents linkBorrow+ ( Ur digest+ , nextBorrow+ , weightBorrow+ , markBorrow+ , payloadContent+ , scoreContent+ , linkContent+ ) <-+ multiStoreScanPureBorrowWorker+ multiStoreScanNodeCount+ 0+ 0+ 0+ nextBorrow+ weightBorrow+ markBorrow+ payloadContent+ scoreContent+ linkContent+ let !() =+ consumeViews+ nextBorrow+ weightBorrow+ markBorrow+ payloadContent+ scoreContent+ linkContent+ Control.pure (Ur digest)+ let !() = consume fields+ Control.pure (Ur digest)+ let !(Ur _) = share storeBorrow+ pureAfter (finishMultiStoreOutput digest (reclaim lender))+ )++multiStoreScanPureBorrowDirectRoot ::+ MultiStoreScanInput ->+ MultiStoreScanResult+{-# NOINLINE multiStoreScanPureBorrowDirectRoot #-}+multiStoreScanPureBorrowDirectRoot input =+ let !inputValidationReads = validateInput input+ in unur+ ( linearly \linear -> DataFlow.do+ (allocationLinear, borrowLinear) <- dup linear+ store <- newMultiStore input allocationLinear+ runBO borrowLinear Control.do+ (storeBorrow, lender) <- borrowM store+ (Ur trace, storeBorrow) <-+ reborrowing storeBorrow \local -> Control.do+ let %1 !(fixedRootBorrows, growableRootBorrows) =+ local .@ (fixedRootsField, growableRootsField)+ let %1 !(nextBorrow, weightBorrow, markBorrow) =+ fixedRootBorrows .@ (nextField, weightField, markField)+ let %1 !(payloadBorrow, scoreBorrow, linkBorrow) =+ growableRootBorrows .@ (payloadField, scoreField, linkField)+ let %1 !payloadContent = Growable.getContents payloadBorrow+ let %1 !scoreContent = Growable.getContents scoreBorrow+ let %1 !linkContent = Growable.getContents linkBorrow+ ( Ur trace+ , nextBorrow+ , weightBorrow+ , markBorrow+ , payloadContent+ , scoreContent+ , linkContent+ ) <-+ multiStoreScanPureBorrowTraceWorker+ multiStoreScanNodeCount+ 0+ 0+ emptyAccessTrace {traceHeaderReads = 3}+ nextBorrow+ weightBorrow+ markBorrow+ payloadContent+ scoreContent+ linkContent+ let !() =+ consumeViews+ nextBorrow+ weightBorrow+ markBorrow+ payloadContent+ scoreContent+ linkContent+ Control.pure (Ur trace)+ let !(Ur _) = share storeBorrow+ pureAfter+ ( finishMultiStoreResult+ inputValidationReads+ trace+ (reclaim lender)+ )+ )++multiStoreScanPureBorrowNestedRoot ::+ MultiStoreScanInput ->+ MultiStoreScanResult+{-# NOINLINE multiStoreScanPureBorrowNestedRoot #-}+multiStoreScanPureBorrowNestedRoot input =+ let !inputValidationReads = validateInput input+ in unur+ ( linearly \linear -> DataFlow.do+ (allocationLinear, borrowLinear) <- dup linear+ store <- newMultiStore input allocationLinear+ runBO borrowLinear Control.do+ (storeBorrow, lender) <- borrowM store+ (Ur trace, storeBorrow) <-+ reborrowing storeBorrow \local -> Control.do+ let %1 !(fixedRootBorrows, growableRootBorrows) =+ local .@ (fixedRootsField, growableRootsField)+ let %1 !(nextBorrow, weightBorrow, markBorrow) =+ fixedRootBorrows .@ (nextField, weightField, markField)+ let %1 !(payloadBorrow, scoreBorrow, linkBorrow) =+ growableRootBorrows .@ (payloadField, scoreField, linkField)+ (Ur trace, fields) <-+ reborrowings+ ( nextBorrow+ :- weightBorrow+ :- markBorrow+ :- payloadBorrow+ :- scoreBorrow+ :- linkBorrow+ :- BNil+ )+ \case+ nextBorrow+ :- weightBorrow+ :- markBorrow+ :- payloadBorrow+ :- scoreBorrow+ :- linkBorrow+ :- BNil -> Control.do+ let %1 !payloadContent =+ Growable.getContents payloadBorrow+ let %1 !scoreContent =+ Growable.getContents scoreBorrow+ let %1 !linkContent =+ Growable.getContents linkBorrow+ ( Ur trace+ , nextBorrow+ , weightBorrow+ , markBorrow+ , payloadContent+ , scoreContent+ , linkContent+ ) <-+ multiStoreScanPureBorrowTraceWorker+ multiStoreScanNodeCount+ 0+ 0+ emptyAccessTrace {traceHeaderReads = 3}+ nextBorrow+ weightBorrow+ markBorrow+ payloadContent+ scoreContent+ linkContent+ let !() =+ consumeViews+ nextBorrow+ weightBorrow+ markBorrow+ payloadContent+ scoreContent+ linkContent+ Control.pure (Ur trace)+ let !() = consume fields+ Control.pure (Ur trace)+ let !(Ur _) = share storeBorrow+ pureAfter+ ( finishMultiStoreResult+ inputValidationReads+ trace+ (reclaim lender)+ )+ )++multiStoreScanPureBorrowWorker ::+ Int ->+ Int ->+ Int ->+ Int64 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector V.Vector (Int, Int)) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ BO+ α+ ( Ur Int64+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector V.Vector (Int, Int))+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ )+{-# INLINEABLE multiStoreScanPureBorrowWorker #-}+multiStoreScanPureBorrowWorker !remaining !index !visits !digest nextBorrow weightBorrow markBorrow payloadContent scoreContent linkContent =+ -- An ordinary strict Int64 accumulator is still rebuilt as I64# at every+ -- recursive call because the BO result also returns six linear borrows.+ -- Entering an explicitly unboxed local loop keeps the accumulator in+ -- Int64# until the single ownership boundary below.+ case digest of+ I64# digest# ->+ go+ remaining+ index+ visits+ digest#+ nextBorrow+ weightBorrow+ markBorrow+ payloadContent+ scoreContent+ linkContent+ where+ go ::+ Int ->+ Int ->+ Int ->+ GHC.Int64# ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector V.Vector (Int, Int)) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ BO+ α+ ( Ur Int64+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector V.Vector (Int, Int))+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ )+ go !remaining !index !visits digest# nextBorrow weightBorrow markBorrow payloadContent scoreContent linkContent+ | remaining <= 0 =+ Control.pure+ ( Ur (I64# digest#)+ , nextBorrow+ , weightBorrow+ , markBorrow+ , payloadContent+ , scoreContent+ , linkContent+ )+ | otherwise = Control.do+ (Ur nextIndex, nextBorrow) <-+ Fixed.unsafeGet index nextBorrow+ (Ur weightValue, weightBorrow) <-+ Fixed.unsafeGet index weightBorrow+ (Ur markValue, markBorrow) <-+ Fixed.unsafeGet index markBorrow+ (Ur (payloadTag, payloadDelta), payloadContent) <-+ Fixed.unsafeGet index payloadContent+ (Ur scoreValue, scoreContent) <-+ Fixed.unsafeGet index scoreContent+ (Ur linkValue, linkContent) <-+ Fixed.unsafeGet index linkContent+ let !shouldWrite =+ (weightValue + scoreValue + payloadTag + visits) `rem` 5 == 0+ !nextDigest =+ I64# digest#+ + fromIntegral+ ( nextIndex+ + weightValue+ + markValue+ + payloadTag+ + payloadDelta+ + scoreValue+ + linkValue+ )+ case nextDigest of+ I64# nextDigest# ->+ if shouldWrite+ then Control.do+ markBorrow <-+ Fixed.unsafeWrite index (markValue + 1) markBorrow+ scoreContent <-+ Fixed.unsafeWrite+ index+ (scoreValue + payloadDelta + 1)+ scoreContent+ go+ (remaining - 1)+ ((nextIndex + linkValue) `rem` multiStoreScanNodeCount)+ (visits + 1)+ nextDigest#+ nextBorrow+ weightBorrow+ markBorrow+ payloadContent+ scoreContent+ linkContent+ else+ go+ (remaining - 1)+ ((nextIndex + linkValue) `rem` multiStoreScanNodeCount)+ (visits + 1)+ nextDigest#+ nextBorrow+ weightBorrow+ markBorrow+ payloadContent+ scoreContent+ linkContent++multiStoreScanPureBorrowTraceWorker ::+ Int ->+ Int ->+ Int ->+ AccessTrace ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector V.Vector (Int, Int)) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ BO+ α+ ( Ur AccessTrace+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector V.Vector (Int, Int))+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ )+{-# NOINLINE multiStoreScanPureBorrowTraceWorker #-}+multiStoreScanPureBorrowTraceWorker !remaining !index !visits !trace nextBorrow weightBorrow markBorrow payloadContent scoreContent linkContent+ | remaining <= 0 =+ Control.pure+ ( Ur trace+ , nextBorrow+ , weightBorrow+ , markBorrow+ , payloadContent+ , scoreContent+ , linkContent+ )+ | otherwise = Control.do+ (Ur nextIndex, nextBorrow) <-+ Fixed.unsafeGet index nextBorrow+ (Ur weightValue, weightBorrow) <-+ Fixed.unsafeGet index weightBorrow+ (Ur markValue, markBorrow) <-+ Fixed.unsafeGet index markBorrow+ (Ur (payloadTag, payloadDelta), payloadContent) <-+ Fixed.unsafeGet index payloadContent+ (Ur scoreValue, scoreContent) <-+ Fixed.unsafeGet index scoreContent+ (Ur linkValue, linkContent) <-+ Fixed.unsafeGet index linkContent+ let !shouldWrite =+ (weightValue + scoreValue + payloadTag + visits) `rem` 5 == 0+ !nextTrace =+ recordTraceVisit+ index+ nextIndex+ weightValue+ markValue+ payloadTag+ payloadDelta+ scoreValue+ linkValue+ shouldWrite+ trace+ if shouldWrite+ then Control.do+ markBorrow <-+ Fixed.unsafeWrite index (markValue + 1) markBorrow+ scoreContent <-+ Fixed.unsafeWrite+ index+ (scoreValue + payloadDelta + 1)+ scoreContent+ multiStoreScanPureBorrowTraceWorker+ (remaining - 1)+ ((nextIndex + linkValue) `rem` multiStoreScanNodeCount)+ (visits + 1)+ nextTrace+ nextBorrow+ weightBorrow+ markBorrow+ payloadContent+ scoreContent+ linkContent+ else+ multiStoreScanPureBorrowTraceWorker+ (remaining - 1)+ ((nextIndex + linkValue) `rem` multiStoreScanNodeCount)+ (visits + 1)+ nextTrace+ nextBorrow+ weightBorrow+ markBorrow+ payloadContent+ scoreContent+ linkContent++consumeViews ::+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector V.Vector (Int, Int)) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ ()+consumeViews nextBorrow weightBorrow markBorrow payloadContent scoreContent linkContent =+ let !(Ur _) = share nextBorrow+ !(Ur _) = share weightBorrow+ !(Ur _) = share markBorrow+ !(Ur _) = share payloadContent+ !(Ur _) = share scoreContent+ !(Ur _) = share linkContent+ in ()++newOwningMultiStore ::+ MultiStoreScanInput ->+ Linearly %1 ->+ OwningMultiStore+{-# NOINLINE newOwningMultiStore #-}+newOwningMultiStore =+ GHC.noinline \input linear ->+ dup linear & \(nextLinear, rest1) ->+ dup rest1 & \(weightLinear, rest2) ->+ dup rest2 & \(markLinear, rest3) ->+ dup rest3 & \(payloadLinear, rest4) ->+ dup rest4 & \(scoreLinear, linkLinear) ->+ OwningMultiStore+ { owningFixedRoots =+ OwningFixedRoots+ { owningNext =+ OwningUnboxedFixed.fromVector+ (inputNext input)+ nextLinear+ , owningWeight =+ OwningUnboxedFixed.fromVector+ (inputWeight input)+ weightLinear+ , owningMark =+ OwningUnboxedFixed.fromVector+ (inputMark input)+ markLinear+ }+ , owningGrowableRoots =+ OwningGrowableRoots+ { owningPayload =+ OwningBoxedGrowable.fromVector+ (inputPayload input)+ payloadLinear+ , owningScore =+ OwningUnboxedGrowable.fromVector+ (inputScore input)+ scoreLinear+ , owningLink =+ OwningUnboxedGrowable.fromVector+ (inputLink input)+ linkLinear+ }+ }++newFixedUnrestrictedStore ::+ MultiStoreScanInput ->+ Linearly %1 ->+ FixedUnrestrictedStore+{-# NOINLINE newFixedUnrestrictedStore #-}+newFixedUnrestrictedStore =+ GHC.noinline \input linear ->+ dup linear & \(nextLinear, rest1) ->+ dup rest1 & \(weightLinear, rest2) ->+ dup rest2 & \(markLinear, rest3) ->+ dup rest3 & \(payloadLinear, rest4) ->+ dup rest4 & \(scoreLinear, linkLinear) ->+ FixedUnrestrictedStore+ { fixedUnrestrictedRoots =+ FixedRoots+ { next =+ Fixed.fromVector+ (inputNext input)+ nextLinear+ , weight =+ Fixed.fromVector+ (inputWeight input)+ weightLinear+ , mark =+ Fixed.fromVector+ (inputMark input)+ markLinear+ }+ , fixedUnrestrictedGrowableRoots =+ OwningGrowableRoots+ { owningPayload =+ OwningBoxedGrowable.fromVector+ (inputPayload input)+ payloadLinear+ , owningScore =+ OwningUnboxedGrowable.fromVector+ (inputScore input)+ scoreLinear+ , owningLink =+ OwningUnboxedGrowable.fromVector+ (inputLink input)+ linkLinear+ }+ }++newMultiStore :: MultiStoreScanInput -> Linearly %1 -> MultiStore+{-# NOINLINE newMultiStore #-}+newMultiStore =+ GHC.noinline \input linear ->+ dup linear & \(nextLinear, rest1) ->+ dup rest1 & \(weightLinear, rest2) ->+ dup rest2 & \(markLinear, rest3) ->+ dup rest3 & \(payloadLinear, rest4) ->+ dup rest4 & \(scoreLinear, linkLinear) ->+ MultiStore+ { fixedRoots =+ FixedRoots+ { next =+ Fixed.fromVector+ (inputNext input)+ nextLinear+ , weight =+ Fixed.fromVector+ (inputWeight input)+ weightLinear+ , mark =+ Fixed.fromVector+ (inputMark input)+ markLinear+ }+ , growableRoots =+ GrowableRoots+ { payload =+ Growable.fromVector+ (inputPayload input)+ payloadLinear+ , score =+ Growable.fromVector+ (inputScore input)+ scoreLinear+ , link =+ Growable.fromVector+ (inputLink input)+ linkLinear+ }+ }++finishOwningMultiStoreOutput ::+ Int64 ->+ OwningMultiStore %1 ->+ Ur MultiStoreScanOutput+{-# NOINLINE finishOwningMultiStoreOutput #-}+finishOwningMultiStoreOutput+ digest+ ( OwningMultiStore+ (OwningFixedRoots nextOwner weightOwner markOwner)+ (OwningGrowableRoots payloadOwner scoreOwner linkOwner)+ ) =+ case OwningUnboxedFixed.toVector nextOwner of+ Ur nextVector ->+ case OwningUnboxedFixed.toVector weightOwner of+ Ur weightVector ->+ case OwningUnboxedFixed.toVector markOwner of+ Ur markVector ->+ case OwningBoxedGrowable.toVector payloadOwner of+ Ur payloadVector ->+ case OwningUnboxedGrowable.toVector scoreOwner of+ Ur scoreVector ->+ case OwningUnboxedGrowable.toVector linkOwner of+ Ur linkVector ->+ U.length nextVector `lseq`+ U.length weightVector `lseq`+ V.length payloadVector `lseq`+ U.length linkVector `lseq`+ Ur+ MultiStoreScanOutput+ { outputDigest =+ digestVectors+ digest+ markVector+ scoreVector+ , outputMarks = markVector+ , outputScores = scoreVector+ }++finishFixedUnrestrictedStoreOutput ::+ Int64 ->+ FixedUnrestrictedStore %1 ->+ Ur MultiStoreScanOutput+{-# NOINLINE finishFixedUnrestrictedStoreOutput #-}+finishFixedUnrestrictedStoreOutput+ digest+ ( FixedUnrestrictedStore+ (FixedRoots nextOwner weightOwner markOwner)+ (OwningGrowableRoots payloadOwner scoreOwner linkOwner)+ ) =+ case Fixed.toVector nextOwner of+ Ur nextVector ->+ case Fixed.toVector weightOwner of+ Ur weightVector ->+ case Fixed.toVector markOwner of+ Ur markVector ->+ case OwningBoxedGrowable.toVector payloadOwner of+ Ur payloadVector ->+ case OwningUnboxedGrowable.toVector scoreOwner of+ Ur scoreVector ->+ case OwningUnboxedGrowable.toVector linkOwner of+ Ur linkVector ->+ U.length nextVector `lseq`+ U.length weightVector `lseq`+ V.length payloadVector `lseq`+ U.length linkVector `lseq`+ Ur+ MultiStoreScanOutput+ { outputDigest =+ digestVectors+ digest+ markVector+ scoreVector+ , outputMarks = markVector+ , outputScores = scoreVector+ }++finishMultiStoreOutput ::+ Int64 ->+ MultiStore %1 ->+ Ur MultiStoreScanOutput+{-# NOINLINE finishMultiStoreOutput #-}+finishMultiStoreOutput+ digest+ ( MultiStore+ (FixedRoots nextOwner weightOwner markOwner)+ (GrowableRoots payloadOwner scoreOwner linkOwner)+ ) =+ case Fixed.toVector nextOwner of+ Ur nextVector ->+ case Fixed.toVector weightOwner of+ Ur weightVector ->+ case Fixed.toVector markOwner of+ Ur markVector ->+ case Growable.toVector payloadOwner of+ Ur payloadVector ->+ case Growable.toVector scoreOwner of+ Ur scoreVector ->+ case Growable.toVector linkOwner of+ Ur linkVector ->+ U.length nextVector `lseq`+ U.length weightVector `lseq`+ V.length payloadVector `lseq`+ U.length linkVector `lseq`+ Ur+ MultiStoreScanOutput+ { outputDigest =+ digestVectors+ digest+ markVector+ scoreVector+ , outputMarks = markVector+ , outputScores = scoreVector+ }++finishMultiStoreResult ::+ Int ->+ AccessTrace ->+ MultiStore %1 ->+ Ur MultiStoreScanResult+finishMultiStoreResult inputValidationReads trace store =+ case finishMultiStoreOutput (traceReadDigest trace) store of+ Ur output ->+ Ur+ MultiStoreScanResult+ { resultSummary =+ MultiStoreScanSummary+ { visitedNodes = traceVisitedNodes trace+ , elementReads = traceElementReads trace+ , elementWrites = traceElementWrites trace+ , headerReads = traceHeaderReads trace+ , validationReads = inputValidationReads+ , finalDigest = outputDigest output+ }+ , resultVisitedIndices =+ U.fromListN+ multiStoreScanNodeCount+ (reverse (traceVisitedIndicesRev trace))+ , resultEvents =+ V.fromListN+ (traceElementReads trace + traceElementWrites trace)+ (reverse (traceEventsRev trace))+ , resultEventDigest = traceEventDigest trace+ , resultMarks = outputMarks output+ , resultScores = outputScores output+ }++owningFixedRootsField ::+ RecordLabel+ OwningMultiStore+ "owningFixedRoots"+ OwningFixedRoots+owningFixedRootsField = #owningFixedRoots++owningGrowableRootsField ::+ RecordLabel+ OwningMultiStore+ "owningGrowableRoots"+ OwningGrowableRoots+owningGrowableRootsField = #owningGrowableRoots++owningNextField ::+ RecordLabel+ OwningFixedRoots+ "owningNext"+ (OwningUnboxedFixed.Vector Int)+owningNextField = #owningNext++owningWeightField ::+ RecordLabel+ OwningFixedRoots+ "owningWeight"+ (OwningUnboxedFixed.Vector Int)+owningWeightField = #owningWeight++owningMarkField ::+ RecordLabel+ OwningFixedRoots+ "owningMark"+ (OwningUnboxedFixed.Vector Int)+owningMarkField = #owningMark++owningPayloadField ::+ RecordLabel+ OwningGrowableRoots+ "owningPayload"+ (OwningBoxedGrowable.GrowableVector (Int, Int))+owningPayloadField = #owningPayload++owningScoreField ::+ RecordLabel+ OwningGrowableRoots+ "owningScore"+ (OwningUnboxedGrowable.GrowableVector Int)+owningScoreField = #owningScore++owningLinkField ::+ RecordLabel+ OwningGrowableRoots+ "owningLink"+ (OwningUnboxedGrowable.GrowableVector Int)+owningLinkField = #owningLink++fixedUnrestrictedRootsField ::+ RecordLabel+ FixedUnrestrictedStore+ "fixedUnrestrictedRoots"+ FixedRoots+fixedUnrestrictedRootsField = #fixedUnrestrictedRoots++fixedUnrestrictedGrowableRootsField ::+ RecordLabel+ FixedUnrestrictedStore+ "fixedUnrestrictedGrowableRoots"+ OwningGrowableRoots+fixedUnrestrictedGrowableRootsField = #fixedUnrestrictedGrowableRoots++fixedRootsField ::+ RecordLabel MultiStore "fixedRoots" FixedRoots+fixedRootsField = #fixedRoots++growableRootsField ::+ RecordLabel MultiStore "growableRoots" GrowableRoots+growableRootsField = #growableRoots++nextField ::+ RecordLabel FixedRoots "next" (Fixed.Vector U.Vector Int)+nextField = #next++weightField ::+ RecordLabel FixedRoots "weight" (Fixed.Vector U.Vector Int)+weightField = #weight++markField ::+ RecordLabel FixedRoots "mark" (Fixed.Vector U.Vector Int)+markField = #mark++payloadField ::+ RecordLabel+ GrowableRoots+ "payload"+ (Growable.GrowableVector V.Vector (Int, Int))+payloadField = #payload++scoreField ::+ RecordLabel+ GrowableRoots+ "score"+ (Growable.GrowableVector U.Vector Int)+scoreField = #score++linkField ::+ RecordLabel+ GrowableRoots+ "link"+ (Growable.GrowableVector U.Vector Int)+linkField = #link++digestVectors :: Int64 -> U.Vector Int -> U.Vector Int -> Int64+digestVectors initial marks scores =+ let !marksDigest =+ U.ifoldl'+ (\digest index value -> mixDigest digest (index * 17 + value))+ initial+ marks+ in U.ifoldl'+ (\digest index value -> mixDigest digest (index * 31 + value))+ marksDigest+ scores++mixDigest :: Int64 -> Int -> Int64+mixDigest digest value =+ digest * 1_099_511_628_211 + fromIntegral value++benches :: [Benchmark]+benches =+ [ env (pure multiStoreScanDirectInput) \input ->+ bgroup+ "multi-store-scan"+ [ bench "direct" $ nf multiStoreScanDirectBenchmarkRoot input+ , bench "direct/header-matched" $+ nf multiStoreScanDirectHeaderMatchedBenchmarkRoot input+ , bench "pure-borrow/all-owning" $+ nf multiStoreScanPureBorrowOwningBenchmarkRoot input+ , bench "pure-borrow/fixed-unrestricted" $+ nf+ multiStoreScanPureBorrowFixedUnrestrictedBenchmarkRoot+ input+ , bench "pure-borrow/all-unrestricted/direct-shape" $+ nf multiStoreScanPureBorrowDirectBenchmarkRoot input+ , bench "pure-borrow/all-unrestricted/nested-shape" $+ nf multiStoreScanPureBorrowNestedBenchmarkRoot input+ ]+ ]++defaultMain :: IO ()+defaultMain = Bench.defaultMain benches
+ internal-src/test-bench-common/PureBorrow/Internal/Bench/Unboxed.hs view
@@ -0,0 +1,421 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}++module PureBorrow.Internal.Bench.Unboxed (+ defaultMain,+ benches,+ directFixedUnboxedKernel,+ directFixedUnboxedMaterialization,+ directGrowableUnboxedGrowthKernel,+ directGrowableUnboxedMaterialization,+ directGrowableUnboxedNoGrowthKernel,+ owningBoxedQsortKernel,+ pureBorrowFixedUnboxedKernel,+ pureBorrowFixedUnboxedMaterialization,+ pureBorrowGrowableUnboxedGrowthKernel,+ pureBorrowGrowableUnboxedMaterialization,+ pureBorrowGrowableUnboxedNoGrowthKernel,+ unrestrictedBoxedFftKernel,+ unrestrictedBoxedQsortKernel,+ unrestrictedUnboxedFftKernel,+ unrestrictedUnboxedQsortKernel,+) where++import Control.Concurrent.DivideConquer.Linear qualified as DC+import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure+import Control.Monad.ST.Strict (ST, runST)+import Control.Syntax.DataFlow qualified as DataFlow+import Data.Complex (Complex (..))+import Data.Ref.Linear qualified as Ref+import Data.STRef (STRef, newSTRef, readSTRef, writeSTRef)+import Data.Vector qualified as B+import Data.Vector.Generic qualified as G+import Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted qualified as UnrestrictedVector+import Data.Vector.Mutable.Linear.Borrow qualified as OwningVector+import Data.Vector.Unboxed qualified as U+import Data.Vector.Unboxed.Mutable qualified as UM+import Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow qualified as Growable+import Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow.Internal qualified as GrowableInternal+import Data.Vector.Unboxed.Mutable.Linear.Borrow qualified as Vector+import Data.Vector.Unboxed.Mutable.Linear.Borrow.Internal qualified as VectorInternal+import GHC.IO (unsafePerformIO)+import Prelude.Linear+import Test.Tasty.Bench hiding (defaultMain)+import Test.Tasty.Bench qualified as Bench+import Unsafe.Linear qualified as Unsafe+import Prelude qualified as NonLinear++directFixedUnboxedKernel :: U.Vector Int -> U.Vector Int+{-# NOINLINE directFixedUnboxedKernel #-}+directFixedUnboxedKernel input =+ U.modify (\vector -> directWorker (UM.length vector) 0 vector) input++directWorker :: Int -> Int -> UM.MVector s Int -> ST s ()+{-# NOINLINE directWorker #-}+directWorker !length_ !index vector+ | index >= length_ = NonLinear.pure ()+ | otherwise = do+ value <- UM.unsafeRead vector index+ UM.unsafeWrite vector index (value + 1)+ directWorker length_ (index + 1) vector++{- | Benchmark-only monomorphic finalization for the fixed-vector kernels.++For 'Int', 'move' is observationally the identity. Bypassing the public+materialization loop isolates the update worker; this is not an API-performance+root.+-}+unsafeBenchmarkFreezeFixedInt :: Vector.Vector Int %1 -> Ur (U.Vector Int)+{-# NOINLINE unsafeBenchmarkFreezeFixedInt #-}+unsafeBenchmarkFreezeFixedInt =+ Unsafe.toLinear \(VectorInternal.Vector vector) ->+ Ur (unsafePerformIO (U.unsafeFreeze vector))++directFixedUnboxedMaterialization :: U.Vector Int -> U.Vector Int+{-# NOINLINE directFixedUnboxedMaterialization #-}+directFixedUnboxedMaterialization input =+ U.modify (\_ -> NonLinear.pure ()) input++pureBorrowFixedUnboxedMaterialization :: U.Vector Int -> U.Vector Int+{-# NOINLINE pureBorrowFixedUnboxedMaterialization #-}+pureBorrowFixedUnboxedMaterialization input =+ unur $+ linearly \linear ->+ Vector.toVector (Vector.fromVector input linear)++pureBorrowFixedUnboxedKernel :: U.Vector Int -> U.Vector Int+{-# NOINLINE pureBorrowFixedUnboxedKernel #-}+pureBorrowFixedUnboxedKernel input =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Vector.fromVector input ownerLinear)+ pureBorrowWorker (U.length input) 0 vector+ pureAfter (unsafeBenchmarkFreezeFixedInt (reclaim lend))++directGrowableUnboxedNoGrowthKernel :: U.Vector Int -> U.Vector Int+{-# NOINLINE directGrowableUnboxedNoGrowthKernel #-}+directGrowableUnboxedNoGrowthKernel input =+ U.modify+ ( \vector -> do+ -- The header ref mirrors the growable owner's indirection, so the+ -- baseline pays for the same loads the measured variant does.+ header <- newSTRef (U.length input, vector)+ (_, content) <- readSTRef header+ directWorker (UM.length content) 0 content+ -- Match the growable owner's final header access.+ (_, _) <- readSTRef header+ NonLinear.pure ()+ )+ input++{- | Benchmark-only monomorphic finalization for the growable-vector kernels.++The reclaimed owner is consumed exactly once and only its initialized prefix is+frozen. As with 'unsafeBenchmarkFreezeFixedInt', this isolates the kernel and+must not be interpreted as public materialization cost.+-}+unsafeBenchmarkFreezeGrowableInt ::+ Growable.GrowableVector Int %1 ->+ Ur (U.Vector Int)+{-# NOINLINE unsafeBenchmarkFreezeGrowableInt #-}+unsafeBenchmarkFreezeGrowableInt =+ Unsafe.toLinear \(GrowableInternal.GrowableVector ref) ->+ case Ref.free ref of+ GrowableInternal.Header logicalSize vector ->+ Ur+ ( unsafePerformIO+ (U.unsafeFreeze (UM.unsafeTake logicalSize vector))+ )++directGrowableUnboxedMaterialization :: U.Vector Int -> U.Vector Int+{-# NOINLINE directGrowableUnboxedMaterialization #-}+directGrowableUnboxedMaterialization input =+ U.modify+ ( \vector -> do+ header <- newSTRef (U.length input, vector)+ (_, _) <- readSTRef header+ NonLinear.pure ()+ )+ input++pureBorrowGrowableUnboxedMaterialization :: U.Vector Int -> U.Vector Int+{-# NOINLINE pureBorrowGrowableUnboxedMaterialization #-}+pureBorrowGrowableUnboxedMaterialization input =+ unur $+ linearly \linear ->+ Growable.toVector (Growable.fromVector input linear)++pureBorrowGrowableUnboxedNoGrowthKernel :: U.Vector Int -> U.Vector Int+{-# NOINLINE pureBorrowGrowableUnboxedNoGrowthKernel #-}+pureBorrowGrowableUnboxedNoGrowthKernel input =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ ( Growable.unsafeFromMutable+ (unsafePerformIO (U.thaw input))+ ownerLinear+ )+ vector <-+ Growable.withContent_ vector \content ->+ pureBorrowWorker (U.length input) 0 content+ let !() = consume vector+ pureAfter (unsafeBenchmarkFreezeGrowableInt (reclaim lend))++directGrowableUnboxedGrowthKernel :: U.Vector Int -> (U.Vector Int, Int)+{-# NOINLINE directGrowableUnboxedGrowthKernel #-}+-- The result length differs from the input's, so this one cannot go through+-- 'U.modify'; 'runST' keeps it equally safe, and the buffer never escapes.+directGrowableUnboxedGrowthKernel input = runST do+ initial <- UM.unsafeNew 0+ header <- newSTRef (0, initial)+ directGrowthWorker input 0 header+ (_, capacityContent) <- readSTRef header+ (logicalSize, content) <- readSTRef header+ frozen <- U.freeze (UM.unsafeTake logicalSize content)+ NonLinear.pure (frozen, UM.length capacityContent)++directGrowthWorker ::+ U.Vector Int ->+ Int ->+ STRef s (Int, UM.MVector s Int) ->+ ST s ()+{-# NOINLINE directGrowthWorker #-}+directGrowthWorker input !index header+ | index >= U.length input = NonLinear.pure ()+ | otherwise = do+ (logicalSize, content) <- readSTRef header+ grown <-+ if logicalSize < UM.length content+ then NonLinear.pure content+ else do+ let !capacity = growthTarget (UM.length content) (logicalSize + 1)+ target <- UM.unsafeNew capacity+ UM.unsafeCopy+ (UM.unsafeTake logicalSize target)+ (UM.unsafeTake logicalSize content)+ NonLinear.pure target+ UM.unsafeWrite grown logicalSize (U.unsafeIndex input index)+ writeSTRef header (logicalSize + 1, grown)+ directGrowthWorker input (index + 1) header++pureBorrowGrowableUnboxedGrowthKernel :: U.Vector Int -> (U.Vector Int, Int)+{-# NOINLINE pureBorrowGrowableUnboxedGrowthKernel #-}+pureBorrowGrowableUnboxedGrowthKernel input =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.empty ownerLinear)+ vector <- pureBorrowGrowthWorker input 0 vector+ Growable.capacity vector & \(Ur finalCapacity, vector) -> DataFlow.do+ consume vector+ pureAfter+ (attachCapacity finalCapacity (unsafeBenchmarkFreezeGrowableInt (reclaim lend)))++attachCapacity :: Int -> Ur (U.Vector Int) %1 -> Ur (U.Vector Int, Int)+attachCapacity =+ Unsafe.toLinear2 \finalCapacity (Ur frozen) ->+ Ur (frozen, finalCapacity)++pureBorrowGrowthWorker ::+ forall α.+ U.Vector Int ->+ Int ->+ Mut α (Growable.GrowableVector Int) %1 ->+ BO α (Mut α (Growable.GrowableVector Int))+{-# NOINLINE pureBorrowGrowthWorker #-}+pureBorrowGrowthWorker input !index vector+ | index >= U.length input = Control.pure vector+ | otherwise = Control.do+ vector <- Growable.push (U.unsafeIndex input index) vector+ pureBorrowGrowthWorker input (index + 1) vector++growthTarget :: Int -> Int -> Int+{-# INLINE growthTarget #-}+growthTarget oldCapacity required+ | required <= oldCapacity = oldCapacity+ | oldCapacity <= 0 = required `max` 1+ | oldCapacity > maxBound `quot` 2 = required+ | otherwise = required `max` (oldCapacity * 2)++pureBorrowWorker ::+ forall α.+ Int ->+ Int ->+ Mut α (Vector.Vector Int) %1 ->+ BO α ()+{-# NOINLINE pureBorrowWorker #-}+pureBorrowWorker !length_ !index vector+ | index >= length_ = Control.pure (consume vector)+ | otherwise = Control.do+ ((), vector) <-+ Vector.unsafeUpdate+ index+ (\ !value -> Control.pure ((), value + 1))+ vector+ pureBorrowWorker length_ (index + 1) vector++owningBoxedQsortKernel :: B.Vector Int -> B.Vector Int+{-# NOINLINE owningBoxedQsortKernel #-}+owningBoxedQsortKernel input =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (OwningVector.fromVector input ownerLinear)+ OwningVector.qsort 0 vector+ pureAfter (OwningVector.toVector (reclaim lend))++unrestrictedQsortKernel ::+ (G.Vector v Int) =>+ v Int ->+ v Int+{-# INLINEABLE unrestrictedQsortKernel #-}+unrestrictedQsortKernel input =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (UnrestrictedVector.fromVector input ownerLinear)+ DC.qsort 0 vector+ pureAfter (UnrestrictedVector.toVector (reclaim lend))++unrestrictedBoxedQsortKernel :: B.Vector Int -> B.Vector Int+{-# NOINLINE unrestrictedBoxedQsortKernel #-}+unrestrictedBoxedQsortKernel = unrestrictedQsortKernel++unrestrictedUnboxedQsortKernel :: U.Vector Int -> U.Vector Int+{-# NOINLINE unrestrictedUnboxedQsortKernel #-}+unrestrictedUnboxedQsortKernel = unrestrictedQsortKernel++unrestrictedFftKernel ::+ (G.Vector v (Complex Double)) =>+ v (Complex Double) ->+ v (Complex Double)+{-# INLINEABLE unrestrictedFftKernel #-}+unrestrictedFftKernel input =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (UnrestrictedVector.fromVector input ownerLinear)+ Control.void $+ DC.sequentialDivideAndConquer+ (DC.fftDC' 128)+ vector+ pureAfter (UnrestrictedVector.toVector (reclaim lend))++unrestrictedBoxedFftKernel ::+ B.Vector (Complex Double) ->+ B.Vector (Complex Double)+{-# NOINLINE unrestrictedBoxedFftKernel #-}+unrestrictedBoxedFftKernel = unrestrictedFftKernel++unrestrictedUnboxedFftKernel ::+ U.Vector (Complex Double) ->+ U.Vector (Complex Double)+{-# NOINLINE unrestrictedUnboxedFftKernel #-}+unrestrictedUnboxedFftKernel = unrestrictedFftKernel++defaultMain :: IO ()+defaultMain = Bench.defaultMain benches++benches :: [Benchmark]+benches =+ NonLinear.concat+ [ [ env+ (NonLinear.pure $ U.generate length_ (`NonLinear.rem` 1024))+ \input ->+ bgroup+ ("kernel/fixed-unboxed/" <> show length_)+ [ bench "direct" $ nf directFixedUnboxedKernel input+ , bench "pure-borrow" $ nf pureBorrowFixedUnboxedKernel input+ ]+ , env+ (NonLinear.pure $ U.generate length_ (`NonLinear.rem` 1024))+ \input ->+ bgroup+ ("kernel/growable-unboxed/no-growth/" <> show length_)+ [ bench "direct" $ nf directGrowableUnboxedNoGrowthKernel input+ , bench "pure-borrow" $ nf pureBorrowGrowableUnboxedNoGrowthKernel input+ ]+ , env+ (NonLinear.pure $ U.generate length_ (`NonLinear.rem` 1024))+ \input ->+ bgroup+ ("kernel/growable-unboxed/growth/" <> show length_)+ [ bench "direct" $ nf directGrowableUnboxedGrowthKernel input+ , bench "pure-borrow" $ nf pureBorrowGrowableUnboxedGrowthKernel input+ ]+ , env+ (NonLinear.pure $ U.generate length_ (`NonLinear.rem` 1024))+ \input ->+ bgroup+ ("public-materialization/fixed-unboxed/" <> show length_)+ [ bench "direct" $ nf directFixedUnboxedMaterialization input+ , bench "pure-borrow" $ nf pureBorrowFixedUnboxedMaterialization input+ ]+ , env+ (NonLinear.pure $ U.generate length_ (`NonLinear.rem` 1024))+ \input ->+ bgroup+ ("public-materialization/growable-unboxed/" <> show length_)+ [ bench "direct" $ nf directGrowableUnboxedMaterialization input+ , bench "pure-borrow" $ nf pureBorrowGrowableUnboxedMaterialization input+ ]+ ]+ | length_ <- [0, 1, 1024, 1024 * 1024]+ ]+ <> [ env+ ( NonLinear.pure+ ( B.generate length_ (comparisonQsortValue length_)+ , U.generate length_ (comparisonQsortValue length_)+ )+ )+ \ ~(boxedInput, unboxedInput) ->+ bgroup+ ( "algorithm/qsort/storage-and-element-ownership/"+ <> show length_+ )+ [ bench "owning/boxed" $+ nf owningBoxedQsortKernel boxedInput+ , bench "unrestricted/boxed" $+ nf unrestrictedBoxedQsortKernel boxedInput+ , bench "unrestricted/unboxed" $+ nf unrestrictedUnboxedQsortKernel unboxedInput+ ]+ | length_ <- [8 * 1024, 32 * 1024]+ ]+ <> [ env+ ( NonLinear.pure+ ( B.generate length_ comparisonFftValue+ , U.generate length_ comparisonFftValue+ )+ )+ \ ~(boxedInput, unboxedInput) ->+ bgroup+ ( "algorithm/fft/storage-and-element-ownership/"+ <> show length_+ )+ [ bench "unrestricted/boxed" $+ nf unrestrictedBoxedFftKernel boxedInput+ , bench "unrestricted/unboxed" $+ nf unrestrictedUnboxedFftKernel unboxedInput+ ]+ | length_ <- [64 * 1024, 1024 * 1024]+ ]++comparisonQsortValue :: Int -> Int -> Int+comparisonQsortValue length_ index =+ (index * 1103515245 + 12345) `NonLinear.rem` (length_ + 1)++comparisonFftValue :: Int -> Complex Double+comparisonFftValue index =+ let position = fromIntegral index+ in (sin (position / 17) + cos (position / 31)) :+ 0
+ internal-src/test-bench-common/PureBorrow/Internal/Bench/Worklist/Resume.hs view
@@ -0,0 +1,2688 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE LinearTypes #-}+{-# LANGUAGE OverloadedLabels #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE TypeOperators #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}++module PureBorrow.Internal.Bench.Worklist.Resume (+ WorklistGrowth (..),+ WorklistOutcome (..),+ WorklistReopenShape (..),+ WorklistTarget (..),+ WorklistSummary (..),+ WorklistOutput (..),+ worklistBatchSize,+ worklistInitialCapacity,+ worklistNodeCount,+ worklistDirectOpenOnceRoot,+ worklistDirectOpenOnceRootWithSeed,+ worklistDirectReopenRoot,+ worklistDirectReopenRootWithSeed,+ worklistPureBorrowOpenOnceRoot,+ worklistPureBorrowOpenOnceRootWithSeed,+ worklistPureBorrowOpenOnceWorker,+ worklistPureBorrowOpenOnceEdgeWorker,+ worklistPureBorrowCheckedOpenOnceRoot,+ worklistPureBorrowCheckedOpenOnceRootWithSeed,+ worklistPureBorrowCheckedOpenOnceWorker,+ worklistPureBorrowCheckedOpenOnceEdgeWorker,+ worklistPureBorrowFlatReopenRoot,+ worklistPureBorrowFlatReopenRootWithSeed,+ worklistPureBorrowNestedReopenRoot,+ worklistPureBorrowNestedReopenRootWithSeed,+ worklistPureBorrowResumeWorker,+ worklistPureBorrowResumeEdgeWorker,+ benches,+ defaultMain,+) where++import Control.DeepSeq (NFData)+import Control.Functor.Linear qualified as Control+import Control.Monad (when)+import Control.Monad.Borrow.Pure+import Control.Monad.Borrow.Pure.Experimental.Borrows (+ Aliases (..),+ Muts,+ reborrowings,+ )+import Control.Monad.ST.Strict (ST, runST)+import Control.Syntax.DataFlow qualified as DataFlow+import Data.Int (Int64)+import Data.Record.Linear.Borrow.Experimental.PatternMatch (+ RecordLabel,+ (.@),+ )+import Data.STRef (STRef, newSTRef, readSTRef, writeSTRef)+import Data.Vector qualified as V+import Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted qualified as Growable+import Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted qualified as Fixed+import Data.Vector.Mutable qualified as MV+import Data.Vector.Unboxed qualified as U+import Data.Vector.Unboxed.Mutable qualified as UM+import GHC.Exts qualified as GHC+import GHC.Generics (Generic)+import Prelude.Linear (+ lseq,+ unur,+ (&),+ )+import Test.Tasty.Bench (Benchmark, bench, bgroup, nf)+import Test.Tasty.Bench qualified as Bench++data WorklistGrowth+ = NoGrowth+ | NoGrowthBatch64+ | NoGrowthBatch8+ | SparseGrowth+ | DenseGrowth+ deriving stock (Bounded, Enum, Eq, Generic, Show)+ deriving anyclass (NFData)++data WorklistTarget+ = Drain+ | StopEarly+ deriving stock (Bounded, Enum, Eq, Generic, Show)+ deriving anyclass (NFData)++data WorklistReopenShape+ = FlatReopen+ | NestedReopen+ deriving stock (Bounded, Enum, Eq, Generic, Show)+ deriving anyclass (NFData)++data WorklistOutcome+ = Drained+ | Stopped+ deriving stock (Eq, Generic, Show)+ deriving anyclass (NFData)++data WorklistSummary = WorklistSummary+ { outcome :: !WorklistOutcome+ , visitedNodes :: !Int+ , enqueueTransitions :: !Int+ , offsetReads :: !Int+ , adjacencyReads :: !Int+ , payloadReads :: !Int+ , markReads :: !Int+ , markWrites :: !Int+ , queueReads :: !Int+ , queueWrites :: !Int+ , logWrites :: !Int+ , contentOpens :: !Int+ , resumeBoundaries :: !Int+ , headerUpdates :: !Int+ , bufferGrowths :: !Int+ , finalDigest :: !Int64+ }+ deriving stock (Eq, Generic, Show)+ deriving anyclass (NFData)++data WorklistOutput = WorklistOutput+ { summary :: !WorklistSummary+ , finalMarks :: !(U.Vector Int)+ , finalState :: !(U.Vector Int)+ , finalQueue :: !(U.Vector Int)+ , finalLog :: !(U.Vector Int)+ }+ deriving stock (Eq, Generic, Show)+ deriving anyclass (NFData)++data UnboxedHeader s+ = UnboxedHeader !Int !(UM.MVector s Int)++data BoxedHeader s+ = BoxedHeader !Int !(MV.MVector s (Int, Int))++data TraversalState = TraversalState+ { stateHead :: !Int+ , stateTail :: !Int+ , stateVisits :: !Int+ , stateEnqueues :: !Int+ , stateLogSize :: !Int+ , stateDigest :: !Int64+ }++data SegmentResult = SegmentResult+ { segmentState :: !TraversalState+ , segmentPendingRev :: ![Int]+ , segmentLogRev :: ![Int]+ , segmentStopped :: !Bool+ }++data ReopenEvidence = ReopenEvidence+ { reopenTraversal :: !TraversalState+ , reopenScopeCount :: !Int+ , reopenHeaderUpdates :: !Int+ , reopenGrowthCount :: !Int+ , reopenOutcome :: !WorklistOutcome+ }++data WorklistFixedRoots = WorklistFixedRoots+ { fixedOffsets :: !(Fixed.Vector U.Vector Int)+ , fixedMarks :: !(Fixed.Vector U.Vector Int)+ , fixedState :: !(Fixed.Vector U.Vector Int)+ }++data WorklistGraphRoots = WorklistGraphRoots+ { graphAdjacency :: !(Growable.GrowableVector U.Vector Int)+ , graphPayload :: !(Growable.GrowableVector V.Vector (Int, Int))+ }++data WorklistFrontierRoots = WorklistFrontierRoots+ { frontierQueue :: !(Growable.GrowableVector U.Vector Int)+ , frontierLog :: !(Growable.GrowableVector U.Vector Int)+ }++data WorklistStore = WorklistStore+ { worklistFixedRoots :: !WorklistFixedRoots+ , worklistGraphRoots :: !WorklistGraphRoots+ , worklistFrontierRoots :: !WorklistFrontierRoots+ }++type FlatWorklist α =+ Muts+ α+ '[ Fixed.Vector U.Vector Int+ , Fixed.Vector U.Vector Int+ , Fixed.Vector U.Vector Int+ , Growable.GrowableVector U.Vector Int+ , Growable.GrowableVector V.Vector (Int, Int)+ , Growable.GrowableVector U.Vector Int+ , Growable.GrowableVector U.Vector Int+ ]++type NestedFrontierWorklist α =+ Muts+ α+ '[ Growable.GrowableVector U.Vector Int+ , Growable.GrowableVector U.Vector Int+ ]++worklistNodeCount :: Int+worklistNodeCount = 4096++worklistDegree :: Int+worklistDegree = 3++worklistEdgeCount :: Int+worklistEdgeCount = worklistNodeCount * worklistDegree++worklistEarlyStop :: Int+worklistEarlyStop = 1365++defaultMain :: IO ()+defaultMain = Bench.defaultMain benches++benches :: [Benchmark]+benches =+ [ bgroup+ "worklist-open-once"+ [ bgroup+ (show target)+ [ bench "direct" $+ nf worklistDirectOpenOnceRoot target+ , bench "pure-borrow" $+ nf worklistPureBorrowOpenOnceRoot target+ , -- Same traversal through the checked public element surface. The+ -- difference against "pure-borrow" is the cost of the checked+ -- facade alone, and is reported separately from the+ -- safe-minus-direct excess.+ bench "pure-borrow-checked" $+ nf worklistPureBorrowCheckedOpenOnceRoot target+ ]+ | target <- [minBound .. maxBound]+ ]+ , bgroup+ "worklist-push-extend-reopen"+ [ bgroup+ (show growth)+ [ bgroup+ (show target)+ [ bench "direct-flat" $+ nf+ (worklistDirectReopenRoot FlatReopen growth)+ target+ , bench "pure-borrow-flat" $+ nf+ (worklistPureBorrowFlatReopenRoot growth)+ target+ , bench "direct-nested" $+ nf+ (worklistDirectReopenRoot NestedReopen growth)+ target+ , bench "pure-borrow-nested" $+ nf+ (worklistPureBorrowNestedReopenRoot growth)+ target+ ]+ | target <- [minBound .. maxBound]+ ]+ | growth <- [minBound .. maxBound]+ ]+ ]++worklistOffsets :: U.Vector Int+worklistOffsets =+ U.generate (worklistNodeCount + 1) \node ->+ node * worklistDegree++worklistAdjacency :: U.Vector Int+worklistAdjacency =+ U.generate worklistEdgeCount \edge ->+ let (node, slot) = edge `quotRem` worklistDegree+ in case slot of+ 0 -> (node + 1) `rem` worklistNodeCount+ 1 -> (node * 17 + 13) `rem` worklistNodeCount+ _ -> (node * 31 + 7) `rem` worklistNodeCount++worklistPayload :: V.Vector (Int, Int)+worklistPayload =+ V.generate worklistEdgeCount \edge ->+ let !node = edge `quot` worklistDegree+ !neighbor = worklistAdjacency U.! edge+ in (edge `rem` 11, (node + neighbor) `rem` 17)++targetVisits :: WorklistTarget -> Int+targetVisits Drain = worklistNodeCount+targetVisits StopEarly = worklistEarlyStop++initialMarks :: U.Vector Int+initialMarks =+ U.replicate worklistNodeCount 0 U.// [(0, 1)]++initialState :: U.Vector Int+initialState = U.fromListN 5 [0, 1, 0, 0, 0]++data WorklistStorage+ = OpenOnceStorage+ | ReopenStorage !WorklistGrowth++newWorklistStore :: WorklistStorage -> Linearly %1 -> WorklistStore+{-# NOINLINE newWorklistStore #-}+newWorklistStore =+ GHC.noinline \storage linear ->+ dup linear & \(offsetsLinear, rest1) ->+ dup rest1 & \(marksLinear, rest2) ->+ dup rest2 & \(stateLinear, rest3) ->+ dup rest3 & \(adjacencyLinear, rest4) ->+ dup rest4 & \(payloadLinear, rest5) ->+ dup rest5 & \(queueLinear, logLinear) ->+ WorklistStore+ { worklistFixedRoots =+ WorklistFixedRoots+ { fixedOffsets =+ Fixed.fromVector+ worklistOffsets+ offsetsLinear+ , fixedMarks =+ Fixed.fromVector+ initialMarks+ marksLinear+ , fixedState =+ Fixed.fromVector+ initialState+ stateLinear+ }+ , worklistGraphRoots =+ WorklistGraphRoots+ { graphAdjacency =+ Growable.fromVector+ worklistAdjacency+ adjacencyLinear+ , graphPayload =+ Growable.fromVector+ worklistPayload+ payloadLinear+ }+ , worklistFrontierRoots =+ WorklistFrontierRoots+ { frontierQueue =+ case storage of+ OpenOnceStorage ->+ Growable.fromVector+ ( U.cons+ 0+ ( U.replicate+ (worklistNodeCount - 1)+ 0+ )+ )+ queueLinear+ ReopenStorage growth ->+ Growable.withCapacity+ (worklistInitialCapacity growth)+ queueLinear+ , frontierLog =+ case storage of+ OpenOnceStorage ->+ Growable.fromVector+ (U.replicate worklistNodeCount 0)+ logLinear+ ReopenStorage growth ->+ Growable.withCapacity+ (worklistInitialCapacity growth)+ logLinear+ }+ }++worklistFixedRootsField ::+ RecordLabel WorklistStore "worklistFixedRoots" WorklistFixedRoots+worklistFixedRootsField = #worklistFixedRoots++worklistGraphRootsField ::+ RecordLabel WorklistStore "worklistGraphRoots" WorklistGraphRoots+worklistGraphRootsField = #worklistGraphRoots++worklistFrontierRootsField ::+ RecordLabel WorklistStore "worklistFrontierRoots" WorklistFrontierRoots+worklistFrontierRootsField = #worklistFrontierRoots++fixedOffsetsField ::+ RecordLabel+ WorklistFixedRoots+ "fixedOffsets"+ (Fixed.Vector U.Vector Int)+fixedOffsetsField = #fixedOffsets++fixedMarksField ::+ RecordLabel+ WorklistFixedRoots+ "fixedMarks"+ (Fixed.Vector U.Vector Int)+fixedMarksField = #fixedMarks++fixedStateField ::+ RecordLabel+ WorklistFixedRoots+ "fixedState"+ (Fixed.Vector U.Vector Int)+fixedStateField = #fixedState++graphAdjacencyField ::+ RecordLabel+ WorklistGraphRoots+ "graphAdjacency"+ (Growable.GrowableVector U.Vector Int)+graphAdjacencyField = #graphAdjacency++graphPayloadField ::+ RecordLabel+ WorklistGraphRoots+ "graphPayload"+ (Growable.GrowableVector V.Vector (Int, Int))+graphPayloadField = #graphPayload++frontierQueueField ::+ RecordLabel+ WorklistFrontierRoots+ "frontierQueue"+ (Growable.GrowableVector U.Vector Int)+frontierQueueField = #frontierQueue++frontierLogField ::+ RecordLabel+ WorklistFrontierRoots+ "frontierLog"+ (Growable.GrowableVector U.Vector Int)+frontierLogField = #frontierLog++worklistDirectOpenOnceRoot ::+ WorklistTarget ->+ WorklistOutput+{-# NOINLINE worklistDirectOpenOnceRoot #-}+worklistDirectOpenOnceRoot =+ worklistDirectOpenOnceRootWithSeed 0++worklistDirectOpenOnceRootWithSeed ::+ Int ->+ WorklistTarget ->+ WorklistOutput+{-# NOINLINE worklistDirectOpenOnceRootWithSeed #-}+worklistDirectOpenOnceRootWithSeed seed target =+ runST do+ offsets <- U.thaw worklistOffsets+ marks <- U.thaw initialMarks+ state <- U.thaw initialState+ adjacencyHeader <- newUnboxedHeaderFromVector worklistAdjacency+ payloadHeader <- newBoxedHeaderFromVector worklistPayload+ queueHeader <-+ newUnboxedHeaderFromVector+ (U.cons 0 (U.replicate (worklistNodeCount - 1) 0))+ logHeader <-+ newUnboxedHeaderFromVector+ (U.replicate worklistNodeCount 0)+ UnboxedHeader _ adjacency <- readSTRef adjacencyHeader+ BoxedHeader _ payload <- readSTRef payloadHeader+ UnboxedHeader _ queue <- readSTRef queueHeader+ UnboxedHeader _ outputLog <- readSTRef logHeader+ finalTraversal <-+ runOpenOnce+ (targetVisits target)+ offsets+ adjacency+ payload+ marks+ queue+ outputLog+ (TraversalState 0 1 0 0 0 (initialDigestForSeed seed))+ writeTraversalState state finalTraversal+ finishDirect+ (if stateVisits finalTraversal == targetVisits target && target == StopEarly then Stopped else Drained)+ 4+ 0+ 0+ 0+ offsets+ adjacency+ payload+ marks+ state+ queue+ outputLog+ finalTraversal++worklistDirectReopenRoot ::+ WorklistReopenShape ->+ WorklistGrowth ->+ WorklistTarget ->+ WorklistOutput+{-# NOINLINE worklistDirectReopenRoot #-}+worklistDirectReopenRoot =+ worklistDirectReopenRootWithSeed 0++worklistDirectReopenRootWithSeed ::+ Int ->+ WorklistReopenShape ->+ WorklistGrowth ->+ WorklistTarget ->+ WorklistOutput+{-# NOINLINE worklistDirectReopenRootWithSeed #-}+worklistDirectReopenRootWithSeed seed reopenShape growth target =+ runST do+ offsets <- U.thaw worklistOffsets+ marks <- U.thaw initialMarks+ state <- U.thaw initialState+ adjacencyHeader <- newUnboxedHeaderFromVector worklistAdjacency+ payloadHeader <- newBoxedHeaderFromVector worklistPayload+ queueHeader <- newUnboxedHeader (worklistInitialCapacity growth) U.empty+ _ <- appendUnboxed queueHeader (U.singleton 0)+ logHeader <- newUnboxedHeader (worklistInitialCapacity growth) U.empty+ (finalTraversal, scopeCount, headerUpdateCount, growthCount, finalOutcome) <-+ case reopenShape of+ FlatReopen ->+ runFlatReopenDirect+ growth+ target+ offsets+ marks+ state+ adjacencyHeader+ payloadHeader+ queueHeader+ logHeader+ (TraversalState 0 1 0 0 0 (initialDigestForSeed seed))+ 0+ 0+ 0+ NestedReopen -> do+ UnboxedHeader _ adjacency <- readSTRef adjacencyHeader+ BoxedHeader _ payload <- readSTRef payloadHeader+ runNestedReopenDirect+ growth+ target+ offsets+ adjacency+ payload+ marks+ state+ queueHeader+ logHeader+ (TraversalState 0 1 0 0 0 (initialDigestForSeed seed))+ 0+ 0+ 0+ UnboxedHeader _ adjacency <- readSTRef adjacencyHeader+ BoxedHeader _ payload <- readSTRef payloadHeader+ UnboxedHeader queueSize queue <- readSTRef queueHeader+ UnboxedHeader logSize outputLog <- readSTRef logHeader+ finishDirect+ finalOutcome+ ( case reopenShape of+ FlatReopen -> 4 * scopeCount+ NestedReopen -> 2 + 2 * scopeCount+ )+ (max 0 (scopeCount - 1))+ headerUpdateCount+ growthCount+ offsets+ adjacency+ payload+ marks+ state+ (UM.unsafeTake queueSize queue)+ (UM.unsafeTake logSize outputLog)+ finalTraversal++worklistPureBorrowOpenOnceRoot ::+ WorklistTarget ->+ WorklistOutput+{-# NOINLINE worklistPureBorrowOpenOnceRoot #-}+worklistPureBorrowOpenOnceRoot =+ worklistPureBorrowOpenOnceRootWithSeed 0++worklistPureBorrowOpenOnceRootWithSeed ::+ Int ->+ WorklistTarget ->+ WorklistOutput+{-# NOINLINE worklistPureBorrowOpenOnceRootWithSeed #-}+worklistPureBorrowOpenOnceRootWithSeed seed target =+ unur+ ( linearly \linear -> DataFlow.do+ (allocationLinear, borrowLinear) <- dup linear+ store <- newWorklistStore OpenOnceStorage allocationLinear+ runBO borrowLinear Control.do+ (storeBorrow, lender) <- borrowM store+ (Ur traversal, storeBorrow) <-+ reborrowing storeBorrow \local -> Control.do+ let %1 !(fixedRootBorrows, graphRootBorrows, frontierRootBorrows) =+ local+ .@ ( worklistFixedRootsField+ , worklistGraphRootsField+ , worklistFrontierRootsField+ )+ let %1 !(offsetsBorrow, marksBorrow, stateBorrow) =+ fixedRootBorrows+ .@ (fixedOffsetsField, fixedMarksField, fixedStateField)+ let %1 !(adjacencyBorrow, payloadBorrow) =+ graphRootBorrows+ .@ (graphAdjacencyField, graphPayloadField)+ let %1 !(queueBorrow, logBorrow) =+ frontierRootBorrows+ .@ (frontierQueueField, frontierLogField)+ let %1 !adjacencyContent =+ Growable.getContents adjacencyBorrow+ let %1 !payloadContent =+ Growable.getContents payloadBorrow+ let %1 !queueContent =+ Growable.getContents queueBorrow+ let %1 !logContent =+ Growable.getContents logBorrow+ (Ur initial, stateBorrow) <-+ readTraversalState stateBorrow+ let !seededInitial =+ initial+ { stateDigest = initialDigestForSeed seed+ }+ ( Ur traversal+ , offsetsBorrow+ , marksBorrow+ , stateBorrow+ , adjacencyContent+ , payloadContent+ , queueContent+ , logContent+ ) <-+ worklistPureBorrowOpenOnceWorker+ (targetVisits target)+ seededInitial+ offsetsBorrow+ marksBorrow+ stateBorrow+ adjacencyContent+ payloadContent+ queueContent+ logContent+ let !() =+ consumeWorklistViews+ offsetsBorrow+ marksBorrow+ stateBorrow+ adjacencyContent+ payloadContent+ queueContent+ logContent+ Control.pure (Ur traversal)+ let !(Ur _) = share storeBorrow+ pureAfter+ ( finishWorklistStore+ (outcomeFor target traversal)+ 4+ 0+ 0+ 0+ traversal+ (reclaim lender)+ )+ )++{- | The open-once traversal, reading and writing through the /checked/ public+element surface instead of the unchecked one.++This is the copied-read attribution control. It differs from+'worklistPureBorrowOpenOnceRoot' in exactly one respect: every element access+goes through 'Fixed.copyAtMut' and 'Fixed.write' rather than+'Fixed.unsafeGet' and 'Fixed.unsafeWrite'. Ownership, lifetimes, projection+structure, transition counts and the final digest are identical, so the+allocation difference between the two roots is the cost of the public checked+facade alone: the bounds check, the @size@ call, the @Ur@ boxing, and whatever+survives of the @HasCallStack@ obligation those operations carry.++Both roots must therefore produce equal 'WorklistOutput'. Note that+@copyAtMut@ on this non-element-owning family is defined as @get@; it is not a+@Copyable@ copy, and this control deliberately does not switch to an+element-owning family, which would change the ownership mode and make the+allocation numbers incomparable.+-}+worklistPureBorrowCheckedOpenOnceRoot ::+ WorklistTarget ->+ WorklistOutput+{-# NOINLINE worklistPureBorrowCheckedOpenOnceRoot #-}+worklistPureBorrowCheckedOpenOnceRoot =+ worklistPureBorrowCheckedOpenOnceRootWithSeed 0++worklistPureBorrowCheckedOpenOnceRootWithSeed ::+ Int ->+ WorklistTarget ->+ WorklistOutput+{-# NOINLINE worklistPureBorrowCheckedOpenOnceRootWithSeed #-}+worklistPureBorrowCheckedOpenOnceRootWithSeed seed target =+ unur+ ( linearly \linear -> DataFlow.do+ (allocationLinear, borrowLinear) <- dup linear+ store <- newWorklistStore OpenOnceStorage allocationLinear+ runBO borrowLinear Control.do+ (storeBorrow, lender) <- borrowM store+ (Ur traversal, storeBorrow) <-+ reborrowing storeBorrow \local -> Control.do+ let %1 !(fixedRootBorrows, graphRootBorrows, frontierRootBorrows) =+ local+ .@ ( worklistFixedRootsField+ , worklistGraphRootsField+ , worklistFrontierRootsField+ )+ let %1 !(offsetsBorrow, marksBorrow, stateBorrow) =+ fixedRootBorrows+ .@ (fixedOffsetsField, fixedMarksField, fixedStateField)+ let %1 !(adjacencyBorrow, payloadBorrow) =+ graphRootBorrows+ .@ (graphAdjacencyField, graphPayloadField)+ let %1 !(queueBorrow, logBorrow) =+ frontierRootBorrows+ .@ (frontierQueueField, frontierLogField)+ let %1 !adjacencyContent =+ Growable.getContents adjacencyBorrow+ let %1 !payloadContent =+ Growable.getContents payloadBorrow+ let %1 !queueContent =+ Growable.getContents queueBorrow+ let %1 !logContent =+ Growable.getContents logBorrow+ (Ur initial, stateBorrow) <-+ readTraversalStateChecked stateBorrow+ let !seededInitial =+ initial+ { stateDigest = initialDigestForSeed seed+ }+ ( Ur traversal+ , offsetsBorrow+ , marksBorrow+ , stateBorrow+ , adjacencyContent+ , payloadContent+ , queueContent+ , logContent+ ) <-+ worklistPureBorrowCheckedOpenOnceWorker+ (targetVisits target)+ seededInitial+ offsetsBorrow+ marksBorrow+ stateBorrow+ adjacencyContent+ payloadContent+ queueContent+ logContent+ let !() =+ consumeWorklistViews+ offsetsBorrow+ marksBorrow+ stateBorrow+ adjacencyContent+ payloadContent+ queueContent+ logContent+ Control.pure (Ur traversal)+ let !(Ur _) = share storeBorrow+ pureAfter+ ( finishWorklistStore+ (outcomeFor target traversal)+ 4+ 0+ 0+ 0+ traversal+ (reclaim lender)+ )+ )++worklistPureBorrowFlatReopenRoot ::+ WorklistGrowth ->+ WorklistTarget ->+ WorklistOutput+{-# NOINLINE worklistPureBorrowFlatReopenRoot #-}+worklistPureBorrowFlatReopenRoot =+ worklistPureBorrowReopenRoot FlatReopen++worklistPureBorrowFlatReopenRootWithSeed ::+ Int ->+ WorklistGrowth ->+ WorklistTarget ->+ WorklistOutput+{-# NOINLINE worklistPureBorrowFlatReopenRootWithSeed #-}+worklistPureBorrowFlatReopenRootWithSeed seed =+ worklistPureBorrowReopenRootWithSeed seed FlatReopen++worklistPureBorrowNestedReopenRoot ::+ WorklistGrowth ->+ WorklistTarget ->+ WorklistOutput+{-# NOINLINE worklistPureBorrowNestedReopenRoot #-}+worklistPureBorrowNestedReopenRoot =+ worklistPureBorrowReopenRoot NestedReopen++worklistPureBorrowNestedReopenRootWithSeed ::+ Int ->+ WorklistGrowth ->+ WorklistTarget ->+ WorklistOutput+{-# NOINLINE worklistPureBorrowNestedReopenRootWithSeed #-}+worklistPureBorrowNestedReopenRootWithSeed seed =+ worklistPureBorrowReopenRootWithSeed seed NestedReopen++worklistPureBorrowReopenRoot ::+ WorklistReopenShape ->+ WorklistGrowth ->+ WorklistTarget ->+ WorklistOutput+{-# NOINLINE worklistPureBorrowReopenRoot #-}+worklistPureBorrowReopenRoot =+ worklistPureBorrowReopenRootWithSeed 0++worklistPureBorrowReopenRootWithSeed ::+ Int ->+ WorklistReopenShape ->+ WorklistGrowth ->+ WorklistTarget ->+ WorklistOutput+{-# NOINLINE worklistPureBorrowReopenRootWithSeed #-}+worklistPureBorrowReopenRootWithSeed seed reopenShape growth target =+ unur+ ( linearly \linear -> DataFlow.do+ (allocationLinear, borrowLinear) <- dup linear+ store <-+ newWorklistStore+ (ReopenStorage growth)+ allocationLinear+ runBO borrowLinear Control.do+ (storeBorrow, lender) <- borrowM store+ (Ur evidence, storeBorrow) <-+ reborrowing storeBorrow \local -> Control.do+ let %1 !(fixedRootBorrows, graphRootBorrows, frontierRootBorrows) =+ local+ .@ ( worklistFixedRootsField+ , worklistGraphRootsField+ , worklistFrontierRootsField+ )+ let %1 !(offsetsBorrow, marksBorrow, stateBorrow) =+ fixedRootBorrows+ .@ (fixedOffsetsField, fixedMarksField, fixedStateField)+ let %1 !(adjacencyBorrow, payloadBorrow) =+ graphRootBorrows+ .@ (graphAdjacencyField, graphPayloadField)+ let %1 !(queueBorrow, logBorrow) =+ frontierRootBorrows+ .@ (frontierQueueField, frontierLogField)+ queueBorrow <- Growable.push 0 queueBorrow+ (Ur initial, stateBorrow) <-+ readTraversalState stateBorrow+ let !seededInitial =+ initial+ { stateDigest = initialDigestForSeed seed+ }+ case reopenShape of+ FlatReopen -> Control.do+ (Ur evidence, fields) <-+ runFlatReopenPureBorrow+ growth+ target+ seededInitial+ (worklistInitialCapacity growth)+ (worklistInitialCapacity growth)+ 0+ 0+ 0+ ( offsetsBorrow+ :- marksBorrow+ :- stateBorrow+ :- adjacencyBorrow+ :- payloadBorrow+ :- queueBorrow+ :- logBorrow+ :- BNil+ )+ let !() = consume fields+ Control.pure (Ur evidence)+ NestedReopen -> Control.do+ let !(Ur sharedOffsets) = share offsetsBorrow+ let !(Ur sharedAdjacency) = share adjacencyBorrow+ let !(Ur sharedPayload) = share payloadBorrow+ let !adjacencyContent =+ Growable.getContents sharedAdjacency+ let !payloadContent =+ Growable.getContents sharedPayload+ (Ur evidence, fixedFields) <-+ reborrowings+ (marksBorrow :- stateBorrow :- BNil)+ \case+ marks :- state :- BNil -> Control.do+ ( Ur evidence+ , marks+ , state+ , frontierFields+ ) <-+ runNestedReopenPureBorrow+ growth+ target+ seededInitial+ (worklistInitialCapacity growth)+ (worklistInitialCapacity growth)+ 0+ 0+ 0+ (subShare sharedOffsets)+ (subShare adjacencyContent)+ (subShare payloadContent)+ marks+ state+ (upcast (queueBorrow :- logBorrow :- BNil))+ let !(Ur _) = share marks+ !(Ur _) = share state+ !() = consume frontierFields+ Control.pure (Ur evidence)+ let !() = consume fixedFields+ Control.pure (Ur evidence)+ let !(Ur _) = share storeBorrow+ pureAfter+ ( finishWorklistStore+ (reopenOutcome evidence)+ ( case reopenShape of+ FlatReopen ->+ 4 * reopenScopeCount evidence+ NestedReopen ->+ 2 + 2 * reopenScopeCount evidence+ )+ (max 0 (reopenScopeCount evidence - 1))+ (reopenHeaderUpdates evidence)+ (reopenGrowthCount evidence)+ (reopenTraversal evidence)+ (reclaim lender)+ )+ )++runFlatReopenPureBorrow ::+ WorklistGrowth ->+ WorklistTarget ->+ TraversalState ->+ Int ->+ Int ->+ Int ->+ Int ->+ Int ->+ FlatWorklist α %1 ->+ BO α (Ur ReopenEvidence, FlatWorklist α)+runFlatReopenPureBorrow+ growth+ target+ current+ queueCapacity+ logCapacity+ scopes+ updates+ growths+ fields+ | stateVisits current >= targetVisits target =+ Control.pure+ ( Ur+ ( reopenEvidence+ target+ current+ scopes+ updates+ growths+ )+ , fields+ )+ | stateHead current >= stateTail current =+ Control.pure+ ( Ur+ ReopenEvidence+ { reopenTraversal = current+ , reopenScopeCount = scopes+ , reopenHeaderUpdates = updates+ , reopenGrowthCount = growths+ , reopenOutcome = Drained+ }+ , fields+ )+ | otherwise = Control.do+ (Ur segment, fields) <-+ reborrowings fields \case+ offsets+ :- marks+ :- state+ :- adjacency+ :- payload+ :- queue+ :- outputLog+ :- BNil -> Control.do+ let %1 !adjacencyContent =+ Growable.getContents adjacency+ let %1 !payloadContent =+ Growable.getContents payload+ let %1 !queueContent =+ Growable.getContents queue+ let %1 !logContent =+ Growable.getContents outputLog+ ( Ur segment+ , offsets+ , marks+ , state+ , adjacencyContent+ , payloadContent+ , queueContent+ , logContent+ ) <-+ worklistPureBorrowResumeWorker+ (targetVisits target)+ ( min+ (stateTail current)+ (stateHead current + worklistBatchSize growth)+ )+ current+ []+ []+ offsets+ marks+ state+ adjacencyContent+ payloadContent+ queueContent+ logContent+ let !() =+ consumeWorklistViews+ offsets+ marks+ state+ adjacencyContent+ payloadContent+ queueContent+ logContent+ Control.pure (Ur segment)+ let %1 !( offsets+ :- marks+ :- state+ :- adjacency+ :- payload+ :- queue+ :- outputLog+ :- BNil+ ) = fields+ let !pending =+ U.fromList (reverse (segmentPendingRev segment))+ !newLogs =+ U.fromList (reverse (segmentLogRev segment))+ !nextTraversal = segmentState segment+ !( nextQueueCapacity+ , nextLogCapacity+ , nextGrowths+ ) =+ nextCapacities+ queueCapacity+ logCapacity+ growths+ pending+ nextTraversal+ !nextUpdates =+ updates+ + (if U.null pending then 0 else 1)+ + 1+ !nextScopes = scopes + 1+ queue <-+ if U.null pending+ then Control.pure queue+ else Growable.extend pending queue+ outputLog <- Growable.extend newLogs outputLog+ state <-+ writeTraversalStatePureBorrow nextTraversal state+ let %1 !nextFields =+ offsets+ :- marks+ :- state+ :- adjacency+ :- payload+ :- queue+ :- outputLog+ :- BNil+ if segmentStopped segment+ then+ Control.pure+ ( Ur+ ( reopenEvidence+ target+ nextTraversal+ nextScopes+ nextUpdates+ nextGrowths+ )+ , nextFields+ )+ else+ runFlatReopenPureBorrow+ growth+ target+ nextTraversal+ nextQueueCapacity+ nextLogCapacity+ nextScopes+ nextUpdates+ nextGrowths+ nextFields++runNestedReopenPureBorrow ::+ WorklistGrowth ->+ WorklistTarget ->+ TraversalState ->+ Int ->+ Int ->+ Int ->+ Int ->+ Int ->+ Share α (Fixed.Vector U.Vector Int) ->+ Share α (Fixed.Vector U.Vector Int) ->+ Share α (Fixed.Vector V.Vector (Int, Int)) ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ NestedFrontierWorklist α %1 ->+ BO+ α+ ( Ur ReopenEvidence+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ , NestedFrontierWorklist α+ )+runNestedReopenPureBorrow+ growth+ target+ current+ queueCapacity+ logCapacity+ scopes+ updates+ growths+ offsets+ adjacency+ payload+ marks+ state+ fields+ | stateVisits current >= targetVisits target =+ Control.pure+ ( Ur+ ( reopenEvidence+ target+ current+ scopes+ updates+ growths+ )+ , marks+ , state+ , fields+ )+ | stateHead current >= stateTail current =+ Control.pure+ ( Ur+ ReopenEvidence+ { reopenTraversal = current+ , reopenScopeCount = scopes+ , reopenHeaderUpdates = updates+ , reopenGrowthCount = growths+ , reopenOutcome = Drained+ }+ , marks+ , state+ , fields+ )+ | otherwise = Control.do+ ((Ur segment, marks, state), fields) <-+ reborrowings fields \case+ queue :- outputLog :- BNil -> Control.do+ let %1 !queueContent =+ Growable.getContents queue+ let %1 !logContent =+ Growable.getContents outputLog+ ( Ur segment+ , offsetsOccurrence+ , marks+ , state+ , adjacencyOccurrence+ , payloadOccurrence+ , queueContent+ , logContent+ ) <-+ worklistPureBorrowResumeWorker+ (targetVisits target)+ ( min+ (stateTail current)+ (stateHead current + worklistBatchSize growth)+ )+ current+ []+ []+ offsets+ marks+ state+ adjacency+ payload+ queueContent+ logContent+ let !() = consume offsetsOccurrence+ !() = consume adjacencyOccurrence+ !() = consume payloadOccurrence+ !(Ur _) = share queueContent+ !(Ur _) = share logContent+ Control.pure (Ur segment, marks, state)+ let %1 !(queue :- outputLog :- BNil) =+ fields+ let !pending =+ U.fromList (reverse (segmentPendingRev segment))+ !newLogs =+ U.fromList (reverse (segmentLogRev segment))+ !nextTraversal = segmentState segment+ !( nextQueueCapacity+ , nextLogCapacity+ , nextGrowths+ ) =+ nextCapacities+ queueCapacity+ logCapacity+ growths+ pending+ nextTraversal+ !nextUpdates =+ updates+ + (if U.null pending then 0 else 1)+ + 1+ !nextScopes = scopes + 1+ queue <-+ if U.null pending+ then Control.pure queue+ else Growable.extend pending queue+ outputLog <- Growable.extend newLogs outputLog+ state <-+ writeTraversalStatePureBorrow nextTraversal state+ let %1 !nextFields =+ queue :- outputLog :- BNil+ if segmentStopped segment+ then+ Control.pure+ ( Ur+ ( reopenEvidence+ target+ nextTraversal+ nextScopes+ nextUpdates+ nextGrowths+ )+ , marks+ , state+ , nextFields+ )+ else+ runNestedReopenPureBorrow+ growth+ target+ nextTraversal+ nextQueueCapacity+ nextLogCapacity+ nextScopes+ nextUpdates+ nextGrowths+ offsets+ adjacency+ payload+ marks+ state+ nextFields++reopenEvidence ::+ WorklistTarget ->+ TraversalState ->+ Int ->+ Int ->+ Int ->+ ReopenEvidence+reopenEvidence target traversal scopes updates growths =+ ReopenEvidence+ { reopenTraversal = traversal+ , reopenScopeCount = scopes+ , reopenHeaderUpdates = updates+ , reopenGrowthCount = growths+ , reopenOutcome =+ case target of+ Drain -> Drained+ StopEarly -> Stopped+ }++nextCapacities ::+ Int ->+ Int ->+ Int ->+ U.Vector Int ->+ TraversalState ->+ (Int, Int, Int)+nextCapacities+ queueCapacity+ logCapacity+ growths+ pending+ traversal =+ let !nextQueueCapacity =+ if U.null pending+ then queueCapacity+ else growthTarget queueCapacity (stateTail traversal)+ !nextLogCapacity =+ growthTarget logCapacity (stateLogSize traversal)+ !nextGrowths =+ growths+ + fromEnum (nextQueueCapacity > queueCapacity)+ + fromEnum (nextLogCapacity > logCapacity)+ in (nextQueueCapacity, nextLogCapacity, nextGrowths)++worklistPureBorrowResumeWorker ::+ ( α >= ζ+ , β >= ζ+ , γ >= ζ+ , δ >= ζ+ , ε >= ζ+ ) =>+ Int ->+ Int ->+ TraversalState ->+ [Int] ->+ [Int] ->+ Borrow bk1 α (Fixed.Vector U.Vector Int) %1 ->+ Mut β (Fixed.Vector U.Vector Int) %1 ->+ Mut γ (Fixed.Vector U.Vector Int) %1 ->+ Borrow bk2 δ (Fixed.Vector U.Vector Int) %1 ->+ Borrow bk3 ε (Fixed.Vector V.Vector (Int, Int)) %1 ->+ Mut ζ (Fixed.Vector U.Vector Int) %1 ->+ Mut ζ (Fixed.Vector U.Vector Int) %1 ->+ BO+ ζ+ ( Ur SegmentResult+ , Borrow bk1 α (Fixed.Vector U.Vector Int)+ , Mut β (Fixed.Vector U.Vector Int)+ , Mut γ (Fixed.Vector U.Vector Int)+ , Borrow bk2 δ (Fixed.Vector U.Vector Int)+ , Borrow bk3 ε (Fixed.Vector V.Vector (Int, Int))+ , Mut ζ (Fixed.Vector U.Vector Int)+ , Mut ζ (Fixed.Vector U.Vector Int)+ )+{-# NOINLINE worklistPureBorrowResumeWorker #-}+worklistPureBorrowResumeWorker+ stopAfter+ snapshotTail+ current+ pendingRev+ logRev+ offsets+ marks+ state+ adjacency+ payload+ queue+ outputLog+ | stateVisits current >= stopAfter =+ Control.pure+ ( Ur (SegmentResult current pendingRev logRev True)+ , offsets+ , marks+ , state+ , adjacency+ , payload+ , queue+ , outputLog+ )+ | stateHead current >= snapshotTail =+ Control.pure+ ( Ur (SegmentResult current pendingRev logRev False)+ , offsets+ , marks+ , state+ , adjacency+ , payload+ , queue+ , outputLog+ )+ | otherwise = Control.do+ (Ur node, queue) <-+ Fixed.unsafeGet (stateHead current) queue+ (Ur start, offsets) <-+ Fixed.unsafeGet node offsets+ (Ur end, offsets) <-+ Fixed.unsafeGet (node + 1) offsets+ ( Ur (nextTail, nextEnqueues, nextPendingRev, nodeDigest)+ , adjacency+ , payload+ , marks+ ) <-+ worklistPureBorrowResumeEdgeWorker+ start+ end+ (stateTail current)+ (stateEnqueues current)+ pendingRev+ (stateDigest current)+ adjacency+ payload+ marks+ let !logValue = digestToLogValue nodeDigest node+ !nextState =+ TraversalState+ { stateHead = stateHead current + 1+ , stateTail = nextTail+ , stateVisits = stateVisits current + 1+ , stateEnqueues = nextEnqueues+ , stateLogSize = stateLogSize current + 1+ , stateDigest = mixDigest nodeDigest logValue+ }+ worklistPureBorrowResumeWorker+ stopAfter+ snapshotTail+ nextState+ nextPendingRev+ (logValue : logRev)+ offsets+ marks+ state+ adjacency+ payload+ queue+ outputLog++worklistPureBorrowResumeEdgeWorker ::+ (α >= δ, β >= δ, γ >= δ) =>+ Int ->+ Int ->+ Int ->+ Int ->+ [Int] ->+ Int64 ->+ Borrow bk1 α (Fixed.Vector U.Vector Int) %1 ->+ Borrow bk2 β (Fixed.Vector V.Vector (Int, Int)) %1 ->+ Mut γ (Fixed.Vector U.Vector Int) %1 ->+ BO+ δ+ ( Ur (Int, Int, [Int], Int64)+ , Borrow bk1 α (Fixed.Vector U.Vector Int)+ , Borrow bk2 β (Fixed.Vector V.Vector (Int, Int))+ , Mut γ (Fixed.Vector U.Vector Int)+ )+-- This benchmark-internal export exists only to anchor optimized-Core+-- inspection. Its indices are unchecked; only the benchmark roots establish+-- the offset and neighbor bounds required by the unsafe element operations.+{-# INLINEABLE worklistPureBorrowResumeEdgeWorker #-}+worklistPureBorrowResumeEdgeWorker+ edge+ end+ tailIndex+ enqueues+ pendingRev+ digest+ adjacency+ payload+ marks+ | edge >= end =+ Control.pure+ ( Ur (tailIndex, enqueues, pendingRev, digest)+ , adjacency+ , payload+ , marks+ )+ | otherwise = Control.do+ (Ur neighbor, adjacency) <-+ Fixed.unsafeGet edge adjacency+ (Ur (tag, delta), payload) <-+ Fixed.unsafeGet edge payload+ (Ur marked, marks) <-+ Fixed.unsafeGet neighbor marks+ let !nextDigest =+ mixDigest+ digest+ (neighbor * 31 + tag * 17 + delta + marked)+ if marked == 0+ then Control.do+ marks <- Fixed.unsafeWrite neighbor 1 marks+ worklistPureBorrowResumeEdgeWorker+ (edge + 1)+ end+ (tailIndex + 1)+ (enqueues + 1)+ (neighbor : pendingRev)+ nextDigest+ adjacency+ payload+ marks+ else+ worklistPureBorrowResumeEdgeWorker+ (edge + 1)+ end+ tailIndex+ enqueues+ pendingRev+ nextDigest+ adjacency+ payload+ marks++worklistPureBorrowOpenOnceWorker ::+ Int ->+ TraversalState ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector V.Vector (Int, Int)) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ BO+ α+ ( Ur TraversalState+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector V.Vector (Int, Int))+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ )+{-# NOINLINE worklistPureBorrowOpenOnceWorker #-}+worklistPureBorrowOpenOnceWorker+ stopAfter+ current+ offsets+ marks+ state+ adjacency+ payload+ queue+ outputLog+ | stateVisits current >= stopAfter+ || stateHead current >= stateTail current = Control.do+ state <- writeTraversalStatePureBorrow current state+ Control.pure+ ( Ur current+ , offsets+ , marks+ , state+ , adjacency+ , payload+ , queue+ , outputLog+ )+ | otherwise = Control.do+ (Ur node, queue) <-+ Fixed.unsafeGet (stateHead current) queue+ (Ur start, offsets) <-+ Fixed.unsafeGet node offsets+ (Ur end, offsets) <-+ Fixed.unsafeGet (node + 1) offsets+ ( Ur (nextTail, nextEnqueues, nodeDigest)+ , adjacency+ , payload+ , marks+ , queue+ ) <-+ worklistPureBorrowOpenOnceEdgeWorker+ start+ end+ (stateTail current)+ (stateEnqueues current)+ (stateDigest current)+ adjacency+ payload+ marks+ queue+ let !logValue = digestToLogValue nodeDigest node+ outputLog <-+ Fixed.unsafeWrite+ (stateLogSize current)+ logValue+ outputLog+ let !nextState =+ TraversalState+ { stateHead = stateHead current + 1+ , stateTail = nextTail+ , stateVisits = stateVisits current + 1+ , stateEnqueues = nextEnqueues+ , stateLogSize = stateLogSize current + 1+ , stateDigest = mixDigest nodeDigest logValue+ }+ worklistPureBorrowOpenOnceWorker+ stopAfter+ nextState+ offsets+ marks+ state+ adjacency+ payload+ queue+ outputLog++worklistPureBorrowOpenOnceEdgeWorker ::+ Int ->+ Int ->+ Int ->+ Int ->+ Int64 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector V.Vector (Int, Int)) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ BO+ α+ ( Ur (Int, Int, Int64)+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector V.Vector (Int, Int))+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ )+-- This benchmark-internal export exists only to anchor optimized-Core+-- inspection. Its indices are unchecked; only the benchmark roots establish+-- the edge, neighbor, and queue-capacity bounds required by the unsafe element+-- operations.+{-# INLINEABLE worklistPureBorrowOpenOnceEdgeWorker #-}+worklistPureBorrowOpenOnceEdgeWorker+ edge+ end+ tailIndex+ enqueues+ digest+ adjacency+ payload+ marks+ queue+ | edge >= end =+ Control.pure+ ( Ur (tailIndex, enqueues, digest)+ , adjacency+ , payload+ , marks+ , queue+ )+ | otherwise = Control.do+ (Ur neighbor, adjacency) <-+ Fixed.unsafeGet edge adjacency+ (Ur (tag, delta), payload) <-+ Fixed.unsafeGet edge payload+ (Ur marked, marks) <-+ Fixed.unsafeGet neighbor marks+ let !nextDigest =+ mixDigest+ digest+ (neighbor * 31 + tag * 17 + delta + marked)+ if marked == 0+ then Control.do+ marks <-+ Fixed.unsafeWrite neighbor 1 marks+ queue <-+ Fixed.unsafeWrite tailIndex neighbor queue+ worklistPureBorrowOpenOnceEdgeWorker+ (edge + 1)+ end+ (tailIndex + 1)+ (enqueues + 1)+ nextDigest+ adjacency+ payload+ marks+ queue+ else+ worklistPureBorrowOpenOnceEdgeWorker+ (edge + 1)+ end+ tailIndex+ enqueues+ nextDigest+ adjacency+ payload+ marks+ queue++{- | Checked counterpart of 'worklistPureBorrowOpenOnceWorker'.++Every element access uses the checked public entry point. The traversal,+transition counts and digest are identical to the unchecked worker; only the+element-access surface differs.+-}+worklistPureBorrowCheckedOpenOnceWorker ::+ Int ->+ TraversalState ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector V.Vector (Int, Int)) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ BO+ α+ ( Ur TraversalState+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector V.Vector (Int, Int))+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ )+{-# NOINLINE worklistPureBorrowCheckedOpenOnceWorker #-}+worklistPureBorrowCheckedOpenOnceWorker+ stopAfter+ current+ offsets+ marks+ state+ adjacency+ payload+ queue+ outputLog+ | stateVisits current >= stopAfter+ || stateHead current >= stateTail current = Control.do+ state <- writeTraversalStateCheckedPureBorrow current state+ Control.pure+ ( Ur current+ , offsets+ , marks+ , state+ , adjacency+ , payload+ , queue+ , outputLog+ )+ | otherwise = Control.do+ (Ur node, queue) <-+ Fixed.copyAtMut (stateHead current) queue+ (Ur start, offsets) <-+ Fixed.copyAtMut node offsets+ (Ur end, offsets) <-+ Fixed.copyAtMut (node + 1) offsets+ ( Ur (nextTail, nextEnqueues, nodeDigest)+ , adjacency+ , payload+ , marks+ , queue+ ) <-+ worklistPureBorrowCheckedOpenOnceEdgeWorker+ start+ end+ (stateTail current)+ (stateEnqueues current)+ (stateDigest current)+ adjacency+ payload+ marks+ queue+ let !logValue = digestToLogValue nodeDigest node+ outputLog <-+ Fixed.write+ (stateLogSize current)+ logValue+ outputLog+ let !nextState =+ TraversalState+ { stateHead = stateHead current + 1+ , stateTail = nextTail+ , stateVisits = stateVisits current + 1+ , stateEnqueues = nextEnqueues+ , stateLogSize = stateLogSize current + 1+ , stateDigest = mixDigest nodeDigest logValue+ }+ worklistPureBorrowCheckedOpenOnceWorker+ stopAfter+ nextState+ offsets+ marks+ state+ adjacency+ payload+ queue+ outputLog++{- | Checked counterpart of 'worklistPureBorrowOpenOnceEdgeWorker'.++Unlike its unchecked sibling this worker establishes its own bounds, so it+carries no unchecked-access proof obligation. It exists to anchor the+checked-surface attribution measurement.+-}+worklistPureBorrowCheckedOpenOnceEdgeWorker ::+ Int ->+ Int ->+ Int ->+ Int ->+ Int64 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector V.Vector (Int, Int)) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ BO+ α+ ( Ur (Int, Int, Int64)+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector V.Vector (Int, Int))+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ )+{-# INLINEABLE worklistPureBorrowCheckedOpenOnceEdgeWorker #-}+worklistPureBorrowCheckedOpenOnceEdgeWorker+ edge+ end+ tailIndex+ enqueues+ digest+ adjacency+ payload+ marks+ queue+ | edge >= end =+ Control.pure+ ( Ur (tailIndex, enqueues, digest)+ , adjacency+ , payload+ , marks+ , queue+ )+ | otherwise = Control.do+ (Ur neighbor, adjacency) <-+ Fixed.copyAtMut edge adjacency+ (Ur (tag, delta), payload) <-+ Fixed.copyAtMut edge payload+ (Ur marked, marks) <-+ Fixed.copyAtMut neighbor marks+ let !nextDigest =+ mixDigest+ digest+ (neighbor * 31 + tag * 17 + delta + marked)+ if marked == 0+ then Control.do+ marks <-+ Fixed.write neighbor 1 marks+ queue <-+ Fixed.write tailIndex neighbor queue+ worklistPureBorrowCheckedOpenOnceEdgeWorker+ (edge + 1)+ end+ (tailIndex + 1)+ (enqueues + 1)+ nextDigest+ adjacency+ payload+ marks+ queue+ else+ worklistPureBorrowCheckedOpenOnceEdgeWorker+ (edge + 1)+ end+ tailIndex+ enqueues+ nextDigest+ adjacency+ payload+ marks+ queue++readTraversalStateChecked ::+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ BO+ α+ ( Ur TraversalState+ , Mut α (Fixed.Vector U.Vector Int)+ )+readTraversalStateChecked state = Control.do+ (Ur headIndex, state) <- Fixed.copyAtMut 0 state+ (Ur tailIndex, state) <- Fixed.copyAtMut 1 state+ (Ur visits, state) <- Fixed.copyAtMut 2 state+ (Ur enqueues, state) <- Fixed.copyAtMut 3 state+ (Ur logSize, state) <- Fixed.copyAtMut 4 state+ Control.pure+ ( Ur+ TraversalState+ { stateHead = headIndex+ , stateTail = tailIndex+ , stateVisits = visits+ , stateEnqueues = enqueues+ , stateLogSize = logSize+ , stateDigest = initialDigest+ }+ , state+ )++writeTraversalStateCheckedPureBorrow ::+ TraversalState ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ BO α (Mut α (Fixed.Vector U.Vector Int))+writeTraversalStateCheckedPureBorrow traversal state = Control.do+ state <- Fixed.write 0 (stateHead traversal) state+ state <- Fixed.write 1 (stateTail traversal) state+ state <- Fixed.write 2 (stateVisits traversal) state+ state <- Fixed.write 3 (stateEnqueues traversal) state+ Fixed.write 4 (stateLogSize traversal) state++readTraversalState ::+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ BO+ α+ ( Ur TraversalState+ , Mut α (Fixed.Vector U.Vector Int)+ )+readTraversalState state = Control.do+ (Ur headIndex, state) <- Fixed.unsafeGet 0 state+ (Ur tailIndex, state) <- Fixed.unsafeGet 1 state+ (Ur visits, state) <- Fixed.unsafeGet 2 state+ (Ur enqueues, state) <- Fixed.unsafeGet 3 state+ (Ur logSize, state) <- Fixed.unsafeGet 4 state+ Control.pure+ ( Ur+ TraversalState+ { stateHead = headIndex+ , stateTail = tailIndex+ , stateVisits = visits+ , stateEnqueues = enqueues+ , stateLogSize = logSize+ , stateDigest = initialDigest+ }+ , state+ )++writeTraversalStatePureBorrow ::+ TraversalState ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ BO α (Mut α (Fixed.Vector U.Vector Int))+writeTraversalStatePureBorrow traversal state = Control.do+ state <- Fixed.unsafeWrite 0 (stateHead traversal) state+ state <- Fixed.unsafeWrite 1 (stateTail traversal) state+ state <- Fixed.unsafeWrite 2 (stateVisits traversal) state+ state <- Fixed.unsafeWrite 3 (stateEnqueues traversal) state+ Fixed.unsafeWrite 4 (stateLogSize traversal) state++consumeWorklistViews ::+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector V.Vector (Int, Int)) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ ()+consumeWorklistViews+ offsets+ marks+ state+ adjacency+ payload+ queue+ outputLog =+ let !(Ur _) = share offsets+ !(Ur _) = share marks+ !(Ur _) = share state+ !(Ur _) = share adjacency+ !(Ur _) = share payload+ !(Ur _) = share queue+ !(Ur _) = share outputLog+ in ()++outcomeFor :: WorklistTarget -> TraversalState -> WorklistOutcome+outcomeFor target traversal+ | target == StopEarly+ && stateVisits traversal >= targetVisits target =+ Stopped+ | otherwise =+ Drained++runOpenOnce ::+ Int ->+ UM.MVector s Int ->+ UM.MVector s Int ->+ MV.MVector s (Int, Int) ->+ UM.MVector s Int ->+ UM.MVector s Int ->+ UM.MVector s Int ->+ TraversalState ->+ ST s TraversalState+runOpenOnce stopAfter offsets adjacency payload marks queue outputLog current+ | stateVisits current >= stopAfter =+ pure current+ | stateHead current >= stateTail current =+ pure current+ | otherwise = do+ node <- UM.unsafeRead queue (stateHead current)+ start <- UM.unsafeRead offsets node+ end <- UM.unsafeRead offsets (node + 1)+ (nextTail, nextEnqueues, nodeDigest) <-+ scanEdgesOpenOnce+ start+ end+ adjacency+ payload+ marks+ queue+ (stateTail current)+ (stateEnqueues current)+ (stateDigest current)+ let !logValue = digestToLogValue nodeDigest node+ UM.unsafeWrite outputLog (stateLogSize current) logValue+ let !nextState =+ TraversalState+ { stateHead = stateHead current + 1+ , stateTail = nextTail+ , stateVisits = stateVisits current + 1+ , stateEnqueues = nextEnqueues+ , stateLogSize = stateLogSize current + 1+ , stateDigest = mixDigest nodeDigest logValue+ }+ runOpenOnce+ stopAfter+ offsets+ adjacency+ payload+ marks+ queue+ outputLog+ nextState++scanEdgesOpenOnce ::+ Int ->+ Int ->+ UM.MVector s Int ->+ MV.MVector s (Int, Int) ->+ UM.MVector s Int ->+ UM.MVector s Int ->+ Int ->+ Int ->+ Int64 ->+ ST s (Int, Int, Int64)+scanEdgesOpenOnce edge end adjacency payload marks queue tailIndex enqueues digest+ | edge >= end =+ pure (tailIndex, enqueues, digest)+ | otherwise = do+ neighbor <- UM.unsafeRead adjacency edge+ (tag, delta) <- MV.unsafeRead payload edge+ marked <- UM.unsafeRead marks neighbor+ let !nextDigest =+ mixDigest+ digest+ (neighbor * 31 + tag * 17 + delta + marked)+ if marked == 0+ then do+ UM.unsafeWrite marks neighbor 1+ UM.unsafeWrite queue tailIndex neighbor+ scanEdgesOpenOnce+ (edge + 1)+ end+ adjacency+ payload+ marks+ queue+ (tailIndex + 1)+ (enqueues + 1)+ nextDigest+ else+ scanEdgesOpenOnce+ (edge + 1)+ end+ adjacency+ payload+ marks+ queue+ tailIndex+ enqueues+ nextDigest++runFlatReopenDirect ::+ WorklistGrowth ->+ WorklistTarget ->+ UM.MVector s Int ->+ UM.MVector s Int ->+ UM.MVector s Int ->+ STRef s (UnboxedHeader s) ->+ STRef s (BoxedHeader s) ->+ STRef s (UnboxedHeader s) ->+ STRef s (UnboxedHeader s) ->+ TraversalState ->+ Int ->+ Int ->+ Int ->+ ST s (TraversalState, Int, Int, Int, WorklistOutcome)+runFlatReopenDirect+ growth+ target+ offsets+ marks+ state+ adjacencyHeader+ payloadHeader+ queueHeader+ logHeader+ current+ scopes+ updates+ growths+ | stateVisits current >= targetVisits target =+ pure+ ( current+ , scopes+ , updates+ , growths+ , case target of+ Drain -> Drained+ StopEarly -> Stopped+ )+ | stateHead current >= stateTail current =+ pure (current, scopes, updates, growths, Drained)+ | otherwise = do+ UnboxedHeader _ adjacency <- readSTRef adjacencyHeader+ BoxedHeader _ payload <- readSTRef payloadHeader+ (segment, nextScopes, nextUpdates, nextGrowths) <-+ runReopenSegmentDirect+ growth+ target+ offsets+ adjacency+ payload+ marks+ state+ queueHeader+ logHeader+ current+ scopes+ updates+ growths+ if segmentStopped segment+ then+ pure+ ( segmentState segment+ , nextScopes+ , nextUpdates+ , nextGrowths+ , case target of+ Drain -> Drained+ StopEarly -> Stopped+ )+ else+ runFlatReopenDirect+ growth+ target+ offsets+ marks+ state+ adjacencyHeader+ payloadHeader+ queueHeader+ logHeader+ (segmentState segment)+ nextScopes+ nextUpdates+ nextGrowths++runNestedReopenDirect ::+ WorklistGrowth ->+ WorklistTarget ->+ UM.MVector s Int ->+ UM.MVector s Int ->+ MV.MVector s (Int, Int) ->+ UM.MVector s Int ->+ UM.MVector s Int ->+ STRef s (UnboxedHeader s) ->+ STRef s (UnboxedHeader s) ->+ TraversalState ->+ Int ->+ Int ->+ Int ->+ ST s (TraversalState, Int, Int, Int, WorklistOutcome)+runNestedReopenDirect+ growth+ target+ offsets+ adjacency+ payload+ marks+ state+ queueHeader+ logHeader+ current+ scopes+ updates+ growths+ | stateVisits current >= targetVisits target =+ pure+ ( current+ , scopes+ , updates+ , growths+ , case target of+ Drain -> Drained+ StopEarly -> Stopped+ )+ | stateHead current >= stateTail current =+ pure (current, scopes, updates, growths, Drained)+ | otherwise = do+ (segment, nextScopes, nextUpdates, nextGrowths) <-+ runReopenSegmentDirect+ growth+ target+ offsets+ adjacency+ payload+ marks+ state+ queueHeader+ logHeader+ current+ scopes+ updates+ growths+ if segmentStopped segment+ then+ pure+ ( segmentState segment+ , nextScopes+ , nextUpdates+ , nextGrowths+ , case target of+ Drain -> Drained+ StopEarly -> Stopped+ )+ else+ runNestedReopenDirect+ growth+ target+ offsets+ adjacency+ payload+ marks+ state+ queueHeader+ logHeader+ (segmentState segment)+ nextScopes+ nextUpdates+ nextGrowths++runReopenSegmentDirect ::+ WorklistGrowth ->+ WorklistTarget ->+ UM.MVector s Int ->+ UM.MVector s Int ->+ MV.MVector s (Int, Int) ->+ UM.MVector s Int ->+ UM.MVector s Int ->+ STRef s (UnboxedHeader s) ->+ STRef s (UnboxedHeader s) ->+ TraversalState ->+ Int ->+ Int ->+ Int ->+ ST s (SegmentResult, Int, Int, Int)+{-# INLINE runReopenSegmentDirect #-}+runReopenSegmentDirect+ growth+ target+ offsets+ adjacency+ payload+ marks+ state+ queueHeader+ logHeader+ current+ scopes+ updates+ growths = do+ UnboxedHeader queueSize queue <- readSTRef queueHeader+ UnboxedHeader _ outputLog <- readSTRef logHeader+ when (queueSize /= stateTail current) $+ error "worklist queue header and scalar tail diverged"+ when (UM.length outputLog < stateLogSize current) $+ error "worklist log header and scalar frontier diverged"+ segment <-+ runSegment+ (targetVisits target)+ (min queueSize (stateHead current + worklistBatchSize growth))+ offsets+ adjacency+ payload+ marks+ queue+ current+ []+ []+ let !pending = U.fromList (reverse (segmentPendingRev segment))+ !newLogs = U.fromList (reverse (segmentLogRev segment))+ queueGrew <-+ if U.null pending+ then pure False+ else appendUnboxed queueHeader pending+ logGrew <- appendUnboxed logHeader newLogs+ writeTraversalState state (segmentState segment)+ let !nextScopes = scopes + 1+ !nextUpdates =+ updates+ + (if U.null pending then 0 else 1)+ + 1+ !nextGrowths =+ growths+ + fromEnum queueGrew+ + fromEnum logGrew+ pure (segment, nextScopes, nextUpdates, nextGrowths)++runSegment ::+ Int ->+ Int ->+ UM.MVector s Int ->+ UM.MVector s Int ->+ MV.MVector s (Int, Int) ->+ UM.MVector s Int ->+ UM.MVector s Int ->+ TraversalState ->+ [Int] ->+ [Int] ->+ ST s SegmentResult+runSegment stopAfter snapshotTail offsets adjacency payload marks queue current queueRev logRev+ | stateVisits current >= stopAfter =+ pure (SegmentResult current queueRev logRev True)+ | stateHead current >= snapshotTail =+ pure (SegmentResult current queueRev logRev False)+ | otherwise = do+ node <- UM.unsafeRead queue (stateHead current)+ start <- UM.unsafeRead offsets node+ end <- UM.unsafeRead offsets (node + 1)+ (nextTail, nextEnqueues, nextQueueRev, nodeDigest) <-+ scanEdgesSegment+ start+ end+ adjacency+ payload+ marks+ (stateTail current)+ (stateEnqueues current)+ queueRev+ (stateDigest current)+ let !logValue = digestToLogValue nodeDigest node+ !nextState =+ TraversalState+ { stateHead = stateHead current + 1+ , stateTail = nextTail+ , stateVisits = stateVisits current + 1+ , stateEnqueues = nextEnqueues+ , stateLogSize = stateLogSize current + 1+ , stateDigest = mixDigest nodeDigest logValue+ }+ runSegment+ stopAfter+ snapshotTail+ offsets+ adjacency+ payload+ marks+ queue+ nextState+ nextQueueRev+ (logValue : logRev)++scanEdgesSegment ::+ Int ->+ Int ->+ UM.MVector s Int ->+ MV.MVector s (Int, Int) ->+ UM.MVector s Int ->+ Int ->+ Int ->+ [Int] ->+ Int64 ->+ ST s (Int, Int, [Int], Int64)+scanEdgesSegment edge end adjacency payload marks tailIndex enqueues queueRev digest+ | edge >= end =+ pure (tailIndex, enqueues, queueRev, digest)+ | otherwise = do+ neighbor <- UM.unsafeRead adjacency edge+ (tag, delta) <- MV.unsafeRead payload edge+ marked <- UM.unsafeRead marks neighbor+ let !nextDigest =+ mixDigest+ digest+ (neighbor * 31 + tag * 17 + delta + marked)+ if marked == 0+ then do+ UM.unsafeWrite marks neighbor 1+ scanEdgesSegment+ (edge + 1)+ end+ adjacency+ payload+ marks+ (tailIndex + 1)+ (enqueues + 1)+ (neighbor : queueRev)+ nextDigest+ else+ scanEdgesSegment+ (edge + 1)+ end+ adjacency+ payload+ marks+ tailIndex+ enqueues+ queueRev+ nextDigest++finishDirect ::+ WorklistOutcome ->+ Int ->+ Int ->+ Int ->+ Int ->+ UM.MVector s Int ->+ UM.MVector s Int ->+ MV.MVector s (Int, Int) ->+ UM.MVector s Int ->+ UM.MVector s Int ->+ UM.MVector s Int ->+ UM.MVector s Int ->+ TraversalState ->+ ST s WorklistOutput+finishDirect+ finalOutcome+ opens+ resumes+ updates+ growths+ offsets+ adjacency+ payload+ marks+ state+ queue+ outputLog+ traversal = do+ offsetsVector <- U.unsafeFreeze offsets+ adjacencyVector <- U.unsafeFreeze adjacency+ payloadVector <- V.unsafeFreeze payload+ marksVector <- U.unsafeFreeze marks+ stateVector <- U.unsafeFreeze state+ queueVector <-+ U.unsafeFreeze (UM.unsafeTake (stateTail traversal) queue)+ logVector <-+ U.unsafeFreeze (UM.unsafeTake (stateLogSize traversal) outputLog)+ U.length offsetsVector `seq`+ U.length adjacencyVector `seq`+ V.length payloadVector `seq`+ pure+ ( makeWorklistOutput+ finalOutcome+ opens+ resumes+ updates+ growths+ traversal+ marksVector+ stateVector+ queueVector+ logVector+ )++finishWorklistStore ::+ WorklistOutcome ->+ Int ->+ Int ->+ Int ->+ Int ->+ TraversalState ->+ WorklistStore %1 ->+ Ur WorklistOutput+{-# NOINLINE finishWorklistStore #-}+finishWorklistStore+ finalOutcome+ opens+ resumes+ updates+ growths+ traversal+ ( WorklistStore+ (WorklistFixedRoots offsetsOwner marksOwner stateOwner)+ (WorklistGraphRoots adjacencyOwner payloadOwner)+ (WorklistFrontierRoots queueOwner logOwner)+ ) =+ case Fixed.toVector offsetsOwner of+ Ur offsetsVector ->+ case Fixed.toVector marksOwner of+ Ur marksVector ->+ case Fixed.toVector stateOwner of+ Ur stateVector ->+ case Growable.toVector adjacencyOwner of+ Ur adjacencyVector ->+ case Growable.toVector payloadOwner of+ Ur payloadVector ->+ case Growable.toVector queueOwner of+ Ur queueVector ->+ case Growable.toVector logOwner of+ Ur logVector ->+ U.length offsetsVector `lseq`+ U.length adjacencyVector `lseq`+ V.length payloadVector `lseq`+ Ur+ ( makeWorklistOutput+ finalOutcome+ opens+ resumes+ updates+ growths+ traversal+ marksVector+ stateVector+ ( U.take+ (stateTail traversal)+ queueVector+ )+ ( U.take+ (stateLogSize traversal)+ logVector+ )+ )++makeWorklistOutput ::+ WorklistOutcome ->+ Int ->+ Int ->+ Int ->+ Int ->+ TraversalState ->+ U.Vector Int ->+ U.Vector Int ->+ U.Vector Int ->+ U.Vector Int ->+ WorklistOutput+makeWorklistOutput+ finalOutcome+ opens+ resumes+ updates+ growths+ traversal+ marksVector+ stateVector+ queueVector+ logVector =+ let !digest =+ digestOutput+ (stateDigest traversal)+ marksVector+ stateVector+ queueVector+ logVector+ !visits = stateVisits traversal+ !enqueues = stateEnqueues traversal+ in WorklistOutput+ { summary =+ WorklistSummary+ { outcome = finalOutcome+ , visitedNodes = visits+ , enqueueTransitions = enqueues+ , offsetReads = visits * 2+ , adjacencyReads = visits * worklistDegree+ , payloadReads = visits * worklistDegree+ , markReads = visits * worklistDegree+ , markWrites = enqueues+ , queueReads = visits+ , queueWrites = enqueues+ , logWrites = visits+ , contentOpens = opens+ , resumeBoundaries = resumes+ , headerUpdates = updates+ , bufferGrowths = growths+ , finalDigest = digest+ }+ , finalMarks = marksVector+ , finalState = stateVector+ , finalQueue = queueVector+ , finalLog = logVector+ }++writeTraversalState :: UM.MVector s Int -> TraversalState -> ST s ()+writeTraversalState state traversal = do+ UM.unsafeWrite state 0 (stateHead traversal)+ UM.unsafeWrite state 1 (stateTail traversal)+ UM.unsafeWrite state 2 (stateVisits traversal)+ UM.unsafeWrite state 3 (stateEnqueues traversal)+ UM.unsafeWrite state 4 (stateLogSize traversal)++newUnboxedHeaderFromVector :: U.Vector Int -> ST s (STRef s (UnboxedHeader s))+newUnboxedHeaderFromVector vector = do+ buffer <- U.thaw vector+ newSTRef (UnboxedHeader (U.length vector) buffer)++newBoxedHeaderFromVector :: V.Vector (Int, Int) -> ST s (STRef s (BoxedHeader s))+newBoxedHeaderFromVector vector = do+ buffer <- V.thaw vector+ newSTRef (BoxedHeader (V.length vector) buffer)++newUnboxedHeader :: Int -> U.Vector Int -> ST s (STRef s (UnboxedHeader s))+newUnboxedHeader requested initial = do+ let !capacity = max requested (U.length initial)+ buffer <- UM.new capacity+ U.copy (UM.unsafeTake (U.length initial) buffer) initial+ newSTRef (UnboxedHeader (U.length initial) buffer)++appendUnboxed :: STRef s (UnboxedHeader s) -> U.Vector Int -> ST s Bool+appendUnboxed header values = do+ UnboxedHeader logicalSize buffer <- readSTRef header+ let !required = logicalSize + U.length values+ !oldCapacity = UM.length buffer+ !target = growthTarget oldCapacity required+ (grown, didGrow) <-+ if target <= oldCapacity+ then pure (buffer, False)+ else do+ grown <- UM.new target+ UM.unsafeCopy+ (UM.unsafeTake logicalSize grown)+ (UM.unsafeTake logicalSize buffer)+ pure (grown, True)+ U.copy (UM.unsafeSlice logicalSize (U.length values) grown) values+ writeSTRef header (UnboxedHeader required grown)+ pure didGrow++worklistInitialCapacity :: WorklistGrowth -> Int+worklistInitialCapacity NoGrowth = worklistNodeCount+worklistInitialCapacity NoGrowthBatch64 = worklistNodeCount+worklistInitialCapacity NoGrowthBatch8 = worklistNodeCount+worklistInitialCapacity SparseGrowth = 256+worklistInitialCapacity DenseGrowth = 1++worklistBatchSize :: WorklistGrowth -> Int+worklistBatchSize NoGrowth = 256+worklistBatchSize NoGrowthBatch64 = 64+worklistBatchSize NoGrowthBatch8 = 8+worklistBatchSize SparseGrowth = 64+worklistBatchSize DenseGrowth = 8++growthTarget :: Int -> Int -> Int+growthTarget oldCapacity required+ | required <= oldCapacity = oldCapacity+ | oldCapacity <= 0 = max required 1+ | oldCapacity > maxBound `quot` 2 = required+ | otherwise = max required (oldCapacity * 2)++initialDigest :: Int64+initialDigest = 0x51A7C0DE++initialDigestForSeed :: Int -> Int64+initialDigestForSeed seed =+ initialDigest + fromIntegral seed++digestToLogValue :: Int64 -> Int -> Int+digestToLogValue digest node =+ fromIntegral+ ((digest + fromIntegral (node * 97 + 11)) `rem` 1000000007)++digestOutput ::+ Int64 ->+ U.Vector Int ->+ U.Vector Int ->+ U.Vector Int ->+ U.Vector Int ->+ Int64+digestOutput initial marks state queue outputLog =+ let !marksDigest =+ U.ifoldl'+ (\digest index value -> mixDigest digest (index * 17 + value))+ initial+ marks+ !stateDigest =+ U.ifoldl'+ (\digest index value -> mixDigest digest (index * 19 + value))+ marksDigest+ state+ !queueDigest =+ U.ifoldl'+ (\digest index value -> mixDigest digest (index * 23 + value))+ stateDigest+ queue+ in U.ifoldl'+ (\digest index value -> mixDigest digest (index * 29 + value))+ queueDigest+ outputLog++mixDigest :: Int64 -> Int -> Int64+mixDigest digest value =+ (digest * 6364136223846793005)+ + fromIntegral value+ + 1442695040888963407
pure-borrow.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.4 name: pure-borrow-version: 0.0.0.0+version: 0.1.0.0 synopsis: Rust-style borrowing in Linear Haskell with purity description: This package realizes rust-style borrowing in Linear Haskell with purity and concurrency support.@@ -21,22 +21,37 @@ CHANGELOG.md README.md -data-files:- dockerfiles/artifact/Dockerfile- scripts/genplot.gnuplot--tested-with: ghc ==9.10.2 || ==9.12.4 || ==9.14.1+data-files: scripts/genplot.gnuplot+tested-with: ghc ==9.10.3 || ==9.12.4 || ==9.14.1 source-repository head type: git location: https://github.com/SoftwareFoundationGroupAtKyotoU/pure-borrow -flag artifact- description: Build the artifact runner executable, which runs all the benchmarks and produces CSV files.+flag examples+ description: Build the example executable, which runs some examples of the library. default: False manual: True -common defaults+flag slow+ description:+ Restore the previous, sublifetime-allocating implementations of the+ scalar borrow scopes (`sharing`, `sharing'`, `sharing_`, `reborrowing`,+ `reborrowing'`, `reborrowing_`), of their plural counterparts+ (`reborrowings`, `reborrowings'`, `reborrowings_`), of `srunBO`/`srunBO_`+ and of `copyAtMut`. These build the sublifetime at runtime, with a token+ and a lender, where the default implementations delimit exactly the same+ sublifetime statically. Enable to A/B the two against each other, or to+ check whether a suspected miscompilation or unsoundness is attributable+ to the erased scopes.++ default: False+ manual: True++-- Settings shared by every component, minus @linear-base@.+-- A component that does not use linear types at all imports this instead of+-- @defaults@, so that @-Wunused-packages@ stays clean for it.+common base-defaults default-language: GHC2021 default-extensions: LinearTypes autogen-modules: Paths_pure_borrow@@ -53,19 +68,46 @@ -Wredundant-constraints -Wunused-packages - build-depends:- base >=4.17 && <5,- linear-base >=0.7,+ build-depends: base >=4.17 && <5 +common defaults+ import: base-defaults+ build-depends: linear-base >=0.7++common examples+ import: defaults++ if flag(examples)+ buildable: True+ else+ buildable: False++ ghc-options:+ -threaded+ -rtsopts+ -O2+ "-with-rtsopts=-N -s"++ default-language: GHC2021+ library import: defaults++ if flag(slow)+ cpp-options: -DPURE_BORROW_SLOW_SCOPES build-depends: array,+ atomic-primops, containers, deepseq,+ hashable, hybrid-vectors,+ integer-logarithms, linear-generics,+ primitive,+ random, stm,+ stm-chans, vector, vector-algorithms, @@ -73,8 +115,8 @@ -- cabal-gild: discover src --exclude src/**/Utils.hs --exclude src/**/Utils/**/*.hs exposed-modules: Control.Concurrent.DivideConquer.Linear- Control.Concurrent.STM.TMDeque- Control.Concurrent.STM.TMDequeRingBuffer+ Control.Concurrent.DivideConquer.Linear.Internal+ Control.Concurrent.Queue.ChaseLev Control.Monad.Borrow.Pure Control.Monad.Borrow.Pure.Affine Control.Monad.Borrow.Pure.Affine.Internal@@ -97,19 +139,37 @@ Data.Coerce.Directed.Internal Data.Coerce.Directed.Unsafe Data.Comonad.Linear+ Data.HashMap.RobinHood.Mutable.Linear+ Data.HashMap.RobinHood.Mutable.Linear.Borrow+ Data.HashMap.RobinHood.Mutable.Linear.Borrow.Internal+ Data.HashMap.RobinHood.Mutable.Linear.Internal Data.Record.Linear.Borrow.Experimental.PatternMatch Data.Record.Linear.Borrow.Experimental.Split Data.Ref.Linear Data.Ref.Linear.Borrow Data.Ref.Linear.Unlifted Data.Unique.Linear+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted.Internal+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity.Internal+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted.Internal+ Data.Vector.Mutable.Growable.Linear.Borrow+ Data.Vector.Mutable.Growable.Linear.Borrow.Internal Data.Vector.Mutable.Linear.Borrow+ Data.Vector.Mutable.Linear.Borrow.Internal+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow+ Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow.Internal+ Data.Vector.Unboxed.Mutable.Linear.Borrow+ Data.Vector.Unboxed.Mutable.Linear.Borrow.Internal -- cabal-gild: discover src --include src/**/Utils.hs --include src/**/Utils/**/*.hs other-modules: Control.Concurrent.DivideConquer.Utils.OnceChan.Linear Control.Concurrent.DivideConquer.Utils.OnceChan.Linear.Unlifted Control.Concurrent.DivideConquer.Utils.QueuePool+ Control.Concurrent.DivideConquer.Utils.Semaphore Control.Monad.Borrow.Pure.Utils build-tool-depends: cabal-gild:cabal-gild >=1.6.0.0@@ -121,12 +181,33 @@ type: exitcode-stdio-1.0 hs-source-dirs: test main-is: Main.hs- -- cabal-gild: discover test --exclude=test/Main.hs+ -- cabal-gild: discover test --exclude=test/Main.hs --exclude=test/typing-fail/** other-modules: Control.Concurrent.DivideConquer.LinearSpec+ Control.Monad.Borrow.Pure.BOSpec+ Control.Monad.Borrow.Pure.CopyableSpec+ Control.Monad.Borrow.Pure.Experimental.Borrows.TypingCases+ Control.Monad.Borrow.Pure.Experimental.BorrowsSpec Control.Monad.Borrow.Pure.Lifetime.TypingCases Control.Monad.Borrow.Pure.LifetimeSpec+ Data.HashMap.RobinHood.Mutable.Linear.BorrowSpec+ Data.HashMap.RobinHood.Mutable.LinearSpec+ Data.HashMap.RobinHood.Mutable.LinearSpec.Cases+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted.TypingCases+ Data.Vector.Generic.Mutable.Growable.Linear.Borrow.UnrestrictedSpec+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity.TypingCases+ Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.MultiplicitySpec+ Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted.TypingCases+ Data.Vector.Generic.Mutable.Linear.Borrow.UnrestrictedSpec+ Data.Vector.Mutable.Growable.Linear.BorrowSpec+ Data.Vector.Mutable.Growable.Linear.TypingCases Data.Vector.Mutable.Linear.BorrowSpec+ Data.Vector.Unboxed.Mutable.Growable.Linear.BorrowSpec+ Data.Vector.Unboxed.Mutable.Growable.Linear.TypingCases+ Data.Vector.Unboxed.Mutable.Linear.BorrowSpec+ Data.Vector.Unboxed.Mutable.Linear.TypingCases+ PureBorrow.Internal.Bench.MultiStoreScanSpec+ PureBorrow.Internal.Bench.Worklist.ResumeSpec ghc-options: -O2@@ -137,14 +218,59 @@ tasty-discover:tasty-discover >=5.0.1 build-depends:+ containers, deepseq, falsify,+ hashable, pure-borrow,+ pure-borrow:test-bench-common,+ random, tasty, tasty-expected-failure, tasty-hunit,+ unordered-containers, vector, +-- Keep Core inspections in a separate component: in the larger main test+-- component GHC leaves concrete generic-vector dictionaries unspecialized.+test-suite pure-borrow-inspection+ import: defaults++ -- PureBorrow.Inspection.Sublifetime asserts the Core shape of the statically+ -- erased sublifetime delimiters, which is exactly what @+slow@ swaps out.+ -- PureBorrow.Inspection.Flags reads this define to invert those obligations+ -- there; it is the only module in the component that uses CPP.+ if flag(slow)+ cpp-options: -DPURE_BORROW_SLOW_SCOPES+ default-language: GHC2021+ type: exitcode-stdio-1.0+ hs-source-dirs: test-inspection+ main-is: Main.hs+ -- cabal-gild: discover test-inspection --exclude=test-inspection/Main.hs+ other-modules:+ PureBorrow.Inspection.Fft+ PureBorrow.Inspection.Flags+ PureBorrow.Inspection.GenericGrowableUnrestricted+ PureBorrow.Inspection.MultiStoreScan+ PureBorrow.Inspection.QSort+ PureBorrow.Inspection.Sublifetime+ PureBorrow.Inspection.Worklist.Resume++ -- The -dsuppress-* set that makes a failing obligation readable belongs in+ -- OPTIONS_GHC on the module that states the obligation, not here: `cabal+ -- check` rejects `ghc-options: -d*` in a distributed package.+ ghc-options:+ -O2+ -fplugin=Test.Tasty.Inspection.Plugin++ build-depends:+ pure-borrow,+ pure-borrow:test-bench-common,+ tasty,+ tasty-expected-failure,+ tasty-inspection-testing,+ vector,+ test-suite pure-borrow-doctests import: defaults @@ -167,22 +293,24 @@ doctest-parallel >=0.4.1, pure-borrow, -library qsort-bench-suites+-- Hosts the common cases used in both benchmarks and tests.+library test-bench-common import: defaults- hs-source-dirs: internal-src/qsort-bench-suites- -- cabal-gild: discover internal-src/qsort-bench-suites- exposed-modules: PureBorrow.Internal.Bench.QSort+ hs-source-dirs: internal-src/test-bench-common+ -- cabal-gild: discover internal-src/test-bench-common+ exposed-modules:+ PureBorrow.Internal.Bench.MultiStoreScan+ PureBorrow.Internal.Bench.Unboxed+ PureBorrow.Internal.Bench.Worklist.Resume+ build-depends: base >=4.7 && <5, deepseq,- optparse-applicative, pure-borrow,- random,- tasty, tasty-bench, vector,- vector-algorithms, + ghc-options: -O2 default-language: GHC2021 benchmark qsort-bench@@ -198,101 +326,145 @@ build-depends: base >=4.7 && <5,- pure-borrow:qsort-bench-suites,+ pure-borrow,+ random,+ tasty,+ tasty-bench,+ vector,+ vector-algorithms, default-language: GHC2021 -library qsort-demo-impl+-- Every single-threaded micro-benchmark lives here, discovered by+-- tasty-discover from bench/suite. Kernels shared with pure-borrow-test or+-- pure-borrow-inspection stay in lib:test-bench-common and are re-exported by a+-- thin module under PureBorrow.Bench; kernels used only by this benchmark live+-- in bench/suite directly.+benchmark pure-borrow-bench import: defaults- hs-source-dirs: internal-src/qsort-demo-impl- -- cabal-gild: discover internal-src/qsort-demo-impl- exposed-modules: PureBorrow.Demo.QSort+ type: exitcode-stdio-1.0+ main-is: Main.hs+ hs-source-dirs: bench/suite+ -- cabal-gild: discover bench/suite --exclude=bench/suite/Main.hs+ other-modules:+ PureBorrow.Bench.CopyAt+ PureBorrow.Bench.Growable+ PureBorrow.Bench.HashMap+ PureBorrow.Bench.Ingredients+ PureBorrow.Bench.MultiStoreScan+ PureBorrow.Bench.ScopeDensity+ PureBorrow.Bench.Unboxed+ PureBorrow.Bench.Worklist.Resume++ ghc-options:+ -threaded+ -rtsopts+ -O2+ "-with-rtsopts=-N1 -T"++ build-tool-depends:+ tasty-discover:tasty-discover >=5.0.1+ build-depends: base >=4.7 && <5, deepseq,- optparse-applicative,+ hashable, pure-borrow,- random,+ pure-borrow:test-bench-common,+ tasty,+ tasty-bench,+ unordered-containers, vector,- vector-algorithms, default-language: GHC2021 -executable qsort+benchmark fft-bench import: defaults- main-is: qsort.hs- hs-source-dirs: app+ type: exitcode-stdio-1.0+ main-is: fft.hs+ hs-source-dirs: bench ghc-options: -threaded -rtsopts- -with-rtsopts=-N+ -O2+ "-with-rtsopts=-N -s" build-depends: base >=4.7 && <5,- pure-borrow:qsort-demo-impl,+ pure-borrow,+ random,+ tasty,+ tasty-bench,+ vector, default-language: GHC2021 -executable convert-qsort-bench-csv+library demo-impl import: defaults- main-is: convert-qsort-bench-csv.hs- hs-source-dirs: app- ghc-options:- -threaded- -rtsopts- -with-rtsopts=-N + if flag(examples)+ buildable: True+ else+ buildable: False++ hs-source-dirs: internal-src/demo-impl+ -- cabal-gild: discover internal-src/demo-impl+ exposed-modules:+ PureBorrow.Demo.Fft+ PureBorrow.Demo.QSort+ build-depends: base >=4.7 && <5,- bytestring,- cassava, deepseq,- monoidal-containers,+ directory,+ filepath,+ fmlist, optparse-applicative, pure-borrow,- text,- unordered-containers,+ random, vector, vector-algorithms, default-language: GHC2021 -executable artifact-runner- import: defaults+executable qsort+ import: examples+ main-is: qsort.hs+ hs-source-dirs: app+ build-depends:+ base >=4.7 && <5,+ pure-borrow:demo-impl, - if flag(artifact)- buildable: True- else- buildable: False+executable fft+ import: examples+ main-is: fft.hs+ hs-source-dirs: app+ build-depends:+ base >=4.7 && <5,+ pure-borrow:demo-impl, - main-is: artifact-runner.hs+executable convert-qsort-bench-csv+ import: base-defaults+ main-is: convert-qsort-bench-csv.hs hs-source-dirs: app ghc-options:- -O2 -threaded -rtsopts- "-with-rtsopts=-N -s"+ -with-rtsopts=-N + if flag(examples)+ buildable: True+ else+ buildable: False+ build-depends: base >=4.7 && <5, bytestring, cassava,- containers,- deepseq,- directory,- file-embed, monoidal-containers, optparse-applicative,- process,- pure-borrow,- pure-borrow:qsort-bench-suites,- pure-borrow:qsort-demo-impl,- tasty,- template-haskell,- temporary, text,- transformers,+ unordered-containers, vector,- vector-algorithms, default-language: GHC2021
src/Control/Concurrent/DivideConquer/Linear.hs view
@@ -7,75 +7,142 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE LinearTypes #-} {-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE PartialTypeSignatures #-} {-# LANGUAGE QualifiedDo #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeAbstractions #-} {-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE NoImplicitPrelude #-} {-# OPTIONS_GHC -Wno-name-shadowing #-} {-# OPTIONS_GHC -Wno-partial-type-signatures #-} module Control.Concurrent.DivideConquer.Linear ( divideAndConquer,+ divideAndConquer', DivideConquer (..),+ Result (..),+ Conquer (..), + -- * Alternative naive and sequential implementations+ sequentialDivideAndConquer,+ sequentialDivideAndConquer',+ naiveDivideAndConquer,+ naiveDivideAndConquer',+ -- * Examples+ qsort, qsortDC,+ qsortDC',+ fftDC,+ fftDC',++ -- ** Example internals+ -- $example-internals+ Pair (..),+ FftCoe (..),+ combineLoop, ) where import Control.Applicative qualified as NonLinear-import Control.Concurrent (ThreadId, forkIO, killThread)+import Control.Concurrent (ThreadId, forkIO)+import Control.Concurrent.DivideConquer.Linear.Internal (combineLoop) import Control.Concurrent.DivideConquer.Utils.OnceChan.Linear (Sink, Source) import Control.Concurrent.DivideConquer.Utils.OnceChan.Linear qualified as Once-import Control.Concurrent.DivideConquer.Utils.QueuePool (QueuePool, newQueuePool, popWork, pushWork, pushWorkMaster)+import Control.Concurrent.DivideConquer.Utils.QueuePool (QueuePool, newQueuePool, popWork, pushWorkMaster, pushWorks)+import Control.Concurrent.DivideConquer.Utils.Semaphore (Semaphore)+import Control.Concurrent.DivideConquer.Utils.Semaphore qualified as Semaphore import Control.Functor.Linear qualified as Control import Control.Monad.Borrow.Pure.Affine (Affine, GenericallyAffine (..)) import Control.Monad.Borrow.Pure.BO import Control.Monad.Borrow.Pure.BO.Unsafe-import Control.Monad.Borrow.Pure.Copyable+import Control.Monad.Borrow.Pure.Copyable ()+import Control.Monad.Borrow.Pure.Experimental.Borrows+import Control.Monad.Borrow.Pure.Experimental.Loop (iterReborrowing_)+import Data.Bifunctor.Linear qualified as BiL+import Data.Bits (bit, popCount, shiftR)+import Data.Complex (Complex (..))+import Data.Function (fix) import Data.Functor.Linear qualified as Data import Data.Kind (Type)-import Data.List.Linear qualified as LL-import Data.List.NonEmpty.Linear (NonEmpty (..))-import Data.List.NonEmpty.Linear qualified as NEL import Data.Proxy (Proxy (..)) import Data.V.Linear (V) import Data.V.Linear.Internal (V (..)) import Data.Vector qualified as V+import Data.Vector.Generic qualified as G+import Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted qualified as Vector+import Data.Vector.Internal.Check (HasCallStack) import Data.Vector.Mutable.Linear.Borrow qualified as LV-import GHC.Exts qualified as GHC import GHC.Generics qualified as GHC import GHC.TypeNats (SomeNat (..), someNatVal) import Generics.Linear.TH (deriveGenericAnd1)-import Prelude.Linear+import Math.NumberTheory.Logarithms (intLog2)+import Prelude.Linear hiding (foldMap) import Prelude.Linear.Generically (Generically, Generically1)-import System.IO.Unsafe (unsafePerformIO)+import System.Random (RandomGen) import Unsafe.Linear qualified as Unsafe+import Prelude qualified as P -data DivideConquer α t a = DivideConquer- { divide :: forall β. (α >= β) => Mut β a %1 -> BO β (Result β t a)+{- $example-internals++The worked examples above expose these because they appear in their own+signatures: 'fftDC'' returns a @'DivideConquer' 'FftCoe' α 'Pair' …@, so a caller+cannot so much as write its type without them.+'combineLoop' is the FFT butterfly, exposed so that a benchmark or+inspection test can specialize it at a concrete backend.+-}++data Result c β t a r = Done !r | Continue !(t (Ur c, Mut β a))++data DivideConquer c α t a r = DivideConquer+ { initialise :: forall β. (α >= β) => Mut β a %1 -> BO β (Ur c)+ , divide :: forall β. (α >= β) => c -> Mut β a %1 -> BO β (Result c β t a r)+ , conquer :: Conquer c α t a r } -data Result β t a = Done | Continue (t (Mut β a))+data Conquer c α t a r where+ NoConquer :: Conquer c α t a ()+ Conquer :: (forall β. (α >= β) => c -> Mut β a %1 -> t r %1 -> BO β r) -> Conquer c α t a r -data Work α a (t :: Type -> Type) where- Process :: Mut α a %1 -> Sink () %1 -> Work α a t %1 -> Work α a t- Unite :: t (Source ()) %1 -> Sink () %1 -> Work α a t- Final :: Work α a t+data Switch r a+ = Switch+ {-# UNPACK #-} !(Semaphore a)+ !(Sink r) -newtype Thread = Thread ThreadId+release ::+ r %1 ->+ Switch r a %1 ->+ BO α (Maybe a)+release r (Switch sem dest) = Control.do+ Once.put dest r+ Semaphore.release sem -instance Consumable Thread where- {-# NOINLINE consume #-}- consume = GHC.noinline $ Unsafe.toLinear \(Thread tid) -> unsafePerformIO $ do- killThread tid+newRootSwitch :: BO α (Switch r (BO α ()), Source r)+newRootSwitch = Control.do+ (sink, source) <- asksLinearly Once.new+ sem <- Semaphore.newSemaphore $ Control.pure ()+ Control.pure (Switch sem sink, source) +data Work c α a (t :: Type -> Type) r where+ Process ::+ !c ->+ !(Mut α a) %1 ->+ !(Switch r (BO α ())) %1 ->+ Work c α a t r+ Resume ::+ !(BO α ()) %1 ->+ Work c α a t r++newtype Thread = Thread ThreadId+ newtype DList a = DList ([a] %1 -> [a]) instance Semigroup (DList a) where- DList f <> DList g = DList (f . g)+ DList l <> DList r = DList (l . r) {-# INLINE (<>) #-} instance Monoid (DList a) where@@ -90,88 +157,174 @@ toListD (DList f) = f [] {-# INLINE toListD #-} --- TODO: perhaps we can use atomic counter here again?--data QState α a t- = Idle !(Mut α (QueuePool (Work α a t)))- | DoThen !(Work α a t) !(Mut α (QueuePool (Work α a t)))+newtype QState c α a t r = Idle (Mut α (QueuePool (Work c α a t r))) popQState ::- QState α a t %1 ->- BO α (Maybe (Work α a t, QState α a t))+ QState c α a t r %1 ->+ BO α (Maybe (Work c α a t r, QState c α a t r)) popQState = \case Idle q -> Control.do- m <- popWork q- case m of- Nothing -> Control.pure Nothing- Just (work, q) -> Control.pure (Just (work, Idle q))- DoThen work q -> Control.pure $ Just (work, Idle q)--enqueue :: QState α a t %1 -> Work α a t %1 -> BO α (QState α a t)-enqueue q work = case q of- Idle q -> Idle Control.<$> pushWork q work- DoThen work' q -> error "Could not happen!" work q work'+ Data.fmap (BiL.second Idle) Control.<$> popWork q -doAndEnqueue :: QState α a t %1 -> Work α a t %1 -> Work α a t %1 -> BO α (QState α a t)-doAndEnqueue q work cont = case q of- Idle q -> DoThen work Control.<$> pushWork q cont- DoThen work' q -> error "Could not happen!" work cont work' q+enqueues :: QState c α a t r %1 -> [Work c α a t r] %1 -> BO α (QState c α a t r)+enqueues q work = case q of+ Idle q -> Idle Control.<$> pushWorks q work divideAndConquer ::- forall α β t a.- (Data.Traversable t, Consumable (t ()), α >= β) =>+ forall c α β t a g.+ (Data.Traversable t, α >= β, RandomGen g) =>+ g -> -- | The # of workers. Int ->- DivideConquer α t a ->+ DivideConquer c α t a () -> Mut α a %1 -> BO β (Mut α a)-divideAndConquer n DivideConquer {..} ini+divideAndConquer g n dc = Control.fmap (uncurry lseq) . divideAndConquer' g n dc++divideAndConquer' ::+ forall c α β t a r g.+ (Data.Traversable t, α >= β, RandomGen g) =>+ g ->+ -- | The # of workers.+ Int ->+ DivideConquer c α t a r ->+ Mut α a %1 ->+ BO β (r, Mut α a)+divideAndConquer' g n DivideConquer {..} ini | n == 0 = error ("divideAndConquer: # of workers must be positive, but got: " <> show n) ini | otherwise =- upcast $- uncurry (lseq @()) Control.<$> reborrowing' ini \(ini :: Mut γ a) ->+ upcast @(BO _ (r, Mut _ a)) @(BO β (r, Mut α a)) $+ reborrowing' ini \(ini :: Mut γ a) -> someNatVal (fromIntegral n) & \(SomeNat (_ :: Proxy n)) -> Control.do- (workers, master) <- newQueuePool @n+ (workers, master) <- newQueuePool @n g (masterQ, masterLend) <- asksLinearly $ borrow master- (rootSink, rootSource) <- asksLinearly Once.new+ (switch, rootSource) <- newRootSwitch+ (Ur c, ini) <- initialise <%~ ini - Control.void $ pushWorkMaster masterQ $ Process ini rootSink Final+ Control.void $ pushWorkMaster masterQ $ Process c ini switch concurrentMap_ worker workers- Once.take rootSource+ r <- Once.take rootSource - Control.pure (upcast $ consume Control.<$> reclaim' masterLend)+ Control.pure (upcast $ r Control.<$ reclaim' @γ masterLend) where- worker :: (α >= α') => Mut α' (QueuePool (Work α' a t)) %1 -> BO α' ()- worker q =+ worker :: (α >= α') => Mut α' (QueuePool (Work c α' a t r)) %1 -> BO α' ()+ worker q = Control.do whileJust_ (Idle q) popQState \q -> \case- Final -> Control.pure q- Process ini sink next -> Control.do- q <- enqueue q next- resl <- divide ini- case resl of- Done -> Control.do- Once.put sink ()- Control.pure q- Continue ts -> Control.do- (sources, ks) <-- flip Control.runStateT mempty $ Data.for ts \work -> Control.do- (sink, source) <- Control.lift $ asksLinearly Once.new- Control.modify (<> singletonD (work, sink))- Control.pure source- let %1 !cont = Unite sources sink- case NEL.nonEmpty $ toListD ks of- Nothing -> enqueue q cont- Just ((ini, sink) :| ks) ->- doAndEnqueue- q- (Process ini sink Final)- $ LL.foldr (uncurry Process) cont ks- Unite children sink -> Control.do- Control.void $ Data.traverse Once.take children- Once.put sink ()+ Resume k -> Control.do+ k Control.pure q+ -- NOTE: this leakage should be safe, because the finalization on ini'+ -- will only occur after all the subdivisions are processed.+ Process c ini switch ->+ Unsafe.toLinear (\a -> (a, a)) ini & \(ini, ini') -> Control.do+ resl <- divide c ini+ case resl of+ Done r -> Control.do+ cont <- release r switch+ ini' `lseq` case cont of+ Nothing -> Control.pure q+ Just k -> enqueues q [Resume k]+ Continue ts -> Control.do+ (sources, ks) <- Control.do+ flip Control.runStateT mempty $+ Data.for ts \work ->+ Control.StateT \ks -> Control.do+ (sink, source) <- asksLinearly Once.new+ Control.pure (source, ks <> singletonD (work, sink))+ sem <- Semaphore.newSemaphore Control.do+ case conquer of+ NoConquer -> Control.do+ cont <- release () switch+ maybe (Control.pure ()) id cont+ unsafeLeak sources `lseq` ini' `lseq` Control.pure ()+ Conquer conq -> Control.do+ rs <- Data.traverse Once.take sources+ r <- conq c ini' rs+ cont <- release r switch+ maybe (Control.pure ()) id cont+ (tasks, sem) <- flip Control.runStateT sem $+ Data.for (toListD ks) \((Ur c, work), sink) -> Control.StateT \sem -> Control.do+ (sem, sem') <- Semaphore.retain sem+ Control.pure (Process c work (Switch sem sink), sem')+ cont <- Semaphore.release sem+ case cont of+ Nothing -> enqueues q tasks+ Just k -> Control.do+ unsafeLeak tasks `lseq` enqueues q [Resume k] +sequentialDivideAndConquer ::+ forall c α t a.+ (Data.Traversable t, Consumable (t ())) =>+ DivideConquer c α t a () ->+ Mut α a %1 ->+ BO α (Mut α a)+sequentialDivideAndConquer conq =+ Control.fmap (uncurry lseq) . sequentialDivideAndConquer' conq++sequentialDivideAndConquer' ::+ forall c α t a r.+ (Data.Traversable t, Consumable (t ())) =>+ DivideConquer c α t a r ->+ Mut α a %1 ->+ BO α (r, Mut α a)+sequentialDivideAndConquer' DivideConquer {..} ini = reborrowing ini \ini -> Control.do+ (Ur c, ini) <- initialise <%~ ini+ loop c ini+ where+ loop :: c -> Mut (γ /\ α) a %1 -> BO (γ /\ α) r+ loop c x = Control.do+ (resl, x) <- reborrowing x \x -> Control.do+ resl <- divide c (x)+ case resl of+ Done r -> Control.pure $ Left r+ Continue ts -> Control.do+ rs <- Data.traverse (\(Ur c, t) -> assocRBO $ loop c (assocBorrowL t)) ts+ Control.pure $ Right rs+ case resl of+ Left r -> x `lseq` Control.pure r+ Right rs -> case conquer of+ NoConquer -> Control.pure $ consume (x, rs)+ Conquer conq -> conq c x rs++naiveDivideAndConquer ::+ forall c α t a.+ (Data.Traversable t, Consumable (t ())) =>+ DivideConquer c α t a () ->+ Mut α a %1 ->+ BO α (Mut α a)+naiveDivideAndConquer conq =+ Control.fmap (uncurry lseq) . naiveDivideAndConquer' conq++naiveDivideAndConquer' ::+ forall c α t a r.+ (Data.Traversable t, Consumable (t ())) =>+ DivideConquer c α t a r ->+ Mut α a %1 ->+ BO α (r, Mut α a)+naiveDivideAndConquer' DivideConquer {..} ini = reborrowing ini \ini -> Control.do+ (Ur c, ini) <- initialise <%~ ini+ loop c ini+ where+ loop :: c -> Mut (γ /\ α) a %1 -> BO (γ /\ α) r+ loop c x = Control.do+ (resl, x) <- reborrowing x \x -> Control.do+ resl <- divide c (x)+ case resl of+ Done r -> Control.pure $ Left r+ Continue ts -> Control.do+ rs <- runPar $ Data.traverse (\(Ur c, t) -> Par $ assocRBO $ loop c (assocBorrowL t)) ts+ Control.pure $ Right rs+ case resl of+ Left r -> x `lseq` Control.pure r+ Right rs -> case conquer of+ NoConquer -> Control.pure $ consume (x, rs)+ Conquer conq -> conq c x rs++unsafeLeak :: a %1 -> ()+{-# NOINLINE unsafeLeak #-}+unsafeLeak = Unsafe.toLinear \ !_ -> ()+ concurrentMap_ :: forall n a α. (a %1 -> BO α ()) ->@@ -234,33 +387,368 @@ traverse = Data.genericTraverse {-# INLINE traverse #-} +data FftCoe = FftCoe+ { cosθ :: {-# UNPACK #-} !Double+ , sinθ :: {-# UNPACK #-} !Double+ , size :: {-# UNPACK #-} !Int+ }+ deriving (Show)++{- | Sort a vector, optionally using nested parallelism.++A zero budget is sequential. At every recursive split a positive budget is+halved, bounding the depth at which 'parBO' is used.+-}+qsort ::+ forall v a α β.+ (G.Vector v a, Ord a, α >= β) =>+ Word ->+ Mut α (Vector.Vector v a) %1 ->+ BO β ()+{-# INLINEABLE qsort #-}+qsort = go+ where+ go ::+ Word ->+ Mut α (Vector.Vector v a) %1 ->+ BO β ()+ go budget vector =+ case Vector.size vector of+ (Ur 0, vector) -> Control.pure (consume vector)+ (Ur 1, vector) -> Control.pure (consume vector)+ (Ur length_, vector) -> Control.do+ let pivotIndex = length_ `quot` 2+ (Ur pivot, vector) <-+ Vector.unsafeGet pivotIndex vector+ (lower, upper) <-+ partitionVector pivot vector 0 length_+ let nextBudget = budget `quot` 2+ Control.void $+ parIf+ (nextBudget P.> 0)+ (go nextBudget lower)+ (go nextBudget upper)++partitionVector ::+ (G.Vector v a, Ord a, α >= β) =>+ a ->+ Mut α (Vector.Vector v a) %1 ->+ Int ->+ Int ->+ BO+ β+ ( Mut α (Vector.Vector v a)+ , Mut α (Vector.Vector v a)+ )+{-# INLINEABLE partitionVector #-}+partitionVector pivot = partitionUp+ where+ partitionUp vector lower upper+ | lower < upper = Control.do+ (Ur element, vector) <-+ Vector.unsafeGet lower vector+ if element < pivot+ then partitionUp vector (lower + 1) upper+ else partitionDown vector lower (upper - 1)+ | otherwise =+ Control.pure (Vector.splitAt lower vector)++ partitionDown vector lower upper+ | lower < upper = Control.do+ (Ur element, vector) <-+ Vector.unsafeGet upper vector+ if pivot < element+ then partitionDown vector lower (upper - 1)+ else Control.do+ vector <-+ Vector.unsafeSwap vector lower upper+ partitionUp vector (lower + 1) upper+ | otherwise =+ Control.pure (Vector.splitAt lower vector)++parIf :: Bool %1 -> BO α a %1 -> BO α b %1 -> BO α (a, b)+{-# INLINE parIf #-}+parIf condition =+ if condition+ then parBO+ else Control.liftA2 (,)++{- | Sort a vector with the work-sharing scheduler.++The worker count must be positive. Subvectors no longer than the threshold are+sorted sequentially.+-} qsortDC ::- (Ord a, Copyable a, α >= β) =>- -- | The # of workers.+ (G.Vector v a, Ord a, α >= β, RandomGen g) =>+ g -> Int ->- -- | Threshold for the length of vector to switch to sequential sort. Int ->- Mut α (LV.Vector a) %1 ->- BO β (Mut α (LV.Vector a))-qsortDC nwork thresh = divideAndConquer nwork (qsortDC' thresh)+ Mut α (Vector.Vector v a) %1 ->+ BO β (Mut α (Vector.Vector v a))+{-# INLINE qsortDC #-}+qsortDC generator workers threshold =+ divideAndConquer+ generator+ workers+ (qsortDC' threshold) +-- | Construct a quicksort workload with the given sequential cutoff. qsortDC' ::- (Ord a, Copyable a) =>- -- | Threshold for the length of vector to switch to sequential sort.+ (G.Vector v a, Ord a) => Int ->- DivideConquer α Pair (LV.Vector a)-qsortDC' thresh = DivideConquer- { divide = \vs ->- case LV.size vs of- (Ur n, v)- | n <= 1 ->- v `lseq` Control.pure Done- | n <= thresh ->- Done Control.<$ LV.qsort 0 v+ ()+ α+ Pair+ (Vector.Vector v a)+ ()+{-# INLINEABLE qsortDC' #-}+qsortDC' threshold =+ DivideConquer+ { initialise = Control.pure . move . consume+ , divide = \_ vector ->+ case Vector.size vector of+ (Ur length_, vector)+ | length_ <= 1 ->+ vector `lseq` Control.pure (Done ())+ | length_ <= threshold -> Control.do+ !() <- qsort 0 vector+ Control.pure (Done ()) | otherwise -> Control.do- let i = n `quot` 2- (Ur pivot, v) <- LV.copyAtMut i v- (lo, hi) <- LV.divide pivot v 0 n- Control.pure $ Continue $ Pair lo hi+ let pivotIndex = length_ `quot` 2+ (Ur pivot, vector) <-+ Vector.unsafeGet pivotIndex vector+ (lower, upper) <-+ partitionVector pivot vector 0 length_+ Control.pure $+ Continue $+ Pair+ (Ur (), lower)+ (Ur (), upper)+ , conquer = NoConquer }++{- | Transform a power-of-two vector with the work-sharing scheduler.++The worker count must be positive. The vector length is checked here and must+be a power of two. Subvectors no longer than the threshold are transformed+sequentially.+-}+fftDC ::+ ( G.Vector v (Complex Double)+ , α >= β+ , RandomGen g+ , HasCallStack+ ) =>+ g ->+ Int ->+ Int ->+ Mut α (Vector.Vector v (Complex Double)) %1 ->+ BO β (Mut α (Vector.Vector v (Complex Double)))+{-# INLINE fftDC #-}+fftDC generator workers threshold vector =+ case Vector.size vector of+ (Ur length_, vector)+ | popCount length_ /= 1 ->+ vector `lseq`+ error+ ( "fftDC: the length "+ <> show length_+ <> " of vector must be a power of 2"+ )+ | otherwise ->+ divideAndConquer+ generator+ workers+ (fftDC' threshold)+ vector++{- | Construct an FFT workload with the given sequential cutoff.++This lower-level constructor does not validate the input length. Every vector+run with the returned workload must have power-of-two length; use 'fftDC' when+that check should be performed by the API.+-}+fftDC' ::+ forall v α.+ (G.Vector v (Complex Double)) =>+ Int ->+ DivideConquer+ FftCoe+ α+ Pair+ (Vector.Vector v (Complex Double))+ ()+{-# INLINEABLE fftDC' #-}+fftDC' threshold =+ DivideConquer+ { initialise = \array ->+ case Vector.size array of+ (Ur length_, array) -> Control.do+ Control.void (reverseBit array)+ Control.pure $+ Ur+ FftCoe+ { cosθ =+ cos+ (2 * pi / fromIntegral length_)+ , sinθ =+ sin+ (2 * pi / fromIntegral length_)+ , size = length_+ }+ , divide = \coefficient@FftCoe {..} vector ->+ if+ | size <= 1 ->+ vector `lseq` Control.pure (Done ())+ | size <= threshold ->+ Done ()+ Control.<$ sequential coefficient vector+ | otherwise -> Control.do+ (Ur nextCoefficient, lower, upper) <-+ step coefficient vector+ Control.pure $+ Continue $+ Pair+ (Ur nextCoefficient, lower)+ (Ur nextCoefficient, upper)+ , conquer =+ Conquer \coefficient vector results ->+ results `lseq` combine coefficient vector+ }+ where+ step ::+ FftCoe ->+ Mut β (Vector.Vector v (Complex Double)) %1 ->+ BO+ β+ ( Ur FftCoe+ , Mut β (Vector.Vector v (Complex Double))+ , Mut β (Vector.Vector v (Complex Double))+ )+ step FftCoe {..} vector = Control.do+ let !half = size `quot` 2+ !doubleCosine =+ 2 * cosθ * cosθ - 1+ !doubleSine =+ 2 * sinθ * cosθ+ !nextCoefficient =+ FftCoe+ { cosθ = doubleCosine+ , sinθ = doubleSine+ , size = half+ }+ %1 !(lower, upper) =+ Vector.splitAt half vector+ Control.pure+ (Ur nextCoefficient, lower, upper)++ sequential ::+ FftCoe ->+ Mut β (Vector.Vector v (Complex Double)) %1 ->+ BO β ()+ sequential coefficient vector =+ case Vector.size vector of+ (Ur length_, vector)+ | length_ <= 1 ->+ Control.pure (consume vector)+ | otherwise -> Control.do+ vector <-+ reborrowing_ vector \shorter -> Control.do+ (Ur nextCoefficient, lower, upper) <-+ step coefficient shorter+ sequential nextCoefficient lower+ sequential nextCoefficient upper+ combine coefficient vector++ combine ::+ FftCoe ->+ Mut β (Vector.Vector v (Complex Double)) %1 ->+ BO β ()+ combine FftCoe {..} vector = Control.do+ let !half = size `quot` 2+ !root = cosθ :+ sinθ+ combineLoop half root 0 1 vector++reverseBit ::+ forall v a α.+ (G.Vector v a) =>+ Mut α (Vector.Vector v a) %1 ->+ BO α ()+{-# INLINEABLE reverseBit #-}+reverseBit vector =+ Vector.size vector+ & \(Ur length_, vector) -> Control.do+ let !bits = intLog2 length_+ !middle = bit (bits `shiftR` 1)+ consume+ Control.<$> reborrowing' vector \shorter -> Control.do+ (table, lend) <-+ borrowLinearlyM (LV.constant middle 0)+ table <- buildTable bits <%= table+ Control.void $+ iterReborrowing_+ (middle - 1)+ (table :- shorter :- BNil)+ \((+ 1) -> !first) (table :- current :- BNil) -> Control.do+ (Ur firstOffset, table) <-+ LV.copyAtMut first table+ Control.void $+ iterReborrowing_+ first+ (table :- current :- BNil)+ \second (table :- current :- BNil) -> Control.do+ (Ur secondOffset, table) <-+ LV.copyAtMut second table+ let !forward =+ second + firstOffset+ !backward =+ first + secondOffset+ current <-+ Vector.unsafeSwap+ current+ forward+ backward+ if even bits+ then+ Control.pure $+ current `lseq`+ consume table+ else+ consume . (,table)+ Control.<$> Vector.unsafeSwap+ current+ (forward + middle)+ (backward + middle)++ Control.pure $+ upcast @_ @(After _ ()) $+ consume . LV.toList+ Control.<$> reclaim' lend+ where+ buildTable ::+ Int ->+ Mut β (LV.Vector Int) %1 ->+ BO β ()+ buildTable bits table =+ fix+ ( \loop !high !low table ->+ if low + 1 >= high+ then Control.pure (consume table)+ else Control.do+ let !highBit = bit (high - 1)+ !lowBit = bit low+ table <-+ iterReborrowing_ lowBit table \index table -> Control.do+ (Ur value, table) <-+ LV.copyAtMut index table+ consume+ Control.<$> LV.set+ (lowBit + index)+ (value + highBit)+ table+ loop (high - 1) (low + 1) table+ )+ bits+ 0+ table
+ src/Control/Concurrent/DivideConquer/Linear/Internal.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE LinearTypes #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -fno-spec-constr #-}+{-# OPTIONS_HADDOCK hide #-}++{- |+The FFT butterfly, kept in its own module solely so that+@-fno-spec-constr@ can apply to it and nothing else.++SpecConstr on this loop defeats the specialization that+@pure-borrow-inspection@ requires: at @-O2@ the @combine loop has no+type-class dictionaries@ assertion fails without the flag. Setting the flag+on "Control.Concurrent.DivideConquer.Linear" instead would also disable+SpecConstr for the scheduler, the Chase-Lev queue and the worker loop, which+measures up to 32% slower on @fft.worksteal@. The flag is module-scoped and+has no per-binding form, so a one-binding module is the only way to scope it+correctly.+-}+module Control.Concurrent.DivideConquer.Linear.Internal (+ combineLoop,+) where++import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.BO+import Data.Complex (Complex)+import Data.Vector.Generic qualified as G+import Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted qualified as Vector+import Prelude.Linear+import Prelude qualified as P++combineLoop ::+ (G.Vector v (Complex Double)) =>+ Int ->+ Complex Double ->+ Int ->+ Complex Double ->+ Mut α (Vector.Vector v (Complex Double)) %1 ->+ BO α ()+{-# INLINEABLE combineLoop #-}+combineLoop !half !root !index !weight vector+ | index >= half =+ Control.pure (consume vector)+ | otherwise = Control.do+ (Ur evenValue, vector) <-+ Vector.unsafeGet index vector+ (Ur oddValue, vector) <-+ Vector.unsafeGet (half + index) vector+ let !weightedOdd = weight P.* oddValue+ vector <-+ Vector.unsafeWrite+ index+ (evenValue P.+ weightedOdd)+ vector+ vector <-+ Vector.unsafeWrite+ (half + index)+ (evenValue P.- weightedOdd)+ vector+ combineLoop+ half+ root+ (index + 1)+ (weight P.* root)+ vector
src/Control/Concurrent/DivideConquer/Utils/QueuePool.hs view
@@ -3,6 +3,7 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DerivingVia #-}+{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE ImpredicativeTypes #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE LinearTypes #-}@@ -16,141 +17,220 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UnboxedTuples #-} {-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE NoFieldSelectors #-} {-# LANGUAGE NoImplicitPrelude #-} {-# OPTIONS_GHC -Wno-name-shadowing #-} {-# OPTIONS_GHC -Wno-partial-type-signatures #-} +{- | Implements a hybrid scheduler of work-stealing and work-sharing.+Each thread has its own local queue and steals from others when it's idle, but sleeps and waits for works to be pushed after several tries.+When pushing works, it shares a batch with a waiter (if any) to avoid starvation.+-} module Control.Concurrent.DivideConquer.Utils.QueuePool ( QueuePool, newQueuePool,- pushWork, pushWorks, popWork, pushWorkMaster, ) where -import Control.Applicative (Alternative (..)) import Control.Applicative qualified as P-import Control.Concurrent (yield)-import Control.Concurrent.STM (STM, atomically, retry)-import Control.Concurrent.STM.TMDeque (TMDeque, closeTMDeque, isClosedTMDeque, newTMDequeIO, pushFrontTMDeque, sizeTMDeque, tryPopBackTMDeque, tryPopFrontTMDeque)+import Control.Concurrent (MVar, newEmptyMVar, putMVar, takeMVar, threadDelay, tryPutMVar, yield)+import Control.Concurrent.Queue.ChaseLev (ChaseLevDeq, StealResult (..), close, estimateSize, isClosed, newDeq, pushFront, pushFronts, stealHalf, tryPopFront)+import Control.Concurrent.STM (atomically)+import Control.Concurrent.STM.TMQueue (TMQueue, closeTMQueue, isClosedTMQueue, newTMQueueIO, readTMQueue, tryReadTMQueue, unGetTMQueue, writeTMQueue)+import Control.Monad (forM_) import Control.Monad qualified as NonLinear import Control.Monad qualified as P import Control.Monad.Borrow.Pure.BO import Control.Monad.Borrow.Pure.BO.Unsafe (Alias (..), unsafeSystemIOToBO)-import Data.Coerce (coerce)-import Data.Foldable qualified as P import Data.Function (fix)+import Data.IORef (IORef, newIORef, readIORef, writeIORef) import Data.List qualified as L-import Data.Monoid (Alt (..))-import Data.Ord (Down (..))-import Data.Ord qualified as P+import Data.List.NonEmpty (NonEmpty ((:|))) import Data.V.Linear (V, theLength) import Data.V.Linear.Internal (V (..)) import Data.Vector qualified as V-import Data.Vector.Algorithms.Intro qualified as AI-import Data.Vector.Hybrid.Mutable qualified as HMV-import Data.Vector.Mutable (RealWorld)+-- import Debug.Trace (traceEventIO) import GHC.Exts qualified as GHC import GHC.IO qualified as GHC import GHC.TypeLits (KnownNat) import Prelude.Linear+import System.Random.Stateful (Random (randoms), RandomGen, StdGen, mkStdGen, randomR) import Unsafe.Linear qualified as Unsafe import Prelude qualified as P data QueuePool a = QueuePool- { mine :: !(TMDeque a)- , others :: !(V.MVector RealWorld (TMDeque a))- , num :: !Int+ { mine :: !(ChaseLevDeq a)+ , others :: !(V.Vector (ChaseLevDeq a))+ , mySwitch :: {-# UNPACK #-} !(MVar ())+ , waiting :: {-# UNPACK #-} !(TMQueue (MVar ()))+ , injection :: !(TMQueue (NonEmpty a))+ , num :: {-# UNPACK #-} !Int+ , gen :: {-# UNPACK #-} !(IORef StdGen) } -newtype MasterQueuePool a = MasterQueuePool [TMDeque a]+data MasterQueuePool a = MasterQueuePool+ { pools :: ![ChaseLevDeq a]+ , switches :: ![MVar ()]+ , waiting :: !(TMQueue (MVar ()))+ , injection :: !(TMQueue (NonEmpty a))+ } instance Consumable (MasterQueuePool a) where- consume = consume . map consumeTMDQ . Unsafe.coerce @_ @[TMDeque a]--consumeTMDQ :: TMDeque a %1 -> ()-{-# NOINLINE consumeTMDQ #-}-consumeTMDQ = GHC.noinline $ Unsafe.toLinear \q -> GHC.unsafePerformIO do- !() <- atomically $ closeTMDeque q- P.pure ()+ {-# NOINLINE consume #-}+ consume = GHC.noinline $ Unsafe.toLinear \MasterQueuePool {..} -> GHC.unsafePerformIO do+ P.mapM_ close pools+ P.mapM_ (flip tryPutMVar ()) switches+ atomically do+ closeTMQueue waiting+ closeTMQueue injection newQueuePool ::- forall n a α.- (KnownNat n) =>+ forall n a α g.+ (KnownNat n, RandomGen g) =>+ g -> BO α (V n (Mut α (QueuePool a)), MasterQueuePool a)-newQueuePool = unsafeSystemIOToBO do+newQueuePool g = unsafeSystemIOToBO do let n = theLength @n - qs <- NonLinear.replicateM n newTMDequeIO- pools <-+ qs <- NonLinear.replicateM n newDeq+ waiting <- newTMQueueIO+ injection <- newTMQueueIO+ qs <- P.mapM- ( \(num, ini, mine, tl) -> do- others <- V.unsafeThaw $ V.fromList $ tl <> ini+ ( \(num, ini, mine, tl, seed) -> do+ let others = V.fromList $ tl <> ini+ gen <- newIORef $ mkStdGen seed+ mySwitch <- newEmptyMVar P.pure P.$ QueuePool {others, ..} )- P.$ L.zip4+ P.$ L.zip5 [0 ..] (L.inits qs) qs (P.drop 1 $ L.tails qs)- let master = MasterQueuePool $ P.map (mine P.. coerce) pools- P.pure (V $ V.fromList $ map UnsafeAlias pools, master)+ (randoms g)+ let pools = P.map (.mine) qs+ switches = P.map (.mySwitch) qs+ master = MasterQueuePool {..}+ P.pure (V $ V.fromList $ map UnsafeAlias qs, master) pushWorkMaster :: Mut α (MasterQueuePool a) %1 -> a %1 -> BO α (Mut α (MasterQueuePool a))-pushWorkMaster = Unsafe.toLinear2 \(UnsafeAlias (MasterQueuePool pools)) work ->+pushWorkMaster = Unsafe.toLinear2 \pool@(UnsafeAlias (MasterQueuePool {pools})) work -> case pools of- (q : qs) -> unsafeSystemIOToBO do- atomically $ pushFrontTMDeque q work- P.pure $ UnsafeAlias $ MasterQueuePool (q : qs)+ (q : _) -> unsafeSystemIOToBO do+ pushFront q work+ P.pure pool [] -> error "impossible: the length of pools is determined by the type-level nat n and cannot be zero" -pushWork :: Mut α (QueuePool a) %1 -> a %1 -> BO α (Mut α (QueuePool a))-pushWork = Unsafe.toLinear2 \(UnsafeAlias QueuePool {..}) work ->- unsafeSystemIOToBO do- atomically $ pushFrontTMDeque mine work- P.pure $ UnsafeAlias QueuePool {..}--newtype Backwards f a = Backwards {runBackwards :: f a}- deriving newtype (P.Functor)--instance (P.Applicative f) => P.Applicative (Backwards f) where- pure = Backwards P.. P.pure- Backwards f <*> Backwards x = Backwards (x P.<**> f)---- | Pushes works, the first element is on top.+-- | Pushes works, the last element is on the front. pushWorks :: Mut α (QueuePool a) %1 -> [a] %1 -> BO α (Mut α (QueuePool a))-pushWorks = Unsafe.toLinear2 \(UnsafeAlias QueuePool {..}) work ->+pushWorks = Unsafe.toLinear2 \(UnsafeAlias QueuePool {..}) works -> unsafeSystemIOToBO do- atomically $ runBackwards P.$ P.traverse_ (Backwards P.. pushFrontTMDeque mine) work+ pushFronts mine works+ -- If a worker is asleep, give it a batch on every publication. The batch+ -- must be visible before the wakeup, so the signalled worker can never wake+ -- and wait for a not-yet-published injection.+ token <- atomically do+ P.join P.<$> tryReadTMQueue waiting+ forM_ token \token -> fix \self -> do+ half <- stealHalf mine+ case half of+ Nothing -> do+ putMVar token ()+ Just (Found ts) -> do+ atomically $ writeTMQueue injection ts+ putMVar token ()+ Just Race -> do+ yield+ self+ Just Empty -> do+ atomically $ unGetTMQueue waiting token+ P.pure $ UnsafeAlias QueuePool {..} popWork :: Mut α (QueuePool a) %1 -> BO α (Maybe (a, Mut α (QueuePool a))) popWork = Unsafe.toLinear \qs@(UnsafeAlias QueuePool {..}) -> unsafeSystemIOToBO do- atomically (tryPopFrontTMDeque mine) P.>>= \case- Nothing -> P.pure Nothing- Just (Just x) -> P.pure $ Just (x, qs)- Just Nothing -> fix \self -> do- !ranks <-- V.unsafeThaw- P.=<< atomically P.. (\x -> do xs <- V.mapM sizeTMDeque x; xs P.<$ P.unless (V.any (P.> 0) xs) retry)- P.=<< V.unsafeFreeze others- let ranked = HMV.unsafeZip ranks others- !() <- AI.sortBy (P.comparing P.$ Down P.. P.fst) ranked- others' <- V.unsafeFreeze others-- progress <-- atomically do- ( isClosedTMDeque mine P.>>= \closed ->- if closed then P.pure Nothing else retry- )- <|> getAlt (P.foldMap' (Alt P.. (P.fmap Just P.. fromJustSTM P.<=< tryPopBackTMDeque)) P.$ others')- <|> P.pure (Just Nothing)- case progress of- Nothing -> P.pure Nothing- Just Nothing -> yield P.*> self- Just (Just x) -> P.pure $ Just (x, qs)--fromJustSTM :: Maybe (Maybe a) -> STM (Maybe a)-fromJustSTM = P.maybe (P.pure Nothing) $ P.maybe retry (P.pure P.. Just)+ -- num <- estimateSize mine+ -- traceEventIO $ "EVT: Estimated size: " <> show num+ tryPopFront mine P.>>= \case+ Nothing -> do+ -- traceEventIO "EVT: Finished!"+ P.pure Nothing+ Just (Just x) -> do+ -- traceEventIO "EVT: Got work from own queue!"+ P.pure $ Just (x, qs)+ Just Nothing ->+ (0 :: Int) & fix \self !retry ->+ if retry >= V.length others + 32+ then do+ -- traceEventIO "EVT: Too many retries. Sleep until pushed..."+ closed <- atomically $ do+ writeTMQueue waiting mySwitch+ isClosedTMQueue waiting+ if closed+ then P.pure Nothing+ else do+ takeMVar mySwitch+ -- traceEventIO $ "EVT: Woken up! Retrying to pop..."+ mtasks <- atomically $ readTMQueue injection+ -- traceEventIO $ "EVT: Got " <> show (P.fmap P.length mtasks) <> " injected tasks!"+ case mtasks of+ Nothing -> P.pure Nothing+ Just (x :| xs) -> do+ pushFronts mine xs+ P.pure $ Just (x, qs)+ else do+ let sleep = P.unless (retry < V.length others) do+ g <- readIORef gen+ let !wait = min 100 (1.5 ^ retry :: Double)+ (!q, g') = randomR (1, floor wait) g+ writeIORef gen g'+ -- traceEventIO $ "EVT: Failed to steal. Waiting for " <> show q <> " us..."+ if q > 10 then threadDelay q else yield+ cls <- isClosed mine+ if cls+ then do+ P.pure Nothing+ else do+ let !nOthers = V.length others+ if+ | V.null others -> P.pure Nothing+ | nOthers == 1 -> do+ let !q = V.unsafeHead others+ progress <- stealHalf q+ case progress of+ Nothing -> do+ -- traceEventIO "EVT: Closing..."+ P.pure Nothing+ Just (Found (x :| xs)) -> do+ -- traceEventIO $ "EVT: Stolen! " <> P.show (P.length xs P.+ 1)+ pushFronts mine xs+ P.pure $ Just (x, qs)+ Just Empty -> self (retry + 1)+ Just Race -> sleep P.*> self (retry + 1)+ | otherwise -> do+ g0 <- readIORef gen+ let (!i, !g1) = randomR (0, nOthers - 1) g0+ (!j0, !g2) = randomR (0, nOthers - 2) g1+ !j = if j0 P.== i then nOthers - 1 else j0+ writeIORef gen g2+ let !q1 = V.unsafeIndex others i+ !q2 = V.unsafeIndex others j+ !s1 <- estimateSize q1+ !s2 <- estimateSize q2+ -- traceEventIO $ "EVT: Steal candidates' sizes: " <> show (s1, s2)+ let !targ = if s1 P.>= s2 then q1 else q2+ progress <- stealHalf targ+ case progress of+ Nothing -> do+ -- traceEventIO "EVT: Closing..."+ P.pure Nothing+ Just (Found (x :| xs)) -> do+ -- traceEventIO $ "EVT: Stolen! " <> P.show (P.length xs P.+ 1)+ pushFronts mine xs+ P.pure $ Just (x, qs)+ Just Empty -> self (retry + 1)+ Just Race -> sleep P.*> self (retry + 1)
+ src/Control/Concurrent/DivideConquer/Utils/Semaphore.hs view
@@ -0,0 +1,58 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE LinearTypes #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE OverloadedLabels #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UnboxedTuples #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}+{-# OPTIONS_GHC -Wno-partial-type-signatures #-}++module Control.Concurrent.DivideConquer.Utils.Semaphore (+ Semaphore,+ newSemaphore,+ release,+ retain,+) where++import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure+import Control.Monad.Borrow.Pure.BO.Unsafe (unsafeSystemIOToBO)+import Control.Monad.Primitive (RealWorld)+import Data.Functor qualified as P+import Data.Primitive.PrimVar (PrimVar)+import Data.Primitive.PrimVar qualified as PVar+import Prelude.Linear+import Unsafe.Linear qualified as Unsafe+import Prelude qualified as P++data Semaphore a = Semaphore !a {-# UNPACK #-} !(PrimVar RealWorld Int)++-- | Create a semaphore with initial capacity 1.+newSemaphore :: a %1 -> BO α (Semaphore a)+newSemaphore a = Semaphore a Control.<$> unsafeSystemIOToBO (PVar.newPrimVar 1)++-- | Returns 'Just' and original resource if the capacity becomes @0@, otherwise returns 'Nothing'.+release :: Semaphore a %1 -> BO α (Maybe a)+release = Unsafe.toLinear \(Semaphore a var) -> unsafeSystemIOToBO do+ i <- PVar.fetchSubInt var 1+ if i P.== 1+ then P.pure (Just a)+ else P.pure Nothing++retain :: Semaphore a %1 -> BO α (Semaphore a, Semaphore a)+retain = Unsafe.toLinear \sem@(Semaphore _ var) -> unsafeSystemIOToBO do+ P.void $ PVar.fetchAddInt var 1+ P.pure (sem, sem)
+ src/Control/Concurrent/Queue/ChaseLev.hs view
@@ -0,0 +1,226 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE DisambiguateRecordFields #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE NoFieldSelectors #-}++module Control.Concurrent.Queue.ChaseLev (+ ChaseLevDeq,+ newDeq,+ pushFront,+ pushFronts,+ tryPopBack,+ stealHalf,+ StealResult (..),+ tryPopFront,+ estimateSize,+ capacity,+ close,+ isClosed,+) where++import Control.Monad (forM_, unless, (<$!>))+import Data.Atomics (loadLoadBarrier, storeLoadBarrier, writeBarrier)+import Data.Bits ((.&.))+import Data.IORef (IORef, newIORef, readIORef, writeIORef)+import Data.List.NonEmpty (NonEmpty)+import Data.List.NonEmpty qualified as NE+import Data.Primitive.Array (MutableArray)+import Data.Primitive.Array qualified as Array+import Data.Primitive.PrimVar (PrimVar, casInt, newPrimVar, readPrimVar, writePrimVar)+import Data.Vector qualified as V+import GHC.Exts (RealWorld)+import Math.NumberTheory.Logarithms (intLog2')++data ChaseLevDeq a = CL+ { top :: {-# UNPACK #-} !(PrimVar RealWorld Int)+ , activeArray :: !(IORef (MutableArray RealWorld a))+ , bottom :: {-# UNPACK #-} !(PrimVar RealWorld Int)+ , closed :: {-# UNPACK #-} !(IORef Bool)+ , estimatedSize :: {-# UNPACK #-} !(PrimVar RealWorld Int)+ }++data Stat = Stat {top, bottom :: !Int}++newDeq :: IO (ChaseLevDeq a)+newDeq = do+ !top <- newPrimVar 0+ !activeArray <- newIORef =<< Array.newArray 32 undefined+ !closed <- newIORef False+ !bottom <- newPrimVar 0+ !estimatedSize <- newPrimVar 0+ pure CL {..}++getStat :: ChaseLevDeq a -> IO Stat+{-# INLINE getStat #-}+getStat dq = Stat <$> readPrimVar dq.top <*> readPrimVar dq.bottom++{-# INLINE capacity #-}+capacity :: ChaseLevDeq a -> IO Int+capacity = fmap Array.sizeofMutableArray . readIORef . (.activeArray)++occupancy :: Stat -> Int+{-# INLINE occupancy #-}+occupancy = (-) <$> (.bottom) <*> (.top)++pushFront :: ChaseLevDeq a -> a -> IO ()+pushFront q = pushFronts q . (: [])++-- | Puts the last element on the front.+pushFronts :: ChaseLevDeq a -> [a] -> IO ()+pushFronts _ [] = pure ()+pushFronts q !a = do+ let !n = length a+ closed <- readIORef q.closed+ unless closed do+ !capa <- capacity q+ !stat <- getStat q+ let !size = occupancy stat+ arr <-+ if size + n >= capa - 1+ then do+ let !start = stat.top .&. (capa - 1)+ !end = stat.bottom .&. (capa - 1)+ !newCapa = (2 * capa) `max` (2 ^ (intLog2' (size + n) + 1))+ oldArr <- readIORef q.activeArray+ newArr <- Array.newArray newCapa undefined+ if end >= start+ then do+ Array.copyMutableArray newArr start oldArr start size+ else do+ let !lhSize = capa - start+ !rhSize = size - lhSize+ Array.copyMutableArray newArr start oldArr start lhSize+ Array.copyMutableArray newArr (start + lhSize) oldArr 0 rhSize+ writeIORef q.activeArray newArr+ pure newArr+ else readIORef q.activeArray++ let !curCapa = Array.sizeofMutableArray arr+ forM_ (zip [0 ..] a) $ \(!i, !x) ->+ Array.writeArray arr ((stat.bottom + i) .&. (curCapa - 1)) x+ writeBarrier+ writePrimVar q.bottom $! stat.bottom + n+ writePrimVar q.estimatedSize $! size + n++{- |+ * @Nothing@ — closed (end-of-stream)+ * @Just Nothing@ — open and empty (would block)+ * @Just (Just a)@ — got an element+-}+tryPopFront :: ChaseLevDeq a -> IO (Maybe (Maybe a))+tryPopFront q = do+ !b <- subtract 1 <$> readPrimVar q.bottom+ writePrimVar q.bottom b+ storeLoadBarrier+ !t <- readPrimVar q.top++ !arr <- readIORef q.activeArray+ let !capa = Array.sizeofMutableArray arr++ -- NOTE: Do not force, otherwise undefined will hit+ task <- Array.readArray arr (b .&. (capa - 1))+ if+ | b == t -> do+ -- last one element - might be stolen!+ let !t' = t + 1+ !old <- casInt q.top t t'+ let !success = old == t+ writePrimVar q.bottom t'+ writePrimVar q.estimatedSize 0+ if success+ then pure $ Just $ Just task+ else do+ closed <- readIORef q.closed+ if closed+ then pure Nothing+ else pure $ Just Nothing+ | b > t -> do+ writePrimVar q.estimatedSize $! b - t+ pure $ Just $ Just task+ | otherwise -> do+ writePrimVar q.bottom t+ writePrimVar q.estimatedSize 0+ pure $ Just Nothing++data StealResult a = Found a | Empty | Race+ deriving (Show, Eq, Ord)++{- |+ * @Nothing@ — closed (end-of-stream)+ * @Just Nothing@ — open and empty (would block)+ * @Just (Just a)@ — got an element+-}+tryPopBack :: ChaseLevDeq a -> IO (Maybe (StealResult a))+tryPopBack q = do+ !t <- readPrimVar q.top+ loadLoadBarrier+ !b <- readPrimVar q.bottom+ if t >= b+ then do+ closed <- readIORef q.closed+ if closed+ then pure Nothing+ else pure $ Just Empty+ else do+ arr <- readIORef q.activeArray+ let !capa = Array.sizeofMutableArray arr+ -- NOTE: we must not force, otherwise undefined will hit+ task <- Array.readArray arr (t .&. (capa - 1))+ let !t' = t + 1+ !old <- casInt q.top t t'+ let !success = old == t+ if success+ then do+ writePrimVar q.estimatedSize $! b - t - 1+ pure $! Just $ Found task+ else pure $ Just Race++-- | back-element first+stealHalf :: ChaseLevDeq a -> IO (Maybe (StealResult (NonEmpty a)))+stealHalf q = do+ !t <- readPrimVar q.top+ loadLoadBarrier+ !b <- readPrimVar q.bottom+ if t >= b+ then do+ closed <- readIORef q.closed+ if closed+ then pure Nothing+ else pure $ Just Empty+ else do+ let !avail = b - t+ !count = if avail == 1 then 1 else avail `quot` 2+ !t' = t + count+ arr <- readIORef q.activeArray+ let !capa = Array.sizeofMutableArray arr+ -- NOTE: we must not force, otherwise undefined will hit+ tasks <- V.generateM count \i ->+ Array.readArray arr $ (t + i) .&. (capa - 1)+ !old <- casInt q.top t t'+ let !success = old == t+ if success+ then do+ writePrimVar q.estimatedSize $! avail - count+ pure $! Just $ Found $ NE.fromList $ V.toList tasks+ else pure $ Just Race++estimateSize :: ChaseLevDeq a -> IO Int+{-# INLINE estimateSize #-}+estimateSize q = readPrimVar q.estimatedSize++close :: ChaseLevDeq a -> IO ()+{-# INLINE close #-}+close q = writeIORef q.closed True++isClosed :: ChaseLevDeq a -> IO Bool+isClosed q = do+ closed <- readIORef q.closed+ if closed+ then do+ !size <- occupancy <$!> getStat q+ pure $! size == 0+ else pure False
− src/Control/Concurrent/STM/TMDeque.hs
@@ -1,229 +0,0 @@-{-# LANGUAGE BlockArguments #-}-{-# LANGUAGE NoLinearTypes #-}--{- | A closable, concurrent double-ended queue backed by STM, with amortized-O(1) operations. The underlying implementation uses a two-stack design with-separate 'TVar's for the front and rear, reducing STM contention: in the-common case, @pushFront@ and @popBack@ touch disjoint variables and do not-conflict.--Closing semantics follow @stm-chans@ conventions:-- * __Closed + empty__ → read returns @Nothing@ (end-of-stream)- * __Closed + non-empty__ → read returns @Just a@ (drain remaining)- * __Open + empty__ → read blocks (@retry@)- * __Open + non-empty__ → read returns @Just a@- * __Write to closed__ → silently ignored--}-module Control.Concurrent.STM.TMDeque (- -- * The TMDeque type- TMDeque,-- -- * Construction- newTMDeque,- newTMDequeIO,-- -- * Push operations- pushFrontTMDeque,-- -- * Pop operations (blocking)- popFrontTMDeque,- popBackTMDeque,-- -- * Pop operations (non-blocking)- tryPopFrontTMDeque,- tryPopBackTMDeque,-- -- * Closing & queries- closeTMDeque,- isClosedTMDeque,- isClosedTMDequeIO,- isEmptyTMDeque,- sizeTMDeque,- countTMDequeIO,-) where--import Control.Concurrent.STM (STM, TVar, modifyTVar', newTVar, newTVarIO, readTVar, retry, writeTVar)-import Control.Concurrent.STM.TVar (readTVarIO)-import Control.Monad (unless)--{- | Reverse a non-empty list and split into head and tail.-Precondition: the input list is non-empty.--}-unconsReverse :: [a] -> (a, [a])-unconsReverse xs = case reverse xs of- y : ys -> (y, ys)- [] -> error "TMDeque.unconsReverse: impossible – called on empty list"----------------------------------------------------------------------------- STM two-stack queue----------------------------------------------------------------------------- | A closable, STM-backed double-ended queue with amortized O(1) operations.-data TMDeque a- = TMDeque- {-# UNPACK #-} !(TVar Bool) -- closed flag (monotonic: False → True)- {-# UNPACK #-} !(TVar [a]) -- front (push end)- {-# UNPACK #-} !(TVar [a]) -- rear (pop end)- {-# UNPACK #-} !(TVar Int) -- size (maintained for O(1) count)---- | Create a new empty 'TMDeque'.-newTMDeque :: STM (TMDeque a)-newTMDeque = TMDeque <$> newTVar False <*> newTVar [] <*> newTVar [] <*> newTVar 0---- | IO variant of 'newTMDeque'.-newTMDequeIO :: IO (TMDeque a)-newTMDequeIO = TMDeque <$> newTVarIO False <*> newTVarIO [] <*> newTVarIO [] <*> newTVarIO 0--{- | Push an element to the front of the deque. Silently ignored if the-deque is closed.--}-pushFrontTMDeque :: TMDeque a -> a -> STM ()-pushFrontTMDeque (TMDeque closedVar frontVar _rearVar sizeVar) x = do- closed <- readTVar closedVar- unless closed do- modifyTVar' frontVar (x :)- modifyTVar' sizeVar (+ 1)--{- | Pop an element from the front. Blocks if the deque is open and empty.-Returns @Nothing@ when the deque is closed and empty (end-of-stream).--}-popFrontTMDeque :: TMDeque a -> STM (Maybe a)-popFrontTMDeque (TMDeque closedVar frontVar rearVar sizeVar) = do- f <- readTVar frontVar- case f of- x : f' -> do- writeTVar frontVar f'- modifyTVar' sizeVar (subtract 1)- pure (Just x)- [] -> do- r <- readTVar rearVar- case r of- _ : _ -> do- let (x, f') = unconsReverse r- writeTVar rearVar []- writeTVar frontVar f'- modifyTVar' sizeVar (subtract 1)- pure (Just x)- [] -> do- closed <- readTVar closedVar- if closed- then pure Nothing- else retry--{- | Non-blocking pop from the front.-- * @Nothing@ — closed (end-of-stream)- * @Just Nothing@ — open and empty (would block)- * @Just (Just a)@ — got an element--}-tryPopFrontTMDeque :: TMDeque a -> STM (Maybe (Maybe a))-tryPopFrontTMDeque (TMDeque closedVar frontVar rearVar sizeVar) = do- f <- readTVar frontVar- case f of- x : f' -> do- writeTVar frontVar f'- modifyTVar' sizeVar (subtract 1)- pure (Just (Just x))- [] -> do- r <- readTVar rearVar- case r of- _ : _ -> do- let (x, f') = unconsReverse r- writeTVar rearVar []- writeTVar frontVar f'- modifyTVar' sizeVar (subtract 1)- pure (Just (Just x))- [] -> do- closed <- readTVar closedVar- if closed- then pure Nothing- else pure (Just Nothing)--{- | Pop an element from the back. Blocks if the deque is open and empty.-Returns @Nothing@ when the deque is closed and empty (end-of-stream).--}-popBackTMDeque :: TMDeque a -> STM (Maybe a)-popBackTMDeque (TMDeque closedVar frontVar rearVar sizeVar) = do- r <- readTVar rearVar- case r of- x : r' -> do- writeTVar rearVar r'- modifyTVar' sizeVar (subtract 1)- pure (Just x)- [] -> do- f <- readTVar frontVar- case f of- _ : _ -> do- let (x, r') = unconsReverse f- writeTVar frontVar []- writeTVar rearVar r'- modifyTVar' sizeVar (subtract 1)- pure (Just x)- [] -> do- closed <- readTVar closedVar- if closed- then pure Nothing- else retry--{- | Non-blocking pop from the back.-- * @Nothing@ — closed (end-of-stream)- * @Just Nothing@ — open and empty (would block)- * @Just (Just a)@ — got an element--}-tryPopBackTMDeque :: TMDeque a -> STM (Maybe (Maybe a))-tryPopBackTMDeque (TMDeque closedVar frontVar rearVar sizeVar) = do- r <- readTVar rearVar- case r of- x : r' -> do- writeTVar rearVar r'- modifyTVar' sizeVar (subtract 1)- pure (Just (Just x))- [] -> do- f <- readTVar frontVar- case f of- _ : _ -> do- let (x, r') = unconsReverse f- writeTVar frontVar []- writeTVar rearVar r'- modifyTVar' sizeVar (subtract 1)- pure (Just (Just x))- [] -> do- closed <- readTVar closedVar- if closed- then pure Nothing- else pure (Just Nothing)--{- | Close the deque. After closing, writes are silently ignored and reads-will drain remaining elements before signalling end-of-stream. Closing-is idempotent.--}-closeTMDeque :: TMDeque a -> STM ()-closeTMDeque (TMDeque closedVar _ _ _) = writeTVar closedVar True---- | Check whether the deque has been closed.-isClosedTMDeque :: TMDeque a -> STM Bool-isClosedTMDeque (TMDeque closedVar _ _ _) = readTVar closedVar---- | Check whether the deque has been closed.-isClosedTMDequeIO :: TMDeque a -> IO Bool-isClosedTMDequeIO (TMDeque closedVar _ _ _) = readTVarIO closedVar---- | Check whether the deque is currently empty.-isEmptyTMDeque :: TMDeque a -> STM Bool-isEmptyTMDeque (TMDeque _ frontVar rearVar _) = do- f <- readTVar frontVar- case f of- _ : _ -> pure False- [] -> do- r <- readTVar rearVar- pure (null r)---- | Return the number of elements currently in the deque. O(1).-sizeTMDeque :: TMDeque a -> STM Int-sizeTMDeque (TMDeque _ _ _ sizeVar) = readTVar sizeVar---- | IO variant of 'countTMDeque'. O(1).-countTMDequeIO :: TMDeque a -> IO Int-countTMDequeIO (TMDeque _ _ _ sizeVar) = readTVarIO sizeVar
− src/Control/Concurrent/STM/TMDequeRingBuffer.hs
@@ -1,218 +0,0 @@-{-# LANGUAGE BlockArguments #-}-{-# LANGUAGE OverloadedRecordDot #-}-{-# LANGUAGE NoLinearTypes #-}--module Control.Concurrent.STM.TMDequeRingBuffer (- -- * The TMDeque type- TMDeque,-- -- * Construction- newTMDeque,- newTMDequeIO,-- -- * Push operations- pushFrontTMDeque,-- -- * Pop operations (blocking)- popFrontTMDeque,- popBackTMDeque,-- -- * Pop operations (non-blocking)- tryPopFrontTMDeque,- tryPopBackTMDeque,-- -- * Closing & queries- closeTMDeque,- isClosedTMDeque,- isClosedTMDequeIO,- isEmptyTMDeque,- estimateSizeTMDequeIO,- sizeTMDeque,-) where--import Control.Concurrent.STM-import Control.Monad (when)-import Data.Array.Base (newArray_, readArray, writeArray)-import Data.Function (fix, (&))--{- | 0 | 1 | 2 | ... | i | ... | N - 1 |- ^ ^- | |- back front--}-data TMDeque a = TMDeque- { closed :: TVar Bool- , ringBuffer :: TVar (TArray Int a)- , capacity :: TVar Int- , front :: TVar Int- , back :: TVar Int- }--newtype UniqIx = UniqIx Int--initialCapacity :: Int-initialCapacity = 64---- | Create a new empty 'TMDeque'.-newTMDeque :: STM (TMDeque a)-newTMDeque =- TMDeque- <$> newTVar False- <*> (newTVar =<< newArray_ (0, initialCapacity - 1))- <*> newTVar initialCapacity- <*> newTVar 0- <*> newTVar 0---- | IO variant of 'newTMDeque', which is faster without STM transaction overhead.-newTMDequeIO :: IO (TMDeque a)-newTMDequeIO =- TMDeque- <$> newTVarIO False- <*> (newTVarIO =<< newArray_ (0, initialCapacity - 1))- <*> newTVarIO initialCapacity- <*> newTVarIO 0- <*> newTVarIO 0--growThreshold :: Int-growThreshold = 16--{- | Push an element to the front of the deque. Silently ignored if the-deque is closed.--}-pushFrontTMDeque :: TMDeque a -> a -> STM ()-pushFrontTMDeque deq v = do- growIfNeeded deq- capa <- readTVar deq.capacity- UniqIx dest <- stateTVar deq.front \i ->- let !j = i + 1- in (UniqIx $ i `rem` capa, j)- buf <- readTVar deq.ringBuffer- writeArray buf dest v--growIfNeeded :: TMDeque a -> STM ()-{-# INLINE growIfNeeded #-}-growIfNeeded deq = do- capa <- readTVar deq.capacity- size <- sizeTMDeque deq- when (capa - size - 1 <= growThreshold) do- ring <- doubleDeq capa deq- writeTVar deq.ringBuffer ring- writeTVar deq.capacity (capa * 2)--sizeTMDeque :: TMDeque a -> STM Int-sizeTMDeque deq = do- front <- readTVar deq.front- back <- readTVar deq.back- pure $ front - back--doubleDeq :: Int -> TMDeque a -> STM (TArray Int a)-{-# INLINE doubleDeq #-}-doubleDeq oldSize deq = do- let !newSize = oldSize * 2- back <- (`rem` oldSize) <$> readTVar deq.back- front <- (`rem` oldSize) <$> readTVar deq.front- arr <- readTVar deq.ringBuffer- dest <- newArray_ (0, newSize - 1)- if back <= front- then -- linear copy on [back, front]- back & fix \go !i -> when (i < front) do- e <- readArray arr i- writeArray dest i e- go (i + 1)- else do- -- first copy [0, front), then copy [back, oldSize)- 0 & fix \go !i -> when (i < front) do- e <- readArray arr i- writeArray dest i e- go (i + 1)- back & fix \go !i -> when (i < oldSize) do- e <- readArray arr i- writeArray dest (i + oldSize) e- go (i + 1)- pure dest--{- | Pop an element from the front. Blocks if the deque is open and empty.-Returns @Nothing@ when the deque is closed and empty (back-of-stream).--}-popFrontTMDeque :: TMDeque a -> STM (Maybe a)-popFrontTMDeque deq = do- may <- tryPopFrontTMDeque deq- maybe retry pure may--{- | Pop an element from the back. Blocks if the deque is open and empty.-Returns @Nothing@ when the deque is closed and empty (back-of-stream).--}-popBackTMDeque :: TMDeque a -> STM (Maybe a)-popBackTMDeque deq = do- may <- tryPopBackTMDeque deq- maybe retry pure may--{- | Non-blocking pop from the front.-- * @Nothing@ — closed (end-of-stream)- * @Just Nothing@ — open and empty (would block)- * @Just (Just a)@ — got an element--}-tryPopFrontTMDeque :: TMDeque a -> STM (Maybe (Maybe a))-tryPopFrontTMDeque deq = do- size <- sizeTMDeque deq- if size == 0- then do- closed <- readTVar deq.closed- if closed- then pure Nothing- else pure (Just Nothing)- else do- capa <- readTVar deq.capacity- UniqIx dest <- stateTVar deq.front \i ->- let !j = i - 1- in (UniqIx $ j `rem` capa, j)- buf <- readTVar deq.ringBuffer- e <- readArray buf dest- pure (Just (Just e))--{- | Non-blocking pop from the back.-- * @Nothing@ — closed (end-of-stream)- * @Just Nothing@ — open and empty (would block)- * @Just (Just a)@ — got an element--}-tryPopBackTMDeque :: TMDeque a -> STM (Maybe (Maybe a))-tryPopBackTMDeque deq = do- back <- readTVar deq.back- front <- readTVar deq.front- if back == front- then do- closed <- readTVar deq.closed- if closed- then pure Nothing- else pure (Just Nothing)- else do- capa <- readTVar deq.capacity- UniqIx dest <- stateTVar deq.back \i ->- let !j = i + 1- in (UniqIx $ i `rem` capa, j)- buf <- readTVar deq.ringBuffer- e <- readArray buf dest- pure (Just (Just e))---- | Close the deque. After this, all push operations will be ignored, and all pop operations will return @Nothing@ once the deque is empty.-closeTMDeque :: TMDeque a -> STM ()-closeTMDeque deq = writeTVar deq.closed True---- | Check if the deque is closed.-isClosedTMDeque :: TMDeque a -> STM Bool-isClosedTMDeque deq = readTVar deq.closed---- | IO variant of 'isClosedTMDeque'.-isClosedTMDequeIO :: TMDeque a -> IO Bool-isClosedTMDequeIO deq = readTVarIO deq.closed---- | Check if the deque is empty. Note that an open deque may become non-empty after this returns.-isEmptyTMDeque :: TMDeque a -> STM Bool-isEmptyTMDeque deq = (==) <$> readTVar deq.front <*> readTVar deq.back---- | IO variant of 'countTMDeque'.-estimateSizeTMDequeIO :: TMDeque a -> IO Int-estimateSizeTMDequeIO deq =- (-) <$> readTVarIO deq.front <*> readTVarIO deq.back
src/Control/Monad/Borrow/Pure.hs view
@@ -53,6 +53,10 @@ -- * Parallel computation parBO,+ Par (..),+ runPar,+ mapConcurrentlyOf,+ forConcurrentlyOf, -- * Borrowing -- $borrow@@ -97,6 +101,7 @@ -- ** Utility function to manipulate borrows joinMut, joinLend,+ subShare, coerceShare, -- ** Copying and Cloning@@ -126,7 +131,10 @@ import Control.Monad.Borrow.Pure.BO import Control.Monad.Borrow.Pure.Clone import Control.Monad.Borrow.Pure.Copyable+import Control.Optics.Linear (Traversal, traverseOf) import Data.Unrestricted.Linear (Consumable (..), Dupable (..), Movable (..), Ur (..), dup, dup3)+import Prelude.Linear ((.))+import Prelude.Linear qualified as PL {- $setup >>> :set -XBlockArguments -XLinearTypes -XNoImplicitPrelude -XImpredicativeTypes -XQualifiedDo@@ -402,6 +410,24 @@ * Use 'sharing' or 'sharing_' to share temporarily and then regain the original 'Mut'. * Use 'reborrowing' or 'reborrowing_' to create a shorter-lived 'Mut' and then regain the original 'Mut'. * Use 'reclaim' or 'reclaim'' to recover the original resource from a 'Lend' after the lifetime ends.++== Performance-sensitive loops++Choose the loop structure that performs the least algorithmic work first.+The sublifetime that a 'sharing' or a 'reborrowing' delimits is erased at compile time: no lifetime token is allocated, no lender is created, and no @After@ plumbing survives.+What does remain is one opaque call at each scope exit, through which the borrow is handed back, and it allocates nothing.+For an otherwise read-only loop, prefer sharing once outside the loop and shortening that shared borrow inside each iteration with 'subShare':++@+'share' resource \& \('Ur' shared) ->+ ... 'subShare' shared ...+@++That way the body never has to give the borrow back, so neither the @(r, 'Mut' α a)@ pair nor the restoring call is paid on every iteration.+Functions containing hot loops over operations that return+@(Ur a, container)@ should themselves be @INLINE@, @INLINABLE@, or specialised.+That lets GHC eliminate the transient tuple and 'Ur' constructors; without+cross-module inlining those constructors can allocate on every read. -} {- $copy-and-clone@@ -435,6 +461,11 @@ This includes, for example, 'Data.Ref.Linear.Ref' or 'Data.Vector.Mutable.Linear.Borrow.Vector'. The fact that the 'clone'd value is only accessible inside 'BO' ensures that we cannot leak mutable states inside @a@ into /unrestricted/ contexts -- otherwise, we can introduce mutable values into unrestricted context via @'move' :: 'Share' α a -> 'Ur' ('Share' α a)@.++In performance-sensitive loops, moving a whole structured value can be a sign+of an avoidable deep copy, particularly for generically derived 'Movable'+instances. Prefer operating through the borrow or returning only the required+unrestricted fields in 'Ur' rather than moving the enclosing structure. -} {- $splitting@@ -451,3 +482,17 @@ We also provide experimental splitting on record types in "Data.Record.Linear.Borrow.Experimental.PatternMatch" and "Data.Record.Linear.Borrow.Experimental.Split". -}++mapConcurrentlyOf ::+ Traversal s t a b ->+ (a %1 -> BO α b) ->+ s %1 ->+ BO α t+mapConcurrentlyOf l f = runPar . traverseOf l (Par . f)++forConcurrentlyOf ::+ Traversal s t a b ->+ s %1 ->+ (a %1 -> BO α b) ->+ BO α t+forConcurrentlyOf l = PL.flip (mapConcurrentlyOf l)
src/Control/Monad/Borrow/Pure/BO.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE BlockArguments #-}+{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DerivingVia #-} {-# LANGUAGE ExplicitNamespaces #-}@@ -41,6 +42,8 @@ -- * Parallel computation parBO,+ Par (..),+ runPar, -- * Borrowing Alias,@@ -50,6 +53,7 @@ Mut, Share, Lend,+ subShare, coerceShare, shareCoercion, borrowM,@@ -111,9 +115,15 @@ import Control.Syntax.DataFlow qualified as DataFlow import Data.Coerce (Coercible) import Data.Coerce.Directed+import Data.Function qualified as NonLinear+import Data.Functor.Linear qualified as Data import Data.Type.Coercion (Coercion (..)) import Prelude.Linear +#ifndef PURE_BORROW_SLOW_SCOPES+import Control.Monad.Borrow.Pure.Lifetime.Token.Unsafe qualified as Unsafe+#endif+ {- | Runs a 'BO' computation and returns the result of postprocessing 'After' the lifetime has ended. @@ -149,6 +159,7 @@ -- | A variant of 'borrow' that obtains 'Linearly' viar 'LinearOnly'. borrowLinearOnly :: forall α a. (LinearOnly a) => a %1 -> (Mut α a, Lend α a)+{-# INLINE borrowLinearOnly #-} borrowLinearOnly !a = case withLinearly a of (!lin, !a) -> borrow a lin @@ -164,7 +175,11 @@ (forall β. Share (β /\ α) a -> BO (β /\ α') r) %1 -> BO α' (Mut α a) {-# INLINE sharing_ #-}+#ifdef PURE_BORROW_SLOW_SCOPES sharing_ v k = uncurry lseq Control.<$> sharing v k+#else+sharing_ = unsafeBorrowScope_+#endif -- | Flipped infix version of 'sharing_', smoewhat analgous to '(Control.<$>)' and @(<%=)@ in @lens@ package. (<$=) ::@@ -185,7 +200,11 @@ (forall β. Share (β /\ α) a -> BO (β /\ α') r) %1 -> BO α' (r, Mut α a) {-# INLINE sharing #-}+#ifdef PURE_BORROW_SLOW_SCOPES sharing v k = sharing' v (\mut -> Control.pure Control.<$> k mut)+#else+sharing = unsafeBorrowScope+#endif -- | Flipped infix version of 'sharing', smoewhat analgous to '(Control.<$>)' and @(<%~)@ in @lens@ package. (<$~) ::@@ -207,11 +226,15 @@ (forall β. Share (β /\ α) a -> BO (β /\ α') (After β r)) %1 -> BO α' (r, Mut α a) {-# INLINE sharing' #-}+#ifdef PURE_BORROW_SLOW_SCOPES sharing' v k = DataFlow.do srunBO DataFlow.do (v, lend) <- reborrow v share v & \(Ur v) -> Control.do k v Control.<&> \v -> (,) Control.<$> v Control.<*> upcast (reclaim' lend)+#else+sharing' = unsafeBorrowScope'+#endif {- | Executes an operation on 'Mut'able borrow in sub lifetime. You may need @-XImpredicativeTypes@ extension to use this function.@@ -222,11 +245,16 @@ Mut α a %1 -> (forall β. Mut (β /\ α) a %1 -> BO (β /\ α') (After β r)) %1 -> BO α' (r, Mut α a)+{-# INLINE reborrowing' #-}+#ifdef PURE_BORROW_SLOW_SCOPES reborrowing' v k = srunBO DataFlow.do (v, lend) <- reborrow v Control.do v <- k v Control.pure $ (,) Control.<$> v Control.<*> upcast (reclaim' lend)+#else+reborrowing' = unsafeBorrowScope'+#endif {- | A variant of 'reborrowing'' that returns the direct value of the operation on the reborrowed mutable borrow. There is also a flipped infix version '(<%~)'.@@ -237,7 +265,12 @@ Mut α a %1 -> (forall β. Mut (β /\ α) a %1 -> BO (β /\ α') r) %1 -> BO α' (r, Mut α a)+{-# INLINE reborrowing #-}+#ifdef PURE_BORROW_SLOW_SCOPES reborrowing mutα k = reborrowing' mutα (\mut -> Control.pure Control.<$> k mut)+#else+reborrowing = unsafeBorrowScope+#endif -- | Flipped infix version of 'reborrowing', smoewhat analgous to '(Control.<$>)' and @(<%~)@ in @lens@ package. (<%~) ::@@ -260,7 +293,12 @@ Mut α a %1 -> (forall β. Mut (β /\ α) a %1 -> BO (β /\ α') r) %1 -> BO α' (Mut α a)+{-# INLINE reborrowing_ #-}+#ifdef PURE_BORROW_SLOW_SCOPES reborrowing_ mutα k = reborrowing mutα (Control.fmap consume . k) Control.<&> \((), a) -> a+#else+reborrowing_ = unsafeBorrowScope_+#endif -- | Flipped infix version of 'reborrowing_', smoewhat analgous to '(Control.<$>)' and @(<%=)@ in @lens@ package. (<%=) ::@@ -278,6 +316,7 @@ Linearly %1 -> (forall α. Mut α a %1 -> BO α r) %1 -> (r, a)+{-# INLINE modifyBO #-} modifyBO v lin k = DataFlow.do (lin, lin') <- dup lin runBO lin Control.do@@ -291,6 +330,7 @@ Linearly %1 -> (forall α. Mut α a %1 -> BO α ()) %1 -> a+{-# INLINE modifyBO_ #-} modifyBO_ v lin k = DataFlow.do (lin, lin') <- dup lin runBO lin Control.do@@ -304,6 +344,7 @@ a %1 -> (forall α. Mut α a %1 -> BO α r) %1 -> (r, a)+{-# INLINE modifyLinearOnlyBO #-} modifyLinearOnlyBO v k = DataFlow.do (lin, v) <- withLinearly v runBO lin Control.do@@ -317,6 +358,7 @@ a %1 -> (forall α. Mut α a %1 -> BO α ()) %1 -> a+{-# INLINE modifyLinearOnlyBO_ #-} modifyLinearOnlyBO_ v k = DataFlow.do (lin, v) <- withLinearly v runBO lin Control.do@@ -332,6 +374,16 @@ {-# INLINE pureAfter #-} pureAfter a = Control.pure (After a) +{- | Shorten a shared borrow to a sublifetime.++This is the inference-friendly, borrow-kind-fixed variant of 'upcast'. It is+particularly useful when a shared borrow is captured outside a read-only loop+and must be used inside the loop's shorter lifetime.+-}+subShare :: (α >= β) => Share α a -> Share β a+{-# INLINE subShare #-}+subShare shr = upcast shr+ coerceShare :: forall b α a. (Coercible a b) => Share α a %1 -> Share α b {-# INLINE coerceShare #-} coerceShare = coerceLin@@ -360,13 +412,57 @@ -- | Runs a 'BO' computation within the ephemeral sublifetime and returns the result. srunBO :: (forall α. BO (α /\ β) (After α a)) %1 -> BO β a {-# INLINE srunBO #-}+#ifdef PURE_BORROW_SLOW_SCOPES srunBO bo = asksLinearlyM \lin -> newLifetime' lin \now -> Control.do (now, f) <- sexecBO bo now Ur end <- Control.pure (endLifetime now) Control.pure (withEnd end f)+#else+srunBO bo = Control.do+ after <- unsafeCastBO bo+ Control.pure $! withEnd Unsafe.UnsafeEnd after+#endif -- | A variant of 'srunBO' that returns the direct value of 'BO' computation. srunBO_ :: (forall α. BO (α /\ β) a) %1 -> BO β a {-# INLINE srunBO_ #-}+#ifdef PURE_BORROW_SLOW_SCOPES srunBO_ k = srunBO Control.do a <- k; Control.pure $ After a+#else+srunBO_ = \bo -> unsafeCastBO bo+#endif++{- | A parallel comoutation applicative functor for 'BO' monad.+All the computations chained by '<*>' or 'liftA2' will be executed in parallel.+-}+newtype Par α a = Par (BO α a)+ deriving newtype (Data.Functor, Control.Functor)++runPar :: Par α a %1 -> BO α a+runPar = coerceLin+{-# INLINE runPar #-}++instance Data.Applicative (Par α) where+ pure = Par NonLinear.. Data.pure+ {-# INLINE pure #-}+ Par f <*> Par x = Par Control.do+ (f, x) <- parBO f x+ Control.pure $ f x+ {-# INLINE (<*>) #-}+ liftA2 f (Par x) (Par y) = Par Control.do+ (x, y) <- parBO x y+ Control.pure $ f x y+ {-# INLINE liftA2 #-}++instance Control.Applicative (Par α) where+ pure = Par . Control.pure+ {-# INLINE pure #-}+ Par f <*> Par x = Par Control.do+ (f, x) <- parBO f x+ Control.pure $ f x+ {-# INLINE (<*>) #-}+ liftA2 f (Par x) (Par y) = Par Control.do+ (x, y) <- parBO x y+ Control.pure $ f x y+ {-# INLINE liftA2 #-}
src/Control/Monad/Borrow/Pure/BO/Internal.hs view
@@ -16,6 +16,7 @@ {-# LANGUAGE StandaloneKindSignatures #-} {-# LANGUAGE TypeData #-} {-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeFamilyDependencies #-} {-# LANGUAGE UnboxedTuples #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE UnliftedNewtypes #-}@@ -30,6 +31,9 @@ module Control.Monad.Borrow.Pure.BO.Internal, ) where +import Control.Applicative qualified as NonLinear+import Control.Concurrent (forkIO, newEmptyMVar, putMVar, takeMVar)+import Control.Exception (evaluate) import Control.Exception qualified as SystemIO import Control.Functor.Linear qualified as Control import Control.Monad qualified as NonLinear@@ -52,7 +56,7 @@ import Data.Type.Equality ((:~:) (Refl)) import GHC.Base (TYPE) import GHC.Base qualified as GHC-import GHC.Exts (State#, runRW#)+import GHC.Exts (Multiplicity (..), State#, runRW#) import GHC.ST qualified as ST import GHC.TypeError (ErrorMessage (..)) import Generics.Linear@@ -128,6 +132,11 @@ (<*>) = \f g -> f Control.<*> g {-# INLINE (<*>) #-} + liftA2 f (BO g) (BO h) = BO \s -> case g s of+ (# s', a #) -> case h s' of+ (# s'', b #) -> (# s'', f a b #)+ {-# INLINE liftA2 #-}+ instance Control.Applicative (BO α) where pure a = BO \s -> (# s, a #) {-# INLINE pure #-}@@ -137,11 +146,20 @@ (# s'', a #) -> (# s'', h a #) {-# INLINE (<*>) #-} + liftA2 f (BO g) (BO h) = BO \s -> case g s of+ (# s', a #) -> case h s' of+ (# s'', b #) -> (# s'', f a b #)+ {-# INLINE liftA2 #-}+ instance Control.Monad (BO α) where BO fa >>= f = BO \s -> case fa s of (# s', a #) -> (f a) PL.& \(BO g) -> g s' {-# INLINE (>>=) #-} + BO fa >> BO fb = BO \s -> case fa s of+ (# s', () #) -> fb s'+ {-# INLINE (>>) #-}+ -- | Unsafely converts a 'BO' computation to linear 'L.IO'. unsafeBOToLinIO :: BO α a %1 -> L.IO a {-# INLINE unsafeBOToLinIO #-}@@ -224,36 +242,103 @@ -- | Run two computations in parallel, returning their results as a tuple. parBO :: BO α a %1 -> BO α b %1 -> BO α (a, b)-parBO = Unsafe.toLinear2 \a b ->- BO $- Unsafe.toLinear \s ->- case Unsafe.toLinear2 GHC.spark# (case unsafeUnBO a (GHC.noDuplicate# s) of (# _, a #) -> GHC.lazy a) s of- (# _, a #) ->- case Unsafe.toLinear2 GHC.spark# (case unsafeUnBO b (GHC.noDuplicate# s) of (# _, b #) -> GHC.lazy b) s of- (# _, b #) ->- case Unsafe.toLinear2 GHC.seq# a s of- (# s, !a #) -> case Unsafe.toLinear2 GHC.seq# b s of- (# s, !b #) -> (# s, (a, b) #)+parBO = Unsafe.toLinear2 \a b -> unsafeSystemIOToBO do+ aVar <- newEmptyMVar+ bVar <- newEmptyMVar+ NonLinear.void $+ forkIO $+ putMVar aVar NonLinear.=<< evaluate NonLinear.=<< unsafeBOToSystemIO a+ NonLinear.void $+ forkIO $+ putMVar bVar NonLinear.=<< evaluate NonLinear.=<< unsafeBOToSystemIO b+ !a' <- takeMVar aVar+ !b' <- takeMVar bVar+ NonLinear.pure (a', b') evaluateBO :: a %1 -> BO α a {-# INLINE evaluateBO #-} evaluateBO a = unsafeSystemIOToBO (Unsafe.toLinear SystemIO.evaluate a) -- | Alias of kind 'ak' to a resource of type 'a'.-type Alias :: AliasKind -> Lifetime -> Type -> Type-newtype Alias ak α a = UnsafeAlias a+type Alias :: AliasKind -> Type -> Type+newtype Alias ak a = UnsafeAlias a -unsafeUnalias :: Alias ak α a %1 -> a+unsafeUnalias :: Alias ak a %1 -> a unsafeUnalias (UnsafeAlias x) = x -type role Alias nominal nominal representational+{- |+Retags an alias with another 'AliasKind', leaving the aliased resource alone. +The role annotation below makes @ak@ nominal precisely so that this retagging is+not derivable, so every use is a proof obligation about the kind being moved to:+a 'Share' must not be widened into a 'Mut', a borrower must not become a lender,+and the lifetime it is retagged to must be one throughout which the resource is+really borrowed.++This is a coercion, not a coincidence of representation: 'Alias' is a newtype+over the resource, so the retagged alias is the very same value.+-}+unsafeCastAlias :: Alias ak a %1 -> Alias ak' a+{-# INLINE unsafeCastAlias #-}+unsafeCastAlias = coerceLin++{-+Note [Restoring a borrow must break its Core identity]+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+Not every read of a borrowed resource is threaded through this monad's state token.+A growable vector keeps its length and its backing buffer behind a `Data.Ref.Linear.Ref`, and the reads that only project that header -- `size`, `capacity`, `getContents` and their neighbours -- go through `Data.Ref.Linear.unsafeReadRef`, an @INLINE@ pass-through to the `GHC.Magic.noinline`-wrapped `Data.Ref.Linear.Unlifted.unsafeReadRef#`, which opens its own `runRW#`.+To GHC such a read is a plain function of the borrow.+Two of them on the same borrow /variable/ are therefore the same Core expression, and common-subexpression elimination is entitled to serve the second from the first.++What keeps that honest is that every operation which replaces the header writes through `Data.Ref.Linear.Unlifted.unsafeWriteRef#`, which is @NOINLINE@, and hands back the reference that write returned.+The optimizer cannot see through it, so a read after a growth scrutinises a different expression than a read before it, and the two do not merge.+The whole ordering guarantee for header traffic rests on that one data dependency.++A delimiter that returns the borrow its caller passed in throws the dependency away.+The restored borrow is then the caller's own binder, so a read after the scope is syntactically the read before it, and CSE deletes the later one -- serving a stale length and a stale buffer across every growth the scope performed.+Writing through the stale buffer at an index the fresh length admits runs off the end of the allocation, which is how this last surfaced downstream: a SIGSEGV inside the collector, or silently wrong data under a nursery large enough that the damage is never traversed.++So a delimiter restores its borrow through `reviveAlias`, and both of its properties are load-bearing.++Do not weaken the @OPAQUE@ to @NOINLINE@.+Measured on GHC 9.12.4 at -O2 a @NOINLINE@ version is an equally good barrier today, and for a knowable reason: the argument's demand comes out lazy and the result is already an unboxed tuple, so no worker/wrapper split occurs and no @$w@ worker is generated.+But that is a property of one demand signature rather than a guarantee, and @OPAQUE@ is the one pragma GHC documents as suppressing inlining, worker/wrapper, specialisation and rules together.+The barrier is the whole of the memory safety of these delimiters, so it should rest on a contract rather than on a coincidence.++It lives in `BO` rather than being a pure function because a pure barrier depends on nothing the scope produced, so nothing would stop it -- or the reads that consume its result -- from floating above the scope body.+Consuming the state token pins the restoration after the scope's effects.+A pure @OPAQUE@ barrier also measures a few percent worse in a tight loop, but that is a side benefit and not the reason.++The cost is one out-of-line call per scope exit, around 0.7ns on aarch64-darwin with GHC 9.12.4, plus whatever it costs that a loop-carried borrow can no longer be unboxed past the barrier: measured together at 14-20% of a tight L1-resident loop that does nothing but the scope, and unmeasurable in any benchmark this repository ships.+No closure is allocated and the recursion remains a self tail call; what the call adds is a non-tail continuation and, in a loop, a boxed rather than unboxed loop-carried borrow.++This is a statement about the delimiters, not about `Ref` in general.+Threading the header reads through the state token, so that they are ordered like every other mutable operation and this whole hazard disappears, would be the durable fix; it is an API break and is not attempted here.+The barrier is also one-directional -- it stops a post-scope read being served from a pre-scope one, but nothing stops a pre-scope read from sinking below the scope -- so that deferral should not drift indefinitely.+-}++{- | Return a borrow to the caller of a delimiter, through a barrier the optimizer cannot see through.++Semantically this is `Control.pure` at 'Alias', and it carries no proof obligation of its own: its argument and result types are identical, so it cannot widen a 'Share' into a 'Mut', relabel a 'Lend', or lengthen a lifetime, and the `BO` index it returns at is as free as `Control.pure`'s already is.+That is also why it comes with no @TypingCases@ entry: there is no program that should stop typechecking because of it.++What it does carry is an obligation on /callers/.+Any delimiter that runs a continuation and then hands the caller back the borrow it was given must restore it through this.+Returning the caller's own occurrence instead lets common-subexpression elimination serve a post-scope read of the resource from a pre-scope one, across every write the scope performed.+See Note [Restoring a borrow must break its Core identity] for why, and for why the @OPAQUE@ and the state token are both load-bearing.+-}+reviveAlias :: Alias ak a %1 -> BO α (Alias ak a)+{-# OPAQUE reviveAlias #-}+reviveAlias a = BO \s -> (# s, a #)++type role Alias nominal representational+ -- | Alias kind. data AliasKind = -- | Borrower.- Borrow BorrowKind+ Borrow BorrowKind Lifetime | -- | Lender.- Lend+ Lend Lifetime -- | Borrower kind. data BorrowKind@@ -264,11 +349,11 @@ -- | Borrower of kind @bk@ that is active during the lifetime @α@. type Borrow :: BorrowKind -> Lifetime -> Type -> Type-type Borrow bk = Alias ('Borrow bk)+type Borrow bk α = Alias ('Borrow bk α) -- | Mutable borrower, which is affine and can update the data. type Mut :: Lifetime -> Type -> Type-type Mut = Borrow 'Mut+type Mut α = Borrow 'Mut α assocBorrowR :: Borrow bk ((α /\ β) /\ γ) a %1 ->@@ -283,10 +368,10 @@ assocBorrowL = coerceLin assocBorrowEq ::- forall bk α β γ a.- (Borrow bk ((α /\ β) /\ γ) a) :~: (Borrow bk (α /\ (β /\ γ)) a)+ forall (bk :: BorrowKind) α β γ a.+ Borrow bk ((α /\ β) /\ γ) a :~: Borrow bk (α /\ (β /\ γ)) a {-# INLINE assocBorrowEq #-}-assocBorrowEq = Unsafe.coerce $ Refl @(Borrow bk (α /\ β /\ γ) a)+assocBorrowEq = Unsafe.coerce $ Refl @(Borrow bk ((α /\ β) /\ γ) a) assocLendR :: Lend ((α /\ β) /\ γ) a %1 ->@@ -307,21 +392,24 @@ instance (bk ~ 'Mut) => LinearOnly (Borrow bk α a) where linearOnly = UnsafeLinearOnly -deriving via AsAffine (Borrow bk α a) instance Consumable (Borrow bk α a)+deriving via+ AsAffine (Alias bor a)+ instance+ (bor ~ ('Borrow bk α)) => Consumable (Alias bor a) -- | Shared borrower, which is unrestricted but usually can only read from the data. type Share :: Lifetime -> Type -> Type-type Share = Borrow 'Share+type Share α = Borrow 'Share α -instance Affine (Borrow bk α a) where+instance (ak ~ 'Borrow bk α) => Affine (Alias ak a) where aff = UnsafeAff {-# INLINE aff #-} -instance (k ~ 'Borrow 'Share) => Dupable (Alias k α a) where+instance (k ~ 'Borrow 'Share α) => Dupable (Alias k a) where dup2 = Unsafe.toLinear $ NonLinear.join (,) {-# INLINE dup2 #-} -instance (k ~ 'Borrow 'Share) => Movable (Alias k α a) where+instance (k ~ 'Borrow 'Share α) => Movable (Alias k a) where move = Unsafe.toLinear Ur {-# INLINE move #-} @@ -336,7 +424,7 @@ -- | Lender, which can retrieve the lifetime at the lifetime @α@. type Lend :: Lifetime -> Type -> Type-type Lend = Alias 'Lend+type Lend α = Alias ('Lend α) instance (α <= β, a <: b) => Lend α a <: Lend β b where subtype = UnsafeSubtype@@ -367,6 +455,105 @@ reborrow = Unsafe.toLinear \ !mutA -> (Data.Coerce.coerce mutA, Data.Coerce.coerce mutA) +{- |+Run and discard the result of a continuation with a representation-identical+borrow narrowed to a fresh sublifetime, then, on normal return, restore the+original mutable borrow.++This is the trusted non-finalizing delimiter used by the scalar public+result-discarding combinators. The rank-2 continuation cannot return its+private @β@ at a caller-nameable lifetime; existentially hiding it supplies no+ambient outlives evidence. The outer 'Mut' is retained only inside this+function while the continuation runs. The continuation result is consumed+before the outer borrow is restored. The continuation and state token are each+consumed exactly once. Since the lifetime indices have runtime-erased+representations, no runtime lifetime token or lender is required.++The borrow is handed back through 'reviveAlias' rather than returned directly;+see Note [Restoring a borrow must break its Core identity] there.+-}+unsafeBorrowScope_ ::+ forall bk α α' a r.+ (Consumable r) =>+ Mut α a %1 ->+ (forall β. Borrow bk (β /\ α) a %(BorrowMultiplicity bk) -> BO (β /\ α') r) %1 ->+ BO α' (Mut α a)+{-# INLINE unsafeBorrowScope_ #-}+unsafeBorrowScope_ = Unsafe.toLinear2 \mut k ->+ unsafeSrunBO_ Control.do+ r <- k (unsafeCastAlias mut)+ -- @consume r@ stays in the returned value rather than in the action, so it runs when the caller forces the restored borrow.+ -- Sequencing it at scope exit instead would make this delimiter stricter than the one @+slow@ restores, and the two are required to stay observationally equivalent.+ restored <- reviveAlias mut+ Control.pure (consume r `lseq` restored)++{- |+Run a continuation with a representation-identical borrow narrowed to a fresh+sublifetime, then, on normal return, restore the original mutable borrow+alongside the continuation's result.++This is the trusted delimiter used by the scalar public result-returning+combinators, and every obligation discharged in 'unsafeBorrowScope_' is+discharged here in the same way. The result type is fixed by the caller, so it+cannot mention the private @β@ and no borrow at @β@ escapes in it.+-}+unsafeBorrowScope ::+ forall bk α α' a r.+ Mut α a %1 ->+ (forall β. Borrow bk (β /\ α) a %(BorrowMultiplicity bk) -> BO (β /\ α') r) %1 ->+ BO α' (r, Mut α a)+{-# INLINE unsafeBorrowScope #-}+unsafeBorrowScope = Unsafe.toLinear2 \mut k ->+ unsafeSrunBO_ Control.do+ r <- k (unsafeCastAlias mut)+ (r,) Control.<$> reviveAlias mut++{- |+The finalizing variant of 'unsafeBorrowScope': the continuation returns its+result 'After' the sublifetime, and this discharges that 'After' before+restoring the original mutable borrow.++Beyond the obligations of 'unsafeBorrowScope', the 'EndToken' supplied to+'withEnd' is the runtime-erased one. That is sound for the same reason it is in+'Control.Monad.Borrow.Pure.BO.srunBO': the continuation has already returned, so+the sublifetime it was typechecked in is over by the time the token is applied,+and the caller-fixed result type cannot mention that lifetime.+-}+unsafeBorrowScope' ::+ forall bk α α' a r.+ Mut α a %1 ->+ (forall β. Borrow bk (β /\ α) a %(BorrowMultiplicity bk) -> BO (β /\ α') (After β r)) %1 ->+ BO α' (r, Mut α a)+{-# INLINE unsafeBorrowScope' #-}+unsafeBorrowScope' = Unsafe.toLinear2 \mut k ->+ unsafeSrunBO_ Control.do+ after <- k (unsafeCastAlias mut)+ (withEnd UnsafeEnd after,) Control.<$> reviveAlias mut++type BorrowMultiplicity :: BorrowKind -> Multiplicity+type family BorrowMultiplicity bk where+ BorrowMultiplicity 'Mut = One+ BorrowMultiplicity 'Share = Many++{- |+Run a rank-2 'BO' action in a statically delimited fresh sublifetime without+constructing a runtime lifetime token.++The action is typechecked parametrically for every private lifetime, so it+cannot rely on the implementation's erased instantiation at the ambient+lifetime or return a borrow at a caller-nameable lifetime. Existentially hiding+the private lifetime supplies no evidence needed to use such a borrow in an+ambient 'BO'. The state-token coercion executes the action exactly once. This+is the non-finalizing analogue of 'srunBO'; it cannot eliminate 'After' or+provide 'End' evidence.+-}+unsafeSrunBO_ ::+ forall β a.+ (forall α. BO (α /\ β) a) %1 ->+ BO β a+{-# INLINE unsafeSrunBO_ #-}+unsafeSrunBO_ action = unsafeCastBO (action @β)+ -- | Collapse a borrower to a mutable borrower. joinMut :: Borrow bk α (Mut β a) %1 -> Borrow bk (α /\ β) a joinMut = coerceLin@@ -376,16 +563,16 @@ -- | Distribute an alias over a functor. class DistributesAlias f where- split_ :: Alias ak α (f x) %1 -> f (Alias ak α x)+ split_ :: Alias ak (f x) %1 -> f (Alias ak x) default split_ :: (GenericDistributesAlias f) =>- Alias ak α (f x) %1 -> f (Alias ak α x)+ Alias ak (f x) %1 -> f (Alias ak x) split_ = genericSplit split ::- forall f x ak α.+ forall f x ak. (DistributesAlias f) =>- Alias ak α (f x) %1 -> f (Alias ak α x)+ Alias ak (f x) %1 -> f (Alias ak x) {-# INLINE [1] split #-} split = split_ @@ -413,11 +600,11 @@ deriving anyclass instance DistributesAlias Mon.Last -splitPair :: Alias ak α (a, b) %1 -> (Alias ak α a, Alias ak α b)+splitPair :: Alias ak (a, b) %1 -> (Alias ak a, Alias ak b) {-# INLINE splitPair #-} splitPair = coerceLin -splitEither :: Alias ak α (Either a b) %1 -> Either (Alias ak α a) (Alias ak α b)+splitEither :: Alias ak (Either a b) %1 -> Either (Alias ak a) (Alias ak b) {-# INLINE splitEither #-} splitEither = coerceLin @@ -432,16 +619,16 @@ type GenericDistributesAlias f = (Generic1 f, GDistributeAlias (Rep1 f)) genericSplit ::- forall f x ak α.+ forall f x ak. (GenericDistributesAlias f) =>- Alias ak α (f x) %1 -> f (Alias ak α x)+ Alias ak (f x) %1 -> f (Alias ak x) {-# INLINE genericSplit #-} genericSplit = to1 . gdistributeAlias @(Rep1 f) . unsafeMapAlias from1 -unsafeMapAlias :: (a %1 -> b) %1 -> Alias ak α a %1 -> Alias ak α b+unsafeMapAlias :: (a %1 -> b) %1 -> Alias ak a %1 -> Alias ak b {-# INLINE unsafeMapAlias #-} unsafeMapAlias f = coerceLin (\x -> let !y = f x in y) @@ -450,7 +637,7 @@ split_ = Generically1 . genericSplit . unsafeMapAlias \(Generically1 f) -> f class GDistributeAlias f where- gdistributeAlias :: Alias ak α (f x) %1 -> f (Alias ak α x)+ gdistributeAlias :: Alias ak (f x) %1 -> f (Alias ak x) instance ( GDistributeAlias f
src/Control/Monad/Borrow/Pure/BO/Unsafe.hs view
@@ -23,6 +23,8 @@ Alias (..), unsafeUnalias, unsafeMapAlias,+ unsafeCastAlias,+ reviveAlias, -- * Conversions from/to 'BO' monad. unsafeBOToLinIO,
src/Control/Monad/Borrow/Pure/Clone.hs view
@@ -53,7 +53,7 @@ deriving newtype (Copyable) instance (Copyable a) => Clone (AsCopyable a) where- clone = Control.pure . copy+ clone borrowed = Control.pure $! copy borrowed {-# INLINE clone #-} deriving via AsCopyable Int instance Clone Int
src/Control/Monad/Borrow/Pure/Copyable.hs view
@@ -22,9 +22,9 @@ {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE NoImplicitPrelude #-} {-# OPTIONS_GHC -Wno-name-shadowing #-}+{-# OPTIONS_GHC -Wno-orphans #-} {-# OPTIONS_GHC -Wno-partial-type-signatures #-} {-# OPTIONS_GHC -Wno-redundant-constraints #-}-{-# OPTIONS_HADDOCK hide #-} module Control.Monad.Borrow.Pure.Copyable ( Copyable (..),@@ -42,6 +42,7 @@ import Control.Monad.Borrow.Pure.BO.Internal import Control.Monad.Borrow.Pure.Utils (coerceLin) import Data.Array.Mutable.Linear (Array)+import Data.Complex import Data.Int import Data.Kind (Constraint, Type) import Data.Semigroup qualified as Sem@@ -52,8 +53,18 @@ import Numeric.Natural (Natural) import Prelude.Linear import Prelude.Linear.Unsatisfiable (Unsatisfiable, unsatisfiable)+import Unsafe.Linear qualified as Unsafe +-- | Values that can be copied from a live borrow. class Copyable a where+ {- | Copy the borrowed value.++ Evaluating this method must complete the copy and return the result in+ weak head normal form. It must not return a thunk whose evaluation depends+ on the borrow remaining live. Composite instances must complete their+ component copies as well, so copying a recursive value traverses its finite+ structure before returning.+ -} copy :: Borrow bk α a %1 -> a instance Copyable (Ur a) where@@ -110,36 +121,61 @@ deriving via UnsafeAssumeNoVar Bool instance Copyable Bool +instance (Copyable a) => Copyable (Complex a) where+ copy = \(UnsafeAlias (!real :+ !imaginary)) ->+ let !realCopy = copy (UnsafeAlias real)+ !imaginaryCopy = copy (UnsafeAlias imaginary)+ in realCopy :+ imaginaryCopy+ {-# INLINE copy #-}++instance Consumable (Complex Double) where+ consume = Unsafe.toLinear \_ -> ()+ {-# INLINE consume #-}++instance Dupable (Complex Double) where+ dup2 = Unsafe.toLinear \value -> (value, value)+ {-# INLINE dup2 #-}++instance Movable (Complex Double) where+ move = Unsafe.toLinear \ !value -> Ur value+ {-# INLINE move #-}++deriving via Generically1 Complex instance Copyable1 Complex+ type GenericCopyable a = (Generic a, GCopyable (Rep a)) genericCopy :: (GenericCopyable a) => Borrow bk α a %1 -> a {-# INLINE genericCopy #-}-genericCopy (UnsafeAlias x) = to (gcopy (UnsafeAlias (from x)))+genericCopy (UnsafeAlias x) =+ let !representation = gcopy (UnsafeAlias (from x))+ in to $! representation type GCopyable :: forall {k}. (k -> Type) -> Constraint class GCopyable f where gcopy :: Borrow bk α (f x) %1 -> f x instance (Copyable a) => GCopyable (K1 i a) where- gcopy = \(UnsafeAlias (K1 !a)) -> K1 (copy (UnsafeAlias a))+ gcopy = \(UnsafeAlias (K1 !a)) -> K1 $! copy (UnsafeAlias a) {-# INLINE gcopy #-} instance (GCopyable f, GCopyable g) => GCopyable (f :*: g) where gcopy (UnsafeAlias (!f :*: !g)) =- gcopy (UnsafeAlias f) :*: gcopy (UnsafeAlias g)+ let !fCopy = gcopy (UnsafeAlias f)+ !gCopy = gcopy (UnsafeAlias g)+ in fCopy :*: gCopy instance (GCopyable f) => GCopyable (M1 i c f) where gcopy = \case- UnsafeAlias (M1 !x) -> M1 (gcopy (UnsafeAlias x))+ UnsafeAlias (M1 !x) -> M1 $! gcopy (UnsafeAlias x) instance (GCopyable f) => GCopyable (MP1 m f) where gcopy = \case- UnsafeAlias (MP1 !x) -> MP1 (gcopy (UnsafeAlias x))+ UnsafeAlias (MP1 !x) -> MP1 $! gcopy (UnsafeAlias x) instance (GCopyable f, GCopyable g) => GCopyable (f :+: g) where gcopy = \case- UnsafeAlias (L1 !x) -> L1 (gcopy (UnsafeAlias x))- UnsafeAlias (R1 !x) -> R1 (gcopy (UnsafeAlias x))+ UnsafeAlias (L1 !x) -> L1 $! gcopy (UnsafeAlias x)+ UnsafeAlias (R1 !x) -> R1 $! gcopy (UnsafeAlias x) instance GCopyable U1 where gcopy = \case@@ -149,7 +185,10 @@ gcopy = \case {} . unsafeUnalias instance (GenericCopyable a) => Copyable (Generically a) where- copy = Generically . genericCopy . unsafeMapAlias (\(Generically x) -> x)+ copy borrow =+ Generically $!+ genericCopy+ (unsafeMapAlias (\(Generically x) -> x) borrow) deriving via Generically () instance Copyable () @@ -214,18 +253,27 @@ newtype AsCopyable1 f a = AsCopyable1 (f a) instance (Copyable1 f, Copyable a) => Copyable (AsCopyable1 f a) where- copy = AsCopyable1 . copy1 . unsafeMapAlias \(AsCopyable1 x) -> x+ copy borrow =+ AsCopyable1 $!+ copy1+ (unsafeMapAlias (\(AsCopyable1 x) -> x) borrow) {-# INLINE copy #-} -- | Lifting of the 'Copyable' operation to unary type constructors. class Copyable1 f where+ {- | Copy every contained value and force each result to weak head normal+ form before returning. Recursive structures are traversed before the borrow+ can end.+ -} liftCopy :: (Borrow bk α a %1 -> b) -> Borrow bk α (f a) %1 -> f b type GenericCopyable1 f = (Copyable1 (Rep1 @Type f), Generic1 f) -genericLiftCopy :: forall f bk a b α. (GenericCopyable1 f) => (Borrow bk α a %1 -> b) -> Borrow bk α (f a) %1 -> f b+genericLiftCopy :: forall f bk α a b. (GenericCopyable1 f) => (Borrow bk α a %1 -> b) -> Borrow bk α (f a) %1 -> f b {-# INLINE genericLiftCopy #-}-genericLiftCopy f (UnsafeAlias x) = to1 $ liftCopy f (UnsafeAlias $ from1 x)+genericLiftCopy f (UnsafeAlias x) =+ let !representation = liftCopy f (UnsafeAlias $ from1 x)+ in to1 $! representation genericCopy1 :: forall f a α. (GenericCopyable1 f, Copyable a) => Share α (f a) %1 -> f a {-# INLINE genericCopy1 #-}@@ -233,22 +281,28 @@ copy1 :: (Copyable1 f, Copyable a) => Borrow bk α (f a) %1 -> f a {-# INLINE copy1 #-}-copy1 = liftCopy copy+copy1 borrow =+ let !copied = liftCopy copy borrow+ in copied instance (GenericCopyable1 f) => Copyable1 (Generically1 @Type f) where- liftCopy f = Generically1 . genericLiftCopy f . coerceLin+ liftCopy f borrow =+ Generically1 $! genericLiftCopy f (coerceLin borrow) {-# INLINE liftCopy #-} instance (Copyable c) => Copyable1 (K1 i c) where- liftCopy _ = coerceLin $! copy @c+ liftCopy _ borrow =+ K1 $! copy @c (coerceLin borrow) {-# INLINE liftCopy #-} instance Copyable1 Par1 where- liftCopy f = Par1 . f . coerceLin+ liftCopy f borrow =+ Par1 $! f (coerceLin borrow) {-# INLINE liftCopy #-} instance (Copyable1 f) => Copyable1 (M1 i c f) where- liftCopy f = M1 . liftCopy f . coerceLin+ liftCopy f borrow =+ M1 $! liftCopy f (coerceLin borrow) {-# INLINE liftCopy #-} instance (Copyable1 l, Copyable1 r) => Copyable1 (l :*: r) where@@ -259,8 +313,8 @@ {-# INLINE liftCopy #-} instance (Copyable1 f, Copyable1 g) => Copyable1 (f :.: g) where- liftCopy f = \(UnsafeAlias (Comp1 x)) ->- Comp1 . liftCopy (liftCopy f) $ UnsafeAlias x+ liftCopy f = \(UnsafeAlias (Comp1 !x)) ->+ Comp1 $! liftCopy (liftCopy f) (UnsafeAlias x) {-# INLINE liftCopy #-} instance (Copyable1 l, Copyable1 r) => Copyable1 (l :+: r) where@@ -269,6 +323,15 @@ R1 !r -> R1 $! (liftCopy f (UnsafeAlias r)) {-# INLINE liftCopy #-} +instance Copyable1 U1 where+ liftCopy _ = \case+ UnsafeAlias U1 -> U1+ {-# INLINE liftCopy #-}++instance Copyable1 V1 where+ liftCopy _ = \case {} . unsafeUnalias+ {-# INLINE liftCopy #-}+ {- | A variant of 'copy' that returns 'Ur' wrapped copy of the value. 'Ur' wrapper was not necessary because 'Share' is always introduced unrestricted, whereas 'Mut' is introduced linearly, so it is convenient to have 'Ur' wrapped version.@@ -277,4 +340,4 @@ {-# INLINE copyMut #-} copyMut mut = let !(Ur shr) = share mut- in Ur (copy shr)+ in Ur $! copy shr
src/Control/Monad/Borrow/Pure/Experimental/Borrows.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE BlockArguments #-}+{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE DeriveAnyClass #-}@@ -10,46 +11,275 @@ {-# LANGUAGE PartialTypeSignatures #-} {-# LANGUAGE QualifiedDo #-} {-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RoleAnnotations #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeAbstractions #-}+{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE NoImplicitPrelude #-} {-# OPTIONS_GHC -Wno-name-shadowing #-} {-# OPTIONS_GHC -Wno-redundant-constraints #-} {- |-The module provides 'Borrows', which is a heterogeneous list of 'Borrow's in the same lifetime.+The module provides 'Aliases', which is a heterogeneous list of 'Alias'es in the same lifetime. -} module Control.Monad.Borrow.Pure.Experimental.Borrows (- Borrows (..),+ Aliases (..),+ Muts,+ Shares,+ Borrows,+ Lends,+ reborrows,+ reborrowings',+ reborrowings,+ reborrowings_,+ reviveAliases, ) where import Control.Functor.Linear qualified as Control+import Control.Monad qualified as NonLinear import Control.Monad.Borrow.Pure.Affine import Control.Monad.Borrow.Pure.Affine.Unsafe (unsafeAff) import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.BO.Internal import Control.Monad.Borrow.Pure.Experimental.Reborrowable+import Control.Monad.Borrow.Pure.Lifetime.Token.Unsafe qualified as Unsafe.Token import Data.Coerce.Directed.Unsafe import Data.Kind import Prelude.Linear hiding (foldMap)+import Unsafe.Coerce (unsafeCoerce) import Unsafe.Linear qualified as Unsafe -type Borrows :: BorrowKind -> Lifetime -> [Type] -> Type-data Borrows bk α xs where- BNil :: Borrows bk α '[]- (:-) :: !(Borrow bk α x) %1 -> !(Borrows bk α xs) %1 -> Borrows bk α (x ': xs)+#ifdef PURE_BORROW_SLOW_SCOPES+import Control.Syntax.DataFlow qualified as DataFlow+#endif +type Aliases :: AliasKind -> [Type] -> Type+data Aliases k xs where+ BNil :: Aliases k '[]+ (:-) :: !(Alias k x) %1 -> !(Aliases k xs) %1 -> Aliases k (x ': xs)++type role Aliases nominal nominal+ infixr 5 :- -instance Affine (Borrows bk α xs) where+type Lends :: Lifetime -> [Type] -> Type+type Lends α = Aliases ('Lend α)++type Borrows :: BorrowKind -> Lifetime -> [Type] -> Type+type Borrows bk α = Aliases ('Borrow bk α)++type Muts :: Lifetime -> [Type] -> Type+type Muts α = Borrows 'Mut α++type Shares :: Lifetime -> [Type] -> Type+type Shares α = Borrows 'Share α++{- |+Only borrow bundles are affine; a @'Lends' α xs@ must not be discardable.+Dropping a bundle of lenders would abandon the owners it holds, and a lender is the sole capability to 'reclaim' one.+The @k ~ \'Borrow bk α@ constraint therefore excludes @\'Lend α@, exactly as the scalar @'Affine' ('Alias' ak a)@ instance does.+-}+instance (k ~ 'Borrow bk α) => Affine (Aliases k xs) where aff = unsafeAff+ {-# INLINE aff #-} -deriving via AsAffine (Borrows bk α xs) instance Consumable (Borrows bk α xs)+deriving via+ AsAffine (Aliases k xs)+ instance+ (k ~ 'Borrow bk α) =>+ Consumable (Aliases k xs) -instance (β <= α) => Borrows bk α xs <: Borrows bk' β xs where+instance (k ~ 'Borrow 'Share α) => Dupable (Aliases k xs) where+ dup2 = Unsafe.toLinear $ NonLinear.join (,)+ {-# INLINE dup2 #-}++instance (k ~ 'Borrow 'Share α) => Movable (Aliases k xs) where+ move = Unsafe.toLinear Ur+ {-# INLINE move #-}++instance (α >= β, xs <: ys, ys <: xs) => Muts α xs <: Muts β ys where subtype = UnsafeSubtype -instance Reborrowable (Borrows bk) where- locally' = Unsafe.toLinear \bors k -> Control.do- (,bors) Control.<$> srunBO (k $ upcast bors)+instance (α >= β, xs <: ys) => Shares α xs <: Shares β ys where+ subtype = UnsafeSubtype++instance (α <= β, a <: b) => Lends α a <: Lends β b where+ subtype = UnsafeSubtype++instance Reborrowable (Muts α) where+ type LifetimeOf (Muts α) = α+ type WithLifetime (Muts α) β = Muts β+ locally' = reborrowings' {-# INLINE locally' #-}+ locally = reborrowings+ {-# INLINE locally #-}+ locally_ = reborrowings_+ {-# INLINE locally_ #-}++-- | A plural form of 'reborrow', which reborrows multiple borrows in the given 'Muts' at once.+reborrows :: forall β α a. (α >= β) => Muts α a %1 -> (Muts β a, Lend β (Muts α a))+reborrows = Unsafe.toLinear \v -> (unsafeCoerce v, unsafeCoerce v)++{- | Return a bundle of borrows to the caller of a delimiter, through a barrier the optimizer cannot see through.++This is the plural counterpart of 'Control.Monad.Borrow.Pure.BO.Unsafe.reviveAlias', and it exists for the same reason.+See Note [Restoring a borrow must break its Core identity] in "Control.Monad.Borrow.Pure.BO.Internal".++'reborrowings'' would otherwise restore the caller's own occurrence, since 'reborrows' hands the same value out as both borrow and lender and 'reclaim' is a newtype unwrap.+It happens not to misbehave today, because 'reclaim'' is reached through 'withEnd', whose @withDict@ desugars through the wired-in @nospec@ and survives every Core-to-Core pass — but that is a coincidence of one desugaring, and it is exactly the kind of accident the Note argues a delimiter must not rest on.++This is exported so that the Core obligations in @pure-borrow-inspection@ can state what the erased plural delimiters must compile to, barrier included.+Exporting it weakens nothing: it is 'Control.Functor.Linear.pure' behind an @OPAQUE@, so the worst a caller can do with it is add a barrier that was not needed.+-}+reviveAliases :: Aliases k xs %1 -> BO α (Aliases k xs)+{-# OPAQUE reviveAliases #-}+reviveAliases as = Control.pure as++{- |+Retag a bundle's lifetime, leaving its borrow kind, spine, order and payloads+untouched.++The plural counterpart of 'Control.Monad.Borrow.Pure.BO.Unsafe.unsafeCastAlias',+and unlike it this cannot be a @coerceLin@: 'Aliases' is a GADT rather than a+newtype over its payload, so no 'Data.Coerce.Coercible' relates two alias kinds+and the coercion has to be a raw one.++Why that raw coercion is representationally sound: @k@ occurs in 'Aliases' only+inside the @!('Alias' k x)@ field of @(':-')@, and 'Alias' is a newtype over+@x@, so the alias kind has no runtime witness anywhere in the structure and two+kinds give the same layout. That is precisely the argument @type role Aliases+nominal nominal@ declines to make on the caller's behalf, and this function+punches through it, so the argument has to be made here instead.++The caller's obligation is the narrower one the signature now enforces in part:+the retagged bundle must be a lifetime narrowing the type system would have+permitted, and it must not escape the scope that narrowed it. Keeping the+borrow kind fixed in the type is deliberate — every use in this module narrows+a lifetime and nothing more, and a future edit that retagged a 'Lends' bundle+as a 'Muts' one should be a type error rather than a silence.+-}+unsafeCastAliases :: Borrows bk α xs %1 -> Borrows bk β xs+{-# INLINE unsafeCastAliases #-}+unsafeCastAliases = Unsafe.toLinear unsafeCoerce++{-+Note [The plural delimiters are separate functions]+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+These three mirror `unsafeBorrowScope`, `unsafeBorrowScope_` and+`unsafeBorrowScope'` line for line, and discharge exactly the obligations+documented there; only the retagging and the restoration barrier differ, because+`Muts α xs` is `Aliases ('Borrow 'Mut α) xs` rather than an `Alias`.++They are not written as one delimiter shared with the scalar case.+A shared one would have to abstract over the type constructor and over the+lifetime retagging, which is what `Reborrowable`'s `WithLifetime` does -- and+instantiating an unsafe coercion at that class would make it available to+third-party instances that need not use this representation at all.+That is the coercion the design forbids, so per-representation delimiters plus+instance-supplied methods is the shape, and the duplication is the price.++They are deliberately not exported.+A trusted delimiter belongs behind the combinators that discharge its+obligations, and `Experimental.Borrows` is a documented module rather than an+`.Internal` one.+-}++{- |+The plural non-finalizing delimiter, restoring the bundle on normal return.++See Note [The plural delimiters are separate functions] and the obligations on+'Control.Monad.Borrow.Pure.BO.Unsafe.unsafeBorrowScope'.+-}+unsafeBorrowsScope ::+ forall α α' xs r.+ Muts α xs %1 ->+ (forall β. Muts (β /\ α) xs %1 -> BO (β /\ α') r) %1 ->+ BO α' (r, Muts α xs)+{-# INLINE unsafeBorrowsScope #-}+unsafeBorrowsScope = Unsafe.toLinear2 \muts k ->+ unsafeSrunBO_ Control.do+ r <- k (unsafeCastAliases muts)+ (r,) Control.<$> reviveAliases muts++{- |+The plural result-discarding delimiter.++The result is consumed in the returned value rather than at scope exit, so this+stays observationally equivalent to the implementation @+slow@ restores.+-}+unsafeBorrowsScope_ ::+ forall α α' xs r.+ (Consumable r) =>+ Muts α xs %1 ->+ (forall β. Muts (β /\ α) xs %1 -> BO (β /\ α') r) %1 ->+ BO α' (Muts α xs)+{-# INLINE unsafeBorrowsScope_ #-}+unsafeBorrowsScope_ = Unsafe.toLinear2 \muts k ->+ unsafeSrunBO_ Control.do+ r <- k (unsafeCastAliases muts)+ restored <- reviveAliases muts+ Control.pure (consume r `lseq` restored)++{- |+The plural finalizing delimiter, whose continuation returns its result 'After'+the sublifetime.++The 'EndToken' is the runtime-erased one, sound for the reason given on+'Control.Monad.Borrow.Pure.BO.Unsafe.unsafeBorrowScope'': the continuation has+returned by the time it is applied, so the sublifetime it was typechecked in is+over, and the caller-fixed result type cannot mention it.+-}+unsafeBorrowsScope' ::+ forall α α' xs r.+ Muts α xs %1 ->+ (forall β. Muts (β /\ α) xs %1 -> BO (β /\ α') (After β r)) %1 ->+ BO α' (r, Muts α xs)+{-# INLINE unsafeBorrowsScope' #-}+unsafeBorrowsScope' = Unsafe.toLinear2 \muts k ->+ unsafeSrunBO_ Control.do+ after <- k (unsafeCastAliases muts)+ (withEnd Unsafe.Token.UnsafeEnd after,) Control.<$> reviveAliases muts++-- | A plural form of 'reborrowing''.+reborrowings' ::+ Muts α a %1 ->+ (forall β. Muts (β /\ α) a %1 -> BO (β /\ α') (After β r)) %1 ->+ BO α' (r, Muts α a)+{-# INLINE reborrowings' #-}+#ifdef PURE_BORROW_SLOW_SCOPES+reborrowings' v k = Control.do+ (r, restored) <- srunBO DataFlow.do+ (v, lend) <- reborrows v+ Control.do+ v <- k v+ Control.pure $ (,) Control.<$> v Control.<*> upcast (reclaim' lend)+ (r,) Control.<$> reviveAliases restored+#else+reborrowings' = unsafeBorrowsScope'+#endif++-- | A plural form of 'reborrowing'.+reborrowings ::+ Muts α a %1 ->+ (forall β. Muts (β /\ α) a %1 -> BO (β /\ α') r) %1 ->+ BO α' (r, Muts α a)+{-# INLINE reborrowings #-}+#ifdef PURE_BORROW_SLOW_SCOPES+reborrowings mutα k = reborrowings' mutα (\mut -> Control.pure Control.<$> k mut)+#else+reborrowings = unsafeBorrowsScope+#endif++-- | A plural form of 'reborrowing_'.+reborrowings_ ::+ (Consumable r) =>+ Muts α a %1 ->+ (forall β. Muts (β /\ α) a %1 -> BO (β /\ α') r) %1 ->+ BO α' (Muts α a)+{-# INLINE reborrowings_ #-}+#ifdef PURE_BORROW_SLOW_SCOPES+reborrowings_ mutα k = reborrowings mutα (Control.fmap consume . k) Control.<&> \((), a) -> a+#else+reborrowings_ = unsafeBorrowsScope_+#endif
src/Control/Monad/Borrow/Pure/Experimental/Loop.hs view
@@ -21,9 +21,14 @@ {- | This module provides 'Foldable' class, and provides a way to loop through it while reborrowing existing 'Borrow's into sublifetime. The module also introduces 'Borrows', which is a heterogeneous list of 'Borrow's in the same lifetime.++For performance-sensitive code, choose the loop's algorithmic structure before+trying to avoid sublifetimes. If a loop only reads a stable resource, share it+once outside the loop, capture the resulting 'Share', and use 'subShare' inside+the iteration. Keep the loop worker @INLINE@ or @INLINABLE@ so 'Ur'-boxed reads+can be eliminated by the optimiser. -} module Control.Monad.Borrow.Pure.Experimental.Loop (- Borrows (..), forReborrowing, forReborrowingOf_, forReborrowing_,@@ -43,12 +48,12 @@ GenericFoldable, genericFoldMap, ifoldMapDefault,+ iterReborrowing_, ) where import Control.Functor.Linear qualified as Control import Control.Monad.Borrow.Pure import Control.Monad.Borrow.Pure.BO.Unsafe-import Control.Monad.Borrow.Pure.Experimental.Borrows import Control.Monad.Borrow.Pure.Experimental.Reborrowable import Control.Monad.Borrow.Pure.Utils (coerceLin) import Data.Bifunctor.Linear qualified as Bi@@ -69,14 +74,14 @@ -} forReborrowing :: (Data.Traversable t, Reborrowable bor) =>- bor α xs %1 ->+ bor xs %1 -> t b %1 -> ( forall β.- bor (β /\ α) xs %1 ->+ WithLifetime bor (β /\ LifetimeOf bor) xs %1 -> b %1 -> BO (β /\ α) c ) ->- BO α (t c, bor α xs)+ BO α (t c, bor xs) {-# INLINE forReborrowing #-} forReborrowing bors tb k = flip Control.runStateT bors $@@ -145,14 +150,14 @@ forReborrowingOf_ :: (Reborrowable bor) => Fold s a %1 ->- bor α xs %1 ->+ bor xs %1 -> s %1 -> ( forall β.- bor (β /\ α) xs %1 ->+ WithLifetime bor (β /\ LifetimeOf bor) xs %1 -> a %1 -> BO (β /\ α) () ) ->- BO α (bor α xs)+ BO α (bor xs) {-# INLINE forReborrowingOf_ #-} forReborrowingOf_ fld bors s k = flip Control.execStateT bors $@@ -162,29 +167,29 @@ forReborrowing_ :: (Foldable t, Reborrowable bor) =>- bor α xs %1 ->+ bor xs %1 -> t a %1 -> ( forall β.- bor (β /\ α) xs %1 ->+ WithLifetime bor (β /\ LifetimeOf bor) xs %1 -> a %1 -> BO (β /\ α) () ) ->- BO α (bor α xs)+ BO α (bor xs) {-# INLINE forReborrowing_ #-} forReborrowing_ = forReborrowingOf_ foldMap iforReborrowingOf_ :: (Reborrowable bor) => IndexedFold i s a %1 ->- bor α xs %1 ->+ bor xs %1 -> s %1 -> ( forall β.- bor (β /\ α) xs %1 ->+ WithLifetime bor (β /\ LifetimeOf bor) xs %1 -> i %1 -> a %1 -> BO (β /\ α) () ) ->- BO α (bor α xs)+ BO α (bor xs) {-# INLINE iforReborrowingOf_ #-} iforReborrowingOf_ fld bors s k = flip Control.execStateT bors $@@ -194,15 +199,15 @@ iforReborrowing_ :: (FoldableWithIndex i t, Reborrowable bor) =>- bor α xs %1 ->+ bor xs %1 -> t a %1 -> ( forall β.- bor (β /\ α) xs %1 ->+ WithLifetime bor (β /\ LifetimeOf bor) xs %1 -> i %1 -> a %1 -> BO (β /\ α) () ) ->- BO α (bor α xs)+ BO α (bor xs) {-# INLINE iforReborrowing_ #-} iforReborrowing_ = iforReborrowingOf_ ifoldMap @@ -314,3 +319,25 @@ instance FoldableWithIndex k (LHM.HashMap k) where ifoldMap f = foldMap (uncurry f) . unur . LHM.toList++iterReborrowing_ ::+ forall bor α xs.+ (Reborrowable bor) =>+ Int ->+ bor xs %1 ->+ ( forall β.+ Int ->+ WithLifetime bor (β /\ LifetimeOf bor) xs %1 ->+ BO (β /\ α) ()+ ) ->+ BO α (bor xs)+{-# INLINE iterReborrowing_ #-}+iterReborrowing_ n bor k = go bor 0+ where+ {-# INLINE go #-}+ go :: bor xs %1 -> Int -> BO α (bor xs)+ go !bor !i+ | i < n = Control.do+ bor <- locally_ bor \bor -> k i bor+ go bor (i + 1)+ | otherwise = Control.pure bor
src/Control/Monad/Borrow/Pure/Experimental/Reborrowable.hs view
@@ -8,20 +8,51 @@ {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeAbstractions #-}+{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE NoImplicitPrelude #-} {-# OPTIONS_GHC -Wno-name-shadowing #-} module Control.Monad.Borrow.Pure.Experimental.Reborrowable ( Reborrowable (..),- locally,- locally_, ) where import Control.Functor.Linear qualified as Control import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.BO.Unsafe (reviveAlias)+import Data.Kind (Constraint, Type) import Prelude.Linear -class Reborrowable bor where+{- |+Borrow-like values that can be narrowed to a sublifetime and restored afterwards.++=== The obligation every method carries++An implementation of 'locally'', 'locally' or 'locally_' must not hand the+caller back the occurrence it was given. It must return it through a barrier+the optimizer cannot see through and that consumes the 'BO' state token —+'Control.Monad.Borrow.Pure.BO.Unsafe.reviveAlias' for a scalar borrow,+'Control.Monad.Borrow.Pure.Experimental.Borrows.reviveAliases' for a bundle.++This is not a performance convention. Reads that project a mutable header do+not go through the state token, so to GHC two of them on the same borrow+/variable/ are the same expression, and common-subexpression elimination is+entitled to serve the second from the first — across every write the scope+performed. A delimiter that returns its caller's own binder makes a post-scope+read syntactically identical to a pre-scope one; the result is a stale length+and a stale buffer, and writing through them runs off the end of the+allocation. See @Note [Restoring a borrow must break its Core identity]@ in+"Control.Monad.Borrow.Pure.BO.Internal" for the full argument, and treat it as+binding on any instance you write.++Each method is separately overridable, so each one owes this independently:+supplying a fast 'locally' while leaving 'locally'' to the default does not+discharge it for 'locally'.+-}+type Reborrowable :: (k -> Type) -> Constraint+class (bor ~ WithLifetime bor (LifetimeOf bor)) => Reborrowable bor where+ type LifetimeOf bor :: Lifetime+ type WithLifetime bor (α :: Lifetime) :: k -> Type+ {- | Executes an operation on a borrow in sub lifetime. You may need @-XImpredicativeTypes@ extension to use this function.@@ -29,34 +60,72 @@ Generalization of 'reborrowing'' and 'sharing'' that works for both 'Mut' and 'Share' borrows. -} locally' ::- bor α a %1 ->- (forall β. bor (β /\ α) a %1 -> BO (β /\ α') (After β r)) %1 ->- BO α' (r, bor α a)+ bor a %1 ->+ (forall β. WithLifetime bor (β /\ LifetimeOf bor) a %1 -> BO (β /\ α') (After β r)) %1 ->+ BO α' (r, bor a) -instance Reborrowable Mut where- {-# SPECIALIZE instance Reborrowable Mut #-}+ {- |+ The non-finalizing form, whose continuation returns its result directly.++ This is a method rather than a function over 'locally'' so that an instance+ can supply a delimiter that never builds an 'After' at all. The default is+ the composition it replaces, so an existing instance keeps working and keeps+ its current cost; @'Mut'@, @'Share'@ and+ @'Control.Monad.Borrow.Pure.Experimental.Borrows.Muts'@ override it.+ -}+ locally ::+ bor a %1 ->+ (forall β. WithLifetime bor (β /\ LifetimeOf bor) a %1 -> BO (β /\ α') r) %1 ->+ BO α' (r, bor a)+ locally bor k = locally' bor \bor -> Control.pure Control.<$> k bor+ {-# INLINE locally #-}++ {- |+ The result-discarding form.++ The consumption of the result sits /in/ the returned value rather than being+ sequenced at scope exit, so it runs when the caller forces the restored+ borrow. That is deliberate: sequencing it at exit would make this stricter+ than the implementation @+slow@ restores, and the two are required to stay+ observationally equivalent. Linearity gives the restored borrow exactly one+ holder, so any use of it forces the consumption exactly once and first.+ -}+ locally_ ::+ (Consumable r) =>+ bor a %1 ->+ (forall β. WithLifetime bor (β /\ LifetimeOf bor) a %1 -> BO (β /\ α') r) %1 ->+ BO α' (bor a)+ locally_ bor k = uncurry lseq Control.<$> locally bor k+ {-# INLINE locally_ #-}++instance Reborrowable (Mut α) where+ type LifetimeOf (Mut α) = α+ type WithLifetime (Mut α) β = Mut β+ {-# SPECIALIZE instance Reborrowable (Mut α) #-} locally' = reborrowing' {-# INLINE locally' #-}+ locally = reborrowing+ {-# INLINE locally #-}+ locally_ = reborrowing_+ {-# INLINE locally_ #-} -instance Reborrowable Share where- {-# SPECIALIZE instance Reborrowable Share #-}+instance Reborrowable (Share α) where+ type LifetimeOf (Share α) = α+ type WithLifetime (Share α) β = Share β+ {-# SPECIALIZE instance Reborrowable (Share α) #-}++ -- 'move' for a shared borrow is the identity, so @sh@ is the caller's own occurrence and returning it directly would carry the defect in Note [Restoring a borrow must break its Core identity].+ -- Hand it back through 'reviveAlias' as the scalar delimiters do. locally' shr k = Control.do let %1 !(Ur sh) = move shr- (,sh) Control.<$> srunBO (k (upcast sh))+ r <- srunBO (k (upcast sh))+ (r,) Control.<$> reviveAlias sh {-# INLINE locally' #-} -locally ::- (Reborrowable bor) =>- bor α a %1 ->- (forall β. bor (β /\ α) a %1 -> BO (β /\ α') r) %1 ->- BO α' (r, bor α a)-{-# INLINE locally #-}-locally bor k = locally' bor \mut -> Control.pure Control.<$> k mut--locally_ ::- (Reborrowable bor, Consumable r) =>- bor α a %1 ->- (forall β. bor (β /\ α) a %1 -> BO (β /\ α') r) %1 ->- BO α' (bor α a)-{-# INLINE locally_ #-}-locally_ bor k = uncurry lseq Control.<$> locally bor k+ -- The same, through the non-finalizing 'srunBO_', so that a continuation+ -- which returns its result directly never builds an 'After' to discharge.+ locally shr k = Control.do+ let %1 !(Ur sh) = move shr+ r <- srunBO_ (k (upcast sh))+ (r,) Control.<$> reviveAlias sh+ {-# INLINE locally #-}
src/Control/Monad/Borrow/Pure/Lifetime/Token/Internal.hs view
@@ -106,16 +106,22 @@ {-# INLINE pure #-} After f <*> After r = After (f r) {-# INLINE (<*>) #-}+ liftA2 f (After a) (After b) = After (f a b)+ {-# INLINE liftA2 #-} instance Control.Applicative (After α) where pure a = After a {-# INLINE pure #-} After f <*> After r = After (f r) {-# INLINE (<*>) #-}+ liftA2 f (After a) (After b) = After (f a b)+ {-# INLINE liftA2 #-} instance Control.Monad (After α) where After r >>= k = After (unAfter (k r)) {-# INLINE (>>=) #-}+ After r >> After a = After (r `lseq` a)+ {-# INLINE (>>) #-} -- | Witness that the current computation is in a linear context. data Linearly = UnsafeLinearly
src/Control/Monad/Borrow/Pure/Utils.hs view
@@ -15,6 +15,18 @@ {-# INLINE coerceLin #-} coerceLin = Unsafe.toLinear Data.Coerce.coerce +{- | Drop a linearly bound value without consuming it.++This is for an /alias/ of a resource that some other owner is still+responsible for: consuming it would claim an ownership this scope does not+have, and holding it is impossible where the surrounding function has to+return. Every use is a proof obligation that the value really is an alias,+and that dropping it releases nothing -- otherwise it is exactly a leak.+-}+unsafeLeak :: a %1 -> ()+{-# INLINE unsafeLeak #-}+unsafeLeak = Unsafe.toLinear (\ !_ -> ())+ lseq# :: forall a (s :: UnliftedType). (Consumable a) => a %1 -> s %1 -> s {-# INLINE lseq# #-} lseq# a = case consume a of
src/Data/Coerce/Directed/Internal.hs view
@@ -106,3 +106,9 @@ genericUpcast :: (GenericSubtype a b) => a %1 -> b genericUpcast = to . gupcast . from++instance '[] <: ('[] :: [k]) where+ subtype = UnsafeSubtype++instance (a <: b, as <: bs) => (a ': as) <: (b ': bs) where+ subtype = UnsafeSubtype
+ src/Data/HashMap/RobinHood/Mutable/Linear.hs view
@@ -0,0 +1,58 @@+{-# LANGUAGE NoImplicitPrelude #-}++{- |+A linearly owned mutable hash table, using Robin Hood hashing with+backward-shift deletion.++The table linearly owns its backing store, so it must be threaded through+@%1 ->@ and consumed exactly once; its keys and values, by contrast, are+GC-owned and bound nonlinearly. That is why 'lookup' hands back a plain+@'Ur' ('Maybe' v)@ with no 'Movable' constraint, and why duplicating a table+copies only the slot array. A table over linearly owned values would need a+different representation, and is not what this module provides.++Every operation here is an ordinary linear function rather than a 'BO' action.+To mutate a table in place through a borrow — the usual shape once a table is+a field of some larger structure — use+"Data.HashMap.RobinHood.Mutable.Linear.Borrow", which keeps this table behind+a linear 'Data.Ref.Linear.Ref'.++This module is intended to be imported qualified.+-}+module Data.HashMap.RobinHood.Mutable.Linear (+ HashMap,+ Hashable,++ -- * Construction+ new,+ fromList,++ -- * Mutation+ insert,+ insertMany,+ delete,+ alter,+ alterF,++ -- * Suspended insertion+ InsertPlan,+ lookupForInsert,+ unsafeInsertPrepared,++ -- * Query+ lookup,+ member,+ size,+ capacity,++ -- * Iteration+ foldMapWithKey,+ toList,++ -- * Combining maps+ union,+) where++import Data.HashMap.RobinHood.Mutable.Linear.Internal+import Data.Hashable (Hashable)+import Prelude ()
+ src/Data/HashMap/RobinHood/Mutable/Linear/Borrow.hs view
@@ -0,0 +1,274 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}++{- |+A borrow-aware Robin Hood hash table, to be mutated as @'Mut' α ('HashMap' k v)@.++This is "Data.HashMap.RobinHood.Mutable.Linear" behind a linear+'Data.Ref.Linear.Ref'. The indirection is what makes the table usable through+a borrow at all: the owned table replaces its backing array when it grows, so+a mutation produces a new table value, and through a borrow there is nowhere+to thread that value back to. Writing it into the reference instead means a+growth is visible to every enclosing borrow without the enclosing structure+being rebuilt.++Keys and values are GC-owned, as in the underlying table. Consequently a+lookup returns @'Ur' ('Maybe' v)@ rather than a borrow of the stored value,+and duplicating a table copies only its slot array.++This module is intended to be imported qualified.+-}+module Data.HashMap.RobinHood.Mutable.Linear.Borrow (+ HashMap,+ Hashable,++ -- * Construction+ empty,+ fromList,++ -- * Mutation+ insert,+ delete,+ alter,+ alterF,++ -- * Suspended insertion+ InsertPlan,+ lookupForInsert,+ unsafeInsertPrepared,++ -- * Query+ size,+ lookup,+ member,++ -- * Iteration+ toList,++ -- * Bulk operations+ swap,+ take,+ take_,+ union,+ extend,+) where++import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure+import Control.Syntax.DataFlow qualified as DataFlow+import Data.Bifunctor.Linear qualified as Bi+import Data.Functor.Linear qualified as Data+import Data.HashMap.RobinHood.Mutable.Linear (Hashable)+import Data.HashMap.RobinHood.Mutable.Linear qualified as Raw+import Data.HashMap.RobinHood.Mutable.Linear.Borrow.Internal+import Data.Ref.Linear qualified as Ref+import Data.Ref.Linear.Borrow qualified as Ref+import Prelude.Linear hiding (insert, lookup, take)+import Prelude qualified as NonLinear++-- * Construction++-- | \(O(n)\). An empty table sized for at least the given number of entries.+empty :: forall k v. Int -> Linearly %1 -> HashMap k v+{-# INLINE empty #-}+empty size l =+ dup l & \(tableLinear, refLinear) ->+ HashMap (Ref.new (Raw.new size tableLinear) refLinear)++-- | \(O(n)\) amortized. Build a table from a list, later keys winning.+fromList :: (Hashable k) => [(k, v)] -> Linearly %1 -> HashMap k v+{-# INLINE fromList #-}+fromList dic l =+ dup l & \(tableLinear, refLinear) ->+ HashMap (Ref.new (Raw.fromList dic tableLinear) refLinear)++-- * Mutation++-- | \(O(1)\) amortized. Insert an entry, returning the value it displaced.+insert ::+ (Hashable k) =>+ k ->+ v ->+ Mut α (HashMap k v) %1 ->+ BO α (Ur (Maybe v), Mut α (HashMap k v))+{-# INLINE insert #-}+insert key !v !dic = Control.do+ (Ur mval, dic) <-+ Ref.update+ (\dic -> Control.pure $ Raw.insert key v dic)+ (coerceBor dic)+ Control.pure (Ur $ forceMay mval, recoerceBor dic)++-- | \(O(1)\) amortized. Remove a key, returning the value it held.+delete ::+ (Hashable k) =>+ k ->+ Mut α (HashMap k v) %1 ->+ BO α (Ur (Maybe v), Mut α (HashMap k v))+{-# INLINE delete #-}+delete key dic = Control.do+ (Ur mval, dic) <-+ Ref.update+ (\dic -> Control.pure $ Raw.delete key dic)+ (coerceBor dic)+ Control.pure (Ur $ forceMay mval, recoerceBor dic)++-- | \(O(1)\) amortized. Insert, update or delete the entry at a key.+alter ::+ (Hashable k) =>+ (Maybe v -> Maybe v) ->+ k ->+ Mut α (HashMap k v) %1 ->+ BO α (Mut α (HashMap k v))+{-# INLINE alter #-}+alter f k =+ Control.fmap recoerceBor+ . Ref.modify (Raw.alter f k)+ . coerceBor++-- | \(O(1)\) amortized. 'alter' with the replacement produced in 'BO'.+alterF ::+ (Hashable k) =>+ (Maybe v -> BO α (Ur (Maybe v))) ->+ k ->+ Mut α (HashMap k v) %1 ->+ BO α (Mut α (HashMap k v))+{-# INLINE alterF #-}+alterF f key dic = Control.do+ ((), dic) <-+ Ref.update+ ( Control.fmap ((),)+ . Raw.alterF (\ !may -> Data.fmap forceMay Control.<$> f (forceMay may)) key+ )+ (coerceBor dic)+ Control.pure $ recoerceBor dic++-- * Suspended insertion++{- | \(O(1)\) amortized. Look a key up, and on a miss suspend the probe.++Resume the returned plan with 'unsafeInsertPrepared' to insert without a+second traversal.+-}+lookupForInsert ::+ forall k v bk α.+ (Hashable k) =>+ k ->+ Borrow bk α (HashMap k v) %1 ->+ BO α (Ur (Either v (InsertPlan k)), Borrow bk α (HashMap k v))+{-# INLINE lookupForInsert #-}+lookupForInsert key = askRaw go+ where+ go :: Raw.HashMap k v %1 -> (Ur (Either v (InsertPlan k)), Raw.HashMap k v)+ go hm = case Raw.lookupForInsert key hm of+ (Ur result, hm) -> (Ur (NonLinear.fmap InsertPlan result), hm)++{- | \(O(1)\) amortized. Resume an unsuccessful 'lookupForInsert' as an insertion.++The table must not have been mutated since the plan was produced.+-}+unsafeInsertPrepared ::+ InsertPlan k ->+ v ->+ Mut α (HashMap k v) %1 ->+ BO α (Mut α (HashMap k v))+{-# INLINE unsafeInsertPrepared #-}+unsafeInsertPrepared (InsertPlan plan) !v =+ Control.fmap recoerceBor+ . Ref.modify (Raw.unsafeInsertPrepared plan v)+ . coerceBor++-- * Query++{-+Every query below consumes one occurrence of the borrow and hands the same+occurrence back, exactly as 'Data.Vector.Mutable.Linear.Borrow.size' does. A+shared caller may ignore the returned borrow; a mutable one threads it on, so+that a sequence of queries against a single @'Mut' α@ needs no reborrowing.+-}++-- | \(O(1)\). The number of live entries.+size ::+ Borrow bk α (HashMap k v) %1 ->+ BO α (Ur Int, Borrow bk α (HashMap k v))+{-# INLINE size #-}+size = askRaw Raw.size++-- | \(O(1)\) amortized. The value stored at a key, if any.+lookup ::+ (Hashable k) =>+ k ->+ Borrow bk α (HashMap k v) %1 ->+ BO α (Ur (Maybe v), Borrow bk α (HashMap k v))+{-# INLINE lookup #-}+lookup !key !dic = askRaw (Raw.lookup key) dic++-- | \(O(1)\) amortized. Whether a key is present.+member ::+ (Hashable k) =>+ k ->+ Borrow bk α (HashMap k v) %1 ->+ BO α (Ur Bool, Borrow bk α (HashMap k v))+{-# INLINE member #-}+member key = askRaw (Raw.member key)++-- * Iteration++-- | \(O(n)\). The table's entries, in unspecified order.+toList ::+ Borrow bk α (HashMap k v) %1 ->+ BO α (Ur [(k, v)], Borrow bk α (HashMap k v))+{-# INLINE toList #-}+toList = askRawUr Raw.toList++-- * Bulk operations++-- | \(O(1)\). Replace a borrowed table with another, returning the old one.+swap ::+ forall k v α.+ HashMap k v %1 ->+ Mut α (HashMap k v) %1 ->+ BO α (HashMap k v, Mut α (HashMap k v))+{-# INLINE swap #-}+swap new dic = asksLinearlyM \lin -> Control.do+ Bi.second recoerceBor+ Control.<$> Ref.update+ (\ !old -> Control.pure (HashMap $ Ref.new old lin, Ref.free $ inner new))+ (coerceBor dic)++-- | \(O(1)\). Take every entry out of a borrowed table, leaving it empty.+take :: forall k v α. Mut α (HashMap k v) %1 -> BO α (HashMap k v, Mut α (HashMap k v))+take dic = Control.do+ Bi.second recoerceBor Control.<$> Ref.update go (coerceBor dic)+ where+ go :: Raw.HashMap k v %1 -> BO α (HashMap k v, Raw.HashMap k v)+ go s = asksLinearlyM \lin ->+ dup lin & \(refLinear, tableLinear) ->+ Control.pure (HashMap $! Ref.new s refLinear, Raw.new 16 tableLinear)++-- | \(O(1)\). A borrow-discarding variant of 'take'.+take_ :: forall k v α. Mut α (HashMap k v) %1 -> BO α (HashMap k v)+{-# INLINE take_ #-}+take_ dic = Control.fmap (uncurry $ flip lseq) $ take dic++{- | \(O(n)\) amortized. Union of two owned tables.++The smaller table is inserted into the larger, so a key present in both takes+the value from the table that is inserted second.+-}+union :: (Hashable k) => HashMap k v %1 -> HashMap k v %1 -> HashMap k v+{-# INLINE union #-}+union (HashMap ref1) (HashMap ref2) = DataFlow.do+ (l, ref1) <- withLinearly ref1+ HashMap $! Ref.new (Raw.union (Ref.free ref1) (Ref.free ref2)) l++-- | \(O(n)\) amortized. Insert every entry of an owned table into a borrowed one.+extend :: (Hashable k) => HashMap k v %1 -> Mut α (HashMap k v) %1 -> BO α (Mut α (HashMap k v))+{-# INLINE extend #-}+extend donor dic = Control.do+ let %1 !donor' = Ref.free (inner donor)+ !dic <- Ref.modify (\ !s -> Raw.union s donor') $ coerceBor dic+ Control.pure $! recoerceBor dic
+ src/Data/HashMap/RobinHood/Mutable/Linear/Borrow/Internal.hs view
@@ -0,0 +1,144 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}+{-# OPTIONS_HADDOCK hide #-}++-- | Trusted representation of the borrow-aware Robin Hood hash table.+module Data.HashMap.RobinHood.Mutable.Linear.Borrow.Internal (+ module Data.HashMap.RobinHood.Mutable.Linear.Borrow.Internal,+) where++import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.BO.Unsafe+import Control.Monad.Borrow.Pure.Clone+import Control.Monad.Borrow.Pure.Copyable+import Control.Monad.Borrow.Pure.Utils (coerceLin, unsafeLeak)+import Data.HashMap.RobinHood.Mutable.Linear qualified as Raw+import Data.Ref.Linear (Ref)+import Data.Ref.Linear qualified as Ref+import GHC.TypeError+import Prelude.Linear+import Unsafe.Linear qualified as Unsafe++{- | A borrow-aware Robin Hood hash table.++The owned table of "Data.HashMap.RobinHood.Mutable.Linear" replaces its own+backing array when it grows, so a mutation returns a table value rather than+writing through the old one. Threading that value back is impossible through a+borrow, which is why this wrapper keeps the table behind a linear 'Ref': a+growth updates the reference in place, and a @'Mut' α@ of an enclosing+structure sees the new array without the enclosing structure being rebuilt.++Its keys and values are GC-owned, exactly as the underlying table's are.+-}+newtype HashMap k v = HashMap (Ref (Raw.HashMap k v))+ deriving newtype (LinearOnly, Consumable, Dupable, Clone)++-- | A 'Raw.InsertPlan' suspended against a borrowed table.+newtype InsertPlan k = InsertPlan (Raw.InsertPlan k)++{- | A 'HashMap' cannot be 'Copyable', because it owns a mutable reference.++Cloning one is still possible inside 'BO', through the derived 'Clone'.+-}+instance+ (Unsatisfiable (ShowType (HashMap k v) :<>: Text " cannot be copied!")) =>+ Copyable (HashMap k v)+ where+ copy = unsatisfiable++-- | Release the reference of an owned table.+inner :: HashMap k v %1 -> Ref (Raw.HashMap k v)+{-# INLINE inner #-}+inner = coerceLin++coerceBor ::+ forall k v bk α.+ Borrow bk α (HashMap k v) %1 ->+ Borrow bk α (Ref (Raw.HashMap k v))+{-# INLINE coerceBor #-}+coerceBor = coerceLin++recoerceBor ::+ forall k v bk α.+ Borrow bk α (Ref (Raw.HashMap k v)) %1 ->+ Borrow bk α (HashMap k v)+{-# INLINE recoerceBor #-}+recoerceBor = coerceLin++{-+Note [Reading through a borrow leaks an alias]+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+'askRaw' and 'askRawUr' hand a query the table that the 'Ref' still owns, so+what the query threads back out is an alias, not a resource this scope may+release. Both therefore drop it with 'unsafeLeak' rather than treating it as+owned, and hand the caller back the borrow they came in with.++Dropping it is a genuine no-op rather than a leak. The table's keys and values+are GC-owned (see Note [Element ownership] in+"Data.HashMap.RobinHood.Mutable.Linear.Internal"), so consuming a table+bottoms out in dropping a reference to its backing array -- which the 'Ref'+still holds, and which the garbage collector reclaims once the lender frees+the whole structure.++Returning the input borrow unchanged is sound for the same reason it is in+'Data.Vector.Mutable.Growable.Linear.Borrow.size': the occurrence handed back+is the one that was consumed, so no second live borrow of the table is+created, and a mutable caller still holds exactly one.++Neither helper may be used with a query that *replaces* the backing array:+a growth would be written into an alias and then discarded, and the 'Ref'+would keep serving the old array. Every mutating operation goes through+'Ref.update' or 'Ref.modify' instead, which write the returned table back.+-}++{- | Run a table query that threads the table through, and return the borrow.++The query must not replace the table's backing array. See+Note [Reading through a borrow leaks an alias].+-}+askRaw ::+ (Raw.HashMap k v %1 -> (a, Raw.HashMap k v)) %1 ->+ Borrow bk α (HashMap k v) %1 ->+ BO α (a, Borrow bk α (HashMap k v))+{-# INLINE askRaw #-}+askRaw = Unsafe.toLinear2 \f borrow ->+ case borrow of+ UnsafeAlias (HashMap ref) ->+ case Ref.unsafeReadRef ref of+ (!raw, _) -> case f raw of+ (!res, !raw) -> unsafeLeak raw `lseq` Control.pure (res, borrow)++{- | Run a table query that consumes the table and materializes its result.++The query must not replace the table's backing array. See+Note [Reading through a borrow leaks an alias].+-}+askRawUr ::+ (Raw.HashMap k v %1 -> Ur a) %1 ->+ Borrow bk α (HashMap k v) %1 ->+ BO α (Ur a, Borrow bk α (HashMap k v))+{-# INLINE askRawUr #-}+askRawUr = Unsafe.toLinear2 \f borrow ->+ case borrow of+ UnsafeAlias (HashMap ref) ->+ case Ref.unsafeReadRef ref of+ (!raw, _) -> case f raw of+ Ur !res -> Control.pure (Ur res, borrow)++{- | Force a queried value to WHNF.++A mutation reports the value it displaced, and that value may be a thunk+reading the table it was displaced from. Forcing it before the mutation+returns keeps the thunk from being evaluated against a later state of the+table.+-}+forceMay :: Maybe a %1 -> Maybe a+{-# INLINE forceMay #-}+forceMay Nothing = Nothing+forceMay (Just !x) = Just x
+ src/Data/HashMap/RobinHood/Mutable/Linear/Internal.hs view
@@ -0,0 +1,778 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE UnboxedTuples #-}+{-# LANGUAGE UnliftedDatatypes #-}+{-# LANGUAGE UnliftedNewtypes #-}+{-# LANGUAGE NoFieldSelectors #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}+{-# OPTIONS_HADDOCK hide #-}++-- | Trusted representation and operations of the Robin Hood hash table.+module Data.HashMap.RobinHood.Mutable.Linear.Internal (+ module Data.HashMap.RobinHood.Mutable.Linear.Internal,+) where++import Control.Functor.Linear (asks, runReader)+import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.Lifetime.Token (Linearly, withLinearly)+import Control.Monad.Borrow.Pure.Lifetime.Token.Unsafe (+ LinearOnly (..),+ LinearOnlyWitness (..),+ )+import Control.Syntax.DataFlow qualified as DataFlow+import Data.Array.Mutable.Linear qualified as LA+import Data.Bits ((.&.))+import Data.Foldable qualified as NonLinear+import Data.Functor.Linear qualified as Data+import Data.Hashable (Hashable (..))+import Data.Semigroup (Max (..))+import Data.Unrestricted.Linear qualified as Ur+import Data.Word (Word8)+import GHC.Base (Type, UnliftedType)+import GHC.Exts qualified as GHC+import GHC.TypeError (ErrorMessage (..), Unsatisfiable, unsatisfiable)+import Math.NumberTheory.Logarithms (intLog2')+import Prelude.Linear hiding (insert, lookup)+import Unsafe.Linear qualified as Unsafe+import Prelude qualified as NonLinear++{-+Note [Array substrate]+~~~~~~~~~~~~~~~~~~~~~~+The slot storage is @linear-base@'s 'LA.Array', not this package's own+'Data.Vector.Mutable.Linear.Borrow.Vector'.++The table's operations are ordinary linear functions, not 'BO' actions: a+probe reads and writes slots while only threading the owned table through+@%1 ->@. 'LA.Array' is the substrate that supports exactly that, because its+reads and writes are @runRW#@-wrapped primops that inline into the probe loop.+Our own 'Data.Vector.Mutable.Linear.Borrow.Vector' is built for the 'BO' world+instead: outside 'BO' its accessors would each have to go through+'unsafePerformIO', which "AGENTS.md" then requires to be 'NOINLINE' -- one+non-inlinable call per probe step, in the hottest loop of the structure.++Bridging the two substrates therefore stays a deliberate choice, not an+oversight: a borrow-aware view of the table lives in+"Data.HashMap.RobinHood.Mutable.Linear.Borrow", which holds this owned table+behind a linear 'Data.Ref.Linear.Ref'.+-}++{-+Note [Element ownership]+~~~~~~~~~~~~~~~~~~~~~~~~+This table is *not* element-owning, in the sense of "AGENTS.md": its keys and+values are GC-owned and bound nonlinearly, and it linearly owns only its+backing array. That is what makes 'lookup' able to hand back @'Ur' v@ without+a 'Movable' constraint, 'dup2' able to stop at a shallow clone of the slot+array, and 'consume' able to drop the table in \(O(1)\).++Do not relax 'Slot' to bind its value linearly in order to store mutable+resources: every accessor here would then be duplicating a linearly owned+value into an 'Ur'. A map over linearly owned values needs a different+representation, and its materialization has to 'move' every entry.+-}++-- | Distance of an occupied slot from the bucket its fingerprint hashes to.+newtype DIB = DIB Word8+ deriving newtype+ ( NonLinear.Eq+ , NonLinear.Ord+ , NonLinear.Num+ , NonLinear.Enum+ , NonLinear.Real+ , NonLinear.Integral+ , Additive+ , Show+ )++-- | Cached hash value, for fast rejection and for rehashing without rehashing.+newtype Fingerprint = Fingerprint Int+ deriving newtype (NonLinear.Eq, NonLinear.Ord, Show)++-- | Compute the fingerprint of a key.+fingerprint :: (Hashable k) => k -> Fingerprint+{-# INLINE fingerprint #-}+fingerprint = Fingerprint NonLinear.. hash++-- | Bucket index of a fingerprint. The capacity must be a power of two.+fingerprintBucket :: Fingerprint -> Int -> Int+{-# INLINE fingerprintBucket #-}+fingerprintBucket (Fingerprint h) capa = h .&. (capa - 1)++-- | A slot of the table, holding the fingerprint and DIB beside the entry.+data Slot k v where+ Empty :: Slot k v+ Occupied ::+ {-# UNPACK #-} !Fingerprint ->+ {-# UNPACK #-} !DIB ->+ !k ->+ !v ->+ Slot k v+ deriving (Show)++{- | Whether back-shift deletion must stop at this slot.++A slot stops the shift when it is empty, or when it already sits in its own+bucket and so cannot be moved closer to it.+-}+isStopSlot :: Slot k v -> NonLinear.Bool+{-# INLINE isStopSlot #-}+isStopSlot Empty = NonLinear.True+isStopSlot (Occupied _ dib _ _) = dib NonLinear.== 0++-- | Move an occupied slot one bucket closer to its own.+decrementSlot :: Slot k v %1 -> Slot k v+{-# INLINE decrementSlot #-}+decrementSlot Empty = Empty+decrementSlot (Occupied fp dib k v) = Occupied fp (dib NonLinear.- 1) k v++-- | The DIB of an occupied slot.+slotDIB :: Slot k v -> Maybe DIB+{-# INLINE slotDIB #-}+slotDIB Empty = Nothing+slotDIB (Occupied _ dib _ _) = Just dib++{- | The slot array.++This is a newtype rather than a synonym so that the 'LinearOnly' instance the+table needs is declared here, beside the type, instead of orphaned onto+@linear-base@'s 'LA.Array'.+-}+newtype Slots k v = Slots (LA.Array (Slot k v))++instance LinearOnly (Slots k v) where+ linearOnly = UnsafeLinearOnly+ {-# INLINE linearOnly #-}++instance Consumable (Slots k v) where+ consume (Slots arr) = consume arr+ {-# INLINE consume #-}++{- | \(O(n)\). Clone the slot array.++Keys and values are GC-owned, so a shallow copy of the backing store is a+complete duplication. See Note [Element ownership].+-}+instance Dupable (Slots k v) where+ dup2 (Slots arr) =+ dup2 arr & \(arr1, arr2) -> (Slots arr1, Slots arr2)+ {-# INLINE dup2 #-}++{- | \(O(n)\). Allocate a slot array of the given size, all 'Empty'.++'NOINLINE', and applied through 'GHC.noinline', for the same reason+@linear-base@'s own allocation primitives are: were the allocation duplicated+across use sites, or floated out of a loop, two tables would share one+backing store. The 'Unsafe.toLinear' escapes the continuation-passing shape of+'LA.alloc'; the array it releases is freshly allocated and unaliased, and the+consumed 'Linearly' is what makes handing it out linearly sound.+-}+allocSlots :: Int -> Linearly %1 -> Slots k v+{-# NOINLINE allocSlots #-}+allocSlots = GHC.noinline \count linear ->+ linear `lseq` Slots (unur (LA.alloc count Empty (Unsafe.toLinear Ur)))++{- | A mutable hash table using Robin Hood hashing with backward-shift deletion.++The table linearly owns its backing store; its keys and values are GC-owned.+See Note [Element ownership].+-}+data HashMap k v where+ HashMap ::+ -- | Number of live entries.+ {-# UNPACK #-} !Int ->+ -- | Number of buckets. Always a power of two.+ {-# UNPACK #-} !Int ->+ -- | An over-approximation of the largest DIB in the table.+ {-# UNPACK #-} !(Max DIB) ->+ -- | Slots, holding fingerprint, DIB and entry together.+ !(Slots k v) %1 ->+ HashMap k v++{- | A suspended unsuccessful lookup that can be resumed as an insertion.++The table must not be mutated between 'lookupForInsert' and+'unsafeInsertPrepared'.+-}+data InsertPlan k+ = InsertPlan+ !k+ {-# UNPACK #-} !Fingerprint+ {-# UNPACK #-} !Int+ !NonLinear.Bool+ {-# UNPACK #-} !DIB+ !(Maybe DIB)++instance Consumable (HashMap k v) where+ consume (HashMap _ _ _ slots) = consume slots+ {-# INLINE consume #-}++instance Dupable (HashMap k v) where+ dup2 (HashMap size capa maxDIB slots) =+ let %1 !(slots1, slots2) = dup slots+ in (HashMap size capa maxDIB slots1, HashMap size capa maxDIB slots2)+ {-# INLINE dup2 #-}++instance+ (Unsatisfiable ('Text "HashMap is only usable in linear context")) =>+ Movable (HashMap k v)+ where+ move = unsatisfiable++instance LinearOnly (HashMap k v) where+ linearOnly = UnsafeLinearOnly++-- | The table grows once this fraction of its buckets is occupied.+maxLoadFactor :: NonLinear.Double+maxLoadFactor = 0.75++{- | The largest DIB the table tolerates before growing.++The slot array is over-allocated by this many buckets, so a probe that starts+in the last bucket can run to the DIB limit without wrapping around.+-}+maxDibLimit :: DIB+maxDibLimit = 127++{- | \(O(n)\). An empty table sized for at least the given number of entries.++The bucket count is rounded up to a power of two.+-}+new :: Int -> Linearly %1 -> HashMap k v+new capa = runReader Control.do+ let !capa' = 2 ^ intLog2' (2 * max 1 capa - 1)+ !physCapa = capa' + fromIntegral maxDibLimit+ slots <- asks $ allocSlots physCapa+ Control.pure $ HashMap 0 capa' 0 slots++{- | \(O(n)\). Consume the table, folding a monoid over its entries.++Iteration order is unspecified.+-}+foldMapWithKey ::+ forall w k v.+ (Monoid w) =>+ (k -> v -> w) ->+ HashMap k v %1 ->+ w+foldMapWithKey f (HashMap size capa _ slots) = go 0 0 slots mempty+ where+ physCapa = capa + fromIntegral maxDibLimit+ go :: Int -> Int -> Slots k v %1 -> w -> w+ go !i !count !slots !acc+ | count == size || i == physCapa = slots `lseq` acc+ | otherwise =+ unsafeGetSlot i slots & \case+ (Ur (Occupied _ _ k v), slots') ->+ go (i + 1) (count + 1) slots' (acc <> f k v)+ (Ur Empty, slots') ->+ go (i + 1) count slots' acc++unsafeGetSlot :: Int -> Slots k v %1 -> (Ur (Slot k v), Slots k v)+{-# INLINE unsafeGetSlot #-}+unsafeGetSlot i (Slots arr) =+ LA.unsafeGet i arr & \(slot, arr) -> (slot, Slots arr)++unsafeSetSlot :: Int -> Slot k v -> Slots k v %1 -> Slots k v+{-# INLINE unsafeSetSlot #-}+unsafeSetSlot i slot (Slots arr) = Slots (LA.unsafeSet i slot arr)++-- | \(O(1)\) amortized. Insert an entry, returning the value it displaced.+insert :: (Hashable k) => k -> v -> HashMap k v %1 -> (Ur (Maybe v), HashMap k v)+insert k v =+ unswapper+ . alterF (\mval -> Swapper (Ur (Just v)) mval) k++-- | \(O(n)\) amortized. Insert every entry, later keys winning over earlier.+insertMany ::+ (Hashable k) =>+ [(k, v)] ->+ HashMap k v %1 ->+ HashMap k v+{-# INLINE insertMany #-}+insertMany kvs hm =+ appEndo+ ( getDual+ ( NonLinear.foldMap'+ (\(!k, !v) -> Dual $ Endo $ uncurry lseq . insert k v)+ kvs+ )+ )+ hm++-- | \(O(1)\) amortized. Insert, update or delete the entry at a key.+alter ::+ forall k v.+ (Hashable k) =>+ (Maybe v -> Maybe v) ->+ k ->+ HashMap k v %1 ->+ HashMap k v+alter f k hm =+ case probeKeyForAlter k hm of+ (# NotFound st, hm #) ->+ -- Absent: only an insertion can change anything.+ case f Nothing of+ Nothing -> hm+ Just !v -> probeForInsert k v st hm+ (# Found loc@Location {foundAt, slotFp, slotDIB, val}, hm #) ->+ case f (Just val) of+ Nothing -> deleteFrom loc hm+ (Just !v) ->+ -- Present: the slot keeps its fingerprint and DIB.+ hm & \(HashMap size capa maxDIB slots) -> DataFlow.do+ slots <- unsafeSetSlot foundAt (Occupied slotFp slotDIB k v) slots+ HashMap size capa maxDIB slots++-- | \(O(1)\). The number of live entries.+size :: HashMap k v %1 -> (Ur Int, HashMap k v)+{-# INLINE size #-}+size (HashMap sz capa maxDIB slots) = (Ur sz, HashMap sz capa maxDIB slots)++-- | \(O(1)\). The number of buckets.+capacity :: HashMap k v %1 -> (Ur Int, HashMap k v)+{-# INLINE capacity #-}+capacity (HashMap sz capa maxDIB slots) = (Ur capa, HashMap sz capa maxDIB slots)++{- | \(O(n)\) amortized. Union of two tables.++The smaller table is inserted into the larger, so a key present in both takes+the value from the table that is inserted second.+-}+union :: (Hashable k) => HashMap k v %1 -> HashMap k v %1 -> HashMap k v+{-# INLINE union #-}+union hm1 hm2 = case (size hm1, size hm2) of+ ((Ur sz1, hm1), (Ur sz2, hm2)) -> DataFlow.do+ (parent, child) <- if sz1 >= sz2 then (hm1, hm2) else (hm2, hm1)+ appEndo+ (foldMapWithKey (\ !k !v -> Endo $ uncurry lseq . insert k v) child)+ parent++-- | \(O(1)\) amortized. 'alter' with the replacement produced in a functor.+alterF ::+ (Hashable k, Control.Functor f) =>+ (Maybe v -> f (Ur (Maybe v))) %1 ->+ k ->+ HashMap k v %1 ->+ f (HashMap k v)+alterF f k hm =+ case probeKeyForAlter k hm of+ (# NotFound st, hm #) ->+ -- Absent: only an insertion can change anything.+ f Nothing Control.<&> \case+ Ur Nothing -> hm+ Ur (Just !v) -> probeForInsert k v st hm+ (# Found loc@Location {foundAt, slotFp, slotDIB, val}, hm #) ->+ f (Just val) Control.<&> \case+ Ur Nothing -> deleteFrom loc hm+ Ur (Just !v) ->+ -- Present: the slot keeps its fingerprint and DIB.+ hm & \(HashMap size capa maxDIB slots) -> DataFlow.do+ slots <- unsafeSetSlot foundAt (Occupied slotFp slotDIB k v) slots+ HashMap size capa maxDIB slots++{- | \(O(1)\) amortized. Look a key up, and on a miss suspend the probe.++The suspended probe can be resumed as an insertion by+'unsafeInsertPrepared', which then costs no second traversal.+-}+lookupForInsert ::+ (Hashable k) =>+ k ->+ HashMap k v %1 ->+ (Ur (Either v (InsertPlan k)), HashMap k v)+{-# INLINE lookupForInsert #-}+lookupForInsert k hm = case probeKeyForAlter k hm of+ (# Found Location {val}, hm #) -> (Ur (Left val), hm)+ (# NotFound ProbeSuspended {..}, hm #) ->+ ( Ur+ ( Right+ ( InsertPlan+ k+ searchFp+ offset+ (case endType of Vacant -> NonLinear.True; Paused -> NonLinear.False)+ dibAtMiss+ cachedDIB+ )+ )+ , hm+ )++{- | \(O(1)\) amortized. Insert using a plan returned by 'lookupForInsert'.++The table must be the same table, with no intervening mutation. Violating+this precondition can corrupt the Robin Hood invariants.+-}+unsafeInsertPrepared :: InsertPlan k -> v -> HashMap k v %1 -> HashMap k v+{-# INLINE unsafeInsertPrepared #-}+unsafeInsertPrepared (InsertPlan k searchFp offset vacant dibAtMiss cachedDIB) v =+ probeForInsert+ k+ v+ ProbeSuspended+ { searchFp+ , offset+ , endType = if vacant then Vacant else Paused+ , dibAtMiss+ , cachedDIB+ }++-- | A functor that carries the displaced value beside the result.+data Swapper v a where+ Swapper :: a %1 -> Maybe v -> Swapper v a++unswapper :: Swapper v a %1 -> (Ur (Maybe v), a)+{-# INLINE unswapper #-}+unswapper (Swapper l b) = (Ur b, l)++instance Data.Functor (Swapper v) where+ {-# SPECIALIZE instance Data.Functor (Swapper v) #-}+ fmap f = \(Swapper l b) -> Swapper (f l) (b :: Maybe v)+ {-# INLINE fmap #-}++instance Control.Functor (Swapper v) where+ {-# SPECIALIZE instance Control.Functor (Swapper v) #-}+ fmap f = \(Swapper l b) -> Swapper (f l) (b :: Maybe v)+ {-# INLINE fmap #-}++{- | Remove a located entry, shifting the run behind it back one bucket.++Backward-shift deletion keeps every remaining entry reachable without+tombstones: the shift stops at the first slot that is empty or already sits in+its own bucket.+-}+deleteFrom :: Location v -> HashMap k v %1 -> HashMap k v+deleteFrom Location {..} (HashMap size capa maxDIB slots) = go foundAt slots+ where+ physMax = capa + fromIntegral maxDibLimit - 1+ go :: Int -> Slots k v %1 -> HashMap k v+ go !i !slots+ | i == physMax = DataFlow.do+ slots <- unsafeSetSlot i Empty slots+ HashMap (size - 1) capa maxDIB slots+ | otherwise =+ unsafeGetSlot (i + 1) slots & \(Ur nextSlot, slots) ->+ if isStopSlot nextSlot+ then DataFlow.do+ slots <- unsafeSetSlot i Empty slots+ HashMap (size - 1) capa maxDIB slots+ else DataFlow.do+ slots <- unsafeSetSlot i (decrementSlot nextSlot) slots+ go (i + 1) slots++-- | Complete an insertion from the point where its probe stopped.+probeForInsert ::+ forall k v.+ k -> v -> ProbeSuspended -> HashMap k v %1 -> HashMap k v+{-# INLINE probeForInsert #-}+probeForInsert !k !v ProbeSuspended {..} (HashMap size capa maxDIB slots)+ | dibAtMiss NonLinear.> maxDibLimit || fromIntegral (size + 1) / fromIntegral capa >= maxLoadFactor =+ grow size capa searchFp k v slots+ | otherwise = case endType of+ Vacant -> DataFlow.do+ slots <- unsafeSetSlot offset (Occupied searchFp dibAtMiss k v) slots+ HashMap (size + 1) capa (maxDIB NonLinear.<> Max dibAtMiss) slots+ Paused+ | offset == physCapa -> grow size capa searchFp k v slots+ | otherwise -> case cachedDIB of+ Nothing ->+ -- A vacant slot; cannot arise from a paused probe, but is+ -- harmless to handle.+ DataFlow.do+ slots <- unsafeSetSlot offset (Occupied searchFp dibAtMiss k v) slots+ HashMap (size + 1) capa (maxDIB NonLinear.<> Max dibAtMiss) slots+ Just existingDib ->+ if existingDib NonLinear.< dibAtMiss+ then+ unsafeGetSlot offset slots & \case+ (Ur (Occupied existingFp _ k' v'), slots) -> DataFlow.do+ -- Take from the rich and give to the poor.+ slots <- unsafeSetSlot offset (Occupied searchFp dibAtMiss k v) slots+ go size capa (Max dibAtMiss NonLinear.<> maxDIB) existingFp (existingDib + 1) k' v' (offset + 1) slots+ (Ur Empty, slots) -> error "probeForInsert: impossible Empty slot" slots+ else+ if dibAtMiss NonLinear.== maxDibLimit NonLinear.- 1+ then grow size capa searchFp k v slots+ else go size capa maxDIB searchFp (dibAtMiss + 1) k v (offset + 1) slots+ where+ physCapa :: Int+ physCapa = capa + fromIntegral maxDibLimit++ grow :: Int -> Int -> Fingerprint -> k -> v -> Slots k v %1 -> HashMap k v+ grow !size !capa !fp newK newV slots =+ withLinearly slots & \(lin, slots) ->+ rehashInto size physCapa fp newK newV 0 0 slots (new (capa * 2) lin)++ go ::+ Int ->+ Int ->+ Max DIB ->+ Fingerprint ->+ DIB ->+ k ->+ v ->+ Int ->+ Slots k v %1 ->+ HashMap k v+ -- Invariant: curMaxDIB <= maxDibLimit+ -- Invariant: newDIB <= maxDibLimit+ go !size !capa !curMaxDIB !newFp !newDib !newK !newV !i !slots =+ if i == physCapa+ then grow size capa newFp newK newV slots+ else+ unsafeGetSlot i slots+ & \case+ (Ur Empty, slots) ->+ DataFlow.do+ slots <- unsafeSetSlot i (Occupied newFp newDib newK newV) slots+ HashMap (size + 1) capa (curMaxDIB NonLinear.<> Max newDib) slots+ (Ur (Occupied existingFp existingDib k' v'), slots) ->+ if existingDib NonLinear.< newDib+ then DataFlow.do+ -- Take from the rich and give to the poor.+ slots <- unsafeSetSlot i (Occupied newFp newDib newK newV) slots+ -- existingDib < newDib+ -- <==> existingDib + 1 <= newDib <= maxDibLimit+ -- hence the invariant is maintained.+ go size capa (Max newDib NonLinear.<> curMaxDIB) existingFp (existingDib + 1) k' v' (i + 1) slots+ else+ if newDib NonLinear.== maxDibLimit NonLinear.- 1+ then grow size capa newFp newK newV slots+ else go size capa curMaxDIB newFp (newDib + 1) newK newV (i + 1) slots++{- | Insert a key known to be absent, reusing its cached fingerprint.++This is the rehashing path: it never has to hash a key again.+-}+insertFreshWithFingerprint :: Fingerprint -> k -> v -> HashMap k v %1 -> HashMap k v+{-# INLINE insertFreshWithFingerprint #-}+insertFreshWithFingerprint !fp !k !v (HashMap size capa maxDIB slots) =+ let !start = fingerprintBucket fp capa+ !physCapa = capa + fromIntegral maxDibLimit+ in goFreshF size capa physCapa maxDIB 0 fp k v start slots++-- | The probe loop of 'insertFreshWithFingerprint'.+goFreshF ::+ Int -> -- size+ Int -> -- capa+ Int -> -- physCapa+ Max DIB -> -- current max DIB+ DIB -> -- current DIB of the entry being inserted+ Fingerprint -> -- fingerprint of the entry being inserted+ k ->+ v ->+ Int -> -- current index+ Slots k v %1 ->+ HashMap k v+goFreshF !size !capa !physCapa !curMaxDIB !dib !fp !k !v !i !slots+ | i == physCapa || dib NonLinear.> maxDibLimit =+ -- Should not arise during a rehash into a table twice the size, but+ -- growing again is the safe response.+ withLinearly slots & \(lin, slots) ->+ slots `lseq` insertFreshWithFingerprint fp k v (new (capa * 2) lin)+ | otherwise =+ unsafeGetSlot i slots & \case+ (Ur Empty, slots') -> DataFlow.do+ slots' <- unsafeSetSlot i (Occupied fp dib k v) slots'+ HashMap (size + 1) capa (curMaxDIB NonLinear.<> Max dib) slots'+ (Ur (Occupied existingFp existingDib k' v'), slots') ->+ if existingDib NonLinear.< dib+ then DataFlow.do+ -- Take from the rich and give to the poor.+ slots' <- unsafeSetSlot i (Occupied fp dib k v) slots'+ goFreshF size capa physCapa (curMaxDIB NonLinear.<> Max dib) (existingDib + 1) existingFp k' v' (i + 1) slots'+ else+ goFreshF size capa physCapa curMaxDIB (dib + 1) fp k v (i + 1) slots'++-- | Move every entry of the old slot array into a fresh table, then insert.+rehashInto ::+ Int -> -- size of the old table+ Int -> -- physCapa of the old table+ Fingerprint -> -- fingerprint of the key to insert+ k -> -- key to insert+ v -> -- value to insert+ Int -> -- current index+ Int -> -- entries moved so far+ Slots k v %1 ->+ HashMap k v %1 ->+ HashMap k v+rehashInto !oldSize !oldPhysCapa !fp !k !v !i !count !oldSlots !newMap+ | count == oldSize || i >= oldPhysCapa =+ oldSlots `lseq` insertFreshWithFingerprint fp k v newMap+ | otherwise =+ unsafeGetSlot i oldSlots & \case+ (Ur (Occupied fp' _ k' v'), oldSlots') ->+ rehashInto oldSize oldPhysCapa fp k v (i + 1) (count + 1) oldSlots' (insertFreshWithFingerprint fp' k' v' newMap)+ (Ur Empty, oldSlots') ->+ rehashInto oldSize oldPhysCapa fp k v (i + 1) count oldSlots' newMap++-- | \(O(1)\) amortized. The value stored at a key, if any.+lookup :: (Hashable k) => k -> HashMap k v %1 -> (Ur (Maybe v), HashMap k v)+lookup k hm =+ case probeKeyForAlter k hm of+ (# NotFound _, hm #) -> (Ur Nothing, hm)+ (# Found Location {val}, hm #) -> (Ur (Just val), hm)++-- | \(O(1)\) amortized. Whether a key is present.+member :: (Hashable k) => k -> HashMap k v %1 -> (Ur Bool, HashMap k v)+member k hm =+ case probeKeyForAlter k hm of+ (# NotFound {}, hm #) -> (Ur False, hm)+ (# Found {}, hm #) -> (Ur True, hm)++-- | \(O(1)\) amortized. Remove a key, returning the value it held.+delete :: (Hashable k) => k -> HashMap k v %1 -> (Ur (Maybe v), HashMap k v)+{-# INLINE delete #-}+delete k =+ unswapper+ . alterF (\old -> Swapper (Ur Nothing) old) k++-- | Where a successful probe stopped, and what it found there.+type Location :: Type -> UnliftedType+data Location v = Location+ { foundAt :: !Int+ , slotFp :: {-# UNPACK #-} !Fingerprint+ , slotDIB :: {-# UNPACK #-} !DIB+ , val :: !v+ }++-- | The outcome of a probe.+type LookupResult :: Type -> UnliftedType+data LookupResult v where+ Found :: !(Location v) -> LookupResult v+ NotFound :: {-# UNPACK #-} !ProbeSuspended -> LookupResult v++-- | Everything an unsuccessful probe learned, so an insertion can resume it.+type ProbeSuspended :: UnliftedType+data ProbeSuspended = ProbeSuspended+ { searchFp :: {-# UNPACK #-} !Fingerprint+ -- ^ Fingerprint of the key being searched for.+ , offset :: {-# UNPACK #-} !Int+ -- ^ Index the probe stopped at.+ , endType :: !EndType+ , dibAtMiss :: {-# UNPACK #-} !DIB+ -- ^ DIB the searched key would have at 'offset'.+ , cachedDIB :: !(Maybe DIB)+ -- ^ 'Nothing' for an empty slot, @'Just' dib@ for an occupied one.+ }++-- | Why a probe stopped: on a vacant slot, or on an occupied one.+newtype EndType = EndType (# (# #) | (# #) #)++pattern Vacant :: EndType+pattern Vacant = EndType (# (# #) | #)++pattern Paused :: EndType+pattern Paused = EndType (# | (# #) #)++{-# COMPLETE Paused, Vacant #-}++{- | The single probe shared by every lookup, insertion and deletion.++On a miss it returns enough state for an insertion to continue from where the+probe stopped, rather than starting the traversal again.+-}+probeKeyForAlter :: forall k v. (Hashable k) => k -> HashMap k v %1 -> (# LookupResult v, HashMap k v #)+{-# INLINE probeKeyForAlter #-}+probeKeyForAlter k (HashMap size capa maxDIB slots) =+ go start 0 slots+ where+ !searchFp = fingerprint k+ !start = fingerprintBucket searchFp capa+ !physCapa = capa + fromIntegral maxDibLimit+ go :: Int -> DIB -> Slots k v %1 -> (# LookupResult v, HashMap k v #)+ go !idx !dib !slots+ | idx == physCapa || dib NonLinear.== maxDibLimit + 1 =+ (#+ NotFound+ ProbeSuspended+ { searchFp+ , offset = idx+ , endType = Paused+ , dibAtMiss = dib+ , cachedDIB = Nothing -- dummy: always triggers a grow+ }+ , HashMap size capa maxDIB slots+ #)+ | dib NonLinear.> getMax maxDIB =+ -- Past the largest DIB in the table: no key can live any further out.+ unsafeGetSlot idx slots & \(Ur slot, slots) ->+ let endType = case slot of+ Empty -> Vacant+ _ -> Paused+ in (#+ NotFound+ ProbeSuspended+ { searchFp+ , offset = idx+ , endType+ , dibAtMiss = dib+ , cachedDIB = slotDIB slot+ }+ , HashMap size capa maxDIB slots+ #)+ | otherwise =+ unsafeGetSlot idx slots & \case+ (Ur Empty, slots) ->+ (#+ NotFound+ ProbeSuspended+ { searchFp+ , offset = idx+ , endType = Vacant+ , dibAtMiss = dib+ , cachedDIB = Nothing+ }+ , HashMap size capa maxDIB slots+ #)+ (Ur (Occupied slotFp existingDib k' val), slots) ->+ if+ | existingDib NonLinear.< dib ->+ -- Robin Hood early exit: the key would have displaced this+ -- entry, so it cannot be further along the run.+ (#+ NotFound+ ProbeSuspended+ { searchFp+ , offset = idx+ , endType = Paused+ , dibAtMiss = dib+ , cachedDIB = Just existingDib+ }+ , HashMap size capa maxDIB slots+ #)+ | slotFp NonLinear./= searchFp -> go (idx + 1) (dib + 1) slots -- reject without touching the key+ | k NonLinear.== k' ->+ (#+ Found Location {foundAt = idx, slotFp, slotDIB = existingDib, val}+ , HashMap size capa maxDIB slots+ #)+ | otherwise -> go (idx + 1) (dib + 1) slots++{- | \(O(n)\). Consume the table into its entries.++Entry order is unspecified. Accumulation goes through 'Endo' so that the list+is built by one right-nested traversal rather than repeated concatenation.+-}+toList :: HashMap k v %1 -> Ur [(k, v)]+{-# INLINE toList #-}+toList =+ Ur.lift materialiseDiffList+ . foldMapWithKey (\ !k !v -> Ur (Endo ((k, v) :)))++-- | Run a difference list built by 'toList' into an ordinary list.+materialiseDiffList :: Endo [a] -> [a]+{-# INLINE materialiseDiffList #-}+materialiseDiffList diff = appEndo diff []++-- | \(O(n)\) amortized. Build a table from a list, later keys winning.+fromList :: (Hashable k) => [(k, v)] -> Linearly %1 -> HashMap k v+fromList kvs = insertMany kvs . new (NonLinear.length kvs)
src/Data/Record/Linear/Borrow/Experimental/PatternMatch.hs view
@@ -54,7 +54,7 @@ import Control.Monad.Borrow.Pure.Affine (Affine (..), AsAffine (..)) import Control.Monad.Borrow.Pure.Affine.Unsafe (unsafeAff) import Control.Monad.Borrow.Pure.BO-import Control.Monad.Borrow.Pure.BO.Unsafe (unsafeMapAlias)+import Control.Monad.Borrow.Pure.BO.Internal import Data.Kind (Constraint) import GHC.Base (TYPE, Type, proxy#) import GHC.OverloadedLabels (IsLabel (..))@@ -299,7 +299,11 @@ where type SplitBorrow (l1, l2, l3, l4) bk α a =- (Borrow bk α (ValueOf l1), Borrow bk α (ValueOf l2), Borrow bk α (ValueOf l3), Borrow bk α (ValueOf l4))+ ( Borrow bk α (ValueOf l1)+ , Borrow bk α (ValueOf l2)+ , Borrow bk α (ValueOf l3)+ , Borrow bk α (ValueOf l4)+ ) splitRecord (RecLab, RecLab, RecLab, RecLab) = Unsafe.toLinear \r -> ( unsafeMapAlias (Unsafe.toLinear (getField @f1)) r , unsafeMapAlias (Unsafe.toLinear (getField @f2)) r
src/Data/Record/Linear/Borrow/Experimental/Split.hs view
@@ -45,8 +45,8 @@ (!#), ) where +import Control.Monad.Borrow.Pure.BO (Lifetime) import Control.Monad.Borrow.Pure.BO.Internal-import Control.Monad.Borrow.Pure.Lifetime import Data.Kind (Constraint) import GHC.Base (Multiplicity (..), TYPE, Type) import GHC.OverloadedLabels (IsLabel (..))@@ -95,10 +95,10 @@ For more complex, partial splitting of a record, see [Splitting a record borrow into pieces](#split) for more detail. -} (.#) ::- forall field r a k α.- Borrow k α r %1 ->+ forall field r a bk α.+ Borrow bk α r %1 -> RecordLabel r field a ->- Borrow k α a+ Borrow bk α a UnsafeAlias !r .# RecLab = UnsafeAlias $! Unsafe.toLinear (getField @field @r @a) r infixl 9 .#
src/Data/Ref/Linear/Borrow.hs view
@@ -31,6 +31,14 @@ import Unsafe.Linear qualified as Unsafe import Prelude qualified as NonLinear +{- | Perform one read-modify-write traversal and return an auxiliary result.++This is the canonical operation when a mutation must also report what it+observed: for example, an insert can return the displaced old value and use it+to detect a collision without a separate lookup. Prefer this shape over+lookup-then-update in hot paths, since the latter silently traverses the+underlying structure twice.+-} update :: (α >= β) => (a %1 -> BO β (b, a)) %1 -> Mut α (Ref a) %1 -> BO β (b, Mut α (Ref a)) {-# INLINE update #-} update f (UnsafeAlias mv) = DataFlow.do
+ src/Data/Vector/Generic/Mutable/Growable/Linear/Borrow/Unrestricted.hs view
@@ -0,0 +1,79 @@+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE NoImplicitPrelude #-}++{- |+Growable, linearly owned mutable vectors with unrestricted, GC-owned elements+and a backend selected through @vector@'s generic interface.++A 'GrowableVector' keeps its replaceable backing buffer behind a stable mutable+header. Consequently, 'reserve', 'push', and 'extend' preserve the identity+reclaimed by an existing @Lend@. The backend parameter @v@ may be any immutable+backend supported by 'Data.Vector.Generic.Vector'.++'getContents' projects a mutable or shared growable borrow to the corresponding+fixed-size unrestricted vector borrow over exactly the initialized prefix.+'withContent' provides the same projection in a rank-2 no-growth scope and+restores the growable borrow afterward. Growth remains unavailable until every+fixed content borrow has ended.++The owner and mutable backing are linear, but entries are not element-owned.+Constructing, growing, cloning, discarding, and materializing a vector therefore+do not invoke @Consumable@, @Movable@, @Copyable@, or @Dupable@ operations on+elements. 'toVector' freezes the initialized prefix in \(O(1)\).++The growable owner deliberately has no splitting operation because independent+replaceable headers over overlapping storage would invalidate borrows after+growth. Split only a fixed view inside 'withContent'.++Growth provides a normal-return ownership guarantee. Allocation and copying+complete before the logical size is published; an owner is not promised to be+recoverable after an exception escapes a @BO@ computation.++Custom backends are a trusted extensibility boundary. Their mutable operations+must obey the usual @vector@ laws, including fresh allocation, exact lengths,+non-overlapping storage, copying thaw and freeze, and alias-free ownership+transfer through unsafe freeze and thaw.+-}+module Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted (+ GrowableVector,+ empty,+ constant,+ fromList,+ withCapacity,+ fromVector,+ unsafeFromMutable,+ unsafeFromVector,+ toVector,+ toList,+ copyToVector,+ size,+ capacity,+ get,+ unsafeGet,+ head,+ unsafeHead,+ last,+ unsafeLast,+ copyAt,+ unsafeCopyAt,+ copyAtMut,+ unsafeCopyAtMut,+ set,+ unsafeSet,+ write,+ unsafeWrite,+ update,+ unsafeUpdate,+ modify,+ swap,+ unsafeSwap,+ reserve,+ reserveAdditional,+ push,+ extend,+ getContents,+ withContent,+ withContent_,+) where++import Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted.Internal
+ src/Data/Vector/Generic/Mutable/Growable/Linear/Borrow/Unrestricted/Internal.hs view
@@ -0,0 +1,867 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RoleAnnotations #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}+{-# OPTIONS_GHC -Wno-redundant-constraints #-}+{-# OPTIONS_HADDOCK hide #-}++module Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted.Internal (+ module Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted.Internal,+) where++import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.Affine (aff, pop)+import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.BO.Internal (unsafeSrunBO_)+import Control.Monad.Borrow.Pure.BO.Unsafe+import Control.Monad.Borrow.Pure.Clone+import Control.Monad.Borrow.Pure.Copyable+import Control.Monad.Borrow.Pure.Lifetime.Token.Unsafe (+ LinearOnly (..),+ LinearOnlyWitness (..),+ )+import Data.Ref.Linear qualified as Ref+import Data.Ref.Linear.Borrow qualified as RefBorrow+import Data.Unrestricted.Linear qualified as Ur+import Data.Vector.Generic qualified as G+import Data.Vector.Generic.Mutable qualified as GM+import Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted qualified as Fixed+import Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted.Internal qualified as Fixed.Internal+import Data.Vector.Mutable (RealWorld)+import GHC.Exts qualified as GHC+import GHC.IO (unsafePerformIO)+import GHC.Stack (HasCallStack)+import GHC.TypeError+import Prelude.Linear hiding (getContents, head, last)+import Unsafe.Linear qualified as Unsafe+import Prelude qualified as NonLinear++data Header v a where+ Header ::+ {-# UNPACK #-} !Int ->+ !(G.Mutable v RealWorld a) %1 ->+ Header v a++-- | A growable vector with a stable header and replaceable generic backing.+data GrowableVector v a where+ GrowableVector ::+ !(Ref.Ref (Header v a)) %1 ->+ GrowableVector v a++type role Header nominal nominal++type role GrowableVector nominal nominal++instance LinearOnly (GrowableVector v a) where+ linearOnly = UnsafeLinearOnly+ {-# INLINE linearOnly #-}++instance+ ( Unsatisfiable+ (ShowType (GrowableVector v a) :<>: Text " cannot be copied!")+ ) =>+ Copyable (GrowableVector v a)+ where+ copy = unsatisfiable++instance Consumable (GrowableVector v a) where+ consume = Unsafe.toLinear \_ -> ()+ {-# INLINE consume #-}++instance (G.Vector v a) => Clone (GrowableVector v a) where+ clone =+ Unsafe.toLinear \(UnsafeAlias (GrowableVector ref)) ->+ case Ref.unsafeReadRef ref of+ (Header logicalSize buffer, duplicateRef) ->+ pop (aff duplicateRef) `lseq` Control.do+ cloned <-+ unsafeSystemIOToBO do+ target <- GM.unsafeNew (GM.length buffer)+ GM.unsafeCopy+ (GM.unsafeTake logicalSize target)+ (GM.unsafeTake logicalSize buffer)+ NonLinear.pure target+ linear <- askLinearly+ Control.pure+ (GrowableVector (Ref.new (Header logicalSize cloned) linear))+ {-# INLINE clone #-}++allocateBuffer ::+ (G.Vector v a) =>+ Int ->+ Linearly %1 ->+ G.Mutable v RealWorld a+{-# NOINLINE allocateBuffer #-}+allocateBuffer =+ GHC.noinline \count linear ->+ linear `lseq` unsafePerformIO (GM.unsafeNew count)++cloneBuffer ::+ (G.Vector v a) =>+ v a ->+ Linearly %1 ->+ G.Mutable v RealWorld a+{-# NOINLINE cloneBuffer #-}+cloneBuffer =+ GHC.noinline \source linear ->+ linear `lseq` unsafePerformIO (G.thaw source)++-- | \(O(1)\). Construct an empty vector with zero capacity.+empty :: (G.Vector v a) => Linearly %1 -> GrowableVector v a+{-# NOINLINE empty #-}+empty = withCapacity 0++{- | \(O(n)\). Construct @count@ initialized copies of a value.++As in @vector@, a negative count produces an empty vector.+-}+constant ::+ (G.Vector v a) =>+ Int ->+ a ->+ Linearly %1 ->+ GrowableVector v a+{-# NOINLINE constant #-}+constant =+ GHC.noinline \count value linear ->+ fromVector (G.replicate count value) linear++-- | \(O(n)\). Copy an unrestricted list into a new vector.+fromList ::+ (G.Vector v a) =>+ [a] ->+ Linearly %1 ->+ GrowableVector v a+{-# NOINLINE fromList #-}+fromList =+ GHC.noinline \values linear ->+ fromVector (G.fromList values) linear++{- | \(O(1)\). Construct an empty vector with the requested capacity.++The capacity must be non-negative. Spare storage is not considered initialized.+-}+withCapacity ::+ (HasCallStack, G.Vector v a) =>+ Int ->+ Linearly %1 ->+ GrowableVector v a+{-# NOINLINE withCapacity #-}+withCapacity =+ GHC.noinline \requested linear ->+ if requested < 0+ then error ("withCapacity: negative capacity " <> show requested) linear+ else+ dup linear & \(bufferLinear, refLinear) ->+ GrowableVector+ ( Ref.new+ (Header 0 (allocateBuffer requested bufferLinear))+ refLinear+ )++-- | \(O(n)\). Copy an immutable vector into a new growable owner.+fromVector ::+ (G.Vector v a) =>+ v a ->+ Linearly %1 ->+ GrowableVector v a+{-# NOINLINE fromVector #-}+fromVector =+ GHC.noinline \source linear ->+ dup linear & \(bufferLinear, refLinear) ->+ GrowableVector+ ( Ref.new+ (Header (G.length source) (cloneBuffer source bufferLinear))+ refLinear+ )++{- | \(O(1)\). Take ownership of a mutable vector without copying.++The complete source slice is treated as initialized. The caller must not retain+any alias that can access its allocation.+-}+unsafeFromMutable ::+ (G.Vector v a) =>+ G.Mutable v state a %1 ->+ Linearly %1 ->+ GrowableVector v a+{-# INLINE unsafeFromMutable #-}+unsafeFromMutable =+ Unsafe.toLinear \source linear ->+ GrowableVector+ ( Ref.new+ (Header (GM.length source) (Unsafe.coerce source))+ linear+ )++{- | \(O(1)\). Unsafely take ownership of an immutable vector's storage.++The complete source is initialized. No immutable alias may be observed after+this operation because subsequent growth and mutation reuse its storage.+-}+unsafeFromVector ::+ (G.Vector v a) =>+ v a %1 ->+ Linearly %1 ->+ GrowableVector v a+{-# NOINLINE unsafeFromVector #-}+unsafeFromVector =+ GHC.noinline $+ Unsafe.toLinear \source linear ->+ GrowableVector+ ( Ref.new+ ( Header+ (G.length source)+ (unsafePerformIO (G.unsafeThaw source))+ )+ linear+ )++{- | \(O(1)\). Consume the owner and freeze exactly its initialized prefix.++No element crosses an ownership boundary: entries were GC-owned already. Spare+capacity is neither exposed nor copied.+-}+toVector ::+ (G.Vector v a) =>+ GrowableVector v a %1 ->+ Ur (v a)+{-# NOINLINE toVector #-}+toVector =+ GHC.noinline $+ Unsafe.toLinear \(GrowableVector ref) ->+ case Ref.free ref of+ Header logicalSize buffer ->+ Ur+ ( unsafePerformIO+ (G.unsafeFreeze (GM.unsafeTake logicalSize buffer))+ )++-- | \(O(n)\). Consume the owner and return its initialized prefix as a list.+toList ::+ (G.Vector v a) =>+ GrowableVector v a %1 ->+ Ur [a]+{-# INLINE toList #-}+toList = Ur.lift G.toList . toVector++{- | \(O(n)\). Copy the initialized prefix into an immutable vector and thread+the live growable borrow.+-}+copyToVector ::+ (G.Vector v a, α >= β) =>+ Borrow bk α (GrowableVector v a) %1 ->+ BO β (Ur (v a), Borrow bk α (GrowableVector v a))+{-# INLINE copyToVector #-}+copyToVector =+ Unsafe.toLinear \vector@(UnsafeAlias (GrowableVector ref)) ->+ case Ref.unsafeReadRef ref of+ (Header logicalSize buffer, duplicateRef) ->+ pop (aff duplicateRef) `lseq`+ unsafeSystemIOToBO do+ snapshot <- G.freeze (GM.unsafeTake logicalSize buffer)+ NonLinear.pure (Ur snapshot, vector)++toRefMut ::+ Mut α (GrowableVector v a) %1 ->+ Mut α (Ref.Ref (Header v a))+{-# INLINE toRefMut #-}+toRefMut =+ unsafeMapAlias+ (Unsafe.toLinear \(GrowableVector ref) -> ref)++fromRefMut ::+ Mut α (Ref.Ref (Header v a)) %1 ->+ Mut α (GrowableVector v a)+{-# INLINE fromRefMut #-}+fromRefMut =+ unsafeMapAlias+ (Unsafe.toLinear GrowableVector)++withHeader ::+ (α >= β) =>+ (Header v a %1 -> BO β (result, Header v a)) %1 ->+ Mut α (GrowableVector v a) %1 ->+ BO β (result, Mut α (GrowableVector v a))+{-# INLINE withHeader #-}+withHeader action vector = Control.do+ (result, ref) <- RefBorrow.update action (toRefMut vector)+ Control.pure (result, fromRefMut ref)++-- | \(O(1)\). Return the number of initialized elements and thread the borrow.+size ::+ Borrow bk α (GrowableVector v a) %1 ->+ (Ur Int, Borrow bk α (GrowableVector v a))+{-# INLINE size #-}+size =+ Unsafe.toLinear \vector@(UnsafeAlias (GrowableVector ref)) ->+ case Ref.unsafeReadRef ref of+ (Header logicalSize _, duplicateRef) ->+ pop (aff duplicateRef) `lseq` (Ur logicalSize, vector)++-- | \(O(1)\). Return the backing allocation size and thread the borrow.+capacity ::+ (G.Vector v a) =>+ Borrow bk α (GrowableVector v a) %1 ->+ (Ur Int, Borrow bk α (GrowableVector v a))+{-# INLINE capacity #-}+capacity =+ Unsafe.toLinear \vector@(UnsafeAlias (GrowableVector ref)) ->+ case Ref.unsafeReadRef ref of+ (Header _ buffer, duplicateRef) ->+ pop (aff duplicateRef) `lseq` (Ur (GM.length buffer), vector)++-- | Read an initialized element and thread the growable borrow.+get ::+ (HasCallStack, G.Vector v a, α >= β) =>+ Int ->+ Borrow bk α (GrowableVector v a) %1 ->+ BO β (Ur a, Borrow bk α (GrowableVector v a))+{-# INLINE get #-}+get index vector =+ case size vector of+ (Ur logicalSize, vector) ->+ if index < 0 || index >= logicalSize+ then+ error+ ( "get: index "+ <> show index+ <> " out of bounds for length "+ <> show logicalSize+ )+ vector+ else unsafeGet index vector++-- | Unchecked 'get'. The index must satisfy @0 <= index < size@.+unsafeGet ::+ (G.Vector v a, α >= β) =>+ Int ->+ Borrow bk α (GrowableVector v a) %1 ->+ BO β (Ur a, Borrow bk α (GrowableVector v a))+{-# INLINE unsafeGet #-}+unsafeGet index =+ Unsafe.toLinear \vector@(UnsafeAlias (GrowableVector ref)) ->+ case Ref.unsafeReadRef ref of+ (Header _ buffer, duplicateRef) ->+ pop (aff duplicateRef) `lseq`+ unsafeSystemIOToBO do+ value <- GM.unsafeRead buffer index+ NonLinear.pure (Ur value, vector)++-- | Read the first initialized element.+head ::+ (HasCallStack, G.Vector v a, α >= β) =>+ Borrow bk α (GrowableVector v a) %1 ->+ BO β (Ur a, Borrow bk α (GrowableVector v a))+{-# INLINE head #-}+head = get 0++-- | Unchecked 'head'. The vector must be non-empty.+unsafeHead ::+ (G.Vector v a, α >= β) =>+ Borrow bk α (GrowableVector v a) %1 ->+ BO β (Ur a, Borrow bk α (GrowableVector v a))+{-# INLINE unsafeHead #-}+unsafeHead = unsafeGet 0++-- | Read the last initialized element.+last ::+ (HasCallStack, G.Vector v a, α >= β) =>+ Borrow bk α (GrowableVector v a) %1 ->+ BO β (Ur a, Borrow bk α (GrowableVector v a))+{-# INLINE last #-}+last vector =+ case size vector of+ (Ur logicalSize, vector) ->+ if logicalSize <= 0+ then error "last: empty vector" vector+ else unsafeGet (logicalSize - 1) vector++-- | Unchecked 'last'. The vector must be non-empty.+unsafeLast ::+ (G.Vector v a, α >= β) =>+ Borrow bk α (GrowableVector v a) %1 ->+ BO β (Ur a, Borrow bk α (GrowableVector v a))+{-# INLINE unsafeLast #-}+unsafeLast vector =+ case size vector of+ (Ur logicalSize, vector) -> unsafeGet (logicalSize - 1) vector++-- | Read an initialized element through a shared borrow.+copyAt ::+ (HasCallStack, G.Vector v a, α >= β) =>+ Int ->+ Share α (GrowableVector v a) ->+ BO β (Ur a)+{-# INLINE copyAt #-}+copyAt index vector = Control.do+ (value, vector) <- get index vector+ Control.pure (consume vector `lseq` value)++-- | Unchecked 'copyAt'. The index must satisfy @0 <= index < size@.+unsafeCopyAt ::+ (G.Vector v a, α >= β) =>+ Int ->+ Share α (GrowableVector v a) ->+ BO β (Ur a)+{-# INLINE unsafeCopyAt #-}+unsafeCopyAt index vector = Control.do+ (value, vector) <- unsafeGet index vector+ Control.pure (consume vector `lseq` value)++-- | Read an initialized element and retain the mutable growable borrow.+copyAtMut ::+ (HasCallStack, G.Vector v a, α >= β) =>+ Int ->+ Mut α (GrowableVector v a) %1 ->+ BO β (Ur a, Mut α (GrowableVector v a))+{-# INLINE copyAtMut #-}+copyAtMut = get++-- | Unchecked 'copyAtMut'. The index must satisfy @0 <= index < size@.+unsafeCopyAtMut ::+ (G.Vector v a, α >= β) =>+ Int ->+ Mut α (GrowableVector v a) %1 ->+ BO β (Ur a, Mut α (GrowableVector v a))+{-# INLINE unsafeCopyAtMut #-}+unsafeCopyAtMut = unsafeGet++-- | Replace an initialized element and return the displaced value.+set ::+ (HasCallStack, G.Vector v a, α >= β) =>+ Int ->+ a ->+ Mut α (GrowableVector v a) %1 ->+ BO β (Ur a, Mut α (GrowableVector v a))+{-# INLINE set #-}+set index value vector =+ case size vector of+ (Ur logicalSize, vector) ->+ if index < 0 || index >= logicalSize+ then+ error+ ( "set: index "+ <> show index+ <> " out of bounds for length "+ <> show logicalSize+ )+ vector+ else unsafeSet index value vector++-- | Unchecked 'set'. The index must satisfy @0 <= index < size@.+unsafeSet ::+ (G.Vector v a, α >= β) =>+ Int ->+ a ->+ Mut α (GrowableVector v a) %1 ->+ BO β (Ur a, Mut α (GrowableVector v a))+{-# INLINE unsafeSet #-}+unsafeSet index value =+ Unsafe.toLinear \vector@(UnsafeAlias (GrowableVector ref)) ->+ case Ref.unsafeReadRef ref of+ (Header _ buffer, duplicateRef) ->+ pop (aff duplicateRef) `lseq`+ unsafeSystemIOToBO do+ oldValue <- GM.unsafeExchange buffer index value+ NonLinear.pure (Ur oldValue, vector)++-- | Replace an initialized element and discard the displaced GC-owned value.+write ::+ (HasCallStack, G.Vector v a, α >= β) =>+ Int ->+ a ->+ Mut α (GrowableVector v a) %1 ->+ BO β (Mut α (GrowableVector v a))+{-# INLINE write #-}+write index value vector =+ case size vector of+ (Ur logicalSize, vector) ->+ if index < 0 || index >= logicalSize+ then+ error+ ( "write: index "+ <> show index+ <> " out of bounds for length "+ <> show logicalSize+ )+ vector+ else unsafeWrite index value vector++-- | Unchecked 'write'. The index must satisfy @0 <= index < size@.+unsafeWrite ::+ (G.Vector v a, α >= β) =>+ Int ->+ a ->+ Mut α (GrowableVector v a) %1 ->+ BO β (Mut α (GrowableVector v a))+{-# INLINE unsafeWrite #-}+unsafeWrite index value =+ Unsafe.toLinear \vector@(UnsafeAlias (GrowableVector ref)) ->+ case Ref.unsafeReadRef ref of+ (Header _ buffer, duplicateRef) ->+ pop (aff duplicateRef) `lseq`+ unsafeSystemIOToBO do+ GM.unsafeWrite buffer index value+ NonLinear.pure vector++-- | Transform an initialized element and return an unrestricted result.+update ::+ (HasCallStack, G.Vector v a, α >= β) =>+ Int ->+ (a -> BO β (Ur result, Ur a)) ->+ Mut α (GrowableVector v a) %1 ->+ BO β (Ur result, Mut α (GrowableVector v a))+{-# INLINE update #-}+update index action vector =+ case size vector of+ (Ur logicalSize, vector) ->+ if index < 0 || index >= logicalSize+ then+ error+ ( "update: index "+ <> show index+ <> " out of bounds for length "+ <> show logicalSize+ )+ vector+ else unsafeUpdate index action vector++-- | Unchecked 'update'. The index must satisfy @0 <= index < size@.+unsafeUpdate ::+ (G.Vector v a, α >= β) =>+ Int ->+ (a -> BO β (Ur result, Ur a)) ->+ Mut α (GrowableVector v a) %1 ->+ BO β (Ur result, Mut α (GrowableVector v a))+{-# INLINE unsafeUpdate #-}+unsafeUpdate index action =+ Unsafe.toLinear \vector@(UnsafeAlias (GrowableVector ref)) ->+ case Ref.unsafeReadRef ref of+ (Header _ buffer, duplicateRef) ->+ pop (aff duplicateRef) `lseq` Control.do+ Ur value <-+ unsafeSystemIOToBO do+ value <- GM.unsafeRead buffer index+ NonLinear.pure (Ur value)+ (result, Ur updatedValue) <- action value+ () <- unsafeSystemIOToBO (GM.unsafeWrite buffer index updatedValue)+ Control.pure (result, vector)++-- | Transform an initialized element.+modify ::+ (HasCallStack, G.Vector v a, α >= β) =>+ Int ->+ (a -> a) ->+ Mut α (GrowableVector v a) %1 ->+ BO β (Mut α (GrowableVector v a))+{-# INLINE modify #-}+modify index function vector = Control.do+ (Ur (), vector) <-+ update+ index+ (\value -> Control.pure (Ur (), Ur (function value)))+ vector+ Control.pure vector++-- | Swap two initialized elements.+swap ::+ (HasCallStack, G.Vector v a, α >= β) =>+ Mut α (GrowableVector v a) %1 ->+ Int ->+ Int ->+ BO β (Mut α (GrowableVector v a))+{-# INLINE swap #-}+swap vector first second =+ case size vector of+ (Ur logicalSize, vector) ->+ if first+ < 0+ || first+ >= logicalSize+ || second+ < 0+ || second+ >= logicalSize+ then+ error+ ( "swap: indices "+ <> show (first, second)+ <> " out of bounds for length "+ <> show logicalSize+ )+ vector+ else unsafeSwap vector first second++-- | Unchecked 'swap'. Both indices must satisfy @0 <= index < size@.+unsafeSwap ::+ (G.Vector v a, α >= β) =>+ Mut α (GrowableVector v a) %1 ->+ Int ->+ Int ->+ BO β (Mut α (GrowableVector v a))+{-# INLINE unsafeSwap #-}+unsafeSwap =+ Unsafe.toLinear \vector@(UnsafeAlias (GrowableVector ref)) first second ->+ case Ref.unsafeReadRef ref of+ (Header _ buffer, duplicateRef) ->+ pop (aff duplicateRef) `lseq`+ unsafeSystemIOToBO do+ GM.unsafeSwap buffer first second+ NonLinear.pure vector++{- | Ensure at least the requested absolute capacity.++The request must be non-negative. Logical size and initialized contents do not+change.+-}+reserve ::+ (HasCallStack, G.Vector v a, α >= β) =>+ Int ->+ Mut α (GrowableVector v a) %1 ->+ BO β (Mut α (GrowableVector v a))+{-# INLINE reserve #-}+reserve requested vector+ | requested < 0 =+ error ("reserve: negative capacity " <> show requested) vector+ | otherwise = Control.do+ ((), vector) <-+ withHeader+ ( Unsafe.toLinear \(Header logicalSize buffer) -> Control.do+ grown <- growTo logicalSize requested buffer+ Control.pure ((), Header logicalSize grown)+ )+ vector+ Control.pure vector++{- | Ensure capacity for at least the current size plus the requested amount.++The additional amount must be non-negative.+-}+reserveAdditional ::+ (HasCallStack, G.Vector v a, α >= β) =>+ Int ->+ Mut α (GrowableVector v a) %1 ->+ BO β (Mut α (GrowableVector v a))+{-# INLINE reserveAdditional #-}+reserveAdditional additional vector+ | additional < 0 =+ error+ ("reserveAdditional: negative additional capacity " <> show additional)+ vector+ | otherwise = Control.do+ ((), vector) <-+ withHeader+ ( Unsafe.toLinear \(Header logicalSize buffer) ->+ let !required =+ checkedAdd "reserveAdditional" logicalSize additional+ in Control.do+ grown <- growTo logicalSize required buffer+ Control.pure ((), Header logicalSize grown)+ )+ vector+ Control.pure vector++-- | Append one unrestricted element to the initialized prefix.+push ::+ (HasCallStack, G.Vector v a, α >= β) =>+ a ->+ Mut α (GrowableVector v a) %1 ->+ BO β (Mut α (GrowableVector v a))+{-# INLINE push #-}+push value vector = Control.do+ ((), vector) <-+ withHeader+ ( Unsafe.toLinear \(Header logicalSize buffer) ->+ let !required = checkedAdd "push" logicalSize 1+ !target = growthTarget (GM.length buffer) required+ in Control.do+ grown <- growTo logicalSize target buffer+ grown <- writeAt logicalSize value grown+ Control.pure ((), Header required grown)+ )+ vector+ Control.pure vector++-- | Append all elements of an immutable vector.+extend ::+ (HasCallStack, G.Vector v a, α >= β) =>+ v a ->+ Mut α (GrowableVector v a) %1 ->+ BO β (Mut α (GrowableVector v a))+{-# INLINE extend #-}+extend source vector = Control.do+ ((), vector) <-+ withHeader+ ( Unsafe.toLinear \(Header logicalSize buffer) ->+ let !sourceSize = G.length source+ !required = checkedAdd "extend" logicalSize sourceSize+ !target = growthTarget (GM.length buffer) required+ in Control.do+ grown <- growTo logicalSize target buffer+ grown <- copyImmutableInto source logicalSize grown+ Control.pure ((), Header required grown)+ )+ vector+ Control.pure vector++copyImmutable ::+ (G.Vector v a) =>+ v a ->+ Int ->+ G.Mutable v RealWorld a ->+ NonLinear.IO ()+{-# INLINE copyImmutable #-}+copyImmutable source offset target =+ G.copy+ (GM.unsafeSlice offset (G.length source) target)+ source++copyImmutableInto ::+ (G.Vector v a) =>+ v a ->+ Int ->+ G.Mutable v RealWorld a %1 ->+ BO β (G.Mutable v RealWorld a)+{-# INLINE copyImmutableInto #-}+copyImmutableInto source offset =+ Unsafe.toLinear \target -> unsafeSystemIOToBO do+ copyImmutable source offset target+ NonLinear.pure target++writeAt ::+ (G.Vector v a) =>+ Int ->+ a ->+ G.Mutable v RealWorld a %1 ->+ BO β (G.Mutable v RealWorld a)+{-# INLINE writeAt #-}+writeAt index value =+ Unsafe.toLinear \target -> unsafeSystemIOToBO do+ GM.unsafeWrite target index value+ NonLinear.pure target++growTo ::+ (G.Vector v a) =>+ Int ->+ Int ->+ G.Mutable v RealWorld a %1 ->+ BO β (G.Mutable v RealWorld a)+{-# INLINE growTo #-}+growTo =+ Unsafe.toLinear3 \logicalSize requested buffer ->+ let !oldCapacity = GM.length buffer+ in if requested <= oldCapacity+ then Control.pure buffer+ else unsafeSystemIOToBO do+ grown <- GM.unsafeNew requested+ GM.unsafeCopy+ (GM.unsafeTake logicalSize grown)+ (GM.unsafeTake logicalSize buffer)+ NonLinear.pure grown++growthTarget :: Int -> Int -> Int+{-# INLINE growthTarget #-}+growthTarget oldCapacity required+ | required <= oldCapacity = oldCapacity+ | oldCapacity <= 0 = required `max` 1+ | oldCapacity > maxBound `quot` 2 = required+ | otherwise = required `max` (oldCapacity * 2)++checkedAdd :: (HasCallStack) => NonLinear.String -> Int -> Int -> Int+{-# INLINE checkedAdd #-}+checkedAdd operation left right+ | right > maxBound - left =+ error (operation <> ": capacity overflow")+ | otherwise = left + right++{- | Project a growable borrow to the fixed initialized prefix.++The result preserves the borrow kind and lifetime and exposes neither spare+capacity nor growth. A mutable result may be split with the fixed unrestricted+vector API. The growable borrow becomes recoverable only after all resulting+fixed borrows have ended.++Each call performs one header read. Where a transaction branches, prefer+projecting once at its entry --++@+let %1 !content = 'getContents' borrow+@++-- over projecting separately inside each branch. Both are correct and consume+the growable occurrence exactly once; the entry form simply gives the+optimizer one read to place rather than one per surviving branch, which+matters for code size in transactions with several control exits.+-}+getContents ::+ (G.Vector v a) =>+ Borrow bk α (GrowableVector v a) %1 ->+ Borrow bk α (Fixed.Vector v a)+{-# INLINE getContents #-}+getContents =+ Unsafe.toLinear \(UnsafeAlias (GrowableVector ref)) ->+ -- SAFETY: unsafeReadRef exposes the current header while returning the same+ -- borrowed Ref handle. Discarding that returned handle does not free the+ -- authoritative header retained by an enclosing lender. The input+ -- growable occurrence is consumed by this projection and cannot be used+ -- for reserve or growth. The result preserves its borrow kind and lifetime+ -- and exposes exactly the initialized prefix, so mutable access cannot+ -- coexist with growth and shared access remains read-only. The fixed safe+ -- API can split this slice but cannot resize it, reveal spare capacity, or+ -- consume/freeze the growable backing owner. Nominal backend and element+ -- roles prevent selecting operations for a different representation.+ case Ref.unsafeReadRef ref of+ (Header logicalSize buffer, duplicateRef) ->+ pop (aff duplicateRef) `lseq`+ UnsafeAlias+ (Fixed.Internal.unsafeFromMutableSlice 0 logicalSize buffer)++{- | Borrow the fixed initialized prefix in a rank-2 no-growth scope.++The callback receives one linear occurrence for either borrow kind. Shared+callers may use 'move' to regain unrestricted use. A mutable growable borrow is+restored only after the callback result is produced and the fixed view ends.+-}+withContent ::+ (G.Vector v a) =>+ Borrow bk α (GrowableVector v a) %1 ->+ ( forall β.+ Borrow bk (β /\ α) (Fixed.Vector v a) %1 ->+ BO (β /\ α) result+ ) %1 ->+ BO α (result, Borrow bk α (GrowableVector v a))+{-# INLINE withContent #-}+withContent =+ Unsafe.toLinear2 \vector action ->+ -- SAFETY: the coercion changes only the phantom lifetime of this one+ -- retained borrow occurrence. unsafeSrunBO_ chooses a fresh rigid+ -- sublifetime and invokes action exactly once. Linearity keeps vector+ -- inaccessible until action has consumed every fixed slice and returned;+ -- the result type cannot mention the fresh lifetime. Only then is the+ -- original growable occurrence restored. This is a normal-return+ -- guarantee; the module promises no owner recovery after an exception.+ -- The growable borrow is handed back through `reviveAlias`, as the scalar delimiters do: see Note [Restoring a borrow must break its Core identity] in "Control.Monad.Borrow.Pure.BO.Internal".+ unsafeSrunBO_ Control.do+ result <- action (getContents (Unsafe.coerce vector))+ (result,) Control.<$> reviveAlias vector++-- | A result-discarding variant of 'withContent'.+withContent_ ::+ (G.Vector v a, Consumable result) =>+ Borrow bk α (GrowableVector v a) %1 ->+ ( forall β.+ Borrow bk (β /\ α) (Fixed.Vector v a) %1 ->+ BO (β /\ α) result+ ) %1 ->+ BO α (Borrow bk α (GrowableVector v a))+{-# INLINE withContent_ #-}+withContent_ vector action =+ withContent vector action Control.<&> \(result, vector) ->+ consume result `lseq` vector
+ src/Data/Vector/Generic/Mutable/Linear/Borrow/Experimental/Multiplicity.hs view
@@ -0,0 +1,58 @@+{-# LANGUAGE NoImplicitPrelude #-}++{- |+__Experimental.__ This API is unstable and may change without preserving+compatibility.++Linearly owned mutable vectors whose element ownership is selected by a+multiplicity parameter.++The public backend parameter @v@ selects any immutable backend supported by+@vector@'s generic interface. The owner and its mutable backing remain linear.++Custom backends are a trusted extensibility boundary: their mutable-vector+operations must obey the usual @vector@ laws, including fresh allocation and+cloning, exact indexing, disjoint non-overlapping splits, copying thaw, and+alias-free ownership transfer through unsafe freeze/thaw. The standard boxed,+unboxed, and primitive backends satisfy these requirements.+-}+module Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity (+ Vector,+ KnownMultiplicity,+ GetResult,+ UpdateAction,+ Bound,+ PossiblyConsumable,+ PossiblyMovable,+ PossiblyCopyable,+ empty,+ constant,+ fromList,+ fromVector,+ unsafeFromVector,+ unsafeFromMutable,+ toVector,+ toList,+ copyToVector,+ size,+ get,+ unsafeGet,+ head,+ unsafeHead,+ last,+ unsafeLast,+ copyAt,+ copyAtMut,+ set,+ unsafeSet,+ write,+ unsafeWrite,+ update,+ unsafeUpdate,+ modify,+ swap,+ unsafeSwap,+ splitAt,+) where++import Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity.Internal
+ src/Data/Vector/Generic/Mutable/Linear/Borrow/Experimental/Multiplicity/Internal.hs view
@@ -0,0 +1,710 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE RoleAnnotations #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeFamilyDependencies #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}+{-# OPTIONS_GHC -Wno-redundant-constraints #-}+{-# OPTIONS_HADDOCK hide #-}++module Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity.Internal (+ module Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity.Internal,+) where++import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.BO.Unsafe+import Control.Monad.Borrow.Pure.Clone+import Control.Monad.Borrow.Pure.Copyable+import Control.Monad.Borrow.Pure.Lifetime.Token.Unsafe (+ LinearOnly (..),+ LinearOnlyWitness (..),+ )+import Data.Kind (Constraint, Type)+import Data.Vector.Generic qualified as G+import Data.Vector.Generic.Mutable qualified as GM+import Data.Vector.Mutable (RealWorld)+import GHC.Exts (Multiplicity (..))+import GHC.Exts qualified as GHC+import GHC.IO (unsafePerformIO)+import GHC.Stack (HasCallStack)+import GHC.TypeError+import Prelude.Linear hiding (head, last, splitAt)+import Unsafe.Linear qualified as Unsafe+import Prelude qualified as NonLinear++-- | Linearly owned mutable vector with mode-indexed element ownership.+type Vector :: GHC.Multiplicity -> (Type -> Type) -> Type -> Type+newtype Vector p v a = Vector {content :: G.Mutable v RealWorld a}++-- | Linear mutable vector, owning the elements exclusively.+type OwningVector = Vector One++-- | Linear mutable vector, but the elements are bound unrestricted.+type UrVector = Vector Many++type role Vector nominal nominal nominal++-- | A linearly owned result for 'One', or a GC-owned result for 'Many'.+type Bound :: Multiplicity -> Type -> Type+type family Bound p a = result where+ Bound One a = a+ Bound Many a = Ur a++-- | 'Consumable' when the multiplicity is 'One', and always satisfied when the multiplicity is 'Many'.+type PossiblyConsumable :: Multiplicity -> Type -> Constraint+type family PossiblyConsumable p a where+ PossiblyConsumable One a = (Consumable a)+ PossiblyConsumable Many a = ()++-- | 'Movable' when the multiplicity is 'One', and always satisfied when the multiplicity is 'Many'.+type PossiblyMovable :: Multiplicity -> Type -> Constraint+type family PossiblyMovable p a where+ PossiblyMovable One a = (Movable a)+ PossiblyMovable Many a = ()++-- | 'Copyable' when the multiplicity is 'One', and always satisfied when the multiplicity is 'Many'.+type PossiblyCopyable :: Multiplicity -> Type -> Constraint+type family PossiblyCopyable p a where+ PossiblyCopyable One a = (Copyable a)+ PossiblyCopyable Many a = ()++-- | Result of reading an element while respecting the vector's element ownership.+type GetResult :: Multiplicity -> BorrowKind -> Lifetime -> (Type -> Type) -> Type -> Type+type family GetResult p bk α v a where+ GetResult One bk α v a = Borrow bk α a+ GetResult Many bk α v a =+ (Ur a, Borrow bk α (Vector Many v a))++-- | Mode-indexed element update callback.+type UpdateAction :: Multiplicity -> Lifetime -> Type -> Type -> Type+type UpdateAction p β result a =+ a %p -> BO β (Bound p result, Bound p a)++-- | Construct a fixed-size view over a raw mutable-vector slice.+unsafeFromMutableSlice ::+ (G.Vector v a) =>+ Int ->+ Int ->+ G.Mutable v RealWorld a %1 ->+ Vector p v a+{-# INLINE unsafeFromMutableSlice #-}+unsafeFromMutableSlice offset length_ =+ Unsafe.toLinear \buffer ->+ Vector (GM.unsafeSlice offset length_ buffer)++instance LinearOnly (Vector p v a) where+ linearOnly = UnsafeLinearOnly+ {-# INLINE linearOnly #-}++instance+ (Unsatisfiable (ShowType (Vector p v a) :<>: Text " cannot be copied!")) =>+ Copyable (Vector p v a)+ where+ copy = unsatisfiable++instance (G.Vector v a) => Consumable (Vector Many v a) where+ consume = Unsafe.toLinear \_ -> ()+ {-# INLINE consume #-}++instance (G.Vector v a, Consumable a) => Consumable (Vector One v a) where+ consume =+ Unsafe.toLinear \(Vector vector) ->+ unsafePerformIO (consumeElements 0 (GM.length vector) vector)+ -- Only the 'One' instance releases elements, and it does so under+ -- 'unsafePerformIO'. Inlining would let GHC duplicate that call across use+ -- sites, or float it out of a scope, and each copy would consume the+ -- elements again; 'NOINLINE' keeps exactly one occurrence. The 'Many'+ -- instance above owns nothing and stays 'INLINE'.+ {-# NOINLINE consume #-}++consumeElements ::+ (G.Vector v a, Consumable a) =>+ Int ->+ Int ->+ G.Mutable v RealWorld a ->+ IO ()+{-# INLINE consumeElements #-}+consumeElements !index !length_ vector+ | index >= length_ = NonLinear.pure ()+ | otherwise = do+ value <- GM.unsafeRead vector index+ let !() = consume value+ consumeElements (index + 1) length_ vector++instance (G.Vector v a) => Clone (Vector Many v a) where+ clone = Unsafe.toLinear \(UnsafeAlias (Vector vector)) ->+ Vector Control.<$> unsafeSystemIOToBO (GM.clone vector)+ {-# INLINE clone #-}++instance (G.Vector v a, Dupable a) => Clone (Vector One v a) where+ clone = Unsafe.toLinear \(UnsafeAlias (Vector source)) ->+ Vector Control.<$> unsafeSystemIOToBO do+ let !length_ = GM.length source+ target <- GM.unsafeNew length_+ let go !index+ | index >= length_ = NonLinear.pure ()+ | otherwise = do+ value <- GM.unsafeRead source index+ let (!_, !clonedValue) = dup value+ GM.unsafeWrite target index clonedValue+ go (index + 1)+ go 0+ NonLinear.pure target+ {-# INLINE clone #-}++-- | \(O(1)\). Construct an empty vector.+empty :: (G.Vector v a) => Linearly %1 -> Vector p v a+{-# NOINLINE empty #-}+empty =+ GHC.noinline \linear ->+ linear `lseq` Vector (unsafePerformIO (GM.unsafeNew 0))++{- | \(O(n)\). Construct a vector containing @count@ copies of a value.++As in @vector@, a negative count produces an empty vector.+-}+constant ::+ (G.Vector v a) =>+ Int ->+ a ->+ Linearly %1 ->+ Vector Many v a+{-# NOINLINE constant #-}+constant =+ GHC.noinline \count value linear ->+ linear `lseq` Vector (unsafePerformIO (GM.replicate count value))++-- | \(O(n)\). Materialize a list whose binding multiplicity matches the mode.+fromList ::+ (G.Vector v a) =>+ [a] %p ->+ Linearly %1 ->+ Vector p v a+{-# NOINLINE fromList #-}+fromList =+ GHC.noinline $ Unsafe.toLinear \values linear ->+ linear `lseq`+ Vector+ (unsafePerformIO (G.thaw (G.fromList values)))++-- | \(O(n)\). Copy an immutable vector into a new owner.+fromVector ::+ (G.Vector v a) =>+ v a ->+ Linearly %1 ->+ Vector Many v a+{-# NOINLINE fromVector #-}+fromVector =+ GHC.noinline \source linear ->+ linear `lseq` Vector (unsafePerformIO (G.thaw source))++{- | \(O(1)\). Unsafely take ownership of an immutable vector.++The caller must ensure that no alias of the source vector, including an+overlapping slice, is ever observed again.+-}+unsafeFromVector ::+ (G.Vector v a) =>+ v a %1 ->+ Linearly %1 ->+ Vector p v a+{-# NOINLINE unsafeFromVector #-}+unsafeFromVector =+ GHC.noinline $+ Unsafe.toLinear \source linear ->+ linear `lseq` Vector (unsafePerformIO (G.unsafeThaw source))++{- | \(O(1)\). Unsafely take ownership of a mutable vector.++The caller must not retain any alias or overlapping slice of the input. The+entire adopted slice must be initialized.+-}+unsafeFromMutable ::+ (G.Vector v a) =>+ G.Mutable v state a %1 ->+ Linearly %1 ->+ Vector p v a+{-# INLINE unsafeFromMutable #-}+unsafeFromMutable =+ Unsafe.toLinear \source linear ->+ linear `lseq` Vector (Unsafe.coerce source)++type KnownMultiplicity :: Multiplicity -> Constraint++-- | Internal evidence selecting the ownership-sensitive implementation.+class KnownMultiplicity p where+ mMove :: a %1 -> Bound p a++ -- | \(O(1)\). Consume the owner and freeze its backing storage.+ toVector :: (G.Vector v a, PossiblyMovable p a) => Vector p v a %1 -> Ur (v a)++ mCopy :: forall a. (PossiblyCopyable p a) => a -> Bound p a++ mUnbind :: Bound p a %1 -> a++ copyVector ::+ (G.Vector v a, PossiblyCopyable p a) =>+ v a ->+ Ur (v a)++ mUnsafeWrite ::+ forall v a α β.+ (G.Vector v a, α >= β, PossiblyConsumable p a) =>+ Int ->+ a %p ->+ Mut α (Vector p v a) %1 ->+ BO β (Mut α (Vector p v a))++ mUnsafeGet ::+ forall bk α β v a.+ (G.Vector v a, α >= β) =>+ Int ->+ Borrow bk α (Vector p v a) %1 ->+ BO β (GetResult p bk α v a)++instance KnownMultiplicity Many where+ {-# SPECIALIZE instance KnownMultiplicity Many #-}+ mMove = Unsafe.toLinear Ur+ {-# INLINE mMove #-}++ toVector = Unsafe.toLinear \v -> Ur $ unsafeToVector v+ {-# INLINE toVector #-}++ mCopy = Unsafe.toLinear Ur+ {-# INLINE mCopy #-}++ mUnbind = \(Ur value) -> value+ {-# INLINE mUnbind #-}++ copyVector = Unsafe.toLinear Ur+ {-# INLINE copyVector #-}++ mUnsafeWrite =+ Unsafe.toLinear3 \index value array@(UnsafeAlias (Vector vector)) ->+ unsafeSystemIOToBO do+ GM.unsafeWrite vector index value+ NonLinear.pure array+ {-# INLINE mUnsafeWrite #-}++ mUnsafeGet index =+ Unsafe.toLinear \vectorBorrow@(UnsafeAlias (Vector vector)) ->+ unsafeSystemIOToBO do+ value <- GM.unsafeRead vector index+ NonLinear.pure (Ur value, vectorBorrow)+ {-# INLINE mUnsafeGet #-}++instance KnownMultiplicity One where+ {-# SPECIALIZE instance KnownMultiplicity One #-}+ mMove = id+ {-# INLINE mMove #-}+ toVector = Unsafe.toLinear \(v :: Vector One v a) ->+ G.mapM (Unsafe.toLinear move) (unsafeToVector v)+ {-# INLINE toVector #-}++ mCopy = \value -> copy (UnsafeAlias value)+ {-# INLINE mCopy #-}++ mUnbind = id+ {-# INLINE mUnbind #-}++ copyVector =+ G.mapM \value ->+ Ur $! copy (UnsafeAlias value)+ {-# INLINE copyVector #-}++ mUnsafeWrite =+ Unsafe.toLinear3 \index value array@(UnsafeAlias (Vector vector)) ->+ unsafeSystemIOToBO do+ oldValue <- GM.unsafeExchange vector index value+ let !() = consume oldValue+ NonLinear.pure array+ {-# INLINE mUnsafeWrite #-}++ mUnsafeGet index =+ Unsafe.toLinear \(UnsafeAlias (Vector vector)) ->+ UnsafeAlias Control.<$> unsafeSystemIOToBO (GM.unsafeRead vector index)+ {-# INLINE mUnsafeGet #-}++unsafeToVector ::+ (G.Vector v a) =>+ Vector p v a %1 ->+ v a+{-# NOINLINE unsafeToVector #-}+unsafeToVector =+ GHC.noinline $+ Unsafe.toLinear \(Vector vector) ->+ unsafePerformIO (G.unsafeFreeze vector)++unsafeToList :: (G.Vector v a) => Vector p v a %1 -> [a]+{-# NOINLINE unsafeToList #-}+unsafeToList = GHC.noinline $+ Unsafe.toLinear \(Vector vector) ->+ G.toList $! unsafePerformIO (G.unsafeFreeze vector)++-- | \(O(n)\). Consume the owner and materialize its elements as a list.+toList :: (KnownMultiplicity p, G.Vector v a) => Vector p v a %1 -> Bound p [a]+{-# INLINE toList #-}+{-# SPECIALIZE INLINE toList :: (G.Vector v a) => Vector Many v a %1 -> Ur [a] #-}+{-# SPECIALIZE INLINE toList :: (G.Vector v a) => Vector One v a %1 -> [a] #-}+toList (v :: Vector p v a) = mMove @p (unsafeToList v)++{- | \(O(n)\). Copy a live vector into an immutable vector and thread its+borrow.+-}+copyToVector ::+ ( G.Vector v a+ , α >= β+ , KnownMultiplicity p+ , PossiblyCopyable p a+ ) =>+ Borrow bk α (Vector p v a) %1 ->+ BO β (Ur (v a), Borrow bk α (Vector p v a))+{-# INLINE copyToVector #-}+copyToVector =+ Unsafe.toLinear \vectorBorrow@(UnsafeAlias (Vector vector) :: Borrow bk α (Vector p v a)) ->+ unsafeSystemIOToBO do+ snapshot <- G.freeze vector+ case copyVector @p snapshot of+ Ur !copied -> NonLinear.pure (Ur copied, vectorBorrow)++-- | \(O(1)\). Return the number of elements and thread the vector borrow.+size ::+ (G.Vector v a) =>+ Borrow bk α (Vector p v a) %1 ->+ (Ur Int, Borrow bk α (Vector p v a))+{-# INLINE size #-}+size =+ Unsafe.toLinear \vectorBorrow@(UnsafeAlias (Vector vector)) ->+ (Ur (GM.length vector), vectorBorrow)++-- | Read the element at an index and thread the vector borrow.+get ::+ (HasCallStack, G.Vector v a, α >= β, KnownMultiplicity p) =>+ Int ->+ Borrow bk α (Vector p v a) %1 ->+ BO β (GetResult p bk α v a)+{-# INLINE get #-}+get index vector =+ case size vector of+ (Ur length_, vector)+ | index < 0 || index >= length_ ->+ error+ ( "get: index "+ <> show index+ <> " out of bounds for length "+ <> show length_+ )+ vector+ | otherwise -> unsafeGet index vector++-- | Unchecked 'get'. The index must satisfy @0 <= index < size@.+unsafeGet ::+ forall p v a α β bk.+ (G.Vector v a, α >= β, KnownMultiplicity p) =>+ Int ->+ Borrow bk α (Vector p v a) %1 ->+ BO β (GetResult p bk α v a)+{-# INLINE unsafeGet #-}+unsafeGet = mUnsafeGet @p++-- | Read the first element and thread the vector borrow.+head ::+ (HasCallStack, G.Vector v a, α >= β, KnownMultiplicity p) =>+ Borrow bk α (Vector p v a) %1 ->+ BO β (GetResult p bk α v a)+{-# INLINE head #-}+head = get 0++-- | Unchecked 'head'. The vector must be non-empty.+unsafeHead ::+ (G.Vector v a, α >= β, KnownMultiplicity p) =>+ Borrow bk α (Vector p v a) %1 ->+ BO β (GetResult p bk α v a)+{-# INLINE unsafeHead #-}+unsafeHead = unsafeGet 0++-- | Read the last element and thread the vector borrow.+last ::+ (HasCallStack, G.Vector v a, α >= β, KnownMultiplicity p) =>+ Borrow bk α (Vector p v a) %1 ->+ BO β (GetResult p bk α v a)+{-# INLINE last #-}+last vector =+ case size vector of+ (Ur length_, vector)+ | length_ <= 0 -> error "last: empty vector" vector+ | otherwise -> unsafeGet (length_ - 1) vector++-- | Unchecked 'last'. The vector must be non-empty.+unsafeLast ::+ (G.Vector v a, α >= β, KnownMultiplicity p) =>+ Borrow bk α (Vector p v a) %1 ->+ BO β (GetResult p bk α v a)+{-# INLINE unsafeLast #-}+unsafeLast vector =+ case size vector of+ (Ur length_, vector) -> unsafeGet (length_ - 1) vector++-- | Read an element through a shared borrow.+copyAt ::+ ( HasCallStack+ , G.Vector v a+ , α >= β+ , KnownMultiplicity p+ , PossiblyCopyable p a+ ) =>+ Int ->+ Share α (Vector p v a) ->+ BO β (Bound p a)+{-# INLINE copyAt #-}+copyAt =+ Unsafe.toLinear2 \index vector@(UnsafeAlias (Vector buffer) :: Share α (Vector p v a)) ->+ let !length_ = GM.length buffer+ in if index < 0 || index >= length_+ then+ error+ ( "get: index "+ <> show index+ <> " out of bounds for length "+ <> show length_+ )+ vector+ else unsafeSystemIOToBO do+ !value <- GM.unsafeRead buffer index+ let !copied = mCopy @p value+ NonLinear.pure copied++-- | Read an element and retain the mutable vector borrow.+copyAtMut ::+ ( HasCallStack+ , G.Vector v a+ , α >= β+ , KnownMultiplicity p+ , PossiblyCopyable p a+ ) =>+ Int ->+ Mut α (Vector p v a) %1 ->+ BO β (Bound p a, Mut α (Vector p v a))+{-# INLINE copyAtMut #-}+copyAtMut = Unsafe.toLinear2 \i mut@(UnsafeAlias (Vector v) :: Mut α (Vector p v a)) ->+ let !len = GM.length v+ in if i < 0 || i >= len+ then error ("get: index " <> show i <> " out of bound: " <> show len) mut+ else unsafeSystemIOToBO do+ !a <- GM.unsafeRead v i+ let !copied = mCopy @p a+ NonLinear.pure (copied, mut)++-- | Replace an element and return the displaced value.+set ::+ (HasCallStack, G.Vector v a, α >= β, KnownMultiplicity p) =>+ Int ->+ a %p ->+ Mut α (Vector p v a) %1 ->+ BO β (Bound p a, Mut α (Vector p v a))+{-# INLINE set #-}+set index value array =+ case size array of+ (Ur length_, array)+ | index < 0 || index >= length_ ->+ error+ ( "set: index "+ <> show index+ <> " out of bounds for length "+ <> show length_+ )+ array+ value+ | otherwise -> unsafeSet index value array++-- | Unchecked 'set'. The index must satisfy @0 <= index < size@.+unsafeSet ::+ forall p v a α β.+ (G.Vector v a, α >= β, KnownMultiplicity p) =>+ Int ->+ a %p ->+ Mut α (Vector p v a) %1 ->+ BO β (Bound p a, Mut α (Vector p v a))+{-# INLINE unsafeSet #-}+unsafeSet =+ Unsafe.toLinear3 \index !value array@(UnsafeAlias (Vector vector) :: Mut α (Vector p v a)) ->+ unsafeSystemIOToBO do+ oldValue <- GM.unsafeExchange vector index value+ NonLinear.pure (mMove @p oldValue, array)++-- | Replace an element and consume the displaced value.+write ::+ ( HasCallStack+ , G.Vector v a+ , α >= β+ , KnownMultiplicity p+ , PossiblyConsumable p a+ ) =>+ Int ->+ a %p ->+ Mut α (Vector p v a) %1 ->+ BO β (Mut α (Vector p v a))+{-# INLINE write #-}+write index value array =+ case size array of+ (Ur length_, array)+ | index < 0 || index >= length_ ->+ error+ ( "write: index "+ <> show index+ <> " out of bounds for length "+ <> show length_+ )+ array+ value+ | otherwise -> unsafeWrite index value array++-- | Unchecked 'write'. The index must satisfy @0 <= index < size@.+unsafeWrite ::+ forall p v a α β.+ ( G.Vector v a+ , α >= β+ , KnownMultiplicity p+ , PossiblyConsumable p a+ ) =>+ Int ->+ a %p ->+ Mut α (Vector p v a) %1 ->+ BO β (Mut α (Vector p v a))+{-# INLINE unsafeWrite #-}+unsafeWrite = mUnsafeWrite @p++{- | Transform an element and return an auxiliary result.++For an owning vector, the callback and element are linear. For an unrestricted+vector, both are unrestricted and the callback returns 'Ur'-wrapped results.+The mutable vector borrow is unavailable until the replacement has been+written. This is a normal-return guarantee; no owner recovery is promised+after an exception.+-}+update ::+ forall p v a α β result.+ (HasCallStack, G.Vector v a, α >= β, KnownMultiplicity p) =>+ Int ->+ UpdateAction p β result a %p ->+ Mut α (Vector p v a) %1 ->+ BO β (Bound p result, Mut α (Vector p v a))+{-# INLINE update #-}+update index action array =+ case size array of+ (Ur length_, array)+ | index < 0 || index >= length_ ->+ error+ ( "update: index "+ <> show index+ <> " out of bounds for length "+ <> show length_+ )+ array+ action+ | otherwise ->+ unsafeUpdate @p @v @a @α @β @result index action array++{- | Unchecked 'update'. The index must satisfy @0 <= index < size@.++The ownership guarantees are the same as for 'update'.+-}+unsafeUpdate ::+ forall p v a α β result.+ (G.Vector v a, α >= β, KnownMultiplicity p) =>+ Int ->+ UpdateAction p β result a %p ->+ Mut α (Vector p v a) %1 ->+ BO β (Bound p result, Mut α (Vector p v a))+{-# INLINE unsafeUpdate #-}+unsafeUpdate index action =+ Unsafe.toLinear \(UnsafeAlias array@(Vector vector)) -> Control.do+ value <- unsafeSystemIOToBO (GM.unsafeRead vector index)+ (result, updatedValue) <- Unsafe.toLinear action value+ let !unboundValue = mUnbind @p updatedValue+ () <-+ unsafeSystemIOToBO+ (Unsafe.toLinear3 GM.unsafeWrite vector index unboundValue)+ Control.pure (result, UnsafeAlias array)++-- | Transform an element.+modify ::+ forall p v a α β.+ (HasCallStack, G.Vector v a, α >= β, KnownMultiplicity p) =>+ Int ->+ (a %p -> a) %p ->+ Mut α (Vector p v a) %1 ->+ BO β (Mut α (Vector p v a))+{-# INLINE modify #-}+modify index function array = Control.do+ (unit, array) <-+ update @p @v @a @α @β @()+ index+ ( Unsafe.toLinear \value ->+ Control.pure+ ( mMove @p ()+ , mMove @p (Unsafe.toLinear function value)+ )+ )+ array+ case mUnbind @p unit of+ () -> Control.pure array++-- | Swap two elements.+swap ::+ (HasCallStack, G.Vector v a, α >= β) =>+ Mut α (Vector p v a) %1 ->+ Int ->+ Int ->+ BO β (Mut α (Vector p v a))+{-# INLINE swap #-}+swap array first second =+ case size array of+ (Ur length_, array)+ | first < 0 || first >= length_ || second < 0 || second >= length_ ->+ error+ ( "swap: indices "+ <> show (first, second)+ <> " out of bounds for length "+ <> show length_+ )+ array+ | otherwise -> unsafeSwap array first second++-- | Unchecked 'swap'. Both indices must satisfy @0 <= index < size@.+unsafeSwap ::+ (G.Vector v a, α >= β) =>+ Mut α (Vector p v a) %1 ->+ Int ->+ Int ->+ BO β (Mut α (Vector p v a))+{-# INLINE unsafeSwap #-}+unsafeSwap =+ Unsafe.toLinear \array@(UnsafeAlias (Vector vector)) first second ->+ unsafeSystemIOToBO do+ GM.unsafeSwap vector first second+ NonLinear.pure array++{- | Split a borrow into two disjoint fixed ranges without copying.++The index is clamped to @[0, size]@, matching @vector@'s 'GM.splitAt'.+For a custom backend, safety requires its two returned slices not to overlap.+-}+splitAt ::+ (G.Vector v a) =>+ Int ->+ Borrow bk α (Vector p v a) %1 ->+ ( Borrow bk α (Vector p v a)+ , Borrow bk α (Vector p v a)+ )+{-# INLINE splitAt #-}+splitAt index =+ Unsafe.toLinear \(UnsafeAlias (Vector vector)) ->+ case GM.splitAt index vector of+ (left, right) ->+ (UnsafeAlias (Vector left), UnsafeAlias (Vector right))
+ src/Data/Vector/Generic/Mutable/Linear/Borrow/Unrestricted.hs view
@@ -0,0 +1,47 @@+{-# LANGUAGE NoImplicitPrelude #-}++{- |+Linearly owned mutable vectors whose elements are unrestricted and GC-owned.++The public backend parameter @v@ selects any immutable backend supported by+@vector@'s generic interface. The owner and its mutable backing remain linear.++Custom backends are a trusted extensibility boundary: their mutable-vector+operations must obey the usual @vector@ laws, including fresh allocation and+cloning, exact indexing, disjoint non-overlapping splits, copying thaw, and+alias-free ownership transfer through unsafe freeze/thaw. The standard boxed,+unboxed, and primitive backends satisfy these requirements.+-}+module Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted (+ Vector,+ empty,+ constant,+ fromList,+ fromVector,+ unsafeFromVector,+ unsafeFromMutable,+ toVector,+ toList,+ copyToVector,+ size,+ get,+ unsafeGet,+ head,+ unsafeHead,+ last,+ unsafeLast,+ copyAt,+ copyAtMut,+ set,+ unsafeSet,+ write,+ unsafeWrite,+ update,+ unsafeUpdate,+ modify,+ swap,+ unsafeSwap,+ splitAt,+) where++import Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted.Internal
+ src/Data/Vector/Generic/Mutable/Linear/Borrow/Unrestricted/Internal.hs view
@@ -0,0 +1,477 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE RoleAnnotations #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}+{-# OPTIONS_GHC -Wno-redundant-constraints #-}+{-# OPTIONS_HADDOCK hide #-}++module Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted.Internal (+ module Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted.Internal,+) where++import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.BO.Unsafe+import Control.Monad.Borrow.Pure.Clone+import Control.Monad.Borrow.Pure.Copyable+import Control.Monad.Borrow.Pure.Lifetime.Token.Unsafe (+ LinearOnly (..),+ LinearOnlyWitness (..),+ )+import Data.Unrestricted.Linear qualified as Ur+import Data.Vector.Generic qualified as G+import Data.Vector.Generic.Mutable qualified as GM+import Data.Vector.Mutable (RealWorld)+import GHC.Exts qualified as GHC+import GHC.IO (unsafePerformIO)+import GHC.Stack (HasCallStack)+import GHC.TypeError+import Prelude.Linear hiding (head, last, splitAt)+import Unsafe.Linear qualified as Unsafe+import Prelude qualified as NonLinear++-- | Linearly owned mutable vector of unrestricted elements.+newtype Vector v a = Vector {content :: G.Mutable v RealWorld a}++type role Vector nominal nominal++-- | Construct a fixed-size view over a raw mutable-vector slice.+unsafeFromMutableSlice ::+ (G.Vector v a) =>+ Int ->+ Int ->+ G.Mutable v RealWorld a %1 ->+ Vector v a+{-# INLINE unsafeFromMutableSlice #-}+unsafeFromMutableSlice offset length_ =+ Unsafe.toLinear \buffer ->+ Vector (GM.unsafeSlice offset length_ buffer)++instance LinearOnly (Vector v a) where+ linearOnly = UnsafeLinearOnly+ {-# INLINE linearOnly #-}++instance+ (Unsatisfiable (ShowType (Vector v a) :<>: Text " cannot be copied!")) =>+ Copyable (Vector v a)+ where+ copy = unsatisfiable++instance Consumable (Vector v a) where+ consume = Unsafe.toLinear \_ -> ()+ {-# INLINE consume #-}++instance (G.Vector v a) => Clone (Vector v a) where+ clone = Unsafe.toLinear \(UnsafeAlias (Vector vector)) ->+ Vector Control.<$> unsafeSystemIOToBO (GM.clone vector)+ {-# INLINE clone #-}++-- | \(O(1)\). Construct an empty vector.+empty :: (G.Vector v a) => Linearly %1 -> Vector v a+{-# NOINLINE empty #-}+empty =+ GHC.noinline \linear ->+ linear `lseq` Vector (unsafePerformIO (GM.unsafeNew 0))++{- | \(O(n)\). Construct a vector containing @count@ copies of a value.++As in @vector@, a negative count produces an empty vector.+-}+constant ::+ (G.Vector v a) =>+ Int ->+ a ->+ Linearly %1 ->+ Vector v a+{-# NOINLINE constant #-}+constant =+ GHC.noinline \count value linear ->+ linear `lseq` Vector (unsafePerformIO (GM.replicate count value))++-- | \(O(n)\). Copy an unrestricted list into a new vector.+fromList ::+ (G.Vector v a) =>+ [a] ->+ Linearly %1 ->+ Vector v a+{-# NOINLINE fromList #-}+fromList =+ GHC.noinline \values linear ->+ linear `lseq`+ Vector+ (unsafePerformIO (G.thaw (G.fromList values)))++-- | \(O(n)\). Copy an immutable vector into a new owner.+fromVector ::+ (G.Vector v a) =>+ v a ->+ Linearly %1 ->+ Vector v a+{-# NOINLINE fromVector #-}+fromVector =+ GHC.noinline \source linear ->+ linear `lseq` Vector (unsafePerformIO (G.thaw source))++{- | \(O(1)\). Unsafely take ownership of an immutable vector.++The caller must ensure that no alias of the source vector, including an+overlapping slice, is ever observed again.+-}+unsafeFromVector ::+ (G.Vector v a) =>+ v a %1 ->+ Linearly %1 ->+ Vector v a+{-# NOINLINE unsafeFromVector #-}+unsafeFromVector =+ GHC.noinline $+ Unsafe.toLinear \source linear ->+ linear `lseq` Vector (unsafePerformIO (G.unsafeThaw source))++{- | \(O(1)\). Unsafely take ownership of a mutable vector.++The caller must not retain any alias or overlapping slice of the input. The+entire adopted slice must be initialized.+-}+unsafeFromMutable ::+ (G.Vector v a) =>+ G.Mutable v state a %1 ->+ Linearly %1 ->+ Vector v a+{-# INLINE unsafeFromMutable #-}+unsafeFromMutable =+ Unsafe.toLinear \source linear ->+ linear `lseq` Vector (Unsafe.coerce source)++-- | \(O(1)\). Consume the owner and freeze its backing storage.+toVector ::+ (G.Vector v a) =>+ Vector v a %1 ->+ Ur (v a)+{-# NOINLINE toVector #-}+toVector =+ GHC.noinline $+ Unsafe.toLinear \(Vector vector) ->+ Ur (unsafePerformIO (G.unsafeFreeze vector))++-- | \(O(n)\). Consume the owner and return its elements as a list.+toList ::+ (G.Vector v a) =>+ Vector v a %1 ->+ Ur [a]+{-# INLINE toList #-}+toList = Ur.lift G.toList . toVector++{- | \(O(n)\). Copy a live vector into an immutable vector and thread its+borrow.+-}+copyToVector ::+ (G.Vector v a, α >= β) =>+ Borrow bk α (Vector v a) %1 ->+ BO β (Ur (v a), Borrow bk α (Vector v a))+{-# INLINE copyToVector #-}+copyToVector =+ Unsafe.toLinear \vectorBorrow@(UnsafeAlias (Vector vector)) ->+ unsafeSystemIOToBO do+ snapshot <- G.freeze vector+ NonLinear.pure (Ur snapshot, vectorBorrow)++-- | \(O(1)\). Return the number of elements and thread the vector borrow.+size ::+ (G.Vector v a) =>+ Borrow bk α (Vector v a) %1 ->+ (Ur Int, Borrow bk α (Vector v a))+{-# INLINE size #-}+size =+ Unsafe.toLinear \vectorBorrow@(UnsafeAlias (Vector vector)) ->+ (Ur (GM.length vector), vectorBorrow)++-- | Read the element at an index and thread the vector borrow.+get ::+ (HasCallStack, G.Vector v a, α >= β) =>+ Int ->+ Borrow bk α (Vector v a) %1 ->+ BO β (Ur a, Borrow bk α (Vector v a))+{-# INLINE get #-}+get index vector =+ case size vector of+ (Ur length_, vector)+ | index < 0 || index >= length_ ->+ error+ ( "get: index "+ <> show index+ <> " out of bounds for length "+ <> show length_+ )+ vector+ | otherwise -> unsafeGet index vector++-- | Unchecked 'get'. The index must satisfy @0 <= index < size@.+unsafeGet ::+ (G.Vector v a, α >= β) =>+ Int ->+ Borrow bk α (Vector v a) %1 ->+ BO β (Ur a, Borrow bk α (Vector v a))+{-# INLINE unsafeGet #-}+unsafeGet index =+ Unsafe.toLinear \vectorBorrow@(UnsafeAlias (Vector vector)) ->+ unsafeSystemIOToBO do+ value <- GM.unsafeRead vector index+ NonLinear.pure (Ur value, vectorBorrow)++-- | Read the first element and thread the vector borrow.+head ::+ (HasCallStack, G.Vector v a, α >= β) =>+ Borrow bk α (Vector v a) %1 ->+ BO β (Ur a, Borrow bk α (Vector v a))+{-# INLINE head #-}+head = get 0++-- | Unchecked 'head'. The vector must be non-empty.+unsafeHead ::+ (G.Vector v a, α >= β) =>+ Borrow bk α (Vector v a) %1 ->+ BO β (Ur a, Borrow bk α (Vector v a))+{-# INLINE unsafeHead #-}+unsafeHead = unsafeGet 0++-- | Read the last element and thread the vector borrow.+last ::+ (HasCallStack, G.Vector v a, α >= β) =>+ Borrow bk α (Vector v a) %1 ->+ BO β (Ur a, Borrow bk α (Vector v a))+{-# INLINE last #-}+last vector =+ case size vector of+ (Ur length_, vector)+ | length_ <= 0 -> error "last: empty vector" vector+ | otherwise -> unsafeGet (length_ - 1) vector++-- | Unchecked 'last'. The vector must be non-empty.+unsafeLast ::+ (G.Vector v a, α >= β) =>+ Borrow bk α (Vector v a) %1 ->+ BO β (Ur a, Borrow bk α (Vector v a))+{-# INLINE unsafeLast #-}+unsafeLast vector =+ case size vector of+ (Ur length_, vector) -> unsafeGet (length_ - 1) vector++-- | Read an element through a shared borrow.+copyAt ::+ (HasCallStack, G.Vector v a, α >= β) =>+ Int ->+ Share α (Vector v a) ->+ BO β (Ur a)+{-# INLINE copyAt #-}+copyAt index vector = Control.do+ (value, vector) <- get index vector+ Control.pure (consume vector `lseq` value)++-- | Read an element and retain the mutable vector borrow.+copyAtMut ::+ (HasCallStack, G.Vector v a, α >= β) =>+ Int ->+ Mut α (Vector v a) %1 ->+ BO β (Ur a, Mut α (Vector v a))+{-# INLINE copyAtMut #-}+copyAtMut = get++-- | Replace an element and return the displaced value.+set ::+ (HasCallStack, G.Vector v a, α >= β) =>+ Int ->+ a ->+ Mut α (Vector v a) %1 ->+ BO β (Ur a, Mut α (Vector v a))+{-# INLINE set #-}+set index value array =+ case size array of+ (Ur length_, array)+ | index < 0 || index >= length_ ->+ error+ ( "set: index "+ <> show index+ <> " out of bounds for length "+ <> show length_+ )+ array+ | otherwise -> unsafeSet index value array++-- | Unchecked 'set'. The index must satisfy @0 <= index < size@.+unsafeSet ::+ (G.Vector v a, α >= β) =>+ Int ->+ a ->+ Mut α (Vector v a) %1 ->+ BO β (Ur a, Mut α (Vector v a))+{-# INLINE unsafeSet #-}+unsafeSet index value =+ Unsafe.toLinear \array@(UnsafeAlias (Vector vector)) ->+ unsafeSystemIOToBO do+ oldValue <- GM.unsafeExchange vector index value+ NonLinear.pure (Ur oldValue, array)++-- | Replace an element and discard the displaced unrestricted value.+write ::+ (HasCallStack, G.Vector v a, α >= β) =>+ Int ->+ a ->+ Mut α (Vector v a) %1 ->+ BO β (Mut α (Vector v a))+{-# INLINE write #-}+write index value array =+ case size array of+ (Ur length_, array)+ | index < 0 || index >= length_ ->+ error+ ( "write: index "+ <> show index+ <> " out of bounds for length "+ <> show length_+ )+ array+ | otherwise -> unsafeWrite index value array++-- | Unchecked 'write'. The index must satisfy @0 <= index < size@.+unsafeWrite ::+ (G.Vector v a, α >= β) =>+ Int ->+ a ->+ Mut α (Vector v a) %1 ->+ BO β (Mut α (Vector v a))+{-# INLINE unsafeWrite #-}+unsafeWrite index value =+ Unsafe.toLinear \array@(UnsafeAlias (Vector vector)) ->+ unsafeSystemIOToBO do+ GM.unsafeWrite vector index value+ NonLinear.pure array++{- | Transform an element and return an auxiliary unrestricted result.++The callback receives and returns unrestricted values. The mutable vector+borrow is unavailable until the replacement has been written. This is a+normal-return guarantee; no owner recovery is promised after an exception.+-}+update ::+ (HasCallStack, G.Vector v a, α >= β) =>+ Int ->+ (a -> BO β (Ur result, Ur a)) ->+ Mut α (Vector v a) %1 ->+ BO β (Ur result, Mut α (Vector v a))+{-# INLINE update #-}+update index action array =+ case size array of+ (Ur length_, array)+ | index < 0 || index >= length_ ->+ error+ ( "update: index "+ <> show index+ <> " out of bounds for length "+ <> show length_+ )+ array+ | otherwise -> unsafeUpdate index action array++{- | Unchecked 'update'. The index must satisfy @0 <= index < size@.++The callback receives and returns unrestricted values. The mutable vector+borrow is unavailable until the replacement has been written. This is a+normal-return guarantee; no owner recovery is promised after an exception.+-}+unsafeUpdate ::+ (G.Vector v a, α >= β) =>+ Int ->+ (a -> BO β (Ur result, Ur a)) ->+ Mut α (Vector v a) %1 ->+ BO β (Ur result, Mut α (Vector v a))+{-# INLINE unsafeUpdate #-}+unsafeUpdate index action =+ Unsafe.toLinear \(UnsafeAlias array@(Vector vector)) -> Control.do+ Ur value <-+ unsafeSystemIOToBO do+ value <- GM.unsafeRead vector index+ NonLinear.pure (Ur value)+ (result, Ur updatedValue) <- action value+ () <- unsafeSystemIOToBO (GM.unsafeWrite vector index updatedValue)+ Control.pure (result, UnsafeAlias array)++-- | Transform an element.+modify ::+ (HasCallStack, G.Vector v a, α >= β) =>+ Int ->+ (a -> a) ->+ Mut α (Vector v a) %1 ->+ BO β (Mut α (Vector v a))+{-# INLINE modify #-}+modify index function array = Control.do+ (Ur (), array) <-+ update+ index+ (\value -> Control.pure (Ur (), Ur (function value)))+ array+ Control.pure array++-- | Swap two elements.+swap ::+ (HasCallStack, G.Vector v a, α >= β) =>+ Mut α (Vector v a) %1 ->+ Int ->+ Int ->+ BO β (Mut α (Vector v a))+{-# INLINE swap #-}+swap array first second =+ case size array of+ (Ur length_, array)+ | first+ < 0+ || first+ >= length_+ || second+ < 0+ || second+ >= length_ ->+ error+ ( "swap: indices "+ <> show (first, second)+ <> " out of bounds for length "+ <> show length_+ )+ array+ | otherwise -> unsafeSwap array first second++-- | Unchecked 'swap'. Both indices must satisfy @0 <= index < size@.+unsafeSwap ::+ (G.Vector v a, α >= β) =>+ Mut α (Vector v a) %1 ->+ Int ->+ Int ->+ BO β (Mut α (Vector v a))+{-# INLINE unsafeSwap #-}+unsafeSwap =+ Unsafe.toLinear \array@(UnsafeAlias (Vector vector)) first second ->+ unsafeSystemIOToBO do+ GM.unsafeSwap vector first second+ NonLinear.pure array++{- | Split a borrow into two disjoint fixed ranges without copying.++The index is clamped to @[0, size]@, matching @vector@'s 'GM.splitAt'.+For a custom backend, safety requires its two returned slices not to overlap.+-}+splitAt ::+ (G.Vector v a) =>+ Int ->+ Borrow bk α (Vector v a) %1 ->+ ( Borrow bk α (Vector v a)+ , Borrow bk α (Vector v a)+ )+{-# INLINE splitAt #-}+splitAt index =+ Unsafe.toLinear \(UnsafeAlias (Vector vector)) ->+ case GM.splitAt index vector of+ (left, right) ->+ (UnsafeAlias (Vector left), UnsafeAlias (Vector right))
+ src/Data/Vector/Mutable/Growable/Linear/Borrow.hs view
@@ -0,0 +1,75 @@+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE NoImplicitPrelude #-}++{- |+Growable, linearly owned boxed vectors with a stable outer identity.++A 'GrowableVector' keeps its replaceable backing buffer behind a mutable+header, so 'reserve', 'push', and 'extend' preserve the identity+reclaimed by an existing @Lend@. 'getContents' opens that header once and+converts a mutable or shared growable borrow into the corresponding fixed-size+borrow of exactly the initialized logical prefix. The fixed view cannot+reserve, grow, freeze, consume the growable owner, or expose spare capacity.++'withContent' combines this constant-time projection with a short borrow scope+that preserves the input borrow kind. Its callback receives one linear+occurrence; shared content can use @move@ to recover unrestricted use. A mutable+growable borrow is unavailable while the callback runs and is recovered+afterward. Use it for repeated no-growth access; perform growth only between+mutable content scopes.++The growable owner deliberately has no splitting operation. Splitting it would+either duplicate the stable header or create independently replaceable headers+over overlapping storage: subsequent growth could invalidate one side, and+parallel mutation would no longer be race-free. To process disjoint regions,+open a fixed 'getContents' view inside 'withContent' and split that fixed view;+growth remains unavailable until all pieces have been consumed.++Growth has a normal-return ownership guarantee. Capacity arithmetic and+allocation happen before the logical length is published, and no user callback+runs during a partially completed growth operation. This module does not claim+that an owner is recoverable after a synchronous or asynchronous exception+escapes a @BO@ computation.+-}+module Data.Vector.Mutable.Growable.Linear.Borrow (+ GrowableVector,+ empty,+ constant,+ fromList,+ withCapacity,+ fromVector,+ unsafeFromMutable,+ unsafeFromVector,+ toVector,+ toList,+ size,+ capacity,+ get,+ unsafeGet,+ head,+ unsafeHead,+ last,+ unsafeLast,+ copyAt,+ unsafeCopyAt,+ copyAtMut,+ unsafeCopyAtMut,+ set,+ unsafeSet,+ update,+ unsafeUpdate,+ modify,+ swap,+ unsafeSwap,+ indicesMut,+ unsafeIndicesMut,+ reserve,+ reserveAdditional,+ push,+ extend,+ getContents,+ withContent,+ withContent_,+) where++import Data.Vector.Mutable.Growable.Linear.Borrow.Internal
+ src/Data/Vector/Mutable/Growable/Linear/Borrow/Internal.hs view
@@ -0,0 +1,894 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RoleAnnotations #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}+{-# OPTIONS_GHC -Wno-redundant-constraints #-}+{-# OPTIONS_HADDOCK hide #-}++module Data.Vector.Mutable.Growable.Linear.Borrow.Internal (+ module Data.Vector.Mutable.Growable.Linear.Borrow.Internal,+) where++import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.Affine (aff, pop)+import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.BO.Internal (unsafeSrunBO_)+import Control.Monad.Borrow.Pure.BO.Unsafe+import Control.Monad.Borrow.Pure.Copyable+import Control.Monad.Borrow.Pure.Lifetime.Token.Unsafe (+ LinearOnly (..),+ LinearOnlyWitness (..),+ )+import Data.IntSet qualified as IntSet+import Data.Ref.Linear qualified as Ref+import Data.Ref.Linear.Borrow qualified as RefBorrow+import Data.Unrestricted.Linear qualified as Ur+import Data.Vector qualified as V+import Data.Vector.Mutable qualified as MV+import Data.Vector.Mutable.Linear.Borrow qualified as Fixed+import Data.Vector.Mutable.Linear.Borrow.Internal qualified as Fixed.Internal+import GHC.Exts qualified as GHC+import GHC.IO (unsafePerformIO)+import GHC.Stack (HasCallStack)+import GHC.TypeError+import Prelude.Linear hiding (getContents, head, last)+import Unsafe.Linear qualified as Unsafe+import Prelude qualified as NonLinear++data Header a where+ Header ::+ {-# UNPACK #-} !Int ->+ !(MV.IOVector a) %1 ->+ Header a++-- | A linearly owned boxed vector with a stable header and replaceable backing allocation.+data GrowableVector a where+ GrowableVector :: !(Ref.Ref (Header a)) %1 -> GrowableVector a++type role Header nominal++type role GrowableVector nominal++instance LinearOnly (GrowableVector a) where+ linearOnly = UnsafeLinearOnly+ {-# INLINE linearOnly #-}++instance+ (Unsatisfiable (ShowType (GrowableVector a) :<>: Text " cannot be copied!")) =>+ Copyable (GrowableVector a)+ where+ copy = unsatisfiable++instance (Consumable a) => Consumable (GrowableVector a) where+ consume =+ Unsafe.toLinear \(GrowableVector ref) ->+ case Ref.free ref of+ Header logicalSize buffer -> consumeInitialized logicalSize buffer+ -- Reaches 'unsafePerformIO' through 'consumeInitialized'. See the note+ -- there: this must not be duplicated across call sites.+ {-# NOINLINE consume #-}++allocateBuffer :: Int -> Linearly %1 -> MV.IOVector a+{-# NOINLINE allocateBuffer #-}+allocateBuffer =+ GHC.noinline \count linear ->+ linear `lseq` unsafePerformIO (MV.unsafeNew count)++cloneBuffer :: V.Vector a -> Linearly %1 -> MV.IOVector a+{-# NOINLINE cloneBuffer #-}+cloneBuffer =+ GHC.noinline \source linear ->+ linear `lseq` unsafePerformIO (V.thaw source)++-- | \(O(1)\). Construct an empty vector with zero capacity.+empty :: Linearly %1 -> GrowableVector a+{-# NOINLINE empty #-}+empty = withCapacity 0++-- | \(O(n)\). Construct @n@ initialized elements. The count must be non-negative.+constant ::+ Int ->+ a ->+ Linearly %1 ->+ GrowableVector a+{-# NOINLINE constant #-}+constant = GHC.noinline \count value linear ->+ fromVector (V.replicate count value) linear++-- | \(O(n)\). Construct a vector from a list.+fromList ::+ [a] ->+ Linearly %1 ->+ GrowableVector a+{-# NOINLINE fromList #-}+fromList = GHC.noinline \values linear ->+ fromVector (V.fromList values) linear++{- | \(O(1)\). Construct an empty vector with the requested capacity.++The capacity must be non-negative. No element in the spare allocation is+considered initialized.+-}+withCapacity :: (HasCallStack) => Int -> Linearly %1 -> GrowableVector a+{-# NOINLINE withCapacity #-}+withCapacity = GHC.noinline \requested linear ->+ if requested < 0+ then error ("withCapacity: negative capacity " <> show requested) linear+ else+ dup linear & \(bufferLinear, refLinear) ->+ GrowableVector+ (Ref.new (Header 0 (allocateBuffer requested bufferLinear)) refLinear)++-- | \(O(n)\). Copy all elements of an immutable boxed vector.+fromVector ::+ V.Vector a ->+ Linearly %1 ->+ GrowableVector a+{-# NOINLINE fromVector #-}+fromVector = GHC.noinline \source linear ->+ dup linear & \(bufferLinear, refLinear) ->+ GrowableVector+ ( Ref.new+ (Header (V.length source) (cloneBuffer source bufferLinear))+ refLinear+ )++{- | \(O(1)\). Take ownership of a boxed mutable vector without copying.++The complete source is treated as initialized. The caller must not retain any+alias that can access the source allocation.+-}+unsafeFromMutable ::+ MV.MVector state a %1 ->+ Linearly %1 ->+ GrowableVector a+{-# INLINE unsafeFromMutable #-}+unsafeFromMutable =+ Unsafe.toLinear \source linear ->+ GrowableVector+ ( Ref.new+ (Header (MV.length source) (Unsafe.coerce source))+ linear+ )++{- | \(O(1)\). Unsafely take ownership of an immutable boxed vector's storage.++The complete source is treated as initialized. No immutable alias may be read+after this operation, because subsequent growable mutation reuses its storage.+-}+unsafeFromVector ::+ V.Vector a %1 ->+ Linearly %1 ->+ GrowableVector a+{-# NOINLINE unsafeFromVector #-}+unsafeFromVector =+ GHC.noinline $+ Unsafe.toLinear \source linear ->+ GrowableVector+ ( Ref.new+ ( Header+ (V.length source)+ (unsafePerformIO (V.unsafeThaw source))+ )+ linear+ )++{- | \(O(n)\). Move every initialized element into GC ownership, then freeze+exactly that prefix.++Spare capacity is neither exposed nor materialized.+-}+toVector ::+ (Movable a) =>+ GrowableVector a %1 ->+ Ur (V.Vector a)+{-# NOINLINE toVector #-}+toVector =+ GHC.noinline $+ Unsafe.toLinear \(GrowableVector ref) ->+ case Ref.free ref of+ Header logicalSize buffer ->+ let !frozen =+ unsafePerformIO do+ moveInitialized logicalSize buffer+ V.unsafeFreeze (MV.unsafeTake logicalSize buffer)+ in Ur frozen++-- | \(O(n)\). Consume the owner and materialize its initialized prefix as a list.+toList ::+ (Movable a) =>+ GrowableVector a %1 ->+ Ur [a]+{-# INLINE toList #-}+toList = Ur.lift V.toList . toVector++moveInitialized ::+ (Movable a) =>+ Int ->+ MV.IOVector a ->+ NonLinear.IO ()+{-# INLINE moveInitialized #-}+moveInitialized !logicalSize buffer = go 0+ where+ go !index+ | index >= logicalSize = NonLinear.pure ()+ | otherwise = do+ value <- MV.unsafeRead buffer index+ case move value of+ Ur !moved -> MV.unsafeWrite buffer index moved+ go (index + 1)++{- | Consume the initialized prefix, releasing each element exactly once.++The traversal only reads the buffer, but it runs under 'unsafePerformIO'.+That makes the binding a trusted boundary rather than an ordinary pure+function: were it inlined, GHC could duplicate the call across use sites, or+float it out of a scope, and each copy would consume the elements again. The+'NOINLINE' keeps exactly one occurrence, so the exactly-once discipline the+linear types promise is preserved in the generated code too.+-}+consumeInitialized ::+ (Consumable a) =>+ Int ->+ MV.IOVector a %1 ->+ ()+{-# NOINLINE consumeInitialized #-}+consumeInitialized =+ Unsafe.toLinear2 \logicalSize buffer ->+ let go !index+ | index >= logicalSize = NonLinear.pure ()+ | otherwise = do+ value <- MV.unsafeRead buffer index+ let !() = consume value+ go (index + 1)+ in unsafePerformIO (go 0)++toRefMut ::+ Mut α (GrowableVector a) %1 ->+ Mut α (Ref.Ref (Header a))+{-# INLINE toRefMut #-}+toRefMut =+ unsafeMapAlias+ (Unsafe.toLinear \(GrowableVector ref) -> ref)++fromRefMut ::+ Mut α (Ref.Ref (Header a)) %1 ->+ Mut α (GrowableVector a)+{-# INLINE fromRefMut #-}+fromRefMut =+ unsafeMapAlias+ (Unsafe.toLinear GrowableVector)++withHeader ::+ (α >= β) =>+ (Header a %1 -> BO β (result, Header a)) %1 ->+ Mut α (GrowableVector a) %1 ->+ BO β (result, Mut α (GrowableVector a))+{-# INLINE withHeader #-}+withHeader action vector = Control.do+ (result, ref) <- RefBorrow.update action (toRefMut vector)+ Control.pure (result, fromRefMut ref)++-- | \(O(1)\). Return the number of initialized elements and thread the borrow.+size ::+ Borrow bk α (GrowableVector a) %1 ->+ (Ur Int, Borrow bk α (GrowableVector a))+{-# INLINE size #-}+size =+ Unsafe.toLinear \vector@(UnsafeAlias (GrowableVector ref)) ->+ case Ref.unsafeReadRef ref of+ (Header logicalSize _, duplicateRef) ->+ pop (aff duplicateRef) `lseq` (Ur logicalSize, vector)++-- | \(O(1)\). Return the backing allocation size and thread the borrow.+capacity ::+ Borrow bk α (GrowableVector a) %1 ->+ (Ur Int, Borrow bk α (GrowableVector a))+{-# INLINE capacity #-}+capacity =+ Unsafe.toLinear \vector@(UnsafeAlias (GrowableVector ref)) ->+ case Ref.unsafeReadRef ref of+ (Header _ buffer, duplicateRef) ->+ pop (aff duplicateRef) `lseq` (Ur (MV.length buffer), vector)++{- | Borrow the element at an index in the initialized prefix.++This consumes the growable borrow. The growable owner can be recovered only+through its enclosing lender after the returned element borrow ends. Use+'withContent' for repeated no-growth access.+-}+get ::+ (HasCallStack, α >= β) =>+ Int ->+ Borrow bk α (GrowableVector a) %1 ->+ BO β (Borrow bk α a)+{-# INLINE get #-}+get index vector =+ case size vector of+ (Ur logicalSize, vector) ->+ if index < 0 || index >= logicalSize+ then+ error+ ( "get: index "+ <> show index+ <> " out of bounds for length "+ <> show logicalSize+ )+ vector+ else unsafeGet index vector++-- | Unchecked 'get'. The index must satisfy @0 <= index < size@.+unsafeGet ::+ (α >= β) =>+ Int ->+ Borrow bk α (GrowableVector a) %1 ->+ BO β (Borrow bk α a)+{-# INLINE unsafeGet #-}+unsafeGet =+ Unsafe.toLinear2 \index (UnsafeAlias (GrowableVector ref)) ->+ case Ref.unsafeReadRef ref of+ (Header _ buffer, duplicateRef) ->+ pop (aff duplicateRef) `lseq`+ UnsafeAlias+ Control.<$> unsafeSystemIOToBO (MV.unsafeRead buffer index)++-- | Borrow the first initialized element. Fails when the vector is empty.+head ::+ (HasCallStack, α >= β) =>+ Borrow bk α (GrowableVector a) %1 ->+ BO β (Borrow bk α a)+{-# INLINE head #-}+head = get 0++-- | Unchecked 'head'. The vector must be non-empty.+unsafeHead ::+ (α >= β) =>+ Borrow bk α (GrowableVector a) %1 ->+ BO β (Borrow bk α a)+{-# INLINE unsafeHead #-}+unsafeHead = unsafeGet 0++-- | Borrow the last initialized element. Fails when the vector is empty.+last ::+ (HasCallStack, α >= β) =>+ Borrow bk α (GrowableVector a) %1 ->+ BO β (Borrow bk α a)+{-# INLINE last #-}+last vector =+ case size vector of+ (Ur logicalSize, vector) ->+ if logicalSize <= 0+ then error "last: empty vector" vector+ else unsafeGet (logicalSize - 1) vector++-- | Unchecked 'last'. The vector must be non-empty.+unsafeLast ::+ (α >= β) =>+ Borrow bk α (GrowableVector a) %1 ->+ BO β (Borrow bk α a)+{-# INLINE unsafeLast #-}+unsafeLast vector =+ case size vector of+ (Ur logicalSize, vector) -> unsafeGet (logicalSize - 1) vector++-- | Copy the element at an index through a shared borrow.+copyAt ::+ (HasCallStack, Copyable a, α >= β) =>+ Int ->+ Share α (GrowableVector a) ->+ BO β (Ur a)+{-# INLINE copyAt #-}+copyAt =+ Unsafe.toLinear2 \index (UnsafeAlias (GrowableVector ref)) ->+ case Ref.unsafeReadRef ref of+ (Header logicalSize buffer, duplicateRef) ->+ pop (aff duplicateRef) `lseq`+ if index < 0 || index >= logicalSize+ then+ error+ ( "copyAt: index "+ <> show index+ <> " out of bounds for length "+ <> show logicalSize+ )+ buffer+ else unsafeSystemIOToBO do+ !value <- MV.unsafeRead buffer index+ let !copied = copy (UnsafeAlias value)+ NonLinear.pure (Ur copied)++-- | Unchecked 'copyAt'. The index must satisfy @0 <= index < size@.+unsafeCopyAt ::+ (Copyable a, α >= β) =>+ Int ->+ Share α (GrowableVector a) ->+ BO β (Ur a)+{-# INLINE unsafeCopyAt #-}+unsafeCopyAt =+ Unsafe.toLinear2 \index (UnsafeAlias (GrowableVector ref)) ->+ case Ref.unsafeReadRef ref of+ (Header _ buffer, duplicateRef) ->+ pop (aff duplicateRef) `lseq`+ unsafeSystemIOToBO do+ !value <- MV.unsafeRead buffer index+ let !copied = copy (UnsafeAlias value)+ NonLinear.pure (Ur copied)++-- | Copy the element at an index and return the mutable growable borrow.+copyAtMut ::+ (HasCallStack, Copyable a, α >= β) =>+ Int ->+ Mut α (GrowableVector a) %1 ->+ BO β (Ur a, Mut α (GrowableVector a))+{-# INLINE copyAtMut #-}+copyAtMut index vector =+ case size vector of+ (Ur logicalSize, vector) ->+ if index < 0 || index >= logicalSize+ then+ error+ ( "copyAtMut: index "+ <> show index+ <> " out of bounds for length "+ <> show logicalSize+ )+ vector+ else unsafeCopyAtMut index vector++-- | Unchecked 'copyAtMut'. The index must satisfy @0 <= index < size@.+unsafeCopyAtMut ::+ (Copyable a, α >= β) =>+ Int ->+ Mut α (GrowableVector a) %1 ->+ BO β (Ur a, Mut α (GrowableVector a))+{-# INLINE unsafeCopyAtMut #-}+unsafeCopyAtMut =+ Unsafe.toLinear2 \index vector@(UnsafeAlias (GrowableVector ref)) ->+ case Ref.unsafeReadRef ref of+ (Header _ buffer, duplicateRef) ->+ pop (aff duplicateRef) `lseq`+ unsafeSystemIOToBO do+ !value <- MV.unsafeRead buffer index+ let !copied = copy (UnsafeAlias value)+ NonLinear.pure (Ur copied, vector)++-- | Replace an initialized element and return the displaced value.+set ::+ (HasCallStack, α >= β) =>+ Int ->+ a %1 ->+ Mut α (GrowableVector a) %1 ->+ BO β (a, Mut α (GrowableVector a))+{-# INLINE set #-}+set index value vector =+ case size vector of+ (Ur logicalSize, vector) ->+ if index < 0 || index >= logicalSize+ then+ error+ ( "set: index "+ <> show index+ <> " out of bounds for length "+ <> show logicalSize+ )+ value+ vector+ else unsafeSet index value vector++-- | Unchecked 'set'. The index must satisfy @0 <= index < size@.+unsafeSet ::+ (α >= β) =>+ Int ->+ a %1 ->+ Mut α (GrowableVector a) %1 ->+ BO β (a, Mut α (GrowableVector a))+{-# INLINE unsafeSet #-}+unsafeSet =+ Unsafe.toLinear3 \index !value vector ->+ withHeader+ ( Unsafe.toLinear \(Header logicalSize buffer) ->+ unsafeSystemIOToBO do+ !oldValue <- MV.unsafeRead buffer index+ MV.unsafeWrite buffer index value+ NonLinear.pure (oldValue, Header logicalSize buffer)+ )+ vector++-- | Linearly transform an initialized element and return an auxiliary result.+update ::+ (HasCallStack, α >= β) =>+ Int ->+ (a %1 -> BO β (result, a)) %1 ->+ Mut α (GrowableVector a) %1 ->+ BO β (result, Mut α (GrowableVector a))+{-# INLINE update #-}+update index action vector =+ case size vector of+ (Ur logicalSize, vector) ->+ if index < 0 || index >= logicalSize+ then+ error+ ( "update: index "+ <> show index+ <> " out of bounds for length "+ <> show logicalSize+ )+ action+ vector+ else unsafeUpdate index action vector++-- | Unchecked 'update'. The index must satisfy @0 <= index < size@.+unsafeUpdate ::+ (α >= β) =>+ Int ->+ (a %1 -> BO β (result, a)) %1 ->+ Mut α (GrowableVector a) %1 ->+ BO β (result, Mut α (GrowableVector a))+{-# INLINE unsafeUpdate #-}+unsafeUpdate index action vector =+ withHeader+ ( Unsafe.toLinear \(Header logicalSize buffer) -> Control.do+ value <- unsafeSystemIOToBO (MV.unsafeRead buffer index)+ (!result, !updatedValue) <- action value+ buffer <- writeAt index updatedValue buffer+ Control.pure (result, Header logicalSize buffer)+ )+ vector++-- | Linearly transform an initialized element.+modify ::+ (HasCallStack, α >= β) =>+ Int ->+ (a %1 -> a) %1 ->+ Mut α (GrowableVector a) %1 ->+ BO β (Mut α (GrowableVector a))+{-# INLINE modify #-}+modify index function vector = Control.do+ ((), vector) <-+ update+ index+ (Control.pure . ((),) . function)+ vector+ Control.pure vector++-- | Unchecked 'swap'. Both indices must satisfy @0 <= index < size@.+unsafeSwap ::+ (α >= β) =>+ Mut α (GrowableVector a) %1 ->+ Int ->+ Int ->+ BO β (Mut α (GrowableVector a))+{-# INLINE unsafeSwap #-}+unsafeSwap =+ Unsafe.toLinear3 \vector first second -> Control.do+ ((), vector) <-+ withHeader+ ( Unsafe.toLinear \(Header logicalSize buffer) ->+ unsafeSystemIOToBO do+ MV.unsafeSwap buffer first second+ NonLinear.pure ((), Header logicalSize buffer)+ )+ vector+ Control.pure vector++-- | Swap two initialized elements.+swap ::+ (HasCallStack, α >= β) =>+ Mut α (GrowableVector a) %1 ->+ Int ->+ Int ->+ BO β (Mut α (GrowableVector a))+{-# INLINE swap #-}+swap vector first second =+ case size vector of+ (Ur logicalSize, vector) ->+ if first+ < 0+ || first+ >= logicalSize+ || second+ < 0+ || second+ >= logicalSize+ then+ error+ ( "swap: indices "+ <> show (first, second)+ <> " out of bounds for length "+ <> show logicalSize+ )+ vector+ else unsafeSwap vector first second++{- | Borrow several initialized elements mutably without validation.++Every index must satisfy @0 <= index < size@, and the indices must be+pairwise distinct. Violating distinctness can create aliased mutable borrows+and a data race when they are used in parallel.+-}+unsafeIndicesMut ::+ (α >= β) =>+ Mut α (GrowableVector a) %1 ->+ [Int] %1 ->+ BO β [Mut α a]+{-# INLINE unsafeIndicesMut #-}+unsafeIndicesMut vector =+ Fixed.unsafeIndicesMut (getContents vector)++{- | Borrow several initialized elements mutably.++Fails if any index is out of bounds or if an index occurs more than once.+-}+indicesMut ::+ (HasCallStack, α >= β) =>+ Mut α (GrowableVector a) %1 ->+ [Int] %1 ->+ BO β [Mut α a]+{-# INLINE indicesMut #-}+indicesMut =+ Unsafe.toLinear2 \vector indices ->+ case size vector of+ (Ur logicalSize, vector)+ | any+ ( \index ->+ move index & \(Ur index) ->+ index < 0 || index >= logicalSize+ )+ indices ->+ error+ ( "indicesMut: indices out of bounds: "+ <> show indices+ <> " for length "+ <> show logicalSize+ )+ vector+ | NonLinear.length indices+ > IntSet.size (IntSet.fromList indices) ->+ error ("indicesMut: duplicate indices: " <> show indices) vector+ | otherwise ->+ Fixed.unsafeIndicesMut (getContents vector) indices++{- | Ensure that the absolute capacity is at least the requested value.++The requested capacity must be non-negative. Logical size and initialized+contents do not change. Reallocation destructively transfers the initialized+prefix into fresh storage.+-}+reserve ::+ (HasCallStack, α >= β) =>+ Int ->+ Mut α (GrowableVector a) %1 ->+ BO β (Mut α (GrowableVector a))+{-# INLINE reserve #-}+reserve requested vector+ | requested < 0 =+ error ("reserve: negative capacity " <> show requested) vector+ | otherwise = Control.do+ ((), vector) <-+ withHeader+ ( Unsafe.toLinear \(Header logicalSize buffer) -> Control.do+ grown <- growTo logicalSize requested buffer+ Control.pure ((), Header logicalSize grown)+ )+ vector+ Control.pure vector++{- | Ensure capacity for at least the current size plus the requested amount.++The additional amount must be non-negative. Logical size and initialized+contents do not change.+-}+reserveAdditional ::+ (HasCallStack, α >= β) =>+ Int ->+ Mut α (GrowableVector a) %1 ->+ BO β (Mut α (GrowableVector a))+{-# INLINE reserveAdditional #-}+reserveAdditional additional vector+ | additional < 0 =+ error+ ("reserveAdditional: negative additional capacity " <> show additional)+ vector+ | otherwise = Control.do+ ((), vector) <-+ withHeader+ ( Unsafe.toLinear \(Header logicalSize buffer) ->+ let !required =+ checkedAdd "reserveAdditional" logicalSize additional+ in Control.do+ grown <- growTo logicalSize required buffer+ Control.pure ((), Header logicalSize grown)+ )+ vector+ Control.pure vector++{- | Append one linearly supplied element to the initialized prefix.++Reallocation, when required, destructively transfers the old initialized+prefix into fresh storage.+-}+push ::+ (HasCallStack, α >= β) =>+ a %1 ->+ Mut α (GrowableVector a) %1 ->+ BO β (Mut α (GrowableVector a))+{-# INLINE push #-}+push =+ Unsafe.toLinear2 \ !value vector -> Control.do+ ((), vector) <-+ withHeader+ ( Unsafe.toLinear \(Header logicalSize buffer) ->+ let !required = checkedAdd "push" logicalSize 1+ !target = growthTarget (MV.length buffer) required+ in Control.do+ grown <- growTo logicalSize target buffer+ grown <- writeAt logicalSize value grown+ Control.pure ((), Header required grown)+ )+ vector+ Control.pure vector++-- | Append copies of all elements of an immutable boxed vector.+extend ::+ (HasCallStack, α >= β) =>+ V.Vector a ->+ Mut α (GrowableVector a) %1 ->+ BO β (Mut α (GrowableVector a))+{-# INLINE extend #-}+extend source vector = Control.do+ ((), vector) <-+ withHeader+ ( Unsafe.toLinear \(Header logicalSize buffer) ->+ let !sourceSize = V.length source+ !required = checkedAdd "extend" logicalSize sourceSize+ !target = growthTarget (MV.length buffer) required+ in Control.do+ grown <- growTo logicalSize target buffer+ grown <- copyImmutableInto source logicalSize grown+ Control.pure ((), Header required grown)+ )+ vector+ Control.pure vector++copyImmutable :: V.Vector a -> Int -> MV.IOVector a -> NonLinear.IO ()+{-# INLINE copyImmutable #-}+copyImmutable source offset target =+ V.copy (MV.unsafeSlice offset (V.length source) target) source++copyImmutableInto ::+ V.Vector a ->+ Int ->+ MV.IOVector a %1 ->+ BO β (MV.IOVector a)+{-# INLINE copyImmutableInto #-}+copyImmutableInto source offset =+ Unsafe.toLinear \target -> unsafeSystemIOToBO do+ copyImmutable source offset target+ NonLinear.pure target++writeAt ::+ Int ->+ a %1 ->+ MV.IOVector a %1 ->+ BO β (MV.IOVector a)+{-# INLINE writeAt #-}+writeAt =+ Unsafe.toLinear3 \index value target -> unsafeSystemIOToBO do+ MV.unsafeWrite target index value+ NonLinear.pure target++growTo ::+ Int ->+ Int ->+ MV.IOVector a %1 ->+ BO β (MV.IOVector a)+{-# INLINE growTo #-}+growTo =+ Unsafe.toLinear3 \logicalSize requested buffer ->+ let !oldCapacity = MV.length buffer+ in if requested <= oldCapacity+ then Control.pure buffer+ else unsafeSystemIOToBO do+ grown <- MV.unsafeNew requested+ MV.unsafeCopy+ (MV.unsafeTake logicalSize grown)+ (MV.unsafeTake logicalSize buffer)+ NonLinear.pure grown++growthTarget :: Int -> Int -> Int+{-# INLINE growthTarget #-}+growthTarget oldCapacity required+ | required <= oldCapacity = oldCapacity+ | oldCapacity <= 0 = required `max` 1+ | oldCapacity > maxBound `quot` 2 = required+ | otherwise = required `max` (oldCapacity * 2)++checkedAdd :: (HasCallStack) => NonLinear.String -> Int -> Int -> Int+{-# INLINE checkedAdd #-}+checkedAdd operation left right+ | right > maxBound - left =+ error (operation <> ": capacity overflow")+ | otherwise = left + right++{- | Project a growable borrow to a fixed borrow of its initialized prefix.++This consumes one occurrence of the growable borrow, preserves its borrow kind+and lifetime, and performs one header read. The result exposes neither spare+capacity nor growth. A mutable result may be split using the fixed-vector API;+the mutable growable owner becomes recoverable only after every resulting+fixed borrow has ended. A shared input follows the ordinary unrestricted+'Share' rules.++Where a transaction branches, prefer projecting once at its entry --+@let %1 !content = 'getContents' borrow@ -- over projecting separately inside+each branch. Both are correct and consume the growable occurrence exactly+once; the entry form simply gives the optimizer one header read to place+rather than one per surviving branch.+-}+getContents ::+ Borrow bk α (GrowableVector a) %1 ->+ Borrow bk α (Fixed.Vector a)+{-# INLINE getContents #-}+getContents =+ Unsafe.toLinear \(UnsafeAlias (GrowableVector ref)) ->+ case Ref.unsafeReadRef ref of+ (Header logicalSize buffer, duplicateRef) ->+ pop (aff duplicateRef) `lseq`+ UnsafeAlias+ (Fixed.Internal.unsafeFromMutableSlice 0 logicalSize buffer)++{-+Note [Uniformly linear content callback]+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+Ideally the callback arrow would use @BorrowMultiplicity bk@, making a+shared callback unrestricted. GHC 9.12 rejects that signature because type+families cannot witness multiplicity equality (GHC #19517). Keep one linear+callback occurrence for both borrow kinds until that limitation is removed;+shared callers can use 'move' to recover unrestricted use.+-}++{- | Borrow the fixed initialized prefix in a rank-2 no-growth scope.++The callback and returned growable borrow preserve the input borrow kind. The+callback receives one linear occurrence for either kind; use 'move' on shared+content when unrestricted use is desired. For a mutable input, the growable+borrow is restored only after the callback result is produced and the fixed+view has ended.++See Note [Uniformly linear content callback] for why the callback stays linear+for a shared borrow too.+-}+withContent ::+ Borrow bk α (GrowableVector a) %1 ->+ ( forall β.+ Borrow bk (β /\ α) (Fixed.Vector a) %1 ->+ BO (β /\ α) result+ ) %1 ->+ BO α (result, Borrow bk α (GrowableVector a))+{-# INLINE withContent #-}+withContent =+ Unsafe.toLinear2 \vector action ->+ -- The growable borrow is handed back through `reviveAlias`, as the scalar delimiters do: see Note [Restoring a borrow must break its Core identity] in "Control.Monad.Borrow.Pure.BO.Internal".+ unsafeSrunBO_ Control.do+ result <- action (getContents (Unsafe.coerce vector))+ (result,) Control.<$> reviveAlias vector++-- | A result-discarding variant of 'withContent'.+withContent_ ::+ (Consumable result) =>+ Borrow bk α (GrowableVector a) %1 ->+ ( forall β.+ Borrow bk (β /\ α) (Fixed.Vector a) %1 ->+ BO (β /\ α) result+ ) %1 ->+ BO α (Borrow bk α (GrowableVector a))+{-# INLINE withContent_ #-}+withContent_ vector action =+ withContent vector action Control.<&> \(result, vector) ->+ consume result `lseq` vector
src/Data/Vector/Mutable/Linear/Borrow.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE BlockArguments #-}+{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE ImpredicativeTypes #-} {-# LANGUAGE LambdaCase #-}@@ -40,7 +41,9 @@ unsafeSwap, copyAt, copyAtMut,- inplace,+ unsafeInplace,+ modifyBoxedMVector,+ modifyBoxedVector, -- * An example algorithm implementations qsort,@@ -50,15 +53,9 @@ ) where import Control.Functor.Linear qualified as Control-import Control.Monad qualified as NonLinear import Control.Monad.Borrow.Pure.BO import Control.Monad.Borrow.Pure.BO.Unsafe-import Control.Monad.Borrow.Pure.Clone import Control.Monad.Borrow.Pure.Copyable-import Control.Monad.Borrow.Pure.Lifetime.Token.Unsafe (- LinearOnly (..),- LinearOnlyWitness (..),- ) import Control.Monad.Borrow.Pure.Utils import Control.Monad.ST.Strict (ST) import Control.Syntax.DataFlow qualified as DataFlow@@ -69,10 +66,10 @@ import Data.Vector qualified as V import Data.Vector.Mutable (RealWorld) import Data.Vector.Mutable qualified as MV+import Data.Vector.Mutable.Linear.Borrow.Internal (Vector (..)) import GHC.Exts qualified as GHC import GHC.IO (unsafePerformIO) import GHC.Stack (HasCallStack)-import GHC.TypeError import Prelude.Linear hiding (head, last, splitAt) import Unsafe.Linear qualified as Unsafe import Prelude qualified as NonLinear@@ -83,8 +80,6 @@ This is because Pure Borrow can now treat nested mutability safely, so we must allow mutable values to be stored inside 'Vector'. This manifests in the type of 'set' - it returns the old value, which MUST NOT drop in favour of the new value. -}-newtype Vector a = Vector {content :: MV.MVector RealWorld a}- empty :: Linearly %1 -> Vector a {-# NOINLINE empty #-} empty =@@ -134,27 +129,50 @@ {- Note [Unrestricted Materialization of Vector] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-We impose 'Copyable' on 'toVector' and 'toList' to ensure elements doesn't bare any essentially linear contents inside, but we don't make use of the constraint internally.-Is it a cheating? Maybe. Think hard about it.+Consuming 'toVector' and 'toList' transfer their elements from a linear owner+to an unrestricted, GC-owned result. 'Movable' is exactly the evidence for+that transfer. Each element is passed through 'move', which performs any deep+copy required by its 'Movable' instance. 'Copyable' is neither sufficient nor+required. -} --- | /O(1)/. Freezes @'Vector' a@ to @'V.Vector' a@ from @vector@ package, /without/ copying.+-- | /O(n)/. Move every element into GC ownership, then freeze the storage. toVector :: -- See Note [Unrestricted Materialization of Vector].- (Copyable a) =>+ (Movable a) => Vector a %1 -> Ur (V.Vector a) {-# NOINLINE toVector #-} toVector = GHC.noinline $- Unsafe.toLinear \(Vector v) -> Ur $ unsafePerformIO $ V.unsafeFreeze v+ Unsafe.toLinear \(Vector v) ->+ let !frozen =+ unsafePerformIO do+ moveElements 0 (MV.length v) v+ V.unsafeFreeze v+ in Ur frozen --- Same applies to 'Copyable' here, as in 'toVector'.+-- Same applies to 'Movable' here, as in 'toVector'. toList :: -- See Note [Unrestricted Materialization of Vector].- (Copyable a) =>+ (Movable a) => Vector a %1 -> Ur [a] {-# INLINE toList #-} toList = Ur.lift V.toList . toVector +moveElements ::+ (Movable a) =>+ Int ->+ Int ->+ MV.IOVector a ->+ NonLinear.IO ()+{-# INLINE moveElements #-}+moveElements !index !length_ vector+ | index >= length_ = NonLinear.pure ()+ | otherwise = do+ value <- MV.unsafeRead vector index+ case move value of+ Ur !moved -> MV.unsafeWrite vector index moved+ moveElements (index + 1) length_ vector+ {- | Unsafely thaws 'V.Vector' (from @vector@ package) to a 'Vector', reusing the same memory. This is highly unsafe@@ -189,8 +207,8 @@ -- | 'set' without bound check. unsafeSet :: (α >= β) => Int -> a %1 -> Mut α (Vector a) %1 -> BO β (a, Mut α (Vector a))-unsafeSet = Unsafe.toLinear3 \i a mut@(UnsafeAlias (Vector v)) -> unsafeSystemIOToBO do- old <- MV.unsafeRead v i+unsafeSet = Unsafe.toLinear3 \i !a mut@(UnsafeAlias (Vector v)) -> unsafeSystemIOToBO do+ !old <- MV.unsafeRead v i MV.unsafeWrite v i a NonLinear.pure (old, mut) @@ -243,7 +261,7 @@ unsafeUpdate :: (α >= β) => Int -> (a %1 -> BO β (b, a)) %1 -> Mut α (Vector a) %1 -> BO β (b, Mut α (Vector a)) unsafeUpdate i = Unsafe.toLinear2 \k (UnsafeAlias v) -> Control.do a <- unsafeSystemIOToBO $ MV.unsafeRead (content v) i- (b, a') <- k a+ (!b, !a') <- k a () <- unsafeSystemIOToBO $ Unsafe.toLinear3 MV.unsafeWrite (content v) i a' Control.pure $ (b, UnsafeAlias v) @@ -287,29 +305,6 @@ let (v1, v2) = MV.splitAt i v in (UnsafeAlias (Vector v1), UnsafeAlias (Vector v2)) -instance LinearOnly (Vector a) where- linearOnly = UnsafeLinearOnly- {-# INLINE linearOnly #-}--instance- (Unsatisfiable (ShowType (Vector a) :<>: Text " cannot be copied!")) =>- Copyable (Vector a)- where- copy = unsatisfiable--instance (Dupable a) => Clone (Vector a) where- clone = Unsafe.toLinear \(UnsafeAlias (Vector v)) -> unsafeSystemIOToBO do- let !n = MV.length v- !new <- MV.new n- let go !i = NonLinear.when (i < n) do- x <- MV.unsafeRead v i- let (!_, !x') = dup x- MV.unsafeWrite new i x'- go (i + 1)- go 0- NonLinear.pure (Vector new)- {-# INLINE clone #-}- unsafeSwap :: (α >= β) => Mut α (Vector a) %1 -> Int -> Int -> BO β (Mut α (Vector a)) unsafeSwap = Unsafe.toLinear3 \(UnsafeAlias v) i j -> Control.do () <- unsafeSystemIOToBO $ MV.unsafeSwap v.content i j@@ -325,21 +320,54 @@ else unsafeSwap v i j copyAt :: (Copyable a, α >= β) => Int -> Share α (Vector a) -> BO β (Ur a)-copyAt i v = Control.do Ur s <- move Control.<$> get i v; Control.pure $ Ur $ copy s+copyAt i v = Control.do Ur !s <- move Control.<$> get i v; Control.pure $! Ur $! copy s copyAtMut :: forall a α β. (Copyable a, α >= β) => Int -> Mut α (Vector a) %1 -> BO β (Ur a, Mut α (Vector a))+{-# INLINE copyAtMut #-}+#ifdef PURE_BORROW_SLOW_SCOPES copyAtMut i v = upcast $ sharing @_ @α v $ copyAt i+#else+copyAtMut = Unsafe.toLinear2 \i mut@(UnsafeAlias (Vector v)) ->+ let !len = MV.length v+ in if i < 0 || i >= len+ then error ("get: index " <> show i <> " out of bound: " <> show len) mut+ else unsafeSystemIOToBO do+ !a <- MV.unsafeRead v i+ -- The raw read temporarily aliases the element retained by the+ -- vector. 'copy' consumes that alias and returns only an authorized+ -- unrestricted copy; the mutable vector borrow stays exclusive.+ let !copied = copy (UnsafeAlias a)+ NonLinear.pure (Ur copied, mut)+#endif -- | Applies an in-place mutation on 'V.MVector' from @vector@ package.-inplace ::+unsafeInplace :: (α >= β) => (forall s. V.MVector s a -> ST s ()) %1 -> Mut α (Vector a) %1 -> BO β (Mut α (Vector a))-{-# INLINE inplace #-}-inplace = Unsafe.toLinear2 \f (UnsafeAlias v) -> Control.do+{-# INLINE unsafeInplace #-}+unsafeInplace = Unsafe.toLinear2 \f (UnsafeAlias v) -> Control.do !() <- unsafeSTToBO $ f $ content $ coerceLin v Control.pure (UnsafeAlias v)++modifyBoxedMVector ::+ (forall α. Mut α (Vector a) %1 -> BO α ()) %1 ->+ V.MVector s a %1 ->+ ST s ()+{-# INLINE modifyBoxedMVector #-}+modifyBoxedMVector f v = do+ unsafeBOToST (f (UnsafeAlias (Vector (unsafeCoerceVector v))))++unsafeCoerceVector :: MV.MVector s a %1 -> MV.MVector RealWorld a+unsafeCoerceVector = Unsafe.coerce++modifyBoxedVector ::+ (forall α. Mut α (Vector a) %1 -> BO α ()) ->+ V.Vector a ->+ V.Vector a+{-# INLINE modifyBoxedVector #-}+modifyBoxedVector f = V.modify (\x -> modifyBoxedMVector f x) {- | A simple parallel implementation of quicksort. It uses a sequential divide-and-conquer when size <8,
+ src/Data/Vector/Mutable/Linear/Borrow/Internal.hs view
@@ -0,0 +1,61 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE RoleAnnotations #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_HADDOCK hide #-}++module Data.Vector.Mutable.Linear.Borrow.Internal (+ module Data.Vector.Mutable.Linear.Borrow.Internal,+) where++import Control.Monad qualified as NonLinear+import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.BO.Unsafe+import Control.Monad.Borrow.Pure.Clone+import Control.Monad.Borrow.Pure.Copyable+import Control.Monad.Borrow.Pure.Lifetime.Token.Unsafe (+ LinearOnly (..),+ LinearOnlyWitness (..),+ )+import Data.Vector.Mutable (RealWorld)+import Data.Vector.Mutable qualified as MV+import GHC.TypeError+import Prelude.Linear+import Unsafe.Linear qualified as Unsafe+import Prelude qualified as NonLinear++-- | Trusted representation of the public fixed-size boxed vector.+newtype Vector a = Vector {content :: MV.MVector RealWorld a}++type role Vector nominal++-- | Construct a fixed-size view over a raw mutable-vector slice.+unsafeFromMutableSlice :: Int -> Int -> MV.MVector RealWorld a %1 -> Vector a+{-# INLINE unsafeFromMutableSlice #-}+unsafeFromMutableSlice =+ Unsafe.toLinear3 \offset length_ buffer ->+ Vector (MV.unsafeSlice offset length_ buffer)++instance LinearOnly (Vector a) where+ linearOnly = UnsafeLinearOnly+ {-# INLINE linearOnly #-}++instance+ (Unsatisfiable (ShowType (Vector a) :<>: Text " cannot be copied!")) =>+ Copyable (Vector a)+ where+ copy = unsatisfiable++instance (Dupable a) => Clone (Vector a) where+ clone = Unsafe.toLinear \(UnsafeAlias (Vector v)) -> unsafeSystemIOToBO do+ let !n = MV.length v+ !new <- MV.new n+ let go !i = NonLinear.when (i < n) do+ x <- MV.unsafeRead v i+ let (!_, !x') = dup x+ MV.unsafeWrite new i x'+ go (i + 1)+ go 0+ NonLinear.pure (Vector new)+ {-# INLINE clone #-}
+ src/Data/Vector/Unboxed/Mutable/Growable/Linear/Borrow.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE NoImplicitPrelude #-}++{- |+A growable unboxed variant of+"Data.Vector.Mutable.Growable.Linear.Borrow".+-}+module Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow (+ GrowableVector,+ empty,+ constant,+ fromList,+ withCapacity,+ fromVector,+ unsafeFromMutable,+ unsafeFromVector,+ toVector,+ toList,+ size,+ capacity,+ get,+ unsafeGet,+ head,+ unsafeHead,+ last,+ unsafeLast,+ copyAt,+ copyAtMut,+ set,+ unsafeSet,+ update,+ unsafeUpdate,+ modify,+ swap,+ unsafeSwap,+ reserve,+ reserveAdditional,+ push,+ extend,+ getContents,+ withContent,+ withContent_,+) where++import Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow.Internal
+ src/Data/Vector/Unboxed/Mutable/Growable/Linear/Borrow/Internal.hs view
@@ -0,0 +1,797 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RoleAnnotations #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}+{-# OPTIONS_GHC -Wno-redundant-constraints #-}+{-# OPTIONS_HADDOCK hide #-}++module Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow.Internal (+ module Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow.Internal,+) where++import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.Affine (aff, pop)+import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.BO.Internal (unsafeSrunBO_)+import Control.Monad.Borrow.Pure.BO.Unsafe+import Control.Monad.Borrow.Pure.Copyable+import Control.Monad.Borrow.Pure.Lifetime.Token.Unsafe (+ LinearOnly (..),+ LinearOnlyWitness (..),+ )+import Data.Ref.Linear qualified as Ref+import Data.Ref.Linear.Borrow qualified as RefBorrow+import Data.Unrestricted.Linear qualified as Ur+import Data.Vector.Unboxed qualified as U+import Data.Vector.Unboxed.Mutable qualified as UM+import Data.Vector.Unboxed.Mutable.Linear.Borrow qualified as Fixed+import Data.Vector.Unboxed.Mutable.Linear.Borrow.Internal qualified as Fixed.Internal+import GHC.Exts qualified as GHC+import GHC.IO (unsafePerformIO)+import GHC.Stack (HasCallStack)+import GHC.TypeError+import Prelude.Linear hiding (getContents, head, last)+import Unsafe.Linear qualified as Unsafe+import Prelude qualified as NonLinear++data Header a where+ Header ::+ {-# UNPACK #-} !Int ->+ !(UM.IOVector a) %1 ->+ Header a++-- | Growable unboxed linear mutable vector.+data GrowableVector a where+ GrowableVector :: !(Ref.Ref (Header a)) %1 -> GrowableVector a++type role Header nominal++type role GrowableVector nominal++instance LinearOnly (GrowableVector a) where+ linearOnly = UnsafeLinearOnly+ {-# INLINE linearOnly #-}++instance+ (Unsatisfiable (ShowType (GrowableVector a) :<>: Text " cannot be copied!")) =>+ Copyable (GrowableVector a)+ where+ copy = unsatisfiable++instance (U.Unbox a, Consumable a) => Consumable (GrowableVector a) where+ consume =+ Unsafe.toLinear \(GrowableVector ref) ->+ case Ref.free ref of+ Header logicalSize buffer -> consumeInitialized logicalSize buffer+ -- Reaches 'unsafePerformIO' through 'consumeInitialized'. See the note+ -- there: this must not be duplicated across call sites.+ {-# NOINLINE consume #-}++allocateBuffer ::+ (U.Unbox a) =>+ Int ->+ Linearly %1 ->+ UM.IOVector a+{-# NOINLINE allocateBuffer #-}+allocateBuffer =+ GHC.noinline \count linear ->+ linear `lseq` unsafePerformIO (UM.unsafeNew count)++cloneBuffer ::+ (U.Unbox a) =>+ U.Vector a ->+ Linearly %1 ->+ UM.IOVector a+{-# NOINLINE cloneBuffer #-}+cloneBuffer =+ GHC.noinline \source linear ->+ linear `lseq` unsafePerformIO (U.thaw source)++-- | \(O(1)\). Construct an empty vector with zero capacity.+empty :: (U.Unbox a) => Linearly %1 -> GrowableVector a+{-# NOINLINE empty #-}+empty = withCapacity 0++-- | \(O(n)\). Construct @n@ initialized copies of a value.+constant ::+ (U.Unbox a) =>+ Int ->+ a ->+ Linearly %1 ->+ GrowableVector a+{-# NOINLINE constant #-}+constant =+ GHC.noinline \count value linear ->+ fromVector (U.replicate count value) linear++-- | \(O(n)\). Move a linear list into a new vector.+fromList ::+ (U.Unbox a) =>+ [a] %1 ->+ Linearly %1 ->+ GrowableVector a+{-# NOINLINE fromList #-}+fromList =+ GHC.noinline $+ Unsafe.toLinear2 \values linear ->+ dup linear & \(bufferLinear, refLinear) ->+ case Fixed.fromList values bufferLinear of+ Fixed.Internal.Vector buffer ->+ GrowableVector+ (Ref.new (Header (UM.length buffer) buffer) refLinear)++{- | \(O(1)\). Construct an empty vector with requested capacity.++The capacity must be non-negative. Spare storage is not initialized.+-}+withCapacity ::+ (HasCallStack, U.Unbox a) =>+ Int ->+ Linearly %1 ->+ GrowableVector a+{-# NOINLINE withCapacity #-}+withCapacity =+ GHC.noinline \requested linear ->+ if requested < 0+ then error ("withCapacity: negative capacity " <> show requested) linear+ else+ dup linear & \(bufferLinear, refLinear) ->+ GrowableVector+ (Ref.new (Header 0 (allocateBuffer requested bufferLinear)) refLinear)++-- | \(O(n)\). Copy an immutable unboxed vector into a new owner.+fromVector ::+ (U.Unbox a) =>+ U.Vector a ->+ Linearly %1 ->+ GrowableVector a+{-# NOINLINE fromVector #-}+fromVector =+ GHC.noinline \source linear ->+ dup linear & \(bufferLinear, refLinear) ->+ GrowableVector+ ( Ref.new+ (Header (U.length source) (cloneBuffer source bufferLinear))+ refLinear+ )++{- | \(O(1)\). Take ownership of a mutable unboxed vector.++The complete source slice must be initialized, and the caller must retain no+alias or overlapping slice.+-}+unsafeFromMutable ::+ (U.Unbox a) =>+ UM.MVector state a %1 ->+ Linearly %1 ->+ GrowableVector a+{-# INLINE unsafeFromMutable #-}+unsafeFromMutable =+ Unsafe.toLinear \source linear ->+ GrowableVector+ ( Ref.new+ (Header (UM.length source) (Unsafe.coerce source))+ linear+ )++{- | \(O(1)\). Unsafely take ownership of immutable unboxed storage.++No immutable alias, including an overlapping slice, may be observed after+this operation.+-}+unsafeFromVector ::+ (U.Unbox a) =>+ U.Vector a %1 ->+ Linearly %1 ->+ GrowableVector a+{-# NOINLINE unsafeFromVector #-}+unsafeFromVector =+ GHC.noinline $+ Unsafe.toLinear \source linear ->+ GrowableVector+ ( Ref.new+ ( Header+ (U.length source)+ (unsafePerformIO (U.unsafeThaw source))+ )+ linear+ )++{- | \(O(n)\). Move every initialized element into GC ownership, then freeze+that prefix.++Spare capacity is not exposed.+-}+toVector ::+ (U.Unbox a, Movable a) =>+ GrowableVector a %1 ->+ Ur (U.Vector a)+{-# NOINLINE toVector #-}+toVector =+ GHC.noinline $+ Unsafe.toLinear \(GrowableVector ref) ->+ case Ref.free ref of+ Header logicalSize buffer ->+ let !frozen =+ unsafePerformIO do+ moveInitialized logicalSize buffer+ U.unsafeFreeze (UM.unsafeTake logicalSize buffer)+ in Ur frozen++-- | \(O(n)\). Consume the owner and materialize its initialized prefix.+toList ::+ (U.Unbox a, Movable a) =>+ GrowableVector a %1 ->+ Ur [a]+{-# INLINE toList #-}+toList = Ur.lift U.toList . toVector++moveInitialized ::+ (U.Unbox a, Movable a) =>+ Int ->+ UM.IOVector a ->+ NonLinear.IO ()+{-# INLINE moveInitialized #-}+moveInitialized !logicalSize buffer = go 0+ where+ go !index+ | index >= logicalSize = NonLinear.pure ()+ | otherwise = do+ value <- UM.unsafeRead buffer index+ case move value of+ Ur !moved -> UM.unsafeWrite buffer index moved+ go (index + 1)++{- | Consume the initialized prefix, releasing each element exactly once.++The traversal only reads the buffer, but it runs under 'unsafePerformIO'.+That makes the binding a trusted boundary rather than an ordinary pure+function: were it inlined, GHC could duplicate the call across use sites, or+float it out of a scope, and each copy would consume the elements again. The+'NOINLINE' keeps exactly one occurrence, so the exactly-once discipline the+linear types promise is preserved in the generated code too.+-}+consumeInitialized ::+ (U.Unbox a, Consumable a) =>+ Int ->+ UM.IOVector a %1 ->+ ()+{-# NOINLINE consumeInitialized #-}+consumeInitialized =+ Unsafe.toLinear2 \logicalSize buffer ->+ let go !index+ | index >= logicalSize = NonLinear.pure ()+ | otherwise = do+ value <- UM.unsafeRead buffer index+ let !() = consume value+ go (index + 1)+ in unsafePerformIO (go 0)++toRefMut ::+ Mut α (GrowableVector a) %1 ->+ Mut α (Ref.Ref (Header a))+{-# INLINE toRefMut #-}+toRefMut =+ unsafeMapAlias+ (Unsafe.toLinear \(GrowableVector ref) -> ref)++fromRefMut ::+ Mut α (Ref.Ref (Header a)) %1 ->+ Mut α (GrowableVector a)+{-# INLINE fromRefMut #-}+fromRefMut =+ unsafeMapAlias+ (Unsafe.toLinear GrowableVector)++withHeader ::+ (α >= β) =>+ (Header a %1 -> BO β (result, Header a)) %1 ->+ Mut α (GrowableVector a) %1 ->+ BO β (result, Mut α (GrowableVector a))+{-# INLINE withHeader #-}+withHeader action vector = Control.do+ (result, ref) <- RefBorrow.update action (toRefMut vector)+ Control.pure (result, fromRefMut ref)++-- | \(O(1)\). Return logical size and thread the borrow.+size ::+ (U.Unbox a) =>+ Borrow bk α (GrowableVector a) %1 ->+ (Ur Int, Borrow bk α (GrowableVector a))+{-# INLINE size #-}+size =+ Unsafe.toLinear \vector@(UnsafeAlias (GrowableVector ref)) ->+ case Ref.unsafeReadRef ref of+ (Header logicalSize _, duplicateRef) ->+ pop (aff duplicateRef) `lseq` (Ur logicalSize, vector)++-- | \(O(1)\). Return allocation capacity and thread the borrow.+capacity ::+ (U.Unbox a) =>+ Borrow bk α (GrowableVector a) %1 ->+ (Ur Int, Borrow bk α (GrowableVector a))+{-# INLINE capacity #-}+capacity =+ Unsafe.toLinear \vector@(UnsafeAlias (GrowableVector ref)) ->+ case Ref.unsafeReadRef ref of+ (Header _ buffer, duplicateRef) ->+ pop (aff duplicateRef) `lseq` (Ur (UM.length buffer), vector)++-- | Borrow an initialized element at an index.+get ::+ (HasCallStack, U.Unbox a, α >= β) =>+ Int ->+ Borrow bk α (GrowableVector a) %1 ->+ BO β (Borrow bk α a)+{-# INLINE get #-}+get index vector =+ case size vector of+ (Ur logicalSize, vector) ->+ if index < 0 || index >= logicalSize+ then+ error+ ( "get: index "+ <> show index+ <> " out of bounds for length "+ <> show logicalSize+ )+ vector+ else unsafeGet index vector++-- | Unchecked 'get'. The index must satisfy @0 <= index < size@.+unsafeGet ::+ (U.Unbox a, α >= β) =>+ Int ->+ Borrow bk α (GrowableVector a) %1 ->+ BO β (Borrow bk α a)+{-# INLINE unsafeGet #-}+unsafeGet =+ Unsafe.toLinear2 \index (UnsafeAlias (GrowableVector ref)) ->+ case Ref.unsafeReadRef ref of+ (Header _ buffer, duplicateRef) ->+ pop (aff duplicateRef) `lseq`+ UnsafeAlias+ Control.<$> unsafeSystemIOToBO (UM.unsafeRead buffer index)++-- | Borrow the first initialized element.+head ::+ (HasCallStack, U.Unbox a, α >= β) =>+ Borrow bk α (GrowableVector a) %1 ->+ BO β (Borrow bk α a)+{-# INLINE head #-}+head = get 0++-- | Unchecked 'head'. The vector must be non-empty.+unsafeHead ::+ (U.Unbox a, α >= β) =>+ Borrow bk α (GrowableVector a) %1 ->+ BO β (Borrow bk α a)+{-# INLINE unsafeHead #-}+unsafeHead = unsafeGet 0++-- | Borrow the last initialized element.+last ::+ (HasCallStack, U.Unbox a, α >= β) =>+ Borrow bk α (GrowableVector a) %1 ->+ BO β (Borrow bk α a)+{-# INLINE last #-}+last vector =+ case size vector of+ (Ur logicalSize, vector) ->+ if logicalSize <= 0+ then error "last: empty vector" vector+ else unsafeGet (logicalSize - 1) vector++-- | Unchecked 'last'. The vector must be non-empty.+unsafeLast ::+ (U.Unbox a, α >= β) =>+ Borrow bk α (GrowableVector a) %1 ->+ BO β (Borrow bk α a)+{-# INLINE unsafeLast #-}+unsafeLast vector =+ case size vector of+ (Ur logicalSize, vector) -> unsafeGet (logicalSize - 1) vector++-- | Copy an initialized element through a shared borrow.+copyAt ::+ (HasCallStack, U.Unbox a, Copyable a, α >= β) =>+ Int ->+ Share α (GrowableVector a) ->+ BO β (Ur a)+{-# INLINE copyAt #-}+copyAt index vector = Control.do+ Ur !value <- move Control.<$> get index vector+ Control.pure $! Ur $! copy value++-- | Copy an initialized element and retain the mutable borrow.+copyAtMut ::+ (HasCallStack, U.Unbox a, Copyable a, α >= β) =>+ Int ->+ Mut α (GrowableVector a) %1 ->+ BO β (Ur a, Mut α (GrowableVector a))+{-# INLINE copyAtMut #-}+copyAtMut =+ Unsafe.toLinear2 \index vector@(UnsafeAlias (GrowableVector ref)) ->+ case Ref.unsafeReadRef ref of+ (Header logicalSize buffer, duplicateRef) ->+ pop (aff duplicateRef) `lseq`+ if index < 0 || index >= logicalSize+ then+ error+ ( "copyAtMut: index "+ <> show index+ <> " out of bounds for length "+ <> show logicalSize+ )+ vector+ else unsafeSystemIOToBO do+ !value <- UM.unsafeRead buffer index+ let !copied = copy (UnsafeAlias value)+ NonLinear.pure (Ur copied, vector)++-- | Replace an initialized element and return the displaced value.+set ::+ (HasCallStack, U.Unbox a, α >= β) =>+ Int ->+ a %1 ->+ Mut α (GrowableVector a) %1 ->+ BO β (a, Mut α (GrowableVector a))+{-# INLINE set #-}+set index value vector =+ case size vector of+ (Ur logicalSize, vector) ->+ if index < 0 || index >= logicalSize+ then+ error+ ( "set: index "+ <> show index+ <> " out of bounds for length "+ <> show logicalSize+ )+ value+ vector+ else unsafeSet index value vector++-- | Unchecked 'set'. The index must satisfy @0 <= index < size@.+unsafeSet ::+ (U.Unbox a, α >= β) =>+ Int ->+ a %1 ->+ Mut α (GrowableVector a) %1 ->+ BO β (a, Mut α (GrowableVector a))+{-# INLINE unsafeSet #-}+unsafeSet =+ Unsafe.toLinear3 \index !value vector@(UnsafeAlias (GrowableVector ref)) ->+ case Ref.unsafeReadRef ref of+ (Header _ buffer, duplicateRef) ->+ pop (aff duplicateRef) `lseq`+ unsafeSystemIOToBO do+ !oldValue <- UM.unsafeExchange buffer index value+ NonLinear.pure (oldValue, vector)++-- | Linearly transform an initialized element and return an auxiliary result.+update ::+ (HasCallStack, U.Unbox a, α >= β) =>+ Int ->+ (a %1 -> BO β (result, a)) %1 ->+ Mut α (GrowableVector a) %1 ->+ BO β (result, Mut α (GrowableVector a))+{-# INLINE update #-}+update index action vector =+ case size vector of+ (Ur logicalSize, vector) ->+ if index < 0 || index >= logicalSize+ then+ error+ ( "update: index "+ <> show index+ <> " out of bounds for length "+ <> show logicalSize+ )+ action+ vector+ else unsafeUpdate index action vector++{- | Unchecked 'update'. The index must satisfy @0 <= index < size@.++The callback must return exactly one replacement before the growable borrow is+restored. No exceptional owner recovery is claimed.+-}+unsafeUpdate ::+ (U.Unbox a, α >= β) =>+ Int ->+ (a %1 -> BO β (result, a)) %1 ->+ Mut α (GrowableVector a) %1 ->+ BO β (result, Mut α (GrowableVector a))+{-# INLINE unsafeUpdate #-}+unsafeUpdate index =+ Unsafe.toLinear2 \action vector@(UnsafeAlias (GrowableVector ref)) ->+ case Ref.unsafeReadRef ref of+ (Header _ buffer, duplicateRef) ->+ pop (aff duplicateRef) `lseq` Control.do+ value <- unsafeSystemIOToBO (UM.unsafeRead buffer index)+ (!result, !updatedValue) <- action value+ () <-+ unsafeSystemIOToBO+ (Unsafe.toLinear3 UM.unsafeWrite buffer index updatedValue)+ Control.pure (result, vector)++-- | Linearly transform an initialized element.+modify ::+ (HasCallStack, U.Unbox a, α >= β) =>+ Int ->+ (a %1 -> a) %1 ->+ Mut α (GrowableVector a) %1 ->+ BO β (Mut α (GrowableVector a))+{-# INLINE modify #-}+modify index function vector = Control.do+ ((), vector) <-+ update+ index+ (\value -> Control.pure ((), function value))+ vector+ Control.pure vector++-- | Swap two initialized elements.+swap ::+ (HasCallStack, U.Unbox a, α >= β) =>+ Mut α (GrowableVector a) %1 ->+ Int ->+ Int ->+ BO β (Mut α (GrowableVector a))+{-# INLINE swap #-}+swap vector first second =+ case size vector of+ (Ur logicalSize, vector) ->+ if first+ < 0+ || first+ >= logicalSize+ || second+ < 0+ || second+ >= logicalSize+ then+ error+ ( "swap: indices "+ <> show (first, second)+ <> " out of bounds for length "+ <> show logicalSize+ )+ vector+ else unsafeSwap vector first second++-- | Unchecked 'swap'. Both indices must satisfy @0 <= index < size@.+unsafeSwap ::+ (U.Unbox a, α >= β) =>+ Mut α (GrowableVector a) %1 ->+ Int ->+ Int ->+ BO β (Mut α (GrowableVector a))+{-# INLINE unsafeSwap #-}+unsafeSwap =+ Unsafe.toLinear3 \vector@(UnsafeAlias (GrowableVector ref)) first second ->+ case Ref.unsafeReadRef ref of+ (Header _ buffer, duplicateRef) ->+ pop (aff duplicateRef) `lseq`+ unsafeSystemIOToBO do+ UM.unsafeSwap buffer first second+ NonLinear.pure vector++-- | Ensure at least the requested absolute capacity.+reserve ::+ (HasCallStack, U.Unbox a, α >= β) =>+ Int ->+ Mut α (GrowableVector a) %1 ->+ BO β (Mut α (GrowableVector a))+{-# INLINE reserve #-}+reserve requested vector+ | requested < 0 =+ error ("reserve: negative capacity " <> show requested) vector+ | otherwise = Control.do+ ((), vector) <-+ withHeader+ ( Unsafe.toLinear \(Header logicalSize buffer) -> Control.do+ grown <- growTo logicalSize requested buffer+ Control.pure ((), Header logicalSize grown)+ )+ vector+ Control.pure vector++-- | Ensure capacity for at least current size plus the requested amount.+reserveAdditional ::+ (HasCallStack, U.Unbox a, α >= β) =>+ Int ->+ Mut α (GrowableVector a) %1 ->+ BO β (Mut α (GrowableVector a))+{-# INLINE reserveAdditional #-}+reserveAdditional additional vector+ | additional < 0 =+ error+ ("reserveAdditional: negative additional capacity " <> show additional)+ vector+ | otherwise = Control.do+ ((), vector) <-+ withHeader+ ( Unsafe.toLinear \(Header logicalSize buffer) ->+ let !required =+ checkedAdd "reserveAdditional" logicalSize additional+ in Control.do+ grown <- growTo logicalSize required buffer+ Control.pure ((), Header logicalSize grown)+ )+ vector+ Control.pure vector++-- | Append one linearly supplied element.+push ::+ (HasCallStack, U.Unbox a, α >= β) =>+ a %1 ->+ Mut α (GrowableVector a) %1 ->+ BO β (Mut α (GrowableVector a))+{-# INLINE push #-}+push =+ Unsafe.toLinear2 \ !value vector -> Control.do+ ((), vector) <-+ withHeader+ ( Unsafe.toLinear \(Header logicalSize buffer) ->+ let !required = checkedAdd "push" logicalSize 1+ !target = growthTarget (UM.length buffer) required+ in Control.do+ grown <- growTo logicalSize target buffer+ grown <- writeAt logicalSize value grown+ Control.pure ((), Header required grown)+ )+ vector+ Control.pure vector++-- | Append copies of all elements of an immutable unboxed vector.+extend ::+ (HasCallStack, U.Unbox a, α >= β) =>+ U.Vector a ->+ Mut α (GrowableVector a) %1 ->+ BO β (Mut α (GrowableVector a))+{-# INLINE extend #-}+extend source vector = Control.do+ ((), vector) <-+ withHeader+ ( Unsafe.toLinear \(Header logicalSize buffer) ->+ let !sourceSize = U.length source+ !required = checkedAdd "extend" logicalSize sourceSize+ !target = growthTarget (UM.length buffer) required+ in Control.do+ grown <- growTo logicalSize target buffer+ grown <- copyImmutableInto source logicalSize grown+ Control.pure ((), Header required grown)+ )+ vector+ Control.pure vector++copyImmutable ::+ (U.Unbox a) =>+ U.Vector a ->+ Int ->+ UM.IOVector a ->+ NonLinear.IO ()+{-# INLINE copyImmutable #-}+copyImmutable source offset target =+ U.copy (UM.unsafeSlice offset (U.length source) target) source++copyImmutableInto ::+ (U.Unbox a) =>+ U.Vector a ->+ Int ->+ UM.IOVector a %1 ->+ BO β (UM.IOVector a)+{-# INLINE copyImmutableInto #-}+copyImmutableInto source offset =+ Unsafe.toLinear \target -> unsafeSystemIOToBO do+ copyImmutable source offset target+ NonLinear.pure target++writeAt ::+ (U.Unbox a) =>+ Int ->+ a %1 ->+ UM.IOVector a %1 ->+ BO β (UM.IOVector a)+{-# INLINE writeAt #-}+writeAt =+ Unsafe.toLinear3 \index value target -> unsafeSystemIOToBO do+ UM.unsafeWrite target index value+ NonLinear.pure target++growTo ::+ (U.Unbox a) =>+ Int ->+ Int ->+ UM.IOVector a %1 ->+ BO β (UM.IOVector a)+{-# INLINE growTo #-}+growTo =+ Unsafe.toLinear3 \logicalSize requested buffer ->+ let !oldCapacity = UM.length buffer+ in if requested <= oldCapacity+ then Control.pure buffer+ else unsafeSystemIOToBO do+ grown <- UM.unsafeNew requested+ UM.unsafeCopy+ (UM.unsafeTake logicalSize grown)+ (UM.unsafeTake logicalSize buffer)+ NonLinear.pure grown++growthTarget :: Int -> Int -> Int+{-# INLINE growthTarget #-}+growthTarget oldCapacity required+ | required <= oldCapacity = oldCapacity+ | oldCapacity <= 0 = required `max` 1+ | oldCapacity > maxBound `quot` 2 = required+ | otherwise = required `max` (oldCapacity * 2)++checkedAdd :: (HasCallStack) => NonLinear.String -> Int -> Int -> Int+{-# INLINE checkedAdd #-}+checkedAdd operation left right+ | right > maxBound - left =+ error (operation <> ": capacity overflow")+ | otherwise = left + right++{- | Project a growable borrow to its fixed initialized prefix.++The projection preserves borrow kind and lifetime and exposes no spare+capacity or growth operation.++Where a transaction branches, prefer projecting once at its entry --+@let %1 !content = 'getContents' borrow@ -- over projecting separately inside+each branch. Both are correct and consume the growable occurrence exactly+once; the entry form simply gives the optimizer one header read to place+rather than one per surviving branch.+-}+getContents ::+ (U.Unbox a) =>+ Borrow bk α (GrowableVector a) %1 ->+ Borrow bk α (Fixed.Vector a)+{-# INLINE getContents #-}+getContents =+ Unsafe.toLinear \(UnsafeAlias (GrowableVector ref)) ->+ case Ref.unsafeReadRef ref of+ (Header logicalSize buffer, duplicateRef) ->+ pop (aff duplicateRef) `lseq`+ UnsafeAlias+ (Fixed.Internal.unsafeFromMutableSlice 0 logicalSize buffer)++-- | Borrow the fixed initialized prefix in a rank-2 no-growth scope.+withContent ::+ (U.Unbox a) =>+ Borrow bk α (GrowableVector a) %1 ->+ ( forall β.+ Borrow bk (β /\ α) (Fixed.Vector a) %1 ->+ BO (β /\ α) result+ ) %1 ->+ BO α (result, Borrow bk α (GrowableVector a))+{-# INLINE withContent #-}+withContent =+ Unsafe.toLinear2 \vector action ->+ -- The growable borrow is handed back through `reviveAlias`, as the scalar delimiters do: see Note [Restoring a borrow must break its Core identity] in "Control.Monad.Borrow.Pure.BO.Internal".+ unsafeSrunBO_ Control.do+ result <- action (getContents (Unsafe.coerce vector))+ (result,) Control.<$> reviveAlias vector++-- | A result-discarding variant of 'withContent'.+withContent_ ::+ (U.Unbox a, Consumable result) =>+ Borrow bk α (GrowableVector a) %1 ->+ ( forall β.+ Borrow bk (β /\ α) (Fixed.Vector a) %1 ->+ BO (β /\ α) result+ ) %1 ->+ BO α (Borrow bk α (GrowableVector a))+{-# INLINE withContent_ #-}+withContent_ vector action =+ withContent vector action Control.<&> \(result, vector) ->+ consume result `lseq` vector
+ src/Data/Vector/Unboxed/Mutable/Linear/Borrow.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE NoImplicitPrelude #-}++{- |+An unboxed variant of "Data.Vector.Mutable.Linear.Borrow".+-}+module Data.Vector.Unboxed.Mutable.Linear.Borrow (+ Vector,+ empty,+ constant,+ fromList,+ fromVector,+ unsafeFromVector,+ unsafeFromMutable,+ toVector,+ toList,+ copyToVector,+ size,+ get,+ unsafeGet,+ head,+ unsafeHead,+ last,+ unsafeLast,+ copyAt,+ copyAtMut,+ set,+ unsafeSet,+ update,+ unsafeUpdate,+ modify,+ swap,+ unsafeSwap,+ splitAt,+) where++import Data.Vector.Unboxed.Mutable.Linear.Borrow.Internal
+ src/Data/Vector/Unboxed/Mutable/Linear/Borrow/Internal.hs view
@@ -0,0 +1,535 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE RoleAnnotations #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}+{-# OPTIONS_GHC -Wno-redundant-constraints #-}+{-# OPTIONS_HADDOCK hide #-}++module Data.Vector.Unboxed.Mutable.Linear.Borrow.Internal (+ module Data.Vector.Unboxed.Mutable.Linear.Borrow.Internal,+) where++import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.BO.Unsafe+import Control.Monad.Borrow.Pure.Copyable+import Control.Monad.Borrow.Pure.Lifetime.Token.Unsafe (+ LinearOnly (..),+ LinearOnlyWitness (..),+ )+import Data.Unrestricted.Linear qualified as Ur+import Data.Vector.Unboxed qualified as U+import Data.Vector.Unboxed.Mutable qualified as UM+import GHC.Exts qualified as GHC+import GHC.IO (unsafePerformIO)+import GHC.Stack (HasCallStack)+import GHC.TypeError+import Prelude.Linear hiding (head, last, splitAt)+import Unsafe.Linear qualified as Unsafe+import Prelude qualified as NonLinear++-- | Unboxed linear mutable vector.+newtype Vector a = Vector {content :: UM.IOVector a}++type role Vector nominal++-- | Construct a fixed-size view over a raw mutable-vector slice.+unsafeFromMutableSlice ::+ (U.Unbox a) =>+ Int ->+ Int ->+ UM.IOVector a %1 ->+ Vector a+{-# INLINE unsafeFromMutableSlice #-}+unsafeFromMutableSlice =+ Unsafe.toLinear3 \offset length_ buffer ->+ Vector (UM.unsafeSlice offset length_ buffer)++instance LinearOnly (Vector a) where+ linearOnly = UnsafeLinearOnly+ {-# INLINE linearOnly #-}++instance+ (Unsatisfiable (ShowType (Vector a) :<>: Text " cannot be copied!")) =>+ Copyable (Vector a)+ where+ copy = unsatisfiable++instance (U.Unbox a, Consumable a) => Consumable (Vector a) where+ consume =+ Unsafe.toLinear \(Vector vector) ->+ unsafePerformIO (consumeElements 0 (UM.length vector) vector)+ -- The traversal only reads the buffer, but it runs under+ -- 'unsafePerformIO'. Inlining would let GHC duplicate that call across use+ -- sites, or float it out of a scope, and each copy would consume the+ -- elements again; 'NOINLINE' keeps exactly one occurrence.+ {-# NOINLINE consume #-}++consumeElements ::+ (U.Unbox a, Consumable a) =>+ Int ->+ Int ->+ UM.IOVector a ->+ IO ()+{-# INLINE consumeElements #-}+consumeElements !index !length_ vector+ | index >= length_ = NonLinear.pure ()+ | otherwise = do+ value <- UM.unsafeRead vector index+ let !() = consume value+ consumeElements (index + 1) length_ vector++-- | \(O(1)\). Construct an empty vector.+empty :: (U.Unbox a) => Linearly %1 -> Vector a+{-# NOINLINE empty #-}+empty =+ GHC.noinline \linear ->+ linear `lseq` Vector (unsafePerformIO (UM.unsafeNew 0))++{- | \(O(n)\). Construct a vector containing @count@ copies of a value.++As in @vector@, a negative count produces an empty vector.+-}+constant ::+ (U.Unbox a) =>+ Int ->+ a ->+ Linearly %1 ->+ Vector a+{-# NOINLINE constant #-}+constant =+ GHC.noinline \count value linear ->+ linear `lseq` Vector (unsafePerformIO (UM.replicate count value))++-- | \(O(n)\). Move the elements of a linear list into a new vector.+fromList ::+ (U.Unbox a) =>+ [a] %1 ->+ Linearly %1 ->+ Vector a+{-# NOINLINE fromList #-}+fromList =+ GHC.noinline $+ Unsafe.toLinear \values linear ->+ linear `lseq`+ case measureList values of+ (length_, values) ->+ Vector+ ( unsafePerformIO do+ vector <- UM.unsafeNew length_+ fillList 0 vector values+ NonLinear.pure vector+ )++measureList :: [a] %1 -> (Int, [a])+{-# INLINE measureList #-}+measureList [] = (0, [])+measureList (value : values) =+ case measureList values of+ (length_, values) -> (length_ + 1, value : values)++fillList :: (U.Unbox a) => Int -> UM.IOVector a -> [a] -> IO ()+{-# INLINE fillList #-}+fillList !_ _ [] = NonLinear.pure ()+fillList !index vector (value : values) = do+ UM.unsafeWrite vector index value+ fillList (index + 1) vector values++-- | \(O(n)\). Copy an immutable unboxed vector into a new owner.+fromVector ::+ (U.Unbox a) =>+ U.Vector a ->+ Linearly %1 ->+ Vector a+{-# NOINLINE fromVector #-}+fromVector =+ GHC.noinline \source linear ->+ linear `lseq` Vector (unsafePerformIO (U.thaw source))++{- | \(O(1)\). Unsafely take ownership of an immutable unboxed vector.++The caller must ensure that no alias of the source vector, including an+overlapping slice or compiler-introduced sharing, is ever observed again.+-}+unsafeFromVector ::+ (U.Unbox a) =>+ U.Vector a %1 ->+ Linearly %1 ->+ Vector a+{-# NOINLINE unsafeFromVector #-}+unsafeFromVector =+ GHC.noinline $+ Unsafe.toLinear \source linear ->+ linear `lseq` Vector (unsafePerformIO (U.unsafeThaw source))++{- | \(O(1)\). Unsafely take ownership of a mutable unboxed vector.++The caller must not retain any alias or overlapping slice of the input. The+entire adopted slice must be fully initialized. The state parameter is erased+only because ownership has been transferred.+-}+unsafeFromMutable ::+ (U.Unbox a) =>+ UM.MVector state a %1 ->+ Linearly %1 ->+ Vector a+{-# INLINE unsafeFromMutable #-}+unsafeFromMutable =+ Unsafe.toLinear2 \source linear ->+ linear `lseq` Vector (Unsafe.coerce source)++{- | \(O(n)\). Move every element into GC ownership, then freeze the storage.++'Movable' authorizes transferring the consumed elements into the unrestricted+immutable vector. Each 'move' may perform a deep copy.+-}+toVector ::+ (U.Unbox a, Movable a) =>+ Vector a %1 ->+ Ur (U.Vector a)+{-# NOINLINE toVector #-}+toVector =+ GHC.noinline $+ Unsafe.toLinear \(Vector vector) ->+ let !frozen =+ unsafePerformIO do+ moveElements 0 (UM.length vector) vector+ U.unsafeFreeze vector+ in Ur frozen++-- | \(O(n)\). Consume the owner and materialize its elements as a list.+toList ::+ (U.Unbox a, Movable a) =>+ Vector a %1 ->+ Ur [a]+{-# INLINE toList #-}+toList = Ur.lift U.toList . toVector++moveElements ::+ (U.Unbox a, Movable a) =>+ Int ->+ Int ->+ UM.IOVector a ->+ IO ()+{-# INLINE moveElements #-}+moveElements !index !length_ vector+ | index >= length_ = NonLinear.pure ()+ | otherwise = do+ value <- UM.unsafeRead vector index+ case move value of+ Ur !moved -> UM.unsafeWrite vector index moved+ moveElements (index + 1) length_ vector++{- | \(O(n)\). Copy a live vector into an immutable vector and thread its borrow.++Unlike 'toVector', this leaves the owner live and therefore performs a copy.+-}+copyToVector ::+ (U.Unbox a, Copyable a, α >= β) =>+ Borrow bk α (Vector a) %1 ->+ BO β (Ur (U.Vector a), Borrow bk α (Vector a))+{-# INLINE copyToVector #-}+copyToVector =+ Unsafe.toLinear \array@(UnsafeAlias (Vector vector)) ->+ unsafeSystemIOToBO do+ target <- UM.unsafeNew (UM.length vector)+ copyElements 0 (UM.length vector) vector target+ snapshot <- U.unsafeFreeze target+ NonLinear.pure (Ur snapshot, array)++copyElements ::+ (U.Unbox a, Copyable a) =>+ Int ->+ Int ->+ UM.IOVector a ->+ UM.IOVector a ->+ IO ()+{-# INLINE copyElements #-}+copyElements !index !length_ source target+ | index >= length_ = NonLinear.pure ()+ | otherwise = do+ value <- UM.unsafeRead source index+ let !copied = copy (UnsafeAlias value)+ UM.unsafeWrite target index copied+ copyElements (index + 1) length_ source target++-- | \(O(1)\). Return the number of elements and thread the vector borrow.+size ::+ (U.Unbox a) =>+ Borrow bk α (Vector a) %1 ->+ (Ur Int, Borrow bk α (Vector a))+{-# INLINE size #-}+size =+ Unsafe.toLinear \array@(UnsafeAlias (Vector vector)) ->+ (Ur (UM.length vector), array)++-- | Borrow the element at an index.+get ::+ (HasCallStack, U.Unbox a, α >= β) =>+ Int ->+ Borrow bk α (Vector a) %1 ->+ BO β (Borrow bk α a)+{-# INLINE get #-}+get index vector =+ case size vector of+ (Ur length_, vector)+ | index < 0 || index >= length_ ->+ error+ ( "get: index "+ <> show index+ <> " out of bounds for length "+ <> show length_+ )+ vector+ | otherwise -> unsafeGet index vector++-- | Unchecked 'get'. The index must satisfy @0 <= index < size@.+unsafeGet ::+ (U.Unbox a, α >= β) =>+ Int ->+ Borrow bk α (Vector a) %1 ->+ BO β (Borrow bk α a)+{-# INLINE unsafeGet #-}+unsafeGet =+ Unsafe.toLinear2 \index (UnsafeAlias (Vector vector)) ->+ UnsafeAlias Control.<$> unsafeSystemIOToBO (UM.unsafeRead vector index)++-- | Borrow the first element.+head ::+ (HasCallStack, U.Unbox a, α >= β) =>+ Borrow bk α (Vector a) %1 ->+ BO β (Borrow bk α a)+{-# INLINE head #-}+head = get 0++-- | Unchecked 'head'. The vector must be non-empty.+unsafeHead ::+ (U.Unbox a, α >= β) =>+ Borrow bk α (Vector a) %1 ->+ BO β (Borrow bk α a)+{-# INLINE unsafeHead #-}+unsafeHead = unsafeGet 0++-- | Borrow the last element.+last ::+ (HasCallStack, U.Unbox a, α >= β) =>+ Borrow bk α (Vector a) %1 ->+ BO β (Borrow bk α a)+{-# INLINE last #-}+last vector =+ case size vector of+ (Ur length_, vector)+ | length_ <= 0 -> error "last: empty vector" vector+ | otherwise -> unsafeGet (length_ - 1) vector++-- | Unchecked 'last'. The vector must be non-empty.+unsafeLast ::+ (U.Unbox a, α >= β) =>+ Borrow bk α (Vector a) %1 ->+ BO β (Borrow bk α a)+{-# INLINE unsafeLast #-}+unsafeLast vector =+ case size vector of+ (Ur length_, vector) -> unsafeGet (length_ - 1) vector++-- | Copy an element through a shared borrow.+copyAt ::+ (HasCallStack, U.Unbox a, Copyable a, α >= β) =>+ Int ->+ Share α (Vector a) ->+ BO β (Ur a)+{-# INLINE copyAt #-}+copyAt index vector = Control.do+ Ur !element <- move Control.<$> get index vector+ Control.pure $! Ur $! copy element++{- | Copy an element and retain the mutable vector borrow.++The raw read is only a temporary alias. 'copy' consumes it to produce the+authorized unrestricted result while the mutable vector remains exclusive.+-}+copyAtMut ::+ (HasCallStack, U.Unbox a, Copyable a, α >= β) =>+ Int ->+ Mut α (Vector a) %1 ->+ BO β (Ur a, Mut α (Vector a))+{-# INLINE copyAtMut #-}+copyAtMut =+ Unsafe.toLinear2 \index vector@(UnsafeAlias (Vector buffer)) ->+ let !length_ = UM.length buffer+ in if index < 0 || index >= length_+ then+ error+ ( "get: index "+ <> show index+ <> " out of bounds for length "+ <> show length_+ )+ vector+ else unsafeSystemIOToBO do+ !value <- UM.unsafeRead buffer index+ let !copied = copy (UnsafeAlias value)+ NonLinear.pure (Ur copied, vector)++-- | Replace an element and return the displaced value linearly.+set ::+ (HasCallStack, U.Unbox a, α >= β) =>+ Int ->+ a %1 ->+ Mut α (Vector a) %1 ->+ BO β (a, Mut α (Vector a))+{-# INLINE set #-}+set index value array =+ case size array of+ (Ur length_, array)+ | index < 0 || index >= length_ ->+ error+ ( "set: index "+ <> show index+ <> " out of bounds for length "+ <> show length_+ )+ value+ array+ | otherwise -> unsafeSet index value array++{- | Unchecked 'set'. The index must satisfy @0 <= index < size@.++The exchange completes before the displaced value is returned.+-}+unsafeSet ::+ (U.Unbox a, α >= β) =>+ Int ->+ a %1 ->+ Mut α (Vector a) %1 ->+ BO β (a, Mut α (Vector a))+{-# INLINE unsafeSet #-}+unsafeSet =+ Unsafe.toLinear3 \index !value array@(UnsafeAlias (Vector vector)) ->+ unsafeSystemIOToBO do+ !oldValue <- UM.unsafeExchange vector index value+ NonLinear.pure (oldValue, array)++-- | Linearly transform an element and return an auxiliary result.+update ::+ (HasCallStack, U.Unbox a, α >= β) =>+ Int ->+ (a %1 -> BO β (result, a)) %1 ->+ Mut α (Vector a) %1 ->+ BO β (result, Mut α (Vector a))+{-# INLINE update #-}+update index action array =+ case size array of+ (Ur length_, array)+ | index < 0 || index >= length_ ->+ error+ ( "update: index "+ <> show index+ <> " out of bounds for length "+ <> show length_+ )+ action+ array+ | otherwise -> unsafeUpdate index action array++{- | Unchecked 'update'. The index must satisfy @0 <= index < size@.++The mutable vector borrow remains unavailable while the callback owns the raw+element. The callback must return exactly one replacement, which is written+strictly before the vector borrow is restored. This is a normal-return+guarantee; no exceptional owner recovery is claimed.+-}+unsafeUpdate ::+ (U.Unbox a, α >= β) =>+ Int ->+ (a %1 -> BO β (result, a)) %1 ->+ Mut α (Vector a) %1 ->+ BO β (result, Mut α (Vector a))+{-# INLINE unsafeUpdate #-}+unsafeUpdate index =+ Unsafe.toLinear2 \action (UnsafeAlias array@(Vector vector)) -> Control.do+ value <- unsafeSystemIOToBO (UM.unsafeRead vector index)+ (!result, !updatedValue) <- action value+ () <-+ unsafeSystemIOToBO+ (Unsafe.toLinear3 UM.unsafeWrite vector index updatedValue)+ Control.pure (result, UnsafeAlias array)++-- | Linearly transform an element.+modify ::+ (HasCallStack, U.Unbox a, α >= β) =>+ Int ->+ (a %1 -> a) %1 ->+ Mut α (Vector a) %1 ->+ BO β (Mut α (Vector a))+{-# INLINE modify #-}+modify index function array = Control.do+ ((), array) <-+ update+ index+ (\value -> Control.pure ((), function value))+ array+ Control.pure array++-- | Swap two elements.+swap ::+ (HasCallStack, U.Unbox a, α >= β) =>+ Mut α (Vector a) %1 ->+ Int ->+ Int ->+ BO β (Mut α (Vector a))+{-# INLINE swap #-}+swap array first second =+ case size array of+ (Ur length_, array)+ | first+ < 0+ || first+ >= length_+ || second+ < 0+ || second+ >= length_ ->+ error+ ( "swap: indices "+ <> show (first, second)+ <> " out of bounds for length "+ <> show length_+ )+ array+ | otherwise -> unsafeSwap array first second++-- | Unchecked 'swap'. Both indices must satisfy @0 <= index < size@.+unsafeSwap ::+ (U.Unbox a, α >= β) =>+ Mut α (Vector a) %1 ->+ Int ->+ Int ->+ BO β (Mut α (Vector a))+{-# INLINE unsafeSwap #-}+unsafeSwap =+ Unsafe.toLinear3 \array@(UnsafeAlias (Vector vector)) first second ->+ unsafeSystemIOToBO do+ UM.unsafeSwap vector first second+ NonLinear.pure array++{- | Split a borrow into two disjoint fixed ranges without copying.++The index is clamped to @[0, size]@, matching @vector@'s 'UM.splitAt'.+-}+splitAt ::+ (U.Unbox a) =>+ Int %1 ->+ Borrow bk α (Vector a) %1 ->+ ( Borrow bk α (Vector a)+ , Borrow bk α (Vector a)+ )+{-# INLINE splitAt #-}+splitAt =+ Unsafe.toLinear2 \index (UnsafeAlias (Vector vector)) ->+ case UM.splitAt index vector of+ (left, right) ->+ (UnsafeAlias (Vector left), UnsafeAlias (Vector right))
+ test-inspection/Main.hs view
@@ -0,0 +1,22 @@+module Main (main) where++import PureBorrow.Inspection.Fft qualified as Fft+import PureBorrow.Inspection.GenericGrowableUnrestricted qualified as GenericGrowableUnrestricted+import PureBorrow.Inspection.MultiStoreScan qualified as MultiStoreScan+import PureBorrow.Inspection.QSort qualified as QSort+import PureBorrow.Inspection.Sublifetime qualified as Sublifetime+import PureBorrow.Inspection.Worklist.Resume qualified as WorklistResume+import Test.Tasty (defaultMain, testGroup)++main :: IO ()+main =+ defaultMain $+ testGroup+ "optimized Core"+ [ Fft.tests+ , GenericGrowableUnrestricted.tests+ , MultiStoreScan.tests+ , QSort.tests+ , Sublifetime.tests+ , WorklistResume.tests+ ]
+ test-inspection/PureBorrow/Inspection/Fft.hs view
@@ -0,0 +1,77 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-orphans #-}++module PureBorrow.Inspection.Fft (+ tests,+ unboxedFftCombineLoop,+) where++import Control.Concurrent.DivideConquer.Linear (combineLoop)+import Control.Monad.Borrow.Pure.BO (BO, Mut)+import Data.Complex (Complex)+import Data.Vector.Generic.Mutable qualified as Generic+import Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted qualified as Vector+import Data.Vector.Mutable qualified as Boxed+import Data.Vector.Unboxed qualified as Unboxed+import Prelude.Linear+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.Inspection++{-# SPECIALIZE combineLoop ::+ Int ->+ Complex Double ->+ Int ->+ Complex Double ->+ Mut α (Vector.Vector Unboxed.Vector (Complex Double)) %1 ->+ BO α ()+ #-}++unboxedFftCombineLoop ::+ Int ->+ Complex Double ->+ Int ->+ Complex Double ->+ Mut α (Vector.Vector Unboxed.Vector (Complex Double)) %1 ->+ BO α ()+{-# NOINLINE unboxedFftCombineLoop #-}+unboxedFftCombineLoop = combineLoop++tests :: TestTree+tests =+ testGroup+ "FFT"+ [ $( inspectTest+ ( (hasNoTypeClasses 'unboxedFftCombineLoop)+ { testName =+ Just "combine loop has no type-class dictionaries"+ }+ )+ )+ , $( inspectTest+ ( (hasNoType 'unboxedFftCombineLoop ''Boxed.MVector)+ { testName =+ Just "combine loop has no boxed-vector backing"+ }+ )+ )+ , $( inspectTest+ ( ( doesNotUseAnyOf+ 'unboxedFftCombineLoop+ [ 'combineLoop+ , 'Vector.unsafeGet+ , 'Vector.unsafeWrite+ , 'Generic.unsafeRead+ , 'Generic.unsafeWrite+ , 'Generic.basicUnsafeRead+ , 'Generic.basicUnsafeWrite+ ]+ )+ { testName =+ Just+ "combine loop contains no listed generic-vector operations"+ }+ )+ )+ ]
+ test-inspection/PureBorrow/Inspection/Flags.hs view
@@ -0,0 +1,29 @@+{-# LANGUAGE CPP #-}++{- |+The build configuration the inspection obligations are stated against.++This is the only module in the component that looks at @PURE_BORROW_SLOW_SCOPES@.+An obligation about optimized Core is a statement about one of the two implementations the @slow@ flag selects, so rather than compiling such an obligation out under the other one, state it once and invert it with 'expectFailIfBecause'.+A test that is expected to fail is still a test: the day @+slow@ starts producing the same Core, the suite says so instead of staying quietly green.+-}+module PureBorrow.Inspection.Flags (+ isSlowAPI,+ expectFailIfBecause,+) where++import Test.Tasty (TestTree)+import Test.Tasty.ExpectedFailure (expectFailBecause)++-- | Whether this component was built against the @+slow@ library, i.e. the one whose sublifetime delimiters allocate a real runtime lifetime token.+isSlowAPI :: Bool+#ifdef PURE_BORROW_SLOW_SCOPES+isSlowAPI = True+#else+isSlowAPI = False+#endif++-- | Invert a test tree when the condition holds, recording why.+expectFailIfBecause :: Bool -> String -> TestTree -> TestTree+expectFailIfBecause False _ = id+expectFailIfBecause True reason = expectFailBecause reason
+ test-inspection/PureBorrow/Inspection/GenericGrowableUnrestricted.hs view
@@ -0,0 +1,68 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -O2 #-}++module PureBorrow.Inspection.GenericGrowableUnrestricted (+ tests,+ boxedPush,+ unboxedPush,+) where++import Control.Monad.Borrow.Pure.BO (BO, Mut)+import Data.Vector qualified as Boxed+import Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted qualified as Growable+import Data.Vector.Mutable qualified as BoxedMutable+import Data.Vector.Unboxed qualified as Unboxed+import GHC.Base (IP)+import Prelude.Linear+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.Inspection++boxedPush ::+ Int ->+ Mut α (Growable.GrowableVector Boxed.Vector Int) %1 ->+ BO α (Mut α (Growable.GrowableVector Boxed.Vector Int))+{-# NOINLINE boxedPush #-}+boxedPush = Growable.push++unboxedPush ::+ Int ->+ Mut α (Growable.GrowableVector Unboxed.Vector Int) %1 ->+ BO α (Mut α (Growable.GrowableVector Unboxed.Vector Int))+{-# NOINLINE unboxedPush #-}+unboxedPush = Growable.push++tests :: TestTree+tests =+ testGroup+ "generic growable unrestricted vector"+ [ $( inspectTest+ ( (hasNoTypeClassesExcept 'boxedPush [''IP])+ { testName =+ Just "boxed push retains only CallStack dictionaries"+ }+ )+ )+ , $( inspectTest+ ( (hasNoTypeClassesExcept 'unboxedPush [''IP])+ { testName =+ Just "unboxed push retains only CallStack dictionaries"+ }+ )+ )+ , $( inspectTest+ ( (hasNoType 'boxedPush ''Unboxed.MVector)+ { testName =+ Just "boxed push has no unboxed backing"+ }+ )+ )+ , $( inspectTest+ ( (hasNoType 'unboxedPush ''BoxedMutable.MVector)+ { testName =+ Just "unboxed push has no boxed backing"+ }+ )+ )+ ]
+ test-inspection/PureBorrow/Inspection/MultiStoreScan.hs view
@@ -0,0 +1,122 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE NoImplicitPrelude #-}++module PureBorrow.Inspection.MultiStoreScan (+ tests,+ hotWorker,+ boxedContentProjection,+ unboxedContentProjection,+) where++import Control.Monad.Borrow.Pure.BO (BO, Mut)+import Control.Monad.Borrow.Pure.Experimental.Borrows (Aliases)+import Data.Int (Int64)+import Data.Vector qualified as V+import Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted qualified as Growable+import Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted qualified as Fixed+import Data.Vector.Unboxed qualified as U+import Prelude.Linear+import PureBorrow.Internal.Bench.MultiStoreScan+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.Inspection++hotWorker ::+ Int ->+ Int ->+ Int ->+ Int64 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector V.Vector (Int, Int)) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ BO+ α+ ( Ur Int64+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector V.Vector (Int, Int))+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ )+{-# NOINLINE hotWorker #-}+hotWorker = multiStoreScanPureBorrowWorker++boxedContentProjection ::+ Mut α (Growable.GrowableVector V.Vector (Int, Int)) %1 ->+ Mut α (Fixed.Vector V.Vector (Int, Int))+{-# NOINLINE boxedContentProjection #-}+boxedContentProjection = multiStoreScanBoxedContentProjection++unboxedContentProjection ::+ Mut α (Growable.GrowableVector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int)+{-# NOINLINE unboxedContentProjection #-}+unboxedContentProjection = multiStoreScanUnboxedContentProjection++tests :: TestTree+tests =+ testGroup+ "multi-store scan"+ [ $( inspectTest+ ( (hasNoTypeClasses 'hotWorker)+ { testName =+ Just "hot worker has no type-class dictionaries"+ }+ )+ )+ , $( inspectTest+ ( (hasNoType 'hotWorker ''Growable.GrowableVector)+ { testName =+ Just "hot worker has no growable header"+ }+ )+ )+ , $( inspectTest+ ( (hasNoType 'hotWorker ''Aliases)+ { testName =+ Just "hot worker has no plural-borrow bundle"+ }+ )+ )+ , $( inspectTest+ ( ( doesNotUseAnyOf+ 'hotWorker+ [ 'multiStoreScanPureBorrowWorker+ , 'Fixed.unsafeGet+ , 'Fixed.unsafeWrite+ , 'Growable.getContents+ ]+ )+ { testName =+ Just "hot worker contains no generic access or projection calls"+ }+ )+ )+ , $( inspectTest+ ( ( doesNotUseAnyOf+ 'boxedContentProjection+ [ 'multiStoreScanBoxedContentProjection+ , 'Growable.getContents+ ]+ )+ { testName =+ Just "boxed projection inlines getContents"+ }+ )+ )+ , $( inspectTest+ ( ( doesNotUseAnyOf+ 'unboxedContentProjection+ [ 'multiStoreScanUnboxedContentProjection+ , 'Growable.getContents+ ]+ )+ { testName =+ Just "unboxed projection inlines getContents"+ }+ )+ )+ ]
+ test-inspection/PureBorrow/Inspection/QSort.hs view
@@ -0,0 +1,75 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-orphans #-}++module PureBorrow.Inspection.QSort (+ tests,+ unboxedQsort,+) where++import Control.Concurrent.DivideConquer.Linear (qsort)+import Control.Monad.Borrow.Pure.BO (BO, Mut)+import Data.Vector.Generic qualified as GenericVector+import Data.Vector.Generic.Mutable qualified as Generic+import Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted qualified as Vector+import Data.Vector.Mutable qualified as Boxed+import Data.Vector.Unboxed qualified as Unboxed+import Prelude.Linear+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.Inspection++{-# SPECIALIZE qsort ::+ Word ->+ Mut α (Vector.Vector Unboxed.Vector Int) %1 ->+ BO α ()+ #-}++unboxedQsort ::+ Word ->+ Mut α (Vector.Vector Unboxed.Vector Int) %1 ->+ BO α ()+{-# NOINLINE unboxedQsort #-}+unboxedQsort = qsort++tests :: TestTree+tests =+ testGroup+ "qsort"+ [ $( inspectTest+ ( ( hasNoTypeClassesExcept+ 'unboxedQsort+ [''GenericVector.Vector, ''Ord]+ )+ { testName =+ Just+ "specialization retains only Vector and Ord dictionaries"+ }+ )+ )+ , $( inspectTest+ ( (hasNoType 'unboxedQsort ''Boxed.MVector)+ { testName =+ Just "root has no boxed-vector backing"+ }+ )+ )+ , $( inspectTest+ ( ( doesNotUseAnyOf+ 'unboxedQsort+ [ 'qsort+ , 'Vector.unsafeGet+ , 'Vector.unsafeSwap+ , 'Generic.unsafeRead+ , 'Generic.unsafeSwap+ , 'Generic.unsafeWrite+ , 'Generic.basicUnsafeRead+ , 'Generic.basicUnsafeWrite+ ]+ )+ { testName =+ Just "root contains no listed generic-vector operations"+ }+ )+ )+ ]
+ test-inspection/PureBorrow/Inspection/Sublifetime.hs view
@@ -0,0 +1,387 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE NoImplicitPrelude #-}+-- The set inspection-testing's own "Help, I am drowning in Core!" recommends.+-- A failing obligation prints the Core the way GHC would, and without these a+-- single 'BO' term is mostly coercions, uniques and IdInfo.+-- These stay here rather than in the component's @ghc-options@: `cabal check`+-- rejects `-d*` flags in a distributed package.+{-# OPTIONS_GHC -dsuppress-coercions #-}+{-# OPTIONS_GHC -dsuppress-idinfo #-}+{-# OPTIONS_GHC -dsuppress-module-prefixes #-}+{-# OPTIONS_GHC -dsuppress-type-applications #-}+{-# OPTIONS_GHC -dsuppress-type-signatures #-}+{-# OPTIONS_GHC -dsuppress-uniques #-}++{- |+Core-level obligations for the statically erased sublifetime delimiters.++With the @slow@ flag off, 'srunBO_' must compile to the identity, and 'srunBO' to nothing beyond handing the runtime-erased 'EndToken' to the 'After' the delimited action returned.+In particular neither may retain a runtime lifetime token, nor the 'Linearly' witness that 'newLifetime' consumes to produce one.+This has to hold through 'reborrowing'' and 'sharing'' too, which are the public combinators built on 'srunBO'.++The @+slow@ build restores the token-allocating implementations, so every obligation here is inverted there rather than dropped; see "PureBorrow.Inspection.Flags".+-}+module PureBorrow.Inspection.Sublifetime (+ tests,+ srunBOAt,+ endTokenAt,+ srunBO_At,+ idBOAt,+ reborrowingRefAt,+ reborrowingValueRefAt,+ bumpRefAt,+ bumpRefAfterAt,+ sharingRefAt,+ sharingValueRefAt,+ copyRefAt,+ copyRefAfterAt,+ locallyValueRefAt,+ reborrowingsValueRefAt,+ bumpBundleAt,+) where++import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.BO.Unsafe (reviveAlias)+import Control.Monad.Borrow.Pure.Experimental.Borrows (Aliases (..), Muts, reborrowings, reviveAliases)+import Control.Monad.Borrow.Pure.Experimental.Reborrowable (locally)+import Control.Monad.Borrow.Pure.Lifetime.Token.Unsafe (EndToken (..))+import Data.Ref.Linear (Ref)+import Data.Ref.Linear.Borrow qualified as Ref+import Prelude.Linear+import PureBorrow.Inspection.Flags (expectFailIfBecause, isSlowAPI)+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.Inspection+import Unsafe.Linear qualified as Unsafe+import Prelude qualified as NonLinear++{- | 'srunBO' at a concrete carrier.++The rank-2 argument is kept, so whatever survives here survives because the delimiter emitted it, not because a caller's action was specialised.++The @'Control.fmap' (+ 1)@ is not part of what is being measured; it is there to stop GHC from eta-reducing the probe.+Both @srunBOAt = srunBO@ and @srunBOAt bo = srunBO bo@ collapse to a bare reference to 'srunBO', which is an unsaturated call, so the @INLINE@ never fires.+Every obligation below would then hold vacuously, of a probe containing no delimiter at all.+-}+srunBOAt :: (forall α. BO (α /\ β) (After α Int)) %1 -> BO β Int+{-# NOINLINE srunBOAt #-}+srunBOAt bo = Control.fmap (+ 1) (srunBO bo)++{- | The specification 'srunBOAt' must meet: run the action, then apply the runtime-erased 'EndToken' to the 'After' it returned, under the same eta-reduction guard.++Note the signature: no rank-2 argument and no @/\\@ anywhere.+An equality with 'srunBOAt' therefore says the sublifetime left no residue at all.+-}+endTokenAt :: BO β (After γ Int) %1 -> BO β Int+{-# NOINLINE endTokenAt #-}+endTokenAt bo =+ Control.fmap (+ 1) Control.do+ after <- bo+ Control.pure $! withEnd UnsafeEnd after++-- | 'srunBO_' at a concrete carrier, saturated and rank-2 as above.+srunBO_At :: (forall α. BO (α /\ β) Int) %1 -> BO β Int+{-# NOINLINE srunBO_At #-}+srunBO_At bo = srunBO_ bo++-- | The specification 'srunBO_At' must meet: the identity.+idBOAt :: BO β Int %1 -> BO β Int+{-# NOINLINE idBOAt #-}+idBOAt bo = bo++{- | The body every mutable-side probe below delimits: read the reference, put back its successor, and report what was there.++Sharing it between the probes and their specification is what makes the equalities statements about the delimiter alone.+-}+bumpRef :: (α >= β) => Mut α (Ref Int) %1 -> BO β (Int, Mut α (Ref Int))+{-# INLINE bumpRef #-}+bumpRef = Ref.update \x -> dup2 x & \(seen, next) -> Control.pure (seen, next + 1)++{- | A client-level probe: 'reborrowing'' is one of the two public combinators built on 'srunBO', so the erasure has to reach through it too.++The restored borrow is dropped rather than returned, because a delimiter-free specification cannot both operate on the caller's 'Mut' and hand it back — handing it back is precisely the service the delimiter provides.+Returning it would compare a probe that gives back the box it was handed against a specification that rebuilds one, and the two differ in worker/wrapper shape for a reason that has nothing to do with the sublifetime.+-}+reborrowingRefAt :: Mut α (Ref Int) %1 -> BO α Int+{-# NOINLINE reborrowingRefAt #-}+reborrowingRefAt ref =+ reborrowing'+ ref+ ( \borrowed -> Control.do+ (old, spent) <- bumpRef borrowed+ spent `lseq` Control.pure (After old)+ )+ Control.<&> \(old, mut) -> mut `lseq` old++-- | 'reborrowing', the same probe with the result returned directly instead of 'After' the sublifetime.+reborrowingValueRefAt :: Mut α (Ref Int) %1 -> BO α Int+{-# NOINLINE reborrowingValueRefAt #-}+reborrowingValueRefAt ref =+ reborrowing+ ref+ ( \borrowed -> Control.do+ (old, spent) <- bumpRef borrowed+ spent `lseq` Control.pure old+ )+ Control.<&> \(old, mut) -> mut `lseq` old++{- | The specification 'reborrowingValueRefAt' must meet: 'bumpRef' on the caller's own borrow, plus the one 'reviveAlias' through which the delimiter restores that borrow.++Note the signature — no rank-2 argument and no @/\\@ — so an equality says the reborrow left no sublifetime residue at all, not merely that it allocated no token.+The 'reviveAlias' is not residue of the sublifetime; it is the delimiter's entire remaining cost, and it is load-bearing rather than incidental — see Note [Restoring a borrow must break its Core identity] in "Control.Monad.Borrow.Pure.BO.Internal".+Naming it here is what keeps these equalities honest: an earlier revision of this module asserted equality with the bare body, which said the delimiter compiled to nothing at all, and that was exactly the property that made it unsound.++The specification has to reach for 'Unsafe.toLinear' because it mentions the caller's borrow twice, once to bump through and once to restore, which is precisely what the delimiter does with 'Unsafe.toLinear2'.+-}+bumpRefAt :: Mut α (Ref Int) %1 -> BO α Int+{-# NOINLINE bumpRefAt #-}+bumpRefAt = Unsafe.toLinear \ref -> Control.do+ (old, spent) <- bumpRef ref+ spent `lseq` Control.do+ restored <- reviveAlias ref+ Control.pure (restored `lseq` old)++{- | The specification 'reborrowingRefAt' must meet: 'bumpRefAt', plus the application that hands the runtime-erased 'EndToken' to the 'After' the continuation returned.++That application is the one thing a delimiter taking an @'After' β r@ cannot drop, and 'srunBO' pays it too — see 'endTokenAt'.+-}+bumpRefAfterAt :: forall α. Mut α (Ref Int) %1 -> BO α Int+{-# NOINLINE bumpRefAfterAt #-}+bumpRefAfterAt = Unsafe.toLinear \ref -> Control.do+ (old, spent) <- bumpRef ref+ spent `lseq` Control.do+ restored <- reviveAlias ref+ Control.pure (restored `lseq` withEnd (UnsafeEnd @α) (After old))++{- | The generic path at 'Mut': 'locally' is what a caller reaches for when the borrow type is a parameter.++Its specification is 'bumpRefAt' — the same one 'reborrowingValueRefAt' meets — so the equality says the generic path costs exactly what the scalar delimiter costs, rather than routing through the @'After'@-returning 'Control.Monad.Borrow.Pure.Experimental.Reborrowable.locally'' and paying a closure for a result it hands back directly.+-}+locallyValueRefAt :: Mut α (Ref Int) %1 -> BO α Int+{-# NOINLINE locallyValueRefAt #-}+locallyValueRefAt ref =+ locally+ ref+ ( \borrowed -> Control.do+ (old, spent) <- bumpRef borrowed+ spent `lseq` Control.pure old+ )+ Control.<&> \(old, mut) -> mut `lseq` old++-- | The plural body, shared by the probe and its specification exactly as 'bumpRef' is on the scalar side.+bumpBundle ::+ (α >= β) =>+ Muts α '[Ref Int, Ref Int] %1 ->+ BO β (Int, Muts α '[Ref Int, Ref Int])+{-# INLINE bumpBundle #-}+bumpBundle (left :- right :- BNil) = Control.do+ (seenLeft, left) <- bumpRef left+ (seenRight, right) <- bumpRef right+ Control.pure (seenLeft + seenRight, left :- right :- BNil)++-- | 'reborrowings' over a two-member bundle, with the restored bundle dropped as the scalar probes drop their borrow.+reborrowingsValueRefAt :: Muts α '[Ref Int, Ref Int] %1 -> BO α Int+{-# NOINLINE reborrowingsValueRefAt #-}+reborrowingsValueRefAt bundle =+ reborrowings+ bundle+ ( \borrowed -> Control.do+ (old, spent) <- bumpBundle borrowed+ spent `lseq` Control.pure old+ )+ Control.<&> \(old, muts) -> muts `lseq` old++{- | The specification 'reborrowingsValueRefAt' must meet: the body on the caller's own bundle, plus the one 'reviveAliases' through which the delimiter restores it.++The plural counterpart of 'bumpRefAt', and it carries the same warning: naming the barrier is what stops this equality from asserting that the delimiter compiles to nothing, which is the property that made the scalar erasure unsound before @ebba572@.+-}+bumpBundleAt :: Muts α '[Ref Int, Ref Int] %1 -> BO α Int+{-# NOINLINE bumpBundleAt #-}+bumpBundleAt = Unsafe.toLinear \bundle -> Control.do+ (old, spent) <- bumpBundle bundle+ spent `lseq` Control.do+ restored <- reviveAliases bundle+ Control.pure (restored `lseq` old)++-- | The other 'srunBO' client, on the shared side, with the restored borrow dropped as above.+sharingRefAt :: Mut α (Ref Int) %1 -> BO α Int+{-# NOINLINE sharingRefAt #-}+sharingRefAt ref =+ sharing'+ ref+ ( \shared -> Control.do+ seen <- Ref.copyRef shared+ Control.pure (After seen)+ )+ Control.<&> \(seen, mut) -> mut `lseq` seen++-- | 'sharing', the same probe with the result returned directly instead of 'After' the sublifetime.+sharingValueRefAt :: Mut α (Ref Int) %1 -> BO α Int+{-# NOINLINE sharingValueRefAt #-}+sharingValueRefAt ref =+ sharing ref (\shared -> Ref.copyRef shared) Control.<&> \(seen, mut) -> mut `lseq` seen++{- | The specification 'sharingValueRefAt' must meet: read through the caller's own borrow, plus the restoring 'reviveAlias', with no sublifetime anywhere.++'Data.Ref.Linear.Borrow.copyRef' reads through a borrow of either kind, so the 'Mut' serves here where the probes pass a 'Share' narrowed to the sublifetime.+The 'Unsafe.toLinear' is there for the same reason as in 'bumpRefAt'.+-}+copyRefAt :: Mut α (Ref Int) %1 -> BO α Int+{-# NOINLINE copyRefAt #-}+copyRefAt = Unsafe.toLinear \ref -> Control.do+ seen <- Ref.copyRef ref+ restored <- reviveAlias ref+ Control.pure (restored `lseq` seen)++-- | The specification 'sharingRefAt' must meet: 'copyRefAt' plus the end-token application, as 'bumpRefAfterAt' is to 'bumpRefAt'.+copyRefAfterAt :: forall α. Mut α (Ref Int) %1 -> BO α Int+{-# NOINLINE copyRefAfterAt #-}+copyRefAfterAt = Unsafe.toLinear \ref -> Control.do+ seen <- Ref.copyRef ref+ restored <- reviveAlias ref+ Control.pure (restored `lseq` withEnd (UnsafeEnd @α) (After seen))++{- | Every obligation below describes the statically erased delimiters, so under @+slow@ — where the sublifetime is a genuine runtime token by construction — each one is expected to fail rather than to be skipped.+That inversion is what keeps them honest: an obligation that also holds of the allocating implementation is no evidence about this one, and turns the group red under @+slow@ until it is either sharpened or dropped.++Two plausible-looking obligations were dropped for exactly that reason.+@'hasNoType' \'srunBOAt ''SomeNow@ holds under both, because 'MkSomeNow' wraps a nullary 'Now' and case-of-known-constructor removes the box either way.+@'hasNoType' \'reborrowingRefAt ''Now@ likewise: through 'reborrowing'' the token itself is always erased, and what the allocating version actually leaves behind is the 'Linearly' that produced it.++The four borrow-scope equalities now pin the 'Control.Monad.Borrow.Pure.BO.Unsafe.reviveAlias' barrier as well, because their specifications name it.+That matters beyond bookkeeping: the runtime regressions in "Control.Monad.Borrow.Pure.BOSpec" observe a wrong /answer/, so on a compiler that stopped performing the merge they would go vacuously green rather than red, and these equalities would be the only thing left that notices the barrier being dropped.+-}+tests :: TestTree+tests =+ testGroup "sublifetime delimiting" $+ NonLinear.map+ ( expectFailIfBecause+ isSlowAPI+ "+slow restores the token-allocating sublifetime delimiters"+ )+ [ $( inspectTest+ ( ('srunBO_At ==- 'idBOAt)+ { testName =+ Just "srunBO_ is the identity"+ }+ )+ )+ , $( inspectTest+ ( ('srunBOAt ==- 'endTokenAt)+ { testName =+ Just "srunBO only supplies the erased end token"+ }+ )+ )+ , $( inspectTest+ ( (hasNoType 'srunBOAt ''Now)+ { testName =+ Just "srunBO allocates no lifetime token"+ }+ )+ )+ , $( inspectTest+ ( (hasNoType 'srunBOAt ''Linearly)+ { testName =+ Just "srunBO needs no linearity witness"+ }+ )+ )+ , $( inspectTest+ ( (doesNotUse 'srunBOAt 'askLinearly)+ { testName =+ Just "srunBO does not reach for the ambient Linearly"+ }+ )+ )+ , $( inspectTest+ ( (hasNoType 'reborrowingRefAt ''Linearly)+ { testName =+ Just "reborrowing' needs no linearity witness"+ }+ )+ )+ , $( inspectTest+ ( (doesNotUse 'reborrowingRefAt 'askLinearly)+ { testName =+ Just "reborrowing' does not reach for the ambient Linearly"+ }+ )+ )+ , $( inspectTest+ ( (hasNoType 'sharingRefAt ''Linearly)+ { testName =+ Just "sharing' needs no linearity witness"+ }+ )+ )+ , $( inspectTest+ ( (doesNotUse 'sharingRefAt 'askLinearly)+ { testName =+ Just "sharing' does not reach for the ambient Linearly"+ }+ )+ )+ , $( inspectTest+ ( ('reborrowingRefAt ==- 'bumpRefAfterAt)+ { testName =+ Just "reborrowing' only supplies the erased end token"+ }+ )+ )+ , $( inspectTest+ ( ('reborrowingValueRefAt ==- 'bumpRefAt)+ { testName =+ Just "reborrowing costs nothing over the update it delimits"+ }+ )+ )+ , $( inspectTest+ ( ('sharingRefAt ==- 'copyRefAfterAt)+ { testName =+ Just "sharing' only supplies the erased end token"+ }+ )+ )+ , $( inspectTest+ ( ('sharingValueRefAt ==- 'copyRefAt)+ { testName =+ Just "sharing costs nothing over the read it delimits"+ }+ )+ )+ , $( inspectTest+ ( ('locallyValueRefAt ==- 'bumpRefAt)+ { testName =+ Just "locally at Mut costs what the scalar delimiter costs"+ }+ )+ )+ , $( inspectTest+ ( ('reborrowingsValueRefAt ==- 'bumpBundleAt)+ { testName =+ Just "reborrowings costs nothing over the updates it delimits"+ }+ )+ )+ , $( inspectTest+ ( (hasNoType 'reborrowingsValueRefAt ''Linearly)+ { testName =+ Just "reborrowings needs no linearity witness"+ }+ )+ )+ , $( inspectTest+ ( (doesNotUse 'reborrowingsValueRefAt 'askLinearly)+ { testName =+ Just "reborrowings does not reach for the ambient Linearly"+ }+ )+ )+ ]
+ test-inspection/PureBorrow/Inspection/Worklist/Resume.hs view
@@ -0,0 +1,130 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE NoImplicitPrelude #-}++module PureBorrow.Inspection.Worklist.Resume (+ tests,+ resumeEdgeWorker,+ openOnceEdgeWorker,+) where++import Control.Monad.Borrow.Pure.BO (BO, Borrow, Mut)+import Control.Monad.Borrow.Pure.Experimental.Borrows (Aliases, reborrowings)+import Control.Monad.Borrow.Pure.Lifetime (type (>=))+import Data.Int (Int64)+import Data.Vector qualified as V+import Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted qualified as Growable+import Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted qualified as Fixed+import Data.Vector.Unboxed qualified as U+import Prelude.Linear+import PureBorrow.Internal.Bench.Worklist.Resume+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.Inspection++resumeEdgeWorker ::+ (α >= δ, β >= δ, γ >= δ) =>+ Int ->+ Int ->+ Int ->+ Int ->+ [Int] ->+ Int64 ->+ Borrow bk1 α (Fixed.Vector U.Vector Int) %1 ->+ Borrow bk2 β (Fixed.Vector V.Vector (Int, Int)) %1 ->+ Mut γ (Fixed.Vector U.Vector Int) %1 ->+ BO+ δ+ ( Ur (Int, Int, [Int], Int64)+ , Borrow bk1 α (Fixed.Vector U.Vector Int)+ , Borrow bk2 β (Fixed.Vector V.Vector (Int, Int))+ , Mut γ (Fixed.Vector U.Vector Int)+ )+{-# NOINLINE resumeEdgeWorker #-}+resumeEdgeWorker = worklistPureBorrowResumeEdgeWorker++openOnceEdgeWorker ::+ Int ->+ Int ->+ Int ->+ Int ->+ Int64 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector V.Vector (Int, Int)) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ Mut α (Fixed.Vector U.Vector Int) %1 ->+ BO+ α+ ( Ur (Int, Int, Int64)+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector V.Vector (Int, Int))+ , Mut α (Fixed.Vector U.Vector Int)+ , Mut α (Fixed.Vector U.Vector Int)+ )+{-# NOINLINE openOnceEdgeWorker #-}+openOnceEdgeWorker = worklistPureBorrowOpenOnceEdgeWorker++tests :: TestTree+tests =+ testGroup+ "worklist resume"+ [ $( inspectTest+ ( (hasNoTypeClasses 'resumeEdgeWorker)+ { testName =+ Just "resume edge worker has no type-class dictionaries"+ }+ )+ )+ , $( inspectTest+ ( (hasNoTypeClasses 'openOnceEdgeWorker)+ { testName =+ Just "open-once edge worker has no type-class dictionaries"+ }+ )+ )+ , $( inspectTest+ ( (hasNoType 'resumeEdgeWorker ''Growable.GrowableVector)+ { testName =+ Just "resume edge worker has no growable header"+ }+ )+ )+ , $( inspectTest+ ( (hasNoType 'resumeEdgeWorker ''Aliases)+ { testName =+ Just "resume edge worker has no plural-borrow bundle"+ }+ )+ )+ , $( inspectTest+ ( ( doesNotUseAnyOf+ 'resumeEdgeWorker+ [ 'worklistPureBorrowResumeEdgeWorker+ , 'Fixed.unsafeGet+ , 'Fixed.unsafeWrite+ , 'Growable.getContents+ , 'Growable.extend+ , 'reborrowings+ ]+ )+ { testName =+ Just "resume edge worker contains only specialized backing access"+ }+ )+ )+ , $( inspectTest+ ( ( doesNotUseAnyOf+ 'openOnceEdgeWorker+ [ 'worklistPureBorrowOpenOnceEdgeWorker+ , 'Fixed.unsafeGet+ , 'Fixed.unsafeWrite+ , 'Growable.getContents+ , 'Growable.extend+ , 'reborrowings+ ]+ )+ { testName =+ Just "open-once edge worker contains only specialized backing access"+ }+ )+ )+ ]
test/Control/Concurrent/DivideConquer/LinearSpec.hs view
@@ -12,48 +12,291 @@ ) where import Control.Concurrent.DivideConquer.Linear+import Control.Concurrent.DivideConquer.Linear qualified as DC import Control.Functor.Linear qualified as Control import Control.Monad.Borrow.Pure.BO import Control.Monad.Borrow.Pure.Copyable import Control.Syntax.DataFlow qualified as DataFlow+import Data.Complex (Complex (..), magnitude) import Data.List qualified as List import Data.List qualified as NonLinear import Data.Vector qualified as V+import Data.Vector.Generic qualified as G+import Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted qualified as UnrestrictedVector import Data.Vector.Mutable.Linear.Borrow qualified as VL+import Data.Vector.Unboxed qualified as U import Prelude.Linear+import System.Random (StdGen, mkStdGen) import Test.Falsify.Generator qualified as G import Test.Falsify.Predicate qualified as P import Test.Falsify.Range qualified as G import Test.Tasty (TestTree, testGroup) import Test.Tasty.Falsify (testProperty) import Test.Tasty.Falsify qualified as F-import Test.Tasty.HUnit (testCase, (@?=))+import Test.Tasty.HUnit (assertBool, testCase, (@?=)) import Prelude qualified as NonLinear test_qsort :: TestTree test_qsort = testGroup "qsort"- [ testCase "empty" do- qsortDCVec (V.empty @Int) @?= V.empty+ [ testProperty "empty" do+ seed <- F.gen $ G.int $ G.between (minBound, maxBound)+ F.assert $+ P.expect (V.empty) P..$ ("output", qsortUnrestrictedDCVec (mkStdGen seed) (V.empty @Int)) , testProperty "coincides with Data.List.sort on Ints" do+ seed <- F.gen $ G.int $ G.between (minBound, maxBound) xs <- F.gen $ G.list (G.between (1, 100)) $ G.int $ G.between (-100, 100) let v = V.fromList xs- sorted = qsortDCVec v+ unrestrictedBoxed =+ qsortUnrestrictedDCVec (mkStdGen seed) v+ unrestrictedUnboxed =+ qsortUnrestrictedDCVec+ (mkStdGen seed)+ (U.fromList xs) F.collect "length" [ceiling @_ @Int (fromIntegral @_ @Double (V.length v) / 10) * 10] F.collect "min" [NonLinear.minimum v `quot` 10 * 10] F.collect "max" [NonLinear.maximum v `quot` 10 * 10] F.collect "sorted" [V.and $ V.zipWith (NonLinear.<=) v (V.tail v)] F.info $ "input: " <> show xs F.assert $- P.expect (V.fromList $ List.sort xs)- P..$ ("output", sorted)+ P.expect+ ( List.sort xs+ , List.sort xs+ )+ P..$ ( "output"+ ,+ ( V.toList unrestrictedBoxed+ , U.toList unrestrictedUnboxed+ )+ ) ] -qsortDCVec :: (Ord a, Copyable a) => V.Vector a -> V.Vector a-qsortDCVec v = unur $ linearly \lin -> DataFlow.do- (l1, l2) <- dup lin- runBO l1 Control.do- (v, lend) <- borrowM (VL.fromVector v l2)- Control.void $ qsortDC 10 128 v- Control.pure $ After (VL.toVector (reclaim lend))+test_qsort_exported_modes :: TestTree+test_qsort_exported_modes =+ testCase "direct, naive, and work-sharing modes support boxed and unboxed backends" do+ let input = [5, 1, 4, 1, 3, 2] :: [Int]+ expected = List.sort input+ V.toList (qsortUnrestrictedDirectVec (V.fromList input)) @?= expected+ U.toList (qsortUnrestrictedDirectVec (U.fromList input)) @?= expected+ V.toList (qsortUnrestrictedNaiveVec (V.fromList input)) @?= expected+ U.toList (qsortUnrestrictedNaiveVec (U.fromList input)) @?= expected+ V.toList+ (qsortUnrestrictedDCVec (mkStdGen 42) (V.fromList input))+ @?= expected+ U.toList+ (qsortUnrestrictedDCVec (mkStdGen 42) (U.fromList input))+ @?= expected++qsortUnrestrictedDCVec ::+ (G.Vector v a, Ord a) =>+ StdGen ->+ v a ->+ v a+qsortUnrestrictedDCVec generator source =+ unur $+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ ( UnrestrictedVector.fromVector+ source+ ownerLinear+ )+ Control.void $+ DC.qsortDC+ generator+ 10+ 128+ vector+ pureAfter+ ( UnrestrictedVector.toVector+ (reclaim lend)+ )++qsortUnrestrictedDirectVec ::+ (G.Vector v a, Ord a) =>+ v a ->+ v a+qsortUnrestrictedDirectVec source =+ unur $+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (UnrestrictedVector.fromVector source ownerLinear)+ DC.qsort 0 vector+ pureAfter (UnrestrictedVector.toVector (reclaim lend))++qsortUnrestrictedNaiveVec ::+ (G.Vector v a, Ord a) =>+ v a ->+ v a+qsortUnrestrictedNaiveVec source =+ unur $+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (UnrestrictedVector.fromVector source ownerLinear)+ Control.void $+ naiveDivideAndConquer+ (DC.qsortDC' 4)+ vector+ pureAfter (UnrestrictedVector.toVector (reclaim lend))++test_fft :: TestTree+test_fft =+ testGroup+ "fft"+ [ testCase "agrees across schedulers and backends" do+ let input =+ V.generate 16 \index ->+ fromIntegral index :+ 0+ unboxedInput =+ U.generate 16 \index ->+ fromIntegral index :+ 0+ boxedSequential =+ fftUnrestrictedSequential input+ boxedNaive =+ fftUnrestrictedNaive input+ boxedWorkSharing =+ fftUnrestrictedWorkSharing input+ unboxedSequential =+ fftUnrestrictedSequential unboxedInput+ unboxedNaive =+ fftUnrestrictedNaive unboxedInput+ unboxedWorkSharing =+ fftUnrestrictedWorkSharing unboxedInput+ boxedNaive @?= boxedSequential+ boxedWorkSharing @?= boxedSequential+ unboxedNaive @?= unboxedSequential+ unboxedWorkSharing @?= unboxedSequential+ U.toList unboxedSequential @?= V.toList boxedSequential+ , testCase "matches a direct DFT for small power-of-two inputs" do+ NonLinear.mapM_ checkFftAgainstDft [1, 2, 4, 8, 16]+ ]++checkFftAgainstDft :: Int -> IO ()+checkFftAgainstDft length_ = do+ let values =+ [ fromIntegral ((index * 7 + 3) `NonLinear.mod` 11)+ :+ fromIntegral ((index * 5 + 1) `NonLinear.mod` 7)+ | index <- [0 .. length_ - 1]+ ]+ expected = directDft values+ boxed = V.toList (fftUnrestrictedSequential (V.fromList values))+ unboxed = U.toList (fftUnrestrictedSequential (U.fromList values))+ tolerance = 1e-9 * fromIntegral length_+ assertComplexListsClose tolerance expected boxed+ assertComplexListsClose tolerance expected unboxed++directDft :: [Complex Double] -> [Complex Double]+directDft values =+ [ NonLinear.sum+ [ value+ NonLinear.* (cos angle :+ sin angle)+ | (sampleIndex, value) <- NonLinear.zip [0 :: Int ..] values+ , let angle =+ 2+ * pi+ * fromIntegral outputIndex+ * fromIntegral sampleIndex+ / fromIntegral (NonLinear.length values)+ ]+ | outputIndex <- [0 .. NonLinear.length values - 1]+ ]++assertComplexListsClose ::+ Double ->+ [Complex Double] ->+ [Complex Double] ->+ IO ()+assertComplexListsClose tolerance expected actual =+ assertBool+ ( "expected "+ <> show expected+ <> ", but got "+ <> show actual+ )+ ( NonLinear.length expected+ == NonLinear.length actual+ && NonLinear.and+ ( NonLinear.zipWith+ ( \expectedValue actualValue ->+ magnitude+ (expectedValue NonLinear.- actualValue)+ <= tolerance+ )+ expected+ actual+ )+ )++fftUnrestrictedSequential ::+ (G.Vector v (Complex Double)) =>+ v (Complex Double) ->+ v (Complex Double)+fftUnrestrictedSequential source =+ unur $+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ ( UnrestrictedVector.fromVector+ source+ ownerLinear+ )+ Control.void $+ sequentialDivideAndConquer+ (DC.fftDC' 4)+ vector+ pureAfter+ ( UnrestrictedVector.toVector+ (reclaim lend)+ )++fftUnrestrictedNaive ::+ (G.Vector v (Complex Double)) =>+ v (Complex Double) ->+ v (Complex Double)+fftUnrestrictedNaive source =+ unur $+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (UnrestrictedVector.fromVector source ownerLinear)+ Control.void $+ naiveDivideAndConquer+ (DC.fftDC' 4)+ vector+ pureAfter (UnrestrictedVector.toVector (reclaim lend))++fftUnrestrictedWorkSharing ::+ (G.Vector v (Complex Double)) =>+ v (Complex Double) ->+ v (Complex Double)+fftUnrestrictedWorkSharing source =+ unur $+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ ( UnrestrictedVector.fromVector+ source+ ownerLinear+ )+ Control.void $+ DC.fftDC+ (mkStdGen 42)+ 2+ 4+ vector+ pureAfter+ ( UnrestrictedVector.toVector+ (reclaim lend)+ )
+ test/Control/Monad/Borrow/Pure/BOSpec.hs view
@@ -0,0 +1,570 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE LinearTypes #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}++module Control.Monad.Borrow.Pure.BOSpec (+ module Control.Monad.Borrow.Pure.BOSpec,+) where++import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure+import Control.Monad.Borrow.Pure.BO qualified as BO+import Control.Monad.Borrow.Pure.Experimental.Borrows qualified as Borrows+import Control.Monad.Borrow.Pure.Experimental.Reborrowable qualified as Reborrowable+import Control.Syntax.DataFlow qualified as DataFlow+import Data.Functor.Linear qualified as Data+import Data.HashMap.RobinHood.Mutable.Linear.Borrow qualified as HashMap+import Data.Ref.Linear qualified as Ref+import Data.Ref.Linear.Borrow qualified as RefBorrow+import Data.Type.Equality ((:~:))+import Data.Vector qualified as V+import Data.Vector.Mutable.Growable.Linear.Borrow qualified as Growable+import Data.Vector.Unboxed qualified as U+import Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow qualified as Unboxed+import Prelude.Linear (Ur (..), consume, dup, lseq, unur, ($), (&))+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.HUnit (testCase, (@?=))+import Unsafe.Linear qualified as Unsafe+import Prelude (Int, Maybe (..), otherwise, show, (+), (-), (<>), (>=))+import Prelude qualified as NonLinear++assocBorrowEqTypingCase ::+ forall (bk :: BO.BorrowKind) α β γ a.+ BO.Borrow bk ((α /\ β) /\ γ) a :~: BO.Borrow bk (α /\ (β /\ γ)) a+assocBorrowEqTypingCase = BO.assocBorrowEq @bk @α @β @γ @a++shortenShare :: (α >= β) => Share α a -> Share β a+shortenShare = subShare++addLinear :: Int %1 -> Int %1 -> Int+addLinear = Unsafe.toLinear2 (+)++test_instanceMethods :: TestTree+test_instanceMethods =+ testGroup+ "BO instance methods"+ [ testCase "linear liftA2" do+ linearly (\lin -> runBO_ lin (Control.liftA2 addLinear (Control.pure 20) (Control.pure 22))) @?= (42 :: Int)+ , testCase "non-linear liftA2" do+ linearly (\lin -> runBO_ lin (Data.liftA2 addLinear (Data.pure 20) (Data.pure 22))) @?= (42 :: Int)+ , testCase "linear sequencing" do+ linearly (\lin -> runBO_ lin (Control.pure () Control.>> Control.pure 42)) @?= (42 :: Int)+ ]++{-+Note [Observing a borrow scope's writes through the borrow it restores]+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+The kernels below share one shape: read the length through a borrow, hand that borrow to a scope whose body grows the vector, then read the length again through the borrow the scope handed back.+The second read must observe the growth.++They live here rather than with the growable vector's own tests because the growable vector is the instrument, not the subject.+What is under test is the delimiter -- see Note [Restoring a borrow must break its Core identity] in "Control.Monad.Borrow.Pure.BO.Internal" -- and a growable vector is simply the resource this package ships whose length and buffer are read outside the state token, so a delimiter that hands back the caller's own binder lets common-subexpression elimination serve the second read from the first.++Two details are load-bearing, and both were established by measuring variants against the pre-fix delimiter rather than by reasoning.++Both reads must end up inlined into one Core body: each kernel is therefore a @NOINLINE@ top-level binding with no helper standing between the borrow and either read.+Moving the pre-scope read behind its own @NOINLINE@ helper, or reaching the post-scope read through a recursive worker, makes the kernel pass on the broken delimiter -- vacuous, not merely weaker.+The scope must write the header; which projection each side reads does not matter, and @capacity@ before against @size@ after merges just as readily.++Two details that look load-bearing are not.+The reallocation matters only for the stale-/buffer/ symptom that 'elementAcrossReborrowing' and 'writeAcrossReborrowing' observe; the stale-/length/ symptom reproduces on a push that fits in the existing capacity.+And the @NOINLINE@ on each kernel is insurance rather than a requirement -- a plain CAF in the style of the other spec helpers reproduces too.++Most of these fail as wrong data rather than as a crash: at -O2 before the fix the post-scope read is deleted outright and the pre-scope length is returned in its place.+'writeAcrossReborrowing' is the exception, and is the one that reaches the shape which corrupted the heap downstream: it writes at an index the fresh length admits, through a header that still names the old, shorter buffer.+It fails here as a bounds error only because 'Growable.modify' is the checked operation; the unchecked 'Growable.unsafeSet' at the same index would run off the end of the allocation, which is what the downstream SIGSEGV was.++There is deliberately no 'sharing' or 'sharing_' kernel.+Those delimiters hand their callback a 'Share', nothing in the API writes a growable header through a 'Share', and the 'Mut' that would be needed was consumed by the delimiter -- so such a kernel would pass on the broken build and would be evidence of nothing.+They are fixed all the same, because the obligation belongs to the delimiter rather than to the set of writes that happen to be reachable today, but that half of the fix is prophylactic and is pinned by the Core obligations in @pure-borrow-inspection@ instead.+-}++seeded :: [Int]+seeded = [10, 20, 30]++appended :: Int -> [Int]+appended count = NonLinear.map (100 +) (NonLinear.enumFromTo 0 (count - 1))++-- | The model every kernel below is compared against: the length before, the length after, and the contents.+grown :: Int -> (Int, Int, [Int])+grown count = (3, 3 + count, seeded <> appended count)++pushRange ::+ (α >= β) =>+ Int ->+ Int ->+ Mut α (Growable.GrowableVector Int) %1 ->+ BO β (Mut α (Growable.GrowableVector Int))+{-# INLINE pushRange #-}+pushRange index count vector+ | index >= count = Control.pure vector+ | otherwise = Control.do+ vector <- Growable.push (100 + index) vector+ pushRange (index + 1) count vector++pushRangeUnboxed ::+ (α >= β) =>+ Int ->+ Int ->+ Mut α (Unboxed.GrowableVector Int) %1 ->+ BO β (Mut α (Unboxed.GrowableVector Int))+{-# INLINE pushRangeUnboxed #-}+pushRangeUnboxed index count vector+ | index >= count = Control.pure vector+ | otherwise = Control.do+ vector <- Unboxed.push (100 + index) vector+ pushRangeUnboxed (index + 1) count vector++-- | 'reborrowing': the scope returns a value alongside the restored borrow.+lengthAcrossReborrowing :: Int -> (Int, Int, [Int])+{-# NOINLINE lengthAcrossReborrowing #-}+lengthAcrossReborrowing count =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromVector (V.fromList seeded) ownerLinear)+ Growable.size vector & \(Ur before, vector) -> Control.do+ ((), vector) <-+ reborrowing vector \short ->+ consume Data.<$> pushRange 0 count short+ Growable.size vector & \(Ur after, vector) ->+ vector `lseq` pureAfter (report before after (reclaim lend))++-- | 'reborrowing'': the scope returns its value @After@ the sublifetime.+lengthAcrossReborrowingAfter :: Int -> (Int, Int, [Int])+{-# NOINLINE lengthAcrossReborrowingAfter #-}+lengthAcrossReborrowingAfter count =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromVector (V.fromList seeded) ownerLinear)+ Growable.size vector & \(Ur before, vector) -> Control.do+ ((), vector) <-+ reborrowing' vector \short ->+ (\short -> After (consume short)) Data.<$> pushRange 0 count short+ Growable.size vector & \(Ur after, vector) ->+ vector `lseq` pureAfter (report before after (reclaim lend))++-- | 'reborrowing_': the scope discards its value.+lengthAcrossReborrowingDiscarding :: Int -> (Int, Int, [Int])+{-# NOINLINE lengthAcrossReborrowingDiscarding #-}+lengthAcrossReborrowingDiscarding count =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromVector (V.fromList seeded) ownerLinear)+ Growable.size vector & \(Ur before, vector) -> Control.do+ vector <-+ reborrowing_ vector \short ->+ consume Data.<$> pushRange 0 count short+ Growable.size vector & \(Ur after, vector) ->+ vector `lseq` pureAfter (report before after (reclaim lend))++-- | The same as 'lengthAcrossReborrowing', on the unboxed growable vector.+lengthAcrossReborrowingUnboxed :: Int -> (Int, Int, [Int])+{-# NOINLINE lengthAcrossReborrowingUnboxed #-}+lengthAcrossReborrowingUnboxed count =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Unboxed.fromVector (U.fromList seeded) ownerLinear)+ Unboxed.size vector & \(Ur before, vector) -> Control.do+ ((), vector) <-+ reborrowing vector \short ->+ consume Data.<$> pushRangeUnboxed 0 count short+ Unboxed.size vector & \(Ur after, vector) ->+ vector `lseq` pureAfter (reportUnboxed before after (reclaim lend))++{- | 'Growable.withContent', which delimits a fixed view of the growable vector the same way.++Growth happens after the scope rather than inside it, because the fixed view the callback receives deliberately cannot grow.+Unlike its neighbours this kernel passes on the broken delimiter and cannot be made to fail: the callback receives a bare slice with no header to write, so nothing the scope does can make a merged header read stale.+It is kept as a forward-looking guard on a delimiter that shares the defective shape, not as regression coverage — 'Growable.withContent' restores through the barrier for the same prophylactic reason 'sharing' does.+-}+lengthAcrossContentScope :: Int -> (Int, Int, [Int])+{-# NOINLINE lengthAcrossContentScope #-}+lengthAcrossContentScope count =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromVector (V.fromList seeded) ownerLinear)+ Growable.size vector & \(Ur before, vector) -> Control.do+ ((), vector) <-+ Growable.withContent vector \contents ->+ Control.pure (consume contents)+ vector <- pushRange 0 count vector+ Growable.size vector & \(Ur after, vector) ->+ vector `lseq` pureAfter (report before after (reclaim lend))++{- | The same read-grow-read shape, repeated inside a recursive 'BO' loop.++The straight-line kernels above already reproduce, but a loop is what supplies the inlining depth and the loop-invariant read the downstream report describes, so this covers a read floated out of the loop as well as one merged with its neighbour.+Each round appends one element and records the length it observes afterwards, so a stale read is caught on the round that made it stale rather than only at the end.+-}+lengthsAcrossReborrowingLoop :: Int -> ([Int], [Int])+{-# NOINLINE lengthsAcrossReborrowingLoop #-}+lengthsAcrossReborrowingLoop rounds =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromVector (V.fromList seeded) ownerLinear)+ (Ur observed, vector) <- go 0 vector+ vector `lseq` pureAfter do+ Growable.toVector (reclaim lend) & \(Ur contents) ->+ Ur (observed, V.toList contents)+ where+ go ::+ forall α.+ Int ->+ Mut α (Growable.GrowableVector Int) %1 ->+ BO α (Ur [Int], Mut α (Growable.GrowableVector Int))+ {-# INLINE go #-}+ go index vector+ | index >= rounds = Control.pure (Ur [], vector)+ | otherwise = Control.do+ ((), vector) <-+ reborrowing vector \short ->+ consume Data.<$> Growable.push (100 + index) short+ Growable.size vector & \(Ur seen, vector) -> Control.do+ (Ur rest, vector) <- go (index + 1) vector+ Control.pure (Ur (seen : rest), vector)++{- | An element read through the restored borrow, rather than a length.++The kernels above all materialize their contents from @'reclaim' lend@, which is the owner and never the expression the delimiter resurrects, so their contents component is correct even on the broken build and only their lengths discriminate.+This one reads back through the borrow the scope handed over, so it is the kernel that actually witnesses a stale /buffer/: the push reallocates, the following 'Growable.modify' lands in the new allocation only, and a restored borrow still naming the old header reads the value from before the bump.+-}+elementAcrossReborrowing :: Int -> Int+{-# NOINLINE elementAcrossReborrowing #-}+elementAcrossReborrowing bumpBy =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromVector (V.fromList seeded) ownerLinear)+ ((), vector) <-+ reborrowing vector \short -> Control.do+ short <- Growable.push 100 short+ short <- Growable.modify 1 (addLinear bumpBy) short+ Control.pure (consume short)+ (Ur seen, vector) <- Growable.copyAtMut 1 vector+ vector `lseq` pureAfter (consume (reclaim lend) `lseq` Ur seen)++{- | A write through the restored borrow, at an index only the post-scope length admits.++This is the shape that corrupted the heap downstream.+'Growable.modify' is bounds-checked, so on the broken build it raises rather than scribbling past the allocation; the unchecked 'Growable.unsafeSet' at the same index is what the downstream solver was doing.+-}+writeAcrossReborrowing :: Int -> [Int]+{-# NOINLINE writeAcrossReborrowing #-}+writeAcrossReborrowing count =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromVector (V.fromList seeded) ownerLinear)+ -- The read before the scope is what the bounds check inside 'Growable.modify'+ -- gets merged with; without it there is nothing for the stale read to be+ -- served from, and this kernel passes even on the broken delimiter.+ Growable.size vector & \(Ur before, vector) -> Control.do+ vector <-+ reborrowing_ vector \short ->+ consume Data.<$> pushRange 0 count short+ vector <- Growable.modify (before + count - 1) (addLinear 1) vector+ vector `lseq` pureAfter do+ Growable.toVector (reclaim lend) & \(Ur contents) ->+ Ur (V.toList contents)++{- | The same shape on a plain 'Ref.Ref', whose reads bypass the state token in exactly the same way.++'RefBorrow.copyRef' bottoms out in 'Data.Ref.Linear.unsafeReadRef', so this is the smallest instrument that exhibits the defect at all — no vector involved.+The reads go through their own 'reborrowing' because 'RefBorrow.copyRef' consumes the borrow it reads.+-}+valueAcrossReborrowingRef :: Int -> (Int, Int)+{-# NOINLINE valueAcrossReborrowingRef #-}+valueAcrossReborrowingRef start =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (ref, lend) <- borrowM (Ref.new start ownerLinear)+ (before, ref) <- reborrowing ref RefBorrow.copyRef+ ((), ref) <-+ reborrowing ref \short ->+ consume Data.<$> RefBorrow.modify (addLinear 1) short+ (after, ref) <- reborrowing ref RefBorrow.copyRef+ ref `lseq` pureAfter (consume (Ref.free (reclaim lend)) `lseq` (before, after))++{- | The same shape on the Robin Hood table, which replaces its backing array on growth.++A stale read here is a stale /array/, not a stale count, so the lookup afterwards is the interesting assertion: on the broken delimiter it reports the key as absent after eight successful inserts.+-}+entriesAcrossReborrowingHashMap :: Int -> (Int, Int, Maybe Int)+{-# NOINLINE entriesAcrossReborrowingHashMap #-}+entriesAcrossReborrowingHashMap count =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (table, lend) <- borrowM (HashMap.empty @Int @Int 0 ownerLinear)+ (Ur before, table) <- HashMap.size table+ table <-+ reborrowing_ table \short ->+ consume Data.<$> insertRange 0 count short+ (Ur after, table) <- HashMap.size table+ (Ur found, table) <- HashMap.lookup 0 table+ table `lseq` pureAfter (consume (reclaim lend) `lseq` Ur (before, after, found))++{- | The plural delimiter, which restores a whole 'Borrows.Muts' bundle.++This kernel was written as a guard on an accident: before the plural scopes were erased, 'Borrows.reborrowings'' restored through 'reclaim'' inside an @After@ and so picked up 'withEnd'\'s @nospec@ barrier for free, and the comment here predicted that erasing the delimiter the way 'reborrowing'' was erased would remove it and turn this kernel red.+That erasure has since happened, and the kernel is green because the plural delimiters restore through 'Borrows.reviveAliases' rather than because anything is left of the accident.+It is now the runtime half of the plural obligation, paired with the Core equality in @pure-borrow-inspection@ that names the barrier.+-}+lengthAcrossPluralReborrowing :: Int -> (Int, Int, [Int])+{-# NOINLINE lengthAcrossPluralReborrowing #-}+lengthAcrossPluralReborrowing count =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromVector (V.fromList seeded) ownerLinear)+ Growable.size vector & \(Ur before, vector) -> Control.do+ bundle <-+ Borrows.reborrowings_+ (vector Borrows.:- Borrows.BNil)+ \(short Borrows.:- Borrows.BNil) ->+ consume Data.<$> pushRange 0 count short+ case bundle of+ vector Borrows.:- Borrows.BNil ->+ Growable.size vector & \(Ur after, vector) ->+ vector `lseq` pureAfter (report before after (reclaim lend))++{- | The plural delimiter over a bundle whose members are grown /unequally/.++'lengthAcrossPluralReborrowing' uses a one-member bundle, which cannot tell+apart a barrier on the spine from a barrier on each member: with one member the+two coincide. Here the first member is grown and the second is not, and both+are read back through the borrows the delimiter restored, so a barrier that+only broke the spine's identity would serve a stale length for the grown member.+-}+lengthsAcrossUnequalPluralReborrowing :: Int -> ((Int, Int), (Int, Int))+{-# NOINLINE lengthsAcrossUnequalPluralReborrowing #-}+lengthsAcrossUnequalPluralReborrowing count =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ (grownLinear, keptLinear) <- dup ownerLinear+ runBO runLinear Control.do+ (grown, grownLend) <-+ borrowM (Growable.fromVector (V.fromList seeded) grownLinear)+ (kept, keptLend) <-+ borrowM (Growable.fromVector (V.fromList seeded) keptLinear)+ Growable.size grown & \(Ur grownBefore, grown) ->+ Growable.size kept & \(Ur keptBefore, kept) -> Control.do+ bundle <-+ Borrows.reborrowings_+ (grown Borrows.:- kept Borrows.:- Borrows.BNil)+ \(shortGrown Borrows.:- shortKept Borrows.:- Borrows.BNil) ->+ shortKept `lseq` (consume Data.<$> pushRange 0 count shortGrown)+ case bundle of+ grown Borrows.:- kept Borrows.:- Borrows.BNil ->+ Growable.size grown & \(Ur grownAfter, grown) ->+ Growable.size kept & \(Ur keptAfter, kept) ->+ grown `lseq`+ kept `lseq`+ pureAfter+ ( consume (reclaim grownLend) `lseq`+ consume (reclaim keptLend) `lseq`+ Ur ((grownBefore, grownAfter), (keptBefore, keptAfter))+ )++{- | The result-returning plural delimiter.++Since the plural scopes were erased, @reborrowings@, @reborrowings'@ and+@reborrowings_@ are three independent delimiters rather than one defined+through another, so each needs its own kernel.+-}+lengthAcrossPluralReborrowingValue :: Int -> (Int, Int, [Int])+{-# NOINLINE lengthAcrossPluralReborrowingValue #-}+lengthAcrossPluralReborrowingValue count =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromVector (V.fromList seeded) ownerLinear)+ Growable.size vector & \(Ur before, vector) -> Control.do+ ((), bundle) <-+ Borrows.reborrowings+ (vector Borrows.:- Borrows.BNil)+ \(short Borrows.:- Borrows.BNil) ->+ consume Data.<$> pushRange 0 count short+ case bundle of+ vector Borrows.:- Borrows.BNil ->+ Growable.size vector & \(Ur after, vector) ->+ vector `lseq` pureAfter (report before after (reclaim lend))++-- | The finalizing plural delimiter, whose continuation returns its result @After@ the sublifetime.+lengthAcrossPluralReborrowingAfter :: Int -> (Int, Int, [Int])+{-# NOINLINE lengthAcrossPluralReborrowingAfter #-}+lengthAcrossPluralReborrowingAfter count =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromVector (V.fromList seeded) ownerLinear)+ Growable.size vector & \(Ur before, vector) -> Control.do+ ((), bundle) <-+ Borrows.reborrowings'+ (vector Borrows.:- Borrows.BNil)+ \(short Borrows.:- Borrows.BNil) -> Control.do+ short <- pushRange 0 count short+ Control.pure (Control.pure (consume short))+ case bundle of+ vector Borrows.:- Borrows.BNil ->+ Growable.size vector & \(Ur after, vector) ->+ vector `lseq` pureAfter (report before after (reclaim lend))++-- | The generic delimiter at @Muts@, which dispatches to the plural implementation.+lengthAcrossGenericLocallyPlural :: Int -> (Int, Int, [Int])+{-# NOINLINE lengthAcrossGenericLocallyPlural #-}+lengthAcrossGenericLocallyPlural count =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromVector (V.fromList seeded) ownerLinear)+ Growable.size vector & \(Ur before, vector) -> Control.do+ bundle <-+ Reborrowable.locally_+ (vector Borrows.:- Borrows.BNil)+ \(short Borrows.:- Borrows.BNil) ->+ consume Data.<$> pushRange 0 count short+ case bundle of+ vector Borrows.:- Borrows.BNil ->+ Growable.size vector & \(Ur after, vector) ->+ vector `lseq` pureAfter (report before after (reclaim lend))++{- | The generic delimiter, reached through 'Reborrowable' rather than by naming 'reborrowing_'.++@locally_@ at 'Mut' is now the erased scalar delimiter rather than a composition over the @After@-returning @locally'@, so it needs its own runtime coverage: a caller who writes generic code over the class must see the same ordering as one who names the combinator.+-}+lengthAcrossGenericLocally :: Int -> (Int, Int, [Int])+{-# NOINLINE lengthAcrossGenericLocally #-}+lengthAcrossGenericLocally count =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromVector (V.fromList seeded) ownerLinear)+ Growable.size vector & \(Ur before, vector) -> Control.do+ vector <-+ Reborrowable.locally_ vector \short ->+ consume Data.<$> pushRange 0 count short+ Growable.size vector & \(Ur after, vector) ->+ vector `lseq` pureAfter (report before after (reclaim lend))++insertRange ::+ Int ->+ Int ->+ Mut α (HashMap.HashMap Int Int) %1 ->+ BO α (Mut α (HashMap.HashMap Int Int))+{-# INLINE insertRange #-}+insertRange index count table+ | index >= count = Control.pure table+ | otherwise = Control.do+ (Ur _, table) <- HashMap.insert index (100 + index) table+ insertRange (index + 1) count table++report ::+ Int ->+ Int ->+ Growable.GrowableVector Int %1 ->+ Ur (Int, Int, [Int])+{-# INLINE report #-}+report before after vector =+ Growable.toVector vector & \(Ur contents) ->+ Ur (before, after, V.toList contents)++reportUnboxed ::+ Int ->+ Int ->+ Unboxed.GrowableVector Int %1 ->+ Ur (Int, Int, [Int])+{-# INLINE reportUnboxed #-}+reportUnboxed before after vector =+ Unboxed.toVector vector & \(Ur contents) ->+ Ur (before, after, U.toList contents)++{- | Regression coverage for the erased borrow scopes.++See Note [Observing a borrow scope's writes through the borrow it restores].+-}+test_scopeRestoresAUsableBorrow :: TestTree+test_scopeRestoresAUsableBorrow =+ testGroup+ "a borrow scope's writes are visible through the borrow it restores"+ [ testGroup "reborrowing" (cases lengthAcrossReborrowing)+ , testGroup "reborrowing'" (cases lengthAcrossReborrowingAfter)+ , testGroup "reborrowing_" (cases lengthAcrossReborrowingDiscarding)+ , testGroup "reborrowing, unboxed" (cases lengthAcrossReborrowingUnboxed)+ , testGroup "withContent" (cases lengthAcrossContentScope)+ , testCase "reborrowing in a loop" do+ lengthsAcrossReborrowingLoop 8+ @?= (NonLinear.map (4 +) (NonLinear.enumFromTo 0 7), seeded <> appended 8)+ , testGroup+ "an element read back through the restored borrow"+ [ testCase (show bumpBy <> " added") do+ elementAcrossReborrowing bumpBy @?= 20 + bumpBy+ | bumpBy <- [1, 5, 41]+ ]+ , testGroup+ "a write through the restored borrow lands in the grown buffer"+ [ testCase (show count <> " appended") do+ writeAcrossReborrowing count+ @?= seeded <> NonLinear.init (appended count) <> [100 + count]+ | count <- [1, 2, 5, 17]+ ]+ , testGroup+ "Ref"+ [ testCase (show start) do+ valueAcrossReborrowingRef start @?= (start, start + 1)+ | start <- [0, 1, 41]+ ]+ , testGroup "reborrowings_ (plural)" (cases lengthAcrossPluralReborrowing)+ , testGroup "reborrowings (plural)" (cases lengthAcrossPluralReborrowingValue)+ , testGroup "reborrowings' (plural)" (cases lengthAcrossPluralReborrowingAfter)+ , testGroup "locally_ (generic, Mut)" (cases lengthAcrossGenericLocally)+ , testGroup "locally_ (generic, Muts)" (cases lengthAcrossGenericLocallyPlural)+ , testGroup+ "a two-member bundle where only one member grows"+ [ testCase (show count <> " appended") do+ lengthsAcrossUnequalPluralReborrowing count+ @?= ((3, 3 + count), (3, 3))+ | count <- counts+ ]+ , testGroup+ "RobinHood HashMap"+ [ testCase (show count <> " inserted") do+ entriesAcrossReborrowingHashMap count @?= (0, count, Just 100)+ | count <- [1, 8, 33]+ ]+ ]+ where+ cases :: (Int -> (Int, Int, [Int])) -> [TestTree]+ cases kernel =+ [ testCase (show count <> " appended") (kernel count @?= grown count)+ | count <- counts+ ]+ counts :: [Int]+ counts = [1, 2, 3, 5, 17, 33, 1025]
+ test/Control/Monad/Borrow/Pure/CopyableSpec.hs view
@@ -0,0 +1,157 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE LinearTypes #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-orphans #-}++module Control.Monad.Borrow.Pure.CopyableSpec (+ module Control.Monad.Borrow.Pure.CopyableSpec,+) where++import Control.Monad.Borrow.Pure.BO (Mut, Share, linearly, runBO_)+import Control.Monad.Borrow.Pure.BO.Unsafe (Alias (UnsafeAlias))+import Control.Monad.Borrow.Pure.Clone (AsCopyable (AsCopyable), Clone (clone))+import Control.Monad.Borrow.Pure.Copyable+import Control.Monad.Borrow.Pure.Lifetime (Static)+import Data.Complex (Complex ((:+)))+import Data.IORef (IORef, modifyIORef', newIORef, readIORef, writeIORef)+import GHC.IO (unsafePerformIO)+import Prelude.Linear+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.HUnit+import Unsafe.Linear qualified as Unsafe+import Prelude qualified as NonLinear++data Tracked = Tracked !(IORef Int) !(IORef Bool) !Int++instance Copyable Tracked where+ copy =+ Unsafe.toLinear+ \(UnsafeAlias value@(Tracked copies retired _)) ->+ case unsafePerformIO do+ wasRetired <- readIORef retired+ if wasRetired+ then NonLinear.error "copy evaluated after retirement"+ else modifyIORef' copies NonLinear.succ of+ () -> value++instance Consumable Tracked where+ consume = Unsafe.toLinear \_ -> ()++instance Dupable Tracked where+ dup2 = Unsafe.toLinear \value -> (value, value)++instance Movable Tracked where+ move = Unsafe.toLinear Ur++instance Copyable1 [] where+ liftCopy = genericLiftCopy++trackedValue :: Tracked -> Int+trackedValue (Tracked _ _ value) = value++cloneTracked :: IORef Int -> IORef Bool -> Tracked+cloneTracked copies retired =+ linearly \linear ->+ case runBO_+ linear+ (clone (UnsafeAlias (AsCopyable (Tracked copies retired 10)))) of+ AsCopyable copied -> copied++test_copyStrictness :: TestTree+test_copyStrictness =+ testGroup+ "copy strictness"+ [ testCase "copyMut completes the copy before returning Ur" do+ copies <- newIORef 0+ retired <- newIORef False+ let source = Tracked copies retired 10+ !(Ur copied) =+ copyMut+ (UnsafeAlias source :: Mut Static Tracked)+ copyCount <- readIORef copies+ copyCount @?= 1+ writeIORef retired True+ trackedValue copied @?= 10+ , testCase "generic Copyable forces every copied field" do+ copies <- newIORef 0+ retired <- newIORef False+ let first = Tracked copies retired 10+ second = Tracked copies retired 20+ !(firstCopy, secondCopy) =+ copy+ ( UnsafeAlias (first, second) ::+ Share Static (Tracked, Tracked)+ )+ copyCount <- readIORef copies+ copyCount @?= 2+ writeIORef retired True+ (trackedValue firstCopy, trackedValue secondCopy) @?= (10, 20)+ , testCase "generic Copyable traverses recursive values before returning" do+ copies <- newIORef 0+ retired <- newIORef False+ let first = Tracked copies retired 10+ second = Tracked copies retired 20+ !copied =+ copy+ ( UnsafeAlias [first, second] ::+ Share Static [Tracked]+ )+ copyCount <- readIORef copies+ copyCount @?= 2+ writeIORef retired True+ NonLinear.map trackedValue copied @?= [10, 20]+ , testCase "Complex Copyable copies both components" do+ copies <- newIORef 0+ retired <- newIORef False+ let real = Tracked copies retired 10+ imaginary = Tracked copies retired 20+ !(realCopy :+ imaginaryCopy) =+ copy+ ( UnsafeAlias (real :+ imaginary) ::+ Share Static (Complex Tracked)+ )+ copyCount <- readIORef copies+ copyCount @?= 2+ writeIORef retired True+ (trackedValue realCopy, trackedValue imaginaryCopy) @?= (10, 20)+ , testCase "Complex Double moves without changing its numeric value" do+ case move (10 :+ 20 :: Complex Double) of+ Ur moved -> moved @?= (10 :+ 20)+ , testCase "Copyable1 forces every copied field" do+ copies <- newIORef 0+ retired <- newIORef False+ let real = Tracked copies retired 10+ imaginary = Tracked copies retired 20+ !(realCopy :+ imaginaryCopy) =+ copy1+ ( UnsafeAlias (real :+ imaginary) ::+ Share Static (Complex Tracked)+ )+ copyCount <- readIORef copies+ copyCount @?= 2+ writeIORef retired True+ (trackedValue realCopy, trackedValue imaginaryCopy) @?= (10, 20)+ , testCase "Copyable1 traverses recursive values before returning" do+ copies <- newIORef 0+ retired <- newIORef False+ let first = Tracked copies retired 10+ second = Tracked copies retired 20+ !copied =+ copy1+ ( UnsafeAlias [first, second] ::+ Share Static [Tracked]+ )+ copyCount <- readIORef copies+ copyCount @?= 2+ writeIORef retired True+ NonLinear.map trackedValue copied @?= [10, 20]+ , testCase "Clone through AsCopyable completes copying inside BO" do+ copies <- newIORef 0+ retired <- newIORef False+ let !copied = cloneTracked copies retired+ copyCount <- readIORef copies+ copyCount @?= 1+ writeIORef retired True+ trackedValue copied @?= 10+ ]
+ test/Control/Monad/Borrow/Pure/Experimental/Borrows/TypingCases.hs view
@@ -0,0 +1,77 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -O0 #-}+{-# OPTIONS_GHC -fdefer-type-errors -Wno-deferred-type-errors #-}++{- |+Cases that must /not/ typecheck: a lender may never be discarded in safe code.++A 'Lend' is the sole capability to recover the borrowed owner, so dropping one strands that owner forever.+The scalar 'Affine' instance excludes the @\'Lend@ alias kind by construction, and 'Lends' — a bundle of lenders — must behave identically.+Both forms are exercised here so the pair stays symmetric.+-}+module Control.Monad.Borrow.Pure.Experimental.Borrows.TypingCases (+ module Control.Monad.Borrow.Pure.Experimental.Borrows.TypingCases,+) where++import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.Affine+import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.Experimental.Borrows+import Prelude.Linear++-- | A bundle of lenders is not 'Affine', so it cannot be popped away.+badLendsAff :: Lends α xs %1 -> ()+badLendsAff bundle = pop (aff bundle)++-- | A bundle of lenders is not 'Consumable' either.+badLendsConsume :: Lends α xs %1 -> ()+badLendsConsume = consume++-- | The scalar reference: a single lender is not 'Affine'.+badLendAff :: Lend α a %1 -> ()+badLendAff lend = pop (aff lend)++-- | The scalar reference: a single lender is not 'Consumable'.+badLendConsume :: Lend α a %1 -> ()+badLendConsume = consume++badLendsAffCase :: Int+badLendsAffCase = discardingLenders badLendsAff++badLendsConsumeCase :: Int+badLendsConsumeCase = discardingLenders badLendsConsume++badLendAffCase :: Int+badLendAffCase = discardingLender badLendAff++badLendConsumeCase :: Int+badLendConsumeCase = discardingLender badLendConsume++{- |+Borrow an owner, throw away the mutable borrow, and hand the resulting one-element+'Lends' bundle to @abandon@ instead of reclaiming through it.+-}+discardingLenders :: (forall α. Lends α '[Int] %1 -> ()) -> Int+discardingLenders abandon =+ linearly \linear ->+ runBO_ linear Control.do+ (mut, lend) <- borrowM (42 :: Int)+ let+ !() = consume mut+ !() = abandon (lend :- BNil)+ Control.pure 0++-- | The scalar counterpart of 'discardingLenders'.+discardingLender :: (forall α. Lend α Int %1 -> ()) -> Int+discardingLender abandon =+ linearly \linear ->+ runBO_ linear Control.do+ (mut, lend) <- borrowM (42 :: Int)+ let+ !() = consume mut+ !() = abandon lend+ Control.pure 0
+ test/Control/Monad/Borrow/Pure/Experimental/BorrowsSpec.hs view
@@ -0,0 +1,90 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE LinearTypes #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE NoImplicitPrelude #-}++module Control.Monad.Borrow.Pure.Experimental.BorrowsSpec (+ module Control.Monad.Borrow.Pure.Experimental.BorrowsSpec,+) where++import Control.Exception qualified as Exception+import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure+import Control.Monad.Borrow.Pure.Experimental.Borrows+import Control.Monad.Borrow.Pure.Experimental.Borrows.TypingCases+import Data.List qualified as List+import Prelude.Linear+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.HUnit++-- | A bundle of mutable borrows stays discardable; the owners survive it.+discardMutsBundle :: (Int, Int)+discardMutsBundle =+ linearly \linear ->+ runBO linear Control.do+ (mutA, lendA) <- borrowM (1 :: Int)+ (mutB, lendB) <- borrowM (2 :: Int)+ let !() = consume (mutA :- mutB :- BNil)+ pureAfter (reclaim lendA, reclaim lendB)++-- | The same for a bundle of shared borrows.+discardSharesBundle :: Int+discardSharesBundle =+ linearly \linear ->+ runBO linear Control.do+ (mut, lend) <- borrowM (3 :: Int)+ share mut & \(Ur shared) -> Control.do+ let !() = consume (shared :- BNil)+ pureAfter (reclaim lend)++test_bundleDiscarding :: TestTree+test_bundleDiscarding =+ testGroup+ "alias bundle discarding"+ [ testCase "a Muts bundle can be discarded without losing its owners" do+ discardMutsBundle @?= (1, 2)+ , testCase "a Shares bundle can be discarded without losing its owner" do+ discardSharesBundle @?= 3+ ]++{- |+Discarding a lender is rejected for bundles exactly as it is for a scalar 'Lend'.++@Lends@ used to be unconditionally @Affine@, which let safe code abandon a whole bundle of lenders and strand the owners it held.+These cases freeze the plural and the scalar behaviours as identical.+-}+test_lenderDiscardingIsRejected :: TestTree+test_lenderDiscardingIsRejected =+ testGroup+ "typing boundaries"+ [ expectDeferredTypeError+ "a Lends bundle is not Affine"+ badLendsAffCase+ , expectDeferredTypeError+ "a Lends bundle is not Consumable"+ badLendsConsumeCase+ , expectDeferredTypeError+ "a scalar Lend is not Affine"+ badLendAffCase+ , expectDeferredTypeError+ "a scalar Lend is not Consumable"+ badLendConsumeCase+ ]+ where+ -- Every case fails the same way: the alias kind is 'Lend, and both the scalar and the plural instances accept only a 'Borrow kind.+ expectedFragments = ["Couldn't match type", "Lend", "Borrow"]+ describeFragments = List.intercalate ", " expectedFragments+ expectDeferredTypeError description value =+ testCase description do+ result <- Exception.try @Exception.SomeException (Exception.evaluate value)+ case result of+ Left exception ->+ let rendered = Exception.displayException exception+ in assertBool+ ("unexpected deferred type error: " <> rendered)+ (List.all (`List.isInfixOf` rendered) expectedFragments)+ Right _ ->+ assertFailure+ ("expected deferred type error containing " <> describeFragments)
+ test/Data/HashMap/RobinHood/Mutable/Linear/BorrowSpec.hs view
@@ -0,0 +1,237 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}++-- | Specs of the borrow-aware Robin Hood hash table.+module Data.HashMap.RobinHood.Mutable.Linear.BorrowSpec (+ module Data.HashMap.RobinHood.Mutable.Linear.BorrowSpec,+) where++import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure+import Control.Syntax.DataFlow qualified as DataFlow+import Data.HashMap.RobinHood.Mutable.Linear.Borrow qualified as HM+import Data.HashMap.Strict qualified as HMS+import Data.List qualified as NonLinear+import Data.List.NonEmpty (NonEmpty (..))+import Prelude.Linear+import Test.Falsify.Generator qualified as G+import Test.Falsify.Predicate ((.$))+import Test.Falsify.Predicate qualified as P+import Test.Falsify.Range qualified as G+import Test.Tasty (TestTree)+import Test.Tasty.Falsify (testProperty)+import Test.Tasty.Falsify qualified as F+import Test.Tasty.HUnit+import Prelude qualified as NonLinear++type Table = HM.HashMap String Int++{- | Run an action against a freshly borrowed table.++The action owns the mutable borrow and must consume it; the table itself is+reclaimed and released once the lifetime ends.+-}+withTable ::+ forall r.+ Int ->+ (forall α. Mut α Table %1 -> BO α (Ur r)) ->+ Ur r+withTable capacity action =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (table, lend) <- borrowM (HM.empty capacity ownerLinear)+ Ur result <- action table+ pureAfter (consume (reclaim lend) `lseq` Ur result)++-- | Materialize an owned table's entries, then release it.+drainTable :: Table %1 -> Ur [(String, Int)]+drainTable table =+ linearly \linear ->+ runBO linear Control.do+ (borrowed, lend) <- borrowM table+ (Ur entries, borrowed) <- HM.toList borrowed+ consume borrowed `lseq` pureAfter (consume (reclaim lend) `lseq` Ur entries)++insertAll :: [(String, Int)] -> Mut α Table %1 -> BO α (Mut α Table)+insertAll [] table = Control.pure table+insertAll ((k, v) : rest) table = Control.do+ (Ur _, table) <- HM.insert k v table+ insertAll rest table++sorted :: [(String, Int)] -> [(String, Int)]+sorted = NonLinear.sortOn NonLinear.fst++test_insertLookupDelete :: TestTree+test_insertLookupDelete = testCase "insert, lookup and delete through a mutable borrow" do+ let Ur (displaced, one, missing, present, absent, count, entries) =+ withTable 16 \table -> Control.do+ (Ur displacedOne, table) <- HM.insert "one" 1 table+ (Ur _, table) <- HM.insert "two" 2 table+ (Ur displacedTwo, table) <- HM.insert "two" 22 table+ (Ur one, table) <- HM.lookup "one" table+ (Ur missing, table) <- HM.lookup "three" table+ (Ur present, table) <- HM.member "two" table+ (Ur deleted, table) <- HM.delete "one" table+ (Ur absent, table) <- HM.member "one" table+ (Ur count, table) <- HM.size table+ (Ur entries, table) <- HM.toList table+ consume table `lseq`+ Control.pure+ (Ur ((displacedOne, displacedTwo, deleted), one, missing, present, absent, count, entries))+ displaced @?= (Nothing, Just 2, Just 1)+ one @?= Just 1+ missing @?= Nothing+ present @?= True+ absent @?= False+ count @?= 1+ entries @?= [("two", 22)]++test_growthIsVisibleThroughTheBorrow :: TestTree+test_growthIsVisibleThroughTheBorrow =+ testCase "a growth taken while borrowed is written back to the reference" do+ -- The underlying owned table replaces its backing array when it grows, so+ -- this is the regression test for the 'Ref' indirection: were the grown+ -- table not written back, every entry inserted after the first growth+ -- would be lost.+ let pairs = [(NonLinear.show i, i) | i <- [1 .. 512 :: Int]]+ Ur (count, entries) = withTable 4 \table -> Control.do+ table <- insertAll pairs table+ (Ur count, table) <- HM.size table+ (Ur entries, table) <- HM.toList table+ consume table `lseq` Control.pure (Ur (count, entries))+ count @?= NonLinear.length pairs+ sorted entries @?= sorted pairs++test_preparedInsertion :: TestTree+test_preparedInsertion = testCase "suspended lookups resume as insertions" do+ let expected = [(NonLinear.show i, i) | i <- [1 .. 256 :: Int]]+ Ur (found, entries) = withTable 4 \table -> Control.do+ table <- go 1 table+ (Ur found, table) <- HM.lookup "128" table+ (Ur entries, table) <- HM.toList table+ consume table `lseq` Control.pure (Ur (found, entries))+ found @?= Just 128+ sorted entries @?= sorted expected+ where+ go :: Int -> Mut α Table %1 -> BO α (Mut α Table)+ go i table+ | i NonLinear.> 256 = Control.pure table+ | otherwise = Control.do+ (Ur plan, table) <- HM.lookupForInsert (NonLinear.show i) table+ table <- case plan of+ Left _ -> Control.pure table+ Right plan -> HM.unsafeInsertPrepared plan i table+ go (i + 1) table++test_alter :: TestTree+test_alter = testCase "alter inserts, updates and deletes" do+ let Ur (inserted, updated, deleted) = withTable 16 \table -> Control.do+ table <- HM.alter (\_ -> Just 1) "key" table+ (Ur inserted, table) <- HM.lookup "key" table+ table <- HM.alter (NonLinear.fmap (NonLinear.+ 41)) "key" table+ (Ur updated, table) <- HM.lookup "key" table+ table <- HM.alter (\_ -> Nothing) "key" table+ (Ur deleted, table) <- HM.lookup "key" table+ consume table `lseq` Control.pure (Ur (inserted, updated, deleted))+ inserted @?= Just 1+ updated @?= Just 42+ deleted @?= Nothing++test_alterF :: TestTree+test_alterF = testCase "alterF may inspect the table it is altering" do+ let Ur (observed, final) = withTable 16 \table -> Control.do+ table <- HM.alter (\_ -> Just 7) "key" table+ table <-+ HM.alterF+ (\seen -> Control.pure (Ur (NonLinear.fmap (NonLinear.* 2) seen)))+ "key"+ table+ (Ur final, table) <- HM.lookup "key" table+ (Ur observed, table) <- HM.size table+ consume table `lseq` Control.pure (Ur (observed, final))+ observed @?= 1+ final @?= Just 14++test_takeLeavesAnEmptyTable :: TestTree+test_takeLeavesAnEmptyTable = testCase "take empties the borrowed table" do+ let pairs = [("a", 1), ("b", 2), ("c", 3 :: Int)]+ Ur (taken, remaining) = withTable 16 \table -> Control.do+ table <- insertAll pairs table+ (old, table) <- HM.take table+ (Ur remaining, table) <- HM.toList table+ drainTable old & \(Ur taken) ->+ consume table `lseq` Control.pure (Ur (taken, remaining))+ sorted taken @?= sorted pairs+ remaining @?= []++test_swap :: TestTree+test_swap = testCase "swap exchanges the borrowed table with an owned one" do+ let Ur (displaced, remaining) = withTable 16 \table -> Control.do+ table <- insertAll [("a", 1), ("b", 2)] table+ replacement <- asksLinearlyM \lin -> Control.pure (HM.fromList [("z", 26)] lin)+ (old, table) <- HM.swap replacement table+ (Ur remaining, table) <- HM.toList table+ drainTable old & \(Ur displaced) ->+ consume table `lseq` Control.pure (Ur (displaced, remaining))+ sorted displaced @?= [("a", 1), ("b", 2)]+ remaining @?= [("z", 26)]++test_extend :: TestTree+test_extend = testCase "extend merges an owned table into a borrowed one" do+ let Ur entries = withTable 16 \table -> Control.do+ table <- insertAll [("a", 1), ("b", 2)] table+ donor <- asksLinearlyM \lin -> Control.pure (HM.fromList [("b", 20), ("c", 3)] lin)+ table <- HM.extend donor table+ (Ur entries, table) <- HM.toList table+ consume table `lseq` Control.pure (Ur entries)+ sorted entries @?= [("a", 1), ("b", 20), ("c", 3)]++test_union :: TestTree+test_union = testCase "union of two owned tables" do+ let Ur entries = linearly \linear -> DataFlow.do+ (linLeft, linRight) <- dup linear+ drainTable+ ( HM.union+ (HM.fromList [("a", 1), ("b", 2)] linLeft)+ (HM.fromList [("b", 20), ("c", 3)] linRight)+ )+ sorted entries @?= [("a", 1), ("b", 20), ("c", 3)]++-- | Mutations through a borrow agree with @unordered-containers@.+test_randomMutations :: TestTree+test_randomMutations = testProperty "random mutations agree with the oracle" do+ program <-+ F.gen $+ G.list (G.between (1, 256)) do+ key <- G.list (G.between (1, 4)) (G.elem ('a' :| "bcd"))+ value <- G.int (G.between (-10, 10))+ deleting <- G.bool NonLinear.False+ NonLinear.pure (if deleting then Left key else Right (key, value))+ let expected = NonLinear.foldl' step HMS.empty program+ Ur entries = withTable 8 \table -> Control.do+ table <- apply program table+ (Ur entries, table) <- HM.toList table+ consume table `lseq` Control.pure (Ur entries)+ F.assert $ P.expect expected .$ ("final table", HMS.fromList entries)+ where+ step oracle = \case+ Left key -> HMS.delete key oracle+ Right (key, value) -> HMS.insert key value oracle++ apply ::+ [Either String (String, Int)] ->+ Mut α Table %1 ->+ BO α (Mut α Table)+ apply [] table = Control.pure table+ apply (instruction : rest) table = Control.do+ table <- case instruction of+ Left key -> Control.fmap (\(Ur _, table) -> table) (HM.delete key table)+ Right (key, value) -> Control.fmap (\(Ur _, table) -> table) (HM.insert key value table)+ apply rest table
+ test/Data/HashMap/RobinHood/Mutable/LinearSpec.hs view
@@ -0,0 +1,231 @@+{-# LANGUAGE ApplicativeDo #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE RecordWildCards #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}++-- | Specs of the owned Robin Hood hash table.+module Data.HashMap.RobinHood.Mutable.LinearSpec (+ module Data.HashMap.RobinHood.Mutable.LinearSpec,+) where++import Control.Functor.Linear qualified as Lin+import Control.Monad (forM)+import Control.Monad.Borrow.Pure (linearly)+import Data.Bifunctor.Linear qualified as BiL+import Data.Foldable (forM_)+import Data.HashMap.RobinHood.Mutable.Linear qualified as LHM+import Data.HashMap.RobinHood.Mutable.LinearSpec.Cases+import Data.HashMap.Strict qualified as HMS+import Data.HashSet qualified as HashSet+import Data.Hashable (Hashable (..))+import Data.List (sortOn)+import Data.List.NonEmpty (NonEmpty (..))+import Data.List.NonEmpty qualified as NE+import Data.Map.Strict qualified as Map+import Data.Maybe (isJust, isNothing)+import Data.Unrestricted.Linear (UrT (..), runUrT)+import Data.Unrestricted.Linear qualified as Ur+import GHC.Generics (Generic)+import Prelude.Linear (Ur (..), unur, (&))+import Prelude.Linear qualified as PL+import Test.Falsify.Generator qualified as F+import Test.Falsify.Predicate ((.$))+import Test.Falsify.Predicate qualified as P+import Test.Falsify.Range qualified as F+import Test.Tasty+import Test.Tasty.Falsify (Property, testProperty)+import Test.Tasty.Falsify qualified as F+import Test.Tasty.HUnit++test_case1 :: TestTree+test_case1 = testCase "HashMap case 1" do+ let Ur Case1Result {..} = withNewEmptyHashMap case1+ initOneResident @?= Nothing+ newOneResident @?= Just 1+ initTwoResident @?= Nothing+ deletedOneResident @?= Just 1+ finalResult @?= [("Two", 2)]++test_case2 :: TestTree+test_case2 = testCase "HashMap case 2" do+ let Ur finalResult = withNewEmptyHashMap case2+ let resl = Map.fromList finalResult+ expected = Map.fromList [(show i, i) | i <- [1 .. 15] <> [129 .. 256]]+ Map.size resl @?= Map.size expected+ resl @?= expected++test_case3 :: TestTree+test_case3 = testCase "HashMap case 3" do+ let Ur Case3Result {..} = withNewEmptyHashMap case3+ iniOneReside @?= iniOneResideExpected+ oneBeforeBulkInsert @?= oneBeforeBulkInsertExpected+ oneAfterBulkInsert @?= oneAfterBulkInsertExpected+ sixteenAfterBulkInsert @?= sixteenAfterBulkInsertExpected+ sixteenAfterBulkDelete @?= sixteenAfterBulkDeleteExpected+ poppedSixteen @?= poppedSixteenExpected+ finalSixteen @?= finalSixteenExpected+ Map.size finalResult @?= Map.size expectedResult+ finalResult @?= expectedResult++test_case4 :: TestTree+test_case4 = testCase "HashMap case 4" do+ let Ur (ccVal, lst) = withNewEmptyHashMap \hm ->+ LHM.insert "aa" (-10 :: Int) hm & \(Ur _, hm) ->+ LHM.insert "cc" (-10) hm & \(Ur _, hm) ->+ LHM.lookup "cc" hm & \(Ur valCc, hm) ->+ LHM.toList hm & \(Ur lst) ->+ Ur (valCc, lst)+ sortOn fst lst @?= sortOn fst [("aa", -10), ("cc", -10)]+ ccVal @?= Just (-10)++test_case5 :: TestTree+test_case5 = testCase "HashMap case 5" do+ let input = [("abcba", 1 :: Int), ("bacba", 2), ("aaba", 3), ("baa", 4)]+ let Ur (abcbaVal, lst) = withNewEmptyHashMap \hm ->+ LHM.insertMany input hm & \hm ->+ LHM.lookup "abcba" hm & \(Ur valCc, hm) ->+ LHM.toList hm & \(Ur lst) ->+ Ur (valCc, lst)+ sortOn fst lst @?= sortOn fst input+ abcbaVal @?= lookup "abcba" input++test_preparedInsertion :: TestTree+test_preparedInsertion = testCase "prepared insertion preserves lookup and growth" do+ let expected = Map.fromList [(show i, i) | i <- [1 .. 256 :: Int]]+ Ur (found, actual) = withNewEmptyHashMap (go 1)+ found @?= Just 128+ Map.fromList actual @?= expected+ where+ go :: Int -> LHM.HashMap String Int %1 -> Ur (Maybe Int, [(String, Int)])+ go i hm+ | i <= 256 = case LHM.lookupForInsert (show i) hm of+ (Ur (Left old), hm) -> go (i + 1) (LHM.alter (const (Just old)) (show i) hm)+ (Ur (Right plan), hm) -> go (i + 1) (LHM.unsafeInsertPrepared plan i hm)+ | otherwise = case LHM.lookupForInsert "128" hm of+ (Ur (Left value), hm) -> case LHM.toList hm of+ Ur entries -> Ur (Just value, entries)+ (Ur (Right plan), hm) -> case LHM.toList (LHM.unsafeInsertPrepared plan 0 hm) of+ Ur entries -> Ur (Nothing, entries)++-- | A key whose every value lands in the same bucket.+newtype Colliding = Colliding Int+ deriving (Show, Eq, Ord)++instance Hashable Colliding where+ hashWithSalt _ _ = 0++test_preparedInsertionCollisions :: TestTree+test_preparedInsertionCollisions = testCase "prepared insertion preserves Robin Hood collisions" do+ let expected = Map.fromList [(Colliding i, i) | i <- [1 .. 64]]+ Ur actual = linearly \lin -> go 1 (LHM.new 1 lin)+ Map.fromList actual @?= expected+ where+ go :: Int -> LHM.HashMap Colliding Int %1 -> Ur [(Colliding, Int)]+ go i hm+ | i <= 64 = case LHM.lookupForInsert (Colliding i) hm of+ (Ur (Left old), hm) -> go (i + 1) (LHM.alter (const (Just old)) (Colliding i) hm)+ (Ur (Right plan), hm) -> go (i + 1) (LHM.unsafeInsertPrepared plan i hm)+ | otherwise = LHM.toList hm++data Instruction+ = Inserts (NonEmpty (String, Int))+ | Insert String Int+ | Delete String+ deriving (Show, Eq, Ord, Generic)++instructionG :: F.Gen Instruction+instructionG =+ F.oneof $+ NE.fromList+ [ Insert+ <$> readableStringG+ <*> valG+ , Inserts . NE.fromList+ <$> F.list (F.between (1, 128)) ((,) <$> readableStringG <*> valG)+ ]++readableStringG :: F.Gen String+readableStringG = F.list (F.between (1, 5)) (F.elem $ 'a' :| "bc")++valG :: F.Gen Int+valG = F.int (F.between (-10, 10))++type Semantics = HMS.HashMap String Int++test_randomInstructions :: TestTree+test_randomInstructions = testProperty "random instructions" do+ program <- F.gen $ F.list (F.between (1, 256)) instructionG+ testInstructions program++-- | Run a random program against @unordered-containers@ as the oracle.+testInstructions :: [Instruction] -> Property ()+testInstructions instrs = do+ unur PL.$ linearly \lin ->+ go HMS.empty (LHM.new 16 lin) (pure ()) instrs+ where+ go ::+ Semantics ->+ LHM.HashMap String Int %1 ->+ Property () ->+ [Instruction] ->+ Ur (Property ())+ go sem !hm !act [] = case LHM.toList hm of+ Ur lst ->+ Ur $+ act+ *> F.assert (P.expect sem .$ ("Final dictionary", HMS.fromList lst))+ go sem !hm !act (instr : rest) = case instr of+ Insert k v ->+ let expectOld = HMS.lookup k sem+ sem' = HMS.insert k v sem+ in LHM.insert k v hm & \(Ur oldVal, hm) ->+ LHM.lookup k hm & \(Ur newVal, hm) ->+ let checks = do+ F.collect "colliding insertion" [isJust expectOld]+ F.assert $+ P.expect expectOld+ .$ ("before insert " <> show (k, v), oldVal)+ F.assert $+ P.expect (Just v)+ .$ ("after insert " <> show (k, v), newVal)+ in go sem' hm (act *> checks) rest+ Inserts kvs ->+ let sem' = foldl' (flip $ uncurry HMS.insert) sem kvs+ overlaps = map (\(k, _) -> HMS.member k sem) $ NE.toList kvs+ targKeys = HashSet.toList $ HashSet.fromList $ map fst $ NE.toList kvs+ in LHM.insertMany (NE.toList kvs) hm & \hm ->+ Lin.runState+ ( runUrT PL.$ forM targKeys \k -> UrT $ Lin.state \hm ->+ BiL.first (Ur.lift (k,)) (LHM.lookup k hm)+ )+ hm+ & \(Ur lookups, hm) ->+ let checks = do+ F.label "bulk insertion size" [showSize $ NE.length kvs]+ F.collect "colliding insertion" overlaps+ forM_ lookups \(k, newVals) -> do+ F.assert $+ P.expect (HMS.lookup k sem')+ .$ ("after bulk insert for " <> show k, newVals)+ in go sem' hm (act *> checks) rest+ Delete k ->+ let expectOld = HMS.lookup k sem+ sem' = HMS.delete k sem+ in LHM.delete k hm & \(Ur oldVal, hm) ->+ LHM.lookup k hm & \(Ur newVal, hm) ->+ let checks = do+ F.collect "vacuous deletion" [isNothing expectOld]+ F.assert $+ P.expect expectOld+ .$ ("value before deletion " <> show k, oldVal)+ F.assert $+ P.expect Nothing+ .$ ("value after deletion " <> show k, newVal)+ in go sem' hm (act *> checks) rest++showSize :: Int -> String+showSize 0 = "0"+showSize i =+ let lb = floor @_ @Int (fromIntegral @_ @Double i / 10) * 10+ ub = lb + 10+ in "[" <> show lb <> ", " <> show ub <> ")"
+ test/Data/HashMap/RobinHood/Mutable/LinearSpec/Cases.hs view
@@ -0,0 +1,113 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE NoFieldSelectors #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}++-- | Scripted mutation sequences shared by the Robin Hood hash table specs.+module Data.HashMap.RobinHood.Mutable.LinearSpec.Cases (+ module Data.HashMap.RobinHood.Mutable.LinearSpec.Cases,+) where++import Control.Monad.Borrow.Pure (linearly)+import Control.Syntax.DataFlow qualified as DataFlow+import Data.HashMap.RobinHood.Mutable.Linear as HM+import Data.Map.Strict qualified as Map+import Data.Unrestricted.Linear qualified as Ur+import Prelude.Linear hiding (lookup)+import Prelude qualified as NonLinear++withNewEmptyHashMap :: (HashMap k v %1 -> Ur r) %1 -> Ur r+withNewEmptyHashMap f = linearly $ f . new 16++data Case1Result = Case1Result+ { initOneResident :: Maybe Int+ , newOneResident :: Maybe Int+ , initTwoResident :: Maybe Int+ , deletedOneResident :: Maybe Int+ , finalResult :: [(String, Int)]+ }+ deriving (Show, NonLinear.Eq, NonLinear.Ord)++-- | Insert, look up, insert again and delete, then materialize.+case1 :: HashMap String Int %1 -> Ur Case1Result+case1 hm =+ HM.insert "One" 1 hm & \(Ur initOneResident, hm) ->+ HM.lookup "One" hm & \(Ur newOneResident, hm) ->+ HM.insert "Two" 2 hm & \(Ur initTwoResident, hm) ->+ HM.delete "One" hm & \(Ur deletedOneResident, hm) ->+ HM.toList hm & \(Ur finalResult) ->+ Ur Case1Result {..}++-- | Bulk insert, bulk delete across a growth, then bulk insert again.+case2 :: HashMap String Int %1 -> Ur [(String, Int)]+case2 hm = DataFlow.do+ hm <- HM.insertMany [(show i, i) | i <- [1 .. 128]] hm+ hm <-+ foldl'+ (\hm i -> move i & \(Ur i) -> uncurry lseq (HM.delete (show i) hm))+ hm+ [16 .. 256 :: Int]+ hm <- HM.insertMany [(show i, i) | i <- [129 .. 256]] hm+ HM.toList hm++data Case3Result = Case3Result+ { iniOneReside :: Maybe Int+ , iniOneResideExpected :: Maybe Int+ , oneBeforeBulkInsert :: Maybe Int+ , oneBeforeBulkInsertExpected :: Maybe Int+ , oneAfterBulkInsert :: Maybe Int+ , oneAfterBulkInsertExpected :: Maybe Int+ , sixteenAfterBulkInsert :: Maybe Int+ , sixteenAfterBulkInsertExpected :: Maybe Int+ , sixteenAfterBulkDelete :: Maybe Int+ , sixteenAfterBulkDeleteExpected :: Maybe Int+ , poppedSixteen :: Maybe Int+ , poppedSixteenExpected :: Maybe Int+ , finalSixteen :: Maybe Int+ , finalSixteenExpected :: Maybe Int+ , finalResult :: Map.Map String Int+ , expectedResult :: Map.Map String Int+ }+ deriving (Show, NonLinear.Eq, NonLinear.Ord)++-- | Interleave lookups with the bulk mutations of 'case2'.++{- NOTE: written with @case@ rather than as @DataFlow.do@, unlike 'case2'.++Every step here binds through a pattern that mentions 'Ur', and 'Ur' is+declared in GADT syntax, so GHC 9.10 rates such a pattern refutable in a @do@+statement and demands a `fail` that "Control.Syntax.DataFlow" does not+provide. GHC 9.12 accepts the same code. A @case@ alternative carries no such+rule, so this compiles on every supported compiler; 'case2' can keep its @do@+because it only ever binds plain variables.+-}+case3 :: HashMap String Int %1 -> Ur Case3Result+case3 hm = case HM.insert "1" 919 hm of+ (Ur iniOneReside, hm) -> case HM.lookup "1" hm of+ (Ur oneBeforeBulkInsert, hm) -> case HM.insertMany [(show i, i) | i <- [1 .. 128]] hm of+ hm -> case HM.lookup "1" hm of+ (Ur oneAfterBulkInsert, hm) -> case HM.lookup "16" hm of+ (Ur sixteenAfterBulkInsert, hm) ->+ case foldl'+ (\hm i -> move i & \(Ur i) -> uncurry lseq (HM.delete (show i) hm))+ hm+ [16 .. 256 :: Int] of+ hm -> case HM.lookup "16" hm of+ (Ur sixteenAfterBulkDelete, hm) -> case HM.insertMany [(show i, i) | i <- [129 .. 256]] hm of+ hm -> case HM.insert "16" 9181 hm of+ (Ur poppedSixteen, hm) -> case HM.lookup "16" hm of+ (Ur finalSixteen, hm) -> case Map.fromList `Ur.lift` HM.toList hm of+ Ur finalResult ->+ let iniOneResideExpected = Nothing+ oneBeforeBulkInsertExpected = Just 919+ oneAfterBulkInsertExpected = Just 1+ sixteenAfterBulkInsertExpected = Just 16+ sixteenAfterBulkDeleteExpected = Nothing+ poppedSixteenExpected = Nothing+ finalSixteenExpected = Just 9181+ expectedResult = Map.fromList $ [(show i, i) | i <- [2 .. 15] <> [129 .. 256]] <> [("1", 1), ("16", 9181)]+ in Ur Case3Result {..}
+ test/Data/Vector/Generic/Mutable/Growable/Linear/Borrow/Unrestricted/TypingCases.hs view
@@ -0,0 +1,229 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -O0 #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}+{-# OPTIONS_GHC -fdefer-type-errors -Wno-deferred-type-errors #-}++module Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted.TypingCases (+ module Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted.TypingCases,+) where++import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.Copyable (Copyable (copy))+import Control.Syntax.DataFlow qualified as DataFlow+import Data.Coerce (coerce)+import Data.Vector qualified as V+import Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted qualified as Growable+import Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted qualified as Fixed+import Data.Vector.Mutable.Growable.Linear.Borrow qualified as Owning+import Prelude.Linear+import Unsafe.Linear qualified as Unsafe+import Prelude qualified as NonLinear++newtype WrappedInt = WrappedInt Int++newtype WrappedBackend a = WrappedBackend (V.Vector a)++badBackendCoercion ::+ Growable.GrowableVector V.Vector Int %1 ->+ Growable.GrowableVector WrappedBackend Int+badBackendCoercion = Unsafe.toLinear coerce++badElementCoercion ::+ Growable.GrowableVector V.Vector WrappedInt %1 ->+ Growable.GrowableVector V.Vector Int+badElementCoercion = Unsafe.toLinear coerce++badOwnershipCoercion ::+ Growable.GrowableVector V.Vector Int %1 ->+ Owning.GrowableVector Int+badOwnershipCoercion = Unsafe.toLinear coerce++badGrowableToFixed ::+ Growable.GrowableVector V.Vector Int %1 ->+ Fixed.Vector V.Vector Int+badGrowableToFixed = Unsafe.toLinear coerce++badFixedToGrowable ::+ Fixed.Vector V.Vector Int %1 ->+ Growable.GrowableVector V.Vector Int+badFixedToGrowable = Unsafe.toLinear coerce++badGrowableToFixedUpcast ::+ Growable.GrowableVector V.Vector Int %1 ->+ Fixed.Vector V.Vector Int+badGrowableToFixedUpcast = upcast++badFixedToGrowableUpcast ::+ Fixed.Vector V.Vector Int %1 ->+ Growable.GrowableVector V.Vector Int+badFixedToGrowableUpcast = upcast++badLifetimeSwap ::+ forall α β.+ Mut α (Growable.GrowableVector V.Vector Int) %1 ->+ Mut β (Growable.GrowableVector V.Vector Int)+{-# NOINLINE badLifetimeSwap #-}+badLifetimeSwap =+ Unsafe.toLinear+ ( coerce ::+ Mut α (Growable.GrowableVector V.Vector Int) ->+ Mut β (Growable.GrowableVector V.Vector Int)+ )++badLifetimeSwapCase :: Int+badLifetimeSwapCase =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.empty @V.Vector @Int ownerLinear)+ let !() = consume (badLifetimeSwap vector)+ pureAfter+ ( case Growable.toVector (reclaim lend) of+ Ur frozen -> V.length frozen+ )++badSplit ::+ Mut α (Growable.GrowableVector V.Vector Int) %1 ->+ Growable.GrowableVector V.Vector (Mut α Int)+badSplit = split++badOwnerToContentCoercion ::+ Mut α (Growable.GrowableVector V.Vector Int) %1 ->+ Mut α (Fixed.Vector V.Vector Int)+badOwnerToContentCoercion = Unsafe.toLinear coerce++badElementBorrow ::+ Mut α (Growable.GrowableVector V.Vector Int) %1 ->+ BO α (Mut α Int)+badElementBorrow = Growable.get 0++badDuplicate ::+ Borrow bk α (Growable.GrowableVector V.Vector Int) %1 ->+ Growable.GrowableVector V.Vector Int+badDuplicate = copy++badMutateShared ::+ Share α (Growable.GrowableVector V.Vector Int) ->+ BO α (Share α (Growable.GrowableVector V.Vector Int))+badMutateShared =+ Growable.modify 0 (\value -> value NonLinear.+ 1)++badGrowShared ::+ Share α (Growable.GrowableVector V.Vector Int) ->+ BO α (Share α (Growable.GrowableVector V.Vector Int))+badGrowShared = Growable.reserve 10++badElementCoercionCase :: Int+badElementCoercionCase =+ linearly \linear ->+ case Growable.toVector+ ( badElementCoercion+ (Growable.fromList @V.Vector [WrappedInt 1] linear)+ ) of+ Ur vector -> V.length vector++badOwnershipCoercionCase :: Int+badOwnershipCoercionCase =+ linearly \linear ->+ case Owning.toVector+ (badOwnershipCoercion (Growable.fromList @V.Vector [1] linear)) of+ Ur vector -> V.length vector++badElementBorrowCase :: Int+badElementBorrowCase =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromList @V.Vector [1] ownerLinear)+ element <- badElementBorrow vector+ let !() = consume element+ pureAfter+ ( case Growable.toVector (reclaim lend) of+ Ur frozen -> V.length frozen+ )++badMutateSharedCase :: Int+badMutateSharedCase =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromList @V.Vector [1] ownerLinear)+ vector <-+ sharing_ vector \shared -> Control.do+ shared <- badMutateShared shared+ Control.pure (consume shared)+ let !() = consume vector+ pureAfter+ ( case Growable.toVector (reclaim lend) of+ Ur frozen -> V.length frozen+ )++badGrowSharedCase :: Int+badGrowSharedCase =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromList @V.Vector [1] ownerLinear)+ vector <-+ sharing_ vector \shared -> Control.do+ shared <- badGrowShared shared+ Control.pure (consume shared)+ let !() = consume vector+ pureAfter+ ( case Growable.toVector (reclaim lend) of+ Ur frozen -> V.length frozen+ )++badContentEscapeCase :: Int+badContentEscapeCase =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.empty @V.Vector @Int ownerLinear)+ (escaped, vector) <- Growable.withContent vector Control.pure+ let+ !() = consume escaped+ !() = consume vector+ pureAfter+ ( case Growable.toVector (reclaim lend) of+ Ur frozen -> V.length frozen+ )++badSharedContentEscapeCase :: Int+badSharedContentEscapeCase =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.empty @V.Vector @Int ownerLinear)+ share vector & \(Ur sharedVector) -> Control.do+ (escaped, sharedVector) <-+ Growable.withContent sharedVector Control.pure+ let+ !() = consume escaped+ !() = consume sharedVector+ pureAfter+ ( case Growable.toVector (reclaim lend) of+ Ur frozen -> V.length frozen+ )++preserveMutContent ::+ Mut α (Growable.GrowableVector V.Vector Int) %1 ->+ Mut α (Fixed.Vector V.Vector Int)+preserveMutContent = Growable.getContents++preserveShareContent ::+ Share α (Growable.GrowableVector V.Vector Int) %1 ->+ Share α (Fixed.Vector V.Vector Int)+preserveShareContent = Growable.getContents
+ test/Data/Vector/Generic/Mutable/Growable/Linear/Borrow/UnrestrictedSpec.hs view
@@ -0,0 +1,668 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}++module Data.Vector.Generic.Mutable.Growable.Linear.Borrow.UnrestrictedSpec (+ module Data.Vector.Generic.Mutable.Growable.Linear.Borrow.UnrestrictedSpec,+) where++import Control.Exception qualified as Exception+import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.BO.Unsafe (Alias (UnsafeAlias))+import Control.Monad.Borrow.Pure.Clone (Clone (clone))+import Control.Monad.Borrow.Pure.Copyable (Copyable (copy))+import Control.Syntax.DataFlow qualified as DataFlow+import Data.IORef (IORef, modifyIORef', newIORef, readIORef)+import Data.List qualified as List+import Data.Vector qualified as V+import Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted qualified as Growable+import Data.Vector.Generic.Mutable.Growable.Linear.Borrow.Unrestricted.TypingCases+import Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted qualified as Fixed+import Data.Vector.Primitive qualified as P+import Data.Vector.Unboxed qualified as U+import GHC.IO (unsafePerformIO)+import Prelude.Linear+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.HUnit+import Unsafe.Linear qualified as Unsafe+import Prelude qualified as NonLinear++freezeBoxed :: Growable.GrowableVector V.Vector Int %1 -> [Int]+freezeBoxed vector =+ case Growable.toVector vector of+ Ur frozen -> V.toList frozen++freezeUnboxed :: Growable.GrowableVector U.Vector Int %1 -> [Int]+freezeUnboxed vector =+ case Growable.toVector vector of+ Ur frozen -> U.toList frozen++freezePrimitive :: Growable.GrowableVector P.Vector Int %1 -> [Int]+freezePrimitive vector =+ case Growable.toVector vector of+ Ur frozen -> P.toList frozen++boxedGrowth :: ((Int, Int), [Int])+boxedGrowth =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.empty @V.Vector @Int ownerLinear)+ vector <- Growable.push 1 vector+ vector <- Growable.extend (V.fromList [2, 3]) vector+ vector <- Growable.reserve 10 vector+ (Ur logicalSize, vector) <- Control.pure (Growable.size vector)+ (Ur allocated, vector) <- Control.pure (Growable.capacity vector)+ let !() = consume vector+ pureAfter+ ( (logicalSize, allocated)+ , freezeBoxed (reclaim lend)+ )++unboxedGrowth :: [Int]+unboxedGrowth =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ (Growable.fromList @U.Vector [1, 2] ownerLinear)+ vector <- Growable.push 3 vector+ vector <- Growable.extend (U.fromList [4, 5]) vector+ let !() = consume vector+ pureAfter (freezeUnboxed (reclaim lend))++primitiveGrowth :: [Int]+primitiveGrowth =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ (Growable.withCapacity @P.Vector @Int 1 ownerLinear)+ vector <- Growable.push 7 vector+ vector <- Growable.push 8 vector+ vector <- Growable.push 9 vector+ let !() = consume vector+ pureAfter (freezePrimitive (reclaim lend))++fromVectorCopy :: ([Int], [Int])+fromVectorCopy =+ let source = V.fromList [1, 2, 3]+ result =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromVector source ownerLinear)+ vector <- Growable.write 0 9 vector+ let !() = consume vector+ pureAfter (freezeBoxed (reclaim lend))+ in (V.toList source, result)++unsafeMutableRoundTrip :: [Int]+unsafeMutableRoundTrip =+ linearly \linear ->+ freezeBoxed+ ( Growable.unsafeFromMutable+ (unsafePerformIO (V.thaw (V.fromList [1, 2])))+ linear+ )++unsafeVectorRoundTrip :: [Int]+unsafeVectorRoundTrip =+ linearly \linear ->+ freezeUnboxed+ ( Growable.unsafeFromVector+ (U.fromList [3, 4])+ linear+ )++test_backends :: TestTree+test_backends =+ testGroup+ "construction, growth, and backends"+ [ testCase "boxed backend preserves size and requested capacity" do+ let ((logicalSize, allocated), values) = boxedGrowth+ logicalSize @?= 3+ assertBool "capacity did not grow to the request" (allocated >= 10)+ values @?= [1, 2, 3]+ , testCase "unboxed backend grows and extends" do+ unboxedGrowth @?= [1, 2, 3, 4, 5]+ , testCase "primitive backend grows repeatedly" do+ primitiveGrowth @?= [7, 8, 9]+ , testCase "constant and empty materialize only initialized entries" do+ linearly+ ( \linear ->+ case dup linear of+ (emptyLinear, constantLinear) ->+ ( freezeBoxed+ (Growable.empty @V.Vector @Int emptyLinear)+ , freezeUnboxed+ ( Growable.constant @U.Vector+ 3+ (4 :: Int)+ constantLinear+ )+ )+ )+ @?= ([], [4, 4, 4])+ , testCase "fromVector copies its immutable source" do+ fromVectorCopy @?= ([1, 2, 3], [9, 2, 3])+ , testCase "unsafe constructors adopt complete initialized sources" do+ unsafeMutableRoundTrip @?= [1, 2]+ unsafeVectorRoundTrip @?= [3, 4]+ ]++boxedOperations :: ((Int, Int, Int), [Int], [Int])+boxedOperations =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ (Growable.fromList @V.Vector [1, 2, 3] ownerLinear)+ (Ur first, vector) <- Growable.get 0 vector+ (Ur old, vector) <- Growable.set 1 20 vector+ vector <- Growable.write 2 30 vector+ (Ur auxiliary, vector) <-+ Growable.update+ 0+ (\value -> Control.pure (Ur (value * 10), Ur (value + 1)))+ vector+ vector <-+ Growable.modify 1 (\value -> value NonLinear.+ 1) vector+ vector <- Growable.swap vector 0 2+ vector <- Growable.push 40 vector+ vector <- Growable.extend (V.fromList [50, 60]) vector+ (Ur snapshot, vector) <- Growable.copyToVector vector+ let !() = consume vector+ pureAfter+ ( (first, old, auxiliary)+ , V.toList snapshot+ , freezeBoxed (reclaim lend)+ )++sharedReads :: (Int, Int, Int)+sharedReads =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ (Growable.fromList @V.Vector [11, 22, 33] ownerLinear)+ ((first, second, third), vector) <-+ sharing vector \shared -> Control.do+ Ur first <- Growable.copyAt 0 shared+ Ur second <- Growable.unsafeCopyAt 1 shared+ (third, shared) <-+ Growable.withContent shared \content ->+ move content & \(Ur content) -> Control.do+ Ur third <- Fixed.copyAt 2 content+ Control.pure third+ Control.pure+ (consume shared `lseq` (first, second, third))+ let !() = consume vector+ pureAfter+ ( case Growable.toVector (reclaim lend) of+ Ur _ -> (first, second, third)+ )++ordinaryElementMultiplicity :: (Int, [Int])+ordinaryElementMultiplicity =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ (Growable.fromList @V.Vector [1, 2] ownerLinear)+ let replacement = 7+ (Ur displaced, vector) <- Growable.set 0 replacement vector+ vector <- Growable.write 1 replacement vector+ vector <- Growable.push replacement vector+ let !() = consume vector+ pureAfter+ ( displaced + displaced+ , freezeBoxed (reclaim lend)+ )++test_operations :: TestTree+test_operations =+ testGroup+ "unrestricted element operations"+ [ testCase "get, set, write, update, modify, swap, push, and snapshot" do+ boxedOperations+ @?= ( (1, 2, 10)+ , [30, 21, 2, 40, 50, 60]+ , [30, 21, 2, 40, 50, 60]+ )+ , testCase "shared and fixed-content reads need no copy capability" do+ sharedReads @?= (11, 22, 33)+ , testCase "inputs, callbacks, and displaced values are unrestricted" do+ ordinaryElementMultiplicity @?= (2, [7, 7, 7])+ ]++mirroredSurface :: ([Int], Int, [Int])+mirroredSurface =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ (Growable.fromList @V.Vector [1, 2] ownerLinear)+ vector <- Growable.reserveAdditional 5 vector+ (Ur allocated, vector) <- Control.pure (Growable.capacity vector)+ (Ur first, vector) <- Growable.head vector+ (Ur unsafeFirst, vector) <- Growable.unsafeHead vector+ (Ur final, vector) <- Growable.last vector+ (Ur unsafeFinal, vector) <- Growable.unsafeLast vector+ (Ur copiedFirst, vector) <- Growable.copyAtMut 0 vector+ (Ur copiedFinal, vector) <- Growable.unsafeCopyAtMut 1 vector+ vector <-+ Growable.withContent_ vector \content -> Control.do+ content <- Fixed.modify 0 (\value -> value NonLinear.+ 10) content+ Control.pure (consume content)+ let !() = consume vector+ pureAfter+ ( [first, unsafeFirst, final, unsafeFinal, copiedFirst, copiedFinal]+ , allocated+ , freezeBoxed (reclaim lend)+ )++test_mirroredSurface :: TestTree+test_mirroredSurface =+ testCase "mirrors read helpers, reserveAdditional, and withContent_" do+ let (observed, allocated, values) = mirroredSurface+ observed @?= [1, 1, 2, 2, 1, 2]+ assertBool "additional reserve did not grow capacity" (allocated >= 7)+ values @?= [11, 2]++contentSplit :: [Int]+contentSplit =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ (Growable.fromList @V.Vector [1, 2, 3, 4] ownerLinear)+ ((), vector) <-+ Growable.withContent vector \content -> Control.do+ let !(left, right) = Fixed.splitAt 2 content+ (left, right) <-+ parBO+ (Fixed.modify 0 (\value -> value NonLinear.+ 10) left)+ (Fixed.modify 1 (\value -> value NonLinear.+ 20) right)+ Control.pure (consume (left, right))+ vector <- Growable.push 5 vector+ let !() = consume vector+ pureAfter (freezeBoxed (reclaim lend))++directContentLength :: (Int, [Int])+directContentLength =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ (Growable.fromList @V.Vector [1, 2] ownerLinear)+ vector <- Growable.reserve 16 vector+ case Fixed.size (preserveMutContent vector) of+ (Ur logicalSize, content) ->+ let !() = consume content+ in pureAfter (logicalSize, freezeBoxed (reclaim lend))++sharedContentProjection :: (Int, [Int])+sharedContentProjection =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ (Growable.fromList @V.Vector [1, 2, 3] ownerLinear)+ share vector & \(Ur shared) ->+ move (preserveShareContent shared) & \(Ur content) -> Control.do+ Ur observed <- Fixed.copyAt 1 content+ pureAfter (observed, freezeBoxed (reclaim lend))++withContentMutation :: [Int]+withContentMutation =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ (Growable.fromList @V.Vector [1, 2, 3] ownerLinear)+ vector <-+ Growable.withContent_ vector \content -> Control.do+ content <- Fixed.write 1 20 content+ Control.pure (consume content)+ vector <- Growable.push 4 vector+ let !() = consume vector+ pureAfter (freezeBoxed (reclaim lend))++explicitContentMutation :: [Int]+explicitContentMutation =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ (Growable.fromList @V.Vector [1, 2, 3] ownerLinear)+ ((), vector) <-+ reborrowing vector \short -> Control.do+ content <- Control.pure (Growable.getContents short)+ content <- Fixed.write 1 20 content+ Control.pure (consume content)+ vector <- Growable.push 4 vector+ let !() = consume vector+ pureAfter (freezeBoxed (reclaim lend))++{- | Reproject after a growth that replaces the backing buffer.++Growth publishes a replacement header, so a projection taken after it must+describe the new buffer and the new logical length rather than the one the+scope before it saw. The existing content tests all project within a single+no-growth scope; this one covers the project, grow, reproject sequence+end to end.++The observation is threefold: the second projection reports the grown logical+length, a value written through the first projection survives the relocation+at its old index, and a write through the second projection lands beyond the+original capacity and reaches the final frozen vector. The last of these is+what makes the assertion sharp -- the checked write at index 40 is rejected as+out of bounds against a view that still described the three-element prefix.+-}+reprojectionAfterGrowth :: (Int, Int, Int, [Int])+reprojectionAfterGrowth =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromList @V.Vector [1, 2, 3] ownerLinear)+ (Ur before, vector) <-+ reborrowing vector \short -> Control.do+ let %1 !content = Growable.getContents short+ case Fixed.size content of+ (Ur logicalSize, content) -> Control.do+ content <- Fixed.write 0 100 content+ Control.pure (consume content `lseq` Ur logicalSize)+ vector <- Growable.extend (V.fromList [4 .. 64]) vector+ (Ur after, vector) <-+ reborrowing vector \short -> Control.do+ let %1 !content = Growable.getContents short+ case Fixed.size content of+ (Ur logicalSize, content) -> Control.do+ (Ur preserved, content) <- Fixed.get 0 content+ content <- Fixed.write 40 999 content+ Control.pure+ (consume content `lseq` Ur (logicalSize, preserved))+ let !() = consume vector+ pureAfter+ ( case after of+ (afterLength, preserved) ->+ (before, afterLength, preserved, freezeBoxed (reclaim lend))+ )++test_content :: TestTree+test_content =+ testGroup+ "fixed content scopes"+ [ testCase "split mutation is safe before growth resumes" do+ contentSplit @?= [11, 2, 3, 24, 5]+ , testCase "direct projection excludes spare capacity" do+ directContentLength @?= (2, [1, 2])+ , testCase "getContents preserves mutable and shared kind and lifetime" do+ directContentLength @?= (2, [1, 2])+ sharedContentProjection @?= (2, [1, 2, 3])+ , testCase "withContent matches explicit reborrowing and projection" do+ withContentMutation @?= explicitContentMutation+ withContentMutation @?= [1, 20, 3, 4]+ , testCase "reprojection after growth observes the published header" do+ let (before, after, preserved, values) = reprojectionAfterGrowth+ before @?= 3+ after @?= 64+ preserved @?= 100+ NonLinear.length values @?= 64+ NonLinear.take 3 values @?= [100, 2, 3]+ values NonLinear.!! 40 @?= 999+ ]++data Tracked = Tracked+ { capabilityCalls :: !(IORef Int)+ , trackedValue :: !Int+ }++recordCapability :: Tracked -> Tracked+recordCapability tracked =+ case unsafePerformIO+ (modifyIORef' (capabilityCalls tracked) NonLinear.succ) of+ () -> tracked++instance Consumable Tracked where+ consume =+ Unsafe.toLinear \tracked ->+ recordCapability tracked `NonLinear.seq` ()++instance Dupable Tracked where+ dup2 =+ Unsafe.toLinear \tracked ->+ (recordCapability tracked, recordCapability tracked)++instance Movable Tracked where+ move = Unsafe.toLinear \tracked -> Ur (recordCapability tracked)++instance Copyable Tracked where+ copy =+ Unsafe.toLinear \(UnsafeAlias tracked) ->+ recordCapability tracked++trackedValues :: V.Vector Tracked -> [Int]+trackedValues vector =+ NonLinear.map trackedValue (V.toList vector)++capabilityFreeLifecycle ::+ IORef Int ->+ ([Int], [Int], [Int], [Int])+capabilityFreeLifecycle calls =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ ( Growable.fromVector+ (V.fromList [Tracked calls 1, Tracked calls 2])+ ownerLinear+ )+ vector <- Growable.reserve 8 vector+ vector <- Growable.push (Tracked calls 3) vector+ (cloned, vector) <- sharing vector (\shared -> clone shared)+ (Ur snapshot, vector) <- Growable.copyToVector vector+ (Ur first, vector) <- Growable.get 0 vector+ (Ur old, vector) <- Growable.set 1 (Tracked calls 4) vector+ vector <- Growable.write 0 (Tracked calls 5) vector+ let+ !() = consume vector+ clonedValues =+ case Growable.toVector cloned of+ Ur frozen -> trackedValues frozen+ pureAfter+ ( case Growable.toVector (reclaim lend) of+ Ur frozen ->+ ( trackedValues snapshot+ , clonedValues+ , [trackedValue first, trackedValue old]+ , trackedValues frozen+ )+ )++referenceAliasAfterGrowth :: IORef Int -> [Int]+referenceAliasAfterGrowth reference =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ (Growable.fromVector (V.singleton reference) ownerLinear)+ vector <- Growable.reserve 32 vector+ vector <- Growable.push reference vector+ (Ur observed, vector) <- Growable.get 0 vector+ let !() = consume vector+ pureAfter+ ( case Growable.toVector (reclaim lend) of+ Ur frozen ->+ unsafePerformIO do+ modifyIORef'+ observed+ (\value -> value NonLinear.+ 41)+ NonLinear.mapM readIORef (V.toList frozen)+ )++test_capabilities :: TestTree+test_capabilities =+ testGroup+ "capability-free GC-owned elements"+ [ testCase "the complete lifecycle invokes no element capability" do+ calls <- newIORef 0+ capabilityFreeLifecycle calls+ @?= ( [1, 2, 3]+ , [1, 2, 3]+ , [1, 2]+ , [5, 4, 3]+ )+ readIORef calls NonLinear.>>= (@?= 0)+ , testCase "growth preserves deliberate boxed aliases" do+ reference <- newIORef 1+ referenceAliasAfterGrowth reference @?= [42, 42]+ readIORef reference NonLinear.>>= (@?= 42)+ ]++getOutOfBounds :: Int -> Int+getOutOfBounds index =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ (Growable.fromList @V.Vector [1, 2, 3] ownerLinear)+ (Ur value, vector) <- Growable.get index vector+ let !() = consume vector+ pureAfter+ ( case Growable.toVector (reclaim lend) of+ Ur frozen -> value + V.length frozen+ )++writeOutOfBounds :: Int -> Int+writeOutOfBounds index =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ (Growable.fromList @V.Vector [1, 2, 3] ownerLinear)+ vector <- Growable.write index 4 vector+ let !() = consume vector+ pureAfter+ ( case Growable.toVector (reclaim lend) of+ Ur frozen -> V.sum frozen+ )++negativeCapacity :: Int+negativeCapacity =+ linearly \linear ->+ case Growable.toVector+ (Growable.withCapacity @V.Vector @Int (-1) linear) of+ Ur frozen -> V.length frozen++assertErrorPrefix :: NonLinear.String -> a -> Assertion+assertErrorPrefix expectedPrefix value = do+ result <- Exception.try @Exception.ErrorCall (Exception.evaluate value)+ case result of+ Left exception ->+ assertBool+ ("unexpected error: " <> Exception.displayException exception)+ (expectedPrefix `List.isPrefixOf` Exception.displayException exception)+ Right _ ->+ assertFailure ("expected error beginning with " <> expectedPrefix)++test_bounds :: TestTree+test_bounds =+ testGroup+ "bounds"+ [ testCase "get rejects a negative index" do+ assertErrorPrefix "get: index -1 out of bounds" (getOutOfBounds (-1))+ , testCase "get rejects the upper bound" do+ assertErrorPrefix "get: index 3 out of bounds" (getOutOfBounds 3)+ , testCase "write rejects a negative index" do+ assertErrorPrefix "write: index -1 out of bounds" (writeOutOfBounds (-1))+ , testCase "write rejects the upper bound" do+ assertErrorPrefix "write: index 3 out of bounds" (writeOutOfBounds 3)+ , testCase "construction rejects negative capacity" do+ assertErrorPrefix "withCapacity: negative capacity -1" negativeCapacity+ ]++assertDeferredTypeError :: NonLinear.String -> a -> Assertion+assertDeferredTypeError expectedFragment value = do+ result <- Exception.try @Exception.SomeException (Exception.evaluate value)+ case result of+ Left exception ->+ assertBool+ ("unexpected deferred error: " <> Exception.displayException exception)+ (expectedFragment `List.isInfixOf` Exception.displayException exception)+ Right _ ->+ assertFailure+ ("expected deferred type error containing " <> expectedFragment)++test_typing :: TestTree+test_typing =+ testGroup+ "typing boundaries"+ [ testCase "backend role is nominal" do+ assertDeferredTypeError "Couldn't match type" badBackendCoercion+ , testCase "element role is nominal" do+ assertDeferredTypeError "Couldn't match type" badElementCoercionCase+ , testCase "element ownership cannot be coerced" do+ assertDeferredTypeError "representation" badOwnershipCoercionCase+ , testCase "growable owner cannot be coerced to fixed content" do+ assertDeferredTypeError+ "Couldn't match representation of type"+ badGrowableToFixed+ , testCase "fixed content cannot be coerced to a growable owner" do+ assertDeferredTypeError+ "Couldn't match representation of type"+ badFixedToGrowable+ , testCase "growable owner cannot be upcast to fixed content" do+ assertDeferredTypeError+ "Couldn't match representation of type"+ badGrowableToFixedUpcast+ , testCase "fixed content cannot be upcast to a growable owner" do+ assertDeferredTypeError+ "Couldn't match representation of type"+ badFixedToGrowableUpcast+ , testCase "a growable borrow cannot swap lifetime indices" do+ assertDeferredTypeError "Couldn't match type" badLifetimeSwapCase+ , testCase "a growable owner has no generic split" do+ assertDeferredTypeError+ "DistributesAlias"+ badSplit+ , testCase "a growable borrow cannot be coerced to content" do+ assertDeferredTypeError+ "Couldn't match representation of type"+ badOwnerToContentCoercion+ , testCase "get cannot manufacture an element borrow" do+ assertDeferredTypeError "Couldn't match" badElementBorrowCase+ , testCase "the mutable owner cannot be copied" do+ assertDeferredTypeError "cannot be copied!" badDuplicate+ , testCase "shared borrows cannot mutate" do+ assertDeferredTypeError "Couldn't match" badMutateSharedCase+ , testCase "shared borrows cannot grow" do+ assertDeferredTypeError "Couldn't match" badGrowSharedCase+ , testCase "fixed content cannot escape its scope" do+ assertDeferredTypeError "Couldn't match" badContentEscapeCase+ , testCase "shared fixed content cannot escape its scope" do+ assertDeferredTypeError "Couldn't match" badSharedContentEscapeCase+ ]
+ test/Data/Vector/Generic/Mutable/Linear/Borrow/Experimental/Multiplicity/TypingCases.hs view
@@ -0,0 +1,103 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -O0 #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}+{-# OPTIONS_GHC -fdefer-type-errors -Wno-deferred-type-errors #-}++module Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity.TypingCases (+ module Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity.TypingCases,+) where++import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.BO.Unsafe (Alias (UnsafeAlias))+import Control.Monad.Borrow.Pure.Clone (Clone (clone))+import Control.Syntax.DataFlow qualified as DataFlow+import Data.Vector qualified as V+import Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity qualified as Vector+import GHC.Exts (Multiplicity (One))+import Prelude.Linear++data NoCapabilities = NoCapabilities Int++data ConsumableOnly = ConsumableOnly Int++instance Consumable ConsumableOnly where+ consume (ConsumableOnly value) = consume value++badOwningGet ::+ Mut α (Vector.Vector One V.Vector Int) %1 ->+ BO+ α+ ( Mut α Int+ , Mut α (Vector.Vector One V.Vector Int)+ )+badOwningGet = Vector.get 0++badOwningGetCase :: ()+badOwningGetCase =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO_ runLinear Control.do+ (element, vector) <-+ badOwningGet+ ( UnsafeAlias+ ( Vector.fromList @V.Vector+ [1]+ ownerLinear ::+ Vector.Vector One V.Vector Int+ )+ )+ Control.pure (consume (element, vector))++badConsume ::+ Vector.Vector One V.Vector NoCapabilities %1 ->+ ()+badConsume = consume++badConsumeCase :: ()+badConsumeCase =+ linearly \linear ->+ badConsume+ ( Vector.fromList @V.Vector+ [NoCapabilities 1]+ linear+ )++badClone ::+ Share α (Vector.Vector One V.Vector ConsumableOnly) %1 ->+ BO α (Vector.Vector One V.Vector ConsumableOnly)+badClone = clone++badCloneCase :: ()+badCloneCase =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ ( Vector.fromList @V.Vector+ [ConsumableOnly 1]+ ownerLinear+ )+ (cloned, vector) <- sharing vector (\shared -> badClone shared)+ let !() = consume vector+ pureAfter+ (consume cloned `lseq` consume (reclaim lend))++badCopyCase :: ()+badCopyCase =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ ( Vector.fromList @V.Vector+ [NoCapabilities 1]+ ownerLinear+ )+ (Ur _, vector) <- Vector.copyToVector vector+ let !() = consume vector+ pureAfter (badConsume (reclaim lend))
+ test/Data/Vector/Generic/Mutable/Linear/Borrow/Experimental/MultiplicitySpec.hs view
@@ -0,0 +1,317 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}++module Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.MultiplicitySpec (+ module Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.MultiplicitySpec,+) where++import Control.Exception qualified as Exception+import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.BO.Unsafe (Alias (UnsafeAlias))+import Control.Monad.Borrow.Pure.Clone (Clone (clone))+import Control.Monad.Borrow.Pure.Copyable (Copyable (copy))+import Control.Syntax.DataFlow qualified as DataFlow+import Data.IORef (IORef, modifyIORef', newIORef, readIORef)+import Data.List qualified as List+import Data.Vector qualified as V+import Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity qualified as Vector+import Data.Vector.Generic.Mutable.Linear.Borrow.Experimental.Multiplicity.TypingCases+import GHC.Exts (Multiplicity (One))+import GHC.IO (unsafePerformIO)+import Prelude.Linear+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.HUnit+import Unsafe.Linear qualified as Unsafe+import Prelude qualified as NonLinear++data Tracked = Tracked+ { copyCalls :: !(IORef Int)+ , consumeCalls :: !(IORef Int)+ , duplicateCalls :: !(IORef Int)+ , moveCalls :: !(IORef Int)+ , trackedValue :: !Int+ }++instance Copyable Tracked where+ copy =+ Unsafe.toLinear \(UnsafeAlias tracked) ->+ case unsafePerformIO do+ consumed <- readIORef (consumeCalls tracked)+ if consumed == 0+ then modifyIORef' (copyCalls tracked) NonLinear.succ+ else NonLinear.error "copy invoked after source retirement" of+ () -> tracked++instance Consumable Tracked where+ consume =+ Unsafe.toLinear \tracked ->+ unsafePerformIO+ (modifyIORef' (consumeCalls tracked) NonLinear.succ)++instance Dupable Tracked where+ dup2 =+ Unsafe.toLinear \tracked ->+ case unsafePerformIO+ (modifyIORef' (duplicateCalls tracked) NonLinear.succ) of+ () -> (tracked, tracked)++instance Movable Tracked where+ move =+ Unsafe.toLinear \tracked ->+ case unsafePerformIO+ (modifyIORef' (moveCalls tracked) NonLinear.succ) of+ () -> Ur tracked++newTracked ::+ IORef Int ->+ IORef Int ->+ IORef Int ->+ IORef Int ->+ Int ->+ Tracked+newTracked = Tracked++owningVector ::+ IORef Int ->+ IORef Int ->+ IORef Int ->+ IORef Int ->+ Linearly %1 ->+ Vector.Vector One V.Vector Tracked+owningVector copies consumes duplicates moves =+ Vector.fromList @V.Vector+ [ newTracked copies consumes duplicates moves 10+ , newTracked copies consumes duplicates moves 20+ ]++consumeOwningVector ::+ IORef Int ->+ IORef Int ->+ IORef Int ->+ IORef Int ->+ ()+consumeOwningVector copies consumes duplicates moves =+ linearly \linear ->+ consume (owningVector copies consumes duplicates moves linear)++getOwningElement ::+ IORef Int ->+ IORef Int ->+ IORef Int ->+ IORef Int ->+ ()+getOwningElement copies consumes duplicates moves =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ (owningVector copies consumes duplicates moves ownerLinear)+ element <- Vector.get 0 vector+ let !() = consume element+ pureAfter (consume (reclaim lend))++writeOwningElement ::+ IORef Int ->+ IORef Int ->+ IORef Int ->+ IORef Int ->+ ()+writeOwningElement copies consumes duplicates moves =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ (owningVector copies consumes duplicates moves ownerLinear)+ vector <-+ Vector.write+ 0+ (newTracked copies consumes duplicates moves 30)+ vector+ let !() = consume vector+ pureAfter (consume (reclaim lend))++setOwningElement ::+ IORef Int ->+ IORef Int ->+ IORef Int ->+ IORef Int ->+ ()+setOwningElement copies consumes duplicates moves =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ (owningVector copies consumes duplicates moves ownerLinear)+ (displaced, vector) <-+ Vector.set+ 0+ (newTracked copies consumes duplicates moves 30)+ vector+ let+ !() = consume displaced+ !() = consume vector+ pureAfter (consume (reclaim lend))++cloneOwningVector ::+ IORef Int ->+ IORef Int ->+ IORef Int ->+ IORef Int ->+ ()+cloneOwningVector copies consumes duplicates moves =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ (owningVector copies consumes duplicates moves ownerLinear)+ (cloned, vector) <- sharing vector (\shared -> clone shared)+ let !() = consume vector+ pureAfter+ (consume cloned `lseq` consume (reclaim lend))++finishCopied :: Tracked %1 -> Int+finishCopied =+ Unsafe.toLinear \(Tracked copies consumes duplicates moves value) ->+ consume (Tracked copies consumes duplicates moves value) `lseq` value++copyAtBeforeRecovery ::+ IORef Int ->+ IORef Int ->+ IORef Int ->+ IORef Int ->+ Int+copyAtBeforeRecovery copies consumes duplicates moves =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ (owningVector copies consumes duplicates moves ownerLinear)+ (copied, vector) <- Vector.copyAtMut 0 vector+ let !() = consume vector+ pureAfter+ ( consume (reclaim lend) `lseq`+ finishCopied copied+ )++copyVectorBeforeRecovery ::+ IORef Int ->+ IORef Int ->+ IORef Int ->+ IORef Int ->+ [Int]+copyVectorBeforeRecovery copies consumes duplicates moves =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ (owningVector copies consumes duplicates moves ownerLinear)+ (Ur snapshot, vector) <- Vector.copyToVector vector+ let !() = consume vector+ pureAfter+ ( consume (reclaim lend) `lseq`+ NonLinear.map trackedValue (V.toList snapshot)+ )++moveOwningVector ::+ IORef Int ->+ IORef Int ->+ IORef Int ->+ IORef Int ->+ [Int]+moveOwningVector copies consumes duplicates moves =+ linearly \linear ->+ case Vector.toVector+ (owningVector copies consumes duplicates moves linear) of+ Ur vector -> NonLinear.map trackedValue (V.toList vector)++withCounters ::+ (IORef Int -> IORef Int -> IORef Int -> IORef Int -> Assertion) ->+ Assertion+withCounters assertion = do+ copies <- newIORef 0+ consumes <- newIORef 0+ duplicates <- newIORef 0+ moves <- newIORef 0+ assertion copies consumes duplicates moves++test_owning :: TestTree+test_owning =+ testGroup+ "owning elements"+ [ testCase "consuming the vector consumes every element" $+ withCounters \copies consumes duplicates moves -> do+ Exception.evaluate+ (consumeOwningVector copies consumes duplicates moves)+ readIORef consumes NonLinear.>>= (@?= 2)+ , testCase "get transfers the vector borrow to the element" $+ withCounters \copies consumes duplicates moves -> do+ Exception.evaluate+ (getOwningElement copies consumes duplicates moves)+ readIORef consumes NonLinear.>>= (@?= 2)+ , testCase "write consumes the displaced element exactly once" $+ withCounters \copies consumes duplicates moves -> do+ Exception.evaluate+ (writeOwningElement copies consumes duplicates moves)+ readIORef consumes NonLinear.>>= (@?= 3)+ , testCase "set returns the displaced element to its caller" $+ withCounters \copies consumes duplicates moves -> do+ Exception.evaluate+ (setOwningElement copies consumes duplicates moves)+ readIORef consumes NonLinear.>>= (@?= 3)+ , testCase "clone duplicates every owned element" $+ withCounters \copies consumes duplicates moves -> do+ Exception.evaluate+ (cloneOwningVector copies consumes duplicates moves)+ readIORef duplicates NonLinear.>>= (@?= 2)+ readIORef consumes NonLinear.>>= (@?= 4)+ , testCase "copyAtMut completes copying before owner recovery" $+ withCounters \copies consumes duplicates moves -> do+ copyAtBeforeRecovery copies consumes duplicates moves @?= 10+ readIORef copies NonLinear.>>= (@?= 1)+ , testCase "copyToVector copies every element before owner recovery" $+ withCounters \copies consumes duplicates moves -> do+ copyVectorBeforeRecovery copies consumes duplicates moves+ @?= [10, 20]+ readIORef copies NonLinear.>>= (@?= 2)+ , testCase "toVector moves every element into GC ownership" $+ withCounters \copies consumes duplicates moves -> do+ moveOwningVector copies consumes duplicates moves @?= [10, 20]+ readIORef moves NonLinear.>>= (@?= 2)+ ]++assertDeferredTypeError :: NonLinear.String -> a -> Assertion+assertDeferredTypeError expectedFragment value = do+ result <- Exception.try @Exception.SomeException (Exception.evaluate value)+ case result of+ Left exception ->+ assertBool+ ("unexpected deferred error: " <> Exception.displayException exception)+ (expectedFragment `List.isInfixOf` Exception.displayException exception)+ Right _ ->+ assertFailure+ ("expected deferred type error containing " <> expectedFragment)++test_typing :: TestTree+test_typing =+ testGroup+ "typing boundaries"+ [ testCase "owning get cannot also return the vector borrow" do+ assertDeferredTypeError "Couldn't match" badOwningGetCase+ , testCase "owning consumption requires Consumable elements" do+ assertDeferredTypeError "Consumable NoCapabilities" badConsumeCase+ , testCase "owning clone requires Dupable elements" do+ assertDeferredTypeError "Dupable" badCloneCase+ , testCase "owning copyToVector requires Copyable elements" do+ assertDeferredTypeError "Copyable" badCopyCase+ ]
+ test/Data/Vector/Generic/Mutable/Linear/Borrow/Unrestricted/TypingCases.hs view
@@ -0,0 +1,105 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -O0 #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}+{-# OPTIONS_GHC -fdefer-type-errors -Wno-deferred-type-errors #-}++module Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted.TypingCases (+ module Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted.TypingCases,+) where++import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.Copyable (Copyable (copy))+import Control.Syntax.DataFlow qualified as DataFlow+import Data.Coerce (coerce)+import Data.Vector qualified as V+import Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted qualified as Vector+import Data.Vector.Mutable.Linear.Borrow qualified as Owning+import Prelude.Linear+import Unsafe.Linear qualified as Unsafe+import Prelude qualified as NonLinear++newtype WrappedInt = WrappedInt Int++newtype WrappedBackend a = WrappedBackend (V.Vector a)++badBackendCoercion ::+ Vector.Vector V.Vector Int %1 ->+ Vector.Vector WrappedBackend Int+badBackendCoercion = Unsafe.toLinear coerce++badElementCoercion ::+ Vector.Vector V.Vector WrappedInt %1 ->+ Vector.Vector V.Vector Int+badElementCoercion = Unsafe.toLinear coerce++badOwnershipCoercion ::+ Vector.Vector V.Vector Int %1 ->+ Owning.Vector Int+badOwnershipCoercion = Unsafe.toLinear coerce++badElementBorrow ::+ Mut α (Vector.Vector V.Vector Int) %1 ->+ BO α (Mut α Int)+badElementBorrow = Vector.get 0++badDuplicate ::+ Borrow bk α (Vector.Vector V.Vector Int) %1 ->+ Vector.Vector V.Vector Int+badDuplicate = copy++badMutateShared ::+ Share α (Vector.Vector V.Vector Int) ->+ BO α (Share α (Vector.Vector V.Vector Int))+badMutateShared =+ Vector.modify 0 (\value -> value NonLinear.+ 1)++badElementCoercionCase :: Int+badElementCoercionCase =+ linearly \linear ->+ case Vector.toVector+ ( badElementCoercion+ (Vector.fromList @V.Vector [WrappedInt 1] linear)+ ) of+ Ur vector -> V.length vector++badOwnershipCoercionCase :: Int+badOwnershipCoercionCase =+ linearly \linear ->+ case Owning.toVector+ (badOwnershipCoercion (Vector.fromList @V.Vector [1] linear)) of+ Ur vector -> V.length vector++badElementBorrowCase :: Int+badElementBorrowCase =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Vector.fromList @V.Vector [1] ownerLinear)+ element <- badElementBorrow vector+ let !() = consume element+ pureAfter+ ( case Vector.toVector (reclaim lend) of+ Ur frozen -> V.length frozen+ )++badMutateSharedCase :: Int+badMutateSharedCase =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Vector.fromList @V.Vector [1] ownerLinear)+ vector <-+ sharing_ vector \shared -> Control.do+ shared <- badMutateShared shared+ Control.pure (consume shared)+ let !() = consume vector+ pureAfter+ ( case Vector.toVector (reclaim lend) of+ Ur frozen -> V.length frozen+ )
+ test/Data/Vector/Generic/Mutable/Linear/Borrow/UnrestrictedSpec.hs view
@@ -0,0 +1,446 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}++module Data.Vector.Generic.Mutable.Linear.Borrow.UnrestrictedSpec (+ module Data.Vector.Generic.Mutable.Linear.Borrow.UnrestrictedSpec,+) where++import Control.Exception qualified as Exception+import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.BO.Unsafe (Alias (UnsafeAlias))+import Control.Monad.Borrow.Pure.Clone (Clone (clone))+import Control.Monad.Borrow.Pure.Copyable (Copyable (copy))+import Control.Syntax.DataFlow qualified as DataFlow+import Data.IORef (IORef, modifyIORef', newIORef, readIORef)+import Data.List qualified as List+import Data.Vector qualified as V+import Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted qualified as Vector+import Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted.TypingCases+import Data.Vector.Primitive qualified as P+import Data.Vector.Unboxed qualified as U+import GHC.IO (unsafePerformIO)+import Prelude.Linear+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.HUnit+import Unsafe.Linear qualified as Unsafe+import Prelude qualified as NonLinear++freezeBoxed :: Vector.Vector V.Vector Int %1 -> [Int]+freezeBoxed vector =+ case Vector.toVector vector of+ Ur frozen -> V.toList frozen++freezeUnboxed :: Vector.Vector U.Vector Int %1 -> [Int]+freezeUnboxed vector =+ case Vector.toVector vector of+ Ur frozen -> U.toList frozen++freezePrimitive :: Vector.Vector P.Vector Int %1 -> [Int]+freezePrimitive vector =+ case Vector.toVector vector of+ Ur frozen -> P.toList frozen++test_construction :: TestTree+test_construction =+ testGroup+ "construction and backends"+ [ testCase "boxed round trip" do+ linearly+ ( \linear ->+ freezeBoxed (Vector.fromList @V.Vector [1, 2, 3] linear)+ )+ @?= [1, 2, 3]+ , testCase "unboxed round trip" do+ linearly+ ( \linear ->+ freezeUnboxed (Vector.fromList @U.Vector [1, 2, 3] linear)+ )+ @?= [1, 2, 3]+ , testCase "primitive backend remains extensible" do+ linearly+ ( \linear ->+ freezePrimitive (Vector.fromList @P.Vector [1, 2, 3] linear)+ )+ @?= [1, 2, 3]+ , testCase "constant and empty" do+ linearly+ ( \linear ->+ case dup linear of+ (emptyLinear, constantLinear) ->+ ( freezeBoxed (Vector.empty @V.Vector @Int emptyLinear)+ , freezeUnboxed+ (Vector.constant @U.Vector 3 (7 :: Int) constantLinear)+ )+ )+ @?= ([], [7, 7, 7])+ , testCase "fromVector copies its immutable source" do+ let source = V.fromList [1, 2, 3]+ result =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Vector.fromVector source ownerLinear)+ (Ur _, vector) <- Vector.set 0 99 vector+ let !() = consume vector+ pureAfter (freezeBoxed (reclaim lend))+ V.toList source @?= [1, 2, 3]+ result @?= [99, 2, 3]+ ]++boxedOperations :: ((Int, Int, Int), [Int])+boxedOperations =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Vector.fromList @V.Vector [1, 2, 3] ownerLinear)+ (Ur first, vector) <- Vector.get 0 vector+ (Ur old, vector) <- Vector.set 1 20 vector+ (Ur auxiliary, vector) <-+ Vector.update+ 2+ (\value -> Control.pure (Ur (value * 2), Ur (value + 1)))+ vector+ vector <- Vector.modify 0 (\value -> value NonLinear.+ 10) vector+ vector <- Vector.write 1 21 vector+ vector <- Vector.swap vector 0 2+ let !() = consume vector+ pureAfter ((first, old, auxiliary), freezeBoxed (reclaim lend))++sharedReads :: (Int, Int)+sharedReads =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Vector.fromList @V.Vector [11, 22] ownerLinear)+ ((first, second), vector) <-+ sharing vector \shared -> Control.do+ Ur first <- Vector.copyAt 0 shared+ Ur second <- Vector.copyAt 1 shared+ Control.pure (first, second)+ let !() = consume vector+ pureAfter+ ( case Vector.toVector (reclaim lend) of+ Ur _ -> (first, second)+ )++ordinaryElementMultiplicity :: (Int, [Int])+ordinaryElementMultiplicity =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Vector.fromList @V.Vector [1, 2] ownerLinear)+ let replacement = 7+ (Ur displaced, vector) <- Vector.set 0 replacement vector+ vector <- Vector.write 1 replacement vector+ let !() = consume vector+ pureAfter+ ( displaced + displaced+ , freezeBoxed (reclaim lend)+ )++test_operations :: TestTree+test_operations =+ testGroup+ "unrestricted element operations"+ [ testCase "get, set, update, modify, and swap" do+ boxedOperations @?= ((1, 2, 6), [4, 21, 11])+ , testCase "a shared borrow may be read repeatedly" do+ sharedReads @?= (11, 22)+ , testCase "inputs, callbacks, and displaced values are unrestricted" do+ ordinaryElementMultiplicity @?= (2, [7, 7])+ ]++splitBoxed :: [Int]+splitBoxed =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Vector.fromList @V.Vector [1, 2, 3, 4] ownerLinear)+ let !(left, right) = Vector.splitAt 2 vector+ (left, right) <-+ parBO+ (Vector.modify 0 (\value -> value NonLinear.+ 10) left)+ (Vector.modify 1 (\value -> value NonLinear.+ 20) right)+ let !() = consume (left, right)+ pureAfter (freezeBoxed (reclaim lend))++splitUnboxed :: [Int]+splitUnboxed =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Vector.fromList @U.Vector [1, 2, 3, 4] ownerLinear)+ let !(left, right) = Vector.splitAt 2 vector+ (left, right) <-+ parBO+ (Vector.modify 1 (\value -> value NonLinear.+ 10) left)+ (Vector.modify 0 (\value -> value NonLinear.+ 20) right)+ let !() = consume (left, right)+ pureAfter (freezeUnboxed (reclaim lend))++nestedBoundarySplits :: [Int]+nestedBoundarySplits =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Vector.fromList @V.Vector [1, 2, 3, 4] ownerLinear)+ let !(emptyLeft, rest) = Vector.splitAt 0 vector+ !(prefix, emptyRight) = Vector.splitAt 4 rest+ !(firstTwo, latterTwo) = Vector.splitAt 2 prefix+ !(third, fourth) = Vector.splitAt 1 latterTwo+ firstTwo <-+ Vector.modify 0 (\value -> value NonLinear.+ 10) firstTwo+ third <-+ Vector.modify 0 (\value -> value NonLinear.+ 20) third+ fourth <-+ Vector.modify 0 (\value -> value NonLinear.+ 30) fourth+ let !() = consume (emptyLeft, emptyRight, firstTwo, third, fourth)+ pureAfter (freezeBoxed (reclaim lend))++sharedSplitReads :: (Int, Int)+sharedSplitReads =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Vector.fromList @U.Vector [10, 20, 30, 40] ownerLinear)+ ((Ur leftValue, Ur rightValue), vector) <-+ sharing vector \shared -> Control.do+ let !(left, right) = Vector.splitAt 2 shared+ parBO+ (Vector.copyAt 1 left)+ (Vector.copyAt 0 right)+ let !() = consume vector+ pureAfter+ ( case Vector.toVector (reclaim lend) of+ Ur _ -> (leftValue, rightValue)+ )++test_split :: TestTree+test_split =+ testGroup+ "split ownership"+ [ testCase "boxed disjoint slices mutate in parallel" do+ splitBoxed @?= [11, 2, 3, 24]+ , testCase "unboxed disjoint slices mutate in parallel" do+ splitUnboxed @?= [1, 12, 23, 4]+ , testCase "boundary and nested splits preserve disjoint ownership" do+ nestedBoundarySplits @?= [11, 2, 23, 34]+ , testCase "shared split ranges can be read in parallel" do+ sharedSplitReads @?= (20, 30)+ ]++data Tracked = Tracked+ { capabilityCalls :: !(IORef Int)+ , trackedValue :: !Int+ }++recordCapability :: Tracked -> Tracked+recordCapability tracked =+ case unsafePerformIO+ (modifyIORef' (capabilityCalls tracked) NonLinear.succ) of+ () -> tracked++instance Consumable Tracked where+ consume =+ Unsafe.toLinear \tracked ->+ recordCapability tracked `NonLinear.seq` ()++instance Dupable Tracked where+ dup2 =+ Unsafe.toLinear \tracked ->+ (recordCapability tracked, recordCapability tracked)++instance Movable Tracked where+ move = Unsafe.toLinear \tracked -> Ur (recordCapability tracked)++instance Copyable Tracked where+ copy =+ Unsafe.toLinear \(UnsafeAlias tracked) ->+ recordCapability tracked++capabilityFreeLifecycle :: IORef Int -> ([Int], [Int], [Int])+capabilityFreeLifecycle calls =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ ( Vector.fromVector+ (V.fromList [Tracked calls 1, Tracked calls 2])+ ownerLinear+ )+ (cloned, vector) <- sharing vector (\shared -> clone shared)+ (Ur snapshot, vector) <- Vector.copyToVector vector+ (Ur first, vector) <- Vector.copyAtMut 0 vector+ (Ur old, vector) <- Vector.set 1 (Tracked calls 3) vector+ vector <- Vector.write 0 (Tracked calls 4) vector+ let+ !() = consume vector+ clonedValues =+ case Vector.toVector cloned of+ Ur frozen -> NonLinear.map trackedValue (V.toList frozen)+ pureAfter+ ( case Vector.toVector (reclaim lend) of+ Ur frozen ->+ ( NonLinear.map trackedValue (V.toList snapshot)+ , clonedValues+ , [trackedValue first, trackedValue old]+ <> NonLinear.map trackedValue (V.toList frozen)+ )+ )++test_capabilities :: TestTree+test_capabilities =+ testGroup+ "capability-free elements"+ [ testCase "all fixed operations avoid element capability callbacks" do+ calls <- newIORef 0+ capabilityFreeLifecycle calls+ @?= ([1, 2], [1, 2], [1, 2, 4, 3])+ count <- readIORef calls+ count @?= 0+ , testCase "a clone has independent mutable backing" do+ cloneIndependence @?= ([1, 2], [11, 2])+ ]++cloneIndependence :: ([Int], [Int])+cloneIndependence =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Vector.fromList @V.Vector [1, 2] ownerLinear)+ (cloned, vector) <- sharing vector (\shared -> clone shared)+ vector <-+ Vector.modify 0 (\value -> value NonLinear.+ 10) vector+ let+ !() = consume vector+ clonedValues = freezeBoxed cloned+ pureAfter+ ( clonedValues+ , freezeBoxed (reclaim lend)+ )++referenceAliasValue :: IORef Int -> Int+referenceAliasValue reference =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Vector.fromVector (V.singleton reference) ownerLinear)+ (Ur observed, vector) <- Vector.get 0 vector+ let !() = consume vector+ pureAfter+ ( case Vector.toVector (reclaim lend) of+ Ur frozen ->+ unsafePerformIO do+ modifyIORef'+ observed+ (\value -> value NonLinear.+ 41)+ readIORef (V.head frozen)+ )++test_aliasing :: TestTree+test_aliasing =+ testCase "boxed entries deliberately remain GC-owned aliases" do+ reference <- newIORef 1+ referenceAliasValue reference @?= 42+ originalValue <- readIORef reference+ originalValue @?= 42++getOutOfBounds :: Int -> Int+getOutOfBounds index =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Vector.fromList @V.Vector [1, 2, 3] ownerLinear)+ (Ur value, vector) <- Vector.get index vector+ let !() = consume vector+ pureAfter+ ( case Vector.toVector (reclaim lend) of+ Ur frozen -> value + V.length frozen+ )++writeOutOfBounds :: Int -> Int+writeOutOfBounds index =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Vector.fromList @V.Vector [1, 2, 3] ownerLinear)+ vector <- Vector.write index 4 vector+ let !() = consume vector+ pureAfter+ ( case Vector.toVector (reclaim lend) of+ Ur frozen -> V.sum frozen+ )++assertErrorPrefix :: NonLinear.String -> a -> Assertion+assertErrorPrefix expectedPrefix value = do+ result <- Exception.try @Exception.ErrorCall (Exception.evaluate value)+ case result of+ Left exception ->+ assertBool+ ("unexpected error: " <> Exception.displayException exception)+ (expectedPrefix `List.isPrefixOf` Exception.displayException exception)+ Right _ ->+ assertFailure ("expected error beginning with " <> expectedPrefix)++test_bounds :: TestTree+test_bounds =+ testGroup+ "bounds"+ [ testCase "negative index" do+ assertErrorPrefix "get: index -1 out of bounds" (getOutOfBounds (-1))+ , testCase "upper bound" do+ assertErrorPrefix "get: index 3 out of bounds" (getOutOfBounds 3)+ , testCase "write checks its lower bound" do+ assertErrorPrefix "write: index -1 out of bounds" (writeOutOfBounds (-1))+ , testCase "write checks its upper bound" do+ assertErrorPrefix "write: index 3 out of bounds" (writeOutOfBounds 3)+ ]++assertDeferredTypeError :: NonLinear.String -> a -> Assertion+assertDeferredTypeError expectedFragment value = do+ result <- Exception.try @Exception.SomeException (Exception.evaluate value)+ case result of+ Left exception ->+ assertBool+ ("unexpected deferred error: " <> Exception.displayException exception)+ (expectedFragment `List.isInfixOf` Exception.displayException exception)+ Right _ ->+ assertFailure+ ("expected deferred type error containing " <> expectedFragment)++test_typing :: TestTree+test_typing =+ testGroup+ "typing boundaries"+ [ testCase "backend role is nominal" do+ assertDeferredTypeError "Couldn't match type" badBackendCoercion+ , testCase "element role is nominal" do+ assertDeferredTypeError "Couldn't match type" badElementCoercionCase+ , testCase "ownership families cannot be coerced" do+ assertDeferredTypeError "representation" badOwnershipCoercionCase+ , testCase "get cannot manufacture an element borrow" do+ assertDeferredTypeError "Couldn't match" badElementBorrowCase+ , testCase "the mutable owner cannot be copied" do+ assertDeferredTypeError "cannot be copied!" badDuplicate+ , testCase "shared borrows cannot mutate" do+ assertDeferredTypeError "Couldn't match" badMutateSharedCase+ ]
+ test/Data/Vector/Mutable/Growable/Linear/BorrowSpec.hs view
@@ -0,0 +1,908 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE LinearTypes #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}++module Data.Vector.Mutable.Growable.Linear.BorrowSpec (+ module Data.Vector.Mutable.Growable.Linear.BorrowSpec,+) where++import Control.Exception qualified as Exception+import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.BO.Unsafe (Alias (..))+import Control.Monad.Borrow.Pure.Copyable (Copyable (copy), copyMut)+import Control.Syntax.DataFlow qualified as DataFlow+import Data.IORef (IORef, modifyIORef', newIORef, readIORef)+import Data.List qualified as List+import Data.Ref.Linear qualified as Ref+import Data.Ref.Linear.Borrow qualified as RefBorrow+import Data.Vector qualified as V+import Data.Vector.Mutable qualified as MV+import Data.Vector.Mutable.Growable.Linear.Borrow qualified as Growable+import Data.Vector.Mutable.Growable.Linear.TypingCases+import Data.Vector.Mutable.Linear.Borrow qualified as Fixed+import GHC.IO (unsafePerformIO)+import Prelude.Linear+import Test.Falsify.Generator qualified as G+import Test.Falsify.Predicate qualified as P+import Test.Falsify.Property qualified as F+import Test.Falsify.Range qualified as G+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.Falsify (testProperty)+import Test.Tasty.HUnit+import Unsafe.Linear qualified as Unsafe+import Prelude qualified as NonLinear++data Operation+ = Push !Int+ | Extend !Int !Int+ | Reserve !Int+ | ReserveAdditional !Int+ | Replace !Int !Int+ deriving (Show)++newtype Tracked = Tracked (IORef Int)++instance Consumable Tracked where+ consume =+ Unsafe.toLinear \(Tracked counter) ->+ unsafePerformIO (modifyIORef' counter NonLinear.succ)++newtype NestedRef = NestedRef (Ref.Ref Int)++instance Consumable NestedRef where+ consume =+ Unsafe.toLinear \(NestedRef ref) ->+ consume ref++asBorrowedRef ::+ Mut α NestedRef %1 ->+ Mut α (Ref.Ref Int)+asBorrowedRef = upcast++data CopyTracked = CopyTracked !(IORef Int) !(IORef Int) !Int++instance Copyable CopyTracked where+ copy =+ Unsafe.toLinear \(UnsafeAlias value@(CopyTracked copies retired _)) ->+ case unsafePerformIO do+ retirementCount <- readIORef retired+ if retirementCount == 0+ then modifyIORef' copies NonLinear.succ+ else NonLinear.error "copy invoked after source retirement" of+ () -> value++instance Consumable CopyTracked where+ consume =+ Unsafe.toLinear \(CopyTracked _ retired _) ->+ unsafePerformIO (modifyIORef' retired NonLinear.succ)++data MoveTracked = MoveTracked !(IORef Int) !Int !Bool++instance Consumable MoveTracked where+ consume = Unsafe.toLinear \_ -> ()++instance Dupable MoveTracked where+ dup2 = Unsafe.toLinear \value -> (value, value)++instance Movable MoveTracked where+ move =+ Unsafe.toLinear \(MoveTracked moves value _) ->+ case unsafePerformIO (modifyIORef' moves NonLinear.succ) of+ () -> Ur (MoveTracked moves value True)++materializeMoveTracked :: IORef Int -> [(Int, Bool)]+materializeMoveTracked moves =+ NonLinear.map+ (\(MoveTracked _ value wasMoved) -> (value, wasMoved))+ ( V.toList $+ unur $+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.empty ownerLinear)+ vector <- Growable.push (MoveTracked moves 10 False) vector+ vector <- Growable.push (MoveTracked moves 20 False) vector+ let !() = consume vector+ pureAfter (Growable.toVector (reclaim lend))+ )++discardMaterializedMoveTracked :: IORef Int -> ()+discardMaterializedMoveTracked moves =+ linearly \linear ->+ case Growable.toVector+ ( Growable.fromVector+ (V.fromList [MoveTracked moves 10 False, MoveTracked moves 20 False])+ linear+ ) of+ Ur _ -> ()++decodeOperation :: Int -> Operation+decodeOperation seed =+ case seed `NonLinear.mod` 5 of+ 0 -> Push seed+ 1 -> Extend seed (seed + 1)+ 2 -> Reserve (NonLinear.abs seed `NonLinear.mod` 32)+ 3 -> ReserveAdditional (NonLinear.abs seed `NonLinear.mod` 16)+ _ -> Replace seed (-seed)++applyOperations ::+ [Operation] ->+ Mut α (Growable.GrowableVector Int) %1 ->+ BO α (Mut α (Growable.GrowableVector Int))+applyOperations [] vector = Control.pure vector+applyOperations (operation : operations) vector =+ case operation of+ Push value -> Control.do+ vector <- Growable.push value vector+ applyOperations operations vector+ Extend first second -> Control.do+ vector <- Growable.extend (V.fromList [first, second]) vector+ applyOperations operations vector+ Reserve requested -> Control.do+ vector <- Growable.reserve requested vector+ applyOperations operations vector+ ReserveAdditional additional -> Control.do+ vector <- Growable.reserveAdditional additional vector+ applyOperations operations vector+ Replace rawIndex value ->+ case Growable.size vector of+ (Ur 0, vector) -> applyOperations operations vector+ (Ur logicalSize, vector) -> Control.do+ let !index = NonLinear.abs rawIndex `NonLinear.mod` logicalSize+ (old, vector) <- Growable.set index value vector+ applyOperations operations (consume old `lseq` vector)++applyModel :: [Operation] -> [Int] -> [Int]+applyModel operations initial = NonLinear.foldl step initial operations+ where+ step values = \case+ Push value -> values <> [value]+ Extend first second -> values <> [first, second]+ Reserve _ -> values+ ReserveAdditional _ -> values+ Replace _ _ | NonLinear.null values -> values+ Replace rawIndex value ->+ let !index = NonLinear.abs rawIndex `NonLinear.mod` NonLinear.length values+ in case NonLinear.splitAt index values of+ (prefix, _ : suffix) -> prefix <> (value : suffix)+ (_, []) -> values++freezeList :: Growable.GrowableVector Int %1 -> [Int]+freezeList vector =+ case Growable.toVector vector of+ Ur frozen -> V.toList frozen++freezeLength :: Growable.GrowableVector Int %1 -> Int+freezeLength vector =+ case Growable.toVector vector of+ Ur frozen -> V.length frozen++freezeListUr :: Growable.GrowableVector Int %1 -> Ur [Int]+freezeListUr vector =+ case Growable.toVector vector of+ Ur frozen -> Ur (V.toList frozen)++runOperations :: Int -> [Operation] -> ([Int], Int, Int)+runOperations initialCapacity operations =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.withCapacity initialCapacity ownerLinear)+ vector <- applyOperations operations vector+ Growable.size vector & \(Ur logicalSize, vector) ->+ Growable.capacity vector & \(Ur finalCapacity, vector) -> DataFlow.do+ consume vector+ pureAfter $+ case Growable.toVector (reclaim lend) of+ Ur frozen ->+ Ur (V.toList frozen, logicalSize, finalCapacity)++test_model :: TestTree+test_model =+ testProperty "matches a list model across reserve, push, extend, and replace" do+ initialCapacity <- F.gen $ G.int $ G.between (0, 16)+ seeds <- F.gen $ G.list (G.between (0, 100)) $ G.int $ G.between (-100, 100)+ let !operations = NonLinear.map decodeOperation seeds+ !(actual, logicalSize, finalCapacity) =+ runOperations initialCapacity operations+ !expected = applyModel operations []+ F.collect "operations" [NonLinear.length operations `NonLinear.quot` 10 * 10]+ F.assert $+ P.expect expected P..$ ("contents", actual)+ F.assert $+ P.expect (NonLinear.length expected) P..$ ("logical size", logicalSize)+ F.assert $+ P.satisfies+ ("capacity >= logical size", (NonLinear.>= logicalSize))+ P..$ ("capacity", finalCapacity)++contentRoundTrip :: ((Int, Int), [Int])+contentRoundTrip =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromVector (V.fromList [10, 20, 30]) ownerLinear)+ ((logicalSize, first), vector) <-+ Growable.withContent vector \contents -> Control.do+ Fixed.size contents & \(Ur logicalSize, contents) -> Control.do+ (Ur first, contents) <- Fixed.copyAtMut 0 contents+ contents <- Fixed.modify 1 (+ 1) contents+ Control.pure (consume contents `lseq` (logicalSize, first))+ vector <- Growable.push 40 vector+ let !() = consume vector+ pureAfter $+ ( (logicalSize, first)+ , freezeList (reclaim lend)+ )++test_withContent :: TestTree+test_withContent =+ testCase "opens only the initialized prefix and restores growth access" do+ contentRoundTrip @?= ((3, 10), [10, 21, 30, 40])++directProjection :: (Int, [Int])+directProjection =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.withCapacity 8 ownerLinear)+ vector <- Growable.extend (V.fromList [3, 4, 5]) vector+ Fixed.size (Growable.getContents vector) & \(Ur logicalSize, contents) -> Control.do+ (Ur value, contents) <- Fixed.copyAtMut 2 contents+ let !() = consume contents+ pureAfter $+ ( logicalSize + value+ , freezeList (reclaim lend)+ )++test_getContents :: TestTree+test_getContents =+ testCase "direct projection captures logical length, not spare capacity" do+ directProjection @?= (8, [3, 4, 5])++discardingContentScope :: [Int]+discardingContentScope =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromVector (V.fromList [1, 2, 3]) ownerLinear)+ vector <- Growable.withContent_ vector \contents -> Control.do+ contents <- Fixed.modify 0 (+ 10) contents+ Control.pure (consume contents)+ vector <- Growable.push 4 vector+ let !() = consume vector+ pureAfter $ freezeListUr (reclaim lend)++countedContentScope :: IORef Int -> [Int]+countedContentScope counter =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromVector (V.fromList [1, 2, 3]) ownerLinear)+ vector <- Growable.withContent_ vector \contents ->+ case unsafePerformIO (modifyIORef' counter NonLinear.succ) of+ () -> Control.pure (consume contents)+ vector <- Growable.push 4 vector+ let !() = consume vector+ pureAfter $ freezeListUr (reclaim lend)++test_withContent_ :: TestTree+test_withContent_ =+ testGroup+ "discarding content scopes"+ [ testCase "restores the growable borrow" do+ discardingContentScope @?= [11, 2, 3, 4]+ , testCase "runs the callback exactly once" do+ counter <- newIORef 0+ countedContentScope counter @?= [1, 2, 3, 4]+ count <- readIORef counter+ count @?= 1+ ]++sharedContentProjection :: ((Int, Int, Int), [Int])+sharedContentProjection =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromVector (V.fromList [5, 6, 7]) ownerLinear)+ share vector & \(Ur sharedVector) -> Control.do+ ((first, second), returnedSharedVector) <-+ Growable.withContent sharedVector \linearContents ->+ move linearContents & \(Ur contents) -> Control.do+ Ur first <- Fixed.copyAt 0 contents+ Ur second <- Fixed.copyAt 1 contents+ Control.pure (first, second)+ move returnedSharedVector & \(Ur sharedVector) -> Control.do+ Ur third <- Fixed.copyAt 2 (Growable.getContents sharedVector)+ let !() = consume sharedVector+ pureAfter+ ( (first, second, third)+ , freezeList (reclaim lend)+ )++test_sharedContentProjection :: TestTree+test_sharedContentProjection =+ testCase "preserves Share and permits repeated reads in a content scope" do+ sharedContentProjection @?= ((5, 6, 7), [5, 6, 7])++sharedDiscardingContentScope :: (Int, [Int])+sharedDiscardingContentScope =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromVector (V.fromList [8, 9, 10]) ownerLinear)+ share vector & \(Ur sharedVector) -> Control.do+ returnedSharedVector <-+ Growable.withContent_ sharedVector \linearContents ->+ move linearContents & \(Ur contents) -> Control.do+ Ur first <- Fixed.copyAt 0 contents+ Ur second <- Fixed.copyAt 1 contents+ Control.pure (first + second)+ move returnedSharedVector & \(Ur sharedVector) -> Control.do+ Ur last <- Fixed.copyAt 2 (Growable.getContents sharedVector)+ pureAfter+ ( last+ , freezeList (reclaim lend)+ )++test_sharedDiscardingContentScope :: TestTree+test_sharedDiscardingContentScope =+ testCase "withContent_ preserves a shared growable borrow" do+ sharedDiscardingContentScope @?= (10, [8, 9, 10])++replacement :: (Int, [Int])+replacement =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromVector (V.fromList [7, 8, 9]) ownerLinear)+ (old, vector) <- Growable.set 1 80 vector+ let !() = consume vector+ pureAfter+ ( old+ , freezeList (reclaim lend)+ )++test_set :: TestTree+test_set =+ testCase "returns the displaced value without changing logical size" do+ replacement @?= (8, [7, 80, 9])++mirroredSurface :: ((Int, Int, Int), [Int])+mirroredSurface =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.fromList [1, 2, 3] ownerLinear)+ (Ur middle, vector) <-+ reborrowing vector \short -> Control.do+ element <- Growable.get 1 short+ Control.pure (copyMut element)+ (Ur first, vector) <-+ reborrowing vector \short -> Control.do+ element <- Growable.head short+ Control.pure (copyMut element)+ (Ur final, vector) <-+ reborrowing vector \short -> Control.do+ element <- Growable.last short+ Control.pure (copyMut element)+ ((), vector) <-+ Growable.update+ 1+ (\ !value -> Control.pure ((), value + 10))+ vector+ vector <- Growable.modify 0 (* 2) vector+ vector <- Growable.swap vector 0 2+ let !() = consume vector+ pureAfter+ ( (middle, first, final)+ , freezeList (reclaim lend)+ )++test_mirroredSurface :: TestTree+test_mirroredSurface =+ testCase "mirrors fixed-vector get, head, last, update, modify, and swap" do+ mirroredSurface @?= ((2, 1, 3), [3, 12, 2])++test_constructionSurface :: TestTree+test_constructionSurface =+ testGroup+ "construction"+ [ testCase "constant initializes the complete logical prefix" do+ linearly+ (\linear -> unur $ Growable.toList (Growable.constant 3 (7 :: Int) linear))+ @?= [7, 7, 7]+ , testCase "fromList and toList round-trip" do+ linearly+ (\linear -> unur $ Growable.toList (Growable.fromList [4, 5, 6 :: Int] linear))+ @?= [4, 5, 6]+ , testCase "unsafeFromMutable takes the complete initialized source" do+ linearly+ ( \linear ->+ unur $+ Growable.toList+ ( Growable.unsafeFromMutable+ (unsafePerformIO (MV.replicate 2 (3 :: Int)))+ linear+ )+ )+ @?= [3, 3]+ , testCase "unsafeFromVector takes the complete initialized source" do+ linearly+ ( \linear ->+ unur $+ Growable.toList+ (Growable.unsafeFromVector (V.fromList [2, 4 :: Int]) linear)+ )+ @?= [2, 4]+ ]++assertErrorPrefix :: NonLinear.String -> a -> Assertion+assertErrorPrefix expectedPrefix value = do+ result <- Exception.try @Exception.ErrorCall $ Exception.evaluate value+ case result of+ Left exception ->+ assertBool+ ("unexpected error: " <> Exception.displayException exception)+ (expectedPrefix `List.isPrefixOf` Exception.displayException exception)+ Right _ -> assertFailure ("expected error beginning with " <> expectedPrefix)++copyOutOfBounds :: Int -> Int+copyOutOfBounds index =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromVector (V.fromList [10, 20, 30]) ownerLinear)+ (Ur value, vector) <- Growable.copyAtMut index vector+ let !() = consume vector+ pureAfter (value + freezeLength (reclaim lend))++setOutOfBounds :: Int -> Int+setOutOfBounds index =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromVector (V.fromList [10, 20, 30]) ownerLinear)+ (old, vector) <- Growable.set index 0 vector+ let !() = consume vector+ pureAfter (old + freezeLength (reclaim lend))++emptyHead :: Int+emptyHead =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.empty ownerLinear)+ element <- Growable.head vector+ let !(Ur value) = copyMut element+ pureAfter (value + freezeLength (reclaim lend))++emptyLast :: Int+emptyLast =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.empty ownerLinear)+ element <- Growable.last vector+ let !(Ur value) = copyMut element+ pureAfter (value + freezeLength (reclaim lend))++test_bounds :: TestTree+test_bounds =+ testGroup+ "bounds"+ [ testCase "copy rejects a negative index" do+ assertErrorPrefix+ "copyAtMut: index -1 out of bounds for length 3"+ (copyOutOfBounds (-1))+ , testCase "copy rejects the upper bound" do+ assertErrorPrefix+ "copyAtMut: index 3 out of bounds for length 3"+ (copyOutOfBounds 3)+ , testCase "set rejects a negative index" do+ assertErrorPrefix+ "set: index -1 out of bounds for length 3"+ (setOutOfBounds (-1))+ , testCase "set rejects the upper bound" do+ assertErrorPrefix+ "set: index 3 out of bounds for length 3"+ (setOutOfBounds 3)+ , testCase "head rejects an empty vector" do+ assertErrorPrefix+ "get: index 0 out of bounds for length 0"+ emptyHead+ , testCase "last rejects an empty vector" do+ assertErrorPrefix+ "last: empty vector"+ emptyLast+ , testCase "construction rejects negative capacity" do+ assertErrorPrefix+ "withCapacity: negative capacity -1"+ (linearly \linear -> consume (Growable.withCapacity @Int (-1) linear))+ ]++test_capacity :: TestTree+test_capacity =+ testGroup+ "capacity"+ [ testCase "empty starts with zero logical size" do+ runOperations 0 [] @?= ([], 0, 0)+ , testCase "preallocation does not initialize spare capacity" do+ runOperations 8 [] @?= ([], 0, 8)+ , testCase "reserve preserves contents and does not change size" do+ let !(values, logicalSize, finalCapacity) =+ runOperations 1 [Push 1, Push 2, Reserve 12]+ values @?= [1, 2]+ logicalSize @?= 2+ assertBool "reserve did not reserve enough" (finalCapacity >= 12)+ ]++trackedConsumption :: IORef Int -> ()+trackedConsumption counter =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.withCapacity 0 ownerLinear)+ vector <- Growable.push (Tracked counter) vector+ vector <- Growable.push (Tracked counter) vector+ vector <- Growable.push (Tracked counter) vector+ let !() = consume vector+ pureAfter (consume (reclaim lend))++boxedRefAcrossGrowth :: Int+boxedRefAcrossGrowth =+ linearly \linear -> DataFlow.do+ (refLinear, remainingLinear) <- dup linear+ (ownerLinear, runLinear) <- dup remainingLinear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.empty ownerLinear)+ vector <- Growable.push (NestedRef (Ref.new 1 refLinear)) vector+ vector <- Growable.reserve 64 vector+ ((), vector) <-+ reborrowing vector \short -> Control.do+ element <- Growable.get 0 short+ ref <- RefBorrow.modify (+ 41) (asBorrowedRef element)+ Control.pure (consume ref)+ (observed, vector) <-+ reborrowing vector \short -> Control.do+ element <- Growable.get 0 short+ RefBorrow.copyRef (asBorrowedRef element)+ let !() = consume vector+ pureAfter (consume (reclaim lend) `lseq` observed)++immutableCopyLifecycle :: IORef Int -> IORef Int -> ()+immutableCopyLifecycle copies retired =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ ( Growable.fromVector+ ( V.fromList+ [ CopyTracked copies retired 10+ , CopyTracked copies retired 20+ ]+ )+ ownerLinear+ )+ vector <- Growable.reserve 64 vector+ vector <-+ Growable.extend+ (V.singleton (CopyTracked copies retired 30))+ vector+ vector <- Growable.reserveAdditional 64 vector+ let !() = consume vector+ pureAfter (consume (reclaim lend))++gcOwnedImmutableLifecycle :: IORef Int -> ()+gcOwnedImmutableLifecycle retired =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ ( Growable.fromVector+ (V.singleton (Tracked retired))+ ownerLinear+ )+ vector <-+ Growable.extend+ (V.fromList [Tracked retired, Tracked retired])+ vector+ let !() = consume vector+ pureAfter (consume (reclaim lend))++test_consumable :: TestTree+test_consumable =+ testGroup+ "destructive growth"+ [ testCase "growth moves capabilities and final consumption retires each once" do+ counter <- newIORef 0+ _ <- Exception.evaluate (trackedConsumption counter)+ count <- readIORef counter+ count @?= 3+ , testCase "preserves a nested Ref identity across reallocation" do+ boxedRefAcrossGrowth @?= 42+ , testCase "ordinary constructors need no Copyable instance" do+ counter <- newIORef 0+ _ <-+ Exception.evaluate $+ linearly \linear ->+ dup linear & \(constantLinear, listLinear) ->+ consume+ (Growable.constant 2 (Tracked counter) constantLinear)+ `lseq` consume+ (Growable.fromList [Tracked counter] listLinear)+ retired <- readIORef counter+ retired @?= 3+ , testCase "ordinary immutable sources need no Copyable instance" do+ counter <- newIORef 0+ _ <- Exception.evaluate (gcOwnedImmutableLifecycle counter)+ retired <- readIORef counter+ retired @?= 3+ , testCase "ordinary immutable copies do not invoke Copyable" do+ copies <- newIORef 0+ retired <- newIORef 0+ _ <- Exception.evaluate (immutableCopyLifecycle copies retired)+ copyCount <- readIORef copies+ retiredCount <- readIORef retired+ copyCount @?= 0+ retiredCount @?= 3+ , testCase "materialization invokes move for every owned element" do+ moves <- newIORef 0+ materializeMoveTracked moves @?= [(10, True), (20, True)]+ moveCount <- readIORef moves+ moveCount @?= 2+ , testCase "discarding materialization still invokes every move" do+ moves <- newIORef 0+ _ <- Exception.evaluate (discardMaterializedMoveTracked moves)+ moveCount <- readIORef moves+ moveCount @?= 2+ ]++sharedCopy :: (Int, [Int])+sharedCopy =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.fromList [4, 5, 6] ownerLinear)+ let !(Ur shared) = share vector+ Ur value <- Growable.copyAt 1 shared+ pureAfter (value, freezeList (reclaim lend))++retireCopiedResult ::+ (Ur CopyTracked, Mut α (Growable.GrowableVector CopyTracked)) %1 ->+ Growable.GrowableVector CopyTracked %1 ->+ Int+retireCopiedResult =+ Unsafe.toLinear2 \(copiedResult, borrowed) owner ->+ consume borrowed `lseq`+ consume owner `lseq`+ case copiedResult of+ Ur (CopyTracked _ _ value) -> value++retireSharedCopiedResult ::+ Ur CopyTracked %1 ->+ Growable.GrowableVector CopyTracked %1 ->+ Int+retireSharedCopiedResult =+ Unsafe.toLinear2 \copiedResult owner ->+ consume owner `lseq`+ case copiedResult of+ Ur (CopyTracked _ _ value) -> value++copyAtAfterRetirement :: IORef Int -> IORef Int -> Int+copyAtAfterRetirement copies retired =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ ( Growable.fromList+ [CopyTracked copies retired 10]+ ownerLinear+ )+ let !(Ur shared) = share vector+ copiedResult <- Growable.copyAt 0 shared+ pureAfter (retireSharedCopiedResult copiedResult (reclaim lend))++unsafeCopyAtAfterRetirement :: IORef Int -> IORef Int -> Int+unsafeCopyAtAfterRetirement copies retired =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ ( Growable.fromList+ [CopyTracked copies retired 10]+ ownerLinear+ )+ let !(Ur shared) = share vector+ copiedResult <- Growable.unsafeCopyAt 0 shared+ pureAfter (retireSharedCopiedResult copiedResult (reclaim lend))++copyAtMutAfterRetirement :: IORef Int -> IORef Int -> Int+copyAtMutAfterRetirement copies retired =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ ( Growable.fromList+ [CopyTracked copies retired 10]+ ownerLinear+ )+ copiedResult <- Growable.copyAtMut 0 vector+ pureAfter (retireCopiedResult copiedResult (reclaim lend))++selectedValues :: ((Int, Int), [Int])+selectedValues =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.fromList [4, 5, 6] ownerLinear)+ selected <- Growable.indicesMut vector [0, 2]+ let !(Ur firstValue, Ur finalValue) = copySelected selected+ pureAfter+ ((firstValue, finalValue), freezeList (reclaim lend))++copySelected :: [Mut α Int] %1 -> (Ur Int, Ur Int)+copySelected =+ Unsafe.toLinear \case+ [first, final] -> (copyMut first, copyMut final)+ selected -> error "indicesMut returned the wrong number of borrows" selected++duplicateIndices :: Int+duplicateIndices =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.fromList [4, 5, 6] ownerLinear)+ selected <- Growable.indicesMut vector [0, 0]+ let !() = consume selected+ pureAfter (freezeLength (reclaim lend))++parallelSplitContent :: [Int]+parallelSplitContent =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.fromList [1, 2, 3, 4] ownerLinear)+ vector <- Growable.withContent_ vector \contents -> Control.do+ let !(left, right) = Fixed.splitAt 2 contents+ consume+ Control.<$> parBO+ (Fixed.modify 0 (+ 10) left)+ (Fixed.modify 0 (+ 20) right)+ vector <- Growable.push 5 vector+ let !() = consume vector+ pureAfter (freezeList (reclaim lend))++test_additionalSurface :: TestTree+test_additionalSurface =+ testGroup+ "additional mirrored surface"+ [ testCase "shared copyAt reads without mutable recovery plumbing" do+ sharedCopy @?= (5, [4, 5, 6])+ , testCase "copyAt completes copying before the shared borrow ends" do+ copies <- newIORef 0+ retired <- newIORef 0+ copyAtAfterRetirement copies retired @?= 10+ copyCount <- readIORef copies+ copyCount @?= 1+ retirementCount <- readIORef retired+ retirementCount @?= 1+ , testCase "unsafeCopyAt completes copying before the shared borrow ends" do+ copies <- newIORef 0+ retired <- newIORef 0+ unsafeCopyAtAfterRetirement copies retired @?= 10+ copyCount <- readIORef copies+ copyCount @?= 1+ retirementCount <- readIORef retired+ retirementCount @?= 1+ , testCase "copyAtMut completes copying before mutable recovery" do+ copies <- newIORef 0+ retired <- newIORef 0+ copyAtMutAfterRetirement copies retired @?= 10+ copyCount <- readIORef copies+ copyCount @?= 1+ retirementCount <- readIORef retired+ retirementCount @?= 1+ , testCase "indicesMut returns the requested distinct elements" do+ selectedValues @?= ((4, 6), [4, 5, 6])+ , testCase "indicesMut rejects duplicate indices" do+ assertErrorPrefix+ "indicesMut: duplicate indices: [0,0]"+ duplicateIndices+ , testCase "fixed content can split safely in parallel before growth resumes" do+ parallelSplitContent @?= [11, 2, 23, 4, 5]+ ]++test_typingBoundaries :: TestTree+test_typingBoundaries =+ testGroup+ "typing boundaries"+ [ expectDeferredTypeError+ "GrowableVector element role is nominal"+ "Couldn't match type"+ badElementCoercion+ , expectDeferredTypeError+ "GrowableVector cannot be coerced to a fixed Vector"+ "Couldn't match representation of type"+ badGrowableToFixed+ , expectDeferredTypeError+ "a fixed Vector cannot be coerced to GrowableVector"+ "Couldn't match representation of type"+ badFixedToGrowable+ , expectDeferredTypeError+ "GrowableVector cannot be upcast to a fixed Vector"+ "Couldn't match representation of type"+ badGrowableToFixedUpcast+ , expectDeferredTypeError+ "a fixed Vector cannot be upcast to GrowableVector"+ "Couldn't match representation of type"+ badFixedToGrowableUpcast+ , expectDeferredTypeError+ "a growable borrow cannot swap lifetime indices"+ "Couldn't match type"+ badLifetimeSwapCase+ , expectDeferredTypeError+ "GrowableVector has no generic split"+ "DistributesAlias Growable.GrowableVector"+ badSplit+ , expectDeferredTypeError+ "GrowableVector cannot be copied"+ "cannot be copied!"+ badDuplicate+ , expectDeferredTypeError+ "fixed content cannot escape withContent"+ "Couldn't match type"+ badContentEscapeCase+ , expectDeferredTypeError+ "shared fixed content cannot escape withContent"+ "Couldn't match type"+ badSharedContentEscapeCase+ , expectDeferredTypeError+ "Copyable alone does not permit growable materialization"+ "Movable CopyOnly"+ badGrowableCopyableOnlyToVectorCase+ , expectDeferredTypeError+ "Copyable alone does not permit fixed materialization"+ "Movable CopyOnly"+ badFixedCopyableOnlyToVectorCase+ , expectDeferredTypeError+ "Movable alone does not permit copying through a shared borrow"+ "Copyable NonCopyable"+ badNonCopyableCopyAtCase+ , expectDeferredTypeError+ "Movable alone does not permit copying through a mutable borrow"+ "Copyable NonCopyable"+ badNonCopyableCopyAtMutCase+ ]+ where+ expectDeferredTypeError description expectedFragment value =+ testCase description do+ result <- Exception.try @Exception.SomeException (Exception.evaluate value)+ case result of+ Left exception ->+ assertBool+ ("unexpected deferred type error: " <> Exception.displayException exception)+ (expectedFragment `List.isInfixOf` Exception.displayException exception)+ Right _ ->+ assertFailure+ ("expected deferred type error containing " <> expectedFragment)
+ test/Data/Vector/Mutable/Growable/Linear/TypingCases.hs view
@@ -0,0 +1,172 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -O0 #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}+{-# OPTIONS_GHC -fdefer-type-errors -Wno-deferred-type-errors #-}++module Data.Vector.Mutable.Growable.Linear.TypingCases (+ module Data.Vector.Mutable.Growable.Linear.TypingCases,+) where++import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.Copyable (Copyable (copy))+import Control.Syntax.DataFlow qualified as DataFlow+import Data.Coerce (coerce)+import Data.Vector qualified as V+import Data.Vector.Mutable.Growable.Linear.Borrow qualified as Growable+import Data.Vector.Mutable.Linear.Borrow qualified as Fixed+import Prelude.Linear+import Unsafe.Linear qualified as Unsafe++newtype WrappedInt = WrappedInt Int++newtype CopyOnly = CopyOnly Int++instance Copyable CopyOnly where+ copy = Unsafe.coerce++newtype NonCopyable = NonCopyable Int++instance Consumable NonCopyable where+ consume = Unsafe.toLinear \_ -> ()++instance Dupable NonCopyable where+ dup2 = Unsafe.toLinear \value -> (value, value)++instance Movable NonCopyable where+ move = Unsafe.toLinear \value -> Ur value++badElementCoercion ::+ Growable.GrowableVector WrappedInt %1 ->+ Growable.GrowableVector Int+badElementCoercion = Unsafe.toLinear coerce++badGrowableToFixed ::+ Growable.GrowableVector Int %1 ->+ Fixed.Vector Int+badGrowableToFixed = Unsafe.toLinear coerce++badFixedToGrowable ::+ Fixed.Vector Int %1 ->+ Growable.GrowableVector Int+badFixedToGrowable = Unsafe.toLinear coerce++badGrowableToFixedUpcast ::+ Growable.GrowableVector Int %1 ->+ Fixed.Vector Int+badGrowableToFixedUpcast = upcast++badFixedToGrowableUpcast ::+ Fixed.Vector Int %1 ->+ Growable.GrowableVector Int+badFixedToGrowableUpcast = upcast++badLifetimeSwap ::+ forall α β.+ Mut α (Growable.GrowableVector Int) %1 ->+ Mut β (Growable.GrowableVector Int)+{-# NOINLINE badLifetimeSwap #-}+badLifetimeSwap =+ Unsafe.toLinear+ ( coerce ::+ Mut α (Growable.GrowableVector Int) ->+ Mut β (Growable.GrowableVector Int)+ )++badLifetimeSwapCase :: Int+badLifetimeSwapCase =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.empty ownerLinear)+ let !() = consume (badLifetimeSwap vector)+ pureAfter (consume (reclaim lend) `lseq` 0)++badSplit ::+ Mut α (Growable.GrowableVector Int) %1 ->+ Growable.GrowableVector (Mut α Int)+badSplit = split++badDuplicate ::+ Borrow bk α (Growable.GrowableVector Int) %1 ->+ Growable.GrowableVector Int+badDuplicate = copy++badContentEscapeCase :: Int+badContentEscapeCase =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.empty ownerLinear)+ (escaped, vector) <- Growable.withContent vector Control.pure+ let !() = consume escaped+ !() = consume vector+ pureAfter $+ case Growable.toVector (reclaim lend) of+ Ur frozen -> V.length frozen++badSharedContentEscapeCase :: Int+badSharedContentEscapeCase =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.empty ownerLinear)+ share vector & \(Ur sharedVector) -> Control.do+ (escaped, sharedVector) <-+ Growable.withContent sharedVector Control.pure+ let+ !() = consume escaped+ !() = consume sharedVector+ pureAfter $+ case Growable.toVector (reclaim lend) of+ Ur frozen -> V.length frozen++badGrowableCopyableOnlyToVectorCase :: Int+badGrowableCopyableOnlyToVectorCase =+ linearly \linear ->+ case Growable.toVector+ (Growable.fromVector (V.singleton (CopyOnly 1)) linear) of+ Ur frozen -> V.length frozen++badFixedCopyableOnlyToVectorCase :: Int+badFixedCopyableOnlyToVectorCase =+ linearly \linear ->+ case Fixed.toVector+ (Fixed.fromVector (V.singleton (CopyOnly 1)) linear) of+ Ur frozen -> V.length frozen++badNonCopyableCopyAtCase :: Int+badNonCopyableCopyAtCase =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ ( Growable.fromVector+ (V.singleton (NonCopyable 1))+ ownerLinear+ )+ share vector & \(Ur shared) -> Control.do+ Ur _ <- Growable.copyAt 0 shared+ let !() = consume shared+ pureAfter (consume (reclaim lend) `lseq` 0)++badNonCopyableCopyAtMutCase :: Int+badNonCopyableCopyAtMutCase =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ ( Growable.fromVector+ (V.singleton (NonCopyable 1))+ ownerLinear+ )+ (Ur (NonCopyable copied), vector) <- Growable.copyAtMut 0 vector+ let !() = consume vector+ pureAfter (consume (reclaim lend) `lseq` copied)
test/Data/Vector/Mutable/Linear/BorrowSpec.hs view
@@ -11,14 +11,18 @@ module Data.Vector.Mutable.Linear.BorrowSpec, ) where +import Control.Exception qualified as Exception import Control.Functor.Linear qualified as Control import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.BO.Unsafe (Alias (UnsafeAlias)) import Control.Monad.Borrow.Pure.Copyable import Control.Syntax.DataFlow qualified as DataFlow import Data.Bifunctor.Linear qualified as Bi+import Data.IORef (IORef, modifyIORef', newIORef, readIORef) import Data.List qualified as List import Data.Vector qualified as V import Data.Vector.Mutable.Linear.Borrow qualified as VL+import GHC.IO (unsafePerformIO) import Prelude.Linear import Test.Falsify.Generator qualified as G import Test.Falsify.Predicate qualified as P@@ -27,9 +31,154 @@ import Test.Tasty (TestTree, testGroup) import Test.Tasty.Falsify (testProperty) import Test.Tasty.HUnit+import Unsafe.Linear qualified as Unsafe import Prelude qualified as NonLinear -qsortVec :: (Ord a, Copyable a) => V.Vector a -> V.Vector a+data MoveTracked = MoveTracked !(IORef Int) !Int !Bool++instance Consumable MoveTracked where+ consume = Unsafe.toLinear \_ -> ()++instance Dupable MoveTracked where+ dup2 = Unsafe.toLinear \value -> (value, value)++instance Movable MoveTracked where+ move =+ Unsafe.toLinear \(MoveTracked moves value _) ->+ case unsafePerformIO (modifyIORef' moves NonLinear.succ) of+ () -> Ur (MoveTracked moves value True)++data CopyTracked = CopyTracked !(IORef Int) !(IORef Int) !Int++instance Copyable CopyTracked where+ copy =+ Unsafe.toLinear \(UnsafeAlias value@(CopyTracked copies retired _)) ->+ case unsafePerformIO do+ retirementCount <- readIORef retired+ if retirementCount == 0+ then modifyIORef' copies NonLinear.succ+ else NonLinear.error "copy invoked after source retirement" of+ () -> value++instance Consumable CopyTracked where+ consume =+ Unsafe.toLinear \(CopyTracked _ retired _) ->+ unsafePerformIO (modifyIORef' retired NonLinear.succ)++instance Dupable CopyTracked where+ dup2 = Unsafe.toLinear \value -> (value, value)++instance Movable CopyTracked where+ move =+ Unsafe.toLinear \value@(CopyTracked _ retired _) ->+ case unsafePerformIO (modifyIORef' retired NonLinear.succ) of+ () -> Ur value++materializeMoveTracked :: IORef Int -> [(Int, Bool)]+materializeMoveTracked moves =+ linearly \linear ->+ case VL.toVector+ ( VL.fromVector+ (V.fromList [MoveTracked moves 10 False, MoveTracked moves 20 False])+ linear+ ) of+ Ur vector ->+ NonLinear.map+ (\(MoveTracked _ value wasMoved) -> (value, wasMoved))+ (V.toList vector)++discardMaterializedMoveTracked :: IORef Int -> ()+discardMaterializedMoveTracked moves =+ linearly \linear ->+ case VL.toVector+ ( VL.fromVector+ (V.fromList [MoveTracked moves 10 False, MoveTracked moves 20 False])+ linear+ ) of+ Ur _ -> ()++test_materialization :: TestTree+test_materialization =+ testGroup+ "materialization"+ [ testCase "invokes move for every owned element" do+ moves <- newIORef 0+ materializeMoveTracked moves @?= [(10, True), (20, True)]+ moveCount <- readIORef moves+ moveCount @?= 2+ , testCase "completes moves even when the result is discarded" do+ moves <- newIORef 0+ _ <- Exception.evaluate (discardMaterializedMoveTracked moves)+ moveCount <- readIORef moves+ moveCount @?= 2+ ]++copyAtMutValue :: Int -> [Int] -> (Int, [Int])+copyAtMutValue i xs = linearly \lin -> DataFlow.do+ (lin, lin') <- dup lin+ vec <- VL.fromList xs lin+ runBO lin' Control.do+ (mvec, lend) <- borrowM vec+ (Ur x, mvec) <- VL.copyAtMut i mvec+ let !() = consume mvec+ pureAfter (x, unur $ VL.toList (reclaim lend))++retireCopiedResult ::+ (Ur CopyTracked, Mut α (VL.Vector CopyTracked)) %1 ->+ VL.Vector CopyTracked %1 ->+ Int+retireCopiedResult =+ Unsafe.toLinear2 \(copiedResult, borrowed) owner ->+ consume borrowed `lseq`+ case VL.toVector owner of+ Ur frozen ->+ case V.length frozen of+ !_ ->+ case copiedResult of+ Ur (CopyTracked _ _ value) -> value++copyAtMutAfterRetirement :: IORef Int -> IORef Int -> Int+copyAtMutAfterRetirement copies retired =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ (VL.fromList [CopyTracked copies retired 10] ownerLinear)+ copiedResult <- VL.copyAtMut 0 vector+ pureAfter (retireCopiedResult copiedResult (reclaim lend))++assertCopyAtMutBoundsError :: Int -> Assertion+assertCopyAtMutBoundsError i = do+ result <- Exception.try @Exception.ErrorCall $ Exception.evaluate $ copyAtMutValue i [10, 20, 30]+ case result of+ Left exception ->+ assertBool+ ("unexpected error: " <> Exception.displayException exception)+ (("get: index " <> show i <> " out of bound: 3") `List.isPrefixOf` Exception.displayException exception)+ Right value -> assertFailure ("expected bounds error, got " <> show value)++test_copyAtMut :: TestTree+test_copyAtMut =+ testGroup+ "copyAtMut"+ [ testCase "copies the selected element and preserves the vector" do+ copyAtMutValue 1 [10, 20, 30] @?= (20, [10, 20, 30])+ , testCase "completes copying before mutable recovery" do+ copies <- newIORef 0+ retired <- newIORef 0+ copyAtMutAfterRetirement copies retired @?= 10+ copyCount <- readIORef copies+ copyCount @?= 1+ retirementCount <- readIORef retired+ retirementCount @?= 1+ , testCase "rejects a negative index" do+ assertCopyAtMutBoundsError (-1)+ , testCase "rejects an index at the upper bound" do+ assertCopyAtMutBoundsError 3+ ]++qsortVec :: (Ord a, Copyable a, Movable a) => V.Vector a -> V.Vector a qsortVec v = unur $ linearly \lin -> DataFlow.do (l1, l2) <- dup lin runBO l1 Control.do@@ -167,3 +316,24 @@ test_example3 = testCase "example3" do example3 @?= (12, [12, 1, 7])++discardingScopes :: (Int, [Int])+discardingScopes = linearly \lin -> DataFlow.do+ (lin, lin') <- dup lin+ vec <- VL.fromList [10, 20, 30] lin+ runBO lin' Control.do+ (mvec, lend) <- borrowM vec+ mvec <- reborrowing_ mvec \mvec -> Control.do+ mvec <- sharing_ mvec \shared ->+ consume Control.<$> parBO (VL.copyAt 0 shared) (VL.copyAt 1 shared)+ mvec <- VL.modify 0 (+ 1) mvec+ Control.pure $ consume mvec+ mvec <- VL.modify 2 (+ 2) mvec+ let !(Ur svec) = share mvec+ Ur n <- VL.copyAt 0 svec+ pureAfter $ (n, unur $ VL.toList (reclaim lend))++test_discardingScopes :: TestTree+test_discardingScopes =+ testCase "result-discarding scopes restore the outer mutable borrow" do+ discardingScopes @?= (11, [11, 20, 32])
+ test/Data/Vector/Unboxed/Mutable/Growable/Linear/BorrowSpec.hs view
@@ -0,0 +1,948 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE LinearTypes #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}++module Data.Vector.Unboxed.Mutable.Growable.Linear.BorrowSpec (+ module Data.Vector.Unboxed.Mutable.Growable.Linear.BorrowSpec,+) where++import Control.Exception qualified as Exception+import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.BO.Unsafe (Alias (..))+import Control.Monad.Borrow.Pure.Copyable (Copyable (copy), copyMut)+import Control.Syntax.DataFlow qualified as DataFlow+import Data.IORef (IORef, modifyIORef', newIORef, readIORef)+import Data.List qualified as List+import Data.Ref.Linear qualified as Ref+import Data.Ref.Linear.Borrow qualified as RefBorrow+import Data.Vector.Unboxed qualified as U+import Data.Vector.Unboxed.Mutable qualified as UM+import Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow qualified as Growable+import Data.Vector.Unboxed.Mutable.Growable.Linear.TypingCases+import Data.Vector.Unboxed.Mutable.Linear.Borrow qualified as Fixed+import GHC.IO (unsafePerformIO)+import Prelude.Linear+import PureBorrow.Internal.Bench.Unboxed qualified as UnboxedBench+import Test.Falsify.Generator qualified as G+import Test.Falsify.Predicate qualified as P+import Test.Falsify.Property qualified as F+import Test.Falsify.Range qualified as G+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.Falsify (testProperty)+import Test.Tasty.HUnit+import Unsafe.Linear qualified as Unsafe+import Prelude qualified as NonLinear++data Operation+ = Push !Int+ | Extend !Int !Int+ | Reserve !Int+ | ReserveAdditional !Int+ | Replace !Int !Int+ deriving (Show)++decodeOperation :: Int -> Operation+decodeOperation seed =+ case seed `NonLinear.mod` 5 of+ 0 -> Push seed+ 1 -> Extend seed (seed + 1)+ 2 -> Reserve (NonLinear.abs seed `NonLinear.mod` 32)+ 3 -> ReserveAdditional (NonLinear.abs seed `NonLinear.mod` 16)+ _ -> Replace seed (-seed)++applyOperations ::+ [Operation] ->+ Mut α (Growable.GrowableVector Int) %1 ->+ BO α (Mut α (Growable.GrowableVector Int))+applyOperations [] vector = Control.pure vector+applyOperations (operation : operations) vector =+ case operation of+ Push value -> Control.do+ vector <- Growable.push value vector+ applyOperations operations vector+ Extend first second -> Control.do+ vector <- Growable.extend (U.fromList [first, second]) vector+ applyOperations operations vector+ Reserve requested -> Control.do+ vector <- Growable.reserve requested vector+ applyOperations operations vector+ ReserveAdditional additional -> Control.do+ vector <- Growable.reserveAdditional additional vector+ applyOperations operations vector+ Replace rawIndex value ->+ case Growable.size vector of+ (Ur 0, vector) -> applyOperations operations vector+ (Ur logicalSize, vector) -> Control.do+ let !index = NonLinear.abs rawIndex `NonLinear.mod` logicalSize+ (old, vector) <- Growable.set index value vector+ applyOperations operations (consume old `lseq` vector)++applyModel :: [Operation] -> [Int] -> [Int]+applyModel operations initial = NonLinear.foldl step initial operations+ where+ step values = \case+ Push value -> values <> [value]+ Extend first second -> values <> [first, second]+ Reserve _ -> values+ ReserveAdditional _ -> values+ Replace _ _ | NonLinear.null values -> values+ Replace rawIndex value ->+ let !index = NonLinear.abs rawIndex `NonLinear.mod` NonLinear.length values+ in case NonLinear.splitAt index values of+ (prefix, _ : suffix) -> prefix <> (value : suffix)+ (_, []) -> values++freezeList :: Growable.GrowableVector Int %1 -> [Int]+freezeList vector =+ case Growable.toVector vector of+ Ur frozen -> U.toList frozen++freezeLength :: Growable.GrowableVector Int %1 -> Int+freezeLength vector =+ case Growable.toVector vector of+ Ur frozen -> U.length frozen++runOperations :: Int -> [Operation] -> ([Int], Int, Int)+runOperations initialCapacity operations =+ unur $ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.withCapacity initialCapacity ownerLinear)+ vector <- applyOperations operations vector+ Growable.size vector & \(Ur logicalSize, vector) ->+ Growable.capacity vector & \(Ur finalCapacity, vector) -> DataFlow.do+ consume vector+ pureAfter $+ case Growable.toVector (reclaim lend) of+ Ur frozen ->+ Ur (U.toList frozen, logicalSize, finalCapacity)++test_model :: TestTree+test_model =+ testProperty "matches a list model across growth and mutation" do+ initialCapacity <- F.gen $ G.int $ G.between (0, 16)+ seeds <- F.gen $ G.list (G.between (0, 100)) $ G.int $ G.between (-100, 100)+ let !operations = NonLinear.map decodeOperation seeds+ !(actual, logicalSize, finalCapacity) =+ runOperations initialCapacity operations+ !expected = applyModel operations []+ F.assert $ P.expect expected P..$ ("contents", actual)+ F.assert $+ P.expect (NonLinear.length expected) P..$ ("logical size", logicalSize)+ F.assert $+ P.satisfies+ ("capacity >= logical size", (NonLinear.>= logicalSize))+ P..$ ("capacity", finalCapacity)++test_construction :: TestTree+test_construction =+ testGroup+ "construction"+ [ testCase "empty has no initialized elements" do+ linearly (\linear -> freezeList (Growable.empty linear)) @?= []+ , testCase "constant initializes the complete logical prefix" do+ linearly+ (\linear -> freezeList (Growable.constant 3 (7 :: Int) linear))+ @?= [7, 7, 7]+ , testCase "fromList moves every element" do+ linearly+ (\linear -> freezeList (Growable.fromList [4, 5, 6 :: Int] linear))+ @?= [4, 5, 6]+ , testCase "fromVector copies every element" do+ linearly+ ( \linear ->+ freezeList+ (Growable.fromVector (U.fromList [8, 9 :: Int]) linear)+ )+ @?= [8, 9]+ , testCase "unsafe mutable adoption preserves a nonzero slice offset across growth" do+ linearly+ ( \linear ->+ let source =+ unsafePerformIO do+ whole <- U.thaw (U.fromList [99, 1, 2, 88 :: Int])+ NonLinear.pure (UM.unsafeSlice 1 2 whole)+ in growAdopted source linear+ )+ @?= [1, 2, 3]+ ]++growAdopted ::+ UM.IOVector Int %1 ->+ Linearly %1 ->+ [Int]+growAdopted =+ Unsafe.toLinear2 \source linear ->+ unur $ DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.unsafeFromMutable source ownerLinear)+ vector <- Growable.push 3 vector+ let !() = consume vector+ pureAfter $+ case Growable.toVector (reclaim lend) of+ Ur frozen -> Ur (U.toList frozen)++mirroredSurface :: ((Int, Int, Int, Int), [Int])+mirroredSurface =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.fromList [1, 2, 3] ownerLinear)+ (Ur middle, vector) <-+ reborrowing vector \short -> Control.do+ element <- Growable.get 1 short+ Control.pure (copyMut element)+ (Ur first, vector) <-+ reborrowing vector \short -> Control.do+ element <- Growable.head short+ Control.pure (copyMut element)+ (Ur final, vector) <-+ reborrowing vector \short -> Control.do+ element <- Growable.last short+ Control.pure (copyMut element)+ (Ur copied, vector) <- Growable.copyAtMut 1 vector+ (old, vector) <- Growable.set 1 20 vector+ let !() = consume old+ ((), vector) <-+ Growable.update+ 1+ (\ !value -> Control.pure ((), value + 1))+ vector+ vector <- Growable.modify 0 (+ 10) vector+ vector <- Growable.swap vector 0 2+ let !() = consume vector+ pureAfter+ ( (middle, first, final, copied)+ , freezeList (reclaim lend)+ )++test_mirroredSurface :: TestTree+test_mirroredSurface =+ testCase "mirrors fixed unboxed reads and mutation" do+ mirroredSurface @?= ((2, 1, 3, 2), [3, 21, 11])++contentRoundTrip :: ((Int, Int), [Int])+contentRoundTrip =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromVector (U.fromList [10, 20, 30]) ownerLinear)+ ((logicalSize, first), vector) <-+ Growable.withContent vector \contents -> Control.do+ Fixed.size contents & \(Ur logicalSize, contents) -> Control.do+ (Ur first, contents) <- Fixed.copyAtMut 0 contents+ contents <- Fixed.modify 1 (+ 1) contents+ Control.pure (consume contents `lseq` (logicalSize, first))+ vector <- Growable.push 40 vector+ let !() = consume vector+ pureAfter+ ( (logicalSize, first)+ , freezeList (reclaim lend)+ )++directMutableProjection :: (Int, [Int])+directMutableProjection =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.withCapacity 8 ownerLinear)+ vector <- Growable.extend (U.fromList [3, 4, 5]) vector+ Fixed.size (Growable.getContents vector) & \(Ur logicalSize, contents) -> Control.do+ contents <- Fixed.modify 0 (+ 10) contents+ let !() = consume contents+ pureAfter+ ( logicalSize+ , freezeList (reclaim lend)+ )++parallelSplitContent :: [Int]+parallelSplitContent =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.fromList [1, 2, 3, 4] ownerLinear)+ vector <- Growable.withContent_ vector \contents -> Control.do+ let !(left, right) = Fixed.splitAt 2 contents+ consume+ Control.<$> parBO+ (Fixed.modify 0 (+ 10) left)+ (Fixed.modify 0 (+ 20) right)+ vector <- Growable.push 5 vector+ let !() = consume vector+ pureAfter (freezeList (reclaim lend))++sharedContentProjection :: ((Int, Int), [Int])+sharedContentProjection =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.fromList [5, 6, 7] ownerLinear)+ share vector & \(Ur sharedVector) -> Control.do+ (first, returnedSharedVector) <-+ Growable.withContent sharedVector \linearContents ->+ move linearContents & \(Ur contents) -> Control.do+ Ur first <- Fixed.copyAt 0 contents+ Control.pure first+ move returnedSharedVector & \(Ur sharedVector) -> Control.do+ Ur final <- Fixed.copyAt 2 (Growable.getContents sharedVector)+ let !() = consume sharedVector+ pureAfter ((first, final), freezeList (reclaim lend))++countedContentScope :: IORef Int -> [Int]+countedContentScope counter =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.fromList [1, 2, 3] ownerLinear)+ vector <- Growable.withContent_ vector \contents ->+ case unsafePerformIO (modifyIORef' counter NonLinear.succ) of+ () -> Control.pure (consume contents)+ vector <- Growable.push 4 vector+ let !() = consume vector+ pureAfter (freezeList (reclaim lend))++test_contentProjection :: TestTree+test_contentProjection =+ testGroup+ "content projection"+ [ testCase "exposes only initialized content and restores growth" do+ contentRoundTrip @?= ((3, 10), [10, 21, 30, 40])+ , testCase "direct mutable projection exposes only the initialized prefix" do+ directMutableProjection @?= (3, [13, 4, 5])+ , testCase "permits fixed content to split before growth resumes" do+ parallelSplitContent @?= [11, 2, 23, 4, 5]+ , testCase "preserves shared content access" do+ sharedContentProjection @?= ((5, 7), [5, 6, 7])+ , testCase "runs a content callback exactly once" do+ counter <- newIORef 0+ countedContentScope counter @?= [1, 2, 3, 4]+ count <- readIORef counter+ count @?= 1+ ]++data Tracked = Tracked !(IORef Int) !Int++instance Consumable (U.DoNotUnboxLazy Tracked) where+ consume =+ Unsafe.toLinear \(U.DoNotUnboxLazy (Tracked counter _)) ->+ unsafePerformIO (modifyIORef' counter NonLinear.succ)++instance Consumable (U.DoNotUnboxStrict Tracked) where+ consume =+ Unsafe.toLinear \(U.DoNotUnboxStrict (Tracked counter _)) ->+ unsafePerformIO (modifyIORef' counter NonLinear.succ)++trackedGrowth :: IORef Int -> Int+trackedGrowth counter =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.withCapacity 0 ownerLinear)+ vector <- Growable.push (U.DoNotUnboxLazy (Tracked counter 10)) vector+ vector <- Growable.push (U.DoNotUnboxLazy (Tracked counter 20)) vector+ vector <- Growable.reserve 32 vector+ vector <- Growable.push (U.DoNotUnboxLazy (Tracked counter 30)) vector+ vector <- Growable.reserveAdditional 64 vector+ (oldLabel, vector) <-+ Growable.update+ 1+ ( \(U.DoNotUnboxLazy (Tracked elementCounter label)) ->+ case dup label of+ (oldLabel, replacementLabel) ->+ Control.pure+ ( oldLabel+ , U.DoNotUnboxLazy+ (Tracked elementCounter (replacementLabel + 1))+ )+ )+ vector+ let !() = consume vector+ pureAfter (consume (reclaim lend) `lseq` oldLabel)++strictTrackedGrowth :: IORef Int -> ()+strictTrackedGrowth counter =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.withCapacity 0 ownerLinear)+ vector <- Growable.push (U.DoNotUnboxStrict (Tracked counter 10)) vector+ vector <- Growable.push (U.DoNotUnboxStrict (Tracked counter 20)) vector+ vector <- Growable.reserve 32 vector+ vector <- Growable.push (U.DoNotUnboxStrict (Tracked counter 30)) vector+ vector <- Growable.reserveAdditional 64 vector+ let !() = consume vector+ pureAfter (consume (reclaim lend))++data CopyTracked = CopyTracked !(IORef Int) !(IORef Int) !Int++type UnboxedCopyTracked = U.DoNotUnboxLazy CopyTracked++instance Copyable UnboxedCopyTracked where+ copy =+ Unsafe.toLinear+ \(UnsafeAlias value@(U.DoNotUnboxLazy (CopyTracked copies retired _))) ->+ case unsafePerformIO do+ retirementCount <- readIORef retired+ if retirementCount == 0+ then modifyIORef' copies NonLinear.succ+ else NonLinear.error "copy invoked after source retirement" of+ () -> value++instance Consumable UnboxedCopyTracked where+ consume =+ Unsafe.toLinear+ \(U.DoNotUnboxLazy (CopyTracked _ retired _)) ->+ unsafePerformIO (modifyIORef' retired NonLinear.succ)++data MoveTracked = MoveTracked !(IORef Int) !Int !Bool++type UnboxedMoveTracked = U.DoNotUnboxLazy MoveTracked++instance Consumable UnboxedMoveTracked where+ consume = Unsafe.toLinear \_ -> ()++instance Dupable UnboxedMoveTracked where+ dup2 = Unsafe.toLinear \value -> (value, value)++instance Movable UnboxedMoveTracked where+ move =+ Unsafe.toLinear+ \(U.DoNotUnboxLazy (MoveTracked moves value _)) ->+ case unsafePerformIO (modifyIORef' moves NonLinear.succ) of+ () -> Ur (U.DoNotUnboxLazy (MoveTracked moves value True))++materializeMoveTracked :: IORef Int -> [(Int, Bool)]+materializeMoveTracked moves =+ NonLinear.map+ ( \(U.DoNotUnboxLazy (MoveTracked _ value wasMoved)) ->+ (value, wasMoved)+ )+ ( U.toList $+ unur $+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.empty ownerLinear)+ vector <-+ Growable.push+ (U.DoNotUnboxLazy (MoveTracked moves 10 False))+ vector+ vector <-+ Growable.push+ (U.DoNotUnboxLazy (MoveTracked moves 20 False))+ vector+ let !() = consume vector+ pureAfter (Growable.toVector (reclaim lend))+ )++discardMaterializedMoveTracked :: IORef Int -> ()+discardMaterializedMoveTracked moves =+ linearly \linear ->+ case Growable.toVector+ ( Growable.fromVector+ ( U.fromList+ [ U.DoNotUnboxLazy (MoveTracked moves 10 False)+ , U.DoNotUnboxLazy (MoveTracked moves 20 False)+ ]+ )+ linear+ ) of+ Ur _ -> ()++immutableCopyLifecycle :: IORef Int -> IORef Int -> ()+immutableCopyLifecycle copies retired =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ ( Growable.fromVector+ ( U.fromList+ [ U.DoNotUnboxLazy (CopyTracked copies retired 10)+ , U.DoNotUnboxLazy (CopyTracked copies retired 20)+ ]+ )+ ownerLinear+ )+ vector <- Growable.reserve 64 vector+ vector <-+ Growable.extend+ (U.singleton (U.DoNotUnboxLazy (CopyTracked copies retired 30)))+ vector+ vector <- Growable.reserveAdditional 64 vector+ let !() = consume vector+ pureAfter (consume (reclaim lend))++retireCopiedResult ::+ (Ur UnboxedCopyTracked, Mut α (Growable.GrowableVector UnboxedCopyTracked)) %1 ->+ Growable.GrowableVector UnboxedCopyTracked %1 ->+ Int+retireCopiedResult =+ Unsafe.toLinear2 \(copiedResult, borrowed) owner ->+ consume borrowed `lseq`+ consume owner `lseq`+ case copiedResult of+ Ur (U.DoNotUnboxLazy (CopyTracked _ _ value)) -> value++copyAtMutAfterRetirement :: IORef Int -> IORef Int -> Int+copyAtMutAfterRetirement copies retired =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ ( Growable.fromVector+ (U.singleton (U.DoNotUnboxLazy (CopyTracked copies retired 10)))+ ownerLinear+ )+ copiedResult <- Growable.copyAtMut 0 vector+ pureAfter (retireCopiedResult copiedResult (reclaim lend))++test_copyAtMutStrictness :: TestTree+test_copyAtMutStrictness =+ testCase "copyAtMut completes copying before mutable recovery" do+ copies <- newIORef 0+ retired <- newIORef 0+ copyAtMutAfterRetirement copies retired @?= 10+ copyCount <- readIORef copies+ copyCount @?= 1+ retirementCount <- readIORef retired+ retirementCount @?= 1++gcOwnedImmutableLifecycle :: IORef Int -> ()+gcOwnedImmutableLifecycle retired =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ ( Growable.fromVector+ (U.singleton (U.DoNotUnboxLazy (Tracked retired 10)))+ ownerLinear+ )+ vector <-+ Growable.extend+ ( U.fromList+ [ U.DoNotUnboxLazy (Tracked retired 20)+ , U.DoNotUnboxLazy (Tracked retired 30)+ ]+ )+ vector+ let !() = consume vector+ pureAfter (consume (reclaim lend))++newtype LinearElement = LinearElement (Ref.Ref Int)++type BoxedLinearElement = U.DoNotUnboxLazy LinearElement++instance Consumable BoxedLinearElement where+ consume =+ Unsafe.toLinear \(U.DoNotUnboxLazy (LinearElement ref)) ->+ consume ref++asBorrowedRef ::+ Mut α BoxedLinearElement %1 ->+ Mut α (Ref.Ref Int)+asBorrowedRef = upcast++borrowedRefAcrossGrowth :: Int+borrowedRefAcrossGrowth =+ linearly \linear -> DataFlow.do+ (refLinear, remainingLinear) <- dup linear+ (ownerLinear, runLinear) <- dup remainingLinear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.empty ownerLinear)+ vector <-+ Growable.push+ (U.DoNotUnboxLazy (LinearElement (Ref.new 1 refLinear)))+ vector+ vector <- Growable.reserve 64 vector+ ((), vector) <-+ reborrowing vector \short -> Control.do+ element <- Growable.get 0 short+ ref <- RefBorrow.modify (+ 41) (asBorrowedRef element)+ Control.pure (consume ref)+ (observed, vector) <-+ reborrowing vector \short -> Control.do+ element <- Growable.get 0 short+ RefBorrow.copyRef (asBorrowedRef element)+ let !() = consume vector+ pureAfter (consume (reclaim lend) `lseq` observed)++test_destructiveGrowth :: TestTree+test_destructiveGrowth =+ testGroup+ "destructive growth"+ [ testCase "moves non-Copyable capabilities and retires each exactly once" do+ counter <- newIORef 0+ oldLabel <- Exception.evaluate (trackedGrowth counter)+ oldLabel @?= 20+ retired <- readIORef counter+ retired @?= 3+ , testCase "moves strict boxed-backed capabilities and retires each exactly once" do+ counter <- newIORef 0+ _ <- Exception.evaluate (strictTrackedGrowth counter)+ retired <- readIORef counter+ retired @?= 3+ , testCase "preserves a nested Ref identity across reallocation" do+ borrowedRefAcrossGrowth @?= 42+ , testCase "ordinary constructors need no Copyable instance" do+ counter <- newIORef 0+ _ <-+ Exception.evaluate $+ linearly \linear ->+ consume+ ( Growable.constant+ 2+ (U.DoNotUnboxLazy (Tracked counter 10))+ linear+ )+ retired <- readIORef counter+ retired @?= 2+ , testCase "ordinary immutable sources need no Copyable instance" do+ counter <- newIORef 0+ _ <- Exception.evaluate (gcOwnedImmutableLifecycle counter)+ retired <- readIORef counter+ retired @?= 3+ , testCase "ordinary immutable copies do not invoke Copyable" do+ copies <- newIORef 0+ retired <- newIORef 0+ _ <- Exception.evaluate (immutableCopyLifecycle copies retired)+ copyCount <- readIORef copies+ retiredCount <- readIORef retired+ copyCount @?= 0+ retiredCount @?= 3+ , testCase "materialization invokes move for every owned element" do+ moves <- newIORef 0+ materializeMoveTracked moves @?= [(10, True), (20, True)]+ moveCount <- readIORef moves+ moveCount @?= 2+ , testCase "discarding materialization still invokes every move" do+ moves <- newIORef 0+ _ <- Exception.evaluate (discardMaterializedMoveTracked moves)+ moveCount <- readIORef moves+ moveCount @?= 2+ ]++assertErrorPrefix :: NonLinear.String -> a -> Assertion+assertErrorPrefix expectedPrefix value = do+ result <- Exception.try @Exception.ErrorCall $ Exception.evaluate value+ case result of+ Left exception ->+ assertBool+ ("unexpected error: " <> Exception.displayException exception)+ (expectedPrefix `List.isPrefixOf` Exception.displayException exception)+ Right _ -> assertFailure ("expected error beginning with " <> expectedPrefix)++copyOutOfBounds :: Int -> Int+copyOutOfBounds index =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM (Growable.fromVector (U.fromList [10, 20, 30]) ownerLinear)+ (Ur value, vector) <- Growable.copyAtMut index vector+ let !() = consume vector+ pureAfter (value + freezeLength (reclaim lend))++getOutOfBounds :: Int -> Int+getOutOfBounds index =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.fromList [10, 20, 30] ownerLinear)+ (Ur value, vector) <-+ reborrowing vector \short -> Control.do+ element <- Growable.get index short+ Control.pure (copyMut element)+ let !() = consume vector+ pureAfter (value + freezeLength (reclaim lend))++setOutOfBounds :: Int -> Int+setOutOfBounds index =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.fromList [10, 20, 30] ownerLinear)+ (old, vector) <- Growable.set index 0 vector+ let !() = consume vector+ pureAfter (old + freezeLength (reclaim lend))++updateOutOfBounds :: Int -> Int+updateOutOfBounds index =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.fromList [10, 20, 30] ownerLinear)+ ((), vector) <-+ Growable.update+ index+ (\value -> Control.pure ((), value))+ vector+ let !() = consume vector+ pureAfter (freezeLength (reclaim lend))++swapOutOfBounds :: Int -> Int -> Int+swapOutOfBounds first second =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.fromList [10, 20, 30] ownerLinear)+ vector <- Growable.swap vector first second+ let !() = consume vector+ pureAfter (freezeLength (reclaim lend))++emptyHead :: Int+emptyHead =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.empty ownerLinear)+ element <- Growable.head vector+ let !(Ur value) = copyMut element+ pureAfter (value + freezeLength (reclaim lend))++emptyLast :: Int+emptyLast =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.empty ownerLinear)+ element <- Growable.last vector+ let !(Ur value) = copyMut element+ pureAfter (value + freezeLength (reclaim lend))++reserveOutOfBounds :: Int -> Int+reserveOutOfBounds requested =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.empty ownerLinear)+ vector <- Growable.reserve requested vector+ let !() = consume vector+ pureAfter (freezeLength (reclaim lend))++reserveAdditionalOutOfBounds :: Int -> Int+reserveAdditionalOutOfBounds additional =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.empty ownerLinear)+ vector <- Growable.reserveAdditional additional vector+ let !() = consume vector+ pureAfter (freezeLength (reclaim lend))++capacityTransitions :: (Int, Int, Int, Int)+capacityTransitions =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.withCapacity @Int 2 ownerLinear)+ Growable.capacity vector & \(Ur initial, vector) -> Control.do+ vector <- Growable.push 1 vector+ Growable.capacity vector & \(Ur afterFirst, vector) -> Control.do+ vector <- Growable.push 2 vector+ Growable.capacity vector & \(Ur afterSecond, vector) -> Control.do+ vector <- Growable.push 3 vector+ Growable.capacity vector & \(Ur afterGrowth, vector) -> DataFlow.do+ consume vector+ pureAfter+ ( consume (reclaim lend) `lseq`+ (initial, afterFirst, afterSecond, afterGrowth)+ )++test_bounds :: TestTree+test_bounds =+ testGroup+ "bounds"+ [ testCase "get rejects a negative index" do+ assertErrorPrefix+ "get: index -1 out of bounds for length 3"+ (getOutOfBounds (-1))+ , testCase "get rejects the upper bound" do+ assertErrorPrefix+ "get: index 3 out of bounds for length 3"+ (getOutOfBounds 3)+ , testCase "copyAtMut rejects a negative index" do+ assertErrorPrefix+ "copyAtMut: index -1 out of bounds for length 3"+ (copyOutOfBounds (-1))+ , testCase "copyAtMut rejects the upper bound" do+ assertErrorPrefix+ "copyAtMut: index 3 out of bounds for length 3"+ (copyOutOfBounds 3)+ , testCase "set rejects a negative index" do+ assertErrorPrefix+ "set: index -1 out of bounds for length 3"+ (setOutOfBounds (-1))+ , testCase "set rejects the upper bound" do+ assertErrorPrefix+ "set: index 3 out of bounds for length 3"+ (setOutOfBounds 3)+ , testCase "update rejects an invalid index" do+ assertErrorPrefix+ "update: index 3 out of bounds for length 3"+ (updateOutOfBounds 3)+ , testCase "swap rejects an invalid index" do+ assertErrorPrefix+ "swap: indices (0,3) out of bounds for length 3"+ (swapOutOfBounds 0 3)+ , testCase "head rejects an empty vector" do+ assertErrorPrefix+ "get: index 0 out of bounds for length 0"+ emptyHead+ , testCase "last rejects an empty vector" do+ assertErrorPrefix+ "last: empty vector"+ emptyLast+ , testCase "construction rejects negative capacity" do+ assertErrorPrefix+ "withCapacity: negative capacity -1"+ (linearly \linear -> consume (Growable.withCapacity @Int (-1) linear))+ , testCase "reserve rejects negative capacity" do+ assertErrorPrefix+ "reserve: negative capacity -1"+ (reserveOutOfBounds (-1))+ , testCase "reserveAdditional rejects a negative amount" do+ assertErrorPrefix+ "reserveAdditional: negative additional capacity -1"+ (reserveAdditionalOutOfBounds (-1))+ , testCase "push preserves exact capacity until one element exceeds it" do+ capacityTransitions @?= (2, 2, 2, 4)+ ]++test_typingBoundaries :: TestTree+test_typingBoundaries =+ testGroup+ "typing boundaries"+ [ expectDeferredTypeError+ "element role is nominal"+ "Couldn't match type"+ badElementCoercion+ , expectDeferredTypeError+ "growable and fixed vectors are representation-distinct"+ "Couldn't match representation of type"+ badGrowableToFixed+ , expectDeferredTypeError+ "fixed and growable vectors are representation-distinct"+ "Couldn't match representation of type"+ badFixedToGrowable+ , expectDeferredTypeError+ "growable cannot be upcast to fixed"+ "Couldn't match representation of type"+ badGrowableToFixedUpcast+ , expectDeferredTypeError+ "fixed cannot be upcast to growable"+ "Couldn't match representation of type"+ badFixedToGrowableUpcast+ , expectDeferredTypeError+ "unboxed and boxed growable vectors are representation-distinct"+ "Couldn't match representation of type"+ badUnboxedGrowableToBoxedGrowable+ , expectDeferredTypeError+ "boxed and unboxed growable vectors are representation-distinct"+ "Couldn't match representation of type"+ badBoxedGrowableToUnboxedGrowable+ , expectDeferredTypeError+ "unboxed growable cannot be upcast to boxed growable"+ "Couldn't match representation of type"+ badUnboxedGrowableToBoxedGrowableUpcast+ , expectDeferredTypeError+ "boxed growable cannot be upcast to unboxed growable"+ "Couldn't match representation of type"+ badBoxedGrowableToUnboxedGrowableUpcast+ , expectDeferredTypeError+ "borrow lifetime cannot be swapped"+ "Couldn't match type"+ badLifetimeSwapCase+ , expectDeferredTypeError+ "growable vector has no generic split"+ "DistributesAlias Growable.GrowableVector"+ badSplit+ , expectDeferredTypeError+ "growable vector cannot be copied"+ "cannot be copied!"+ badDuplicate+ , expectDeferredTypeError+ "mutable content cannot escape its scope"+ "Couldn't match type"+ badContentEscapeCase+ , expectDeferredTypeError+ "shared content cannot escape its scope"+ "Couldn't match type"+ badSharedContentEscapeCase+ , expectDeferredTypeError+ "Copyable alone does not permit growable materialization"+ "Movable (U.DoNotUnboxLazy CopyOnly)"+ badGrowableCopyableOnlyToVectorCase+ , expectDeferredTypeError+ "Copyable alone does not permit fixed materialization"+ "Movable (U.DoNotUnboxLazy CopyOnly)"+ badFixedCopyableOnlyToVectorCase+ , expectDeferredTypeError+ "Movable alone does not permit copying through a shared borrow"+ "Copyable (U.DoNotUnboxLazy NonCopyable)"+ badNonCopyableCopyAtCase+ , expectDeferredTypeError+ "Movable alone does not permit copying through a mutable borrow"+ "Copyable (U.DoNotUnboxLazy NonCopyable)"+ badNonCopyableCopyAtMutCase+ ]+ where+ expectDeferredTypeError description expectedFragment value =+ testCase description do+ result <- Exception.try @Exception.SomeException (Exception.evaluate value)+ case result of+ Left exception ->+ assertBool+ ("unexpected deferred type error: " <> Exception.displayException exception)+ (expectedFragment `List.isInfixOf` Exception.displayException exception)+ Right _ ->+ assertFailure+ ("expected deferred type error containing " <> expectedFragment)++test_benchmarkRoots :: TestTree+test_benchmarkRoots =+ testGroup+ "benchmark roots"+ [ testGroup+ ("length " <> show length_)+ [ testCase "no-growth direct and Pure Borrow roots agree" do+ let input =+ U.generate length_ (\index -> index `NonLinear.rem` 17)+ UnboxedBench.pureBorrowGrowableUnboxedNoGrowthKernel input+ @?= UnboxedBench.directGrowableUnboxedNoGrowthKernel input+ , testCase "forced-growth direct and Pure Borrow roots agree" do+ let input =+ U.generate length_ (\index -> index `NonLinear.rem` 17)+ direct@(_, finalCapacity) =+ UnboxedBench.directGrowableUnboxedGrowthKernel input+ UnboxedBench.pureBorrowGrowableUnboxedGrowthKernel input+ @?= direct+ finalCapacity @?= expectedGrowthCapacity length_+ , testCase "public-materialization roots agree" do+ let input =+ U.generate length_ (\index -> index `NonLinear.rem` 17)+ UnboxedBench.pureBorrowGrowableUnboxedMaterialization input+ @?= UnboxedBench.directGrowableUnboxedMaterialization input+ ]+ | length_ <- [0, 1, 257, 1024 * 1024]+ ]++expectedGrowthCapacity :: Int -> Int+expectedGrowthCapacity logicalSize = go 0+ where+ go capacity+ | capacity >= logicalSize = capacity+ | capacity <= 0 = go 1+ | otherwise = go (capacity * 2)
+ test/Data/Vector/Unboxed/Mutable/Growable/Linear/TypingCases.hs view
@@ -0,0 +1,205 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -O0 #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}+{-# OPTIONS_GHC -fdefer-type-errors -Wno-deferred-type-errors #-}++module Data.Vector.Unboxed.Mutable.Growable.Linear.TypingCases (+ module Data.Vector.Unboxed.Mutable.Growable.Linear.TypingCases,+) where++import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.Copyable (Copyable (copy))+import Control.Syntax.DataFlow qualified as DataFlow+import Data.Coerce (coerce)+import Data.Vector.Mutable.Growable.Linear.Borrow qualified as BoxedGrowable+import Data.Vector.Unboxed qualified as U+import Data.Vector.Unboxed.Mutable.Growable.Linear.Borrow qualified as Growable+import Data.Vector.Unboxed.Mutable.Linear.Borrow qualified as Fixed+import Prelude.Linear+import Unsafe.Linear qualified as Unsafe++newtype WrappedInt = WrappedInt Int++newtype CopyOnly = CopyOnly Int++type UnboxedCopyOnly = U.DoNotUnboxLazy CopyOnly++instance Copyable UnboxedCopyOnly where+ copy = Unsafe.coerce++newtype NonCopyable = NonCopyable Int++type UnboxedNonCopyable = U.DoNotUnboxLazy NonCopyable++instance Consumable UnboxedNonCopyable where+ consume = Unsafe.toLinear \_ -> ()++instance Dupable UnboxedNonCopyable where+ dup2 = Unsafe.toLinear \value -> (value, value)++instance Movable UnboxedNonCopyable where+ move = Unsafe.toLinear \value -> Ur value++badElementCoercion ::+ Growable.GrowableVector WrappedInt %1 ->+ Growable.GrowableVector Int+badElementCoercion = Unsafe.toLinear coerce++badGrowableToFixed ::+ Growable.GrowableVector Int %1 ->+ Fixed.Vector Int+badGrowableToFixed = Unsafe.toLinear coerce++badFixedToGrowable ::+ Fixed.Vector Int %1 ->+ Growable.GrowableVector Int+badFixedToGrowable = Unsafe.toLinear coerce++badGrowableToFixedUpcast ::+ Growable.GrowableVector Int %1 ->+ Fixed.Vector Int+badGrowableToFixedUpcast = upcast++badFixedToGrowableUpcast ::+ Fixed.Vector Int %1 ->+ Growable.GrowableVector Int+badFixedToGrowableUpcast = upcast++badUnboxedGrowableToBoxedGrowable ::+ Growable.GrowableVector Int %1 ->+ BoxedGrowable.GrowableVector Int+badUnboxedGrowableToBoxedGrowable = Unsafe.toLinear coerce++badBoxedGrowableToUnboxedGrowable ::+ BoxedGrowable.GrowableVector Int %1 ->+ Growable.GrowableVector Int+badBoxedGrowableToUnboxedGrowable = Unsafe.toLinear coerce++badUnboxedGrowableToBoxedGrowableUpcast ::+ Growable.GrowableVector Int %1 ->+ BoxedGrowable.GrowableVector Int+badUnboxedGrowableToBoxedGrowableUpcast = upcast++badBoxedGrowableToUnboxedGrowableUpcast ::+ BoxedGrowable.GrowableVector Int %1 ->+ Growable.GrowableVector Int+badBoxedGrowableToUnboxedGrowableUpcast = upcast++badLifetimeSwap ::+ forall α β.+ Mut α (Growable.GrowableVector Int) %1 ->+ Mut β (Growable.GrowableVector Int)+{-# NOINLINE badLifetimeSwap #-}+badLifetimeSwap =+ Unsafe.toLinear+ ( coerce ::+ Mut α (Growable.GrowableVector Int) ->+ Mut β (Growable.GrowableVector Int)+ )++badLifetimeSwapCase :: Int+badLifetimeSwapCase =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.empty ownerLinear)+ let !() = consume (badLifetimeSwap vector)+ pureAfter (consume (reclaim lend) `lseq` 0)++badSplit ::+ Mut α (Growable.GrowableVector Int) %1 ->+ Growable.GrowableVector (Mut α Int)+badSplit = split++badDuplicate ::+ Borrow bk α (Growable.GrowableVector Int) %1 ->+ Growable.GrowableVector Int+badDuplicate = copy++badContentEscapeCase :: Int+badContentEscapeCase =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.empty ownerLinear)+ (escaped, vector) <- Growable.withContent vector Control.pure+ let+ !() = consume escaped+ !() = consume vector+ pureAfter $+ case Growable.toVector (reclaim lend) of+ Ur frozen -> U.length frozen++badSharedContentEscapeCase :: Int+badSharedContentEscapeCase =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <- borrowM (Growable.empty ownerLinear)+ share vector & \(Ur sharedVector) -> Control.do+ (escaped, sharedVector) <-+ Growable.withContent sharedVector Control.pure+ let+ !() = consume escaped+ !() = consume sharedVector+ pureAfter $+ case Growable.toVector (reclaim lend) of+ Ur frozen -> U.length frozen++badGrowableCopyableOnlyToVectorCase :: Int+badGrowableCopyableOnlyToVectorCase =+ linearly \linear ->+ case Growable.toVector+ ( Growable.fromVector+ (U.singleton (U.DoNotUnboxLazy (CopyOnly 1)))+ linear+ ) of+ Ur frozen -> U.length frozen++badFixedCopyableOnlyToVectorCase :: Int+badFixedCopyableOnlyToVectorCase =+ linearly \linear ->+ case Fixed.toVector+ ( Fixed.fromVector+ (U.singleton (U.DoNotUnboxLazy (CopyOnly 1)))+ linear+ ) of+ Ur frozen -> U.length frozen++badNonCopyableCopyAtCase :: Int+badNonCopyableCopyAtCase =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ ( Growable.fromVector+ (U.singleton (U.DoNotUnboxLazy (NonCopyable 1)))+ ownerLinear+ )+ share vector & \(Ur shared) -> Control.do+ Ur _ <- Growable.copyAt 0 shared+ let !() = consume shared+ pureAfter (consume (reclaim lend) `lseq` 0)++badNonCopyableCopyAtMutCase :: Int+badNonCopyableCopyAtMutCase =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ ( Growable.fromVector+ (U.singleton (U.DoNotUnboxLazy (NonCopyable 1)))+ ownerLinear+ )+ (Ur (U.DoNotUnboxLazy (NonCopyable copied)), vector) <-+ Growable.copyAtMut 0 vector+ let !() = consume vector+ pureAfter (consume (reclaim lend) `lseq` copied)
+ test/Data/Vector/Unboxed/Mutable/Linear/BorrowSpec.hs view
@@ -0,0 +1,733 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE LinearTypes #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}++module Data.Vector.Unboxed.Mutable.Linear.BorrowSpec (+ module Data.Vector.Unboxed.Mutable.Linear.BorrowSpec,+) where++import Control.Exception qualified as Exception+import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.BO.Unsafe (Alias (UnsafeAlias))+import Control.Monad.Borrow.Pure.Copyable (Copyable (copy), copyMut)+import Control.Syntax.DataFlow qualified as DataFlow+import Data.IORef (IORef, modifyIORef', newIORef, readIORef)+import Data.List qualified as List+import Data.Ref.Linear qualified as Ref+import Data.Ref.Linear.Borrow qualified as RefBorrow+import Data.Vector.Unboxed qualified as U+import Data.Vector.Unboxed.Mutable qualified as UM+import Data.Vector.Unboxed.Mutable.Linear.Borrow qualified as Vector+import Data.Vector.Unboxed.Mutable.Linear.TypingCases+import GHC.IO (unsafePerformIO)+import Prelude.Linear+import PureBorrow.Internal.Bench.Unboxed qualified as UnboxedBench+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.HUnit+import Unsafe.Linear qualified as Unsafe+import Prelude qualified as NonLinear++data Tracked = Tracked !(IORef Int) !Int++instance Consumable (U.DoNotUnboxLazy Tracked) where+ consume =+ Unsafe.toLinear \(U.DoNotUnboxLazy (Tracked counter _)) ->+ unsafePerformIO (modifyIORef' counter NonLinear.succ)++instance Consumable (U.DoNotUnboxStrict Tracked) where+ consume =+ Unsafe.toLinear \(U.DoNotUnboxStrict (Tracked counter _)) ->+ unsafePerformIO (modifyIORef' counter NonLinear.succ)++data MoveTracked = MoveTracked !(IORef Int) !Int !Bool++type UnboxedMoveTracked = U.DoNotUnboxLazy MoveTracked++instance Consumable UnboxedMoveTracked where+ consume = Unsafe.toLinear \_ -> ()++instance Dupable UnboxedMoveTracked where+ dup2 = Unsafe.toLinear \value -> (value, value)++instance Movable UnboxedMoveTracked where+ move =+ Unsafe.toLinear+ \(U.DoNotUnboxLazy (MoveTracked moves value _)) ->+ case unsafePerformIO (modifyIORef' moves NonLinear.succ) of+ () -> Ur (U.DoNotUnboxLazy (MoveTracked moves value True))++materializeMoveTracked :: IORef Int -> [(Int, Bool)]+materializeMoveTracked moves =+ linearly \linear ->+ case Vector.toVector+ ( Vector.fromVector+ ( U.fromList+ [ U.DoNotUnboxLazy (MoveTracked moves 10 False)+ , U.DoNotUnboxLazy (MoveTracked moves 20 False)+ ]+ )+ linear+ ) of+ Ur vector ->+ NonLinear.map+ ( \(U.DoNotUnboxLazy (MoveTracked _ value wasMoved)) ->+ (value, wasMoved)+ )+ (U.toList vector)++discardMaterializedMoveTracked :: IORef Int -> ()+discardMaterializedMoveTracked moves =+ linearly \linear ->+ case Vector.toVector+ ( Vector.fromVector+ ( U.fromList+ [ U.DoNotUnboxLazy (MoveTracked moves 10 False)+ , U.DoNotUnboxLazy (MoveTracked moves 20 False)+ ]+ )+ linear+ ) of+ Ur _ -> ()++data CopyTracked = CopyTracked !(IORef Int) !(IORef Int) !Int++type UnboxedCopyTracked = U.DoNotUnboxLazy CopyTracked++instance Copyable UnboxedCopyTracked where+ copy =+ Unsafe.toLinear+ \(UnsafeAlias value@(U.DoNotUnboxLazy (CopyTracked copies retired _))) ->+ case unsafePerformIO do+ retirementCount <- readIORef retired+ if retirementCount == 0+ then modifyIORef' copies NonLinear.succ+ else NonLinear.error "copy invoked after source retirement" of+ () -> value++instance Consumable UnboxedCopyTracked where+ consume =+ Unsafe.toLinear+ \(U.DoNotUnboxLazy (CopyTracked _ retired _)) ->+ unsafePerformIO (modifyIORef' retired NonLinear.succ)++freezeList :: Vector.Vector Int %1 -> [Int]+freezeList array =+ case Vector.toList array of+ Ur values -> values++freezeLength :: Vector.Vector Int %1 -> Int+freezeLength vector =+ case Vector.toVector vector of+ Ur frozen -> U.length frozen++roundTrip :: [Int]+roundTrip =+ linearly \linear ->+ freezeList (Vector.fromList [1, 2, 3, 4] linear)++test_construction :: TestTree+test_construction =+ testGroup+ "construction"+ [ testCase "empty has no elements" do+ linearly (\linear -> freezeList (Vector.empty linear)) @?= []+ , testCase "constant initializes every element" do+ linearly (\linear -> freezeList (Vector.constant 3 (7 :: Int) linear))+ @?= [7, 7, 7]+ , testCase "fromList moves every element exactly once" do+ roundTrip @?= [1, 2, 3, 4]+ , testCase "fromVector copies an immutable vector" do+ linearly+ (\linear -> freezeList (Vector.fromVector (U.fromList [4, 5, 6]) linear))+ @?= [4, 5, 6]+ , testCase "ordinary sources need no Copyable instance" do+ counter <- newIORef 0+ _ <-+ Exception.evaluate $+ linearly \linear ->+ dup linear & \(constantLinear, vectorLinear) ->+ consume+ ( Vector.constant+ 2+ (U.DoNotUnboxLazy (Tracked counter 1))+ constantLinear+ )+ `lseq` consume+ ( Vector.fromVector+ (U.singleton (U.DoNotUnboxLazy (Tracked counter 2)))+ vectorLinear+ )+ consumed <- readIORef counter+ consumed @?= 3+ , testCase "materialization invokes move for every owned element" do+ moves <- newIORef 0+ materializeMoveTracked moves @?= [(10, True), (20, True)]+ moveCount <- readIORef moves+ moveCount @?= 2+ , testCase "discarding materialization still invokes every move" do+ moves <- newIORef 0+ _ <- Exception.evaluate (discardMaterializedMoveTracked moves)+ moveCount <- readIORef moves+ moveCount @?= 2+ , testCase "unsafeFromVector takes ownership of the source" do+ linearly+ ( \linear ->+ freezeList+ (Vector.unsafeFromVector (U.fromList [8, 9 :: Int]) linear)+ )+ @?= [8, 9]+ , testCase "unsafeFromMutable takes ownership of the complete slice" do+ linearly+ ( \linear ->+ freezeList+ ( Vector.unsafeFromMutable+ (unsafePerformIO (UM.replicate 2 (11 :: Int)))+ linear+ )+ )+ @?= [11, 11]+ ]++mirroredSurface ::+ ( ((Int, Int, Int, Int), (Int, Int, Int))+ , [Int]+ )+mirroredSurface =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (array, lend) <- borrowM (Vector.fromList [1, 2, 3] ownerLinear)+ (Ur logicalSize, array) <- Control.pure (Vector.size array)+ (Ur middle, array) <-+ reborrowing array \short -> Control.do+ element <- Vector.get 1 short+ Control.pure (copyMut element)+ (Ur first, array) <-+ reborrowing array \short -> Control.do+ element <- Vector.head short+ Control.pure (copyMut element)+ (Ur final, array) <-+ reborrowing array \short -> Control.do+ element <- Vector.last short+ Control.pure (copyMut element)+ (Ur copied, array) <- Vector.copyAtMut 1 array+ (old, array) <- Vector.set 1 20 array+ (auxiliary, array) <-+ Vector.update+ 1+ ( \value ->+ case dup value of+ (auxiliary, replacement) ->+ Control.pure (auxiliary, replacement + 1)+ )+ array+ array <- Vector.modify 0 (+ 10) array+ array <- Vector.swap array 0 2+ let !() = consume array+ pureAfter+ (+ ( (logicalSize, middle, first, final)+ , (copied, old, auxiliary)+ )+ , freezeList (reclaim lend)+ )++test_mirroredSurface :: TestTree+test_mirroredSurface =+ testCase "supports borrowed and copied reads, replacement, update, modify, and swap" do+ mirroredSurface @?= (((3, 2, 1, 3), (2, 2, 20)), [3, 21, 11])++retireCopiedResult ::+ (Ur UnboxedCopyTracked, Mut α (Vector.Vector UnboxedCopyTracked)) %1 ->+ Vector.Vector UnboxedCopyTracked %1 ->+ Int+retireCopiedResult =+ Unsafe.toLinear2 \(copiedResult, borrowed) owner ->+ consume borrowed `lseq`+ consume owner `lseq`+ case copiedResult of+ Ur (U.DoNotUnboxLazy (CopyTracked _ _ value)) -> value++copyAtMutAfterRetirement :: IORef Int -> IORef Int -> Int+copyAtMutAfterRetirement copies retired =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ ( Vector.fromList+ [U.DoNotUnboxLazy (CopyTracked copies retired 10)]+ ownerLinear+ )+ copiedResult <- Vector.copyAtMut 0 vector+ pureAfter (retireCopiedResult copiedResult (reclaim lend))++test_copyAtMutStrictness :: TestTree+test_copyAtMutStrictness =+ testCase "copyAtMut completes copying before mutable recovery" do+ copies <- newIORef 0+ retired <- newIORef 0+ copyAtMutAfterRetirement copies retired @?= 10+ copyCount <- readIORef copies+ copyCount @?= 1+ retirementCount <- readIORef retired+ retirementCount @?= 1++sharedReads :: ((Int, Int), [Int])+sharedReads =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (array, lend) <- borrowM (Vector.fromList [5, 6, 7] ownerLinear)+ share array & \(Ur shared) -> Control.do+ Ur first <- Vector.copyAt 0 shared+ Ur second <- Vector.copyAt 1 shared+ pureAfter ((first, second), freezeList (reclaim lend))++test_sharedReads :: TestTree+test_sharedReads =+ testCase "copies repeatedly through a shared borrow" do+ sharedReads @?= ((5, 6), [5, 6, 7])++snapshotThenMutate :: ([Int], [Int])+snapshotThenMutate =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (array, lend) <- borrowM (Vector.fromList [1, 2, 3] ownerLinear)+ (Ur snapshot, array) <- Vector.copyToVector array+ array <- Vector.modify 0 (+ 100) array+ let !() = consume array+ pureAfter (U.toList snapshot, freezeList (reclaim lend))++trackedSnapshot :: IORef Int -> IORef Int -> [Int]+trackedSnapshot copies retired =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (array, lend) <-+ borrowM+ ( Vector.fromList+ [ U.DoNotUnboxLazy (CopyTracked copies retired 10)+ , U.DoNotUnboxLazy (CopyTracked copies retired 20)+ ]+ ownerLinear+ )+ (Ur snapshot, array) <- Vector.copyToVector array+ let !() = consume array+ pureAfter+ ( consume (reclaim lend) `lseq`+ NonLinear.map+ (\(U.DoNotUnboxLazy (CopyTracked _ _ value)) -> value)+ (U.toList snapshot)+ )++test_copyToVector :: TestTree+test_copyToVector =+ testGroup+ "copyToVector"+ [ testCase "copies a stable snapshot while leaving a mutable owner live" do+ snapshotThenMutate @?= ([1, 2, 3], [101, 2, 3])+ , testCase "accepts a shared borrow" do+ sharedSnapshot @?= ([1, 2, 3], [1, 2, 3])+ , testCase "invokes copy for every element while retaining the owner" do+ copies <- newIORef 0+ retired <- newIORef 0+ trackedSnapshot copies retired @?= [10, 20]+ copyCount <- readIORef copies+ copyCount @?= 2+ retirementCount <- readIORef retired+ retirementCount @?= 2+ ]++sharedSnapshot :: ([Int], [Int])+sharedSnapshot =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (array, lend) <- borrowM (Vector.fromList [1, 2, 3] ownerLinear)+ share array & \(Ur shared) -> Control.do+ (Ur snapshot, shared) <- Vector.copyToVector shared+ let !() = consume shared+ pureAfter (U.toList snapshot, freezeList (reclaim lend))++parallelSplit :: Int -> [Int]+parallelSplit splitIndex =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (array, lend) <- borrowM (Vector.fromList [1, 2, 3, 4] ownerLinear)+ let !(left, right) = Vector.splitAt splitIndex array+ (Ur leftSize, left) <- Control.pure (Vector.size left)+ (Ur rightSize, right) <- Control.pure (Vector.size right)+ consume+ Control.<$> parBO+ ( if leftSize > 0+ then Vector.modify 0 (+ 10) left+ else Control.pure left+ )+ ( if rightSize > 0+ then Vector.modify 0 (+ 20) right+ else Control.pure right+ )+ pureAfter (freezeList (reclaim lend))++splitSizes :: Int -> (Int, Int)+splitSizes splitIndex =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (array, lend) <- borrowM (Vector.fromList [1, 2, 3, 4] ownerLinear)+ case Vector.splitAt splitIndex array of+ (left0, right0) ->+ case Vector.size left0 of+ (Ur leftSize, left) ->+ case Vector.size right0 of+ (Ur rightSize, right) -> DataFlow.do+ consume left+ consume right+ pureAfter+ ( leftSize+ , rightSize + freezeLength (reclaim lend) - 4+ )++test_splitAt :: TestTree+test_splitAt =+ testGroup+ "splitAt"+ [ testCase "separates disjoint ranges for parallel mutation" do+ parallelSplit 2 @?= [11, 2, 23, 4]+ , testCase "accepts the lower boundary" do+ splitSizes 0 @?= (0, 4)+ , testCase "accepts the upper boundary" do+ splitSizes 4 @?= (4, 0)+ , testCase "clamps a negative index" do+ splitSizes (-3) @?= (0, 4)+ , testCase "clamps an oversized index" do+ splitSizes 10 @?= (4, 0)+ ]++trackedLifecycle :: IORef Int -> Int+trackedLifecycle counter =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (array, lend) <-+ borrowM+ ( Vector.fromList+ [ U.DoNotUnboxLazy (Tracked counter 10)+ , U.DoNotUnboxLazy (Tracked counter 20)+ ]+ ownerLinear+ )+ (displaced, array) <-+ Vector.set 0 (U.DoNotUnboxLazy (Tracked counter 30)) array+ let !() = consume displaced+ (oldLabel, array) <-+ Vector.update+ 1+ ( \(U.DoNotUnboxLazy (Tracked elementCounter label)) ->+ case dup label of+ (oldLabel, updatedLabel) ->+ Control.pure+ ( oldLabel+ , U.DoNotUnboxLazy+ (Tracked elementCounter (updatedLabel + 1))+ )+ )+ array+ let !() = consume array+ pureAfter (consume (reclaim lend) `lseq` oldLabel)++test_nonCopyableElements :: TestTree+test_nonCopyableElements =+ testGroup+ "non-Copyable elements"+ [ testCase "get preserves a nested Ref identity" do+ borrowedRefAlias @?= 42+ , testCase "moves and retires lazy boxed-backed elements exactly once" do+ counter <- newIORef 0+ oldLabel <- Exception.evaluate (trackedLifecycle counter)+ oldLabel @?= 20+ retired <- readIORef counter+ retired @?= 3+ , testCase "moves and retires strict boxed-backed elements exactly once" do+ counter <- newIORef 0+ result <- Exception.evaluate (strictTrackedLifecycle counter)+ result @?= ()+ retired <- readIORef counter+ retired @?= 2+ ]++borrowedRefAlias :: Int+borrowedRefAlias =+ linearly \linear -> DataFlow.do+ (refLinear, remainingLinear) <- dup linear+ (ownerLinear, runLinear) <- dup remainingLinear+ runBO runLinear Control.do+ (array, lend) <-+ borrowM+ ( Vector.fromList+ [U.DoNotUnboxLazy (LinearElement (Ref.new 1 refLinear))]+ ownerLinear+ )+ ((), array) <-+ reborrowing array \short -> Control.do+ element <- nonCopyableGet short+ modifyBorrowedRef element+ (observed, array) <-+ reborrowing array \short -> Control.do+ element <- nonCopyableGet short+ copyBorrowedRef element+ let !() = consume array+ pureAfter (consume (reclaim lend) `lseq` observed)++asBorrowedRef ::+ Mut α BoxedLinearElement %1 ->+ Mut α (Ref.Ref Int)+asBorrowedRef = upcast++modifyBorrowedRef ::+ Mut α BoxedLinearElement %1 ->+ BO α ()+modifyBorrowedRef element = Control.do+ ref <- RefBorrow.modify (+ 41) (asBorrowedRef element)+ Control.pure (consume ref)++copyBorrowedRef ::+ Mut α BoxedLinearElement %1 ->+ BO α Int+copyBorrowedRef = RefBorrow.copyRef . asBorrowedRef++strictTrackedLifecycle :: IORef Int -> ()+strictTrackedLifecycle counter =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (array, lend) <-+ borrowM+ ( Vector.fromList+ [U.DoNotUnboxStrict (Tracked counter 10)]+ ownerLinear+ )+ (displaced, array) <-+ Vector.set 0 (U.DoNotUnboxStrict (Tracked counter 20)) array+ let+ !() = consume displaced+ !() = consume array+ pureAfter (consume (reclaim lend))++assertErrorPrefix :: NonLinear.String -> a -> Assertion+assertErrorPrefix expectedPrefix value = do+ result <- Exception.try @Exception.ErrorCall $ Exception.evaluate value+ case result of+ Left exception ->+ assertBool+ ("unexpected error: " <> Exception.displayException exception)+ (expectedPrefix `List.isPrefixOf` Exception.displayException exception)+ Right _ -> assertFailure ("expected error beginning with " <> expectedPrefix)++getOutOfBounds :: Int -> Int+getOutOfBounds index =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (array, lend) <- borrowM (Vector.fromList [10, 20, 30] ownerLinear)+ (Ur value, array) <-+ reborrowing array \short -> Control.do+ element <- Vector.get index short+ Control.pure (copyMut element)+ let !() = consume array+ pureAfter (value + freezeLength (reclaim lend))++copyAtMutOutOfBounds :: Int -> Int+copyAtMutOutOfBounds index =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (array, lend) <- borrowM (Vector.fromList [10, 20, 30] ownerLinear)+ (Ur value, array) <- Vector.copyAtMut index array+ let !() = consume array+ pureAfter (value + freezeLength (reclaim lend))++setOutOfBounds :: Int -> Int+setOutOfBounds index =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (array, lend) <- borrowM (Vector.fromList [10, 20, 30] ownerLinear)+ (old, array) <- Vector.set index 0 array+ let !() = consume array+ pureAfter (old + freezeLength (reclaim lend))++updateOutOfBounds :: Int -> Int+updateOutOfBounds index =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (array, lend) <- borrowM (Vector.fromList [10, 20, 30] ownerLinear)+ (old, array) <-+ Vector.update+ index+ ( \value ->+ case dup value of+ (old, replacement) -> Control.pure (old, replacement)+ )+ array+ let !() = consume array+ pureAfter (old + freezeLength (reclaim lend))++swapOutOfBounds :: Int -> Int+swapOutOfBounds index =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (array, lend) <- borrowM (Vector.fromList [10, 20, 30] ownerLinear)+ array <- Vector.swap array 0 index+ let !() = consume array+ pureAfter (freezeLength (reclaim lend))++test_bounds :: TestTree+test_bounds =+ testGroup+ "bounds"+ [ testCase "get rejects a negative index" do+ assertErrorPrefix+ "get: index -1 out of bounds for length 3"+ (getOutOfBounds (-1))+ , testCase "get rejects the upper bound" do+ assertErrorPrefix+ "get: index 3 out of bounds for length 3"+ (getOutOfBounds 3)+ , testCase "copyAtMut rejects a negative index" do+ assertErrorPrefix+ "get: index -1 out of bounds for length 3"+ (copyAtMutOutOfBounds (-1))+ , testCase "copyAtMut rejects the upper bound" do+ assertErrorPrefix+ "get: index 3 out of bounds for length 3"+ (copyAtMutOutOfBounds 3)+ , testCase "set rejects a negative index" do+ assertErrorPrefix+ "set: index -1 out of bounds for length 3"+ (setOutOfBounds (-1))+ , testCase "set rejects the upper bound" do+ assertErrorPrefix+ "set: index 3 out of bounds for length 3"+ (setOutOfBounds 3)+ , testCase "update rejects a negative index" do+ assertErrorPrefix+ "update: index -1 out of bounds for length 3"+ (updateOutOfBounds (-1))+ , testCase "update rejects the upper bound" do+ assertErrorPrefix+ "update: index 3 out of bounds for length 3"+ (updateOutOfBounds 3)+ , testCase "swap rejects a negative index" do+ assertErrorPrefix+ "swap: indices (0,-1) out of bounds for length 3"+ (swapOutOfBounds (-1))+ , testCase "swap rejects the upper bound" do+ assertErrorPrefix+ "swap: indices (0,3) out of bounds for length 3"+ (swapOutOfBounds 3)+ , testCase "head rejects an empty vector" do+ assertErrorPrefix+ "get: index 0 out of bounds for length 0"+ (getOutOfBoundsOnEmpty Vector.head)+ , testCase "last rejects an empty vector" do+ assertErrorPrefix+ "last: empty vector"+ (getOutOfBoundsOnEmpty Vector.last)+ ]++getOutOfBoundsOnEmpty ::+ (forall α. Mut α (Vector.Vector Int) %1 -> BO α (Mut α Int)) ->+ Int+getOutOfBoundsOnEmpty operation =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (array, lend) <- borrowM (Vector.empty ownerLinear)+ (Ur value, array) <-+ reborrowing array \short -> Control.do+ element <- operation short+ Control.pure (copyMut element)+ let !() = consume array+ pureAfter (value + freezeLength (reclaim lend))++test_typingBoundaries :: TestTree+test_typingBoundaries =+ testGroup+ "typing boundaries"+ [ expectDeferredTypeError+ "unboxed Vector element role is nominal"+ "Couldn't match type"+ badElementCoercion+ , expectDeferredTypeError+ "unboxed Vector cannot be coerced to a boxed Vector"+ "Couldn't match representation of type"+ badUnboxedToBoxed+ , expectDeferredTypeError+ "a boxed Vector cannot be coerced to an unboxed Vector"+ "Couldn't match representation of type"+ badBoxedToUnboxed+ , expectDeferredTypeError+ "an unboxed Vector borrow cannot swap lifetime indices"+ "Couldn't match type"+ badLifetimeSwap+ , expectDeferredTypeError+ "unboxed Vector has no generic split"+ "DistributesAlias Unboxed.Vector"+ badSplit+ , expectDeferredTypeError+ "unboxed Vector cannot be copied"+ "cannot be copied!"+ badDuplicate+ , expectDeferredTypeError+ "Movable alone does not permit copyAt"+ "Copyable (U.DoNotUnboxLazy MovableOnly)"+ badNonCopyableCopyAtCase+ , expectDeferredTypeError+ "Movable alone does not permit copyAtMut"+ "Copyable (U.DoNotUnboxLazy MovableOnly)"+ badNonCopyableCopyAtMutCase+ ]+ where+ expectDeferredTypeError description expectedFragment value =+ testCase description do+ result <- Exception.try @Exception.SomeException (Exception.evaluate value)+ case result of+ Left exception ->+ assertBool+ ("unexpected deferred type error: " <> Exception.displayException exception)+ (expectedFragment `List.isInfixOf` Exception.displayException exception)+ Right _ ->+ assertFailure+ ("expected deferred type error containing " <> expectedFragment)++test_benchmarkRoots :: TestTree+test_benchmarkRoots =+ testGroup+ "benchmark roots"+ [ testGroup+ ("length " <> show length_)+ [ testCase "fixed kernel roots agree" do+ let input =+ U.generate length_ (\index -> index `NonLinear.rem` 17)+ UnboxedBench.pureBorrowFixedUnboxedKernel input+ @?= UnboxedBench.directFixedUnboxedKernel input+ , testCase "fixed public-materialization roots agree" do+ let input =+ U.generate length_ (\index -> index `NonLinear.rem` 17)+ UnboxedBench.pureBorrowFixedUnboxedMaterialization input+ @?= UnboxedBench.directFixedUnboxedMaterialization input+ ]+ | length_ <- [0, 1, 257, 1024 * 1024]+ ]
+ test/Data/Vector/Unboxed/Mutable/Linear/TypingCases.hs view
@@ -0,0 +1,124 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE QualifiedDo #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -O0 #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}+{-# OPTIONS_GHC -fdefer-type-errors -Wno-deferred-type-errors #-}++module Data.Vector.Unboxed.Mutable.Linear.TypingCases (+ module Data.Vector.Unboxed.Mutable.Linear.TypingCases,+) where++import Control.Functor.Linear qualified as Control+import Control.Monad.Borrow.Pure.BO+import Control.Monad.Borrow.Pure.Copyable (Copyable (copy))+import Control.Syntax.DataFlow qualified as DataFlow+import Data.Coerce (coerce)+import Data.Ref.Linear qualified as Ref+import Data.Vector.Mutable.Linear.Borrow qualified as Boxed+import Data.Vector.Unboxed qualified as U+import Data.Vector.Unboxed.Mutable.Linear.Borrow qualified as Unboxed+import Prelude.Linear+import Unsafe.Linear qualified as Unsafe++newtype WrappedInt = WrappedInt Int++badElementCoercion ::+ Unboxed.Vector WrappedInt %1 ->+ Unboxed.Vector Int+badElementCoercion = Unsafe.toLinear coerce++badUnboxedToBoxed ::+ Unboxed.Vector Int %1 ->+ Boxed.Vector Int+badUnboxedToBoxed = Unsafe.toLinear coerce++badBoxedToUnboxed ::+ Boxed.Vector Int %1 ->+ Unboxed.Vector Int+badBoxedToUnboxed = Unsafe.toLinear coerce++badLifetimeSwap ::+ Mut α (Unboxed.Vector Int) %1 ->+ Mut β (Unboxed.Vector Int)+badLifetimeSwap = Unsafe.toLinear coerce++badSplit ::+ Mut α (Unboxed.Vector Int) %1 ->+ Unboxed.Vector (Mut α Int)+badSplit = split++badDuplicate ::+ Borrow bk α (Unboxed.Vector Int) %1 ->+ Unboxed.Vector Int+badDuplicate = copy++newtype LinearElement = LinearElement (Ref.Ref Int)++type BoxedLinearElement = U.DoNotUnboxLazy LinearElement++instance Consumable (U.DoNotUnboxLazy LinearElement) where+ consume =+ Unsafe.toLinear \(U.DoNotUnboxLazy (LinearElement ref)) ->+ consume ref++newtype MovableOnly = MovableOnly Int++type UnboxedMovableOnly = U.DoNotUnboxLazy MovableOnly++instance Consumable UnboxedMovableOnly where+ consume = Unsafe.toLinear \_ -> ()++instance Dupable UnboxedMovableOnly where+ dup2 = Unsafe.toLinear \value -> (value, value)++instance Movable UnboxedMovableOnly where+ move = Unsafe.toLinear \value -> Ur value++nonCopyableGet ::+ Mut α (Unboxed.Vector BoxedLinearElement) %1 ->+ BO α (Mut α BoxedLinearElement)+nonCopyableGet = Unboxed.get 0++badNonCopyableCopyAtMut ::+ Mut α (Unboxed.Vector UnboxedMovableOnly) %1 ->+ BO+ α+ ( Ur UnboxedMovableOnly+ , Mut α (Unboxed.Vector UnboxedMovableOnly)+ )+badNonCopyableCopyAtMut = Unboxed.copyAtMut 0++badNonCopyableCopyAtMutCase :: Int+badNonCopyableCopyAtMutCase =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ ( Unboxed.fromList+ [U.DoNotUnboxLazy (MovableOnly 1)]+ ownerLinear+ )+ (Ur _, vector) <- Unboxed.copyAtMut 0 vector+ let !() = consume vector+ pureAfter (consume (reclaim lend) `lseq` 0)++badNonCopyableCopyAtCase :: Int+badNonCopyableCopyAtCase =+ linearly \linear -> DataFlow.do+ (ownerLinear, runLinear) <- dup linear+ runBO runLinear Control.do+ (vector, lend) <-+ borrowM+ ( Unboxed.fromList+ [U.DoNotUnboxLazy (MovableOnly 1)]+ ownerLinear+ )+ share vector & \(Ur shared) -> Control.do+ Ur _ <- Unboxed.copyAt 0 shared+ let !() = consume shared+ pureAfter (consume (reclaim lend) `lseq` 0)
+ test/PureBorrow/Internal/Bench/MultiStoreScanSpec.hs view
@@ -0,0 +1,96 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE TypeApplications #-}++module PureBorrow.Internal.Bench.MultiStoreScanSpec (+ module PureBorrow.Internal.Bench.MultiStoreScanSpec,+) where++import Control.Exception (SomeException, displayException, evaluate, try)+import Data.List (isInfixOf)+import Data.Vector qualified as V+import Data.Vector.Unboxed qualified as U+import PureBorrow.Internal.Bench.MultiStoreScan+import Test.Tasty (TestTree)+import Test.Tasty.HUnit++test_multiStoreScanDirectEvidence :: TestTree+test_multiStoreScanDirectEvidence =+ testCase "multi-store scan direct control preserves its frozen trace and digest" do+ let result = multiStoreScanDirectRoot multiStoreScanDirectInput+ visitedNodes (resultSummary result) @?= 4096+ elementReads (resultSummary result) @?= 24576+ elementWrites (resultSummary result) @?= 1742+ headerReads (resultSummary result) @?= 3+ validationReads (resultSummary result) @?= 8198+ finalDigest (resultSummary result) @?= 7192365686207673759+ resultVisitedIndices result @?= U.fromList [0 .. 4095]+ V.length (resultEvents result) @?= 26318+ resultEventDigest result @?= -6999049615496738955+ U.length (resultMarks result) @?= 4096+ U.length (resultScores result) @?= 4096+ let output =+ multiStoreScanDirectBenchmarkRoot multiStoreScanDirectInput+ outputDigest output @?= finalDigest (resultSummary result)+ outputMarks output @?= resultMarks result+ outputScores output @?= resultScores result++test_multiStoreScanPureBorrowEvidence :: TestTree+test_multiStoreScanPureBorrowEvidence =+ testCase "all attribution and ownership shapes preserve the frozen output" do+ let direct = multiStoreScanDirectRoot multiStoreScanDirectInput+ directOutput =+ multiStoreScanDirectBenchmarkRoot multiStoreScanDirectInput+ multiStoreScanPureBorrowDirectRoot multiStoreScanDirectInput+ @?= direct+ multiStoreScanPureBorrowNestedRoot multiStoreScanDirectInput+ @?= direct+ multiStoreScanDirectHeaderMatchedBenchmarkRoot multiStoreScanDirectInput+ @?= directOutput+ multiStoreScanPureBorrowOwningBenchmarkRoot multiStoreScanDirectInput+ @?= directOutput+ multiStoreScanPureBorrowFixedUnrestrictedBenchmarkRoot multiStoreScanDirectInput+ @?= directOutput+ multiStoreScanPureBorrowDirectBenchmarkRoot multiStoreScanDirectInput+ @?= directOutput+ multiStoreScanPureBorrowNestedBenchmarkRoot multiStoreScanDirectInput+ @?= directOutput++test_multiStoreScanRejectsInvalidInputs :: TestTree+test_multiStoreScanRejectsInvalidInputs =+ testCase "all-unrestricted Pure Borrow shapes reject invalid inputs" do+ let standard = multiStoreScanDirectInput+ expectedDiagnostic =+ "multi-store scan requires six 4096-element vectors, in-range next indices, and zero links"+ invalidInputs =+ [ standard {inputNext = U.replicate 4095 0}+ , standard {inputWeight = U.replicate 4097 0}+ , standard {inputMark = U.replicate 4095 0}+ , standard {inputPayload = V.replicate 4097 (0, 0)}+ , standard {inputScore = U.replicate 4095 0}+ , standard {inputLink = U.replicate 4097 0}+ , standard {inputNext = U.replicate 4096 (-1)}+ , standard {inputNext = U.replicate 4096 4096}+ , standard {inputLink = U.replicate 4096 1}+ ]+ roots =+ [ multiStoreScanPureBorrowDirectRoot+ , multiStoreScanPureBorrowNestedRoot+ ]+ mapM_+ ( \root ->+ mapM_+ ( \input -> do+ outcome <-+ try @SomeException (evaluate (root input))+ case outcome of+ Left exception ->+ let diagnostic = displayException exception+ in assertBool+ ("unexpected invalid-input diagnostic: " <> diagnostic)+ (expectedDiagnostic `isInfixOf` diagnostic)+ Right _ ->+ assertFailure "invalid multi-store input was accepted"+ )+ invalidInputs+ )+ roots
+ test/PureBorrow/Internal/Bench/Worklist/ResumeSpec.hs view
@@ -0,0 +1,248 @@+{-# LANGUAGE BlockArguments #-}++module PureBorrow.Internal.Bench.Worklist.ResumeSpec (+ module PureBorrow.Internal.Bench.Worklist.ResumeSpec,+) where++import Data.Vector.Unboxed qualified as U+import PureBorrow.Internal.Bench.Worklist.Resume+import Test.Tasty (TestTree)+import Test.Tasty.HUnit++test_worklistOpenOnceEvidence :: TestTree+test_worklistOpenOnceEvidence =+ testCase "open-once traversal preserves the frozen trace and direct output" do+ mapM_+ ( \target -> do+ let direct = worklistDirectOpenOnceRoot target+ summary direct @?= expectedSummary target 4 0 0 0+ assertFrozenOutput target direct+ worklistPureBorrowOpenOnceRoot target @?= direct+ )+ [minBound .. maxBound]++test_worklistCheckedSurfaceEvidence :: TestTree+test_worklistCheckedSurfaceEvidence =+ testCase "checked-surface reads reproduce the unchecked traversal exactly" do+ mapM_+ ( \target -> do+ let unchecked = worklistPureBorrowOpenOnceRoot target+ assertFrozenOutput target unchecked+ worklistPureBorrowCheckedOpenOnceRoot target @?= unchecked+ )+ [minBound .. maxBound]+ mapM_+ ( \seed ->+ mapM_+ ( \target ->+ worklistPureBorrowCheckedOpenOnceRootWithSeed seed target+ @?= worklistPureBorrowOpenOnceRootWithSeed seed target+ )+ [minBound .. maxBound]+ )+ [1, 37]++test_worklistReopenEvidence :: TestTree+test_worklistReopenEvidence =+ testCase "flat and nested reopen shapes preserve every frozen growth trace" do+ mapM_+ ( \(growth, target, flatOpens, nestedOpens, resumes, updates, growths) -> do+ let directFlat =+ worklistDirectReopenRoot FlatReopen growth target+ directNested =+ worklistDirectReopenRoot NestedReopen growth target+ summary directFlat+ @?= expectedSummary+ target+ flatOpens+ resumes+ updates+ growths+ summary directNested+ @?= expectedSummary+ target+ nestedOpens+ resumes+ updates+ growths+ assertFrozenOutput target directFlat+ assertSameTraversalOutput directFlat directNested+ worklistPureBorrowFlatReopenRoot growth target+ @?= directFlat+ worklistPureBorrowNestedReopenRoot growth target+ @?= directNested+ )+ reopenCases+ mapM_+ ( \(target, noGrowth, growth) ->+ assertSameTraversalOutput+ (worklistDirectReopenRoot NestedReopen noGrowth target)+ (worklistDirectReopenRoot NestedReopen growth target)+ )+ [ (target, noGrowth, growth)+ | target <- [minBound .. maxBound]+ , (noGrowth, growth) <-+ [ (NoGrowthBatch64, SparseGrowth)+ , (NoGrowthBatch8, DenseGrowth)+ ]+ ]++test_worklistSeededEvidence :: TestTree+test_worklistSeededEvidence =+ testCase "fresh-run seeds preserve direct and Pure Borrow equivalence" do+ mapM_+ ( \seed ->+ mapM_+ ( \target ->+ worklistPureBorrowOpenOnceRootWithSeed seed target+ @?= worklistDirectOpenOnceRootWithSeed seed target+ )+ [minBound .. maxBound]+ )+ [1, 37]+ mapM_+ ( \(label, root) -> do+ let seed1 = root 1+ seed37 = root 37+ assertBool+ (label <> " must include the seed in its digest")+ (finalDigest (summary seed1) /= finalDigest (summary seed37))+ root 1 @?= seed1+ root 37 @?= seed37+ root 1 @?= seed1+ )+ [ ("open-once", \seed -> worklistPureBorrowOpenOnceRootWithSeed seed Drain)+ ,+ ( "dense nested reopen"+ , \seed ->+ worklistPureBorrowNestedReopenRootWithSeed+ seed+ DenseGrowth+ Drain+ )+ ]+ mapM_+ ( \seed ->+ mapM_+ ( \(growth, target) -> do+ worklistPureBorrowFlatReopenRootWithSeed seed growth target+ @?= worklistDirectReopenRootWithSeed+ seed+ FlatReopen+ growth+ target+ worklistPureBorrowNestedReopenRootWithSeed seed growth target+ @?= worklistDirectReopenRootWithSeed+ seed+ NestedReopen+ growth+ target+ )+ [ (growth, target)+ | growth <- [minBound .. maxBound]+ , target <- [minBound .. maxBound]+ ]+ )+ [1, 37]++reopenCases ::+ [ ( WorklistGrowth+ , WorklistTarget+ , Int+ , Int+ , Int+ , Int+ , Int+ )+ ]+reopenCases =+ [ (NoGrowth, Drain, 84, 44, 20, 41, 0)+ , (NoGrowth, StopEarly, 44, 24, 10, 22, 0)+ , (NoGrowthBatch64, Drain, 272, 138, 67, 131, 0)+ , (NoGrowthBatch64, StopEarly, 100, 52, 24, 50, 0)+ , (NoGrowthBatch8, Drain, 2056, 1030, 513, 934, 0)+ , (NoGrowthBatch8, StopEarly, 692, 348, 172, 346, 0)+ , (SparseGrowth, Drain, 272, 138, 67, 131, 8)+ , (SparseGrowth, StopEarly, 100, 52, 24, 50, 7)+ , (DenseGrowth, Drain, 2056, 1030, 513, 934, 21)+ , (DenseGrowth, StopEarly, 692, 348, 172, 346, 19)+ ]++expectedSummary ::+ WorklistTarget ->+ Int ->+ Int ->+ Int ->+ Int ->+ WorklistSummary+expectedSummary target opens resumes updates growths =+ case target of+ Drain ->+ WorklistSummary+ { outcome = Drained+ , visitedNodes = 4096+ , enqueueTransitions = 4095+ , offsetReads = 8192+ , adjacencyReads = 12288+ , payloadReads = 12288+ , markReads = 12288+ , markWrites = 4095+ , queueReads = 4096+ , queueWrites = 4095+ , logWrites = 4096+ , contentOpens = opens+ , resumeBoundaries = resumes+ , headerUpdates = updates+ , bufferGrowths = growths+ , finalDigest = 2728622868939553119+ }+ StopEarly ->+ WorklistSummary+ { outcome = Stopped+ , visitedNodes = 1365+ , enqueueTransitions = 2888+ , offsetReads = 2730+ , adjacencyReads = 4095+ , payloadReads = 4095+ , markReads = 4095+ , markWrites = 2888+ , queueReads = 1365+ , queueWrites = 2888+ , logWrites = 1365+ , contentOpens = opens+ , resumeBoundaries = resumes+ , headerUpdates = updates+ , bufferGrowths = growths+ , finalDigest = 5952155574826728904+ }++assertFrozenOutput :: WorklistTarget -> WorklistOutput -> Assertion+assertFrozenOutput target result = do+ let resultSummary = summary result+ visits = visitedNodes resultSummary+ enqueues = enqueueTransitions resultSummary+ U.sum (finalMarks result) @?= enqueues + 1+ finalState result+ @?= U.fromList+ [ visits+ , enqueues + 1+ , visits+ , enqueues+ , visits+ ]+ U.length (finalQueue result) @?= enqueues + 1+ U.length (finalLog result) @?= visits+ outcome resultSummary+ @?= case target of+ Drain -> Drained+ StopEarly -> Stopped++assertSameTraversalOutput ::+ WorklistOutput ->+ WorklistOutput ->+ Assertion+assertSameTraversalOutput left right = do+ finalMarks left @?= finalMarks right+ finalState left @?= finalState right+ finalQueue left @?= finalQueue right+ finalLog left @?= finalLog right