.global main .align 2 .text .equ NUM_FLAKES, 250 .equ MAX_COLUMN, 80 .equ MAX_LINE, 24 .equ MAX_LINE_X2, 48 storm .req x27 // base address of storm estorm .req x28 // end of storm main: stp x29, x30, [sp, -16]! stp x27, x28, [sp, -16]! // Seed the RNG with the current time. mov w0, wzr bl time bl srand // Allocate the storm. mov x0, NUM_FLAKES // Calculate space needed. mov x1, Flake.size mul x0, x0, x1 str x0, [sp, -16]! // Save for memset. bl malloc // Attempt the allocation. cbnz x0, 1f // Branch if successful. add sp, sp, 16 // Undo stack change. ldr x0, =bad_malloc // Inform user of tragedy. bl puts mov w0, 1 b bye 1: mov storm, x0 // The base address of the // allocated memory will be // preserved in x27. mov estorm, x0 // prep end of storm calculation ldr x2, [sp], 16 // Restore the allocation size. add estorm, estorm, x2 // Preserve end-of-storm. mov w1, wzr // Fill with zero. bl memset // x0 still had base address. bl InitializeStorm 10: bl Erase bl StepAll bl RenderAll mov w0, 1 mov w1, 1 bl Move ldr x0, =nl bl printf bl Delay b 10b 99: mov x0, storm bl free mov w0, wzr bye: ldp x27, x28, [sp], 16 ldp x20, x30, [sp], 16 ret /* line comes to us in w0 column comes to us in w1 */ Move: stp x20, x30, [sp, -16]! mov w2, w1 mov w1, w0 ldr x0, =move_str bl printf ldp x20, x30, [sp], 16 ret Erase: stp x20, x30, [sp, -16]! mov w0, 1 mov w1, 1 bl Move ldr x0, =erase_str bl printf ldp x20, x30, [sp], 16 ret /* Address of flake to render comes to us in x0. */ RenderOne: str x30, [sp, -16]! ldr w1, [x0] ldr w2, [x0, 4] // Ensure 1 <= w1 <= MAX_LINE mov w3, 1 mov w4, MAX_LINE mov w5, MAX_COLUMN cmp w3, w1 bgt 99f cmp w4, w1 blt 99f // Ensure 1 <= w2 <= MAX_COLUMN cmp w3, w2 bgt 99f cmp w5, w2 blt 99f ldr x0, =move_str bl printf ldr x0, =char_str bl printf 99: ldr x30, [sp], 16 ret /* Iterate through the storm rendering each particle */ RenderAll: stp x20, x30, [sp, -16]! mov x20, storm 1: mov x0, x20 bl RenderOne add x20, x20, Flake.size cmp estorm, x20 bgt 1b ldp x20, x30, [sp], 16 ret Delay: str x30, [sp, -16]! mov x0, 1 lsl x0, x0, 17 bl usleep ldr x30, [sp], 16 ret /* Address of flake to step arrives in x0. */ StepOne: stp x20, x30, [sp, -16]! mov x20, x0 ldr w1, [x0] add w1, w1, 1 str w1, [x0] mov w2, MAX_LINE cmp w2, w1 bge 1f bl ResetFlake 1: bl rand mov w1, 3 bl mod sub x0, x0, 1 ldr w1, [x20, 4] add w0, w0, w1 str w0, [x20, 4] ldp x20, x30, [sp], 16 ret StepAll: stp x20, x30, [sp, -16]! mov x20, storm 1: mov x0, x20 bl StepOne add x20, x20, Flake.size cmp estorm, x20 bgt 1b ldp x20, x30, [sp], 16 ret InitializeStorm: stp x20, x30, [sp, -16]! mov x20, storm 1: mov x0, x20 bl ResetFlake add x20, x20, Flake.size cmp estorm, x20 bne 1b ldp x20, x30, [sp], 16 ret /* x0 contains the address of a Flake in need of being reset. The column number of a Flake is anywhere from 1 to 80 (a default terminal). The starting line is anywhere from 1 to 48 (24 x 2) lines ABOVE the screen. This allows the flakes to drift downward without apparent gaps or bunches. */ ResetFlake: stp x20, x30, [sp, -16]! mov x20, x0 // Initialize Column --- 1 <= random value <= 80 bl rand mov x1, MAX_COLUMN bl mod add w0, w0, 1 str w0, [x20, 4] // Initialize Line --- -(1 <= random value <= 48) bl rand mov x1, MAX_LINE_X2 bl mod add w0, w0, 1 neg w0, w0 str w0, [x20] ldp x20, 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 bad_malloc: .asciz "Allocation of flakes has failed" move_str: .asciz "\033[%d;%dH" erase_str: .asciz "\033[2J" char_str: .asciz "*" nl: .asciz "\n" .section Flake .struct 0 Flake.line: .struct Flake.line + 4 Flake.col: .struct Flake.col + 4 Flake.size: