from-scratch/stages/shared/lib.asm
2026-07-14 16:36:54 +02:00

344 lines
7 KiB
NASM

;; 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