asm_book/section_1/structs/practice.S
2023-03-04 15:10:27 -06:00

222 lines
No EOL
6.8 KiB
ArmAsm

#include "apple-linux-convergence.S"
.p2align 2
.text
GLABEL main
/* This practice program demonstrates manipulation of an array of
structs. The code is Apple Silicon and Linux compatible.
*/
/* Here is the struct:
struct S { // offset width padding running total
char * first_name; // 0 8 0 8
char * last_name; // 8 8 0 16
int age; // 16 4 4 24
double gpa; // 24 8 0 32
};
I have found that the .struct method I taught in class works only
on Linux. Therefore, I am using the .equ method to give symbolic
names to the offsets in the struct.
*/
.equ s_first_name, 0
.equ s_last_name, s_first_name + 8
.equ s_age, s_last_name + 8
.equ s_gpa, s_age + 8
.equ s_sizeof, s_gpa + 8
.equ NUM_ELEMENTS, 20
b_ptr .req x20 // base address of the array
t_ptr .req x21 // a temporary pointer used for looping
counter .req x22 // will be a loop counter
MAIN
START_PROC
PUSH_P x29, x30
PUSH_P b_ptr, t_ptr
mov x29, sp
// If b_ptr is not null at the end of the program it means
// that it contains the address of the array which must be
// freed.
mov b_ptr, xzr
// Notice I've broken down the program into its logical steps.
// In this way, each function does one thing and one thing
// only. In assembly language, you really don't want overly
// long functions... they get hard to understand and debug.
bl AllocArray
cbnz x0, 1f
LLD_ADDR x0, mfail
CRT perror
mov w1, 1 // Signal an error occurred.
b 99f
1: mov b_ptr, x0
bl InitArray
bl PrintArray
// When we get here, we're on our way out. If we have
// executed the malloc() sucessfully, the register b_ptr
// will be non-zero. Only call free() in this case.
90: cbz b_ptr, 99f
mov x0, b_ptr
CRT free
// Notice the setting of w0 to zero is held apart from
// the final exit code. I did this so that if malloc()
// fails, a non-zero value can put returned in w0. If
// we get to this next line, we're on a path of normal
// exit.
mov w0, wzr
99: POP_P b_ptr, t_ptr
POP_P x29, x30
ret
END_PROC
/* This function will loop through the entire array printing every
element (which are the same over and over). This time however we
will use an actual loop counter so that we can print the array
index in front of each line.
*/
PrintArray:
START_PROC
PUSH_P x29, x30
PUSH_R counter
mov x29, sp
mov t_ptr, b_ptr
mov counter, -1 // We start at the BOTTOM
b 10f
1: LLD_ADDR x0, fmt
mov x1, counter
ldr x2, [t_ptr, s_first_name]
ldr x3, [t_ptr, s_last_name]
ldr w4, [t_ptr, s_age]
ldr d0, [t_ptr, s_gpa]
#if defined(__APPLE__)
// Let the pain begin. Notice the order is exactly
// right to left relative to the placeholders in the
// template "fmt".
PUSH_R d0
PUSH_P x3, x4
PUSH_P x1, x2
CRT printf
add sp, sp, 48
#else
bl printf
#endif
// Make sure this increment of t_ptr is skipped when
// entering the loop from the branch to the bottom.
add t_ptr, t_ptr, s_sizeof
10: add counter, counter, 1
cmp counter, NUM_ELEMENTS
bne 1b
POP_R counter
POP_P x29, x30
ret
END_PROC
/* This function initializes all of the members of the array (whose
base address is in b_ptr) with identical values. The first and
last names are dummy values. So too are the age and gpa. Who in
class will be the first to tell the rest of us what the pun here
is and the meaning behind the values.
There will be no loop counter here. Instead I'll use the pointer
advancing through the array as the means of stopping the loop.
Loop counters are overrated.
*/
InitArray:
START_PROC
PUSH_P x29, x30
mov x29, sp
// t_ptr is initialized to the beginning of the array and will
// be used to access each element by marching forward in the
// loop. The loop will stop when t_ptr equals the address
// just past the end of the array. This "end" value will be
// kept in x7 which is safe only as long as this function makes
// no function calls of its own.
mov t_ptr, b_ptr
mov x0, s_sizeof
mov x1, NUM_ELEMENTS
mul x0, x0, x1
add x7, x0, b_ptr
// Buffer up the values that will be used to initilize every
// array element. This can be done in scratch registers only
// so long as this function makes no function calls of its
// own (in the loop).
LLD_ADDR x6, dummy_fname
LLD_ADDR x5, dummy_lname
LLD_ADDR x4, d_age
ldr w4, [x4]
LLD_ADDR x3, d_gpa
ldr d0, [x3]
1: cmp x7, t_ptr
beq 99f
str x6, [t_ptr, s_first_name]
str x5, [t_ptr, s_last_name]
str w4, [t_ptr, s_age]
str d0, [t_ptr, s_gpa]
add t_ptr, t_ptr, s_sizeof
b 1b
99: POP_P x29, x30
ret
END_PROC
AllocArray:
START_PROC
PUSH_P x29, x30
mov x29, sp
// Use malloc() to allocate the entire array. The total size
// will be the size of one element times the number of elements.
mov x0, s_sizeof
mov x1, NUM_ELEMENTS
mul x0, x0, x1
CRT malloc
POP_P x29, x30
ret
END_PROC
.data
// The AASCIZ macro ensures alignment is correct
// for each string. I found one case on Apple Silicon
// where this was necessary - doesn't hurt to align all
// the time.
AASCIZ mfail, "malloc() failed"
AASCIZ dummy_fname, "Mortimer"
AASCIZ dummy_lname, "Snerd"
AASCIZ fmt, "[%2d] %s %s %d %f\n"
.p2align 2
d_age: .word 85
d_gpa: .double 1.938
.end