2 {-# LANGUAGE AllowAmbiguousTypes #-}
 
   4 {-# LANGUAGE BangPatterns #-}
 
   6 {-# LANGUAGE DataKinds #-}
 
   8 {-# LANGUAGE ExistentialQuantification #-}
 
  10 -- {-# LANGUAGE MagicHash #-} -- For unsafeCoerce#
 
  11 module Symantic.Semantics.SharingObserver where
 
  13 import Control.Applicative (Applicative (..))
 
  14 import Control.Monad (Monad (..))
 
  16 import Data.Eq (Eq (..))
 
  17 import Data.Function (($), (.))
 
  18 import Data.Functor (Functor, (<$>))
 
  19 import Data.Functor.Compose (Compose (..))
 
  20 import Data.HashMap.Strict (HashMap)
 
  21 import Data.HashSet (HashSet)
 
  22 import Data.Hashable (Hashable, hash, hashWithSalt)
 
  24 import Data.Maybe (Maybe (..), isNothing)
 
  25 import Data.Monoid (Monoid (..))
 
  26 import Data.Ord (Ord (..))
 
  28 -- import GHC.Exts (Int(..))
 
  29 -- import GHC.Prim (unsafeCoerce#)
 
  30 import GHC.StableName (StableName (..), eqStableName, hashStableName, makeStableName)
 
  32 -- import Numeric (showHex)
 
  34 import Control.Monad.Trans.Class qualified as MT
 
  35 import Control.Monad.Trans.Reader qualified as MT
 
  36 import Control.Monad.Trans.State qualified as MT
 
  37 import Control.Monad.Trans.Writer qualified as MT
 
  38 import Data.HashMap.Strict qualified as HM
 
  39 import Data.HashSet qualified as HS
 
  41 import System.IO.Unsafe (unsafePerformIO)
 
  42 import Text.Show (Show (..))
 
  43 import Prelude (error, (+))
 
  45 import Symantic.Syntaxes.Derive
 
  47 -- * Class 'Referenceable'
 
  49 -- | This class is not for end-users like usual symantic operators,
 
  50 -- though it will have to be defined on end-users' interpreters.
 
  51 class Referenceable letName sem where
 
  52   -- | @('ref' isRec letName)@ is a reference to @(letName)@.
 
  53   -- It is introduced by 'observeSharing'.
 
  54   -- @(isRec)@ is 'True' iif. this 'ref'erence is recursive,
 
  55   -- ie. appears within its 'define'.
 
  57   -- TODO: index 'letName' with 'a' to enable dependent-map
 
  58   ref :: Bool -> letName -> sem a
 
  59   ref isRec name = liftDerived (ref isRec name)
 
  61     FromDerived (Referenceable letName) sem =>
 
  66 -- * Class 'Definable'
 
  68 -- | This class is not for end-users like usual symantic operators.
 
  69 -- There should be not need to use it outside this module,
 
  70 -- because used 'define's are gathered in 'Letsable'.
 
  71 class Definable letName sem where
 
  72   -- | @('define' letName sub)@ let-binds @(letName)@ to be equal to @(sub)@.
 
  73   -- This is a temporary node either replaced
 
  74   -- by 'ref' and an entry in 'lets''s 'LetBindings',
 
  75   -- or removed when no 'ref'erence is made to it.
 
  76   define :: letName -> sem a -> sem a
 
  77   define name = liftDerived1 (define name)
 
  79     FromDerived1 (Definable letName) sem =>
 
  84 -- * Class 'MakeLetName'
 
  85 class MakeLetName letName where
 
  86   makeLetName :: SharingName -> IO letName
 
  88 -- * Type 'SharingName'
 
  90 -- | Note that the observable sharing enabled by 'StableName'
 
  91 -- is not perfect as it will not observe all the sharing explicitely done.
 
  93 -- Note also that the observed sharing could be different between ghc and ghci.
 
  94 data SharingName = forall a. SharingName (StableName a)
 
  96 -- | @('makeSharingName' x)@ is like @('makeStableName' x)@ but it also forces
 
  97 -- evaluation of @(x)@ to ensure that the 'StableName' is correct first time,
 
  98 -- which avoids to produce a tree bigger than needed.
 
 100 -- Note that this function uses 'unsafePerformIO' instead of returning in 'IO',
 
 101 -- this is apparently required to avoid infinite loops due to unstable 'StableName'
 
 102 -- in compiled code, and sometimes also in ghci.
 
 104 -- Note that maybe [pseq should be used here](https://gitlab.haskell.org/ghc/ghc/-/issues/2916).
 
 105 makeSharingName :: a -> SharingName
 
 106 makeSharingName !x = SharingName $ unsafePerformIO $ makeStableName x
 
 108 instance Eq SharingName where
 
 109   SharingName x == SharingName y = eqStableName x y
 
 110 instance Hashable SharingName where
 
 111   hash (SharingName n) = hashStableName n
 
 112   hashWithSalt salt (SharingName n) = hashWithSalt salt n
 
 115 instance Show SharingName where
 
 116   showsPrec _ (SharingName n) = showHex (I# (unsafeCoerce# n))
 
 119 -- * Type 'SharingObserver'
 
 120 newtype SharingObserver letName sem a = SharingObserver
 
 121   { unSharingObserver ::
 
 123         (HashSet SharingName)
 
 124         (MT.State (SharingObserverState letName))
 
 125         (SharingFinalizer letName sem a)
 
 128 -- | Interpreter detecting some (Haskell embedded) @let@ definitions used at
 
 129 -- least once and/or recursively, in order to replace them
 
 130 -- with the 'lets' and 'ref' combinators.
 
 131 -- See [Type-safe observable sharing in Haskell](https://doi.org/10.1145/1596638.1596653)
 
 133 -- Beware not to apply 'observeSharing' more than once on the same term
 
 134 -- otherwise some 'define' introduced by the first call
 
 135 -- would be removed by the second call.
 
 140   SharingObserver letName sem a ->
 
 141   WithSharing letName sem a
 
 142 observeSharing (SharingObserver m) =
 
 144         MT.runReaderT m mempty
 
 145           `MT.runState` SharingObserverState
 
 146             { oss_refs = HM.empty
 
 147             , oss_recs = HS.empty
 
 152               | (letName, refCount) <- HM.elems (oss_refs st)
 
 155        in --trace (show refs) $
 
 157             (`MT.runReaderT` refs) $
 
 160 -- ** Type 'WithSharing'
 
 161 type WithSharing letName sem a =
 
 162   (sem a, HM.HashMap letName (SomeLet sem))
 
 165 -- * Type 'WithSharing'
 
 166 data WithSharing letName sem a = WithSharing
 
 167   { lets :: HM.HashMap letName (SomeLet sem)
 
 171   (forall v. sem v -> sem v) ->
 
 172   WithSharing letName sem a ->
 
 173   WithSharing letName sem a
 
 174 mapWithSharing f ws = WithSharing
 
 175   { lets = (\(SomeLet sem) -> SomeLet (f sem)) <$> lets ws
 
 180 -- ** Type 'SharingObserverState'
 
 181 data SharingObserverState letName = SharingObserverState
 
 182   { oss_refs :: HashMap SharingName (letName, Int)
 
 183   , oss_recs :: HashSet SharingName
 
 186 observeSharingNode ::
 
 190   Referenceable letName sem =>
 
 191   MakeLetName letName =>
 
 192   SharingObserver letName sem a ->
 
 193   SharingObserver letName sem a
 
 194 observeSharingNode (SharingObserver m) = SharingObserver $ do
 
 195   let nodeName = makeSharingName m
 
 197   ((letName, seenBefore), seen) <-
 
 201             -- Compose is used to return (letName, seenBefore) along seen
 
 202             -- in the same HashMap lookup.
 
 204               return $ case seenBefore of
 
 206                   ((letName, seenBefore), Just (letName, 0))
 
 208                     letName = unsafePerformIO $ makeLetName nodeName
 
 209                 Just (letName, refCount) ->
 
 210                   ((letName, seenBefore), Just (letName, refCount + 1))
 
 214   parentNames <- MT.ask
 
 215   if nodeName `HS.member` parentNames
 
 217       -- recursive reference to nodeName:
 
 218       -- update seen references
 
 219       -- and mark nodeName as recursive
 
 224             , oss_recs = HS.insert nodeName (oss_recs st)
 
 226       return $ ref True letName
 
 228       -- non-recursive reference to nodeName:
 
 229       -- update seen references
 
 230       -- and recurse if the nodeName hasn't been seen before
 
 231       -- (would be in a preceding sibling branch, not in parentNames).
 
 232       MT.lift $ MT.put st{oss_refs = seen}
 
 233       if isNothing seenBefore
 
 234         then MT.local (HS.insert nodeName) (define letName <$> m)
 
 235         else return $ ref False letName
 
 237 type instance Derived (SharingObserver letName sem) = SharingFinalizer letName sem
 
 239   ( Referenceable letName sem
 
 240   , MakeLetName letName
 
 245   LiftDerived (SharingObserver letName sem)
 
 247   liftDerived = observeSharingNode . SharingObserver . return
 
 249   ( Referenceable letName sem
 
 250   , MakeLetName letName
 
 255   LiftDerived1 (SharingObserver letName sem)
 
 260         f <$> unSharingObserver a
 
 262   ( Referenceable letName sem
 
 263   , MakeLetName letName
 
 268   LiftDerived2 (SharingObserver letName sem)
 
 273         f <$> unSharingObserver a
 
 274           <*> unSharingObserver b
 
 276   ( Referenceable letName sem
 
 277   , MakeLetName letName
 
 282   LiftDerived3 (SharingObserver letName sem)
 
 284   liftDerived3 f a b c =
 
 287         f <$> unSharingObserver a
 
 288           <*> unSharingObserver b
 
 289           <*> unSharingObserver c
 
 291   ( Referenceable letName sem
 
 292   , MakeLetName letName
 
 297   LiftDerived4 (SharingObserver letName sem)
 
 299   liftDerived4 f a b c d =
 
 302         f <$> unSharingObserver a
 
 303           <*> unSharingObserver b
 
 304           <*> unSharingObserver c
 
 305           <*> unSharingObserver d
 
 306 instance Referenceable letName (SharingObserver letName sem) where
 
 307   ref = error "[BUG]: observeSharing MUST NOT be applied twice"
 
 308 instance Definable letName (SharingObserver letName sem) where
 
 309   define = error "[BUG]: observeSharing MUST NOT be applied twice"
 
 310 instance Letsable letName (SharingObserver letName sem) where
 
 311   lets = error "[BUG]: observeSharing MUST NOT be applied twice"
 
 313 -- * Type 'SharingFinalizer'
 
 315 -- | Remove 'define' when non-recursive or unused
 
 316 -- or replace it by 'ref', moving 'define's to the top.
 
 317 newtype SharingFinalizer letName sem a = SharingFinalizer
 
 318   { unFinalizeSharing ::
 
 321         (MT.Writer (LetBindings letName sem))
 
 325 type instance Derived (SharingFinalizer _letName sem) = sem
 
 327   (Eq letName, Hashable letName) =>
 
 328   LiftDerived (SharingFinalizer letName sem)
 
 330   liftDerived = SharingFinalizer . pure
 
 332   (Eq letName, Hashable letName) =>
 
 333   LiftDerived1 (SharingFinalizer letName sem)
 
 335   liftDerived1 f a = SharingFinalizer $ f <$> unFinalizeSharing a
 
 337   (Eq letName, Hashable letName) =>
 
 338   LiftDerived2 (SharingFinalizer letName sem)
 
 342       f <$> unFinalizeSharing a
 
 343         <*> unFinalizeSharing b
 
 345   (Eq letName, Hashable letName) =>
 
 346   LiftDerived3 (SharingFinalizer letName sem)
 
 348   liftDerived3 f a b c =
 
 350       f <$> unFinalizeSharing a
 
 351         <*> unFinalizeSharing b
 
 352         <*> unFinalizeSharing c
 
 354   (Eq letName, Hashable letName) =>
 
 355   LiftDerived4 (SharingFinalizer letName sem)
 
 357   liftDerived4 f a b c d =
 
 359       f <$> unFinalizeSharing a
 
 360         <*> unFinalizeSharing b
 
 361         <*> unFinalizeSharing c
 
 362         <*> unFinalizeSharing d
 
 364   ( Referenceable letName sem
 
 369   Referenceable letName (SharingFinalizer letName sem)
 
 371   ref isRec = liftDerived . ref isRec
 
 373   ( Referenceable letName sem
 
 378   Definable letName (SharingFinalizer letName sem)
 
 380   define name body = SharingFinalizer $ do
 
 383           MT.runWriter $ MT.runReaderT (unFinalizeSharing body) refs
 
 384     if name `HS.member` refs
 
 386         -- This 'define' is 'ref'erenced: move it into the result,
 
 387         -- to put it in scope even when some 'ref' to it exists outside of 'body'
 
 388         -- (which can happen when a body-expression is shared),
 
 389         -- and replace it by a 'ref'.
 
 390         MT.lift $ MT.tell $ HM.insert name (SomeLet sem) defs
 
 391         return $ ref False name
 
 392       else -- Remove this unreferenced 'define' node.
 
 393         unFinalizeSharing body
 
 395 -- * Class 'Letsable'
 
 396 class Letsable letName sem where
 
 397   -- | @('lets' defs x)@ let-binds @(defs)@ in @(x)@.
 
 398   lets :: LetBindings letName sem -> sem a -> sem a
 
 399   lets defs = liftDerived1 (lets ((\(SomeLet val) -> SomeLet (derive val)) <$> defs))
 
 402     FromDerived1 (Letsable letName) sem =>
 
 403     LetBindings letName sem ->
 
 408 data SomeLet sem = forall a. SomeLet (sem a)
 
 410 -- ** Type 'LetBindings'
 
 411 type LetBindings letName sem = HM.HashMap letName (SomeLet sem)
 
 414 -- | Not used but can be written nonetheless.
 
 416   ( Letsable letName sem
 
 420   ) => Letsable letName (SharingFinalizer letName sem) where
 
 421   lets defs x = SharingFinalizer $ do
 
 422     ds <- traverse (\(SomeLet v) -> do
 
 423       r <- unFinalizeSharing v
 
 430 -- ** Type 'OpenRecs'
 
 432 -- | Mutually recursive terms, in open recursion style.
 
 433 type OpenRecs letName a = LetRecs letName (OpenRec letName a)
 
 435 -- | Mutually recursive term, in open recursion style.
 
 436 -- The term is given a @final@ (aka. @self@) map
 
 437 -- of other terms it can refer to (including itself).
 
 438 type OpenRec letName a = LetRecs letName a -> a
 
 440 -- | Recursive let bindings.
 
 441 type LetRecs letName = HM.HashMap letName
 
 443 -- | Least fixpoint combinator.
 
 445 fix f = final where final = f final
 
 447 -- | Least fixpoint combinator of mutually recursive terms.
 
 448 -- @('mutualFix' opens)@ takes a container of terms
 
 449 -- in the open recursion style @(opens)@,
 
 450 -- and return that container of terms with their knots tied-up.
 
 452 -- Used to express mutual recursion and to transparently introduce memoization.
 
 454 -- Here all mutually dependent functions are restricted to the same polymorphic type @(a)@.
 
 455 -- See http://okmij.org/ftp/Computation/fixed-point-combinators.html#Poly-variadic
 
 456 mutualFix :: forall recs a. Functor recs => recs ({-finals-} recs a -> a) -> recs a
 
 457 mutualFix opens = fix f
 
 459     f :: recs a -> recs a
 
 460     f recs = ($ recs) <$> opens