.global main .align 2 /* Walkies - a silly animation using four characters on the Linux console. This program demonstrates low level IO (write()) and is written in the form of an infinite loop (i.e. ^C or kill will be required to halt the program). The approach used here avoids use of two for loops, one for moving to the right and one for moving to the left. Rather, we notice that the only difference between the two for loops would be that one increments and the other decrements. Otherwise, they'd be the same. Instead of the two for loops we'll use a toggle called delta. It will alternate between 1 and -1 when counter reaches MAX_COLUMN (on the way left to right / transition from 1 to -1) or 0 on the way back (-1 to 1) */ .equ MAX_COLUMN, 60 // These are equivalent to .equ NUM_CHARS, 4 // #define in C and C++ counter .req w20 // These also but these are for delta .req w21 // use with registers .section .rodata // In essence: const CHARS: .asciz "|/_\\" TRM: .asciz " \r" // The space is important. usec: .int 75000 // 0.075 seconds delay AT LEAST .text main: stp x29, x30, [sp, -16]! stp counter, delta, [sp, -16]! // counter will move up and down between MAX_COLUMN and 0 // by toggling delta from 1 to -1 and back. This is far // simpler than creating dual for loops which one might do // in a naive implementation. mov counter, wzr mov delta, 1 mov w0, wzr 0: bl Pad bl Emit bl Terminator // Having completed output for this animation cycle, adjust // counter toggling delta if necessary. add counter, counter, delta mov w0, MAX_COLUMN cmp w0, counter bne 1f neg delta, delta b 2f 1: cbnz counter, 2f neg delta, delta // Cause the delay between animation cycles. 2: ldr x0, =usec ldr w0, [x0] bl usleep b 0b // Because of the infinite loop, this code will not // be reached but is here out of habit and good practices. ldp counter, delta, [sp], 16 ldp x29, x30, [sp], 16 mov w0, wzr ret /* Terminator - outputs "\r " - notice the space after the carriage return. It is important. Try redoing this with the space removed to see why. */ Terminator: stp x29, x30, [sp, -16]! mov w0, 1 // 1 is stdout ldr x1, =TRM // Pointer to string mov w2, 2 // 2 characters being printed bl write ldp x29, x30, [sp], 16 ret /* Emit - puts out the symbol derived from counter. */ Emit: stp x29, x30, [sp, -16]! mov w0, counter mov w1, NUM_CHARS bl mod ldr x1, =CHARS add x1, x1, x0 mov w0, 1 mov w2, 1 bl write ldp x29, x30, [sp], 16 ret /* Pad - prints counter spaces to the console. It does this using a for loop printing one space at a time. A reasonable optimization would be to create a large array of spaces and use the length parameter to write() to replace the loop. */ Pad: stp x29, x30, [sp, -16]! str delta, [sp, -16]! // REUSING w21 !!! mov w21, wzr 1: ldr x1, =buff mov w2, ' ' strb w2, [x1] // ' ' is pointed to by x1 mov w0, 1 // 1 is stdout mov w2, 1 // emitting 1 byte bl write add w21, w21, 1 cmp w21, counter blt 1b ldr delta, [sp], 16 ldp x29, x30, [sp], 16 ret /* mod(a, b) - implements a % b - AARCH64 lacks a mod instruction. A strange place to economize, but there you have it. a comes to us in x0 b comes to us in x1 method: * integer divide a by b for example - 5 % 3 would need 5 // 3 yielding 1 * multiply result by b 1 * 3 is 3 * subtract result from a 5 - 3 is 2 and that's our return value. */ mod: sdiv x2, x0, x1 // x2 gets a // b msub x0, x2, x1, x0 // x0 gets a - a // b * b ret .data buff: .space 4 .end