[GHC] #14375: Implement with# primop
GHC
ghc-devs at haskell.org
Fri Oct 20 09:45:39 UTC 2017
#14375: Implement with# primop
-------------------------------------+-------------------------------------
Reporter: simonpj | Owner: (none)
Type: bug | Status: new
Priority: normal | Milestone:
Component: Compiler | Version: 8.2.1
Resolution: | Keywords:
Operating System: Unknown/Multiple | Architecture:
| Unknown/Multiple
Type of failure: None/Unknown | Test Case:
Blocked By: | Blocking:
Related Tickets: #14346 | Differential Rev(s): ​Phab:D4110
Wiki Page: |
-------------------------------------+-------------------------------------
Changes (by simonpj):
* related: => #14346
Comment:
In Phab:D4110 Simon M says
> The one downside of this is that we have to build a function closure to
pass to with#, which entails more allocation than we were doing
previously. But there's an alternative approach that would avoid this:
expand with# during CoreToStg (or CorePrep perhaps) into the original case
expression + touch#. There should be no extra allocation, no new primops
needed, all it does is prevent the simplifier from eliminating the
continuation.
That's a good point. We implement `runST` in this way too.
But that seems very ad hoc. I've realised that we have quite a bunch of
primops that take continuations. For example
{{{
maskAsyncExceptions# :: (State# RealWorld -> (# State# RealWorld, a #))
-> (State# RealWorld -> (# State# RealWorld, a #))
}}}
We don't really want to allocate a continuation here, only to immediately
enter it. But in fact we do!
I've also realised that it's quite easy to avoid:
* When converting to STG, instead of insisting that the argument to
`maskAsyncExceptions#` is a variable, insiste that it is a lambda `(\s.e)`
* When doing code-gen for `maskAsyncExceptions# (\s.e) s2`, emit the mask
code (as now) and continue code-gen for `e`.
That would mean altering STG a bit to allow non-variable arguments.
An alternative would be to convert `maskAsyncExceptions# e s` to this STG:
{{{
join j s = e
in maskAsyncExceptions# j s
}}}
This isn't quite as good because it flushes the live variables of `e` to
the stack, and then takes a jump to it (the latter will be elminated in
Cmm land); but it's much better than what we do now. NB: this would not
be valid Core because `j` is not saturated; but here it's just an
intermediate step in codegen.
Moreover, we don't want to make it ''too'' much like join points; in
particular not in the simplifier. For example
{{{
case (maskAsyncExceptions# (\s. e) s2) of
(# s3, r #) -> blah
----> ????
maskAsyncExceptions (\s. case e of (# s3, r #) -> blah) s2
}}}
Probably not! Because that would broaden the scope of the mask. But it's
fine to treat it in a very join-point-like way at codegen time.
We can apply similar thinking to `catch#`.
{{{
catch# :: (State# RealWorld -> (# State# RealWorld, a #) )
-> (b -> State# RealWorld -> (# State# RealWorld, a #) )
-> State# RealWorld
-> (# State# RealWorld, a #)
}}}
Here we allocate two continuations. But we'd really prefer to allocate
none! Just push a catch frame (which we do anyway).
Perhaps we can generate this STG:
{{{
join jnormal s = e1 s
in join jexception b s = e2 b s
in catch# jnormal jexception s
}}}
Again we compile those join point just as we normally do (live variables
on the stack),
so that invoking one is just "adjust SP and jump". Again this would not
be valid Core,
just a codegen intermediate.
I like this.
Conclusion: let's not do any special codegen stuff for `with#` until we've
worked this out.
--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/14375#comment:1>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler
More information about the ghc-tickets
mailing list