CPSC 355: Tutorial 10
Subroutines
PhD Student
Fall 2017
Outline
- Assignment 4 - details and what's expected of you
- Subroutines - creating and calling reusable chunks of code
Assignment 4
#include <stdio.h>
#define FALSE 0
#define TRUE 1
struct point {
int x,y;
};
struct dimension {
int width, length;
};
struct pyramid {
struct point origin;
struct dimension base;
int height;
int volume;
};
struct pyramid newPyramid(){
struct pyramid p;
p.origin.x = 0;
p.origin.y = 0;
p.base.width = 2;
p.base.length = 2;
p.height = 3;
p.volume = (p.base.width * p.base.length * p.height)/3.;
return p;
}
Assignment 4
void move(struct pyramid *p, int deltaX, int deltaY)
{
p->origin.x += deltaX;
p->origin.y += deltaY;
}
void scale(struct pyramid *p, int factor)
{
p->base.width *= factor;
p->base.length *= factor;
p->height *= factor;
p->volume *= (p->base.width * p->base.length * p->height)/3;
}
void printPyramid(char *name, struct pyramid *p)
{
printf("Pyramid %s origin: (%d, %d)\n", name, p->origin.x, p->origin.y);
printf("\tBase width=%d Base length = %d\n", p->base.width, p->base.length);
printf("\tHeight = %d\n", p->height);
printf("\tVolume = %d\n", p->volume);
}
int equalSize(struct pyramid *p1, struct pyramid *p2)
{
int result = FALSE;
if(p1->base.width == p2->base.width) {
if(p1->base.length == p2->base.length) {
if(p1->height == p2->height) {
result = TRUE;
}
}
}
return result;
}
Assignment 4
int main()
{
struct pyramid first, second;
first = newPyramid();
second = newPyramid();
printf("Initial pyramid values:\n");
printPyramid("first", &first);
printPyramid("second", &second);
if(equalSize(&first, &second)){
move(&first, -5, 7);
scale(&second, 3);
}
printf("\nChanged pyramid values:\n");
printPyramid("first", &first);
printPyramid("second", &second);
}
Assignment 4
- Subroutines - write once, use everywhere
- Structs - we'll go over these in C first, then see how to use them in ASM
- How do we return structs?
Subroutines
#include <stdio.h>
void print_int(int value) {
printf("Our value is %d", value);
}
int main(int argc, char *argv[]) {
print_int(argc);
print_int(42);
}
We want to write reusable code.
In this course, there are two types of subroutines
Open (inline)
Closed
Subroutines
#include <stdio.h>
void print_int(int value) {
printf("Our value is %d", value);
}
int main(int argc, char *argv[]) {
print_int(argc);
print_int(42);
}
Let's attempt to write a subroutine (in a naive way)
_fmt_pint: .string "Our value is %d\n"
print_int:
mov x1, x0 // we know our first
// argument is in x0
ldr x0, =_fmt_pint
bl printf
ret
main:
stp lr, fp, [sp, -16]!
mov fp, sp
// x0 by default contains argc
b print_int
mov w0, 42
b print_int
ldp lr, fp, [sp], 16
ret
A reasonable first try might be to write it like this
What's the behaviour of this? How does print_int know where to return to?
Subroutines
Recall what bl does:
Puts the address of the next instruction into $lr, then jumps to the label
Recall what ret does:
Jumps to $lr, it's essentially a synonym for b lr
bl printf stomps the old value of $lr, so we don't know where to return to
Subroutines
#include <stdio.h>
void print_int(int value) {
printf("Our value is %d", value);
}
int main(int argc, char *argv[]) {
print_int(argc);
print_int(42);
}
That wasn't correct, let's try again
_fmt_pint: .string "Our value is %d\n"
print_int:
mov x1, x0 // we know our first
// argument is in x0
ldr x0, =_fmt_pint
bl printf
ret
main:
stp lr, fp, [sp, -16]!
mov fp, sp
// x0 by default contains argc
bl print_int
mov w0, 42
bl print_int
ldp lr, fp, [sp], 16
ret
Let's keep track of return addresses...
What's the behaviour of this? What does the bl printf do?
Subroutines
_fmt_pint: .string "Our value is %d\n"
print_int:
mov x1, x0 // we know our first
// argument is in x0
ldr x0, =_fmt_pint
bl printf
ret
main:
stp lr, fp, [sp, -16]!
mov fp, sp
// x0 by default contains argc
bl print_int
mov w0, 42
bl print_int
ldp lr, fp, [sp], 16
ret
lr = return address of main
lr = &mov w0, 42
lr = &ret
ret keeps branching to ret = infinite loop
Subroutines
#include <stdio.h>
void print_int(int value) {
printf("Our value is %d", value);
}
int main(int argc, char *argv[]) {
print_int(argc);
print_int(42);
}
Final, correct solution
_fmt_pint: .string "Our value is %d\n"
print_int:
// store lr and fp on the stack
stp lr, fp, [sp, -16]!
mov fp, sp
mov x1, x0 // we know our first
// argument is in x0
ldr x0, =_fmt_pint
bl printf
// restore lr, fp
ldp lr, fp, [sp], 16
ret
main:
stp lr, fp, [sp, -16]!
mov fp, sp
// x0 by default contains argc
bl print_int
mov w0, 42
bl print_int
ldp lr, fp, [sp], 16
ret
Subroutines
Closed subroutines:
- Use x0-x8 for input arguments
- x0-x8 for function output (what if we need to output a struct?)
- Don't use x19-x28 if you can avoid them, if you do use them, you need to explicitly keep track of them (more on this later)
- x9-x15 can be used in computations
- Generally closed subroutines appear only once in code, as opposed to inline subroutines (more on this later)
Subroutines
#include <stdio.h>
void factorial(unsigned int n) {
unsigned int ret = 1;
if(n > 0) {
ret = n*factorial(n-1);
}
return ret;
}
int main(int argc, char *argv[]) {
register int value = factorial(6);
printf("6! = %d\n", value);
return 0;
}
Example 2
with
Subroutines
ret_o = 16
x0_save = 16 + 4
factorial_alloc = -(16 + 4 + 4) & -16
factorial_dealloc = -factorial_alloc
fmt: .string "6! = %d\n"
fp .req x29
lr .req x30
.balign 4
factorial:
// Allocate variables on the stack, save lr, fp
stp lr, fp, [sp, factorial_alloc]!
mov fp, sp // set fp
// restore lr, fp
ldp lr, fp, [sp], factorial_dealloc
ret
.global main
main:
stp lr, fp, [sp, -16]!
mov fp, sp
mov w0, 6
bl factorial
mov w1, w0
ldr x0, =fmt
bl printf
ldp lr, fp, [sp], 16
ret
void factorial(unsigned int n) {
unsigned int ret = 1;
if(n > 0) {
ret = n*factorial(n-1);
}
return ret;
}
Subroutines
ret_o = 16
x0_save = 16 + 4
factorial_alloc = -(16 + 4 + 4) & -16
factorial_dealloc = -factorial_alloc
fmt: .string "6! = %d\n"
fp .req x29
lr .req x30
.balign 4
factorial:
// Allocate variables on the stack, save lr, fp
stp lr, fp, [sp, factorial_alloc]!
mov fp, sp // set fp
mov w9, 1
str w9, [fp, ret_o] // set ret = 1
cmp w0, 0 // if n = 0, skip to end
b.eq exit_factorial
str w0, [fp, x0_save]// save x0
sub w0, w0, 1 // set n-1 as first arg
bl factorial // w0 = factorial(n-1);
ldr w1, [fp, x0_save]// w1 = n
mul w0, w0, w1 // w0 = n*(n-1)!
str w0, [fp, ret_o] // ret = n!
exit_factorial:
ldr w0, [fp, ret_o] // load ret into w0
// restore lr, fp
ldp lr, fp, [sp], factorial_dealloc
ret
.global main
main:
stp lr, fp, [sp, -16]!
mov fp, sp
mov w0, 6
bl factorial
mov w1, w0
ldr x0, =fmt
bl printf
ldp lr, fp, [sp], 16
ret
Inline code
comment(cube(1=input register, 2=output_register))
define(cube, `mul $2, $1, $1
mul $2, $1, $2')
main:
stp lr, fp, [sp, 16]!
mov fp, sp
mov x0, 2
cube(x0, x1)
//....
main:
stp lr, fp, [sp, 16]!
mov fp, sp
mov x0, 2
mul x1, x0, x0
mul x1, x0, x1
// ...
Via m4 preprocessor
Next Day
Structs, pointers as arguments, returning structs.
CPSC 355: Tutorial 10
By Joshua Horacsek
CPSC 355: Tutorial 10
- 1,692