tc39 / ecma262

Status, process, and documents for ECMA-262
https://tc39.es/ecma262/
Other
14.97k stars 1.28k forks source link

Editorial: make suspension of resumable execution contexts explicit #2618

Open bergus opened 2 years ago

bergus commented 2 years ago

While writing this StackOverflow answer, I noticed that async function and generator execution contexts are not explicitly suspended when starting them (GeneratorStart, AsyncBlockStart, AsyncGeneratorStart) or when a resumption is done (GeneratorYield, Await, AsyncGeneratorYield). In particular, the note at AsyncFunctionStart #3 states "It is ill-defined to resume a currently executing context." However, there are a few execution contexts that are resumed without being explicitly suspended before. I came to the conclusion that whenever (in the six algorithms mentions above) it is written

Set the code evaluation state of someContext such that when evaluation is resumed for that execution context the following steps will be performed

it actually means

Suspend someContext and set its code evaluation state such that when evaluation is resumed for that execution context the following steps will be performed

Notice that during the creation of generators, a suspended execution context is shortly at the top of the stack. I suppose this could be avoided by introducing the genContext as a copy of the execution context, like it is done for async functions.

Also I wonder whether there is a (deliberate) distinction between the "running execution context" (defined to be the top element of the stack) and a "resumed" execution context. Does an execution context automatically get resumed when it becomes top of the stack, does it automatically get suspended when a different execution context becomes top of the stack? If yes, I wonder why the spec contains some steps to explicitly suspend an execution context before pushing a different one (e.g. "Suspend prevContext" or "If callerContext is not already suspended, suspend callerContext.") or to explicitly resume an execution context when it becomes running (e.g. "Resume the context that is now on the top of the execution context stack as the running execution context."). If no, I'm missing a mention of resuming the callerContext in [[Call]] #7 where it does "Remove calleeContext from the execution context stack and restore callerContext as the running execution context."

jmdyck commented 2 years ago

Yeah, when it comes to suspension and resumption of execution contexts, the spec doesn't really have a cohesive story. Part of the problem is that there's nothing in the execution context structure that explicitly conveys the suspended/running/whatever state of the context. It's supposedly represented in the 'code evaluation state' (CES) component, but that component isn't well-defined, and there isn't even a discussion of what 'suspend' and 'resume' should mean with respect to it. (See also #2409, which includes pointers to other discussions.)

whenever (in the six algorithms mentions above) it is written

Set the code evaluation state of someContext such that when evaluation is resumed for that execution context the following steps will be performed

it actually means

Suspend someContext and set its code evaluation state such that [...]

It's possible that the intention was that "setting the CES" includes taking it out of whatever previous state it was in. Alternatively, it's possible that setting the CES as described doesn't actually change the running/suspended/whatever-ness of the context, it just sets something off to the side that isn't consulted until the next 'resume'. Who knows.

Does an execution context automatically get resumed when it becomes top of the stack, does it automatically get suspended when a different execution context becomes top of the stack?

I think the answer is just that the spec is inconsistent.

(Note that #2409 asked if "suspend"/"resume" implied a change to the execution context stack, whereas you're asking about the reverse implication.)

bakkot commented 2 years ago

I agree the spec is not currently particularly consistent here. However:

I came to the conclusion that whenever (in the six algorithms mentions above) it is written

Set the code evaluation state of someContext such that when evaluation is resumed for that execution context the following steps will be performed

it actually means

Suspend someContext and set its code evaluation state such that when evaluation is resumed for that execution context the following steps will be performed

I don't think this is the intended interpretation. Rather, I think you should understand that removing an execution context from the stack implies suspending it. The spec is inconsistent about whether this implies resuming the new top-of-stack context or whether this needs to be done explicitly.

I have #2429 open to clarify all this somewhat, though it will still not be as clear as it could be.

bergus commented 2 years ago

Thanks, I had not seen #2409 before, might as well be a duplicate. I think https://github.com/tc39/ecma262/issues/2409#issuecomment-842414242 does answer my question

Stack manipulation always happens explicitly (I think). "Suspend" (whatever it means) is separate, though it typically precedes a Push or Pop. (The cases where it doesn't might be bugs, it's hard to say.)

My guess is that the intent was that a Suspend would only affect a context's 'code evaluation state', but it's a grey area.

@bakkot

Rather, I think you should understand that removing an execution context from the stack implies suspending it.

Ah, that would work as well, though in any case it should be made explicit not implied. But doing it this way would mean "that setting the CES as described doesn't actually change the running/suspended/whatever-ness of the context, it just sets something off to the side that isn't consulted until the next 'resume'", which I don't really like as it's harder to understand and would need some extra "off the side" state in the mental model of a CES.

Edit: I've reviewed #2429 and like that approach as well, not using "Set the code evaluation state" in GeneratorYield, Await, and AsyncGeneratorYield at all but simply putting the future steps after the "Resume". But the problem persists in GeneratorStart and AsyncGeneratorStart, which set aside future steps in the current running execution context before suspending/popping it.