#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 )
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
Description: | modified (diff) |
---|
comment:2 Changed 3 years ago by
Keywords: | Inlining added |
---|---|
Type of failure: | None/Unknown → Runtime performance bug |
comment:3 Changed 3 years ago by
comment:4 Changed 3 years ago by
Milestone: | → 8.2.1 |
---|---|
Resolution: | → fixed |
Status: | new → closed |
comment:5 Changed 3 years ago by
Related Tickets: | #8668 → #8668, #5835, #12791 |
---|
comment:6 Changed 3 years ago by
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
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: