Procedure
Learning this in the context of CS241E. They are essentially functions.
A procedure is an abstraction that encapsulates a reusable unit of code.
Process:
- The code that calls a procedure (i.e. caller) transfers control to the code of the procedure (i.e. callee) by modifying the Program Counter. Arguments can be passed as parameters.
- At the end of execution of procedure, control is transferred back to caller. It can return a value.
To implement this functionality, the implementations of the calling code and of the procedure must agree on conventions :
- Where in memory or in which registers will the arguments and the return value be stored?
- Does the calling code or the procedure allocate and free memory needed for the call?
- Which registers may change their value during a procedure call?
- These are called caller-save registers. If the caller needs their value, it needs to save it elsewhere before the call.
- Which registers must keep their original value after a procedure call?
- These are called callee-save registers. If the procedure (callee) modifies them, it must change them back to their original values before returning to the caller
Caller-Save vs Callee-Save (this is very confusing)
- Caller-Save registers: Values may be modified by a procedure call.
- Almost ALL registers are caller-save, by default.
- Callee-Save Registers: Register values are preserved by a procedure call.
- The Stack Pointer (register 30) and Frame Pointer (register 29) are callee-save. This is super important because of the way we designed our Variable to be accessed, which requires the value of
Reg.framePointer
- The Stack Pointer (register 30) and Frame Pointer (register 29) are callee-save. This is super important because of the way we designed our Variable to be accessed, which requires the value of
Control Transfer
Achieved through the following code
Other Terminology
- Prologue: The code before the body of the procedure, which prepares the procedure for execution
- Epilogue: The code after the body of the procedure, which cleans up the memory that was being used by the procedure and prepares to return to the calling code
Allocating a Procedure Frame
For the variables of the procedure, we should be allocating those on the Stack Frame using Stack Frame using Chunks, which is done with Stack.allocate(frame)
. This sets Reg.result
and Reg.stackPointer
to the memory address.
We also want to set this as the Reg.framePointer
. However, we would not be able to restore this value afterwards, when control goes back to the caller. The caller would not be able to access its variables, which are accessed using Reg.framepointer
.
To fix this, we design the frame pointer as callee-saved
- i.e. the Frame Pointer value needs to be the same at the prologue and epilogue in the callee. We save this frame pointer value in a variable called Dynamic Link, which will hold the address of the frame of its caller.
- There is also a thing called static link, which I will learn about soon to implement Nested Procedures and Nested Procedures and Closures
Implementation: Inside the callee, when you call Stack.allocate(frame)
, this will update Reg.result
. When you store the frame pointer value in the dynamic link, store it at an offset from Reg.result
, NOT Reg.framePointer
.
Saving the Link Register
The same issue that we face with the Reg.framepointer
value being overwritten happens with the Reg.link
value, if multiple procedures are called. We fix it the same way, storing it in the variable savedPC in the prologue. and loading that afterwards.
However, we don’t run into the same issue with the Reg.framePointer
, so we can just make it caller-save.
Passing Arguments
Since the arguments might not fit into Registers, we pass them into Memory.
The arguments can be expressions/procedures themselves, so we need to evaluate these arguments first before calling the procedure.
The caller will run Stack.allocate(parameters)
, and so by convention, the callee will expect the params to be stored in Reg.result
. Remember that the callee will also need to call Stack.allocate(frame)
, which will overwrite Reg.result
, so we store the value into the variable paramPtr that holds the address of the parameters of the procedure.
Final Code for the procedure
Objects
We can actually convert an object into a procedure. Ahh, this is the paradigm with Procedural Programming.
In Scala, the original Scala, the original Object-Oriented Programming code:
Can be converted into a procedure:
The counter made out of closures is going to behave the same as the counter object.
Related
- Tail Call
- First-Class Function / Function Values
- Nested Procedure
- Closure