Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alignment isn't respected in Data.Vector.Storable. #75

Closed
danielwaterworth opened this issue Feb 6, 2015 · 24 comments
Closed

Alignment isn't respected in Data.Vector.Storable. #75

danielwaterworth opened this issue Feb 6, 2015 · 24 comments
Labels

Comments

@danielwaterworth
Copy link

I have a type where the alignment of the type is greater than 8 bytes. When I create a vector, using fromList for example, I find that the alignment isn't respected.

I'm using version 0.10.12.2.

@dolio
Copy link
Contributor

dolio commented Feb 6, 2015

What does this mean exactly? Is it aligning to 8 and overwriting things?

@danielwaterworth
Copy link
Author

It means the items aren't aligned in memory as they should be, which means peeking and pokeing may produce undefined behaviour.

Note that the peek and poke functions might require properly aligned addresses to function correctly. This is architecture dependent; thus, portable code should ensure that when peeking or poking values of some type a, the alignment constraint for a, as given by the function alignment is fulfilled.

@cartazio
Copy link
Contributor

cartazio commented Feb 6, 2015

Could you share your storable instance code?

On Fri, Feb 6, 2015, 1:56 PM Daniel Waterworth notifications@github.com
wrote:

It means the items aren't aligned in memory as they should be, which means
peeking and pokeing may produce undefined behaviour.


Reply to this email directly or view it on GitHub
#75 (comment).

@Shimuuar
Copy link
Contributor

Shimuuar commented Feb 7, 2015

I guess it could happen if sizeof a is not multiple of alignment. Vector uses peek/pokeElemOff which calculates offset in array as n * sizeof a so alignment isn't be respected in this case. Storable's documentation doesn't say much about alinment so I think this is main problem

@danielwaterworth
Copy link
Author

Here you go:

data Word512 =
  Word512
    {-# UNPACK #-} !Word64
    {-# UNPACK #-} !Word64
    {-# UNPACK #-} !Word64
    {-# UNPACK #-} !Word64
    {-# UNPACK #-} !Word64
    {-# UNPACK #-} !Word64
    {-# UNPACK #-} !Word64
    {-# UNPACK #-} !Word64
      deriving Show

instance Storable Word512 where
  sizeOf _ = 64

  alignment _ = 64

  peek ptr =
    let
      ptr' = castPtr ptr
    in
      Word512 <$>
        peek ptr' <*>
        peek (plusPtr ptr' 8) <*>
        peek (plusPtr ptr' 16) <*>
        peek (plusPtr ptr' 24) <*>
        peek (plusPtr ptr' 32) <*>
        peek (plusPtr ptr' 40) <*>
        peek (plusPtr ptr' 48) <*>
        peek (plusPtr ptr' 56)

  poke ptr (Word512 a b c d e f g h) = do
    let ptr' = castPtr ptr
    poke ptr' a
    poke (plusPtr ptr' 8) b
    poke (plusPtr ptr' 16) c
    poke (plusPtr ptr' 24) d
    poke (plusPtr ptr' 32) e
    poke (plusPtr ptr' 40) f
    poke (plusPtr ptr' 48) g
    poke (plusPtr ptr' 56) h

@cartazio
Copy link
Contributor

cartazio commented Feb 7, 2015

https://ghc.haskell.org/trac/ghc/ticket/9806 is a (possibly) related ghc ticket.
is the allocation we do using storable vectors using the affected operations? @dolio @ekmett

@cartazio
Copy link
Contributor

cartazio commented Feb 7, 2015

looks like storable vector doesnt do the right thing

{-# INLINE mallocVector #-}
mallocVector :: Storable a => Int -> IO (ForeignPtr a)
mallocVector =
#if __GLASGOW_HASKELL__ >= 605
    doMalloc undefined
        where
          doMalloc :: Storable b => b -> Int -> IO (ForeignPtr b)
          doMalloc dummy size = mallocPlainForeignPtrBytes (size * sizeOf dummy)
#else
    mallocForeignPtrArray
#endif

@cartazio
Copy link
Contributor

cartazio commented Feb 7, 2015

ok, i think the solution is that we need to use

-- | This function is similar to 'mallocForeignPtrBytes', except that the
-- size and alignment of the memory required is given explicitly as numbers of
-- bytes.
mallocForeignPtrAlignedBytes :: Int -> Int -> IO (ForeignPtr a)
mallocForeignPtrAlignedBytes size _align | size < 0 =
  error "mallocForeignPtrAlignedBytes: size must be >= 0"
mallocForeignPtrAlignedBytes (I# size) (I# align) = do
  r <- newIORef NoFinalizers
  IO $ \s ->
     case newAlignedPinnedByteArray# size align s of { (# s', mbarr# #) ->
       (# s', ForeignPtr (byteArrayContents# (unsafeCoerce# mbarr#))
                         (MallocPtr mbarr# r) #)
     }

which is defined in GHC.ForeignPtr

Next question: what range of ghc versions support this?

@cartazio
Copy link
Contributor

cartazio commented Feb 7, 2015

@dolio whatever fix we do for this in master, we should /probably also do a patchlevel bug fix for 0.10 if it doesn't change the supported ghc version range, right?

@cartazio
Copy link
Contributor

cartazio commented Feb 7, 2015

https://github.com/haskell/vector/blob/master/Data/Vector/Storable/Mutable.hs is where the allocation for storable vectors is defined

@cartazio
Copy link
Contributor

cartazio commented Feb 7, 2015

@hvr has pointed me to https://ghc.haskell.org/trac/ghc/ticket/7067

so we can only directly use the provided operation in GHC >= 7.6,
and have to provide some sort of (less efficient) shim that provides the target alignment in < 7.6

@cartazio
Copy link
Contributor

cartazio commented Feb 7, 2015

ok, we've had newAlignedPinnedByteArray# :: Int# -> Int# -> State# s -> (#State# s, MutableByteArray# s#)
since at least 7.0, possibly earlier!

@cartazio
Copy link
Contributor

cartazio commented Feb 7, 2015

ok, 6.12 too. I dont think 6.10 had it. but >=6.12 should be a large enough supported range :)

@danielwaterworth
Copy link
Author

Wow @cartazio, eight consecutive comments. I've got to say, I'm impressed.

@hvr
Copy link
Member

hvr commented Feb 7, 2015

@danielwaterworth combo-breaker! ;-)

@cartazio
Copy link
Contributor

ok, i think given some of the other changes slated for vector 0.11, newAlignedPinnedByteArray# should be on the table for storable vectors in 0.11 release

@sergv
Copy link
Contributor

sergv commented Aug 12, 2015

Hi, I was trying to fix this issue but couldn't find a backwards-compatible way to construct ForeignPtr from MutableByteArray# type returned by newAlignedPinnedByteArray#.

There's a way to obtain Addr# from the mutable array, but it cannot be straightforwardly converted into ForeignPtr. The reason is that only starting from 7.6.3 the Ghc.ForeignPtr module started exporting ForeignPtrContents datatype required to apply ForeignPtr constructor - see https://downloads.haskell.org/~ghc/7.6.3/docs/html/libraries/base-4.6.0.1/src/GHC-ForeignPtr.html (compare with ghc 7.4.2 where it's not exported https://downloads.haskell.org/~ghc/7.4.2/docs/html/libraries/base-4.5.1.0/src/GHC-ForeignPtr.html).

Another possibility is to convert Addr# to Ptr and then wrap Ptr into ForeignPtr. But the only way to obtain Ptr from Addr# seems to be casting via integers. I'm not sure whether this is a good idea.

I should mention in passing, maybe it's ok to use mallocPlainForeignPtrAlignedBytes on newer ghcs and keep old behavior on older ones?

@vincenthz
Copy link

Ptr is defined as is:

data Ptr = Ptr Addr#

so to convert an Addr# to Ptr you just need the Ptr constructor which is available in GHC.Ptr.

@cartazio
Copy link
Contributor

did we fix this with pr #96 ?

@cartazio cartazio added the bug label Jul 24, 2016
@dolio
Copy link
Contributor

dolio commented Aug 11, 2016

I'm going to close this. I think it should be fixed by #96.

For reference, in case this comes back up, I'm somewhat skeptical of the example given. Word512 there is defined to be made up of several Word64 values. I don't think there should be a hardware reason why the type needs to be 64-byte aligned. It should be possible to be 8-byte aligned, because Word64# is what would impose the hardware constraints (because all the peeks and pokes happen on 8-byte values). I think there would need to be a 512-bit hardware primitive to require 512-bit alignment for the usual reasons.

Of course, it wasn't even enforcing 8-byte alignment before, which was wrong.

@dolio dolio closed this as completed Aug 11, 2016
@danielwaterworth
Copy link
Author

I don't think there should be a hardware reason why the type needs to be 64-byte aligned.

There is. It means that the whole thing fits on one cache line.

@ekmett
Copy link
Member

ekmett commented Aug 11, 2016

Actually, I do have a use for it, which is the same one that @danielwaterworth mentions. I have code that needs stuff to align to cache line sizes to avoid cache-line contention a lot of places. This is the same behavior as the cache_aligned_allocator in intel's thread building blocks in C++.

Otherwise I have to double the size of everything yet again to avoid false sharing.

@cartazio
Copy link
Contributor

This sort of alignment is only possible currently for storable vectors
correct?

On Thursday, August 11, 2016, Edward Kmett notifications@github.com wrote:

Actually, I do have a use for it, which is the same one that
@danielwaterworth https://github.com/danielwaterworth mentions. I have
code that needs stuff to align to cache line sizes to avoid cache-line
contention a lot of places. This is the same behavior as the
cache_aligned_allocator in intel's thread building blocks in C++.

Otherwise I have to double the size of everything yet again to avoid false
sharing.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#75 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAAQwkJ_OqApqgB9yAI3T-M3CWFGzKaRks5qewTZgaJpZM4DczBF
.

@ekmett
Copy link
Member

ekmett commented Aug 11, 2016

Yes, as they are the only thing using, you know, Storable. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

8 participants