packages feed

pure-borrow-0.1.0.0: src/Data/Vector/Mutable/Linear/Borrow.hs

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ImpredicativeTypes #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE QualifiedDo #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# OPTIONS_GHC -Wno-name-shadowing #-}
{-# OPTIONS_GHC -Wno-partial-type-signatures #-}
{-# OPTIONS_GHC -Wno-redundant-constraints #-}

module Data.Vector.Mutable.Linear.Borrow (
  Vector,
  empty,
  constant,
  fromList,
  fromVector,
  unsafeFromVector,
  fromMutable,
  unsafeFromMutable,
  toVector,
  toList,
  size,
  get,
  unsafeGet,
  set,
  unsafeSet,
  update,
  unsafeUpdate,
  modify,
  head,
  unsafeHead,
  last,
  unsafeLast,
  indicesMut,
  unsafeIndicesMut,
  splitAt,
  swap,
  unsafeSwap,
  copyAt,
  copyAtMut,
  unsafeInplace,
  modifyBoxedMVector,
  modifyBoxedVector,

  -- * An example algorithm implementations
  qsort,

  -- ** Internal functions
  divide,
) 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.Utils
import Control.Monad.ST.Strict (ST)
import Control.Syntax.DataFlow qualified as DataFlow
import Data.Function qualified as NonLinear
import Data.Functor.Linear qualified as Data
import Data.IntSet qualified as IntSet
import Data.Unrestricted.Linear qualified as Ur
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 Prelude.Linear hiding (head, last, splitAt)
import Unsafe.Linear qualified as Unsafe
import Prelude qualified as NonLinear

{- |
Linearly owned mutable vector.
Contrary to those in @linear-base@, our 'Vector' owns every element @linearly@.
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.
-}
empty :: Linearly %1 -> Vector a
{-# NOINLINE empty #-}
empty =
  GHC.noinline \l ->
    l `lseq` do
      Vector (unsafePerformIO $ MV.new 0)

constant :: Int -> a -> Linearly %1 -> Vector a
{-# NOINLINE constant #-}
constant = GHC.noinline \n a l ->
  l `lseq` do
    Vector $!
      unsafePerformIO $!
        MV.replicate n a

fromList :: [a] %1 -> Linearly %1 -> Vector a
{-# NOINLINE fromList #-}
fromList = GHC.noinline $ Unsafe.toLinear \as l ->
  l `lseq` do
    Vector $!
      unsafePerformIO $!
        Unsafe.toLinear V.unsafeThaw $!
          Unsafe.toLinear V.fromList as

-- | Convert a 'V.Vector' (from @vector@ package) to a 'Vector'.
fromVector :: V.Vector a -> Linearly %1 -> Vector a
{-# NOINLINE fromVector #-}
fromVector = GHC.noinline $ Unsafe.toLinear \v l ->
  l `lseq` do
    Vector $!
      unsafePerformIO $!
        Unsafe.toLinear V.thaw v

-- | /O(n)/. Clone a 'V.MVector' from @vector@ package to a 'Vector'.
fromMutable :: MV.MVector s a %1 -> Linearly %1 -> Vector a
{-# NOINLINE fromMutable #-}
fromMutable = GHC.noinline $ Unsafe.toLinear \v l ->
  l `lseq` do
    Vector $!
      unsafePerformIO $!
        Unsafe.toLinear MV.clone (Unsafe.coerce v)

unsafeFromMutable :: MV.MVector s a %1 -> Linearly %1 -> Vector a
unsafeFromMutable v lin =
  lin `lseq` Vector (Unsafe.coerce v)

{-
Note [Unrestricted Materialization of Vector]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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(n)/. Move every element into GC ownership, then freeze the storage.
toVector ::
  -- See Note [Unrestricted Materialization of Vector].
  (Movable a) =>
  Vector a %1 -> Ur (V.Vector a)
{-# NOINLINE toVector #-}
toVector = GHC.noinline $
  Unsafe.toLinear \(Vector v) ->
    let !frozen =
          unsafePerformIO do
            moveElements 0 (MV.length v) v
            V.unsafeFreeze v
     in Ur frozen

-- Same applies to 'Movable' here, as in 'toVector'.
toList ::
  -- See Note [Unrestricted Materialization of Vector].
  (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
-}
unsafeFromVector :: V.Vector a %1 -> Linearly %1 -> Vector a
{-# NOINLINE unsafeFromVector #-}
unsafeFromVector = Unsafe.toLinear \v l ->
  l `lseq` GHC.noinline do
    Vector $!
      unsafePerformIO $!
        V.unsafeThaw v

size :: Borrow bk α (Vector a) %1 -> (Ur Int, Borrow bk α (Vector a))
{-# INLINE size #-}
size =
  unsafeUnalias >>> Unsafe.toLinear \(Vector v) ->
    (move (MV.length v), UnsafeAlias (Vector v))

{- |
@'set' i a v@ sets the @i@-th element of @v@ to @a@, and returns the old value alongside.
Note that @a@ is bound linearly.
-}
set :: (HasCallStack, α >= β) => Int -> a %1 -> Mut α (Vector a) %1 -> BO β (a, Mut α (Vector a))
{-# INLINE set #-}
set i a v = DataFlow.do
  (len, v) <- size v
  case len of
    Ur len ->
      if i < 0 || i >= len
        then error ("get: index " <> show i <> " out of bound: " <> show len) v a
        else unsafeSet i a v

-- | '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
  MV.unsafeWrite v i a
  NonLinear.pure (old, mut)

-- | 'get' without bounds check.
unsafeGet :: (α >= β) => Int -> Borrow bk α (Vector a) %1 -> BO β (Borrow bk α a)
{-# INLINE unsafeGet #-}
unsafeGet i =
  Unsafe.toLinear \v ->
    unsafeUnalias v
      NonLinear.& \(Vector v) ->
        UnsafeAlias
          Control.<$> unsafeSystemIOToBO (MV.unsafeRead v i)

head :: (HasCallStack, α >= β) => Borrow bk α (Vector a) %1 -> BO β (Borrow bk α a)
{-# INLINE head #-}
head = get 0

unsafeHead :: (α >= β) => Borrow bk α (Vector a) %1 -> BO β (Borrow bk α a)
{-# INLINE unsafeHead #-}
unsafeHead = unsafeGet 0

unsafeLast :: (α >= β) => Borrow bk α (Vector a) %1 -> BO β (Borrow bk α a)
{-# INLINE unsafeLast #-}
unsafeLast v = DataFlow.do
  (len, v) <- size v
  case len of
    Ur len -> unsafeGet (len - 1) v

last :: (HasCallStack, α >= β) => Borrow bk α (Vector a) %1 -> BO β (Borrow bk α a)
{-# INLINE last #-}
last v = DataFlow.do
  (len, v) <- size v
  case len of
    Ur len
      | len > 0 -> unsafeGet (len - 1) v
      | otherwise -> error ("last: empty vector") v

get ::
  (HasCallStack, α >= β) =>
  Int -> Borrow bk α (Vector a) %1 -> BO β (Borrow bk α a)
{-# INLINE get #-}
get i v = DataFlow.do
  (len, v) <- size v
  case len of
    Ur len ->
      if i < 0 || i >= len
        then error ("get: index " <> show i <> " out of bound: " <> show len) v
        else unsafeGet i v

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
  () <- unsafeSystemIOToBO $ Unsafe.toLinear3 MV.unsafeWrite (content v) i a'
  Control.pure $ (b, UnsafeAlias v)

update :: (α >= β) => Int -> (a %1 -> BO β (b, a)) %1 -> Mut α (Vector a) %1 -> BO β (b, Mut α (Vector a))
update i k v = DataFlow.do
  (len, v) <- size v
  case len of
    Ur len ->
      if i < 0 || i >= len
        then error ("set: index " <> show i <> " out of bound: " <> show len) v k
        else unsafeUpdate i k v

modify :: (α >= β) => Int -> (a %1 -> a) %1 -> Mut α (Vector a) %1 -> BO β (Mut α (Vector a))
modify i f v = Control.do
  ((), ma) <- update i (Control.pure . ((),) . f) v
  Control.pure ma

{- | Get multiple elements at the given indices without bounds and duplication check.
For more safety, use 'indicesMut'.
-}
unsafeIndicesMut :: (α >= β) => Mut α (Vector a) %1 -> [Int] %1 -> BO β [Mut α a]
unsafeIndicesMut = Unsafe.toLinear \v is ->
  Data.traverse
    (\i -> move i & \(Ur i) -> unsafeGet i v)
    is

indicesMut :: (HasCallStack, α >= β) => Mut α (Vector a) %1 -> [Int] %1 -> BO β [Mut α a]
indicesMut = Unsafe.toLinear2 \v is ->
  case size v of
    (Ur len, v) ->
      if
        | any (\i -> move i & \(Ur i) -> i < 0 || i >= len) is ->
            error ("indicesMut: indices out of bound: " <> show is <> " for length " <> show len) v
        | NonLinear.length is > IntSet.size (IntSet.fromList is) ->
            error ("indicesMut: duplicate indices: " <> show is) v
        | otherwise -> unsafeIndicesMut v is

splitAt :: Int %1 -> Borrow bk α (Vector a) %1 -> (Borrow bk α (Vector a), Borrow bk α (Vector a))
{-# INLINE splitAt #-}
splitAt = Unsafe.toLinear2 \i (UnsafeAlias (Vector v)) ->
  let (v1, v2) = MV.splitAt i v
   in (UnsafeAlias (Vector v1), UnsafeAlias (Vector v2))

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
  Control.pure $ UnsafeAlias v

swap :: (HasCallStack, α >= β) => Mut α (Vector a) %1 -> Int -> Int -> BO β (Mut α (Vector a))
swap v i j = DataFlow.do
  (len, v) <- size v
  case len of
    Ur len ->
      if i < 0 || i >= len || j < 0 || j >= len
        then error ("swap: index out of bound: " <> show (i, j) <> " for length " <> show len) v
        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

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.
unsafeInplace ::
  (α >= β) =>
  (forall s. V.MVector s a -> ST s ()) %1 ->
  Mut α (Vector a) %1 ->
  BO β (Mut α (Vector a))
{-# 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,
and parallel divide-and-conquer with 'parBO' otherwise.

This is meant to be a demonstrative implementation and
not practical - you need a genuine parallel scheduler
to scale this up.
-}
qsort ::
  forall a α β.
  (Ord a, Copyable a, α >= β) =>
  {- | Cost for using parallelism. Halved after each recursive call,
  and stops parallelizing when it reaches 1.
  -}
  Word ->
  Mut α (Vector a) %1 ->
  BO β ()
qsort = go
  where
    go :: Word -> Mut α (Vector a) %1 -> BO β ()
    go budget v = case size v of
      (Ur 0, v) -> Control.pure $ consume v
      (Ur 1, v) -> Control.pure $ consume v
      (Ur n, v) -> Control.do
        let i = n `quot` 2
        (Ur pivot, v) <- copyAtMut i v
        (lo, hi) <- divide pivot v 0 n
        let b' = budget `quot` 2
        Control.void $ parIf (b' NonLinear.> 0) (go b' lo) (go b' hi)

parIf :: Bool %1 -> BO α a %1 -> BO α b %1 -> BO α (a, b)
{-# INLINE parIf #-}
parIf p = if p then parBO else Control.liftA2 (,)

divide ::
  (Ord a, Copyable a, α >= β) =>
  a ->
  Mut α (Vector a) %1 ->
  Int ->
  Int ->
  BO β (Mut α (Vector a), Mut α (Vector a))
divide pivot = partUp
  where
    partUp v l u
      | l < u = Control.do
          (Ur e, v) <- copyAtMut l v
          if e < pivot
            then partUp v (l + 1) u
            else partDown v l (u - 1)
      | otherwise = Control.pure $ splitAt l v
    partDown v l u
      | l < u = Control.do
          (Ur e, v) <- copyAtMut u v
          if pivot < e
            then partDown v l (u - 1)
            else Control.do
              v <- unsafeSwap v l u
              partUp v (l + 1) u
      | otherwise = Control.pure $ splitAt l v