"throw" in IO monad is incorrectly optimized away
In this example program, if I set things up just right, GHC will incorrectly execute a function on the IO monad. I can get GHC to execute it properly by removing an unnecessary Id from the export list (!!), by removing some complexity in the function (removing if-stmts, for example), or by inserting a trace statement.
On Linux machines (both 32-bit and 64-bit): This bug occurs with GHC 6.12.3 and 6.12.1. It does not occur with 6.8.2 or with 7, but I do not know if this is because the bug was fixed or perhaps the bug is not triggered. Therefore, I'm attaching this example, so that you can identify the source of the bug, and then judge whether it still exists in GHC 7.
I have tested on Mac OS X with GHC 6.10.4 and the bug does not occur there.
In the attached tar-ball, just type "make" and it will build the executable "Main" from "Main.hs". This program calls an IO monad function in "Wrap.hs", which has several execution paths through it, but one path throws an error, using a function in "Error.hs" (and this is the path that "Main" stimulates). The other files are for data types used by "Wrap". I have attempted to reduce these files to only what is needed. Note that the "Id" type needs to use SpeedyString, or else the bug isn't triggered.
When you run "Main", you get this:
# ./Main
Num elements before (expect 1): 1
Num elements after (expect 1): 0
IF YOU SEE THIS MESSAGE, GHC HAS A BUG
In "addWrap", the first trace shows that the list "is" has one element. When we "concatMapM" over the list, it should still have one element. There is no branch that would produce zero elements, and yet that's what GHC did! If GHC had properly executed the monad statements, then the call to "err" would have thrown an error, looking like this:
# ./Main
Num elements before (expect 1): 1
Main: Normal user error
You can get GHC to produce this correct behavior in several ways. One is to uncomment (and thus add in) the trace statement right before the call to "err":
traceM("reached error")
Another way to get the correct behavior is to remove the export of "unsafeMessageExit" from "Error.hs"! How odd! That function is not used anywhere, but maybe it's existence adds more users of "throw" which changes GHC's use analysis or something?
And, of course, you can prevent the bug by simplifying the program in various ways. Removing some if-expressions or case-expressions from "Wrap" will do it. Note that even just leaving in if-expressions that have the same value in both arms (such as the definition of "r_ctxs") will trigger the bug!