EC

  • Edwin Chan
  • CV
  • CPSC 233
  • CPSC 331
  • CPSC 355
  • CPSC 581
  • Origami
  • Random

Tutorial 5 (Oct 31): Open subroutines, simple closed subroutines

Downloads
[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

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

define(comment)

comment(minimum(1 = output register, 2 = input register, 3 = input register))
define(`minimum',
`mov $1, $3
cmp $2, $3
b.gt done
mov $1, $2
done:')


fmt1: .string "Between %d and %d, %d is smaller.\n"

   .balign 4                // word align instructions
   .global main             // make main() global to call from OS
main:
   stp x29, x30, [sp, -16]! // store frame record, allocate stack memory

   mov x29, sp              // update FP = SP

   mov x19, 3               // make up a number
   mov x20, 5               // make up another number

   adrp x0, fmt1            // 1st arg: print format
   add x0, x0, :lo12:fmt1   // load lower 12 bits of print format
   mov x1, x19              // 2nd arg: 1st number
   mov x2, x20              // 3rd arg: 2nd number

   // start #minimum#
   minimum(x3, x19, x20)    // 4th arg: x3 = min(x19, x20)
   // end #minimum#

   bl printf                    // Print result

   mov w0, 0
   ldp x29, x30, [sp], -16
   ret

 







//Definitions and comments are removed
//by M4, once they have been processed





fmt1: .string "Between %d and %d, %d is smaller.\n"

   .balign 4                
   .global main             
main:
   stp x29, x30, [sp, -16]!

   mov x29, sp  

   mov x19, 3             
   mov x20, 5       

   adrp x0, fmt1         
   add x0, x0, :lo12:fmt1   
   mov x1, x19            
   mov x2, x20        

   // start #minimum#
   mov x3, x20
   cmp x19, x20
   b.gt done
   mov x3, x19
   done:
   // end #minimum#

   bl printf                

   mov w0, 0
   ldp x29, x30, [sp], -16
   ret

Closed Subroutines

* 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:

  1. newSphere(): return a struct by value
  2. move(): pass a struct as a pointer
  3. expand(): pass a struct as a pointer
  4. equal(): pass two structs as pointers
  5. printSphere(): pass a struct as a pointer
  6. 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


fmtinput: .string "The sum of %d, %d, %d, %d, %d, %d, and %d is: "
fmtsum: .string "%d\n"

.balign 4 // word align all instructions

 

 

 

 


//-SUM()----------------------------------------------------------
sum:
   stp x29, x30, [sp, -16]!
   mov x29, sp

   add w0, w0, w1 // input parameters are stored in w0-w7
   add w0, w0, w2 // but sum() only uses w0-w6
   add w0, w0, w3
   add w0, w0, w4
   add w0, w0, w5
   add w0, w0, w6

   return: ldp x29, x30, [sp], 16
   ret

 

//-MAIN()------------------------------------------------------------

.global main      // make main() global
                  // OS calls main(), so only main()
                  // needs to be global

main:
   stp x29, x30, [sp, -16]!

   mov x29, sp

   // Make up seven numbers as parameters
   mov w19, 10
   mov w20, 20
   mov w21, 30
   mov w22, 40
   mov w23, 50
   mov w24, 60
   mov w25, 70

   // Print parameters
   adrp x0, fmtinput // 1st arg to printf: string format
   add x0, x0, :lo12:fmtinput
   mov w1, w19       // 2nd arg to printf: first number
   mov w2, w20       // 3rd arg to printf: second number
   mov w3, w21       // 4th arg to printf: third number
   mov w4, w22       // 5th arg to printf: fourth number
   mov w5, w23       // 6th arg to printf: fifth number
   mov w6, w24       // 7th arg to printf: sixth number
   mov w7, w25       // 8th arg to printf: seventh number
   bl printf

   // Sum up the numbers, result is returned in w0.
   mov w0, w19       // 1st arg to sum: first number
   mov w1, w20       // 2nd arg to sum: second number
   mov w2, w21       // 3rd arg to sum: third number
   mov w3, w22       // 4th arg to sum: fourth number
   mov w4, w23       // 5th arg to sum: fifth number
   mov w5, w24       // 6th arg to sum: sixth number
   mov w6, w25       // 7th arg to sum: seventh number
   bl sum

   mov w1, w0       // Save the sum result before we 
                    // overwrite x0 with print string format

   // Setup print format for result
   adrp x0, fmtsum
   add x0, x0, :lo12:fmtsum
   bl printf

done:
   mov w0, 0

   ldp x29, x30, [sp], 16
   ret

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.

  • Edwin Chan
  • CV
  • CPSC 233
  • CPSC 331
  • CPSC 355
  • CPSC 581
  • Origami
  • Random