{-# LANGUAGE MagicHash, UnboxedTuples #-} -- | Published almost entirely for benchmarks, comparing to stdlib! -- Should have little direct interest to Harfbuzz callers, & I'm not promising a stable API. -- This is here because, as it turns out, Harfbuzz can return a lot of output! module Data.Text.Glyphize.Array where import Foreign.Storable (Storable(..)) import Foreign.ForeignPtr (ForeignPtr, plusForeignPtr, withForeignPtr, mallocForeignPtrArray) import Foreign.Ptr import Foreign.Marshal.Array (copyArray) import GHC.IO (IO(IO)) import GHC.Exts (realWorld#, oneShot) -- | Clone the given array so it can be freed without the losing access to the data. -- Uses `memcpy` so it gets very heavily optimized by the OS. clonePtr :: Storable a => Ptr a -> Int -> IO (ForeignPtr a) clonePtr ptr l = do ret <- mallocForeignPtrArray l withForeignPtr ret $ \ptr' -> copyArray ptr' ptr l return ret -- | Iterate over an array in a ForeignPtr, no matter how small or large it is. peekLazy :: Storable a => ForeignPtr a -> Int -> [a] peekLazy fp 0 = [] peekLazy fp n | n <= chunkSize = withFP $ peekEager [] n | otherwise = withFP $ peekEager (plusForeignPtr fp chunkSize `peekLazy` (-) n chunkSize) chunkSize where withFP = accursedUnutterablePerformIO . withForeignPtr fp -- | Variation of peekArray, taking a tail to append to the decoded list. peekEager :: Storable a => [a] -> Int -> Ptr a -> IO [a] peekEager acc 0 ptr = return acc peekEager acc n ptr = let n' = pred n in do e <- peekElemOff ptr n' peekEager (e:acc) n' ptr -- | How many words should be decoded by `peekLazy` & `iterateLazy`. chunkSize :: Int chunkSize = 1024 -- 4k, benchmarks seem to like it! -- | Convert an array from C code into a Haskell list, -- performant no matter how small or large it is. iterateLazy :: Storable a => Ptr a -> Int -> IO [a] iterateLazy ptr l | l < 0 = putStrLn ("Invalid array length: " ++ show l) >> return [] | l == 0 = return [] | otherwise = do fp <- clonePtr ptr l return $ noCache peekLazy fp $ fromEnum l -- | This \"function\" has a superficial similarity to 'System.IO.Unsafe.unsafePerformIO' but -- it is in fact a malevolent agent of chaos. It unpicks the seams of reality -- (and the 'IO' monad) so that the normal rules no longer apply. It lulls you -- into thinking it is reasonable, but when you are not looking it stabs you -- in the back and aliases all of your mutable buffers. The carcass of many a -- seasoned Haskell programmer lie strewn at its feet. -- -- Witness the trail of destruction: -- -- * -- -- * -- -- * -- -- * -- -- * -- -- * -- -- * -- -- Do not talk about \"safe\"! You do not know what is safe! -- -- Yield not to its blasphemous call! Flee traveller! Flee or you will be -- corrupted and devoured! -- {-# INLINE accursedUnutterablePerformIO #-} accursedUnutterablePerformIO :: IO a -> a accursedUnutterablePerformIO (IO m) = case m realWorld# of (# _, r #) -> r -- | Harfbuzz produces ~40x as much output data as its input data. -- In many applications that input data would be a large fraction of its heap. -- As such, unless callers are processing these results, it is usually more -- efficient for Haskell to recompute the glyphs than to store them. -- -- This synonym of `oneShot` is used to instruct Haskell of this fact. noCache :: (a -> b) -> a -> b noCache = oneShot