7.10.函数运行实现

\(7.10.\)Function Call and Return Implementation

1.The caller's view

  In the calling function's view:

  • Before calling another function, I must push as many arguments as the function expects to get;
  • Next, I invoke the function using call functionName nArgs;
  • After the called function returns, the argument values that I pushed before the call have disappeared from the stack, and a return value (that always exists) appears at the top of the stack;
  • After the called function returns, all my memory segments are exactly the same as they were before the call (except that temp is undefined and some values of my static segment may have changed).

2.The callee's view

  In the called function's view:

  • Before I start executing, myargument segment has been initialized with the argument values passed by the caller.
  • My local variables segment has been allocated and initialized to zeros.
  • My static segment has been set to the static segment of the VM file to which I belong (memory segments this, that, pointer, and temp are undefined upon entry).
  • My working stack is empty.
  • Before returning, I must push a value onto the stack.

3.Handling call

  We can implement the VM code by these steps:

  In terms of call, we do these steps:

  1. Push a label onto the stack, and use the same label when we are going to return after the called function terminates.

    1
    push returnAddress

  2. Push some label that we generate to save the memory segment of the caller:

    1
    2
    3
    4
    push LCL
    push ARG
    push THIS
    push THAT

  3. The argument should be repositioned for the called function, and we should reposition it at the beginning of the ARG segment:

    • We can calculate the address, because we know how many values we pushed. Say we push 5 values the we do :
      1
      ARG = SP - 5
  4. Push the local segment to insure that the function can visit its local variables correctly :

    1
    LCL = SP

  5. We execute the called function by simply writing the command goto nameOfFunction:

    1
    goto functionName

  6. We insert the returnAddress label, so that when the function has been executed, we can get back to the place we invoke it :

    1
    (returnAddress) //Declares a label for the return-address

4.Handling function

  The big picture of handling function is that:

  • The first thing translator does is it takes the function name and generates a label, and the label will serve as the entry point to the translated assembly code of the function.
  • We simply write some assembly code that handles the setting up of the function's execution.
  1. We take the function name and generate its label:
  2. Since we know how many local variables we have to create, we do push 0 nVars times.
    1
    2
    3
    (functionName)
    //repeat nVars times
    push 0

4.Handling return

  After setting up, the function can start doing its thing. We need to generate assembly code that moves the return value to the caller, reinstates the caller's state, and finally goto the return address.

  1. Create some temporary variable (called endFrame) and assign the value of LCL to it:
    1
    endFrame = LCL

  If you look at the stack diagram, you will see that endFrame indeed points at the end of the frame in the host RAM.

  1. Since we should put the return value at the top of the stack, say we have 5 values, the endFrame - 5 will be the exact return address.

    • We need to use * to look inside and get the address.
      1
      retaddr = *(endFrame - 5)
  2. Reposition the return value for the caller.

    • The value should be copied to argument 0, and we already have a pointer ARG located in the position
    • pop is going to retrieve the return value off the stack. We take this value and put it in ARG.
      1
      *ARG = pop()
  3. The caller expects to see a return value and continue to do its work, so the stack pointer should be just after ARG.

    1
    SP = ARG + 1

  4. Then we can begin to recover the various segments that we saved on the stack before.

    1
    2
    3
    4
    THAT = *(endFrame - 1)
    THIS = *(endFrame - 2)
    ARG = *(endFrame - 3)
    LCL = *(endFrame - 4)

  5. Finally we can jump to the return address we copied before:

    1
    goto retAddr

  • Every thing below SP is recycled. The next push is going to override the memory location where SP shows.
  • The caller will see the return value at the top of its working stack.
  • The stack pointer will point at the next world in the memory.