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

Execution Context Stack. Violation of the LIFO order using Generator Function #1089

Closed
smellyshovel opened this Issue Feb 2, 2018 · 8 comments

Comments

Projects
None yet
3 participants
@smellyshovel

smellyshovel commented Feb 2, 2018

The specifications says that the LIFO order of the Running Execution Context transitions in the Execution Context Stack may be violated by some ECMAScript featues:

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.

However the specification doesn't explicitly tell by which exactly.

I've read that the violation may occure when using the Generator Functions. But I absolutely don't understand why. Morover I couldn't have been able to find any mentions of this in the 25.3.3 section of the specification.

function *gen() {
    yield 1;
    return 2;
}

let g = new gen();

console.log(g.next().value);

// some other operations

console.log(g.next().value);

As far as I understand the lifecycle of the Execution Context Stack in the exmple above may look as follows:

EC(global)

->

EC(gen1)
EC(global)

->

EC(global)

->

EC(gen2)
EC(global)

->

EC(global)

So I don't see any LIFO violations here.

Maybe Generator Functions don't even lead to such violation? Or, maybe, I misread the specification. Anyways, could you please explain what's going on with the Execution Context Stack when using generators? And how (and why) does LIFO order of the Running Execution Context transitions violates?

@allenwb

This comment has been minimized.

Show comment
Hide comment
@smellyshovel

This comment has been minimized.

Show comment
Hide comment
@smellyshovel

smellyshovel Feb 2, 2018

@allenwb

Morover I couldn't have been able to find any mentions of this in the 25.3.3 section of the specification.

As I said earlier, I don't see any obvious LIFO order violations there.
For instance 4.c in the 25.3.3.1 just removes genContext from the stack, then 8 in the 25.3.3.3 pushes newly-created one onto the stack and the 10 of the 25.3.3.3 also just removes genContext from the stack (says that is already removed, to be precise).

I would be very grateful if you would honor me and explain what is happening in simple words. These are very basic things for you, but too complicated stuff for me. Anyway, thank you for your time.

smellyshovel commented Feb 2, 2018

@allenwb

Morover I couldn't have been able to find any mentions of this in the 25.3.3 section of the specification.

As I said earlier, I don't see any obvious LIFO order violations there.
For instance 4.c in the 25.3.3.1 just removes genContext from the stack, then 8 in the 25.3.3.3 pushes newly-created one onto the stack and the 10 of the 25.3.3.3 also just removes genContext from the stack (says that is already removed, to be precise).

I would be very grateful if you would honor me and explain what is happening in simple words. These are very basic things for you, but too complicated stuff for me. Anyway, thank you for your time.

@littledan littledan added the question label Feb 4, 2018

@littledan

This comment has been minimized.

Show comment
Hide comment
@littledan

littledan Feb 4, 2018

Member

When you yield from a generator, and call .next() to get back into a generator, the transfer of control flow is more like "swapping to a different stack" than the typical LIFO from ordinary function calls.

Member

littledan commented Feb 4, 2018

When you yield from a generator, and call .next() to get back into a generator, the transfer of control flow is more like "swapping to a different stack" than the typical LIFO from ordinary function calls.

@smellyshovel

This comment has been minimized.

Show comment
Hide comment
@smellyshovel

smellyshovel Feb 4, 2018

@littledan sorry, don't understand what "swapping to a different stack" means.

When the control transfers to a generator first, the new EC is created. Then, when the control transfers back to the place where the generator was invoked (due to the yield statement), created EC is removed from the stack. Then the generator is invoked again (by the .next method), and the new EC is created again. And so on...

I don't quite understand what does LIFO violation means in terms of the specification. Is it like manipulating the ECs below the running EC, or does it mean that the running EC becomes not the top element of the stack? Maybe generator functions don't remove the running execution context from the stack after they done their work? Maybe they just kinda suspend it and the running EC after that is the EC that initiated the creation of the new one (in which the generator was invoked)?

smellyshovel commented Feb 4, 2018

@littledan sorry, don't understand what "swapping to a different stack" means.

When the control transfers to a generator first, the new EC is created. Then, when the control transfers back to the place where the generator was invoked (due to the yield statement), created EC is removed from the stack. Then the generator is invoked again (by the .next method), and the new EC is created again. And so on...

I don't quite understand what does LIFO violation means in terms of the specification. Is it like manipulating the ECs below the running EC, or does it mean that the running EC becomes not the top element of the stack? Maybe generator functions don't remove the running execution context from the stack after they done their work? Maybe they just kinda suspend it and the running EC after that is the EC that initiated the creation of the new one (in which the generator was invoked)?

@littledan

This comment has been minimized.

Show comment
Hide comment
@littledan

littledan Feb 4, 2018

Member

Here's a good introduction I found to coroutines and how they work in terms of switching stacks: http://www.boost.org/doc/libs/1_53_0/libs/coroutine/doc/html/coroutine/intro.html . JavaScript generators are just like coroutines: yield switches to the caller, and next() switches back into the callee.

Member

littledan commented Feb 4, 2018

Here's a good introduction I found to coroutines and how they work in terms of switching stacks: http://www.boost.org/doc/libs/1_53_0/libs/coroutine/doc/html/coroutine/intro.html . JavaScript generators are just like coroutines: yield switches to the caller, and next() switches back into the callee.

@smellyshovel

This comment has been minimized.

Show comment
Hide comment
@smellyshovel

smellyshovel Feb 4, 2018

@littledan

JavaScript generators are just like coroutines: yield switches to the caller, and next() switches back into the callee.

Do I understand correct that it means that the generator's EC doesn't get removed after the yield switches back to the caller context? Like

EC(global) - Running Execution Context (REC)

-> calling the generator

EC(gen1) - REC
EC(global)

-> yielding back to caller

EC(gen) - but why then the REC is not the top element in the stack?
EC(global) - *REC*

-> .next()

EC(gen2) - REC
EC(global)

-> completely terminating the generator

EC(global) - REC

Or does it mean that the generator's context gets saved somewhere else and not in the same Execution Context Stack? But, again, in this case I don't see any violation of the LIFO order of the real one stack. Damn, I feel dumb 😞

smellyshovel commented Feb 4, 2018

@littledan

JavaScript generators are just like coroutines: yield switches to the caller, and next() switches back into the callee.

Do I understand correct that it means that the generator's EC doesn't get removed after the yield switches back to the caller context? Like

EC(global) - Running Execution Context (REC)

-> calling the generator

EC(gen1) - REC
EC(global)

-> yielding back to caller

EC(gen) - but why then the REC is not the top element in the stack?
EC(global) - *REC*

-> .next()

EC(gen2) - REC
EC(global)

-> completely terminating the generator

EC(global) - REC

Or does it mean that the generator's context gets saved somewhere else and not in the same Execution Context Stack? But, again, in this case I don't see any violation of the LIFO order of the real one stack. Damn, I feel dumb 😞

@allenwb

This comment has been minimized.

Show comment
Hide comment
@allenwb

allenwb Feb 4, 2018

Member

Let's just settle this . I originally wrote that sentence and this is what I meant:

In a conventional "call stack" the current activation records (aka "active Execution Contexts") is on the top of the stack. A new activation is pushed onto the stack when a call occurs, and the stack is popped when the procedure invocation represented by the current activation record returns or otherwise permanently terminates its execution. Popped activation records are discarded. This is behavior is what I meant by "stack-like last-in/first-out manner".

With generators, an operation (typically yield) causes an activation record to suspend execution and be removed from the call stack before before its associated procedure has terminated execution. The removed activation record is saved and execution continues using the activation record that was below it on the stack. At some latter point, an operation (typically the next method) causes a saved activation to be pushed again onto the call stack and its execution resumes at the point where it was suspended. This what I meant by a "non-LIFO" transition.

The call stack, by definition is a LIFO data-structure and usually the creation and destruction of activation record tracks that pushes and pops of the call stack. But generators require that in some cases activation records do not strictly track those LIFO stack operations.

Member

allenwb commented Feb 4, 2018

Let's just settle this . I originally wrote that sentence and this is what I meant:

In a conventional "call stack" the current activation records (aka "active Execution Contexts") is on the top of the stack. A new activation is pushed onto the stack when a call occurs, and the stack is popped when the procedure invocation represented by the current activation record returns or otherwise permanently terminates its execution. Popped activation records are discarded. This is behavior is what I meant by "stack-like last-in/first-out manner".

With generators, an operation (typically yield) causes an activation record to suspend execution and be removed from the call stack before before its associated procedure has terminated execution. The removed activation record is saved and execution continues using the activation record that was below it on the stack. At some latter point, an operation (typically the next method) causes a saved activation to be pushed again onto the call stack and its execution resumes at the point where it was suspended. This what I meant by a "non-LIFO" transition.

The call stack, by definition is a LIFO data-structure and usually the creation and destruction of activation record tracks that pushes and pops of the call stack. But generators require that in some cases activation records do not strictly track those LIFO stack operations.

@smellyshovel

This comment has been minimized.

Show comment
Hide comment
@smellyshovel

smellyshovel Feb 4, 2018

@allenwb alright, now it seems like I completely got it. A huge thanks to all of you, guys!

smellyshovel commented Feb 4, 2018

@allenwb alright, now it seems like I completely got it. A huge thanks to all of you, guys!

@littledan littledan closed this Feb 4, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment