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

n! = n\times (n-1) \times (n-2) \times \cdots \times 2 \times 1
n!=n×(n1)×(n2)××2×1n! = n\times (n-1) \times (n-2) \times \cdots \times 2 \times 1
n! = n\times (n-1)!
n!=n×(n1)!n! = n\times (n-1)!
0! = 1
0!=10! = 1

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,717