Opened 6 years ago

Closed 6 years ago

Last modified 6 years ago

#9035 closed bug (invalid)

ghci sometimes displays Word32 as Word64

Reported by: MikeIzbicki Owned by:
Priority: normal Milestone:
Component: GHCi Version: 7.8.2
Keywords: Cc: hvr
Operating System: Linux Architecture: x86_64 (amd64)
Type of failure: Incorrect result at runtime Test Case:
Blocked By: Blocking:
Related Tickets: Differential Rev(s):
Wiki Page:

Description

Given this code:

module Main
    where

import Data.Word
import Unsafe.Coerce
import System.IO

nanFloat :: Float
nanFloat = unsafeCoerce (maxBound :: Word32)

float2word32 :: Float -> Word32
float2word32 = unsafeCoerce

nanDouble :: Double
nanDouble = unsafeCoerce (maxBound :: Word64)

double2word64 :: Double -> Word64
double2word64 = unsafeCoerce

main = do
    putStrLn $ "nanFloat  = " ++ show (float2word32 nanFloat)
    putStrLn $ "nanFloat  = " ++ show (float2word32 $ nanFloat + 1)
    putStrLn $ "nanDouble = " ++ show (double2word64 nanDouble)
    putStrLn $ "nanDouble = " ++ show (double2word64 $ nanDouble + 1)

If we compile with GHC and run, we correctly output:

nanFloat  = 4294967295
nanFloat  = 4294967295
nanDouble = 18446744073709551615
nanDouble = 18446744073709551615

But if we instead load in ghci, we get the following output:

nanFloat  = 4294967295
nanFloat  = 140247862083583
nanDouble = 18446744073709551615
nanDouble = 18446744073709551615

For some reason, ghci is displaying (nanFloat+1) as having significantly more digits than can possibly stored in a Word32 value.

Test system: Intel Core 2 Duo running Debian with GHC 7.8.2

Change History (6)

comment:1 in reply to:  description Changed 6 years ago by hvr

Replying to MikeIzbicki:

For some reason, ghci is displaying (nanFloat+1) as having significantly more digits than can possibly stored in a Word32 value.

Part of the reason is, that Word32 is actually implemented as

data Word32 = W32# Word# deriving (Eq, Ord)

and Word# is actually 64bit wide on the x86_64 platform. So, Word32 internally can actually store more digits than a 32-bit unsigned integer is supposed to hold. What I can't explain, though, is why a single-precision float, which should be 32bit wide, leaks into the hidden unused upper 32bit part of the Float (and thus also into the unsafely coerced Word32) heap object.

comment:2 Changed 6 years ago by igloo

Resolution: invalid
Status: newclosed

This use of unsafeCoerce is not safe: See this which links to here for more details.

comment:3 in reply to:  2 Changed 6 years ago by MikeIzbicki

Replying to igloo:

This use of unsafeCoerce is not safe: See this which links to here for more details.

I don't understand why it is incorrect. Is it because the float is boxed? We can easily change the code to:

nanFloat :: Float
nanFloat = word32ToFloat (maxBound :: Word32)

word32ToFloat :: Word32 -> Float
word32ToFloat (W32# w#) = F# (unsafeCoerce# w#)

float2word32 :: Float -> Word32
float2word32 (F# f#) = W32# (unsafeCoerce# f#)

Now, we are casting between two unboxed types of the same size, which is explicitly allowed. The results, however, are exactly the same as before.

Last edited 6 years ago by MikeIzbicki (previous) (diff)

comment:4 Changed 6 years ago by carter

you can't do an unsafe coerce between Words and Floats in ghc currently and have it be well defined . Its got nothing to do with boxing, it has to do with Floats and Words actually living is completely distinct groups of registers in the CPU. eg on x86, ghc currently only manipulates words in the general purpose registers, and floats are in SSE registers.

(yes, there ARE word manipulation sse instructions, but ghc currently doesn't use them.. yet)

NH2 wrote a cute wee lib to cast between float and words https://github.com/nh2/float-cast, which works by writing the input to memory as one type, and reading the memory location back as the other type. I think the only reason some of your code works at all is your unsafe coerce actually will by accident do that "casted memory read" off the heap.

The other issue is your Word32 is actually going to be 64bit (1 whoel register) value on 64 bit systems, but Floats are always 32b bits, and your unsafe coerce doesn't actually have any well defined way of mapping between the two. Again, anything resembling that working in current GHC is actually a complete "this is undefined" accident :)

comment:5 Changed 6 years ago by MikeIzbicki

Ahh, I see. I missed the clause in the documentation that says you can't convert between floating point and integer types. I have a handful of code that needs fixing now :(

Just for my own curiosity, I'd like to read the spot in GHC that prevents this particular usage of unsafeCoerce. Any idea which files I should look at?

comment:6 Changed 6 years ago by simonpj

I think it would be a great idea for Core Lint to check for uses of unsafeCoerce that don't obey the rules. See #9122.

I'm afraid I don't know where to look for the reason for the int/float difficulty. I'd write a tiny function that exhibits the unsafe conversion and look the code it generates.

Simon

Last edited 6 years ago by simonpj (previous) (diff)
Note: See TracTickets for help on using tickets.