Unexpected boxing in generated code

Simon Peyton-Jones simonpj at microsoft.com
Tue Aug 7 13:14:37 EDT 2007

| I've got an inner loop that I think I can see is strict in the Int
| argument being passed around, but that GHC 6.6.1 isn't unboxing. In the
| following example both functions take a GHC.Base.Int, which I think
| should be an Int#.

OK this is an interesting one. Here's the smallest program that demonstrates the problem.

foreign import ccall unsafe "stdio.h getchar" getchar :: IO CInt

f56 :: State# RealWorld -> Int -> Int
f56 s v2 = case (unIO getchar s) of
           (# s' , v6  #) ->
              case v2 of I# _ -> f56 s' v2

GHC says this is lazy in v2, which it obviously isn't.  Why?  Because there's a special hack (introduced after an earlier bug report) in the strictness analyser to account for the fact that a ccall might exit the program.  Suppose instead of calling 'getchar' we called 'exit'!  Then f56 is not strict in v2 any more.

Here was a larger program that demonstrated the problem:

        do { let len = <expensive> ;
           ; when (...) (exitWith ExitSuccess)
           ; print len }

Suppose exitWith doesn't exit; it loops or returns. Then 'len' is sure to be evaluated, and GHC will evaluate it before the 'when'.

The hack is in the demand analyser, to make it believe that any I/O operation (including getchar!) might exit instead of returning.

OK, so that's the reason you aren't getting proper strictness in your inner loop.  What to do about it? It would be easy to revert to the non-hack situation, in which case 'len' might well be evaluated in the program above, even in the program above.  To make the program sufficiently lazy you could write

        do { let len = <expensive> ;
           ; when (...) (exitWith ExitSuccess)
           ; lazy (print len) }

Here 'lazy' is a (documented) function that makes its argument appear to be evaluated lazily, so far as the demand analyser is concerned.  But this is horribly non-compositional.  ANYWHERE you say
        do { a; b; c }
and b might exit, then you should really say 'lazy c'.

One could imagine an analysis for "definitely does not exit".  But it only really makes sense for IO-ish things.

In short, it's hard to see a beautiful solution.  Does anyone else have ideas?


More information about the Glasgow-haskell-users mailing list