Extra execution context in async functions - javascript

When I was reading spec I saw next part:
NOTE: Copying the execution state is required for AsyncBlockStart to resume its execution. It is ill-defined to resume a currently
executing context.
I don't understand this. Why do we need to copy execution context? Can't we do it without extra execution context or what will be broken without copying in that case?

Evaluation of an async function body happens in a separate execution context that can be repeatedly resumed and suspended. The algorithm steps executed in this context are given in AsyncBlockStart #3.
On await (in Await #8) and completion (i.e. return/throw, in AsyncBlockStart #3.g), the execution context is popped off the stack (and in case of await, suspended to resume where it left off, in Await #9).
On promise fulfillment/rejection (in Await #3.c/5.c) and when starting the async function (in AsyncBlockStart #4), it is pushed onto the stack and resumed.
These push/pop operations need to symmetrically correspond to each other, both when starting and resuming the code it may run into either a suspension or the end of the code; and in all four cases the stack must have the same running execution context on top before and after.
In case of a resumption from promise settlement, that running execution context will be the current promise job. In case of AsyncFunctionStart, that running execution context will be the one created and pushed by the PrepareForOrdinaryCall steps during the [[Call]] to the async function (which goes through OrdinaryCallEvaluateBody, EvaluateBody to EvaluateAsyncFunctionBody which creates the promise and performs AsyncFunctionStart). It will afterwards be popped from the stack in [[Call]] #7 like for any other function.
So why do we need an extra execution context? Because if we didn't create a new one (as a copy of the current), it would have been popped off already when AsyncFunctionStart ends, and [[Call]] would fail to pop it again. (Or worse, pop one too many). Of course, an alternative solution to this problem would have been to not make a copy of the current execution context, reuse the suspendable execution context instead, and just push it again onto the stack (without resuming it, only setting it as the running execution context) after the AsyncBlockStart in AsyncFunctionStart #4. But that would've been weird, no?
After all, it doesn't matter which way it's specified, the outcome would be the same. The execution context is not observable from user code.
Note: re-using the same execution context is in fact what generators do. GeneratorStart #2 (which is called from EvaluateGeneratorBody, where the parameter declarations are evaluated and the Generator instance is created) does use the running execution context as the genContext that is repeatedly resumed and suspended. The main difference is that the start ("first resumption") doesn't already happen during the function call for generators (as it does happen for async functions), it will only happen later in the first next() call.
And actually "It is ill-defined to resume a currently executing context." doesn't apply here. The currently executing context would get implicitly suspended in AsyncBlockStart #3 by setting "the code evaluation state of asyncContext such that when evaluation is resumed […]", just like it does happen in GeneratorStart #4.

Related

What is the behaviour of callback execution in C# (assuming configure await is not set to false and assuming there is synchronization context)?

In Javascript, the callback (of the awaited async function) runs only when the call stack is empty. If there are multiple callbacks, then they are queued (in the event loop queue) such that only 1 callback runs in its entirety before the next callback gets change to run.
In case of C# (assuming ConfigureAwait is not set to false - that is - the callabck will run in the same thread; also assuming there is syncronization context [winforms]):
Does the callback run immediately without waiting for call stack to be empty?
Suppose there are multiple callbacks queued, then while 1 callback is running, and another callback is ready for execution, then does the running callback suspend while the new callback wants to run? Or is it sequential?
The answer is that it depends on the current SynchronizationContext and, if that is null, the current TaskScheduler.
When the awaited Task completes, and you have configured the Awaitable to capture the context and recover on it, then almost certainly that continuation cannot continue immediately (the thread is likely busy executing something else). All that can be done is to schedule the continuation for later execution on the current SynchronizationContext, which is done by calling SynchronizationContext.Post().
How this Post method is implemented depends on the SynchronizationContext (which is abstract). If you are using WPF, the SynchronizationContext is a DispatcherSynchronizationContext. There, Post is implemented by a call to Dispatcher.BeginInvoke() with DispatcherPriority.Normal.
In other words, your continuation gets queued on the dispatcher for later execution with normal priority. Other delegates may have higher priority (e.g. DispatcherPriority.Input), but eventually your delegate gets executed on the dispatcher.
Two things to notice:
BeginInvoke simply queues your delegate. It does not execute it right away. In other words, queuing your delegate is really fast.
The above explanation should illustrate why async-await is just syntactic sugar. You could achieve all these calls yourself, but the compiler does it for you, making your code flow more naturally.

Javascript stack model for generators

While I was using javascript generators to implement a debugger for a small scheme interpreter I starting wondering about the stack model in e.g. the chrome javascript engine. Normally It's enough to have one stack for function call frames. In case of generators I can leave a function call execute another path and then later jump back into the partially executed generator, i.e. put the part of the stack into life that was left.
How is this implemented e.g. in chrome or in the firefox javascript engine? Is the entire virtual stack composed of several virtual stacks or is the part of the stack that is left when yielding written into a generator object? Then it could put back on the stack when entering the generator again.
Generators still run on the same single call stack that normal functions do. There are no multiple stacks that evaluation jumps around between.
When you instantiate a generator (by calling a generator function) and then call its .next() method, it just pushes that call on the top of the stack. It will then run the code inside the generator function.
When it encounters a yield statement, it just pops the call from the stack and returns from the .next() method, continuing as usual after any function call.
The difference between a generator call and a normal function call is what happens when entering and leaving the code.
A normal function leaves at the end of the function body or a return/throw statement, it's finished then. A generator also leaves on yield, but it has to remember the state (basically storing the instruction pointer in the generator instance) so that it can resume execution after the yield. Also it has to remember the state of all local variables, but engines already know how to do that from the implementation of closures.
A normal function enters a call by setting up a fresh environment, and starting execution at the top of the function body. A generator call will restore the state so that it can continue where it left off.
The normal behaviour of the stack is not affected by this.
OK, I lied. yield* makes it all a bit more complicated. A chain of recursively yield*ed generators will need to push and pop multiple stack frames when entering or leaving a .next() call. An engine might optimise this context switch by using multiple stacks. Still, one would see them as stacked atop each other, forming one large stack, and during execution only the top of that single stack is manipulated.
In Chrome/V8's current implementation, as part of yielding, all state that the generator needs for resuming execution later is written to an object. There is only one stack for function call frames.
The details are complicated; if you want to read the source, start at BytecodeGenerator::VisitYield in (v8)/src/interpreter/bytecode-generator.cc.

Is it possible for Global Execution Context to pop off the execution stack?

When JS code starts to run, the Global Execution Context is created and sits at the bottom of the execution stack as to "accomodate" Global Variable Object and'this'.
If it is the case for the execution stack to get empty after the whole JS code is run and there is no Global Execution Context, how are we still able to access the global variables (for example, I am running an html file with its JS code and after its completion, I am still able to see the values of global variables through Chrome console...) or how this still points to global object (there shouldn't be any 'this' if there wasn't any Execution Context!)?
The only explanation I may give to myself is that Global Execution Context never leaves the execution stack; it is always there till I decide to close the browser window. Am I right or not?
Moreover, in case of asynchronous callbacks, when an event gets out of the event queue and gets into JS engine as to be run, what exactly happens in the execution stack? Is the callback's execution context sitting at the bottom of this stack or the global execution context is still there?
There is a similar subject Is the initial global execution context ever popped off the call stack in JavaScript?; however, it does not answer my questions.
Thank you
The execution stack gets empty when the whole code is run.
how are we still able to access the global variables?
Even when no code is executed the global lexical environment with the global object still exists. When you feed some code into chrome console, the code is being evaluated, a new global execution context is being created and initialized with its lexical and variable environments set to the global environment and this bound to the global object. Then your code is executed within this context and the execution stack gets empty again.
how this still points to global object?
Every time a new global execution context is initialized with the global code, this gets bound to the global object.
in case of asynchronous callbacks, when an event gets out of the event queue and gets into JS engine as to be run, what exactly happens in the execution stack?
Again, a new global execution context is created and pushed onto the empty execution stack. In MDN this is described in slightly different terms than in ECMAScript spec:
When the stack is empty, a message is taken out of the queue and processed. The processing consists of calling the associated function (and thus creating an initial stack frame). The message processing ends when the stack becomes empty again. (MDN. Concurrency model and event loop)
Here "stack frame" means "execution context" and "initial stack frame" corresponds to "global execution context".
Is the callback's execution context sitting at the bottom of this stack or the global execution context is still there?
None of them. The stack is empty. And only if it is empty, the oldest callback is taken from the callback/event queue:
When there is no running execution context and the execution context stack is empty, the ECMAScript implementation removes the first PendingJob from a Job Queue and uses the information contained in it to create an execution context and starts execution of the associated Job abstract operation. ECMAScript 6.0 spec

Is the initial global execution context ever popped off the call stack in JavaScript?

Is the "initial global execution context" ever popped off the call stack in JavaScript? I am talking about the execution context that is at the bottom of the stack at all times.
If so, I presume this means it is pushed onto the stack first before a callback is picked up off the Job Queue?
Alternatively, is it the [[Scope]].outer chain that provides access to the global environment whenever a callback is pushed onto the stack?
Is the "initial global execution context" ever popped off the call stack in JavaScript? I am talking about the execution context that is at the bottom of the stack at all times.
Yes, it is. An empty execution context stack is the requirement for any jobs to run.
However, there is no such thing like an "initial global execution context", and since the stack can be empty there is no single context that is at the bottom of the stack all the time.
"Global execution contexts" are created in ScriptEvaluations. Every script does have its own scriptCxt, yet all of them in a shared realm carry the same global environment records. These scriptCtxs are not at the bottom at the stack, though.
An "initial execution context" that sits at the bottom of the stack is created in the ECMAScript Initialisation process. It is pretty meaningless, for it does not hold anything but the new realm and only serves as the context for the initialisation of the realm and global object, but it is also used to start off the job queues.
If so, I presume this means it is pushed onto the stack first before a callback is picked up off the Job Queue?
Yes indeed. We can see this from the instructions for the NextJob algorithm steps. These are performed at the end of the ECMAScript initialisation and end of every job, and basically read as follows:
Suspend the current execution context and pop it from the stack so that the stack is empty.
Get the next job from any queue. If there are no more, proceed as you want (i.e. typically shuts down the process).
Create a new, empty (except for the realm of the job) context newContext and put it at the bottom of the stack
Execute the selected job in this context (which starts over NextJob in the end)
These contexts serve as the base for every job, containing all execution that ever happens. In PromiseJobs, they are used rather directly, while in module- and script evaluation jobs other contexts will be pushed on the stack that serve to hold the respective environment records with whom the code should be executed.
Alternatively, is it the [[Scope]].outer chain that provides access to the global environment whenever a callback is pushed onto the stack?
Yes indeed. The scope chain (that is not to be confused with the execution context stack) does provide access from everywhere to the global environment, which sits at the end of every scope chain.

When does a function get added to the call stack?

Lets say we have a sequence of functions being executed in the global scope, like so:
function second() {}
function first() {
second();
}
first();
When does first get added to the call stack?
Does it get added upon invocation, or does it get added after it calls second (and the execution context is now inside of second) ?
A function is "added to the stack" when it is called. While a "call stack" implementation is not specified in ECMAScript 5th edition it does define the virtual behavior in 10.3 Execution Contexts:
When control is transferred to ECMAScript executable code, control is entering an execution context. Active execution contexts logically form a stack. The top execution context on this logical stack is the running execution context [ie. currently running function]. 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.
It is not technically the function that is part of the stack, but rather the execution context created from invoking the function.
This also agrees with more general Call Stack concept where the active/current function context is also part of the stack.
In computer science, a call stack is a stack data structure that stores information about the active subroutines [including the subroutine currently running] of a computer program .. the details are normally hidden and automatic in high-level programming languages..
Using this definition also ensures a function is never running "off the stack" - which agrees with such a concept from other languages/environments and JavaScript developer tools.
function second() {/* 3rd code here will be executed once second is called */}
function first() {
// 2nd code here will be executed before second(); is called
second();
// 4th code here will be executed after second(); is called and all its code has been executed
}
// 1st code here will be executed before anything is called
first();
// 5th code here will be executed when everything has been called.

Categories