asm

x86 Assembly programs.
git clone git://code.dwrz.net/asm
Log | Files | Refs

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