Tutorial 5 (Oct 31): Open subroutines, simple closed subroutines
[1a.asm][848B] Open Subroutine Ex 1: minimum(int num1, int num2)
[1b.asm][700B] Open Subroutine Ex 2: printPoint(int x, int y, int z)
[2a.s] [1458B] Closed Subroutine Ex 1: isEqual(int num1, int num2)
[2b.s] [1881B] Closed Subroutine Ex 2: sum(int num1, int num2, int num3, int num4, int num5, int num6, int num7)
Open subroutines are usually implemented with macros like m4. Macros are best for simplifying long and repetitive processes, and can be thought of as a "find and replace". Given a macro definition, you can replace many lines with just a word or two, making your code much more organized. For example, if you find yourself always needing to print the coordinates of a point (CPSC 233 students may remember this), you can use a macro. We can represent variables in macros with $1, $2, $3, so on and so forth. A note about macros, sometimes you might mention the macro name in your comments, which will lead to that word being replaced with lines of code as well. To prevent this, simply surround the matched word with #s, like #macroName#.
Example: minimum(int num1, int num2)
Note how M4 finds "minimum" in the code, then simply replaces it with what's in the macro definition.
|1a.asm, with macros||1a.s, after processing with M4|
* This tutorial only discussed simple subroutines, which accept parameters through w0-27 and return a single value only. Next tutorial we will cover examples where we pass in stack variables by reference, through pointers. Next week, Cory will cover examples of using pointers to structs as arguments, and returning structs by value.
Closed subroutines do not rely on macros, and there's more to know about them than open subroutines. Your 4th assignment has a number of closed subroutines, of varying types:
- newSphere(): return a struct by value
- move(): pass a struct as a pointer
- expand(): pass a struct as a pointer
- equal(): pass two structs as pointers
- printSphere(): pass a struct as a pointer
- main(): yup, main() is a subroutine too
It may come as a surprise to some people, but main() is just another subroutine. The difference is that a program will start from the main() function by default. When you look at the example below, pay attention to the similarities between main() and sum(). They both need to allocate memory and save the frame record with STP, and then update the fp to equal sp. They also both need to restore the frame record and deallocate memory using LDP, and then call ret to return. Finally, both main() and sum() store a return value in w0, which is the default return value for subroutines.
To pass in parameters to a subroutine, we generally use w0-w7. It is possible to return multiple values as well by storing the return values in w0-w7. The reason is simple: there is only one set of registers, and they are considered global. If a subroutine stores values in w0-w7, they will still be available to the calling code once the subroutine returns. Note that this is not true for all registers, and that w0-w7 are specificly used for passing parameters to/from subroutines.
|2b.s, sum() subroutine||2b.s, main() subroutine|
Both main() and sum() store x29 and x30 with STP, meaning both of them store a frame record. If a subroutine didn't store the frame record, and then called another subroutine such as printf(), then then FP and LR will be lost. When it comes time to return control at the end of the subroutine, the program will encounter a segmentation fault.
In main(), also note how sum() and printf() are called in very similar ways. This is because printf() is another subroutine, except it is part of the C library. This means you have been using subroutines all along, from writing the main() subroutine to calling the printf() and rand() subroutines.