Feasibility of native RTS support for continuations?

Alexis King lexi.lambda at gmail.com
Thu Feb 13 01:52:00 UTC 2020

> On Feb 10, 2020, at 02:18, Simon Marlow <marlowsd at gmail.com> wrote:
>> On Mon, 10 Feb 2020 at 08:17, Simon Marlow <marlowsd at gmail.com> wrote:
>> Let me just say "unsafePerformIO" :)  You probably want to at least ensure that things don't crash in that case, even if you can't give a sensible semantics to what actually happens. We have a similar situation with unsafeIOToST - we can't tell you exactly what it does in general, except that it doesn't crash (I hope!).
> Typo - I meant unsafeIOToSTM here.

I’ve been thinking about this. At first I figured you were probably right, and I decided I’d switch to a more raiseAsync-like approach. But once I started trying to implement it, I became less convinced it makes sense.

As its name implies, an AP_STACK is sort of like a saturated application, where the stack itself is the “function.” Extending the analogy, a continuation would be a PAP_STACK, since the stack is awaiting a value. This difference is significant. Suppose you write:

    let x = 1 + unsafePerformIO (shift f >>= g) in ...

If you force x, the stack will unwind to the nearest reset. When you unwind past x’s UPDATE_FRAME, you can’t replace the blackhole with an AP_STACK, since the captured slice of the stack represents the expression

    \m -> 1 + unsafePerformIO (m >>= g)

which is a function, not a thunk. The only logical interpretation of this situation is that x is a thunk quite like

    let y = 1 + error "bang"

except that it doesn’t just abort to the enclosing reset frame when forced, it actually composes the captured continuation with the upper frames of the current stack and passes the composed continuation to f to resume computation. That’s an awful lot of trouble given the resulting semantics is going to be unpredictable, anyway!

I should be clear that I do not intend shift/reset to have any safe interface in IO directly. Even if you could make it type safe, it would break all kinds of code that manages resources using bracket. Rather, I have built a library that defines a totally separate Eff monad, and that monad uses the primops directly, wrapped in a safe interface. It isn’t possible for a user to screw things up using unsafePerformIO because there is no way to call shift from IO (unless you wrap shift# in IO yourself, but then I think you can be expected to know what you’re getting yourself into). So without mucking about with GHC.Exts, you still can’t get segfaults.


More information about the ghc-devs mailing list