├── .gitignore ├── LICENSE ├── Makefile ├── README.md └── wcx64.s /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | 4 | # Libraries 5 | *.lib 6 | *.a 7 | 8 | # Shared objects (inc. Windows DLLs) 9 | *.dll 10 | *.so 11 | *.so.* 12 | *.dylib 13 | 14 | # Executables 15 | wcx64 16 | *.exe 17 | *.out 18 | *.app 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | AS = as 2 | ASFLAGS = --64 3 | LD = ld 4 | LDFLAGS = 5 | 6 | all: wcx64 7 | 8 | wcx64: wcx64.o 9 | $(LD) $(LDFLAGS) -o $@ $< 10 | 11 | %.o: %.s 12 | $(AS) $(ASFLAGS) $< -o $@ 13 | 14 | clean: 15 | rm -f *.o wcx64 a.out 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | wcx64 2 | ===== 3 | 4 | **wcx64** is a simplistic `wc` clone in x64 assembly. Usage: 5 | 6 | ``` 7 | $ wcx64 /tmp/1 /tmp/2 /usr/share/dict/words 8 | 0 1 2 /tmp/1 9 | 2 5 23 /tmp/2 10 | 99171 99171 938848 /usr/share/dict/words 11 | 99173 99177 938873 total 12 | ``` 13 | 14 | When not given any command-line arguments, reads from stdin: 15 | 16 | ``` 17 | $ wcx64 < /tmp/2 18 | 2 5 23 19 | ``` 20 | 21 | Always prints the all three counters: line, word, byte. 22 | -------------------------------------------------------------------------------- /wcx64.s: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # wcx64: a simplistic wc clone in x64 assembly. Usage: 3 | # 4 | # $ wcx64 file1 /path/file2 file3 5 | # 6 | # When not given any command-line arguments, reads from stdin. 7 | # Always prints the all three counters: line, word, byte. 8 | # 9 | # Eli Bendersky (eliben@gmail.com) 10 | # This code is in the public domain 11 | #------------------------------------------------------------------------------- 12 | 13 | #------------- CONSTANTS --------------# 14 | .set READ_SYSCALL, 0 15 | .set WRITE_SYSCALL, 1 16 | .set OPEN_SYSCALL, 2 17 | .set CLOSE_SYSCALL, 3 18 | .set EXIT_SYSCALL, 60 19 | .set STDIN_FD, 0 20 | .set STDOUT_FD, 1 21 | 22 | .set O_RDONLY, 0x0 23 | .set OPEN_NO_MODE, 0x0 24 | .set READBUFLEN, 16384 25 | .set ITOABUFLEN, 12 26 | .set NEWLINE, '\n' 27 | .set CR, '\r' 28 | .set TAB, '\t' 29 | .set SPACE, ' ' 30 | 31 | #---------------- DATA ----------------# 32 | .data 33 | 34 | newline_str: 35 | .asciz "\n" 36 | 37 | fourspace_str: 38 | .asciz " " 39 | 40 | total_str: 41 | .asciz "total" 42 | 43 | buf_for_read: 44 | # leave space for terminating 0 45 | .space READBUFLEN + 1, 0x0 46 | 47 | # The itoa buffer here is large enough to hold just 11 digits (plus one 48 | # byte for the terminating null). For the wc counters this is enough 49 | # because it lets us represent 10-digit numbers (up to 10 GB) 50 | # with spaces in between. 51 | # Note: this is an artificial limitation for simplicity in printing out the 52 | # counters; this size can be easily increased. 53 | buf_for_itoa: 54 | .space ITOABUFLEN, 0x0 55 | .set endbuf_for_itoa, buf_for_itoa + ITOABUFLEN - 1 56 | 57 | #---------------- "MAIN" CODE ----------------# 58 | .globl _start 59 | .text 60 | 61 | _start: 62 | # If there are no argv, go to .L_no_argv for reading from 63 | # stdin. 64 | mov (%rsp), %rbx # (%rsp) is argc 65 | cmp $1, %rbx 66 | jle .L_no_argv 67 | 68 | xor %r13, %r13 69 | xor %r14, %r14 70 | xor %r15, %r15 71 | 72 | # In a loop, argv[n] for 1 <= n < argc; rbp holds n. 73 | mov $1, %rbp 74 | 75 | .L_argv_loop: 76 | # Throughout the loop, register assignments: 77 | # r12: argv[n]. Also gets into rdi for passing into the open() syscall 78 | # rbp: argv counter n 79 | # rbx: holds argc 80 | # r13, r14, r15: total numbers counted in all files. 81 | mov 8(%rsp, %rbp, 8), %rdi # argv[n] is in (rsp + 8 + 8*n) 82 | mov %rdi, %r12 83 | 84 | # Call open(argv[n], O_RDONLY). 85 | mov $O_RDONLY, %rsi 86 | mov $OPEN_NO_MODE, %rdx 87 | mov $OPEN_SYSCALL, %rax 88 | syscall 89 | 90 | # Ignore files that can't be opened 91 | cmp $0, %rax 92 | jl .L_next_argv 93 | push %rax # save fd on the stack 94 | 95 | mov %rax, %rdi 96 | call count_in_file 97 | 98 | # Add the counters returned from count_in_file to the totals and pass 99 | # them to print_counters. 100 | mov %rax, %rdi 101 | add %rax, %r13 102 | mov %rdx, %rsi 103 | add %rdx, %r14 104 | mov %r9, %rdx 105 | add %r9, %r15 106 | mov %r12, %rcx 107 | call print_counters 108 | 109 | # Call close(argv[n]) 110 | pop %rdi # restore fd from the stack 111 | mov $CLOSE_SYSCALL, %rax 112 | syscall 113 | 114 | .L_next_argv: 115 | inc %rbp 116 | cmp %rbx, %rbp 117 | jl .L_argv_loop 118 | 119 | # Done with all argv. Now print out the totals. 120 | mov %r13, %rdi 121 | mov %r14, %rsi 122 | mov %r15, %rdx 123 | lea total_str, %rcx 124 | call print_counters 125 | 126 | jmp .L_wcx64_exit 127 | 128 | .L_no_argv: 129 | # Read from stdin, which is file descriptor 0. 130 | mov $STDIN_FD, %rdi 131 | call count_in_file 132 | 133 | # Print the counters without a name string 134 | mov %rax, %rdi 135 | mov %rdx, %rsi 136 | mov %r9, %rdx 137 | mov $0, %rcx 138 | call print_counters 139 | 140 | .L_wcx64_exit: 141 | # exit(0) 142 | mov $0, %rdi 143 | mov $EXIT_SYSCALL, %rax 144 | syscall 145 | ret 146 | 147 | #---------------- FUNCTIONS ----------------# 148 | 149 | # Function count_in_file 150 | # Counts chars, words and lines for a single file. 151 | # 152 | # Arguments: 153 | # rdi file descriptor representing an open file. 154 | # 155 | # Returns: 156 | # rax line count 157 | # rdx word count 158 | # r9 char count 159 | count_in_file: 160 | # Save callee-saved registers. 161 | push %r12 162 | push %r13 163 | push %r14 164 | push %r15 165 | 166 | # Register usage within the function: 167 | # 168 | # rdi: holds the fd 169 | # r9: char counter 170 | # r15: word counter 171 | # r14: line counter 172 | # r13: address of the read buffer 173 | # rcx: loop index for going over a read buffer 174 | # dl: next byte read from the buffer 175 | # r12: state indicator, with the states defined below. 176 | # the word counter is incremented when we switch from 177 | # IN_WHITESPACE to IN_WORD. 178 | .set IN_WORD, 1 179 | .set IN_WHITESPACE, 2 180 | 181 | # In addition, rsi, rdx, rax are used in the call to read(). 182 | # After each call to read(), rax is used for its return value. 183 | xor %r9, %r9 184 | xor %r15, %r15 185 | xor %r14, %r14 186 | lea buf_for_read, %r13 187 | mov $IN_WHITESPACE, %r12 188 | 189 | .L_read_buf: 190 | # Call read(fd, buf_for_read, READBUFLEN). rdi already contains fd 191 | mov %r13, %rsi 192 | mov $READBUFLEN, %rdx 193 | mov $READ_SYSCALL, %rax 194 | syscall 195 | 196 | # From here on, rax holds the number of bytes actually read from the 197 | # file (the return value of read()) 198 | add %rax, %r9 # Update the char counter 199 | 200 | cmp $0, %rax # No bytes read? 201 | je .L_done_with_file 202 | 203 | xor %rcx, %rcx 204 | .L_next_byte_in_buf: 205 | movb (%r13, %rcx, 1), %dl # Read the byte 206 | 207 | # See what we've got and jump to the appropriate label. 208 | cmp $NEWLINE, %dl 209 | je .L_seen_newline 210 | cmp $CR, %dl 211 | je .L_seen_whitespace_not_newline 212 | cmp $SPACE, %dl 213 | je .L_seen_whitespace_not_newline 214 | cmp $TAB, %dl 215 | je .L_seen_whitespace_not_newline 216 | # else, it's not whitespace but a part of a word. 217 | 218 | # If we're in a word already, nothing else to do. 219 | cmp $IN_WORD, %r12 220 | je .L_done_with_this_byte 221 | # else, transition from IN_WHITESPACE to IN_WORD: increment the word 222 | # counter. 223 | inc %r15 224 | mov $IN_WORD, %r12 225 | jmp .L_done_with_this_byte 226 | 227 | .L_seen_newline: 228 | # Increment the line counter and fall through. 229 | inc %r14 230 | 231 | .L_seen_whitespace_not_newline: 232 | cmp $IN_WORD, %r12 233 | je .L_end_current_word 234 | # Otherwise, still in whitespace. 235 | jmp .L_done_with_this_byte 236 | 237 | .L_end_current_word: 238 | mov $IN_WHITESPACE, %r12 239 | 240 | .L_done_with_this_byte: 241 | # Advance read pointer and check if we haven't finished with the read 242 | # buffer yet. 243 | inc %rcx 244 | cmp %rcx, %rax 245 | jg .L_next_byte_in_buf 246 | 247 | # Done going over this buffer. We need to read another buffer 248 | # if rax == READBUFLEN. 249 | cmp $READBUFLEN, %rax 250 | je .L_read_buf 251 | 252 | .L_done_with_file: 253 | # Done with this file. The char count is already in r9. 254 | # Put the word and line counts in their return locations. 255 | mov %r15, %rdx 256 | mov %r14, %rax 257 | 258 | # Restore callee-saved registers. 259 | pop %r15 260 | pop %r14 261 | pop %r13 262 | pop %r12 263 | ret 264 | 265 | # Function print_cstring 266 | # Print a null-terminated string to stdout. 267 | # 268 | # Arguments: 269 | # rdi address of string 270 | # 271 | # Returns: void 272 | print_cstring: 273 | # Find the terminating null 274 | mov %rdi, %r10 275 | .L_find_null: 276 | cmpb $0, (%r10) 277 | je .L_end_find_null 278 | inc %r10 279 | jmp .L_find_null 280 | 281 | .L_end_find_null: 282 | # r10 points to the terminating null. so r10-rdi is the length 283 | sub %rdi, %r10 284 | # Now that we have the length, we can call sys_write 285 | # sys_write(unsigned fd, char* buf, size_t count) 286 | mov $WRITE_SYSCALL, %rax 287 | # Populate address of string into rsi first, because the later 288 | # assignment of fd clobbers rdi. 289 | mov %rdi, %rsi 290 | mov $STDOUT_FD, %rdi 291 | mov %r10, %rdx 292 | syscall 293 | ret 294 | 295 | # Function print_counters 296 | # Print three counters with an optional name to stdout. 297 | # 298 | # Arguments: 299 | # rdi, rsi, rdx: the counters 300 | # rcx: address of the name C-string. If 0, no name is printed. 301 | # 302 | # Returns: void 303 | print_counters: 304 | push %r14 305 | push %r15 306 | push %rdx 307 | push %rsi 308 | push %rdi 309 | # rcx can be clobbered by callees, so save it in %r14. 310 | mov %rcx, %r14 311 | 312 | # r15 is the counter pointer, running over 0, 1, 2 313 | # counter N is at (rsp + 8 * r15) 314 | xor %r15, %r15 315 | 316 | .L_print_next_counter: 317 | # Fill the itoa buffer with spaces. 318 | lea buf_for_itoa, %rdi 319 | mov $SPACE, %rsi 320 | mov $ITOABUFLEN, %rdx 321 | call memset 322 | # Convert the next counter and then call print_cstring with the 323 | # beginning of the itoa buffer - because we want space-prefixed 324 | # output. 325 | mov (%rsp, %r15, 8), %rdi 326 | lea endbuf_for_itoa, %rsi 327 | call itoa 328 | lea buf_for_itoa, %rdi 329 | call print_cstring 330 | inc %r15 331 | cmp $3, %r15 332 | jl .L_print_next_counter 333 | 334 | # If name address is not 0, print out the given null-terminated string 335 | # as well. 336 | cmp $0, %r14 337 | je .L_print_counters_done 338 | lea fourspace_str, %rdi 339 | call print_cstring 340 | mov %r14, %rdi 341 | call print_cstring 342 | 343 | .L_print_counters_done: 344 | lea newline_str, %rdi 345 | call print_cstring 346 | pop %rdi 347 | pop %rsi 348 | pop %rdx 349 | pop %r15 350 | pop %r14 351 | ret 352 | 353 | # Function memset 354 | # Fill memory with some byte 355 | # 356 | # Arguments: 357 | # rdi: pointer to memory 358 | # rsi: fill byte (in the low 8 bits) 359 | # rdx: how many bytes to fill 360 | # 361 | # Returns: void 362 | memset: 363 | xor %r10, %r10 364 | 365 | .L_next_byte: 366 | movb %sil, (%rdi, %r10, 1) # sil is rsi's low 8 bits 367 | inc %r10 368 | cmp %rdx, %r10 369 | jl .L_next_byte 370 | ret 371 | 372 | # Function itoa 373 | # Convert an integer to a null-terminated string in memory. 374 | # Assumes that there is enough space allocated in the target 375 | # buffer for the representation of the integer. Since the number itself 376 | # is accepted in the register, its value is bounded. 377 | # 378 | # Arguments: 379 | # rdi: the integer 380 | # rsi: address of the *last* byte in the target buffer. bytes will be filled 381 | # starting with this address and proceeding lower until the number 382 | # runs out. 383 | # 384 | # Returns: 385 | # rax: address of the first byte in the target string that 386 | # contains valid information. 387 | itoa: 388 | movb $0, (%rsi) # Write the terminating null and advance. 389 | 390 | # If the input number is negative, we mark it by placing 1 into r9 391 | # and negate it. In the end we check if r9 is 1 and add a '-' in front. 392 | mov $0, %r9 393 | cmp $0, %rdi 394 | jge .L_input_positive 395 | neg %rdi 396 | mov $1, %r9 397 | 398 | .L_input_positive: 399 | 400 | mov %rdi, %rax # Place the number into rax for the division. 401 | mov $10, %r8 # The base is in r8 402 | 403 | .L_next_digit: 404 | # Prepare rdx:rax for division by clearing rdx. rax remains from the 405 | # previous div. rax will be rax / 10, rdx will be the next digit to 406 | # write out. 407 | xor %rdx, %rdx 408 | div %r8 409 | 410 | # Write the digit to the buffer, in ascii 411 | dec %rsi 412 | add $0x30, %dl 413 | movb %dl, (%rsi) 414 | 415 | cmp $0, %rax # We're done when the quotient is 0. 416 | jne .L_next_digit 417 | 418 | # If we marked in r9 that the input is negative, it's time to add that 419 | # '-' in front of the output. 420 | cmp $1, %r9 421 | jne .L_itoa_done 422 | dec %rsi 423 | movb $0x2d, (%rsi) 424 | 425 | .L_itoa_done: 426 | mov %rsi, %rax # rsi points to the first byte now; return it. 427 | ret 428 | --------------------------------------------------------------------------------