;; Common Library Functions global strlen global strstr global strcmp global strfind global memcpy global is_alpha global is_digit global is_alphanumeric global is_id_start global is_id_cont global to_digit global parse_i64 global parse_i64_cstr global exit global fasthash section .text ;; fn strlen(s: *const u8) -> usize strlen: xor eax, eax .loop: cmp byte [rdi + rax], 0 je .done inc rax jmp .loop .done: ret ;; find in the first $rsi bytes of $rdi (haystack) the first $rcx bytes of $rdx (needle). ;; returns the index in $rax, or -1 if not found. ;; fn strstr(haystack: *const u8, haystack_len: usize, needle: *const u8, needle_len: usize) -> isize strstr: push rbx push r12 push r13 mov r13, rdi ; save original haystack pointer mov rax, rsi ; remaining haystack length .loop_haystack: cmp rax, rcx jb .not_found ; not enough haystack left for needle xor rbx, rbx ; needle index .loop_needle: cmp rbx, rcx jz .found ; found the needle mov r12b, byte [rdx + rbx] ; needle[needle_index] cmp r12b, byte [rdi + rbx] ; haystack[haystack_index + needle_index] jne .next_haystack ; mismatch, move to next haystack index inc rbx jmp .loop_needle .next_haystack: inc rdi ; move to next haystack index dec rax ; decrease remaining haystack length jmp .loop_haystack .not_found: xor eax, eax dec rax jmp .done .found: sub rdi, r13 ; calculate the index of the found needle mov rax, rdi .done: pop r13 pop r12 pop rbx ret ;; fn strcmp(s1: *const u8, s1_len: usize, s2: *const u8, s2_len: usize) -> i32 strcmp: cmp rcx, rsi cmovb rsi, rcx ; if rhs is shorter, use its length for the loop xor eax, eax .strcmp_loop: cmp rsi, rax jz .strcmp_equal movzx ecx, byte [rdx + rax] cmp byte [rdi + rax], cl lea rax, [rax + 1] je .strcmp_loop seta al ; al = lhs > rhs sbb al, 0 ; al = al - CF ret .strcmp_equal: xor eax, eax ret ;; fn strfind(s: *const u8, len: usize, c: u8) -> isize strfind: strch: xor eax, eax .loop: cmp rax, rsi jge .not_found mov cl, byte [rdi + rax] cmp cl, dl je .found inc rax jmp .loop .not_found: xor eax, eax dec rax .found: ret ;; fn memcpy(src: *const u8, dst: *mut u8, len: usize) memcpy: .loop: test rdx, rdx jz .done mov al, byte [rdi] mov byte [rsi], al inc rsi inc rdi dec rdx jmp .loop .done: ret ;; fn is_alpha(c: u8) -> bool is_alpha: movzx eax, dil and eax, 0x1fffdf ; ignore bit 5 (case) add eax, -65 ; subtract 'A' cmp eax, 26 ; check if in range 0-25 setb al ret ;; fn is_digit(c: u8) -> bool is_digit: movzx edi, dil lea eax, [rdi - 48] ; subtract '0' cmp eax, 10 ; check if in range 0-9 setb al ret ;; fn is_alphanumeric(c: u8) -> bool is_alphanumeric: lea eax, [rdi - 48] cmp eax, 10 setb cl and dil, 0xdf add dil, -65 cmp dil, 26 setb al or al, cl ret ;; fn is_id_start(c: u8) -> bool is_id_start: call is_alpha cmp dil, 95 sete cl or al, cl ret ;; fn is_id_continue(c: u8) -> bool is_id_cont: call is_alphanumeric cmp dil, 45 sete cl or al, cl cmp dil, 95 sete cl or al, cl ret ;; converts char $dil to a digit with radix $rsi, returning it in $edx. $al is set to 1 if the char is a valid digit, and 0 otherwise. ;; fn to_digit(c: u8, radix: u8) -> (is_valid: bool, digit: u8) to_digit: lea eax, [rsi - 2] cmp eax, 35 jae .invalid movzx rdi, dil lea edx, [rdi - 65] ; 'A' = 65 and edx, -33 ; convert to uppercase add edx, 10 ; 'A' should map to 10 lea eax, [rdi - 48] ; '0' = 48 cmp esi, 11 cmovb edx, eax ; if radix <= 10, then take the difference from '0' cmp edi, 58 cmovb edx, eax ; or if char < '9', then take the difference from '0' xor eax, eax cmp edx, esi setb al ; al = edx < radix ret .invalid: xor eax, eax ret ;; fn parse_i64(s: *const u8) -> (is_valid: bool, value: i64) parse_i64_cstr: call strlen mov rsi, rax jmp parse_i64 ;; fn parse_i64(s: *const u8, len: usize) -> (is_valid: bool, value: i64) parse_i64: push rbp mov rbp, rsp push rbx ; buf push r12 ; radix push r13 ; acc push r14 ; end push rdi sub rsp, 8 mov dword [rsp], 1 ; sign = 1 mov rbx, rdi ; buf mov r12, 10 ; radix xor r13, r13 ; acc xor eax, eax mov r14, rsi add r14, rdi ; end = buf + len cmp rbx, r14 jge .done ; if buf >= end, return 0 ; eat leading - cmp byte [rbx], '-' sete al lea rbx, [rbx + rax] shl eax, 1 sub dword [rsp], eax cmp rbx, r14 jge .done ; eat leading + cmp byte [rbx], '+' sete al lea rbx, [rbx + rax] cmp rbx, r14 jge .done mov dil, byte [rbx] mov esi, 10 call to_digit jnb .not_numeric ; radix cmp dil, '0' jne .loop inc rbx cmp rbx, r14 jge .done mov dil, byte [rbx] and dil, 0xdf ; convert to uppercase cmp dil, 'X' sete al lea ecx, [eax + eax * 2] shl ecx, 1 ; multiply by 6 add r12d, ecx ; radix += 'x' ? 6 : 0 cmp dil, 'O' sete al shl al, 1 ; multiply by 2 sub r12d, eax ; radix -= 'o' ? 2 : 0 cmp dil, 'B' sete al shl al, 3 ; multiply by 8 sub r12d, eax ; radix -= 'b' ? 8 : 0 cmp r12d, 10 setne al lea rbx, [rbx + rax] ; skip the radix prefix if it was present .loop: cmp rbx, r14 jge .done mov dil, byte [rbx] inc rbx cmp dil, '_' je .loop mov esi, r12d call to_digit mov ecx, edx test al, al jz .done mov rax, r13 imul r12 add rax, rcx mov r13, rax jmp .loop .not_numeric: xor eax, eax xor edx, edx add rsp, 8 pop rdi jmp .epilogue .done: mov rdx, rax neg r13 cmp dword [rsp], 0 cmovl rdx, r13 add rsp, 8 pop rdi sub rbx, rdi mov rax, rbx .epilogue: pop r14 pop r13 pop r12 pop rbx pop rbp ret ;; fn exit() -> ! exit: xor edi, edi mov rax, 60 syscall ;; fn fasthash(s: *const u8, len: usize) -> u32 fasthash: mov eax, 0x811C9DC5 ; FNV-1a 32-bit offset basis .loop: test rsi, rsi jz .done movzx ecx, byte [rdi] xor eax, ecx imul eax, 0x01000193 ; FNV prime inc rdi dec rsi jmp .loop .done: ret