Code size and compile time regression in GHC 8.10.1
This program triggers a significant code size regression in GHC 8.10.1:
module M
( mkB2
) where
import Control.Monad.Reader
import Data.Maybe
data A1 = A1 (Maybe String) (Maybe String) (Maybe String) (Maybe String)
data A2 = A2 A1 (Maybe String) (Maybe String) (Maybe String) (Maybe String)
(Maybe String) (Maybe String) (Maybe String) (Maybe String)
data B1 = B1 !String !String !String !String
data B2 = B2 !B1 !String !String !String !String !String !String !String !String
type M a = ReaderT [(String, String)] (Either String) a
resolve :: Maybe String -> String -> M (Maybe String)
resolve (Just x) _ = pure (Just x)
resolve Nothing v = asks $ lookup v
mkB1 :: A1 -> M B1
mkB1 (A1 a b c d) = do
a' <- fromMaybe "" <$> resolve a "A"
b' <- fromMaybe "" <$> resolve b "B"
c' <- fromMaybe "" <$> resolve c "C"
d' <- fromMaybe "" <$> resolve d "D"
pure $ B1 a' b' c' d'
mkB2 :: A2 -> M B2
mkB2 (A2 a b c d e f g h i) = do
a' <- mkB1 a
b' <- fromMaybe "db" <$> resolve b "B"
c' <- fromMaybe "dc" <$> resolve c "C"
d' <- fromMaybe "dd" <$> resolve d "D"
e' <- fromMaybe "de" <$> resolve e "E"
f' <- fromMaybe "df" <$> resolve f "F"
g' <- fromMaybe "dg" <$> resolve g "G"
h' <- fromMaybe "dh" <$> resolve h "H"
i' <- fromMaybe "di" <$> resolve i "I"
pure $ B2 a' b' c' d' e' f' g' h' i'
The above program is obviously contrived, but it is reduced from a real module in our production codebase. When compiling with -O -dshow-passes
on GHC 8.8.2, compile times are quick and code size is small, as expected:
*** CoreTidy [M]:
Result size of Tidy Core
= {terms: 812, types: 510, coercions: 11, joins: 21/21}
!!! CoreTidy [M]: finished in 2.23 milliseconds, allocated 2.792 megabytes
*** CorePrep [M]:
Result size of CorePrep
= {terms: 910, types: 636, coercions: 11, joins: 21/38}
!!! CorePrep [M]: finished in 0.80 milliseconds, allocated 1.360 megabytes
*** Stg2Stg:
*** CodeGen [M]:
!!! CodeGen [M]: finished in 40.30 milliseconds, allocated 38.977 megabytes
But when compiled with the same options on GHC 8.10.1, compilation time balloons to over 50 seconds, and code size is increased by almost two orders of magnitude:
*** CoreTidy [M]:
Result size of Tidy Core
= {terms: 72,443, types: 29,596, coercions: 11, joins: 76/76}
!!! CoreTidy [M]: finished in 76.98 milliseconds, allocated 49.157 megabytes
*** CorePrep [M]:
Result size of CorePrep
= {terms: 78,293, types: 32,598, coercions: 11, joins: 76/2,969}
!!! CorePrep [M]: finished in 138.54 milliseconds, allocated 113.637 megabytes
*** Stg2Stg:
*** CodeGen [M]:
!!! CodeGen [M]: finished in 42737.61 milliseconds, allocated 25293.499 megabytes
This appears to happen because GHC is duplicating too much code when performing case-of-case simplifications, but I am not certain what the root cause is.
Draft fix in !3426 (merged)