216 lines
8.3 KiB
ArmAsm
216 lines
8.3 KiB
ArmAsm
# PURPOSE:
|
|
# Loop from numbers 1 to 100.
|
|
# Print "fizz" if a number is divisible by 3.
|
|
# Print "buzz" if a number is divisible by 5.
|
|
# Print "fizzbuzz" if a number is divisible by 3 and 5.
|
|
# Print the number in all other cases.
|
|
# INPUT: None.
|
|
# OUTPUT:
|
|
# Print newline separated strings to stdout.
|
|
# Return a status code of 0.
|
|
# VARIABLES:
|
|
.section .data
|
|
fizz:
|
|
.ascii "fizz\n"
|
|
buzz:
|
|
.ascii "buzz\n"
|
|
fizzbuzz:
|
|
.ascii "fizzbuzz\n"
|
|
number_string: # Used to store the ascii representation of a number.
|
|
.zero 4
|
|
|
|
.section .text
|
|
.globl _start
|
|
|
|
_test_fizzbuzz:
|
|
# _test_fizzbuzz expects a 64-bit integer on the stack.
|
|
# It returns a "print case" at %rax, also a 64-bit int.
|
|
# The return value tracks what should be printed for the input number.
|
|
# The possible return values are:
|
|
# 0, i.e., print the counter.
|
|
# 1, i.e., print "fizz".
|
|
# 2, i.e., print "buzz".
|
|
# 3, i.e., print "fizzbuzz".
|
|
push %rbp
|
|
mov %rsp, %rbp
|
|
sub $8, %rsp # Local stack variable to track print case.
|
|
movq $0, 0(%rsp)
|
|
test_fizz:
|
|
# Retrieve the loop counter to use as the dividend.
|
|
# It's now 24 bytes up from the stack pointer:
|
|
# 8 bytes were just set aside for the print case.
|
|
# 8 bytes were set aside for the base pointer.
|
|
# 8 bytes were set aside for the counter, preceding the call.
|
|
# We really only need a single byte for the counter and print case.
|
|
# As is, the code is not space efficient.
|
|
mov 24(%rsp), %rax
|
|
movq $3, %rbx # Use 3 as the divisor for "fizz" test.
|
|
# Convert double-word to quad-word.
|
|
# Necessary for 32 and 64-bit division.
|
|
cdq
|
|
div %rbx
|
|
cmp $0, %rdx # Was the remainder 0?
|
|
jne test_buzz # If not, test division by 5.
|
|
addq $1, 0(%rsp) # Otherwise, increment the print case.
|
|
test_buzz:
|
|
mov 24(%rsp), %rax # Retrieve the counter again.
|
|
movq $5, %rbx # Use 5 as the divisor for "buzz" test.
|
|
cdq
|
|
div %rbx
|
|
cmp $0, %rdx # Was the remainder 0?
|
|
jne return_print_case # If not, return.
|
|
addq $2, 0(%rsp) # Otherwise, increment the print case by two.
|
|
return_print_case:
|
|
mov 0(%rsp), %rax # Put the print case in %rax.
|
|
mov %rbp, %rsp
|
|
pop %rbp
|
|
ret
|
|
|
|
_itoa:
|
|
# _itoa expects a 64-bit integer on the stack.
|
|
# It converts an integer into its ascii multi-byte representation.
|
|
# The conversion is stored on the number_string buffer.
|
|
# We get the digits in reverse order by dividing by 10:
|
|
# 1234 / 10 yields a remainder of 4, then, 3, 2, 1.
|
|
# We push these onto the stack, then pop off the stack to get the
|
|
# correct order (pop 1 yields 1, pop 2 yields 2, etcetera).
|
|
push %rbp
|
|
mov %rsp, %rbp
|
|
mov 16(%rsp), %rax # Place the integer param in %rax.
|
|
# Uuse %rcx to keep track of how many digits we have processed.
|
|
# We need this to know when to stop popping off the stack.
|
|
mov $0, %rcx
|
|
# Use 10 as the divisor. The remainder will give us the last digit.
|
|
movq $10, %rbx
|
|
conversion_loop:
|
|
inc %rcx # Keep track of the digit we're processing.
|
|
cdq # Setup for division.
|
|
div %rbx
|
|
# Convert to ASCII by adding 48.
|
|
# ASCII "0" is 48, "1" is 49, etc.
|
|
# By adding 48, we can get the ASCII integer that represents our digit.
|
|
add $48, %rdx
|
|
push %rdx # Push the converted remainder onto the stack.
|
|
cmp $0, %rax # Are we done, i.e., is the quotient 0?
|
|
jne conversion_loop # If not, repeat.
|
|
# Otherwise, it's time to store the converted digits in our buffer.
|
|
# Load the effective address of the buffer in %rdx.
|
|
lea number_string, %rdx
|
|
save_to_buffer_loop:
|
|
# Remember how we kept track of our digits with %rcx?
|
|
# We now pop the stack to get each digit back.
|
|
pop %rax
|
|
# The ASCII character is only one byte long.
|
|
# Move that byte into the buffer, by using the address in %rdx.
|
|
movb %al, (%rdx)
|
|
# We've processed this digit, decrement %rcx.
|
|
dec %rcx
|
|
# Increment %rdx so that it points to the buffer's next byte address.
|
|
inc %rdx
|
|
cmp $0, %rcx # Have we processed all digits?
|
|
jne save_to_buffer_loop # If not, loop again.
|
|
# We're done moving data from the stack to the buffer.
|
|
# Let's add a newline right after our digits.
|
|
# The ASCII integer representation of "\n" is 10.
|
|
# %rdx is left pointing at the newline, so callers can compute length.
|
|
movb $10, (%rdx)
|
|
mov %rbp, %rsp
|
|
pop %rbp
|
|
ret
|
|
|
|
_print:
|
|
# print expects a print case and counter, both 64-bit integers,
|
|
# on the stack.
|
|
# The former should be passed at 16 bytes above the stack pointer,
|
|
# the latter at 24 bytes above, i.e.:
|
|
# var print_case = 16(%rsp)
|
|
# var counter = 24(%rsp)
|
|
# Depending on the print case, _print either:
|
|
# 1. Prints the counter, by using _itoa to convert it to ASCII.
|
|
# 2. Prints "fizz", "buzz", or "fizzbuzz".
|
|
# The printing itself is done by asking the kernel to write to stdout.
|
|
push %rbp
|
|
mov %rsp, %rbp
|
|
# Setup the registers for the kernel request.
|
|
mov $4, %rax # System call number for write.
|
|
mov $1, %rbx # stdout.
|
|
# %rcx will hold the address of the buffer to be printed.
|
|
# %rdx will hold the number of bytes to write.
|
|
# How these are set depends on the print case:
|
|
cmpq $0, 16(%rsp)
|
|
je print_counter
|
|
cmpq $1, 16(%rsp)
|
|
je print_fizz
|
|
cmpq $2, 16(%rsp)
|
|
je print_buzz
|
|
cmpq $3, 16(%rsp)
|
|
je print_fizzbuzz
|
|
print_counter:
|
|
# We need to convert the counter to ASCII.
|
|
# _itoa does this for us.
|
|
# _itoa has a side-effect on the number_string buffer.
|
|
# Push the counter again on the stack, for convenience.
|
|
push 24(%rsp)
|
|
# We could access the parameter to print from _itoa, with a larger byte
|
|
# offset. But it's probably a little cleaner to just use another
|
|
# location on the stack.
|
|
call _itoa
|
|
# _itoa also changes the values in the registers, so we'll need to
|
|
# reset them for the request to the kernel.
|
|
mov $4, %rax
|
|
mov $1, %rbx
|
|
mov $number_string, %rcx # Set the buffer the kernel should use.
|
|
# _itoa leaves %rdx pointing at the newline after the digits.
|
|
# Length = (newline address - buffer start) + 1 for the newline.
|
|
sub $number_string, %rdx
|
|
inc %rdx
|
|
jmp print_and_return
|
|
print_fizz:
|
|
mov $fizz, %rcx
|
|
mov $5, %rdx
|
|
jmp print_and_return
|
|
print_buzz:
|
|
mov $buzz, %rcx
|
|
mov $5, %rdx
|
|
jmp print_and_return
|
|
print_fizzbuzz:
|
|
mov $fizzbuzz, %rcx
|
|
mov $9, %rdx
|
|
jmp print_and_return
|
|
print_and_return:
|
|
int $0x80 # Call the kernel.
|
|
mov %rbp, %rsp
|
|
pop %rbp
|
|
ret
|
|
|
|
_exit_normal:
|
|
mov $1, %rax # System call number for exit.
|
|
mov $0, %rbx # Exit code.
|
|
int $0x80 # Call the kernel.
|
|
|
|
_start:
|
|
# Initialize the loop counter as 1.
|
|
mov $1, %rcx
|
|
loop:
|
|
# Determine the print case (which was initialized as 0).
|
|
# Both calls have a side-effect on the print_case buffer.
|
|
# If the counter is divisible by 3, increment print_case by 1.
|
|
# If divisible by 5, increment print_case by 2.
|
|
# A number divisible by 3 and 5 thus results in a print_case of 3.
|
|
# A number that isn't divisible by either leaves print_case at 0.
|
|
push %rcx # Store the counter.
|
|
call _test_fizzbuzz
|
|
# _test_fizzbuzz returns the print case with %rax.
|
|
# Place the return value on the stack, so that it can be used by _print.
|
|
push %rax
|
|
call _print
|
|
pop %rax # Remove %rax from the stack, so that we can
|
|
# restore the loop counter, then increment it.
|
|
pop %rcx
|
|
inc %rcx
|
|
# Check: have we iterated enough times? If not, loop again.
|
|
cmp $101, %rcx
|
|
jl loop
|
|
# Otherwise, we're done.
|
|
call _exit_normal
|