fizzbuzz.s (8326B)
1 # PURPOSE: 2 # Loop from numbers 1 to 100. 3 # Print "fizz" if a number is divisible by 3. 4 # Print "buzz" if a number is divisible by 5. 5 # Print "fizzbuzz" if a number is divisible by 3 and 5. 6 # Print the number in all other cases. 7 # INPUT: None. 8 # OUTPUT: 9 # Print newline separated strings to stdout. 10 # Return a status code of 0. 11 # VARIABLES: 12 .section .data 13 fizz: 14 .ascii "fizz\n" 15 buzz: 16 .ascii "buzz\n" 17 fizzbuzz: 18 .ascii "fizzbuzz\n" 19 number_string: # Used to store the ascii representation of a number. 20 .zero 4 21 22 .section .text 23 .globl _start 24 25 _test_fizzbuzz: 26 # _test_fizzbuzz expects a 64-bit integer on the stack. 27 # It returns a "print case" at %rax, also a 64-bit int. 28 # The return value tracks what should be printed for the input number. 29 # The possible return values are: 30 # 0, i.e., print the counter. 31 # 1, i.e., print "fizz". 32 # 2, i.e., print "buzz". 33 # 3, i.e., print "fizzbuzz". 34 push %rbp 35 mov %rsp, %rbp 36 sub $8, %rsp # Local stack variable to track print case. 37 movq $0, 8(%rsp) 38 test_fizz: 39 # Retrieve the loop counter to use as the dividend. 40 # It's now 24 bytes up from the stack pointer: 41 # 8 bytes were just set aside for the print case. 42 # 8 bytes were set aside for the base pointer. 43 # 8 bytes were set aside for the counter, preceding the call. 44 # We really only need a single byte for the counter and print case. 45 # As is, the code is not space efficient. 46 mov 24(%rsp), %rax 47 movq $3, %rbx # Use 3 as the divisor for "fizz" test. 48 # Convert double-word to quad-word. 49 # Necessary for 32 and 64-bit division. 50 cdq 51 div %rbx 52 cmp $0, %rdx # Was the remainder 0? 53 jne test_buzz # If not, test division by 5. 54 add $1, 8(%rsp) # Otherwise, increment the print case. 55 test_buzz: 56 mov 24(%rsp), %rax # Retrieve the counter again. 57 movq $5, %rbx # Use 5 as the divisor for "buzz" test. 58 cdq 59 div %rbx 60 cmp $0, %rdx # Was the remainder 0? 61 jne return_print_case # If not, return. 62 add $2, 8(%rsp) # Otherwise, increment the print case by two. 63 return_print_case: 64 mov 8(%rsp), %rax # Put the print case in %rax. 65 mov %rbp, %rsp 66 pop %rbp 67 ret 68 69 _itoa: 70 # _itoa expects a 64-bit integer on the stack. 71 # It converts an integer into its ascii multi-byte representation. 72 # The conversion is stored on the number_string buffer. 73 # We get the digits in reverse order by dividing by 10: 74 # 1234 / 10 yields a remainder of 4, then, 3, 2, 1. 75 # We push these onto the stack, then pop off the stack to get the 76 # correct order (pop 1 yields 1, pop 2 yields 2, etcetera). 77 push %rbp 78 mov %rsp, %rbp 79 mov 16(%rsp), %rax # Place the integer param in %rax. 80 # Uuse %rcx to keep track of how many digits we have processed. 81 # We need this to know when to stop popping off the stack. 82 mov $0, %rcx 83 # Use 10 as the divisor. The remainder will give us the last digit. 84 movq $10, %rbx 85 conversion_loop: 86 inc %rcx # Keep track of the digit we're processing. 87 cdq # Setup for division. 88 div %rbx 89 # Convert to ASCII by adding 48. 90 # ASCII "0" is 48, "1" is 49, etc. 91 # By adding 48, we can get the ASCII integer that represents our digit. 92 add $48, %rdx 93 push %rdx # Push the converted remainder onto the stack. 94 cmp $0, %rax # Are we done, i.e., is the quotient 0? 95 jne conversion_loop # If not, repeat. 96 # Otherwise, it's time to store the converted digits in our buffer. 97 # Load the effective address of the buffer in %rdx. 98 lea number_string, %rdx 99 save_to_buffer_loop: 100 # Remember how we kept track of our digits with %rcx? 101 # We now pop the stack to get each digit back. 102 pop %rax 103 # The ASCII character is only one byte long. 104 # Move that byte into the buffer, by using the address in %rdx. 105 movb %al, (%rdx) 106 # We've processed this digit, decrement %rcx. 107 dec %rcx 108 # Increment %rdx so that it points to the buffer's next byte address. 109 inc %rdx 110 cmp $0, %rcx # Have we processed all digits? 111 jne save_to_buffer_loop # If not, loop again. 112 # We're done moving data from the stack to the buffer. 113 # Let's add a newline to the end of our digits. 114 # The ASCII integer representation of "\n" is 10. 115 movb $10, number_string + 3 116 mov %rbp, %rsp 117 pop %rbp 118 ret 119 120 _print: 121 # print expects a print case and counter, both 64-bit integers, 122 # on the stack. 123 # The former should be passed at 16 bytes above the stack pointer, 124 # the latter at 24 bytes above, i.e.: 125 # var print_case = 16(%rsp) 126 # var counter = 24(%rsp) 127 # Depending on the print case, _print either: 128 # 1. Prints the counter, by using _itoa to convert it to ASCII. 129 # 2. Prints "fizz", "buzz", or "fizzbuzz". 130 # The printing itself is done by asking the kernel to write to stdout. 131 push %rbp 132 mov %rsp, %rbp 133 # Setup the registers for the kernel request. 134 mov $4, %rax # System call number for write. 135 mov $1, %rbx # stdout. 136 # %rcx will hold the address of the buffer to be printed. 137 # %rdx will hold the number of bytes to write. 138 # How these are set depends on the print case: 139 cmp $0, 16(%rsp) 140 je print_counter 141 cmp $1, 16(%rsp) 142 je print_fizz 143 cmp $2, 16(%rsp) 144 je print_buzz 145 cmp $3, 16(%rsp) 146 je print_fizzbuzz 147 print_counter: 148 # We need to convert the counter to ASCII. 149 # _itoa does this for us. 150 # _itoa has a side-effect on the number_string buffer. 151 # Push the counter again on the stack, for convenience. 152 push 24(%rsp) 153 # We could access the parameter to print from _itoa, with a larger byte 154 # offset. But it's probably a little cleaner to just use another 155 # location on the stack. 156 call _itoa 157 # _itoa also changes the values in the registers, so we'll need to 158 # reset them for the request to the kernel. 159 mov $4, %rax 160 mov $1, %rbx 161 mov $number_string, %rcx # Set the buffer the kernel should use. 162 # Print 3 digits + 1 newline = 4 bytes. 163 # FIX: this is wrong -- we're not always printing 4 bytes. 164 mov $4, %rdx 165 jmp print_and_return 166 print_fizz: 167 mov $fizz, %rcx 168 mov $5, %rdx 169 jmp print_and_return 170 print_buzz: 171 mov $buzz, %rcx 172 mov $5, %rdx 173 jmp print_and_return 174 print_fizzbuzz: 175 mov $fizzbuzz, %rcx 176 mov $9, %rdx 177 jmp print_and_return 178 print_and_return: 179 int $0x80 # Call the kernel. 180 mov %rbp, %rsp 181 pop %rbp 182 ret 183 184 _exit_normal: 185 mov $1, %rax # System call number for exit. 186 mov $0, %rbx # Exit code. 187 int $0x80 # Call the kernel. 188 189 _start: 190 # Initialize the loop counter as 1. 191 mov $1, %rcx 192 loop: 193 # Determine the print case (which was initialized as 0). 194 # Both calls have a side-effect on the print_case buffer. 195 # If the counter is divisible by 3, increment print_case by 1. 196 # If divisible by 5, increment print_case by 2. 197 # A number divisible by 3 and 5 thus results in a print_case of 3. 198 # A number that isn't divisible by either leaves print_case at 0. 199 push %rcx # Store the counter. 200 call _test_fizzbuzz 201 # _test_fizzbuzz returns the print case with %rax. 202 # Place the return value on the stack, so that it can be used by _print. 203 push %rax 204 call _print 205 pop %rax # Remove %rax from the stack, so that we can 206 # restore the loop counter, then increment it. 207 pop %rcx 208 inc %rcx 209 # Check: have we iterated enough times? If not, loop again. 210 cmp $101, %rcx 211 jl loop 212 # Otherwise, we're done. 213 call _exit_normal