teaching machines

CS 352 Lecture 28 – Functions

November 14, 2016 by . Filed under cs352, fall 2016, lectures.

Dear students,

Today we look at how functional abstractions are built atop rudimentary assembly instructions. The magic to make functions happen is really just two ideas:

The first of these is necessary because we branch to functions. Branching changes the program counter. When a function finishes, we have to resume execution where we left off. The bl instruction preserves this “where we left off”. Essentially, bl myfunc executes these two instructions:

mov lr, pc + 8    // pc + 0
b myfunc          // pc + 4
next instruction  // pc + 8

Note that there’s only one lr register. If our function calls a function, it will need to maintain its own bookmark in lr, which will clobber the original. It’s a function’s job to not clobber the data of the caller. So, we must safeguard a copy of the caller’s lr on the stack:

sub sp, sp, #4
str lr, [sp]

There’s an abbreviation for this:

push {lr}

The calling convention—the ARM Architecture Procedure Call Standard (AAPCS)—tells us how to send data to and receive data from a function:

Registers r0 through r3, the status register, and any stack memory below a function’s stack pointer are likely to be obliterated by a function call. If these contain precious data, get them in a safe place: either in registers r4 through r11 or on the stack. We call vulnerable registers caller-save. On other hand, if a function wants to use registers r4 through r11, r13, or r15, it must preserve a copy of these values and restore them just before it finishes.

Let’s write a few programs that involve functions but no stack memory:

We try (through the compiler) as hard as we can to use registers for our calculations, but we cannot always avoid memory. We will look at how local variables are allocated, manipulated, and released through a few more examples. These will be contrived, because small problems easily fit into the available registers.

See you next class!

Sincerely,

P.S. Here’s the code we wrote together…

caseconverter.s

.text
.global main

main:
  push {lr}

  ldr r0, =letter
  ldr r0, [r0]
  bl toupper

  mov r1, r0
  ldr r0, =message
  bl printf

  pop {lr}
  mov pc, lr

tolower:
  // arg0 is going to be in r0
  // return value in r0
  // if we alter lr, restore it before returning
  orr r0, r0, #32
  mov pc, lr

toupper:
  mvn r1, #32
  and r0, r0, r1
  mov pc, lr

.data
letter:
  .byte 'x'

message:
  .asciz "Today's class is brought to you by the letter %c!\n"

collatz.s

.text
.global main

main:
  push {lr}

  // n = ...
  // while n != 1
  //   print n
  //   n = collatzify n

  //ldr r1, [r1, #4]   // r1 = argv[1]
  //bl atoi
  //
  //mov r1, r0
  //ldr r0, =message
  //bl printf

  ldr r0, =n

  ldr r0, [r0]
  collatzify
  mov r1, r0
  bl printf

  
  collatzify
  mov r1, r0
  bl printf

  pop {lr}
  mov pc, lr

collatzify:
  and r1, r0, #1
  cmp r1, #1
  addeq r0, r0, r0, lsl #1   // r0 = r0 + r0 * 2
  addeq r0, r0, #1
  lsrne r0, r0, #1
  mov pc, lr

.data
n:
  .word 4
message:
  .asciz "%d\n"