Growing stack size w/ inadvertent loop statement - expected behavior?

89 views Asked by At

First the Question - is the behavior below expected logically, or a bug to be reported for GHC?

The code below will leak memory (tested on ghc-8.8.4) because ghc seems to add join point and jumps to it at the end of the loop, building up the stack.

{-# OPTIONS_GHC -fno-full-laziness #-}

module Main where

import Control.Concurrent.Async (async,waitCatch)
import Data.IORef
import GHC.Conc

main :: IO ()
main = do
  val <- newIORef 0 :: IO (IORef Int)
  let loop1 = do
          cval <- readIORef val
          threadDelay 1
          writeIORef val (cval + 1)
          case cval > 100000000 of
            True -> error "done"
            False -> loop1
          loop1 -- Deliberately, add this to cause space leak, but this statement is never executed because of case branching above

  loop1Async <- async loop1
  res <- waitCatch loop1Async

  return ()

Compiling with -O2 -rtsopts -threaded and running with +RTS -s -hT -N will show space leak because of growing stack.

Looking at core output, it seems the leak is due to join (I guess it is a join point) and a jump to it at the end of the loop which grows the stack (if I have read the core correctly). Removing the last statement in loop1 fixes the leak.

ghc core output is here.

Update: Based on feedback in comments, it seems to be logical behavior, not a bug in ghc. So, an answer explaining stack increase would be good to have. This helps us understand what is going on here. ghc core output has been posted above.

1

There are 1 answers

4
Carl On

I would certainly expect that line to cause the use of stack space. That extra call to loop1 is hardly irrelevant.

Change the test constant to 10 instead of a larger number, and replace the return () branch with print cval.

Compare the output you get with and without the "never executed" statement. You might find it's somewhat more executed than you think.