Note [Detecting illegal captures is not guaranteed]
As alluded to in Note [When capturing the continuation fails], the “current continuation” is only well-defined within a given `State#` thread. For a concrete example illustrating why, consider the following program: do tag <- newPromptTag let v = unsafePerformIO (control0 tag (\k -> k ())) prompt tag (pure $! v) If we were to allow this program, what would its result be? The answer depends on how and when we evaluate `v`. If we allocate a thunk for `v` and force it once the prompt has been installed, the program would successfully return `()`. But since GHC can tell that `v` is used strictly, it may very well choose to evaluate it immediately, before the prompt has been installed, in which case there would be no matching prompt in scope at the time the call to `control0` is evaluated, and the program would raise an error. Without uses of `pseq`, GHC makes no guarantees about the order in which it will evaluate pure expressions, so the optimizer may rearrange them significantly. Therefore, any code that attempts such a capture is ill-defined, and we want to do our best to detect and reject such mistakes. However, we cannot guarantee that we will catch all such misuses! For example, given the program prompt tag (pure (1 + unsafePerformIO (control0 tag f))) it is extremely unlikely that we will signal an error, despite the erroneous capture. The reason is Note [Simplification of runRW#] in GHC.CoreToStg.Prep: when `runRW#` appears in a strict context, there is no reason to allocate a thunk, so GHC takes care to ensure it will not do so. With no thunk to update, there is naturally no thunk update frame, so we cannot possibly detect at runtime that anything was amiss. Preserving the information necessary to reliably detect all of these sorts of misuses at runtime in all situations would be disastrous for performance, so it is the programmer’s responsibility to ensure this does not happen. Any program for which continuation capture fails is a buggy program---there is NO WAY to write a safe program that relies upon catching exceptions raised by continuation capture failure. In other words, such programs invoke undefined behavior. Given the behavior of such programs is already undefined, one might ask why we bother detecting and reporting such failure conditions at all. In theory, we could ignore thunk update frames completely and let the program behave unpredictably. But detecting the failures we *can* detect is still worthwhile: * From the programmer’s point of view, best-effort detection and reporting of such misuses is still helpful, and the performance overhead of checking for them is minimal. * From the runtime’s point of view, detecting and eagerly rejecting such uses gives us much more confidence they will not violate internal invariants, so even if a buggy program does the wrong thing, it won’t corrupt the runtime. In summary, the runtime does the best it can, but if it fails to detect and report a misuse of `control0#`, the bug is in the program, not GHC.
References 2
- Simplification of runRW# GHC.CoreToStg.Prep
- When capturing the continuation fails
Referenced by 0
Nothing in the tree points here.