packages feed

vext-0.1.0.0: src-imp/Word1.hs

{-# language BangPatterns #-}
{-# language MagicHash #-}
{-# language RankNTypes #-}
{-# language TypeApplications #-}
{-# language TypeFamilies #-}
{-# language TypeInType #-}
{-# language StandaloneKindSignatures #-}
{-# language UnboxedTuples #-}

-- Note: In this module, we assume that Word is a 64-bit number.
-- It is not hard to correct this assumption with CPP, but this will
-- not be done until someones needs to run this on a 32-bit platform.
--
-- Note: A slightly longer-term goal is to preserve an invariant that
-- any unused trailing bits (there are between 0 and 63 of them in
-- any bit vector) are all zero. Currently, their values are undefined.
-- If they are all zero, then certain read-only operations become
-- faster. For example:
--
-- * Testing that two bit vectors are equal
-- * Testing if any bit in a bit vector is set to true
--
-- Some operations that produce vectors become slower, like @complement@
-- and @initialized@. Others, like @zipAnd@ and @zipOr@, naturally
-- preserve the invariant and do not require special code for the tail. 
--
-- If we start trying to preserve this invariant, we need to first write
-- failing tests that show where the invariant is not currently upheld.
-- At the least, @initialized@ and @unsafeShrinkFreeze@ need changes.
module Word1
  ( R
  , A#
  , M#
  , eq
  , eq#
  , empty#
  , index#
  , write#
  , read#
  , unsafeFreeze#
  , initialized#
  , set#
  , unsafeShrinkFreeze#
  , thaw#
  , freeze#
  , copy#
  ) where

import GHC.Exts
import Data.Kind (Type)
import Data.Unlifted (PrimArray#(..),MutablePrimArray#(..))
import EmptyPrimArray (emptyPrimArray#)

import qualified GHC.Exts as Exts

type A# = PrimArray# @'WordRep
type M# = MutablePrimArray# @'WordRep
type R = 'WordRep

eq :: forall (a :: TYPE R). a -> a -> Bool
{-# inline eq #-}
eq x y = isTrue# (eqWord# (unsafeToWord x) (unsafeToWord y))

eq# :: forall (a :: TYPE R). a -> a -> Int#
{-# inline eq# #-}
eq# x y = eqWord# (unsafeToWord x) (unsafeToWord y)

-- Precondition:
-- Argument is not negative
-- Postconditions:
-- First element is >=0, <n/64
-- Second element is >=0, <64
splitIndex_ :: Int# -> (# Int#, Int# #)
{-# inline splitIndex_ #-}
splitIndex_ bitIx = (# wordIx, intraWordIx #)
  where
  wordIx = bitIx `uncheckedIShiftRL#` 6#
  intraWordIx = bitIx `andI#` 0x3F#

-- Precondition: argument must be 0 or 1.
unsafeFromWord :: forall (a :: TYPE 'WordRep). Word# -> a
unsafeFromWord x = unsafeCoerce# x

unsafeToWord :: forall (a :: TYPE 'WordRep). a -> Word#
unsafeToWord x = unsafeCoerce# x

internalIndex# :: ByteArray# -> Int# -> Word#
internalIndex# arr i =
  let !(# wordIx, intraWordIx #) = splitIndex_ i
      !bitBundle = Exts.indexWordArray# arr wordIx
   in unsafeFromWord ((bitBundle `uncheckedShiftRL#` intraWordIx) `and#` 1## )

index# :: forall (a :: TYPE R). A# a -> Int# -> a
index# (PrimArray# arr) i = unsafeFromWord (internalIndex# arr i)

read# :: forall (s :: Type) (a :: TYPE R).
  M# s a -> Int# -> State# s -> (# State# s, a #)
read# (MutablePrimArray# m) i st =
  let !(# wordIx, intraWordIx #) = splitIndex_ i
      !(# st', bitBundle #) = Exts.readWordArray# m wordIx st
   in (# st', unsafeFromWord ((bitBundle `uncheckedShiftRL#` intraWordIx) `and#` 1## ) #)


write# :: forall (s :: Type) (a :: TYPE R).
  M# s a -> Int# -> a -> State# s -> State# s
write# (MutablePrimArray# arr) i v st =
  internalWrite# arr i (unsafeToWord v) st

internalWrite# :: forall (s :: Type).
  MutableByteArray# s -> Int# -> Word# -> State# s -> State# s
internalWrite# arr i v st =
  let !(# wordIx, intraWordIx #) = splitIndex_ i
      !(# st', bitBundle #) = Exts.readWordArray# arr wordIx st
      !mask = not# (1## `uncheckedShiftL#` intraWordIx)
      !bitBundle' = (bitBundle `and#` mask) `or#` (v `uncheckedShiftL#` intraWordIx)
   in Exts.writeWordArray# arr wordIx bitBundle' st'

empty# :: forall (a :: TYPE R). (# #) -> A# a
empty# = emptyPrimArray#

unsafeFreeze# :: forall (s :: Type) (a :: TYPE R).
     M# s a
  -> State# s
  -> (# State# s, A# a #)
unsafeFreeze# (MutablePrimArray# m) s0 = case unsafeFreezeByteArray# m s0 of
  (# s1, v #) -> (# s1, PrimArray# v #)

-- 
-- 
-- unsafeFreeze# :: forall (s :: Type) (a :: TYPE R).
--      M# s a
--   -> State# s
--   -> (# State# s, A# a #)
-- unsafeFreeze# (MutablePrimArray# m) s0 = case unsafeFreezeByteArray# m s0 of
--   (# s1, v #) -> (# s1, PrimArray# v #)
-- 
-- empty# :: forall (a :: TYPE R). (# #) -> A# a
-- empty# = emptyPrimArray#
-- 
--   (# s1, m #) -> case Exts.setByteArray# m 0# n (Exts.word2Int# (Exts.word8ToWord# (unsafeToW8 a))) s1 of
--     s2 -> (# s2, MutablePrimArray# m #)
-- 
-- set# :: forall (s :: Type) (a :: TYPE R).
--      M# s a
--   -> Int#
--   -> Int#
--   -> a
--   -> State# s
--   -> State# s
-- set# (MutablePrimArray# m) off0 len0 a s0 = Exts.setByteArray# m off0 len0 (Exts.word2Int# (Exts.word8ToWord# (unsafeToW8 a))) s0
-- 

-- shrink and freeze, all at once
unsafeShrinkFreeze# ::
     M# s a
  -> Int#
  -> State# s
  -> (# State# s, A# a #)
unsafeShrinkFreeze# (MutablePrimArray# marr) sz st0 =
  case getSizeofMutableByteArray# marr st0 of
    (# st, oldSzBytes #) ->
      let !(# wordSz, subWordSz #) = splitIndex_ sz
          !paddedSz = wordSz +# if isTrue# (subWordSz ==# 0#) then 0# else 1#
          !szBytes = paddedSz *# 8#
          !st' = case szBytes ==# oldSzBytes of
            1# -> st
            _ -> Exts.shrinkMutableByteArray# marr szBytes st
       in case Exts.unsafeFreezeByteArray# marr st' of
            (# st'', v #) -> (# st'', PrimArray# v #)

-- thaw# :: forall (s :: Type) (a :: TYPE R).
--      A# a
--   -> Int#
--   -> Int#
--   -> State# s
--   -> (# State# s, M# s a #)
-- thaw# (PrimArray# v) off len s0 = case Exts.newByteArray# len s0 of
--   (# s1, m #) -> case Exts.copyByteArray# v off m 0# len s1 of
--     s2 -> (# s2, MutablePrimArray# m #)

set# :: forall (s :: Type) (a :: TYPE R).
     M# s a
  -> Int#
  -> Int#
  -> a
  -> State# s
  -> State# s
{-# inline set# #-}
set# parr@(MutablePrimArray# arr) off0 len0 v st0 =
    let subOff = off0 `andI#` 7#
      -- set non-byte-aligned, initial bits
        len = min# len0 (8# -# subOff)
        st' = bitLoop off0 len st0
        -- set full bytes
        off' = off0 +# len
        len' = len0 -# len
        st'' = writeBytes off' len' st'
        -- set trailing bits smaller than a byte
        off'' = off' +# ((len' `uncheckedIShiftRL#` 3#) `uncheckedIShiftL#` 3#)
        len'' = len' `andI#` 7#
     in bitLoop off'' len'' st''
  where
  -- TODO could split bitLoop into writeBitsUnaligned and writeBitsAligned, which would use masking instead of a loop
  bitLoop _ 0# st = st
  bitLoop off len st =
    let st' = write# parr off v st
     in bitLoop (off +# 1#) (len -# 1#) st'
  writeBytes off len st =
    let !offB = off `uncheckedIShiftRL#` 3#
        !lenB = len `uncheckedIShiftRL#` 3#
     in Exts.setByteArray# arr offB lenB vB st
  vB = case unsafeToWord v of
    0## -> 0#
    _ -> 0xFF#

-- TODO: Zero out any trailing bits. 
initialized# :: forall (s :: Type) (a :: TYPE R).
     Int#
  -> a
  -> State# s
  -> (# State# s, M# s a #)
initialized# sz v0 st =
  let !(# wordSz, subWordSz #) = splitIndex_ sz
      !paddedSz = wordSz +# if isTrue# (subWordSz ==# 0#) then 0# else 1#
      !szBytes = paddedSz *# 8#
      !(# st', marr #) = Exts.newByteArray# szBytes st
      !v = case unsafeToWord v0 of
        0## -> 0#
        _ -> 0xFF#
      !st'' = Exts.setByteArray# marr 0# szBytes v st'
   in (# st'', MutablePrimArray# marr #)

min# :: Int# -> Int# -> Int#
{-# inline min# #-}
min# a b = if isTrue# (a <# b) then a else b

copy# :: forall (s :: Type) (a :: TYPE R).
     M# s a -- destination
  -> Int# -- destination offset
  -> A# a -- source
  -> Int# -- source offset
  -> Int# -- length
  -> State# s
  -> State# s
copy# (MutablePrimArray# dst) doff (PrimArray# src) soff len st =
  internalCopy# dst doff src soff len st

internalCopy# :: MutableByteArray# s -> Int# -> ByteArray# -> Int# -> Int# -> State# s -> State# s
{-# inline copy# #-}
internalCopy# dst 0# src 0# len st =
-- TODO when soff == doff, we can do like set#
-- first align with naiveCopy, then copy by bytes, then copy the traling bits with naiveCopy
-- in fact, this can work even when soff - doff divisible by 8
  let !lenB = len `uncheckedIShiftRL#` 3#
      !st' = Exts.copyByteArray# src 0# dst 0# lenB st
      !off' = lenB `uncheckedIShiftL#` 3#
      !len' = len `andI#` 7#
   in internalNaiveCopy# dst off' src off' len' st'
internalCopy# dst doff src soff len st = internalNaiveCopy# dst doff src soff len st

internalNaiveCopy# :: MutableByteArray# s -> Int# -> ByteArray# -> Int# -> Int# -> State# s -> State# s
-- TODO if I had an index64 :: ByteArray# -> off:Int#  -> len:Int# -> Int#
-- that reads up to `min len 64` unaligned bits starting at off
-- then I could write whole words at a time after aligning the doff, just as in set#
internalNaiveCopy# _ _ _ _ 0# st = st
internalNaiveCopy# dst doff src soff len st =
  let !v = internalIndex# src soff
      !st' = internalWrite# dst doff v st
   in internalNaiveCopy# dst (doff +# 1#) src (soff +# 1#) (len -# 1#) st'

-- This should be rewritten, but it works for now. At least its
-- correctness is clear.
thaw# :: forall (s :: Type) (a :: TYPE R).
     A# a
  -> Int#
  -> Int#
  -> State# s
  -> (# State# s, M# s a #)
thaw# v off len s0 = case initialized# len (unsafeFromWord 0## ) s0 of
  (# s1, m #) -> case copy# m 0# v off len s1 of
    s2 -> (# s2, m #)

freeze# :: forall (s :: Type) (a :: TYPE R).
     M# s a
  -> Int#
  -> Int#
  -> State# s
  -> (# State# s, A# a #)
freeze# (MutablePrimArray# v) off len s0 = case off of
  0# ->
    let !(# wordSz, subWordSz #) = splitIndex_ len
        !paddedSz = wordSz +# if isTrue# (subWordSz ==# 0#) then 0# else 1#
        !szBytes = paddedSz *# 8#
     in case Exts.newByteArray# szBytes s0 of
          (# s1, m #) -> case Exts.copyMutableByteArray# v 0# m 0# szBytes s1 of
            s2 -> case Exts.unsafeFreezeByteArray# m s2 of
              (# s3, x #) -> (# s3, PrimArray# x #)
  _ -> errorWithoutStackTrace "vext:imp:Word1.freeze#: write the non-zero case"