pure-borrow-0.0.0.0: src/Control/Monad/Borrow/Pure/Lifetime/Token/Internal.hs
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE ExplicitNamespaces #-}
{-# LANGUAGE ImpredicativeTypes #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RoleAnnotations #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE UnliftedNewtypes #-}
{-# OPTIONS_GHC -Wno-redundant-constraints #-}
{-# OPTIONS_HADDOCK hide #-}
module Control.Monad.Borrow.Pure.Lifetime.Token.Internal (
module Control.Monad.Borrow.Pure.Lifetime.Token.Internal,
) where
import Control.Functor.Linear qualified as Control
import Control.Monad.Borrow.Pure.Affine.Internal
import Control.Monad.Borrow.Pure.Lifetime.Internal
import Data.Coerce.Directed.Unsafe
import Data.Functor.Linear qualified as Data
import Data.Kind (Constraint)
import Data.Unrestricted.Linear
import GHC.Base (TYPE, UnliftedType, noinline, withDict)
import GHC.Exts qualified as GHC
import GHC.Stack (HasCallStack)
import Unsafe.Linear qualified as Unsafe
type role Now nominal
-- | Witness that the lifetime @α@ is ongoing.
data Now (α :: Lifetime) = UnsafeNow
data SomeNow where
MkSomeNow :: Now (Al i) %1 -> SomeNow
newLifetime :: Linearly %1 -> SomeNow
newLifetime UnsafeLinearly = MkSomeNow UnsafeNow
newLifetime' :: Linearly %1 -> (forall ι. Now (Al ι) %1 -> a) %1 -> a
newLifetime' lin k =
case newLifetime lin of
MkSomeNow now -> k now
-- | Static Lifetime is always available.
nowStatic :: Now Static
nowStatic = UnsafeNow
instance Affine (Now α) where
aff UnsafeNow = UnsafeAff UnsafeNow
{-# INLINE aff #-}
instance LinearOnly (Now α) where
linearOnly = UnsafeLinearOnly
{-# INLINE linearOnly #-}
type role EndToken nominal
-- | Witness that the lifetime @α@ has ended.
data EndToken (α :: Lifetime) = UnsafeEnd
instance (α >= β) => EndToken α <: EndToken β where
subtype = UnsafeSubtype
endLifetime :: Now (Al i) %1 -> (Ur (EndToken (Al i)))
endLifetime UnsafeNow = Ur UnsafeEnd
-- | Witness that the lifetime @α@ has ended.
class End (α :: Lifetime) where
endToken :: EndToken α
-- | Static lifetime lasts forever.
neverEnds :: (HasCallStack, End Static) => a
neverEnds = error "Unreachable: if you see this, you created an End Static in the internal code!"
{- |
Utility type to represent an object available after the lifetime @α@.
You can use 'Control.Applicative' and 'Control.Monad' instances to write 'After' conveniently.
-}
newtype After α a = After ((End α) => a)
instance (α <= β, a <: b) => After α a <: After β b where
subtype = UnsafeSubtype
unAfter :: (End α) => After α a %1 -> a
{-# INLINE unAfter #-}
unAfter (After r) = r
withEnd :: forall α r. EndToken α -> After α r %1 -> r
{-# INLINE withEnd #-}
withEnd end (After a) = Unsafe.toLinear (withDict @(End α) end) a
instance Data.Functor (After α) where
fmap f (After r) = After (f r)
{-# INLINE fmap #-}
instance Control.Functor (After α) where
fmap f (After r) = After (f r)
{-# INLINE fmap #-}
instance Data.Applicative (After α) where
pure a = After a
{-# INLINE pure #-}
After f <*> After r = After (f r)
{-# INLINE (<*>) #-}
instance Control.Applicative (After α) where
pure a = After a
{-# INLINE pure #-}
After f <*> After r = After (f r)
{-# INLINE (<*>) #-}
instance Control.Monad (After α) where
After r >>= k = After (unAfter (k r))
{-# INLINE (>>=) #-}
-- | Witness that the current computation is in a linear context.
data Linearly = UnsafeLinearly
linearly :: (Movable a) => (Linearly %1 -> a) %1 -> a
{-# NOINLINE linearly #-}
linearly = GHC.noinline \f ->
case move (f UnsafeLinearly) of
Ur !x -> x
data LinearOnlyWitness a = UnsafeLinearOnly
-- | A (non-bottom) value of the type @a@ can only live in a linear context.
type LinearOnly :: forall rep. TYPE rep -> Constraint
class LinearOnly a where
linearOnly :: LinearOnlyWitness a
withLinearly :: (LinearOnly a) => a %1 -> (Linearly, a)
{-# NOINLINE withLinearly #-}
withLinearly = noinline \ !a -> (UnsafeLinearly, a)
withLinearly# :: forall (a :: UnliftedType). (LinearOnly a) => a %1 -> (# Linearly, a #)
withLinearly# = noinline \ !a -> (# UnsafeLinearly, a #)
instance LinearOnly Linearly where
linearOnly = UnsafeLinearOnly
{-# INLINE linearOnly #-}
instance Consumable Linearly where
consume = \UnsafeLinearly -> ()
{-# INLINE consume #-}
instance Dupable Linearly where
-- NOTE: without inlining, GHC optimizer (especially, full-laziness and demand analysis)
-- can eliminate duplicated 'Linearly's too eagerly, ruining the state-threading,
-- and result in resource corruption in some cases.
-- Such optimization can manifest when, for example, one duplicates 'Linearly'
-- tokens multiple times and feed them to different allocation functions.
-- Although we are not able to detect the exact situation, but we believe that
-- GHC optimizer then eliminates every invocation on bulk allocation functions
-- into a single one, which introduces unintended reuse of linear resources.
-- Hence, we must instruct GHC not to inline this function and force
dup2 = GHC.noinline \UnsafeLinearly -> (UnsafeLinearly, UnsafeLinearly)
{-# NOINLINE dup2 #-}