Javascript Stack Model for Generators

While I used javascript generators to implement a debugger for a small circuit interpreter, I begin to wonder about the stack model, for example. chrome javascript engine. This is usually enough to have one stack for function frames. In the case of generators, I can leave the function call to execute a different path, and then later return back to the partially executed generator, i.e. Bring the rest of the stack to life.

How is this implemented, for example. in chrome or firefox javascript engine? Is the entire virtual stack composed of several virtual stacks or is it part of the stack that remains when writing to the generator object? Then it can return to the stack when re-entering the generator.

+5
source share
2 answers

In the current Chrome / V8 implementation, as part of yield ing, all the state the generator needs to resume execution later is written to the object. There is only one stack for function call frames.

The details are complex; if you want to read the source, start with BytecodeGenerator::VisitYield at (v8) /src/interpreter/bytecode-generator.cc .

+2
source

Generators still work on the same call stack, performed by normal functions. There are no multiple stacks that are evaluated between them.

When you instantiate the generator (by calling the generator function) and then call its .next() method, it just calls that call at the top of the stack. Then it will run the code inside the generator function.
When it encounters a yield , it simply pops the call from the stack and returns from the .next() method, continuing, as usual, after calling any function.

The difference between a generator call and a regular function call is what happens when you enter and exit the code.
The normal function remains at the end of the function body or in the return / throw expression, then it ends. The generator also goes to yield , but it must remember the state (basically by storing the instruction pointer in the generator instance) so that it can resume execution after yield . He should also remember the state of all local variables, but the engines already know how to do this from the implementation of closures.
A normal function enters a call by setting up a new environment and starting execution in the upper body of the function. A call to the generator will restore state so that it can continue to work where it left off.

The normal behavior of the stack does not affect this.

Ok, I lied. yield* makes things a little more complicated. The recursive chain of yield* ed generators will need to be pressed and placed a few frames of the stack when the .next() call .next() in or out. The engine can optimize this context switch using multiple stacks. However, one could see them stacked on top of each other, forming one large stack, and at runtime only the top of this separate stack is managed.

+1
source

Source: https://habr.com/ru/post/1275460/


All Articles