
The one downside of this is that we have to build a function closure to
#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 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