Skip to content

Commit bdb1e3e

Browse files
committed
Atomic modification operations for lifted arrays
Addresses #64
1 parent 421e5d5 commit bdb1e3e

File tree

1 file changed

+74
-0
lines changed

1 file changed

+74
-0
lines changed

atomic-primops/Data/Atomics.hs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ module Data.Atomics
2121

2222
-- * Atomic operations on mutable arrays
2323
casArrayElem, casArrayElem2, readArrayElem,
24+
atomicModifyArrayElem_,
25+
atomicModifyArrayElem,
26+
atomicModifyArrayElem',
2427

2528
-- * Atomic operations on byte arrays
2629
casByteArrayInt,
@@ -62,6 +65,7 @@ import GHC.Prim
6265
import GHC.Base (Int(I#))
6366
import GHC.IO (IO(IO))
6467
-- import GHC.Word (Word(W#))
68+
import System.IO.Unsafe (unsafeDupablePerformIO)
6569

6670

6771
#if MIN_VERSION_base(4,8,0)
@@ -70,6 +74,8 @@ import Data.Bits
7074
import Data.Primitive.ByteArray (readByteArray)
7175
#endif
7276

77+
import GHC.Exts (lazy)
78+
7379
#ifdef DEBUG_ATOMICS
7480
#warning "Activating DEBUG_ATOMICS... NOINLINE's and more"
7581
{-# NOINLINE seal #-}
@@ -134,6 +140,74 @@ casArrayElem2 (MutableArray arr#) (I# i#) old new = IO$ \s1# ->
134140
case casArrayTicketed# arr# i# old new s1# of
135141
(# s2#, x#, res #) -> (# s2#, (x# ==# 0#, res) #)
136142

143+
-- | A version of 'atomicModifyIORef' for arrays that returns
144+
-- /both/ the new value and the result. This function is very
145+
-- lazy; in particular,
146+
--
147+
-- @ atomicModifyArrayElem_ mary i (const undefined) @
148+
--
149+
-- will succeed, although both the new element and the result will
150+
-- be undefined.
151+
--
152+
atomicModifyArrayElem_ :: forall a b. MutableArray RealWorld a
153+
-> Int
154+
-> (a -> (a, b))
155+
-> IO (a, b)
156+
-- We should ideally implement this in CMM to avoid the extra
157+
-- IORef and such. I think the atomicModifyMutVar# primop should
158+
-- really have been given this type.
159+
atomicModifyArrayElem_ mary i fn = do
160+
original <- readArrayElem mary i
161+
oldref <- newIORef original
162+
let
163+
nr@(new, _) = unsafeDupablePerformIO $ fn . peekTicket <$> readIORef oldref
164+
loop :: Ticket a -> IO (a, b)
165+
loop tick = do
166+
(b,tick') <- casArrayElem2 mary i tick (seal new)
167+
-- We must be *lazy* here;
168+
-- neither new nor nr may be
169+
-- forced until the CAS succeeds.
170+
if b
171+
then do
172+
-- lazy to prevent demand analysis from forcing it early.
173+
return (lazy nr)
174+
else do
175+
writeIORef oldref tick'
176+
loop tick'
177+
loop original
178+
179+
-- | A version of 'atomicModifyIORef' for arrays. Unlike 'atomicModifyIORef',
180+
-- the user function is applied eagerly. In particular,
181+
--
182+
-- @atomicModifyArrayElem mary i (const undefined)@
183+
--
184+
-- will throw an exception immediately.
185+
atomicModifyArrayElem :: forall a b. MutableArray RealWorld a
186+
-> Int
187+
-> (a -> (a, b))
188+
-> IO b
189+
atomicModifyArrayElem mary i fn = do
190+
(_new, res) <- atomicModifyArrayElem_ mary i fn
191+
return res
192+
193+
-- | A version of 'atomicModifyArrayElem' that forces the stored
194+
-- value to WHNF. This is *lazier* than 'atomicModifyIORef''; in
195+
-- particular, it does not force the result value.
196+
--
197+
-- @
198+
-- atomicModifyArrayElem' mary i f =
199+
-- atomicModifyArrayElem mary i (\a -> case f a of (!a', b) -> (a', b))
200+
-- @
201+
atomicModifyArrayElem' :: forall a b. MutableArray RealWorld a
202+
-> Int
203+
-> (a -> (a, b))
204+
-> IO b
205+
atomicModifyArrayElem' mary i fn = do
206+
(new, res) <- atomicModifyArrayElem_ mary i fn
207+
evaluate new
208+
return res
209+
210+
137211
-- | Ordinary processor load instruction (non-atomic, not implying any memory barriers).
138212
readArrayElem :: forall a . MutableArray RealWorld a -> Int -> IO (Ticket a)
139213
-- readArrayElem = unsafeCoerce# readArray#

0 commit comments

Comments
 (0)