Segmentation fault from unsafeFreezeArray#
Summary
Using functions that ultimately desugar into unsafeFreezeArray#
without doing copy beforehand leads to crash.
Steps to reproduce
Clone https://github.com/sergv/array-freeze-bug and do cabal run array-freeze
. If recent enough nix with flakes support is available then do nix develop -c cabal run array-freeze
.
Otherwise consider program:
#!/usr/bin/env cabal
{- cabal:
build-depends:
, base
, primitive
, vector
-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Main (main) where
import Control.Exception
import Control.Monad.Primitive
import Data.Char
import Data.List qualified as L
import Data.Traversable
import Data.Vector qualified as V
import Data.Vector.Mutable qualified as VM
import Data.Vector.Unboxed qualified as U
{-# NOINLINE process #-}
process
:: V.MVector (PrimState IO) (U.Vector Int)
-> [U.Vector Int]
-> V.Vector Int
-> IO Int
process store haystacks needle = do
fmap L.minimum $ for haystacks $ \haystack -> do
V.iforM_ needle $ \idx i -> do
VM.write store idx (U.slice i 5 haystack)
-- -- GOOD
-- store' <- V.freeze store
-- BAD
store' <- V.unsafeFreeze store
evaluate $ processOne store' 0
{-# NOINLINE processOne #-}
processOne
:: V.Vector (U.Vector Int)
-> Int
-> Int
processOne !items !acc
| V.null items
= acc
| otherwise
= let !curr = V.head items
in processOne (V.tail items) $! (acc `max` U.head curr + 1)
main :: IO ()
main = do
let n :: Int
n = 10000
let haystacks :: [U.Vector Int]
haystacks =
map (U.fromList . map ord) $ map (\k -> "abcdefghijklmnopqrstuvwxyz" ++ show k) [1..n]
needle :: V.Vector Int
needle = V.fromList [1..10]
!res <- do
store <- VM.new (V.length needle)
process store haystacks needle
putStrLn $ "res = " ++ show res
pure ()
It can either be built with cabal file present in the git repository or run as standalone cabal script like this: cabal ./Main.hs
.
Expected behavior
In any of outlined scenarios of running the reproduction example the expectation is that program prints a number.
What actually happens is that the program ends with segmentation fault.
Discussion
If unsafeFreeze
in replaced by vanilla freeze
then program works and prints 108. The intention is to avoid copies by employing unsafeFreeze
onto reusable mutable store that gets allocated only once outside of a processing loop.
For convenience the git repsitory includes results of dumping core on GHC 9.4.2. The version that fails: https://github.com/sergv/array-freeze-bug/blob/master/Main.bad.dump-simpl and the version that works: https://github.com/sergv/array-freeze-bug/blob/master/Main.good.dump-simpl. The difference between the two (https://github.com/sergv/array-freeze-bug/blob/master/Main.dump-simpl.diff) comes to calling unsafeArrayFreeze#
vs doing a copy.
Extensive investigation in a bigger program where this problem initially occured suggests that argument of processOne
becomes invalid after some iterations and at one point just contains junk vectors (maybe due to GC, haven't conclusively established that).
Perhaps I'm just missing something about unsafeFrezeArray#
's semantics but its docs are pretty sparse and it seems its return value shoud be valid as long as its argument is kept around.
Environment
- GHC version used: 9.4.2, 9.2.4, 9.0.2, 8.10.7
Optional:
- Operating System: NixOS 22.05
- System Architecture: x86_64