summaryrefslogtreecommitdiffstats
path: root/fizzbuzz.asm
blob: b1b5d1401674be0f94769dcce9d6d25220447888 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
; Version	: 2.11
; Last update	: 30/7/2020
; Description	: FizzBuzz, but in assembly. Left at a good tradeoff between speed and complexity.
; Licence	: GPLv3

section .data
fizz db 'Fizz',0xA
fizzlen equ $-fizz
buzz db 'Buzz',0xA
buzzlen equ $-buzz
fizzbuzz db 'FizzBuzz',0xA
fizzbuzzlen equ $-fizzbuzz

section .text
global _start
_start:
xor r12,r12		;counter register, set to count 0 to 100
xor r9,r9		;r9 is used as a boolean to record if modulus 3 is achieved
jmp skip		;skip incrementing the first time round so 0 is kept

increment: inc r12	;increment the counter
skip: cmp r12,101	;check if 101 has been reached
;skip: cmp r12,1000001	;check if 101 has been reached
je exit			; Exit if numbers 0 to 100 have been calculated

check3: imul r8d,r12d,0xaaaaaaab;calculate modulus 3 quickly by multiplying the counter by 0xaaaaaaab and truncuating the result into r8d.
cmp r8,0x55555555
ja check5			;if r8 is larger than 0x55555555; (r12 % 3 !=0) and so modulus 5 is directly jumped to.

;falls through if (r12 % 3 == 0)
mov r9,1 ; set the fizz boolean to true

check5: imul r8d,r12d,0xcccccccd	;calculate modulus 5 quickly by multiplying the counter by 0xcccccccd and truncuating the result into r8d.
cmp r8,0x33333333
ja checkfizz				;if r8 is larger than 0x33333333; (r12 % 5 !=0). The state of fizz needs to be check to determine if anything needs printing.

;; fall through if (r12 % 5 == 0)
test r9,r9			;r9 is the fizz boolean
je buzzonly			;if there is no fizz, only print buzz

;; fall through if both fizz and buzz are achieved; it's fizzbuzz time
printfizzbuzz: mov edi,1	;file descriptor 1=stdout
mov rsi,fizzbuzz		;mov memory address of text to print into rsi.
mov rdx,fizzbuzzlen		;mov amount of bytes to write into rdx.
mov eax,1			;1=sys_write syscall.
syscall				;do the syscall (sys_write in this case)

xor r9,r9			;zero out the fizz boolean
jmp increment

checkfizz: test r9,r9	;check if fizz is achieved
je printnum		;if not, print current number

;; fall through if fizz is achieved
fizzonly:
mov edi,1		; File descriptor 1=stdout
mov rsi,fizz		;mov memory address of text to print into rsi.
mov rdx,fizzlen		;mov amount of bytes to write into rdx.
mov eax,1		;1=sys_write syscall.
syscall			;do the syscall (sys_write in this case)
xor r9,r9		;zero out the fizz boolean
jmp increment

buzzonly:
mov edi,1		;file descriptor 1=stdout
mov rsi,buzz		;mov memory address of text to print into rsi.
mov rdx,buzzlen		;mov amount of bytes to write into rdx.
mov eax,1		;1=sys_write syscall. 1 must be moved into rax after syscall is used, since rax is overwritten with the return value
syscall			;do the syscall (sys_write in this case)
xor r9,r9		;zero out the fizz boolean
jmp increment


printnum: ALIGN 16	;align the stack to 16 bytes.
mov ecx, 0xA		;base 10
dec rsp
mov [rsp],cl		;newline = 0xa = base
mov rsi,rsp		;move the stack pointer to the source register
;sub rsp,16		;the red-zone is big enough on 64 bit linux to allow printing ~8 or even more digits without modifying the stack pointer. Change the LEA below from "[rsp+16+1]" to "lea edx, [rsp+1]" if removed.

;; rsi is pointing at '\n' on the stack, with 16B of "allocated" space below that. (if the stack pointer wasn't modified, the red-zone is written into instead)
mov rax,r12		;rax = counter
mov r10,0xcccccccd	;used in division by 10 below

;; ecx=remainder = low digit = 0..9.  eax/=10
toascii_digit: mov ecx,eax	;copy eax to ecx for later usage in modulus.

imul rax,r10			;sign multiply rax by 0xcccccccd to complete the first step of division by 10
shr rax,0x23			;shift rax right by 35 to finish off rax/=10

mov edx,eax			;move eax to edx so modulus can be done on it without clobbering eax
lea edx,[rdx+rdx*4]		;multiply edx by 5 as the first step of modulus
add edx,edx			;multiply edx by 2 as the second step of modulus
sub ecx,edx			;subtract edx from the original number (before /=10) to finish off modulus 10

;; the remainder(ecx) is the next calculated digit
add ecx,'0'			;add 48 to ecx to turn it into the ASCII representation of the number
dec rsi				;store digits in MSD-first printing order, working backwards from the end of the string
mov [rsi],cl			;shove cl(lower 8 bits in rcx) onto the stack

test eax,eax
jnz toascii_digit		;if eax does not equal zero, there are more ASCII chars to generate and shove onto the stack

;; rsi points to the first digit
mov eax,1			;sys_write
mov edi,1			;fd = STDOUT_FILENO
lea edx,[rsp+1]			;yes, it's safe to truncate pointers before subtracting to find length.
sub edx,esi			;calculate length, including the \n
syscall				;print the chars on the stack

;add rsp, 24			;undo the push and the buffer reservation (32-bit: add esp,20)
jmp increment

exit:
mov eax, 60			;code for sys_exit
xor edi, edi			;return value of 0
syscall				;do sys_exit