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.
Related
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.
This video talks about a callstack that functions get 'pushed onto' and 'popped off' of.
If a method in the stack takes a long time, to complete, it can cause blocking - new DOM rendering pauses and sometimes the rendering actions get backlogged and execute after any lengthy functions have finished executing.
What about things like assigning variables, if statements and loops? Are they not potentially time-consuming operations that could cause blocking as described here? Is there a model for these sorts of operations like there is for functions?
You've misunderstood. Javascript is single-threaded, so everything that happens in client-side JavaScript (excluding web workers) blocks the main thread. That does not include things like IO that occur outside of the JavaScript interpreted code, so fetching data from an API for instance will not block the main thread. But execution of any code you write - variable assignments, functions, loops, expression evaluation, all that - occurs on the same thread that renders the DOM, so long calculations will cause the UI to freeze (again, unless you hand the computations off to a web worker).
So the question of blocking is unrelated to the call stack, which is the way the JS interpreter keeps track of what function is calling what function, so data is returned to the right caller. As for there being a "model" for assignments etc, the question is a bit unclear, but there is something called the "execution context". The execution context is closely related to the call stack in that a new execution context is created whenever a function is called on the stack. The execution context contains all the data the interpreter needs to execute the code within the current function (or top level code, if that's where we are).
You can read more about the details here: https://levelup.gitconnected.com/learn-javascript-fundamentals-scope-context-execution-context-9fe8673b3164
If you can specifically reference a point in the video, or what it describes, I can be more specific. But I'm not watching a 26 minute video to answer this! But I'll try.
A call stack is the way a javascript interpreter keeps track of where it is. For example, you call function a, a is pushed to the stack and run. If a calls b, then b is pushed to the stack. If b calls c then d, c is pushed to the stack, it completes, it's popped off, and b resumes where it was. Then it calls d and d runs and and returns and is popped off the stack. Then b finished, it's popped off the stack, and a continues where it was.
The stuff you mentioned, if statements, loops, assigning variables (and their scope) all happen inside functions (but it's more complex, scopes close over values and so forth). So yes, they do cause blocking, but they don't have their own spot on the call stack. You can think of them as the content of what runs on an item in the call stack.
https://developer.mozilla.org/en-US/docs/Glossary/Call_stack
This question already has answers here:
Is this recursion or not
(4 answers)
Why the function called by setTimeout has no callstack limit?
(2 answers)
Closed 2 years ago.
Given the following psudo-code (which is taken from my remote camera robot project)
[event handler](when event happens) {
[do some startup and init code]
start_everything_going();
}
// This block does something that will interact with the browser every 1/60 second.
def do_something() {
[code that does something goes here]
requestAnimationFrame(do_something);
}
def start_everything_going() {
requestAnimationFrame(do_something);
}
The way this works using requestAnimationFrame is that the event handler triggers, (say when a joystick is attached or activated), and after some setup or configuration code is run, it calls the initial function to begin the cyclical animation - start_everything_going().
Since requestAnimationFrame runs once and once only when called, it has to be repeatedly called to create a continuous effect.
The canonical method - and the one used in every example I have seen - is that the first invocation is done by a wrapper function called (eventually) by the event handler that triggers it, or something the event handler launches.
In order to repeatedly call requestAnimationFrame, the actual animating function calls requestAnimationFrame, with itself as the argument, as the last thing before exiting.
As far as I know, this is recursion, and is called recursion in all the docs about requestAnimationFrame I've seen. (i.e. MDN, Stack Overflow, (etc.).)
As I remember, a recursive call to a function creates yet another instance of the function, (leaving the original instance suspended in time somewhere), repeating the steps needed to do something. And this continues forever, creating additional instances of itself until, (eventually), some ending condition is reached.
Since, in this case, the called function never returns, there is no way to “unwind” the recursion. And, as far as I know, this will continue until either Hell freezes solid or all available computer resources have been exhausted.
Since this animation refreshes itself every 1/60th second, and can (theoretically) run for hours and hours and hours and hours and hours, what keeps this from folding up like a house of cards and crashing the system due to resource exhaustion?
It's not actually a recursive call in the function-stack sense, only in the broad sense.
When you call requestAnimationFrame(callbackFunction), the requestAnimationFrame function schedules the callback function with the browser to be called on the browser's next frame. Then requestAnimationFrame returns and gets popped off the stack, the function stack finishes executing until every other function returns and gets popped off the stack, and control is handed back to the browser.
In other words, the stack resolves fully, then on the next frame, the browser spins up the function stack again by executing callbackFunction.
A function calling itself is classic recursion, which is very common in programming when a complex algorithm needs to repeat itself, (like calculating a mathematical series or many digits of an irrational number like π).
There's an important distinction here. requestAnimationFrame doesn't actually call itself directly. callbackFunction calls requestAnimatonFrame, which then flags the browser to call callbackFunction on the next frame. Then requestAnimationFrame returns, and the function stack continues to resolve completely.
Since, in this case, the called function never returns, there is no way to “unwind” the recursion.
Technically the called function returns every frame, then the browser executes it again the next frame.
I have been curious of how js code is executed from beginning to the end.
I have read about the event loop and seen this great video,
how stack frames look like here,and also read about how the V8 engine compiles js code here.
Question :
When does V8 starts compiling and executing the code in relation to the event loop stack ?
is it when the function is about to get popped out of the stack?
or do all functions get compiled, right before they are placed on the stack ?
therefore the proccess of putting other function on top is acctually only dealing with machine code, if so does the execution of that machine code occurs when popping the function from the stack ?
In case my question is not understood, i believe via this example it would be better understood
Example :
function foo() {
var name=`foo`;
var obj = {
number: 6
}
console.log(obj.number);
}
function baz() {
var name = `baz`;
console.log(a);
foo();
}
baz();
the first process that occurs is lazy parsing, where all the file is being parsed for syntax errors, but not fully parsed so it takes less time.
going through the function declerations
does the v8 engine now compiles the function declaration code to
machine code ? or its not his turn yet..
baz is called , baz is placed on bottom of the stack ,and in its stack frame the name variable value is stored (since its a primitive).
when exactly does buz gets parsed and converted to machine code ? before its placed on the stack ? or when it pops off ?
console.log placed on top of baz and is executed, - console shows baz
is this is the
place where the console.log js code is compiled to machine code and executed ?
console.logs pops of the stack.
foo is placed on top of baz, obj is placed in heap (since its a reference type), and name=foo is placed in foo`s stack frame.
console.log goes on top of foo, and is executed , console shows 6.
console.log pops off.
foo pops off, along with its local variable value.
baz pops off along with its name=baz local variable
There is no such thing as "the event loop stack".
One concept is the "call stack", which is a term for the fact that when functions call each other, they form a stack-like current state of things. This is mostly a theoretical concept, but as it happens, there is indeed an area of memory that is called "the stack" and is used for functions' local variables, but it is not a data structure with a push/pop interface: the act of calling a function places its data on this stack, and returning from the function removes it again, returning control to the calling function.
This answers part of your function: starting to execute a function is literally exactly the same as having this function placed on the call stack. Those are two descriptions for the same thing.
Another concept is the event queue. You can think of it as a queue of functions waiting to be executed; whenever no other function is executing, the next function from this queue is called. Putting a function into the queue does not require it to have been parsed or compiled. In your example snippet, the event queue is not used at all.
Compiling functions is really unrelated to all this. When a function is called (by another function, or by the event loop), it has to be executable in some form -- but depending on your JavaScript engine, it could get interpreted without any compilation, or it could get compiled to bytecode, or it could get compiled to machine code, or the engine could use this opportunity to switch from one to the other.
Since you asked about V8 specifically: in current versions, when V8 sees a function definition like function f() { ... }, it doesn't do anything anything yet (except for a few cases where V8 guesses that the function will be executed soon, in which case it creates bytecode for it immediately). If the function gets queued as a callback, still no parsing or compilation happens. When a function is called for the first time, V8 creates bytecode for it. When the function is called again, the bytecode exists already, so no additional work is required. When a function runs hot enough, V8 eventually decides to compile optimized machine code for it, usually on a background thread. Additional calls to it are opportunities for V8 to check whether the background thread is done producing machine code already; if so, then the next call will use that optimized code instead of interpreting the function's bytecode like the earlier calls did. Note that these implementation details can and will change over time.
One more note for clarification:
in its stack frame the name variable value is stored (since its a primitive).
Not quite. The variable itself is stored in the stack frame, but only as a reference. Whether it refers to a primitive value or not doesn't matter; strings and objects are both allocated on the heap. The local variable will be destroyed when the function returns and its stack frame is torn down; the respective object or string on the heap will (eventually, at some indeterminate time) be cleaned up by the garbage collector.
I'm building a hybrid native/HTML5 app on iOS. As part of the native/HTML5 bridge, I'm executing some JavaScript code via stringByEvaluatingJavaScriptFromString.
I make sure to call this function from the main dispatch queue on the native side. Most of the time, the effect on the JavaScript side is that the invoked JavaScript code is called directly from the top level (of the JavaScript stack), even if multiple calls to stringByEvaluatingJavaScriptFromString occur in close proximity.
Occasionally, however, I see evidence that a call to stringByEvaluatingJavaScriptFromString occurs during the middle of method execution - that is, a method called by e.g. an event handler doesn't return before the method called by stringByEvaluatingJavaScriptFromString starts to execute.
Given that JavaScript is single-threaded, how is this possible?
This question has been plaguing me for months, and I finally have proof of the answer.
While iOS generally executes stringByEvaluatingJavaScriptFromString calls at the top of the JavaScript stack, there are certain system calls that appear to "yield" to pending evaluations. The one I have specifically identified is XmlHttpRequest.send(): if there happen to be pending evaluations at the time send() is called, send() will block until those evaluations are executed. There may be others as well.
If this behavior is undesirable, you can avoid the re-entrancy of code by wrapping the function invoked by stringByEvaluatingJavaScriptFromString in setTimeout(..., 0). See this question for background information on why such calls can be useful.
Edit: Though it occurs to me after writing this that it's a bit strange that xhr.send() will execute any pending evaluations, but won't execute pending timeouts. I'll have to do some more experiments...
Edit 2: Experiments indicate that xhr.send() does not in fact execute pending timeouts. The inconsistency is a bit odd, but there you have it.