> On Jan 30, 2020, at 02:35, Simon Marlow <[email protected]> wrote:
>
> My guess is you can almost do what you want with asynchronous exceptions but
> some changes to the RTS would be needed.
>
> There's a bit of code in the IO library that literally looks like this
> (https://gitlab.haskell.org/ghc/ghc/blob/master/libraries%2Fbase%2FGHC%2FIO%2FHandle%2FInternals.hs#L175)
Thanks for the pointer, I definitely had not discovered that! That is an
interesting trick. I think your explanation paired with the Note is enough for
it to make sense to me, though I don’t yet understand all the implementation
subtleties of raiseAsync itself.
> Also you might want to optimise the implementation so that it doesn't
> actually tear down the stack as it copies it into the heap, so that you could
> avoid the need to copy it back from the heap again in shift#.
I don’t fully understand this point — do you mean “avoid the need to copy it
back on continuation application”? shift# just copies the stack slice to the
heap, so it doesn’t need to copy it back.
If I was right, and you were referring to continuation application rather than
shift#, I agree with you there. It seems as though it ought to be possible to
represent the stack slice as a stack itself, so if the stack looks like
┌───────────┐
│ RET_SMALL │
├───────────┤
│ CATCH │
├───────────┤
│ RESET │
├───────────┤
then the captured continuation could itself essentially be a stack like
┌───────────┐
│ RET_SMALL │
├───────────┤
│ CATCH │
├───────────┤
│ UNDERFLOW │
└───────────┘
where restoring a continuation just copies the captured stack and updates its
underflow frame to point at the top of the current stack. If the caller
promises not to use the continuation again after applying it, the copying could
be skipped entirely, and the captured stack could just become the new stack.
However, I don’t understand enough about the way the RTS currently works to
know if this is viable. For example, what if I write this:
reset (mask_ (shift f))
Now presumably I want to ensure the masking state is restored when I invoke the
continuation. But it can’t just be unconditionally restored, since if I write
mask_ (reset (shift f >>= g))
then the mask frame isn’t included on the stack, so applying the continuation
shouldn’t affect the masking state. Presumably this means a continuation
restore can’t be as simple as copying the captured stack frames onto the
current stack, since restoring certain frames affects other parts of the RTS
state.
(Tangentially, it seems like this particular example is not handled properly in
the existing capture/restore code, as this comment in Exception.cmm notes:
NB. there's a bug in here. If a thread is inside an
unsafePerformIO, and inside maskAsyncExceptions# (there is an
unmaskAsyncExceptions_ret on the stack), and it is blocked in an
interruptible operation, and it receives an exception, then the
unsafePerformIO thunk will be updated with a stack object
containing the unmaskAsyncExceptions_ret frame. Later, when
someone else evaluates this thunk, the original masking state is
not restored.
I think getting this right probably matters more if continuations are added, so
that’s something else to worry about.)
> So that's shift#. What about reset#? I expect it's something like
> `unsafeInterleaveIO`, that is it creates a thunk to name the continuation.
> You probably also want a `catch` in there, so that we don't tear down more of
> the stack than we need to.
It would be nice to be able to capture slices of the stack that include catch
frames, though theoretically it isn’t necessary — it would be possible to
arrange for a continuation that wants to capture through a catch to do
something like
reset (... (catch (reset ...) ...))
instead, then call shift twice and compose the two continuations by hand
(inserting another call to catch in between). I don’t know enough yet to
understand all the tradeoffs involved, but I’ll see if it’s possible to get
away with the userland emulation, since I figure the less code in the RTS the
better!
> Hope this is helpful.
Very much so, thank you!
> On Jan 30, 2020, at 10:31, Ben Gamari <[email protected]> wrote:
>
> For the record, runtime system captures the stack state in an AP_STACK
> closure. This is done in rts/RaiseAsync.c:raiseAsync and some of this is
> described in the comment attached to that function.
>
> As Simon PJ points out, this is all very tricky stuff, especially in a
> concurrent context. If you make any changes in this area do be sure to
> keep in mind the considerations described in Note [AP_STACKs must be
> eagerly blackholed], which arose out of the very-nast #13615.
Thank you for the pointers — I did manage to find raiseAsync, but I hadn’t seen
that Note. I will try my best to be suitably wary, though I imagine I’m in far
enough over my head that I don’t yet know half the things I need to be wary of.
:)
Alexis
_______________________________________________
ghc-devs mailing list
[email protected]
http://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-devs