Created
January 29, 2015 18:07
-
-
Save mk12/21f0921ab56f2af1e37a to your computer and use it in GitHub Desktop.
Prints a diamond pattern of N rows with alternating $ and *.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; diamond.s | |
; Usage: | |
; diamond <size> | |
; size is a number from 0 to 300 | |
; If size is greater than 300, 300 will be used | |
; If size is even, size + 1 (odd) will be used | |
; If size contains non-numerical characters, | |
; actual diamond size will be unexpected | |
; ---------------------------------------- | |
; ----------------- MAIN ----------------- | |
; ---------------------------------------- | |
section .text | |
global start | |
start: | |
; ===== Get Number of Lines ===== | |
; Get number of arguments | |
pop rax ; argc - the number of command line arguments | |
dec rax ; Don't count argv[0] (the program's name) | |
add rsp, 8 ; Skip argv[0] | |
; Error checking | |
test rax, rax ; Make sure there is at least one argument | |
jz .quit ; If not, quit | |
; Convert string to number (the number of lines) | |
pop rdi ; Get the first argument | |
call atol ; Convert it to a number and store it in rax | |
; Special cases | |
test rax, rax ; Check if the number is 0 | |
jz .quit ; If so, quit | |
cmp rax, 300 ; Check if the number is > 300 | |
jna .endif1 ; If not, skip the next instruction | |
mov rax, 300 ; Limit the number to 300 | |
.endif1: | |
cmp rax, 1 ; Check if the number is 1 (special case) | |
jne .endif2 ; If not, skip the next instructions | |
mov rax, 2 ; 2 bytes of memory required ($\n) | |
sub rsp, rax ; Make room on the stack | |
mov rbp, rsp ; Set up the base pointer | |
jmp .last$ ; Jump to the last "$" character | |
.endif2: | |
; ===== Reserve Memory on the Stack ===== | |
; We divide the number of lines by 2 because there is a relation between | |
; this and the number of bytes of memory required (see next comment) | |
; This is why all diamonds have an odd number of lines, and an even input | |
; number N produces the same output as N+1 (odd) | |
mov rcx, 2 ; Because div cannot be used with immediate values | |
xor rdx, rdx ; Zero rdx to prevent error when dividing | |
div rcx ; Divide the number (rax) in half (we don't need the remainder) | |
; Calculate y for y = 3x^2 + 5x + 2, where x is equal to the input number | |
; divided by 2 (truncated) -- this is the number of bytes of memory | |
; required to store the charactes to be printed (" ", "$", "*" and \n) | |
mov rbx, rax ; Save rax (x) in rbx (hereafter referred to as x) | |
mul rax ; Square rax (x^2) | |
mov rcx, 3 ; Immediate values cannot be used with mul | |
mul rcx ; Multiply by 3 (3x^2) | |
mov rdi, rax ; Save 3x^2 to rdi | |
mov rcx, 5 ; Immediate values cannot be used with mul | |
mov rax, rbx ; Reload x into rax | |
mul rcx ; Multiply by 5 (5x) | |
add rax, rdi ; Add together (5x + 3x^2) | |
add rax, 2 ; Add 2 (3x^2 + 5x + 2) | |
sub rsp, rax ; Make room on the stack | |
; To keep track of location in the stack as characters are written to it | |
; note: rbp is not being used in the usual manner (the stack base pointer) | |
; although by the end of the program it will be pointing there | |
mov rbp, rsp | |
; ===== Write Characters to Memory ===== | |
; These could be optimized away (i.e. put the immediate values in the | |
; subprograms that use them -- memset and oememset) but this way those | |
; subprograms are more generalized and the code is more clear | |
mov r8b, " " ; For writing spaces with memset | |
mov dl, "$" ; For writing "$" characters every second byte (odd) | |
mov dil, "*" ; And "*" characters every other byte (even) with oememset | |
; == First Half of Lines == | |
; Counter initialization | |
xor rsi, rsi ; Zero rsi to be used as the counter | |
; Start of loop | |
.loop1: ; First half of lines (0 to x) | |
cmp rsi, rbx ; Check if the counter (rsi) has reached x | |
je .break ; If so, break out of the loop | |
; Spaces | |
mov rcx, rbx ; Copy x to rcx | |
sub rcx, rsi ; Subtract the counter from it to get the number of spaces | |
call memset ; Write them to the stack | |
; Diamond symbols | |
mov rcx, rsi ; Copy the counter to rcx | |
shl rcx, 1 ; Multiply it by 2 | |
add rcx, 1 ; And add 1 to get the number of symbols | |
call oememset ; Write them to the stack | |
; Newline | |
mov [rbp], byte 0x0a | |
inc rbp | |
; End of loop | |
inc rsi ; Next line | |
jmp .loop1 ; Loop | |
.break: | |
; == Second Half of Lines == | |
; Counter initialization | |
mov rsi, rbx ; Set up the counter once more | |
; Start of loop | |
.loop2: ; Second half of lines (x to 0) | |
test rsi, rsi ; Check if the counter (rsi) has reached 0 | |
jz .finish ; If so, break out of the loop | |
; Spaces | |
mov rcx, rbx ; Copy x to rcx | |
sub rcx, rsi ; Subtract the counter from it to get the number of spaces | |
test rcx, rcx ; Check if there are no spaces (first iteration) | |
; If there are none, skip the next instruction -- this is required | |
; because memset uses the loop instruction, and will write one space | |
; even if rcx is 0, causing a segfault | |
jz .skip | |
call memset ; Write them to the stack | |
.skip: | |
; Diamond symbols | |
mov rcx, rsi ; Copy the counter to rcx | |
shl rcx, 1 ; Multiply it by 2 | |
add rcx, 1 ; Add 1 | |
call oememset ; Write them to the stack | |
; Newline | |
mov [rbp], byte 0x0a | |
inc rbp | |
; End of loop | |
dec rsi ; Next line | |
jmp .loop2 ; Loop | |
.finish: | |
; == Last Line == | |
; Spaces | |
mov rcx, rbx ; Copy x to rcx | |
sub rcx, rsi ; Subtract the counter from it to get the number of spaces | |
call memset ; Write them to the stack | |
.last$: | |
; Diamond symbol (last "$" character) | |
mov [rbp], byte "$" | |
inc rbp | |
; Newline | |
mov [rbp], byte 0x0a | |
inc rbp | |
; ===== Print Diamond and Finish ===== | |
mov rdi, 1 ; Write to stdout = 1 | |
mov rsi, rsp ; Starting address of data | |
mov rdx, rax ; Number of bytes to write | |
; System call write = 4 -- set last to avoid overwriting rax before | |
; setting rdx, because it still contains the number of bytes required | |
mov rax, 0x2000004 | |
syscall ; Invoke the kernel | |
mov rsp, rbp ; Clean up | |
; ===== Quit ===== | |
.quit: | |
mov rax, 0x2000001 ; System call exit = 1 | |
mov rdi, 0 ; Exit success = 0 (successful) | |
syscall ; Invoke the kernel | |
; ---------------------------------------- | |
; ------------- SUBPROGRAMS -------------- | |
; ---------------------------------------- | |
; Args: r8b - the value to write | |
; rbp - the address to begin writing | |
; rcx - the number of bytes to set | |
; Trash: rcx (becomes zero) | |
; Note: rbp becomes rbp+rcx | |
; rcx must be nonzero | |
memset: | |
.loop: | |
mov [rbp], r8b ; Write the byte | |
inc rbp ; Increase the address | |
loop .loop ; Repeat using rcx (number of bytes to write) | |
ret ; Return | |
; Args: dl - the value to write every second byte (odd) | |
; dil - the value to write every other byte (even) | |
; rbp - the address to begin writing | |
; rcx - the number of bytes to set | |
; Trash: rcx (becomes zero) | |
; Note: rbp becomes rbp+rcx | |
; rcx must be nonzero | |
oememset: | |
.loop: | |
test rcx, 1 ; Check if rcx is odd | |
jz .else ; If not, jump to else | |
mov [rbp], dl ; Write the odd byte | |
jmp .endif ; Skip the else block | |
.else: | |
mov [rbp], dil ; Write the even byte | |
.endif: | |
inc rbp ; Increase the address | |
loop .loop ; Repeat using rcx (number of bytes to write) | |
ret ; Return | |
; Args: rdi - address of null-terminated string containing only numerical chars | |
; Return: rax - the numerical representation of the string | |
; Trash: rdi, rsi, rdx, rcx | |
atol: | |
xor rax, rax ; Zero rax; this will hold the number | |
xor rsi, rsi ; Zero rsi because we will be using both sil and rsi | |
mov rcx, 10 ; Because mul cannot be used with immediate values | |
; Loop through each character, convert it to a single digit (0-9), Multiply | |
; the accumulating result in rax by rcx (10) to make room for another digit | |
; and then add the digit -- repeat until null character is reached | |
; Note: assumes numerical input | |
.loop: | |
mov sil, [rdi] ; Get the character (using lower byte of rsi) | |
test sil, sil ; Check if it's null (the end of the string) | |
jz .break ; If so, break out of the loop | |
sub sil, "0" ; Convert it to a number (single digit) | |
mul rcx ; Multiply RAX (result) by ten (destroys rdx) | |
add rax, rsi ; Add the digit | |
inc rdi ; Next character | |
jmp .loop ; Loop | |
.break: | |
ret ; Nothing more to do |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment