CPSC 355: Tutorial 4

Control Flow

PhD Student

Fall 2017

Outline

  • More ARMv8 instructions
  • Control flow (if else, while loops)
  • Macro preprocessor

ARMv8

Registers keep track of results and store data:

  • 31 general purpose 64 bit registers x0-x30 (use w0-w30 for the lower 32 bits of a register).
  • 64 bit stack pointer sp (and wsp)
  • 64 bit zero register xzr (and wzr)
  • 64 pit pc (program counter) address of current instruction
  • x0-x7 are used to pass variables to a function and return results
  • x9-x15 are temporary registers
  • x19 - x28 are callee-saved registers
  • x29 is the frame pointer, x30 is the link register (keeps track of return addresses)

Only ever expect x19-x28 to be saved after you call a function, the rest may be trashed

ARMv8

LDR: Load register

LDR     xd, =label            // Load immediate

         xd = address of label;

LDR     xd, [xn]               // Read value at memory

         xd = *xn;  

 

ARMv8

STR: Store register

 

STR     xn, [xd]               // Write value to memory

         *xd =  xn;  

 

ARMv8

CMP: Compares two operands, sets flags depending on the result

cmp     xn, xm

         sets various flags based on the comparison between          xn and xm

 

ARMv8

B.CC: Compares two operands, sets flags depending on the result

b.cc     label

          jumps to the label based on the condition flags                   currently set

Name Meaning
b.eq branch if equal
b.ne branch if not equal
b.gt branch greater than
b.ge branch greater than or equal
b.lt branch less than
b.le branch less than or equal

Use cmp before this instruction to set flags

Example

// File: scanfexample.s
// Author: Joshua Horacsek
// Date: September 27th, 2017
//
// Description: 
// Demonstrates using scanf, and the ldr pseudo-instruction
input_str:       .string "%d"
output_str:      .string "Your integer: %d\n"
                 .balign 4                 // Makes sure the following instruction's address is 
                                           // divisible by 4, i.e. aligned to the word length of
                                           // the machine
                 .global main              // Enusre that the "main" label is visible to the linker
main:
                 stp  x29, x30, [sp, -16]! // Save FP and LR to the stack
                 mov  x29, sp              // Set FP to the stack addr

                 ldr  x0, =input_str       // Set arg1 to the address of input_str
                 ldr  x1, =temp            // set arg2 to the address of temp
                 bl   scanf                // scanf("%d", temp);
                 ldr  x1, =temp            // x1 = temp;
                 ldr  x19, [x1]            // x19 = *x1;

                 ldr  x0, =output_str      // Set arg1 to the address of output_str
                 mov  x1, x19              // Set arg2 to x19
                 bl printf                 // Call printf("Your integer: %d\n", x19)

                 ldp x29, x30, [sp], 16    // Restore the stack
                 ret                       // return to OS

.data
temp:            .word   1

ARMv8 Control Flow

Obviously we need to control the flow of execution in order to make more sophisticated programs

  • For loops (pre and post testing)
  • If construct
  • If-else construct

ARMv8 Loops

Pre-test loops (in C):

int i = 0; 
while(i < 20) {
    printf("i = %d\n", i);
    i++;
}
          mov x19, 0              // Initialize variable
          b loop_test             // branch to the test
loop_top:

          ldr  x0, =printf_string // "i = %d\n"
          mov  x1, x19
          bl   printf             // printf("i = %d", x19)
          
          add  x19, x19, 1        // i++
loop_test:
          cmp  x19, 20            // i < 20?
          b.lt loop_top           // if so, loop
          

Pre-test loops (in assembly):

ARMv8 Loops

Pre-test loops (in C):

int i = 0; 
while(i < 20) {
    printf("i = %d\n", i);
    i++;
}
for(int i = 0; i < 20; i++) {
    printf("i = %d\n", i);
}

For loops are also "pre-test" loops

These loops do exactly the same thing

ARMv8 Loops

Post-test loops (in C):

int i = 0; 
do {
    printf("i = %d\n", i);
    i++;
} while(x <= 20);
          mov x19, 0              // Initialize variable
loop_top:
          ldr  x0, =printf_string // "i = %d\n"
          mov  x1, x19
          bl   printf             // printf("i = %d", x19)
          
          add  x19, x19, 1        // i++
loop_test:
          cmp  x19, 20            // i <= 20?
          b.le loop_top           // if so, loop
          

Post-test loops (in ASM):

This very slight optimization saves an instruction.

ARMv8

The if construct:

if(a > b) {
   result = a - b
}
          cmp  x19, x20            // x19 = a, x20 = b
          b.le next

          subs x21, x19, x20
next:     // more instructions ..

Equivalent ASM:

ARMv8

The if-else construct:

if(a > b) {
   result = a - b
} else {
   result = b - a
}
          cmp  x19, x20            // x19 = a, x20 = b
          b.le else

          subs x21, x19, x20
          b    next
else:
          subs x21, x20, x19
next:     // more instructions ..

Equivalent ASM:

M4 Preprocessor

Macro preprocessor, similar to the pre-processor that C has (anything with a # in C is a macro command)

Invoke it on the command line via:

m4 myfile.asm > myfile.s
gcc myfile.s -o myfile

It allows us to set aliases for registers, via define. For example we can do:

define(my_var_r, x19)

M4 Preprocessor

// File: scanfexample.asm
// Author: Joshua Horacsek
// Date: September 27th, 2017
//
// Description: 
// Demonstrates using scanf, and the ldr pseudo-instruction

// Define aliases to some registers
define(input_r, x19)

input_str:       .string "%d"
output_str:      .string "Your integer: %d\n"
                 .balign 4                 // Makes sure the following instruction's address is 
                                           // divisible by 4, i.e. aligned to the word length of
                                           // the machine
                 .global main              // Enusre that the "main" label is visible to the linker
main:
                 stp  x29, x30, [sp, -16]! // Save FP and LR to the stack
                 mov  x29, sp              // Set FP to the stack addr

                 ldr  x0, =input_str       // Set arg1 to the address of input_str
                 ldr  x1, =temp            // set arg2 to the address of temp
                 bl   scanf                // scanf("%d", temp);
                 ldr  x1, =temp            // x1 = temp;
                 ldr  input_r, [x1]        // input_r = *x1;

                 ldr  x0, =output_str      // Set arg1 to the address of output_str
                 mov  x1, input_r          // Set arg2 to input_r
                 bl printf                 // Call printf("Your integer: %d\n", input_r)

                 ldp x29, x30, [sp], 16    // Restore the stack
                 ret                       // return to OS

.data
temp:            .word   1

ARMv8

A more complicated example:

#include<stdio.h>

int main(int argc, char *argv) {
    int number = 0, exponent = 0, result = 0;

    puts("Enter a number:");
    scanf("%d", &number);

    puts("Enter an exponent:");
    scanf("%d", &exponent);

    if(exponent <= 0) {
        puts("Invalid exponent");
    } else {
        result = number;
        for(int i = 1; i < exponent; i++) {
            result *= number;
        }
        printf("%d^%d = %d\n", number, exponent, result);
    }
}

ARMv8

Assembly version

// File: expexample.asm
// Author: Joshua Horacsek
// Date: September 27th, 2017
//
// Description: 
// Takes a number and an exponent, and returns the result
// of taking the number and raising it to the exponent

define(number_r,   x19)
define(exponent_r, x20)
define(result_r,   x21)
define(i_r,        x22)
.data           
temp:           .word 1

.text
ent_num_str:    .string "Enter a number:"
ent_exp_str:    .string "Enter an exponent:"
inv_exp_str:    .string "Invalid exponent"
output_str:     .string "%ld^%ld=%ld\n"
scan_str:       .string "%ld"

                .balign 4
                .global main
main:

                stp  x29, x30, [sp, -16]! // Save FP and LR to the stack
                mov  x29, sp              // Set FP to the stack addr

                mov  number_r, 0          // Initialize registers to known values
                mov  exponent_r, 0
                mov  result_r, 0

ARMv8

Continued...


                ldr  x0, =ent_num_str     // Set arg 1 as ent_num_str
                bl   puts                 // puts(x0);

                ldr  x0, =scan_str        // Set arg 1 to addr of scan str
                ldr  x1, =temp            // Set arg 2 to addr of temp
                bl   scanf                // scanf("%d", temp);
                
                ldr  x1, =temp            
                ldr  number_r, [x1]       // number_r = *temp
                
                ldr  x0, =ent_exp_str     // Set arg 1 to addr of ent_exp_str
                bl   puts                 // puts(ent_exp_str);                

                ldr  x0, =scan_str        // Set arg 1 to addr of scan str
                ldr  x1, =temp            // Set arg 2 to addr of temp
                bl   scanf                // scanf("%d", temp);
                
                ldr  x1, =temp
                ldr  exponent_r, [x1]     // exponent_r = *temp
                
                cmp  exponent_r, xzr      // compare the exponent to the zero reg
                b.gt else                 // if exponent_r <= 0, continue

                ldr  x0, =inv_exp_str      
                bl   puts                 // puts(inv_exp_str)
                b    exit                 // Skip to the end
else: 
                mov  i_r, 1               // i_r = 1
                mov  result_r, number_r   // result_r = number_r


ARMv8

Continued...

                b loop_test                        // Do the loop test before all else
loop_top:
                mul result_r, result_r, number_r   // result_r *= number_r
                add i_r, i_r, 1                    // i_r++

loop_test:      cmp i_r, exponent_r                // compare i_r, exponent_r
                b.lt loop_top                      // if i_r < exponent_r, continue looping the top of the loop
                
                ldr x0, =output_str                // Arg 1 = addr of output_str
                mov x1, number_r                   // Arg 2 = number_r
                mov x2, exponent_r                 // Arg 3 = exponent_r
                mov x3, result_r                   // Arg 4 = result_r
                bl  printf                         

exit:           ldp x29, x30, [sp], 16             // Restore the stack
                ret                                // return to OS

To compile

m4 expexample.asm > expexample.s
gcc expexample.s -o expexample

Next Day

Work period, bring your questions.