; for (count=1..100): ; if(count%3 == 0) { print_fizz(); } ; if(count%5 == 0) { print_buzz(); } else { ; if(count%3 && count%5) print(count); ;; } ; print(newline) ; We don't need pointers to these strings at all; The strings are immediate data for a couple mov instructions ;SECTION .rodata ; put constants in .rodata. ; fizz: db "fizz" ; No idea what the trailing 4 was for ; buzz: db "buzz" FIZZMOD equ 3 ; only 3 works, but it would be easy to use a loop BUZZMOD equ 5 ; any value works LASTCOUNT equ 100 ; max 100: we only handle two decimal digits. ; TODO: cleanup that can handle LASTCOUNT%FIZZMOD != 1 and LASTCOUNT%BUZZMOD != 0 SECTION .bss ;;; generate a string in this buffer. (flush it with write(2) on "fizzbuzz" lines) ; buf: resb 4096 buf: resb FIZZMOD * BUZZMOD * 9 ; (worst case: every line is "fizzbuzz\n") SECTION .text global _start _start: ; args for write(2). (syscall clobbers rcx/r11, and rax with the return value) mov edi, 1 ; STDOUT_FILENO. also happens to be __NR_write in the AMD64 Linux ABI mov esi, buf ; static data lives in the low 2G of address space, so we don't need a 64bit mov ;; edx = count. ; calculated each iteration ;; mov eax, edi ; also needed every time. saves 3B vs mov eax, imm32 ; 'fizz' is only used once, so we could just store with an immediate there. That wouldn't micro-fuse, and we'd have to do the newline separately mov r10b, 10 ; base 10 ;;mov r14d, BUZZMOD ; not needed, we don't div for this mov r12, 'fizz' | 10<<32 ; `fizz\n`, but YASM doesn't support NASM's backquotes for \-escapes mov r13, 'buzz' | 10<<32 ; `buzz\n`. When buzz appears, it's always the end of a line ;;;;;;;; Set up for first iteration mov ebp, BUZZMOD ; detect count%BUZZMOD == 0 with a down-counter instead of dividing mov ebx, 1 ; counter starts at 1 mov edx, esi ; current output position = front of buf ALIGN 16 main_loop: ;; TODO: loop FIZZMOD-1 times inside buzz_or_number, or here ;; It doesn't make much sense to unroll this loop but not inline buzz_or_number :/ call buzz_or_number inc ebx call buzz_or_number add ebx, 2 ; counter is never printed on Fizz iterations, so just set up for next main_loop ;; Fizz, and maybe also Buzz mov qword [rdx], r12 ; Fizz with a newline add edx, 5 ; TODO: move this after the branch; adjust the offsets in .fizzbuzz dec ebp jz .fizzbuzz ;;.done_buzz: ; .fizzbuzz duplicates the main_loop branch instead of jumping back here cmp ebx, LASTCOUNT-FIZZMOD jbe main_loop ;;;;;;;;;; END OF main_loop .cleanup: ;;;;;;;;;;;;;;;;;;;;; Cleanup after the loop ; hard-code the fact that 100 % FIZZMOD = 1 more line to print, ; and that 100 % BUZZMOD = 0, so the line is "buzz\n" mov eax, edi ; __NR_write mov [rdx], r13 ; the final "buzz\n". sub edx, buf - 5 ; write_count = current_pos+5 - buf. syscall ; write(1, buf, p - buf). ;; if buf isn't static, then use add edx, 5 / sub edx, esi xor edi, edi mov eax, 231 ; exit_group(0). same as eax=60: exit() for a single-threaded program syscall ;;;;; The fizzbuzz case from the loop .fizzbuzz: ;; count%BUZZMOD == 0: rdx points after the \n at the end of fizz\n, which we need to overwrite ;; this is a macro so we can use it in buzz_or_number, too, where we don't need to back up and overwrite a \n %macro BUZZ_HIT 1 mov [rdx - %1], r13 ; buzz\n. Next line will overwrite the last 3 bytes of the 64b store. add edx, 5 - %1 mov ebp, BUZZMOD ; reset the count%BUZZMOD down-counter %endmacro BUZZ_HIT 1 ; arg=1 to back up and overwrite the \n from "fizz\n" sub edx, esi ; write_count = current_pos - buf mov eax, edi ; __NR_write syscall ; write(1, buf, p - buf). clobbers only rax (return value), and rcx,r11 mov edx, esi ; restart at the front of the buffer ;;; tail-duplication of the main loop, instead of jmp back to the cmp/jbe ;;; could just be a jmp main_loop, if we check at assemble time that LASTCOUNT % FIZZMOD != 0 || LASTCOUNT % BUZZMOD != 0 cmp ebx, LASTCOUNT-FIZZMOD jbe main_loop jmp .cleanup ;;;;;;;;;;;;;;;;;;;;;;; buzz_or_number: called for non-fizz cases ; special calling convention: uses (without clobbering) the same regs as the loop ;; modifies: BUZZMOD down-counter, output position pointer ;; clobbers: rax, rcx ALIGN 32 buzz_or_number: dec ebp jnz .no_buzz ; could make this part of the macro, but flow-control inside macros is probably worse than duplication ;; count%BUZZMOD == 0: append "buzz\n" to the buffer and reset the down-counter BUZZ_HIT 0 ; back up 0 bytes before appending ret .no_buzz: ;; get count as a 1 or 2-digit ASCII number ;; assert(ebx < 10); We don't handle 3-digit numbers mov eax, ebx div r10b ; al = count/10 (first (high) decimal digit), ah = count%10 (second (low) decimal digit). ;; x86 is little-endian, so this is in printing-order already for storing eax ;movzx eax, ax ; avoid partial-reg stalls on pre-Haswell ;; convert integer digits to ASCII by adding '0' to al and ah at the same time, and set the 3rd byte to `\n`. cmp ebx, 9 ; compare against the original counter instead of the div result, for more ILP and earlier detection of branch misprediction jbe .1digit ; most numbers from 1..100 are 2-digit, so make this the not-taken case add eax, 0x0a3030 ;; `00\n`: converts 2 integer digits -> ASCII ;; eax now holds the number + newline as a 3-byte ASCII string mov [rdx], eax add edx, 3 ret .1digit: ;; Could use a 16bit operand-size here to avoid partial-reg stalls, but an imm16 would LCP-stall on Intel. shr eax, 8 ; Shift out the leading 0 add eax, 0x000a30 ;; 1-digit numbers ;; eax now holds the number + newline as a 2-byte ASCII string mov [rdx], ax add edx, 2 ret
Write, Run & Share Assembly code online using OneCompiler's Assembly online compiler for free. It's one of the robust, feature-rich online compilers for Assembly language. Getting started with the OneCompiler's Assembly compiler is simple and pretty fast. The editor shows sample boilerplate code when you choose language as Assembly
and start coding.
Assembly language(asm) is a low-level programming language, where the language instructions will be more similar to machine code instructions.
Every assembler may have it's own assembly language designed for a specific computers or an operating system.
Assembly language requires less execution time and memory. It is more helful for direct hardware manipulation, real-time critical applications. It is used in device drivers, low-level embedded systems etc.
Assembly language usually consists of three sections,
Data section
To initialize variables and constants, buffer size these values doesn't change at runtime.
bss section
To declare variables
text section
_start
specifies the starting of this section where the actually code is written.
There are various define directives to allocate space for variables for both initialized and uninitialized data.
variable-name define-directive initial-value
Define Directive | Description | Allocated Space |
---|---|---|
DB | Define Byte | 1 byte |
DW | Define Word | 2 bytes |
DD | Define Doubleword | 4 bytes |
DQ | Define Quadword | 8 bytes |
DT | Define Ten Bytes | 10 bytes |
Define Directive | Description |
---|---|
RESB | Reserve a Byte |
RESW | Reserve a Word |
RESD | Reserve a Doubleword |
RESQ | Reserve a Quadword |
REST | Reserve a Ten Bytes |
Constants can be defined using
CONSTANT_NAME EQU regular-exp or value
%assign constant_name value
%define constant_name value
Loops are used to iterate a set of statements for a specific number of times.
mov ECX,n
L1:
;<loop body>
loop L1
where n specifies the no of times loops should iterate.
Procedure is a sub-routine which contains set of statements. Usually procedures are written when multiple calls are required to same set of statements which increases re-usuability and modularity.
procedure_name:
;procedure body
ret