hash-cons-0.2.0.0: src/Data/HashCons/Internal.hs
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Data.HashCons.Internal where
import Control.Exception
import Control.Monad (when)
import Data.Hashable (Hashable, hash, hashWithSalt)
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import GHC.Base (compareInt#, Int#, IO (..), anyToAddr#, addr2Int#)
import GHC.Exts (Any, Addr#, unsafeCoerce#)
import System.IO.Unsafe (unsafeDupablePerformIO)
import Text.ParserCombinators.ReadPrec (step)
import Text.Read (Read(..), lexP, parens, prec)
import Text.Read.Lex (Lexeme (Ident))
import Debug.Trace
-- | 'HashCons' with a precomputed hash and an 'IORef' to the value.
--
-- WARNING: Do not use this type to wrap types whose Eq or Ord instances
-- allow distinguishable values to compare as equal; this will result in
-- nondeterminism or even visible mutation of semantically-immutable
-- values at runtime.
data HashCons a = HashConsC
{ _hashCons_hash :: {-# UNPACK #-} !Int -- ^ Precomputed hash
, _hashCons_ref :: {-# UNPACK #-} !(IORef a) -- ^ Reference to the value
}
pattern HashCons :: Hashable a => () => a -> HashCons a
pattern HashCons x <- (unHashCons -> x) where
HashCons x = hashCons x
-- | Create a new 'HashCons'.
hashCons :: Hashable a => a -> HashCons a
hashCons a = HashConsC (hash a) $ unsafeDupablePerformIO $ newIORef a
{-# INLINE hashCons #-}
-- | Extract the value from a 'HashCons'.
unHashCons :: HashCons a -> a
unHashCons (HashConsC _ ref) = unsafeDupablePerformIO $ readIORef ref
{-# INLINE unHashCons #-}
-- | Show instance that displays 'HashCons' in the format "hashCons <x>"
instance Show a => Show (HashCons a) where
showsPrec d hc = showParen (d > appPrec) $
showString "hashCons " . showsPrec (appPrec + 1) (unHashCons hc)
where
appPrec = 10
-- | Read instance that parses 'HashCons' from the format "hashCons <x>"
instance (Read a, Hashable a) => Read (HashCons a) where
readPrec = parens $ prec 10 $ do
Ident "hashCons" <- lexP
a <- step readPrec
pure $ hashCons a
instance Eq a => Eq (HashCons a) where
HashConsC h1 ref1 == HashConsC h2 ref2
| ref1 == ref2 = True
| h1 /= h2 = False
| otherwise = compareAndSubstitute ((==) :: a -> a -> Bool) True ref1 ref2
{- INLINE (==) #-}
-- | NOTE: This instance orders by hash first, and only secondarily by
-- the 'Ord' instance of 'a', to improve performance.
instance Ord a => Ord (HashCons a) where
compare (HashConsC h1 ref1) (HashConsC h2 ref2) = case compare h1 h2 of
EQ -> if ref1 == ref2
then EQ
else compareAndSubstitute compare EQ ref1 ref2
result -> result
{-# INLINE compare #-}
instance Eq a => Hashable (HashCons a) where
hashWithSalt salt (HashConsC h _) = hashWithSalt salt h
{-# INLINE hashWithSalt #-}
-- Compare the values in the IORefs with the given comparator, and if the result
-- indicates that they are equal, replace one with the other, preferring the one
-- whose pointer is lower. This is not expected to be totally stable, but it
-- should be *somewhat* stable, and should push us in direction of coalescing
-- more values. Without this, if you have a, b, and c, all with equal but
-- distinct values, and compare b == a and b == c repeatedly, but never compare
-- a == c, you could end up with the value of b flapping between that of a and
-- c, costing the worst-case equality check time repeatedly, and never settling
-- on a particular representation of the value. With this, you should settle on
-- a single value unless you get extremely unlucky with the way that addresses
-- move around.
compareAndSubstitute
:: Eq r
=> (a -> a -> r)
-> r
-> IORef a
-> IORef a
-> r
compareAndSubstitute cmp eq ref1 ref2 = unsafeDupablePerformIO $ do
a1 <- readIORef ref1
a2 <- readIORef ref2
let result = a1 `cmp` a2
when (result == eq) $ do
-- NOTE: These should already be forced by (==), but in the unlikely event
-- that they are not (i.e. because (==) on their type unconditionally
-- returns True), we need to ensure they are not thunks, according to the
-- documentation of anyToAddr#
evaluate a1
evaluate a2
-- NOTE: There is a race condition here: the addresses could change in
-- between when they are read. However, since either (or neither) swap is
-- fine, we are OK with this only working "most" of the time (which we
-- expect to be a very high fraction).
addrCmpResult <- IO $ \s ->
case anyToAddr# (unsafeCoerce# a1 :: Any) s of
(# s', addr1 #) -> case anyToAddr# (unsafeCoerce# a2 :: Any) s' of
(# s'', addr2 #) -> (# s'', addr2Int# addr1 `compareInt#` addr2Int# addr2 #)
case addrCmpResult of
LT -> writeIORef ref2 a1
GT -> writeIORef ref1 a2
EQ -> pure ()
pure result
{-# INLINE compareAndSubstitute #-}