I am looking at the Wikipedia page on Call Stack, and trying to grok this image:
This is as far as I get lol:
const memory = []
memory[0] = 3 // top of stack pointer
memory[1] = 4 // stackframe pointer
memory[2] = 1000 // max call stack size
memory[3] = 5 // first frame
memory[4] = 0 // first frame return address (exit let's say)
But let's say we have 2 actions: add == 1
, and load == 2
, plus whatever is required to do the stack manipulation. How do i feed it a stream of data to execute some example code? I'm not being to stringent on the parameter order or calling conventions, mainly because I'm not there yet. But this demonstrates what I'm trying to get after.
function add_twice(a, b, c) {
add(a, add(b, c))
}
function start() {
add_twice(1, 2, 3)
}
So that's what we want to accomplish. This is what I imagine (sort of) how it's laid out in memory:
// this is as far as I can get,
// just trying to simulate the `add` function
memory[5] = 2 // load
memory[6] = 100 // some address?
memory[7] = 1 // the first number to add
memory[8] = 2 // load
memory[9] = 101 // some address?
memory[10] = 2 // the second number to add
memory[11] = 1 // call `add`
memory[12] = 102 // where to store result
Now for the execution. We don't even have the nested subroutines yet, I am nowhere close to figuring that out, but I imagine someone knows it easily and could show it with some demo JavaScript code. So here is my attempt at doing the code evaluation, like building a processor or VM sort of thing, to evaluate the code.
function evaluate() {
while (true) {
let frame_address = memory[3]
let operation = memory[frame_address]
switch (operation) {
case 2: // load
let a = memory[operation + 1]
let b = memory[operation + 2]
memory[a] = b
memory[frame_address] = operation + 3
break
case 1: // add
let a = memory[operation + 1]
let input_a = ??
let input_b = ??
break
}
}
}
That's basically as far as I can get. But I would like to, in addition to just this flat list of instructions, see how to do nested calls and maintain the stack, using only this array. Also, I only have these JavaScript local variables like frame_address
and operation
for readability. In reality I would do it like this:
function evaluate() {
while (true) {
switch (memory[memory[3]]) {
case 2: // load
memory[something_a] = memory[memory[memory[3]] + 1]
memory[something_b] = memory[memory[memory[3]] + 2]
memory[memory[3]] = memory[memory[3]] + 3
break
case 1: // add
memory[something_a_2] = memory[memory[memory[3]] + 1]
memory[something_input_a_2] = ??
memory[something_input_b_2] = ??
break
}
}
}
That way I am not falling victim to taking advantage of what JavaScript offers as abstration on top of the machine code, and I can simulate a more realistic VM as if it was implemented in assembly. Any ideas how to do this?
Some key questions I have in doing this include:
- Are the frame pointer and other key things hardcoded into a known place in memory, like I have
memory[3]
? Sort of thing? - How to push parameters onto the stack using only this memory system, not JavaScript object or anything that would make it a lot easier (i.e. cheating ㋡)