Opened 3 years ago

Closed 3 years ago

Last modified 2 years ago

#13376 closed bug (fixed)

GHC fails to specialize a pair of polymorphic INLINABLE functions

Reported by: jberryman Owned by:
Priority: normal Milestone: 8.2.1
Component: Compiler Version: 8.0.1
Keywords: Inlining Cc:
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: Runtime performance bug Test Case:
Blocked By: Blocking:
Related Tickets: #8668, #5835, #12791 Differential Rev(s):
Wiki Page:

Description (last modified by jberryman)

This is a boiled down version of a library I'm working on. It's possible this is the same issue as #8668 which seems to have stalled. Hopefully this example is simpler and useful in that case. Also likely the same as this https://github.com/jmoy/testing-specialize

I have a library which defines the classes H and S; library consumers are likely to define their own H instances, and import S instances declared by other library authors (not me), who will depend on my H.

Performance depends on all of it getting fully-specialized hash (i.e. for each combination of H and S that the consumer uses). But I don't really want hash inlined at every call site.

Here is the code to repro with explanation below. I'm compiling like: ghc --make -Wall -O2 -rtsopts -funbox-strict-fields -ddump-to-file -ddump-simpl -dsuppress-module-prefixes -dsuppress-uniques -ddump-core-stats -ddump-inlinings -ddump-asm -fforce-recomp Main.hs, and we get the same bad behavior on GHC 7.10.3 and GHC 8.0.1:

Lib.hs:

module Lib where

class H h where
  hash :: (S s)=> s -> h -> s

class S s where
  mix :: s -> Int -> s

instance H Int where
  {-# INLINABLE hash #-}
  hash s = \x ->
    s `mix` x 
      -- make this look big:
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x
      `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x `mix` x

S.hs:

module S where

import Lib

newtype Foo = Foo Int
    deriving Show

instance S Foo where
  {-# INLINABLE mix #-}
  mix (Foo x) y = Foo (x+y)

And the Main I'm using, though you can just call print; it's obvious dumping inlinings when the functions get specialized and unboxed (look for "Inlining done: $fNumInt_$c+"):

module Main where

import Lib
import S

import Criterion.Main


main = defaultMain [
    bench "foo" $ whnf (hash (Foo 1)) (1::Int)
  ]

If I use the INLINABLE pragmas above or omit them entirely we get bad code.

If I put an INLINE on the hash declaration in Lib (and no pragmas in S), we get good unboxed additions and things are fast.

Finally and most bizarrely, if I omit the INLINE pragma in hash (and similarly no pragmas in S) but make the body small enough (5 lines of the "mix x mix x..." junk) then we also get nice unboxed code.

EDIT: Also if I move the S constraint into the head of H then INLINABLE and stuff seem to work as expected:

-- lousy workaround; we can tell users to just not touch the `s` 
-- parameter in their own instances:
class (S s)=> H s h where
  hash :: s -> h -> s

Change History (6)

comment:1 Changed 3 years ago by jberryman

Description: modified (diff)

comment:2 Changed 3 years ago by jberryman

Keywords: Inlining added
Type of failure: None/UnknownRuntime performance bug

comment:3 Changed 3 years ago by jberryman

Also adding (per "SPECIALIZE for imported functions" from the user guide) in Main the following:

{-# SPECIALIZE hash :: Foo -> Int -> Foo #-}

...results in a warning I don't really understand:

Ignoring useless SPECIALISE pragma for class method selector ‘hash’

comment:4 Changed 3 years ago by dfeuer

Milestone: 8.2.1
Resolution: fixed
Status: newclosed

This appears to be fixed in master. See #5835 and #12791.

comment:5 Changed 3 years ago by dfeuer

comment:6 Changed 2 years ago by jberryman

Just a note that I verified the example I posted here is fixed in 8.2, as well as jmoy's example I linked to in the first paragraph of my report. However for the latter I encountered what I thought was a weird issue which I don't have time to look into further right now: https://ghc.haskell.org/trac/ghc/ticket/13873

Note: See TracTickets for help on using tickets.