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
| Register | Purpose |
|---|---|
| x19 | Base address of array |
| x20 | Array pointer |
| x21 | Array length |
| x22 | Offset 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