-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Editorial: Various changes around execution contexts #2962
base: main
Are you sure you want to change the base?
Conversation
(force-pushed to fix an 'unused-declaration' error from ecmarkup) |
Force-pushed to resolve merge-conflicts from PR #2681. Also, I've added a fixup commit that makes 'code evaluation state' a bit more concrete. |
spec.html
Outdated
</ul> | ||
<p>The suspended execution associated with _aContext_ will stay suspended until some step transfers control back to _aContext_, which might never happen.</p> | ||
<emu-note>This is analogous to the concept of coroutines in programming languages.</emu-note> | ||
<emu-note>In practice, _aContext_ was the running execution context until just before the “transfer control” step, and _bContext_ is the new running execution context. However, the “transfer control” step itself doesn't manipulate the execution context stack.</emu-note> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this note needed? Who does this clarification help, and what does it help them with?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it helps the person who reads the definition of "transfer of control" and thinks "But wait, if you transfer control to an execution context, does that make it the running execution context? Does 'transfer control' also manipulate the execution context stack?" (Similar to issue #2409. Not that exact question, but a similar uncertainty about implicit stack manipulation.)
Now, in the absence of that note, I think you could still answer those questions by going to the places where 'transfer of control' occurs, and seeing what happens to the EC stack in neighboring steps. But I think it's useful to have a note to this effect close to the definition of 'transfer of control'.
spec.html
Outdated
<h1>Execution Context Stack</h1> | ||
<p>An agent's <dfn id="execution-context-stack" variants="execution context stacks">execution context stack</dfn> is used to organize some or all of the agent's execution contexts. The running execution context is always the top element of this stack.</p> | ||
<p>Adding and removing always happens at the “top” of the execution context stack. When an execution context is added, it becomes the topmost (i.e., the running execution context), and when it is later removed, the execution context “below” it becomes topmost again.</p> | ||
<emu-note>In the absense of generators, every execution context that is pushed onto the stack is new, and when it's removed from the stack it can be discarded. With generators, some execution contexts outlive their time on the stack, exist for a while outside the stack, and then later are pushed onto the stack again.</emu-note> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love this note.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though it should be expanded to include async functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For symmetry, should I also change "generators" to "generator functions"? E.g. "In the absence of generator functions and async functions, ..."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eh, I'm fine with the asymmetry (since there actually is a distinction between "generator" vs "generator function", although not one which is relevant here), and I think it reads better without the extra word.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
spec.html
Outdated
1. Remove _scriptContext_ from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context. | ||
1. Assert: The execution context stack is not empty. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1. Remove _scriptContext_ from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context. | |
1. Assert: The execution context stack is not empty. | |
1. Remove _scriptContext_ from the execution context stack. | |
1. Assert: The execution context stack is not empty. | |
1. NOTE: The execution context that is at the top of the execution context stack is now the running execution context. |
We don't make something the running execution context. By the definition of the term, it just is because of its position on the top of the stack.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think your point is that the wording "and restore ... as the running execution context" misleadingly suggests the need to do something (extra) to make that happen. I basically agree.
However, the "Remove ... and restore ..." wording is well-established in the status quo, in lots of places that this PR doesn't touch, so fixing it should probably be the job of a different PR.
[Later: I made it the first commit of this PR.]
Do we need any wording about what it means for "the running execution context" if the execution context stack is somehow empty? Do we need to describe extra invariants about the non-emptiness of the execution context stack? |
Hm, maybe the 2 commits for "Execution Context section" should be in a separate PR. |
I've added a commit that eliminates wording that treats execution contexts as 'active' things. Specifically, it eliminates phrasing such as:
For example, 19 The Global Object says that the global object "is created before control enters any execution context". But the spec doesn't say what it means for control to enter an EC, or when that first happens. (It doesn't use the phrase anywhere else.) And if you (reasonably) guess that it happens when you create an EC and push it onto the stack, that conflicts with InitializeHostDefinedRealm: the global object is created after an EC is pushed onto the stack. So rather than talk about control entering an EC, it seems way more helpful to just say that the global object is created in InitializeHostDefinedRealm. For another example, 9.9 Forward Progress says that "An agent becomes 'blocked' when its running execution context waits [...] for an external event." But if you look at Atomics.wait and SuspendAgent, there's no mention of anything happening to an execution context, so it seems unnecessary to bring that into the definition of 'blocked'. |
(I didn't eliminate the phrase 'running execution context', despite the fact that it suggests an execution context can 'run'. If we wanted to avoid that, we could call it something like the 'current execution context' or the 'top execution context'. But that would be a lot more disruption for not much benefit, I think.) |
(force-pushed to resolve conflicts from PR #3016) |
(force-pushed to resolve merge conflict) |
(force-pushed to resolve conflict from #3031) |
(force-pushed to resolve merge conflicts) |
spec.html
Outdated
1. Push _scriptContext_ onto the execution context stack; _scriptContext_ is now the running execution context. | ||
1. Let _script_ be _scriptRecord_.[[ECMAScriptCode]]. | ||
1. Let _result_ be Completion(GlobalDeclarationInstantiation(_script_, _globalEnv_)). | ||
1. If _result_.[[Type]] is ~normal~, then | ||
1. Set _result_ to Completion(Evaluation of _script_). | ||
1. If _result_.[[Type]] is ~normal~ and _result_.[[Value]] is ~empty~, then | ||
1. Set _result_ to NormalCompletion(*undefined*). | ||
1. Suspend _scriptContext_ and remove it from the execution context stack. | ||
1. Remove _scriptContext_ from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This still sounds like we are requiring some action be taken.
1. Remove _scriptContext_ from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context. | |
1. Remove _scriptContext_ from the execution context stack. The execution context that is now at the top of the execution context stack is the running execution context. |
We could also just drop that second sentence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I've prepended a commit that eliminates the phrasing "restore [context] as the running execution context".
spec.html
Outdated
1. Let _finalCompletion_ be _result_. | ||
1. Let _callerContext_ be the running execution context. | ||
1. Transfer control from _acGenContext_ to _callerContext_, passing _finalCompletion_. | ||
1. NOTE: The above step never completes, because control is never transferred back to _acGenContext_. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1. NOTE: The above step never completes, because control is never transferred back to _acGenContext_. | |
1. NOTE: This step is never reached because control is never transferred back to _acGenContext_. |
I don't want to have to think about what it means for a step to complete.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
spec.html
Outdated
1. <emu-meta effects="user-code">Resume the suspended evaluation of _genContext_</emu-meta> using NormalCompletion(_value_) as the result of the operation that suspended it. Let _result_ be the value returned by the resumed computation. | ||
1. Assert: When we return here, _genContext_ has already been removed from the execution context stack and _methodContext_ is the currently running execution context. | ||
1. Transfer control from _methodContext_ to _genContext_, passing NormalCompletion(_value_). | ||
1. NOTE: The above step completes only if/when control is transferred back to _methodContext_. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1. NOTE: The above step completes only if/when control is transferred back to _methodContext_. | |
1. NOTE: This step is reached only if/when control is transferred back to _methodContext_. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
(force-pushed to resolve merge conflicts) |
(force-pushed to resolve merge conflicts) |
…ext" Specifically, change: > Remove _X_ from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context. to just: > Remove _X_ from the execution context stack. and change: > Remove _X_ from the execution context stack and restore _Y_ as the running execution context. to: > Remove _X_ from the execution context stack. > Assert: _Y_ is the running execution context again. The "restore" phrasing suggests that the implementation needs to do something, when in fact the context in question becomes the running execution context automatically, because the running execution context is always the top element of the execution context stack.
…t it has only one Return step.
In GeneratorStart, AsyncGeneratorStart, and AsyncBlockStart, the Abstract Closure ends with a `Return`. This is odd, because where would it return to? Normally, a `Return` would return to the operation that invoked the Abstract Closure, but in this case, the invocation mechanism is unusual, and in general, the operation that caused it is no longer waiting for a result. Instead, the `Return` is presumably supposed to send control back to the latest next-side `Transfer control` step. So make that explicit.
The only remaining `Resume` steps are the 3 non-blocking Resumes in ScriptEvaluation, ExecuteModule, and PerformEval. Since there's no definition for "resuming" an execution context, and since it doesn't come up any other time that an EC is removed from the EC stack, I'm concluding that it has no normative effect. So remove the remaining `Resume` steps.
As with `Resume`, there's no definition for "suspending" an execution context. And if `Resume` has no normative effect, it seems very unlikely that `Suspend` would. So drop mentions of suspending an execution context. ---- The "Execution Contexts" section does have a paragraph that talks about suspending execution contexts, but it doesn't really say what the effect of `Suspend` is, it just implies that it's a prerequisite for making a different EC become the running EC. But that seems like a pointless hoop to jump through. Whatever meaning you might ascribe to suspending an execution context, you could just as easily imagine that it happens automatically whenever the running execution context changes. There's no need for the spec to call it out explicitly. It's just a source of "bugs", where we fail to say it when we "should".
Take a paragraph that talks about LexicalEnvironment and VariableEnvironment, and move it to a better spot.
Collect stuff about the execution context stack, move it to a new subsection, and then rework it. Note that there a couple of problems in the status quo: "A new execution context is created whenever control is transferred from the executable code associated with the currently running execution context to executable code that is not associated with that execution context." That became false when generators were added. "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." No, it's always stack-like.
Formerly, we would say: Set the code evaluation state of _genContext_ such that the next time there is a transfer of control to that execution context, _closure_ will be called with no arguments. I.e., we don't say what to set it *to*, just what the eventual effect of the setting should be. This isn't how the spec usually talks. Instead, simply set the code evaluation state to _closure_, and define "transfer of control" to handle that appropriately.
Specifically, eliminate wording such as: - an EC tracks the evaluation/execution of code - an EC evaluates/executes code - an EC can wait for an event - code is 'within' an EC - code is evaluated within an EC - control enters an EC
FYI https://github.com/tc39/ecma262/pull/3391/files#diff-181371b08d71216599b0acccbaabd03c306da6de142ea6275c2135810999805aR28635-R28636 adds new text that will need updating in this PR. |
Yeah, if #3391 merges first, then this branch will get syntax errors after the rebase. |
</td> | ||
</tr> | ||
</table> | ||
</emu-table> | ||
<p>In most situations only the running execution context (the top of the execution context stack) is directly manipulated by algorithms within this specification. Hence when the terms “LexicalEnvironment”, and “VariableEnvironment” are used without qualification they are in reference to those components of the running execution context.</p> | ||
<p>To express the semantics of generators, this document uses steps of the form “<dfn id="transfer-control" variants="transfers control,transfer of control">Transfer control</dfn> from _aContext_ to _bContext_, passing _completion_”, where _aContext_ and _bContext_ are execution contexts and _completion_ is a Completion Record. This means:</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<p>To express the semantics of generators, this document uses steps of the form “<dfn id="transfer-control" variants="transfers control,transfer of control">Transfer control</dfn> from _aContext_ to _bContext_, passing _completion_”, where _aContext_ and _bContext_ are execution contexts and _completion_ is a Completion Record. This means:</p> | |
<p>To express the semantics of generators, this document uses steps of the form “<dfn id="transfer-control" variants="transfer control,transfers control,transfer of control">Transfer control</dfn> from _aContext_ to _bContext_, passing _completion_”, where _aContext_ and _bContext_ are execution contexts and _completion_ is a Completion Record. This means:</p> |
For "If ..., transfer control ...".
<p>In most situations only the running execution context (the top of the execution context stack) is directly manipulated by algorithms within this specification. Hence when the terms “LexicalEnvironment”, and “VariableEnvironment” are used without qualification they are in reference to those components of the running execution context.</p> | ||
<p>To express the semantics of generators, this document uses steps of the form “<dfn id="transfer-control" variants="transfers control,transfer of control">Transfer control</dfn> from _aContext_ to _bContext_, passing _completion_”, where _aContext_ and _bContext_ are execution contexts and _completion_ is a Completion Record. This means:</p> | ||
<ul> | ||
<li>Execution of the current algorithm (the one with the “Transfer control” step) is suspended, and a representation of that suspended execution is saved in the code evaluation state of _aContext_.</li> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<li>Execution of the current algorithm (the one with the “Transfer control” step) is suspended, and a representation of that suspended execution is saved in the code evaluation state of _aContext_.</li> | |
<li>Execution of the current algorithm (the one with the “transfer control” step) is suspended, and a representation of that suspended execution is saved in the code evaluation state of _aContext_.</li> |
Consistent with the below reference.
</ul> | ||
<p>The suspended execution associated with _aContext_ will stay suspended until some step transfers control back to _aContext_, which might never happen.</p> | ||
<emu-note>This is analogous to the concept of coroutines in programming languages.</emu-note> | ||
<emu-note>In practice, _aContext_ was the running execution context until just before the “transfer control” step, and _bContext_ is the new running execution context. However, the “transfer control” step itself doesn't manipulate the execution context stack.</emu-note> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this note needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😆 I just noticed that I asked this same thing in my original review: #2962 (comment)
1. Resume _callerContext_ passing NormalCompletion(_iteratorResult_). If _genContext_ is ever resumed again, let _resumptionValue_ be the Completion Record with which it is resumed. | ||
1. Assert: If control reaches here, then _genContext_ is the running execution context again. | ||
1. Transfer control from _genContext_ to _callerContext_, passing NormalCompletion(_iteratorResult_). | ||
1. NOTE: This step is reached only if/when control is transferred back to _genContext_. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the wording you used above.
1. NOTE: This step is reached only if/when control is transferred back to _genContext_. | |
1. NOTE: This step is reached only when control is transferred back to _genContext_, which might not happen. |
<p>An <dfn variants="execution contexts">execution context</dfn> is a specification device that is used to track the runtime evaluation of code by an ECMAScript implementation. At any point in time, there is at most one execution context per agent that is actually executing code. This is known as the agent's <dfn id="running-execution-context" variants="running execution contexts">running execution context</dfn>. All references to the running execution context in this specification denote the running execution context of the surrounding agent.</p> | ||
<p>The <dfn id="execution-context-stack" variants="execution context stacks">execution context stack</dfn> is used to track execution contexts. The running execution context is always the top element of this stack. A new execution context is created whenever control is transferred from the executable code associated with the currently running execution context to executable code that is not associated with that execution context. The newly created execution context is pushed onto the stack and becomes the running execution context.</p> | ||
<p>An execution context contains whatever implementation specific state is necessary to track the execution progress of its associated code. Each execution context has at least the state components listed in <emu-xref href="#table-state-components-for-all-execution-contexts"></emu-xref>.</p> | ||
<p>An <dfn variants="execution contexts">execution context</dfn> brings together information that is relevant for the runtime evaluation of some unit of code (e.g., a script, a module, or a function object). At any point in time, there is at most one execution context per agent whose associated code is being evaluated. This is known as the agent's <dfn id="running-execution-context" variants="running execution contexts">running execution context</dfn>. All references to the running execution context in this specification denote the running execution context of the surrounding agent.</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<p>An <dfn variants="execution contexts">execution context</dfn> brings together information that is relevant for the runtime evaluation of some unit of code (e.g., a script, a module, or a function object). At any point in time, there is at most one execution context per agent whose associated code is being evaluated. This is known as the agent's <dfn id="running-execution-context" variants="running execution contexts">running execution context</dfn>. All references to the running execution context in this specification denote the running execution context of the surrounding agent.</p> | |
<p>An <dfn variants="execution contexts">execution context</dfn> brings together information that is relevant for the runtime evaluation of some unit of code (e.g., a script, a module, or a function object). At any point in time, there is at most one execution context per agent whose associated code is being evaluated. This is known as the agent's <dfn id="running-execution-context" variants="currently running execution context">running execution context</dfn>. All references to the running execution context in this specification denote the running execution context of the surrounding agent.</p> |
- it should never have to be plural since there can only be one
- we say "currently" before it below, and I'd rather that just be included in an alias for this term
@@ -48832,7 +48856,7 @@ <h1> | |||
<emu-alg> | |||
1. Let _runningContext_ be the running execution context. | |||
1. Let _asyncContext_ be a copy of _runningContext_. | |||
1. NOTE: Copying the execution state is required for AsyncBlockStart to resume its execution. It is ill-defined to resume a currently executing context. | |||
1. NOTE: Copying is required because AsyncFunctionStart has different roles for _asyncContext_ and _runningContext_ that cannot (easily) be filled by a single execution context. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this note could actually use some expansion, but I don't have a concrete suggestion at the moment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM other than nits
This PR follows up on some things that came up in the discussion of PR #2665, and then goes farther.
I've organized it into a bunch of fairly focused commits, in case that helps with review. The commits are divided into 3 groups.
(Commits marked with "(see commit msg)" have some extra discussion in the commit message.)
Transfer control:
Resume+Suspend:
(I think these resolve Clarify Running Execution context and the impact of push/pop and suspend/resume #2409.)
Resume
steps. (see commit msg)Suspend
steps. (see commit msg)Execution Context section:
One of the side-effects is to treat execution contexts more like just data structures. I.e., an EC isn't an active thing, it doesn't execute code, and suspending/resuming it makes no more sense than suspending/resuming a List or Record. If you like that direction, I could do some more cleanup. (I don't think there's much left.)
See also PR #2246 and PR #2681, which are independent of this PR but in the same conceptual neighborhood.