Skip to content

Specialization failure for newtype containing class constraints in RHS.

Summary

For this sort of program:

newtype Foo a = Foo { unFoo :: (Num a, Integral a) => a -> Int }

foo :: Foo a
foo = Foo (\x -> fromIntegral x)

{-# SPECIALIZE foo :: Foo Int #-}

g x = (unFoo foo) x :: Int

GHC fails it's users in multiple ways:

  • It warns about a specialization pragma for a non-overloaded function.
  • It still generates a specialization rule for foo.
  • The generated specializations still uses overloaded functions.

Steps to reproduce

Compile the above program with -O

Expected behavior

We get a warning, that I wouldn't expect to get:

test.hs:27:1: warning: [GHC-35827]
    SPECIALISE pragma for non-overloaded function `foo'
   |
27 | {-# SPECIALIZE foo :: Foo Int #-}
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This presumable happens because the warning doesn't look through the newtype and therefore the dictionary argument is hidden from whatever check we do.


After getting the warning I was expecting the specialization to just fail. However we do generate a rule:

------ Local rules for imported ids --------
"USPEC foo @Int"
    forall.
      foo @Int
      = M.foo1
        `cast` (Sym (M.N:Foo[0] <Int>_N)
                :: ((Num Int, Integral Int) => Int -> Int) ~R# Foo Int)

It's a pleasant surprise. But I would have expected GHC to not generate any specialization rule given the warning it emits.


Last but not least, the specialization we generate is fairly useless:

-- RHS size: {terms: 11, types: 7, coercions: 0, joins: 0/0}
M.foo1 :: (Num Int, Integral Int) => Int -> Int
[GblId,
 Arity=3,
 Str=<A><1P(A,A,A,A,A,A,A,A,1C(1,L))><L>,
 Cpr=1,
 Unf=Unf{Src=StableSystem, TopLvl=True,
         Value=True, ConLike=True, WorkFree=True, Expandable=True,
         Guidance=ALWAYS_IF(arity=3,unsat_ok=True,boring_ok=False)
         Tmpl= \ _ [Occ=Dead]
                 ($dIntegral_aFq [Occ=Once1] :: Integral Int)
                 (x_aEi [Occ=Once1] :: Int) ->
                 case GHC.Num.Integer.integerToInt#
                        (toInteger @Int $dIntegral_aFq x_aEi)
                 of ds_aXI [Occ=Once1]
                 { __DEFAULT ->
                 GHC.Types.I# ds_aXI
                 }}]
M.foo1
  = \ _ [Occ=Dead] ($dIntegral_aFq :: Integral Int) (x_aEi :: Int) ->
      case GHC.Num.Integer.integerToInt#
             (toInteger @Int $dIntegral_aFq x_aEi)
      of ds_aXI
      { __DEFAULT ->
      GHC.Types.I# ds_aXI
      }

Note that it still takes two dictionary arguments, and calls their class methods. Instead of using the presumably known Integral Int dictionary which would allow it to optimize well.

Environment

  • GHC version used: A recentish version of the master branch as well as 9.4.7
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information