问题
Here is a made up example of nested functions with functions passed as parameters and functions returned:
function foo() {
let x = 100
function bar(a, b) {
function mul(cb) {
let m = x * a
cb(m)
}
function add(cb) {
let m = x + b
cb(m)
}
function update(m) {
x = m
}
mul(update)
add(update)
console.log(x)
}
return bar
}
let bar = foo()
bar(2, 3) // => 203
bar(2, 3) // => 409
bar(2, 3) // => 821
bar(2, 3) // => 1645
Without figuring out potential ways in which these functions could be optimized and/or reduced/simplified by a crafty compiler, how would a naive compiler represent these in a call stack with activation records?
bar
is a nested function returned from the parent function. It usesx
from the parent scope, even though the parent scope is now "closed".mul
usesx
from the parent-parent scope, anda
from the parent scope. In addition, it takes a callbackcb
which is a function that updatesx
.add
is similar tomul
.
I have been reading online about how activation records work, and get how to implement them in JavaScript (in a JavaScript VM) using a simple stack. However, I haven't yet found any good resources explaining how to implement nested functions and functions as parameters like the example I posted above. It seems these are related to non-local variables, but there isn't much information on these. Same with nested functions. For example, Wikipedia says this:
the language runtime code must also implicitly pass the environment (data) that the function sees inside its encapsulating function, so that it is reachable also when the current activation of the enclosing function no longer exists. This means that the environment must be stored in another memory area than (the subsequently reclaimed parts of) a chronologically based execution stack, which, in turn, implies some sort of freely dynamic memory allocation.
But it doesn't explain what this looks like in practice, other than saying it is a "closure". This doesn't go into enough practical detail as well.
Basically, I would like to know how the call stack looks at different points in the example, such as in these steps:
foo -> bar -> mul
foo -> bar -> mul -> update
foo -> bar -> console.log(x)
For example, foo -> bar -> mul
means what does the function call stack and activation records look like at the step of say let m = x * a
in the above code? etc..
I understand that you need to somehow keep a reference to the parent "scopes", but what does this look like in practice (say, if you were to show it in JSON or using JavaScript pseudocode)? And since the function bar
is the return
of foo
, we are keeping foo
's locals, but foo has returned. What does this do to the stack?
I would like to understand this because I am implementing a programming language. I would like this language to support all of the JavaScript features I outlined above, but I would like to implement it low-level like in a VM or using Assembly or the like, so need to know how the call stack functions in this advanced system.
来源:https://stackoverflow.com/questions/63763766/what-does-the-call-stack-look-like-in-a-system-of-nested-functions-functions-pa