[GHC] #12463: SPECIALIZABLE pragma?

GHC ghc-devs at haskell.org
Sat Sep 24 20:25:47 UTC 2016


#12463: SPECIALIZABLE pragma?
-------------------------------------+-------------------------------------
        Reporter:  bgamari           |                Owner:
            Type:  feature request   |               Status:  new
        Priority:  low               |            Milestone:
       Component:  Compiler          |              Version:  8.0.1
      Resolution:                    |             Keywords:  Inlining
Operating System:  Unknown/Multiple  |         Architecture:
                                     |  Unknown/Multiple
 Type of failure:  None/Unknown      |            Test Case:
      Blocked By:                    |             Blocking:
 Related Tickets:                    |  Differential Rev(s):
       Wiki Page:                    |
-------------------------------------+-------------------------------------

@@ -27,7 +27,7 @@
- unfolding for `aLibraryFunction` due to its size. If we include an
- `INLINEABLE` pragma (as most performance-aware authors would do) then we
- can convince GHC to produce an unfolding, but only at the expense of
- lowering its inlining cost as well. This is unfortunate since we never
- wanted GHC to inline; merely to specialize. This is issue especially
- prevalent in code using MTL-style effects, where we have ubiquitous
- overloading of very frequently-used functions (e.g. bind).
+ unfolding for `aLibraryFunction` due to its size. We can only convince GHC
+ to produce an unfolding for `aLibraryFunction` if we annotate it with an
+ `INLINEABLE` pragma. While this is often effective, it doesn't really say
+ what we mean: We don't never want GHC to inline; merely to specialize.
+ This is issue especially prevalent in code using MTL-style effects, where
+ we have ubiquitous overloading of very frequently-used functions (e.g.
+ bind).
@@ -47,1 +47,1 @@
- This would request that GHC would keep an inlining around and produce a
+ This pragma requests that GHC keep an inlining around and produce a
@@ -50,1 +50,1 @@
- weak, allowing the linker to cull duplication code when possible.
+ weak, allowing the linker to cull duplicated code when possible.
@@ -52,1 +52,34 @@
- Moreover, a variant of this might be,
+ # Transitive specialisation
+
+ The above `SPECIALISE` pragma still doesn't address the fragility of
+ specialisation, however. Namely, consider,
+ {{{#!hs
+ module ALibrary where
+ class AClass
+ instance AClass Int
+ aLibraryFunction :: AClass a => a -> a
+ aLibraryFunction = {- some large expression involving methods of AClass -}
+ {-# SPECIALISE(a) forall a. aLibraryFunction :: a -> a #-}
+
+ module AnotherLibrary where
+ import ALibrary
+ aFunction :: AClass a => a -> a
+ aFunction x = {- ... -} aLibraryFunction x {- ... -}
+
+ module AUser where
+ import AnotherLibrary
+ f = let x :: Int
+         x = 5
+     in aFunction x
+ }}}
+ Here `aLibraryFunction` may depend crucially on specialisation; however,
+ the polymorphic user `aFunction` has no way of knowing this and may be too
+ large for GHC to produce an unfolding automatically. This ultimately means
+ that GHC will be unable to specialise the eventual instantiation at `Int`
+ in `AUser.f`. This will mean that the performance characteristics of
+ `ALibrary` will be rather fragile.
+
+ One (admittedly rather heavy) approach to solving this fragility is to
+ inform GHC that `aLibraryFunction`'s polymorphic callsites should have
+ unfoldings, ensuring that we are able to specialise the eventual
+ monomorphic callsite,
@@ -60,1 +93,1 @@
- need to know about a library's expectations of the simplifier.
+ need to know about `aLibrarFunction`'s expectations of the simplifier.

New description:

 Currently it is common practice for library authors to use the
 `INLINEABLE` pragma to make it more likely that a polymorphic function
 should get an unfolding in the module's interface file to ensure that GHC
 is able to specialize. While in practice this works reasonably well, it's
 not really saying what we often mean: we don't want to inline, we really
 just want GHC to behave like each use-site's module has a `SPECIALISE`
 pragma for each concrete type that the function is used at. For instance,
 consider,

 {{{#!hs
 module ALibrary where

 aLibraryFunction :: AClass a => a -> a
 aLibraryFunction = {- some large expression involving methods of AClass -}


 module SomeUser where

 import ALibrary

 aUser :: Int -> Int
 aUser = {- some large expression involving aLibraryFunction -}
 }}}
 Ideally, we would want GHC to take and produce one specialized version of
 `aLibraryFunction` for every concrete type which it is used at. However,
 without an `INLINEABLE` function, GHC won't even consider producing an
 unfolding for `aLibraryFunction` due to its size. We can only convince GHC
 to produce an unfolding for `aLibraryFunction` if we annotate it with an
 `INLINEABLE` pragma. While this is often effective, it doesn't really say
 what we mean: We don't never want GHC to inline; merely to specialize.
 This is issue especially prevalent in code using MTL-style effects, where
 we have ubiquitous overloading of very frequently-used functions (e.g.
 bind).

 Really what we want in this case is a way of indicating to GHC that a
 function shouldn't be inlined (use-sites replaced with the body of the
 function), but rather that GHC should try hard to specialize away
 particular type variables. This might look like,
 {{{#!hs
 aLibraryFunction :: AClass a => a -> a
 aLibraryFunction = {- some large expression involving methods of AClass -}
 {-# SPECIALISE(a) forall a. aLibraryFunction :: a -> a #-}
 }}}
 The list of type binders after `SPECIALISE` is the set of binders which
 GHC would attempt to specialize.

 This pragma requests that GHC keep an inlining around and produce a
 specialized version of `aLibraryFunction` every time it saw a concrete
 instantiation of `a`. Moreover, the produced symbols could be declared as
 weak, allowing the linker to cull duplicated code when possible.

 # Transitive specialisation

 The above `SPECIALISE` pragma still doesn't address the fragility of
 specialisation, however. Namely, consider,
 {{{#!hs
 module ALibrary where
 class AClass
 instance AClass Int
 aLibraryFunction :: AClass a => a -> a
 aLibraryFunction = {- some large expression involving methods of AClass -}
 {-# SPECIALISE(a) forall a. aLibraryFunction :: a -> a #-}

 module AnotherLibrary where
 import ALibrary
 aFunction :: AClass a => a -> a
 aFunction x = {- ... -} aLibraryFunction x {- ... -}

 module AUser where
 import AnotherLibrary
 f = let x :: Int
         x = 5
     in aFunction x
 }}}
 Here `aLibraryFunction` may depend crucially on specialisation; however,
 the polymorphic user `aFunction` has no way of knowing this and may be too
 large for GHC to produce an unfolding automatically. This ultimately means
 that GHC will be unable to specialise the eventual instantiation at `Int`
 in `AUser.f`. This will mean that the performance characteristics of
 `ALibrary` will be rather fragile.

 One (admittedly rather heavy) approach to solving this fragility is to
 inform GHC that `aLibraryFunction`'s polymorphic callsites should have
 unfoldings, ensuring that we are able to specialise the eventual
 monomorphic callsite,
 {{{#!hs
 aLibraryFunction :: AClass a => a -> a
 aLibraryFunction = {- some large expression involving methods of AClass -}
 {-# SPECIALISE_RECURSIVE(a) forall a. aLibraryFunction :: a -> a #-}
 }}}
 Which would ensure that polymorphic use-sites of `aLibraryFunction` would
 themselves be marked as `SPECIALISE_RECURSIVE`, shielding users from the
 need to know about `aLibrarFunction`'s expectations of the simplifier.

--

Comment (by bgamari):

 > What is "the recursive variant"?

 The variant of the `RECURSIVE_SPECIALISABLE` pragma that I describe in the
 ticket summary. A better name might be *transitive specialisable*. See
 #8774 for another description of the motivation for this idea.

 > That is not true. INLINABLE does not reduce the inlining cost. It merely
 (and solely) arranges to capture the entire (Core of the) source-code
 defnition, including if the function is recursive. No more and no less.

 Indeed, this was an inaccurate statement and I've removed it.

 > > We really just want GHC to behave like each use-site's module has a
 SPECIALISE pragma for each concrete type that the function is used at.
 > And that is exactly what INLINABLE does.

 Is that true? My understanding is that `INLINEABLE` will merely tell GHC
 to produce an unfolding; it won't ensure that GHC will use that unfolding
 to specialise use-sites. This is the goal here; we want to /ensure/ that
 GHC will specialise if at all possible.

 In the case of the `RECURSIVE_SPECIALISABLE` pragma this even means
 ensuring that polymorphic use-sites are also marked as `INLINEABLE`, to
 ensure that GHC can specialise the final concrete instantiation.

--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/12463#comment:6>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler


More information about the ghc-tickets mailing list