diff options
author | Alexis King <lexi.lambda@gmail.com> | 2020-04-10 01:26:16 -0500 |
---|---|---|
committer | Marge Bot <ben+marge-bot@smart-cactus.org> | 2020-04-12 11:23:27 -0400 |
commit | e8029816fda7602a8163c4d2703ff02982a3e48c (patch) | |
tree | 090075650e5d4440431678912a9f6f82c48d493f /libraries/base/Control | |
parent | 35799dda07813e4c510237290a631d4d11fb92d2 (diff) | |
download | haskell-e8029816fda7602a8163c4d2703ff02982a3e48c.tar.gz |
Add an INLINE pragma to Control.Category.>>>
This fixes #18013 by adding INLINE pragmas to both Control.Category.>>>
and GHC.Desugar.>>>. The functional change in this patch is tiny (just
two lines of pragmas!), but an accompanying Note explains in gory
detail what’s going on.
Diffstat (limited to 'libraries/base/Control')
-rw-r--r-- | libraries/base/Control/Category.hs | 64 |
1 files changed, 64 insertions, 0 deletions
diff --git a/libraries/base/Control/Category.hs b/libraries/base/Control/Category.hs index 96f0c33aed..c033c7618e 100644 --- a/libraries/base/Control/Category.hs +++ b/libraries/base/Control/Category.hs @@ -77,3 +77,67 @@ instance Category Coercion where -- | Left-to-right composition (>>>) :: Category cat => cat a b -> cat b c -> cat a c f >>> g = g . f +{-# INLINE (>>>) #-} -- see Note [INLINE on >>>] + +{- Note [INLINE on >>>] +~~~~~~~~~~~~~~~~~~~~~~~ +It’s crucial that we include an INLINE pragma on >>>, which may be +surprising. After all, its unfolding is tiny, so GHC will be extremely +keen to inline it even without the pragma. Indeed, it is actually +/too/ keen: unintuitively, the pragma is needed to rein in inlining, +not to encourage it. + +How is that possible? The difference lies entirely in whether GHC will +inline unsaturated calls. With no pragma at all, we get the following +unfolding guidance: + ALWAYS_IF(arity=3,unsat_ok=True,boring_ok=True) +But with the pragma, we restrict inlining to saturated calls: + ALWAYS_IF(arity=3,unsat_ok=False,boring_ok=True) +Why does this matter? Because the programmer may have put an INLINE +pragma on (.): + + instance Functor f => Category (Blah f) where + id = ... + Blah f . Blah g = buildBlah (\x -> ...) + {-# INLINE (.) #-} + +The intent here is to inline (.) at all saturated call sites. Perhaps +there is a RULE on buildBlah the programmer wants to fire, or maybe +they just expect the inlining to expose further simplifications. +Either way, code that uses >>> should not defeat this inlining, but if +we inline unsaturated calls, it might! Consider: + + let comp = (>>>) ($fCategoryBlah $dFunctor) in f `comp` (g `comp` h) + +While simplifying this expression, we’ll start with the RHS of comp. +Without the INLINE pragma on >>>, we’ll inline it immediately, even +though it isn’t saturated: + + let comp = \f g -> $fCategoryBlah_$c. $dFunctor g f + in f `comp` (g `comp` h) + +Now `$fCategoryBlah_$c. $dFunctor g f` /is/ a fully-saturated call, so +it will get inlined immediately, too: + + let comp = \(Blah g) (Blah f) -> buildBlah (\x -> ...) + in f `comp` (g `comp` h) + +All okay so far. But if the RHS of (.) is large, comp won’t be inlined +at its use sites, and any RULEs on `buildBlah` will never fire. Bad! + +What happens differently with the INLINE pragma on >>>? Well, we won’t +inline >>> immediately, since it isn’t saturated, which means comp’s +unfolding will be tiny. GHC will inline it at both use sites: + + (>>>) ($fCategoryBlah $dFunctor) f + ((>>>) ($fCategoryBlah $dFunctor) g h) + +And now the calls to >>> are saturated, so they’ll be inlined, +followed by (.), and any RULEs can fire as desired. Problem solved. + +This situation might seem academic --- who would ever write a +definition like comp? Probably nobody, but GHC generates such +definitions when desugaring proc notation, which causes real problems +(see #18013). That could be fixed by changing the proc desugaring, but +fixing it this way is the Right Thing, it might benefit other programs +in more subtle ways too, and it’s easier to boot. -} |