[GHC] #855: Improvements to SpecConstr
GHC
trac at galois.com
Mon Nov 12 10:24:15 EST 2007
#855: Improvements to SpecConstr
----------------------+-----------------------------------------------------
Reporter: simonpj | Owner:
Type: task | Status: new
Priority: normal | Milestone: _|_
Component: Compiler | Version: 6.4.2
Severity: normal | Resolution:
Keywords: | Difficulty: Unknown
Testcase: N/A | Architecture: Unknown
Os: Unknown |
----------------------+-----------------------------------------------------
Changes (by simonpj):
* milestone: 6.8 branch => _|_
Comment:
I'll look at this again when someone finds that `SpecConstr` really isn't
doing the Right Thing for them. My guess is that this will happen either
in array fusion for nested data parallelism, or in the use of stream
fusion for lists (Coutts et al ICFP'07).
Meanwhile, here is the last message I had from Roman on the subject, which
summarises what he did to make `SpecConstr` work reasonably well for their
ICFP paper.
=== Specialising on constants ===
Consider the following program (admittedly contrived, but we get this kind
of code with stream fusion):
{{{
lvl = Just True
foo :: Maybe Bool -> Int -> Int
foo (Just True) i = i
foo _ i = foo lvl i
}}}
`SpecConstr` doesn't optimise this even though it is supposed to
specialise on global variables. If I understand correctly, the reason for
this is the following test in `argToPat`:
{{{
-- Check if the argument is a variable that
-- is in scope at the function definition site
-- It's worth specialising on this if
-- (a) it's used in an interesting way in the body
-- (b) we know what its value is
argToPat in_scope con_env (Var v) arg_occ
| not (isLocalId v) || v `elemInScopeSet` in_scope,
case arg_occ of { UnkOcc -> False; other -> True }, -- (a)
isValueUnfolding (idUnfolding v) -- (b)
= return (True, Var v)
}}}
The problem with this is that v might have been cloned (via extendBndr*)
and in that case, it won't have an unfolding. That is precisely what
happens in the above example, I believe.
I solved this by explicitly recording which binders have a known value
but you might have a better idea.
=== The threshold ===
I don't like the spec-threshold idea at all. In fact, it was the first
thing I had to disable for the ICFP paper since it just doesn't make sense
for the kind of code stream fusion generates. It can produce some very
large loops which *must* be specialised; indeed, `SpecConstr` will usually
decrease the program size.
=== Specialising on lambdas ===
Specialising on lambdas is absolutely crucial for stream fusion (for
nested lists). I completely agree that the direct approach was too
fragile. What I did was a rather bad hack but it worked.
- I extended `FloatOut` (or, rather, `SetLevels`) such that it would
float
out lambdas in recursive arguments, i.e., given
{{{
foo x = ... foo (C (\x. e)) ...
}}}
it would produce
{{{
f a1 ... an x = e
foo x = ... foo (C (f a1 ... an)) ...
}}}
where a1,...,an are the free variables of e.
- This floating would happen before every `SpecConstr` pass (see
below).
- I extended `SpecConstr` (very hackishly) to specialise on partial
applications of global variables. For the above, it would produce
{{{
foo' a1 ... an = ...
}}}
and the rewrite rule
{{{
forall a1 ... an. foo (C (f a1 ... an)) = foo' a1 ... an
}}}
Now, f is used directly in foo'. We have a bad staging problem here,
however: we definitely want to inline f in foo' (i.e., it should get
an INLINE pragma) but not in foo (at least, not before the rule has
fired). I solved this by a terrible hack involving core notes but
clearly, something better is needed here. Note that we might
generate several specialisations which use the same f and we want to
inline the latter in every specialisation.
- Finally, inlining f in foo' and simplifying might expose further
opportunities for `SpecConstr`. For the ICFP paper, I implemented
`FloatOut/SpecConstr/Simplify` loop. Stream fusion with nested lists
requires as many iterations as there are nesting levels (each
iteration would peel away one nesting level). Note that the current
specLoop doesn't help here because we really have to do full
simplification (including inlining).
That was basically all I did. We didn't really use the static argument
transformation because specialising on global variables essentially has
the same effect.
--
Ticket URL: <http://hackage.haskell.org/trac/ghc/ticket/855#comment:2>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler
More information about the Glasgow-haskell-bugs
mailing list