Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clarify Running Execution context and the impact of push/pop and suspend/resume #2409

Open
codehag opened this issue May 17, 2021 · 2 comments · May be fixed by #2962
Open

Clarify Running Execution context and the impact of push/pop and suspend/resume #2409

codehag opened this issue May 17, 2021 · 2 comments · May be fixed by #2962

Comments

@codehag
Copy link
Contributor

codehag commented May 17, 2021

In preparing top level await, something I thought was resolved kept nagging me and I have been trying to get further input.

Specifically, in the top level await specification, we are suspending the running context for both the sync and async modules. In the case of async, that suspended module is still at the top of the stack:

Module suspension: https://github.com/tc39/proposal-top-level-await/blob/main/spec.html#L1208
Reference to running context in the asyncBlockStart: https://github.com/tc39/proposal-top-level-await/blob/main/spec.html#L52

The question is, if the top module on the stack is suspended is it still the running context? If it is still the running context, we should clarify this here: https://tc39.es/ecma262/#running-execution-context -- my expectation that, when we suspend, that the running context will be the next one on the stack, but what does suspend really mean? according to this: https://tc39.es/ecma262/#sec-execution-contexts, running context is always at the top of the stack. I am also wondering if we really need this step, or if pushing a new context for the module is enough in the sync case.

On the other hand, if a suspended module is not the running context (that is, suspending somehow removes it) then we have a bug in the spec code, as we will end up asserting that we are pointing to the wrong one here: https://github.com/tc39/proposal-top-level-await/blob/main/spec.html#L67

In any case, a clarification of push/pop and suspend/resume would help, especially if suspending/resuming is in terms of push/pop. Does suspending mean, save the context, remove it from the stack, and return it when resuming? This seems to be hinted at by:

Once the running execution context has been suspended a different execution context may become the running execution context and commence evaluating its code. At some later time a suspended execution context may again become the running execution context and continue evaluating its code at the point where it had previously been suspended. Transition of the running execution context status among execution contexts usually occurs in stack-like last-in/first-out manner. However, some ECMAScript features require non-LIFO transitions of the running execution context.

The confusion is brought back by the "non-LIFO" part -- is suspend/resume explicitly for non-lifo contexts? If so, what happens when it participates in a LIFO situation like the one above? If a context is suspended, a new context is pushed then popped, is the prior context that is suspended immediately resume?

Or, perhaps better, to update and no longer use suspend/resume -- rather only say that we are pushing or popping.

@jmdyck
Copy link
Collaborator

jmdyck commented May 17, 2021

The question is, if the top module on the stack is suspended is it still the running context?

The running execution context (REC) is just whatever context is top-of-stack. "Suspend" isn't defined, but it's pretty clear that it doesn't manipulate the stack (since that always has to be done separately). So I'd say theoretically yes, if you Suspend the top-of-stack, it's still the REC. However, 'in practice', it only stays that way for a step or two before it's no longer top-of-stack, during which time there aren't any references to the REC, so the answer currently doesn't make a difference normatively.

In any case, a clarification of push/pop and suspend/resume would help, especially if suspending/resuming is in terms of push/pop. Does suspending mean, save the context, remove it from the stack, and return it when resuming?

Nope. 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.

This seems to be hinted at by:

[...] Transition of the running execution context status among execution contexts usually occurs in stack-like last-in/first-out manner. However, some ECMAScript features require non-LIFO transitions of the running execution context.

The confusion is brought back by the "non-LIFO" part -- is suspend/resume explicitly for non-lifo contexts?

I don't think there actually are any "non-LIFO transitions", i.e. cases where the last context to become REC isn't the first one to stop being REC. Generators and Async do interesting things with contexts, but I don't think any of them violate the LIFO-ness of REC transitions. (I.e., the execution context stack really is a stack.)

Or, perhaps better, to update and no longer use suspend/resume -- rather only say that we are pushing or popping.

See also issue #2400, which gets into Suspend+Resume and Push+Pop. @bakkot certainly has ideas in this area.

@bakkot
Copy link
Contributor

bakkot commented May 17, 2021

See also the discussion on esdiscuss which prompted #2400. For posterity I should also link logs from IRC where jmdyck and I were talking this over: May 3, May 4, and May 5.

Or, perhaps better, to update and no longer use suspend/resume -- rather only say that we are pushing or popping.

I'd like to do this, but it requires working through a bit of confusion: there are a couple of places (in particular AsyncGeneratorYield and PrepareForTailCall) where an execution context is popped but the now-topmost context is not immediately resumed. (This can lead to some very weird behavior.)

Getting in to the weeds a bit: I suspect AsyncGeneratorYield can be rewritten to avoid this, but it would have normative implications for the realm of the iterator result object. However, engines are wildly inconsistent about those anyway, so we can almost certainly get away with normative changes of that sort if the rewrite otherwise makes sense. (Edit: I did this in #2413. I managed to keep it editorial by more explicitly managing the realm for creating the iterator result object.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants