Skip to content

fail doesn't inline, leading to missed optimizations

Summary

The function fail doesn't inline. GHC's passes over GHC core are able to detect that expressions following raiseIO# are unreachable. In a more complicated setting (not elaborated on here), I've observed that this missed opportunity to eliminate unreachable code after raiseIO# leads to missed opportunities for other optimizations like worker-wrapper and case-of-known-data-constructor.

Steps to reproduce

With GHC 8.6.4, compile this with ghc-8.6.4 -O2 -fforce-recomp -ddump-simpl -dsuppress-all fail_inline.hs:

{-# language MagicHash #-}

module FailInline
  ( alpha
  , beta
  ) where

import GHC.Exts
import GHC.IO
import Control.Exception

alpha :: IO Int
{-# NOINLINE alpha #-}
alpha = do
  x <- fail "foobar"
  pure (x + 1)

beta :: IO Int
{-# NOINLINE beta #-}
beta = do
  x <- IO (raiseIO# (toException (userError "fizzbuzz")))
  pure (x + 1)

The NOINLINE pragmas are not necessary to trigger this problem, but they make the resulting GHC core more readable. Here are the relevant sections of GHC core that is dumped:

-- RHS size: {terms: 4, types: 4, coercions: 0, joins: 0/0}
beta1
beta1 = \ s_a2ld -> raiseIO# lvl3_r2KW s_a2ld

...

-- RHS size: {terms: 16, types: 29, coercions: 10, joins: 0/0}
alpha1
alpha1
  = \ s_X2lp ->
      case ((noinline (failIO1 `cast` <Co:8>) alpha2) `cast` <Co:2>)
             s_X2lp
      of
      { (# ipv_a2lg, ipv1_a2lh #) ->
      (# ipv_a2lg,
         case ipv1_a2lh of { I# x_a2JK -> I# (+# x_a2JK 1#) } #)
      }

Expected behavior

I expected that alpha and beta would be optimized to the same thing (modulo the different error message).

Environment

  • GHC version used: 8.6.4
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information