[GHC] #14152: Float exit paths out of recursive functions
GHC
ghc-devs at haskell.org
Fri Oct 20 15:17:43 UTC 2017
#14152: Float exit paths out of recursive functions
-------------------------------------+-------------------------------------
Reporter: nomeata | Owner: (none)
Type: task | Status: patch
Priority: normal | Milestone:
Component: Compiler | Version: 8.2.1
Resolution: | Keywords: JoinPoints
Operating System: Unknown/Multiple | Architecture:
| Unknown/Multiple
Type of failure: None/Unknown | Test Case:
Blocked By: | Blocking:
Related Tickets: #14137 #10918 | Differential Rev(s):
Wiki Page: |
-------------------------------------+-------------------------------------
Description changed by nomeata:
Old description:
> This is a spin off of a discussion from #14137.
>
> == The problem ==
>
> We generally avoid inlining or floating a binding into a recursive
> function, because we do not want to duplicat work/allocations.
>
> But sometimes the binding is only used inside the recursive function on
> the “exit path”. In which case it would be good to inline. Example:
>
> {{{#!hs
> let x = f x0
> in let go 10 = h x
> go i = go (i+1)
> in go (0::Int) + 100
> }}}
>
> It would be beneficial to inline `x`.
>
> The problem is that it is not very obvious that this occurence of `x` is
> ok for inlining. In particular, it is not syntactically visible.
>
> == Proposed solution ==
>
> If we apply loopification (#14068), this would turn into
>
> {{{#!hs
> let x = f x0
> in let go n =
> joinrec jgo 10 = h x
> jgo i = call jgo (i+1)
> in call jgo n
> in go (0::Int) + 100
> }}}
>
> I’d like to call this ''first joinrec normal form'', defined as “every
> recursive function where all recursive calls are tail-recursive is a
> recursive join point”.
>
> This ticket proposes to transform this even further and ''float out all
> case alternatives that do not mention `jgo` as non-recursive join-
> points'', as so:
>
> {{{#!hs
> let x = f x0
> in let go n =
> join jexit = h x
> joinrec jgo 10 = call jexit
> jgo i = call jgo (i+1)
> in call jgo n
> in go (0::Int) + 100
> }}}
>
> I’d like to call this ''second `joinrec` normal form'', defined as “in
> first `joinrec` normal form, and all subexpressions of a recursive join
> point `j` that are in tail-call position and do not mention `j` are join
> calls”.
>
> If the floated expression has free variables that are bound inside the
> `joinrec`, they turn into parameters of the newly created joinpoint.
>
> At this point, GHC can tell that `go` is called at most once, and will
> therefore happily inline `x` into the right hand side of `jexit.
>
> == Alternative solutions ==
>
> Ticket #10918 uses Call Arity results to learn that `x` is one-Shot, and
> inline it even in the original code. This works, but the problem is that
> float-out will undo this. See [ticket:10918#comment:5].
>
> == Limitation ==
>
> It only works for recursive functions that are join points, or can be
> turned into join points by loopification (#14068). It does not work
> forexample for
>
> {{{#!hs
> let x = f x0
> let go 0 = h x
> go n = (go (n-1) + 1
> in go 10
> }}}
>
> although it would be equally desirable to float `h x` out of `go` so
> that `x` can be inlined.
>
> == Preservation ==
>
> A remaining tricky point is that we need to stop one of these carefully-
> constructed non-recursive join points being inlined into a recursive join
> point, even if it is invoked at just one place. That should not be hard.
> And in a final run of the simplifer (or in CorePrep) we could switch off
> that restriction and let it inline. (Ticket #14137 is about inlining
> ''more'' join points into recursive join points, so it is the antithesis
> to the present ticket.)
New description:
This is a spin off of a discussion from #14137.
== The problem ==
We generally avoid inlining or floating a binding into a recursive
function, because we do not want to duplicat work/allocations.
But sometimes the binding is only used inside the recursive function on
the “exit path”. In which case it would be good to inline. Example:
{{{#!hs
let x = f x0
in let go 10 = h x
go i = go (i+1)
in go (0::Int) + 100
}}}
It would be beneficial to inline `x`.
The problem is that it is not very obvious that this occurence of `x` is
ok for inlining. In particular, it is not syntactically visible.
== Proposed solution ==
If we apply loopification (#14068), this would turn into
{{{#!hs
let x = f x0
in let go n =
joinrec jgo 10 = h x
jgo i = call jgo (i+1)
in jump jgo n
in go (0::Int) + 100
}}}
I’d like to call this ''first joinrec normal form'', defined as “every
recursive function where all recursive calls are tail-recursive is a
recursive join point”.
This ticket proposes to transform this even further and ''float out all
case alternatives that do not mention `jgo` as non-recursive join-
points'', as so:
{{{#!hs
let x = f x0
in let go n =
join jexit = h x
joinrec jgo 10 = call jexit
jgo i = call jgo (i+1)
in jump jgo n
in go (0::Int) + 100
}}}
I’d like to call this ''second `joinrec` normal form'', defined as “in
first `joinrec` normal form, and all subexpressions of a recursive join
point `j` that are in tail-call position and do not mention `j` are join
calls”.
If the floated expression has free variables that are bound inside the
`joinrec`, they turn into parameters of the newly created joinpoint.
At this point, GHC can tell that `go` is called at most once, and will
therefore happily inline `x` into the right hand side of `jexit.
== Alternative solutions ==
Ticket #10918 uses Call Arity results to learn that `x` is one-Shot, and
inline it even in the original code. This works, but the problem is that
float-out will undo this. See [ticket:10918#comment:5].
== Limitation ==
It only works for recursive functions that are join points, or can be
turned into join points by loopification (#14068). It does not work
forexample for
{{{#!hs
let x = f x0
let go 0 = h x
go n = (go (n-1) + 1
in go 10
}}}
although it would be equally desirable to float `h x` out of `go` so that
`x` can be inlined.
== Preservation ==
A remaining tricky point is that we need to stop one of these carefully-
constructed non-recursive join points being inlined into a recursive join
point, even if it is invoked at just one place. That should not be hard.
And in a final run of the simplifer (or in CorePrep) we could switch off
that restriction and let it inline. (Ticket #14137 is about inlining
''more'' join points into recursive join points, so it is the antithesis
to the present ticket.)
--
--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/14152#comment:25>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler
More information about the ghc-tickets
mailing list