<div dir="ltr"><div dir="ltr">On Sat, 1 Feb 2020 at 00:23, Alexis King <<a href="mailto:lexi.lambda@gmail.com">lexi.lambda@gmail.com</a>> wrote:<br></div><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">> On Jan 30, 2020, at 02:35, Simon Marlow <<a href="mailto:marlowsd@gmail.com" target="_blank">marlowsd@gmail.com</a>> wrote:<br><br>
> 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#.<br>
<br>
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.<br></blockquote><div><br></div><div>The issue here is that raiseAsync is destructive - it *moves* the stack to the heap, rather than copying it. So if you want to continue execution where you left off, for shift#, you would have to copy it back onto the stack again. That's the point I was trying to highlight here.<br></div><div><br></div><div> Cheers</div><div>Simon</div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
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<br>
<br>
┌───────────┐<br>
│ RET_SMALL │<br>
├───────────┤<br>
│ CATCH │<br>
├───────────┤<br>
│ RESET │<br>
├───────────┤<br>
<br>
then the captured continuation could itself essentially be a stack like<br>
<br>
┌───────────┐<br>
│ RET_SMALL │<br>
├───────────┤<br>
│ CATCH │<br>
├───────────┤<br>
│ UNDERFLOW │<br>
└───────────┘<br>
<br>
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.<br>
<br>
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:<br>
<br>
reset (mask_ (shift f))<br>
<br>
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<br>
<br>
mask_ (reset (shift f >>= g))<br>
<br>
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.<br>
<br>
(Tangentially, it seems like this particular example is not handled properly in the existing capture/restore code, as this comment in Exception.cmm notes:<br>
<br>
NB. there's a bug in here. If a thread is inside an<br>
unsafePerformIO, and inside maskAsyncExceptions# (there is an<br>
unmaskAsyncExceptions_ret on the stack), and it is blocked in an<br>
interruptible operation, and it receives an exception, then the<br>
unsafePerformIO thunk will be updated with a stack object<br>
containing the unmaskAsyncExceptions_ret frame. Later, when<br>
someone else evaluates this thunk, the original masking state is<br>
not restored.<br>
<br>
I think getting this right probably matters more if continuations are added, so that’s something else to worry about.)<br>
<br>
> 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.<br>
<br>
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<br>
<br>
reset (... (catch (reset ...) ...))<br>
<br>
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!<br>
<br>
> Hope this is helpful.<br>
<br>
Very much so, thank you!<br>
<br>
> On Jan 30, 2020, at 10:31, Ben Gamari <<a href="mailto:ben@smart-cactus.org" target="_blank">ben@smart-cactus.org</a>> wrote:<br>
> <br>
> For the record, runtime system captures the stack state in an AP_STACK<br>
> closure. This is done in rts/RaiseAsync.c:raiseAsync and some of this is<br>
> described in the comment attached to that function.<br>
> <br>
> As Simon PJ points out, this is all very tricky stuff, especially in a<br>
> concurrent context. If you make any changes in this area do be sure to<br>
> keep in mind the considerations described in Note [AP_STACKs must be<br>
> eagerly blackholed], which arose out of the very-nast #13615.<br>
<br>
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. :)<br>
<br>
Alexis</blockquote></div></div>