Introduce with# as preferred alternative to touch#
This introduces with#
as a safer alternative to touch#
, which is quite
susceptible to the simplifier optimizing it away. with#
is a new primop
of this form,
with# :: a -> (State# s -> (# State s, r #)) -> State# s -> (# State# s, r #)
which evaluates the r
, ensuring that the a
remains alive throughout
evaluation. This is equivalent to the common pattern,
with' :: a -- ^ value to keep alive
-> (State# s -> (# State# s, r #)) -- ^ value to evaluate
-> State# s -> (# State# s, r #)
with' x r s0 =
case r s0 of { (# s1, r' #) ->
case touch# x s1 of { s2 -> (# s2, r' #) } }
but much harder for the simplifier to mess up.
Consider, for instance the case
test :: IO ()
test = do
arr <- newPinnedByteArray 42
doSomething (byteArrayContents arr)
touch arr
If the simplifier believes that doSomething
will fail to return (e.g. because
it is of the form forever ...), then it will be tempted to drop the
continuation containing touch#
. This would result in the garbage collector
inappropriately freeing arr, resulting in catastrophe. This was the cause of
#14346 (closed).
However, with with# we can write this as
test :: IO ()
test = do
arr <- newPinnedByteArray 42
with# arr (unIO (doSomething (byteArrayContents arr)))
This construction the compiler can't mangle as there is no continuation to
drop. Instead, with#
keeps arr alive by pushing a stack frame.
Consequently, arr will be kept alive as long as we are in
unIO (doSomething ...)
. If and when doSomething
returns, the frame will
be dropped and arr allowed to die.