loopification non-sense around void args
Summary
Note [Void arguments in self-recursive tail calls]
looks extremely suspicious. I reproduce it here:
-- Note [Void arguments in self-recursive tail calls]
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--
-- State# tokens can get in the way of the loopification optimization as seen in
-- #11372. Consider this:
--
-- foo :: [a]
-- -> (a -> State# s -> (# State s, Bool #))
-- -> State# s
-- -> (# State# s, Maybe a #)
-- foo [] f s = (# s, Nothing #)
-- foo (x:xs) f s = case f x s of
-- (# s', b #) -> case b of
-- True -> (# s', Just x #)
-- False -> foo xs f s'
--
-- We would like to compile the call to foo as a local jump instead of a call
-- (see Note [Self-recursive tail calls]). However, the generated function has
-- an arity of 2 while we apply it to 3 arguments, one of them being of void
-- type. Thus, we mustn't count arguments of void type when checking whether
-- we can turn a call into a self-recursive jump.
--
Nowadays it's very clear that foo
has a post-unarise arity of 3, and we call it with 3 arguments (one of which happens to be void). And indeed if foo
tail-called itself with only two arguments (and no final void argument) it would be utterly wrong to just jump to its body. (Of course, this sort of call is only type-correct in the presence of strange recursive newtypes.)
Here's a short program that demonstrates that we can in fact generate such bogus self-jumps today:
module Main (main) where
import Data.IORef (newIORef, readIORef, writeIORef)
import Control.Exception (evaluate)
import GHC.Exts (noinline)
newtype Tricky = TrickyCon { unTrickyCon :: IO Tricky }
main :: IO ()
main = do
ref <- newIORef False
let
tricky :: Tricky
tricky = TrickyCon $ do
putStrLn "tricky call"
v <- readIORef ref
case v of
False -> writeIORef ref True >> evaluate (noinline tricky)
True -> putStrLn "this shouldn't be printed" >> pure tricky
() <$ unTrickyCon tricky
(Why does this reproducer uses the special handling of evaluate
/seq#
instead of a direct function call? ...as a work-around for other bugs. Ticket to follow...)
Compile with optimizations and run. The expected output is a single line "tricky call"; the actual output is as follows:
tricky call
tricky call
this shouldn't be printed
Environment
- GHC version used: 9.8.1