Skip to content

GHC fails to specialize higher-rank arguments even if dictionaries are monomorphic

Consider the following program:

{-# LANGUAGE AllowAmbiguousTypes, RankNTypes, ScopedTypeVariables, TypeApplications #-}

showBang :: Show a => a -> String
showBang x = show x ++ "!"

class C a where
  f :: (Show a => r) -> r
instance C () where
  f x = x

g :: forall a. C a => a -> String
g x = f @a (showBang x)
{-# INLINABLE[0] g #-}

s :: String
s = g ()

When the specializer looks at this program, it will discover the call g (), and it will specialize g @(), as it should. However, it will not specialize showBang @(), even though showBang is used completely monomorphically in the body of the specialized version of g! Why does this happen? Well, after desugaring, g becomes something like

g = \@a $dC_a x ->
  f @a $dC_a @String (\$dShow_a -> showBang @a $dShow_a x)

so when the specializer goes to look at the call site of showBang, it simply sees showBang @() $dShow_a x. Since $dShow_a is lambda-bound, GHC considers it to be “uninteresting,” and it declines to specialize showBang. However, this is a bad decision, as this use of showBang is trivially monomorphic!

This is really biting me, as I am using a construction like the above in a scenario where specializing the monomorphic call site opens up an enormous number of additional specialization opportunities, and I am relying on GHC to perform that specialization to get reasonable performance.

Environment

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