|
|
I'm a bit confused by return address.
...
argument 0
argument n-1
return-address
saved LCL
saved ARG
saved THIS
saved THAT
local 0
local n-1
<--- SP
This is what the stack looks like just after a function is called but before it's returned.
After it's return, the return value replaces argument 0 in the stack when *ARG = pop() is called.
So what's exactly stored in return address then?
|
|
This post was updated on .
Careful, don't confuse the "return value" with the "return address."
Upon returning, the return value of the function should be on the top of the stack, and PC should be set to the return address.
|
|
In that case, what does the return address refer to?
I thought after the function is returned, the return value goes where argument 0 is, and SP is set to the next address:
// BEFORE
....
argument 0
argument n-1
return addr
saved LCL
saved ARG
saved THIS
saved THAT
local 0
local n-1
<---- SP
// AFTER
....
return value
<---- SP
If so, what's the point of saving return address? Isn't it just easier to set SP to *(argument 0 + 1)?
|
|
riverfish, I made a typo: PC should be set to the return address (not SP)
Chew on that and see if it makes sense now.
|
|
Sorry bakos, still not making any sense to me!
I'm playing with NestedCall in the VMEmulator.
Inside function Sys.init, it calls Sys.main 0; when it does, the value saved in the 'return address' is 6.
And inside Sys.main, it calls Sys.add12 1; when does, the value saved in the 'return address' is 22.
However, after running the simulation dozens of times I still can't understand the relevancy of these two numbers and how they're used. There doesn't seem to be anything significant stored in either RAM[6] or RAM[22] during this simulation.
If it's suppose to act as a program counter, then doesn't it make logical sense to start at 1 and increment each time a function returns?
|
Administrator
|
First, let me describe how this works in assembly language programs, because that is a published specification. I will describe what I think the VM Emulator does later.
For an assembly language program, program counter means the ROM address of the currently executing instruction. When an ASM program calls a subroutine, the called subroutine needs to know what instruction in the calling program should be jumped to when the called subroutine returns.
For example the ASM code for Sys.init's call to Sys.main might look something like this:
ROM Addr Code
// call Sys.main 0
96 @$RET_ADDR$2
97 D=A
... [ASM code to push D, build the frame, and jump to Sys.main]
157 ($RET_ADDR$2)
// pop temp 1
157 @SP
158 AM=M-1
...
In this case, the return address pushed on the stack will be 157, and the return instruction at the end of Sys.main will get that number off the stack and jump to the next instruction in Sys.init.
The return addresses pushed on the stack by the VM Emulator are a bit confusing.
In the VM emulator, the program counter is a VM instruction number. Unfortunately, those numbers are not visible to users when the VM Emulator is running. They appear to be a running count of the instructions shown in the "Program" panel.
--Mark
|
|
Making sense now and finally got my tests passing. Thanks a lot guys
|
|
My problem is somewhat related.
(I'm working on NestedCalls)
After researching here for quite a while I understand now, how you first have to create a label, at the beginning of a function call, like "@return_add_1" and save the PC-value in a general purpose register (e.g. R15).
Then, after the function-call, I have to insert the label like "(return_add_1)"
And when RETURN is called, I have to jump to the PC-value, currently stored in R15.
My Problem:
R15 get's overwritten at some point with the wrong destination. So unfortunately my code jumps to the wrong address. It's almost right. It jumps to pop temp 0, but should jump to pop temp 1 and then enter the infinite loop.
Do I have to use different general purpose registers to prevent that?
Or is my label injection most probably wrong?
I would be very happy for any advice.
Thanks
|
Administrator
|
niilz wrote
My problem is somewhat related.
(I'm working on NestedCalls)
After researching here for quite a while I understand now, how you first have to create a label, at the beginning of a function call, like "@return_add_1" and save the PC-value in a general purpose register (e.g. R15).
Then, after the function-call, I have to insert the label like "(return_add_1)"
And when RETURN is called, I have to jump to the PC-value, currently stored in R15.
My Problem:
R15 get's overwritten at some point with the wrong destination. So unfortunately my code jumps to the wrong address. It's almost right. It jumps to pop temp 0, but should jump to pop temp 1 and then enter the infinite loop.
Do I have to use different general purpose registers to prevent that?
Or is my label injection most probably wrong?
I would be very happy for any advice.
Thanks
Yes, the registers R13 through R15 are for general use by the VM translator, but you need to be very careful that if you use one of them for some purpose, that it is not possible for any of the code generated by your translator to use that register for anything else until after you are completely done using it for the current purpose.
But if you are storing something there at the beginning of a function call and expecting it to still be there at the end of the function call, then how can that happen if your function calls any other function since THAT function call with change it's value in between when the first function starts and when it stops.
Or, another way to think of it, when Main.main() is called at the very beginning of the program execution, it will store something in R15 and expect that value to still be there at the end of the program when Main.main() ends. Unless you have a mechanism to save and restore the value in R15 when other functions are called, then no other function can use R15.
|
|
First of all thank you for the quick reply.
You have confirmed what I thought would be the problem.
BUT, how do I inform the write_return(), (the implementation which "creates-ASM-for-RETURN"), to which PC it has to jump to. Because write_return() just performs it's algorithm. Where it puts all frame pieces (derived from LCL), back into place. Only problem: write_return() does not know to which function it belongs, right?
So I cannot just jmp to some label, because I wouldn't know it's name...
At least that is what I think and why I don't find an out right now.
|
Administrator
|
Look at the pseudocode in Figure 8.5. What is the very first thing that happens for the 'call' VM command?
One of the things that is stored in the stack frame is the return address -- the instruction address that you need to return to after the function completes. It is located at a specific location in RAM relative to the LCL pointer. If you call ten nested functions, each function has a stack frame and each stack frame has a return address stored in it.
|
|
Oh my! That took me faaaaar too long.
Thank you so very much. My ASM is working now!!!!
I was really close though It also did not work immediately. Because I had to realize, that within the return-call I had to:
- first safe return-address (derived from LCL [LCL-5]) in a register (e.g. R15)
- restore THAT, THIS, ARG, LCL
- And theeeeen jump to the value in R15
Because if you derive the return address from LCL-5, just before the jump, but after you have already reinitiated LCL, the return-address is of course wrong.
|
Administrator
|
niilz wrote
Oh my! That took me faaaaar too long.
Thank you so very much. My ASM is working now!!!!
I was really close though It also did not work immediately. Because I had to realize, that within the return-call I had to:
- first safe return-address (derived from LCL [LCL-5]) in a register (e.g. R15)
- restore THAT, THIS, ARG, LCL
- And theeeeen jump to the value in R15
Because if you derive the return address from LCL-5, just before the jump, but after you have already reinitiated LCL, the return-address is of course wrong.
If you use R15 for this purpose, then you are okay because there is no chance of it being changed by anything else during the time you need it.
There are other ways to do this as well, but at the end of the day any way that works is valid.
|
|
Thanks for this. I've been spending hours trying to figure out how to use the return address.
|
|
I don't understand the concept of return address.
Is the return-address the place on the stack before the function is called?
So do I have to store the location in RAM that is on argument 1 of the current stack function call?
|
Administrator
|
The return address is the address of the program instruction that you want to resume program execution at once the function being called returns.
Consider the following program snippet (in some made up language).
140 x = 16
141 print(x)
142 y = x + 19
....
878 // Start of code to print whatever is passed to it
...
893 return
So the program starts off at instruction 0 and eventually makes it to instruction 140, where it sets the variable x to 16.
It then executes instruction 141, but this is a function call. So it needs to pass some information (namely the value of x) to the function print() and also needs to free up key RAM locations that it knows that print() is going to probably use. So it saves the values of those key RAM locations on the stack, and also the value of x on the stack in a place where print() can find it. It then transfers control to print() by executing a jump to instruction 878.
Execution then continues at instruction 878 and, eventually, makes it to the return statement on instruction 893. At this point, we need to restore the key values that were placed on the stack back to their original RAM locations. But we also need to jump to the instruction following the call to print(), namely instruction 142. But how do we know that we need to jump to that particular instruction? Remember, print() might get called at many different places in the code and each time it is called, control needs to be returned to the line following THAT particular call.
The solution is, as part of saving the key RAM locations on the stack, we also store the address of the instruction that execution needs to continue with when the function returns. This is the return address (i.e., the address to return to). Once of the last things that the return code does is find this address on the stack and load it into the program counter.
|
|
Many thanks for your response!
Do I understand it right:
In your example, the return address would be 141 or 142 (or the corresponding line in the assembler code) and the code would be continued in line 142 after the return?
Both the return address and the call of the function are both references to addresses in ROM/Program not in RAM/Stack?
|
Administrator
|
You've got it. The return address would be 142 (or, as you say, the corresponding line in the assembler code since each line in a high level language can map to dozens, or even hundreds, of lines of assembler code).
And, indeed, both the function call and the return addresses refer to addresses in the program code, which in our case is stored in ROM. In a more typical computer, the program code is also stored in RAM, but not in the Hack architecture (which is more typical of small, embedded processors like microcontrollers).
|
|