Introduction

Hello World

Printing variables

User input

Testing printf

Mathematical operations

Functions

Arrays

Loops

The stack

Projects:

Estimating sin(x)

Calculating cube roots

Sieve of Eratosthenes

Quadratic Solver

Arrays

The first example, below, declares a fixed array of five 64 bit numbers. The .quad specifier indicates 64 bit and the values are specified in the declaration.

        
        .data
        array:
            .quad 10, 20, 30, 40, 50    // Use .quad to specify 64 bit values
        
    

There are two implementations of the process to extract a value at an index. The first is more laborious but the logic is clearer.

        
        // main code
        ldr x0, =output
        ldr x1, =array      // Base address of array
        mov x2, #2          // Element pointer, 30
        lsl x2, x2, #3      // Multiply pointer in x2 by 8 to get the number of addresses to offset
        add x1, x1, x2      // Add the offset to the base address
        ldr x1, [x1]        // Get value at address
        bl printf
        
    

Ignoring the ldr x0, =output as that is setting up the string to be passed to printf. X1 is loaded with the base address of the array. This is the address of element 0 in the array, 10 in this case. Because the array was declared to be of type .quad, each location is eight bytes wide. That is, if the base address was 0x1000, the element 20 would be stored at 0x1008, the element 30 would be stored at 0x1010.
As there are five elements in the array and each element requires eight bytes, the entire array will be in 40 contiguous memory addresses.
This means that we can find the exact memory address of an element by knowing its index in the array and multiplying that index by eight then adding that value to the base address to find the address of the required element.

In the example above the base address is loaded into x1. X2 is then loaded with the immediate value, #2, being the index of the element we want to extract. The next instruction, lsl x2, x2, #3, takes the value in x2, performs a "Logical Shift Left" by the immediate value provided, 3 in this case, and stores the result back in x2. For example, x2 contains 0000 0010. The lsl instruction will shift the bits to the left by three places, appending a zero to the least significant bit each time. The result in x2 will be 0001 0000 or in hex, 0x10

The next line adds the offset in x2 to the base address in x1 to get the address of required element.
In this case: 0x1000 + 0x0010 = 0x1010 which was established earlier.

The final line, ldr x1, [x1] loads x1 with the value stored at the address currently in x1.

The more efficient way of addressing the nth element is using this code:

    
    // main code
    ldr x0, =output
    ldr x1, =array      // Base address of array
    mov x2, #2          // Element of interest, 30
    ldr x1, [x1, x2, LSL #3 ]       // Get value at address
    bl printf
    

The code is the same as the above with one exception, the line ldr x1, [x1, x2, LSL #3]. This mode of addressing is called Register Offset Addressing. The register, x1, will be loaded with the address in x1 added to the value in x2 once that has been logically left shifted by the immediate value.
Conceptually, it may look like this: x1 <- x1 + (x2, LSL 3 places).
The result is the same in both cases.

        
            // arrays1.s

    .global main
    .extern printf

    .data
    array:
        .quad 10, 20, 30, 40, 50    // Use .quad to specify 64 bit values
    output:
        .asciz "Value = %d\n"

    .text
    main:
        // prolog
        stp x29, x30, [sp, -16]!
        mov x29, sp

        // main code
        ldr x0, =output
        ldr x1, =array      // Base address of array
        mov x2, #2          // Element pointer, 30
        lsl x2, x2, #3      // Multiply x2 by 8 to get the number of addresses to offset
        add x1, x1, x2      // Add the offset to the base address
        ldr x1, [x1]        // Get value at address
        bl printf

        // Cleanup
        mov x0, #0
        ldp x29, x30, [sp], 16
        RET
        
    
        
            // arrays1.s

        .global main
        .extern printf

        .data

        array:
            .quad 10, 20, 30, 40, 50    // Use .quad to specify 64 bit values
        output:
            .asciz "Value = %d\n"

        .text
        main:
            // prolog
            stp x29, x30, [sp, -16]!
            mov x29, sp

            // main code
            ldr x0, =output
            ldr x1, =array      // Base address of array
            mov x2, #2          // Element of interest, 30
            ldr x1, [x1, x2, LSL #3 ]       // Get value at address
            bl printf

            // Cleanup
            mov x0, #0
            ldp x29, x30, [sp], 16
            RET
        
    

Iterating over arrays

To iterate over an array the easiest way is to use an integer pointer that points to an index in the array, then use either of the indexing methods shown above to generate the base address offset for each element in the array. The first example looks like this:

        
            // arrays2.s

    .global main
    .extern printf

    .data
    array:
        .quad 10, 20, 30, 40, 50

    output:
        .asciz "Value is %d\n"

    .text
    main:
        // Prolog
        stp x29, x30, [sp, -16]!
        mov x29, sp

        // Main code
        ldr x19, =array     // Base addr of array in x19
        mov x20, #0         // Array pointer
        mov x21, #5         // Array length

    loop:
        ldr x0, =output             // Addr of output str
        lsl x22, x20, #3            // Multiply pointer by 8 to get offset stored in x22
        add x22, x19, x22           // Add offset to base address
        ldr x1, [x22]
        bl printf                   // Print array element

        add x20, x20, #1    // Increment array pointer
        cmp x20, x21        // Compare to array length
        blt loop            // If not end of array, loop back

        // Cleanup
        mov x0, #0
        ldp x29, x30, [sp], 16
        RET
        
    

This example does not use offset register addressing. Instead, the addressing is done using registers x19 - x22 as shown below

RegisterPurpose
x19Base address of array
x20Array pointer
x21Array length
x22Offset address

The registers x19, x20, & x21 can be set up before the loop with x22 being updated each iteration.
Finally, x1 is loaded with the value stored at x22.

The next example performs the same iteration, but it uses offset register addressing. This improves the efficiency of the code.

        
            // arrays2.s

.global main
.extern printf

.data
array:
    .quad 10, 20, 30, 40, 50

output:
    .asciz "Value is %d\n"

.text
main:
    // Prolog
    stp x29, x30, [sp, -16]!
    mov x29, sp

    // Main code
    ldr x19, =array     // Base addr of array in x19
    mov x20, #0         // Array pointer
    mov x21, #5         // Array length

loop:
    ldr x0, =output             // Addr of output str
    ldr x1, [x19, x20, LSL #3]  // Addr of next array element
    bl printf                   // Print array element

    add x20, x20, #1    // Increment array pointer
    cmp x20, x21        // Compare to array length
    blt loop            // If not end of array, loop back

    // Cleanup
    mov x0, #0
    ldp x29, x30, [sp], 16
    RET
        
    

Iteration with storage and retrieval

This next example uses two loops. The first is to step through the array and assign values to each index. The second loop steps through the arrays and prints the values at each index as shown above.

This next example will also show how to initialise an empty array. In this case, values are not assigned using code like this:

        
        array:
            .quad 10, 20, 30, 40, 50
        
    

Rather, space is allocated in memory by using the .skip command.
As the plan is still to create an array of five values, each of which is an eight byte, long decimal, the array will require 40 bytes of memory. This is allocated with this code:

        
         array:
            .skip 40
        
    

Here is the complete code with manual offset calculation:

        
        // arrays3.s
// This app will declare an array that is 40 bytes long.
// Then it will loop through and set them to 10, 20, 30, 40, 50.
// Then it will loop through and print them out.

.global main
.extern printf

.data
array:
	.skip 40
output:
	.asciz "Element %ld contains %ld.\n"
.text
main:
	// prolog
	stp x29, x30, [sp, -16]!
	mov x29, sp

	// main code
	ldr x19, =array		// Base addr of array
	mov x20, #0			// Pointer
	mov x21, #5			// Number of elements
	mov x22, #10		// First value to be stored

load_loop:
	ldr x0, =array		// Base addr of array
	lsl x1, x20, #3		// Offset for currrent pointer value
	add x0, x0, x1		// x0 now has addr of next array element

	str x22, [x0]		// Str the value in x22 into the addr at x0

	add x20, x20, #1	// Inc the loop counter
	add x22, x22, #10	// Inc the value to be stored

	cmp x20, x21		// Check loop counter = array length
	beq print_setup		// Break to the print routine
	b load_loop			// Otherwise repeat

print_setup:
	mov x20, #0			// Reset pointer

print_loop:
	ldr x0, =output		// Load output addr
	mov x1, x20			// Load pointer value for printing
	ldr x2, =array		// Load array base addr
	lsl x3, x20, #3		// Calc pointer offset
	add x2, x2, x3		// Add offset to base addr
	ldr x2, [x2]		// Load value at addr

	bl printf

	add x20, x20, #1	// Incr array pointer
	cmp x20, x21		// Check pointer = array length
	blt print_loop		// If not repeat loop

	// cleanup
	mov x0, #0
	ldp x29, x30, [sp], 16
	RET