from-scratch/stages/lisp0/lisp.asm

1670 lines
39 KiB
NASM

section .bss
buf resb 0x100
align 8,db 0
atoms times 3 resb 8
env resq 1
;;
section .text
global get_env
get_env:
mov rax, qword [rel env]
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.
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
;; rdi: *u8
strlen:
xor rax, rax
.strlen_loop:
cmp byte [rdi + rax], 0
je .strlen_done
inc rax
jmp .strlen_loop
.strlen_done:
ret
;; @param lhs: (rdi, rsi)
;; @param rhs: (rdx, rcx)
;; @return al
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
je .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
;; rdi: src
;; rsi: dst
;; rdx: len
memcpy:
.memcpy_loop:
test rdx, rdx
jz .memcpy_done
mov al, byte [rdi]
mov byte [rsi], al
inc rsi
inc rdi
dec rdx
jmp .memcpy_loop
.memcpy_done:
ret
;; fn getc(*const opaque) -> u8
extern getc
;; fn peekc(*const opaque) -> u8
extern peekc
;; fn panic(msg: *const u8) -> !
extern panic_abort
;; fn alloc(size: usize, align: usize) -> *mut u8
extern alloc
;; fn dealloc(ptr: *mut u8, size: usize, align: usize)
extern dealloc
align 8,db 0
nil dq 1 ; the nil object, with refcount = 1
OBJ_BYTE equ 0
OBJ_CONS equ 1
OBJ_CLOS equ 2
OBJ_ATOM equ 3
OBJ_NUM equ 4
OBJ_PRIM equ 5
OBJ_STR equ 6
OBJ_ARR equ 7
ATOM_QUOTE equ 0
ATOM_TRUE equ 1
ATOM_PLUS equ 2
ATOM_MINUS equ 3
ATOM_MUL equ 4
ATOM_DIV equ 5
ATOM_REM equ 6
ATOM_LT equ 7
ATOM_EQ equ 8
ATOM_CAR equ 9
ATOM_CDR equ 10
ATOM_NOT equ 11
QUOTE_STR db "quote"
QUOTE_STR_LEN equ $ - QUOTE_STR
TRUE_STR db "t"
TRUE_STR_LEN equ $ - TRUE_STR
PLUS_STR db "+"
PLUS_STR_LEN equ $ - PLUS_STR
MINUS_STR db "-"
MINUS_STR_LEN equ $ - MINUS_STR
MUL_STR db "*"
MUL_STR_LEN equ $ - MUL_STR
DIV_STR db "/"
DIV_STR_LEN equ $ - DIV_STR
REM_STR db "%"
REM_STR_LEN equ $ - REM_STR
LT_STR db "<"
LT_STR_LEN equ $ - LT_STR
EQ_STR db "="
EQ_STR_LEN equ $ - EQ_STR
CAR_STR db "car"
CAR_STR_LEN equ $ - CAR_STR
CDR_STR db "cdr"
CDR_STR_LEN equ $ - CDR_STR
NOT_STR db "not"
NOT_STR_LEN equ $ - NOT_STR
;; folds all elements in the list $rdi using the binary function $rdx in the environment $rsi with an accumulator value of $rcx, returning the result in $rax
;; fn fold_list(List<T>, Env, (U, T, Env) -> U, U) -> U
fold_list:
sub rsp, 32
mov qword [rsp], rdi ; rest
mov qword [rsp + 8], rsi ; env
mov qword [rsp + 16], rcx ; acc
mov qword [rsp + 24], rdx ; func
.loop:
mov rdi, qword [rsp]
call get_cdr
mov qword [rsp], rax ; update rest to cdr
call get_car
mov rsi, rax
mov rdi, qword [rsp + 16] ; $rdi = acc
mov rdx, qword [rsp + 8] ; $rdx = env
mov rax, qword [rsp + 24] ; $rax = func
call rax
mov qword [rsp + 16], rax ; update acc to result of func(acc, car)
mov rdi, qword [rsp] ; $rdi = rest
lea rsi, [rel nil]
cmp rdi, rsi
jne .loop
mov rax, qword [rsp + 16] ; return acc
add rsp, 32
ret
;; like fold_list, but requires that the list is non-empty
;; fn reduce_list(List<T>, Env, (T, T, Env) -> T) -> T
reduce_list:
call get_car
mov rcx, rax ; acc = car(list)
call get_cdr ; list = cdr(list)
lea rdi, [rel nil]
cmp rax, rdi
je .done
mov rdi, rax
call fold_list
ret
.done:
mov rax, rcx
ret
;; like reduce_list, calls a predicate instead of a binary function, and returns "t" if the predicate returns non-zero for any pair of adjacent elements, and nil otherwise
;; predicate: fn (T, T, Env) -> bool
any2_list:
sub rsp, 24
mov qword [rsp], rdi ; rest
mov qword [rsp + 8], rsi ; env
mov qword [rsp + 16], rdx ; pred
.loop:
mov rdi, qword [rsp]
call get_cdr
lea rsi, [rel nil]
cmp rax, rsi
je .false
mov qword [rsp], rax ; update rest to cdr
call get_car
mov rsi, rax
mov rdi, qword [rsp]
call get_car
mov rdi, rax
xchg rsi, rdi
mov rdx, qword [rsp + 8] ; $rdx = env
mov rax, qword [rsp + 16] ; $rax = func
call rax
test al, al
jnz .true
jmp .loop
.true:
mov al, 1
add rsp, 24
ret
.false:
xor al, al
add rsp, 24
ret
;; |a, b| {a += b; a}
p_add_inner:
xchg rdi, rsi
call obj_tag_part
cmp al, OBJ_NUM
jne .invalid
call obj_addr_part
mov rcx, rax ; b.addr()
mov rdi, rsi
call obj_tag_part
cmp al, OBJ_NUM
jne .invalid
call obj_addr_part
mov rcx, qword [rcx + 8] ; b.num
add qword [rax + 8], rcx ; a.num += b.num
mov rax, rdi
ret
.invalid:
xor rdi, rdi
call panic_abort
;; |a, b| {a -= b; a}
p_sub_inner:
xchg rdi, rsi
call obj_tag_part
cmp al, OBJ_NUM
jne .invalid
call obj_addr_part
mov rcx, rax ; b.addr()
mov rdi, rsi
call obj_tag_part
cmp al, OBJ_NUM
jne .invalid
call obj_addr_part
mov rcx, qword [rcx + 8] ; b.num
sub qword [rax + 8], rcx ; a.num -= b.num
mov rax, rdi
ret
.invalid:
xor rdi, rdi
call panic_abort
;; |a, b| {a *= b; a}
p_imul_inner:
xchg rdi, rsi
call obj_tag_part
cmp al, OBJ_NUM
jne .invalid
call obj_addr_part
mov rcx, rax ; b.addr()
mov rdi, rsi
call obj_tag_part
cmp al, OBJ_NUM
jne .invalid
call obj_addr_part
mov rcx, qword [rcx + 8] ; b.num
mov rsi, rax ; a.addr()
mov rax, qword [rsi + 8] ; a.num
imul rcx ; a.num * b.num
mov qword [rsi + 8], rax ; a.num = a.num * b.num
mov rax, rdi
ret
.invalid:
xor rdi, rdi
call panic_abort
;; |a, b| {a /= b; a}
p_idiv_inner:
xchg rdi, rsi
call obj_tag_part
cmp al, OBJ_NUM
jne .invalid
call obj_addr_part
mov rcx, rax ; b.addr()
mov rdi, rsi
call obj_tag_part
cmp al, OBJ_NUM
jne .invalid
call obj_addr_part
mov rcx, qword [rcx + 8] ; b.num
mov rsi, rax ; a.addr()
mov rax, qword [rsi + 8] ; a.num
xor rdx, rdx
idiv rcx ; a.num / b.num
mov qword [rsi + 8], rax ; a.num = a.num / b.num
mov rax, rdi
ret
.invalid:
xor rdi, rdi
call panic_abort
;; |a, b| {a %= b; a}
p_irem_inner:
xchg rdi, rsi
call obj_tag_part
cmp al, OBJ_NUM
jne .invalid
call obj_addr_part
mov rcx, rax ; b.addr()
mov rdi, rsi
call obj_tag_part
cmp al, OBJ_NUM
jne .invalid
call obj_addr_part
mov rcx, qword [rcx + 8] ; b.num
mov rsi, rax ; a.addr()
mov rax, qword [rsi + 8] ; a.num
xor rdx, rdx
idiv rcx ; a.num % b.num
mov qword [rsi + 8], rdx ; a.num = a.num % b.num
mov rax, rdi
ret
.invalid:
xor rdi, rdi
call panic_abort
;; |a, b| {a < b}
p_lt_inner:
call obj_tag_part
cmp al, OBJ_NUM
jne .invalid
call obj_addr_part
mov rcx, rax ; a.addr()
mov rdi, rsi
call obj_tag_part
cmp al, OBJ_NUM
jne .invalid
call obj_addr_part
mov rdx, qword [rax + 8] ; b.num
mov rax, qword [rcx + 8] ; a.num
cmp rax, rdx
lea rax, [rel nil]
lea rcx, [rel atoms]
mov rdx, [rcx + ATOM_TRUE * 8] ; get the "t" atom
cmovl rax, rdx ; if a < b, return "t", else return nil
ret
.invalid:
xor rdi, rdi
call panic_abort
p_neq_inner:
call obj_eq
ret
p_lt:
call eval_list
mov rdi, rax
call get_cdr
mov rsi, rax
call get_car
mov rcx, rax
mov rdi, rsi
call get_car
mov rsi, rax
mov rdi, rcx
call p_lt_inner
ret
p_eq:
push rsi
call eval_list
mov rdi, rax
pop rsi
lea rdx, [rel p_neq_inner]
call any2_list ; list.any(|a, b| a != b)
test al, al ; 1 if any pair is unequual
lea rax, [rel nil]
lea rsi, [rel atoms]
cmovz rax, [rsi + ATOM_TRUE * 8] ; if no pair is unequal, return "t", else return nil
ret
;; sum list $rdi in env $rsi
p_add:
sub rsp, 24
mov qword [rsp + 8], rsi ; env
call eval_list
mov qword [rsp], rax ; evaled list
mov rdi, 16
mov rsi, 8
call alloc
mov rdi, rax
mov dword [rdi], 1 ; refcount = 1
mov qword [rdi + 8], 0 ; value = 0
mov rsi, OBJ_NUM
call obj_set_tag
mov rcx, rax ; acc
mov rdi, qword [rsp] ; $rdi = evaled list
mov rsi, qword [rsp + 8] ; $rsi = env
lea rdx, [rel p_add_inner]
call fold_list
add rsp, 24
ret
p_sub:
sub rsp, 24
mov qword [rsp + 8], rsi ; env
call eval_list
mov qword [rsp], rax ; evaled list
mov rdi, 16
mov rsi, 8
call alloc
mov rdi, rax
mov dword [rdi], 1 ; refcount = 1
mov qword [rdi + 8], 0 ; value = 0
mov rsi, OBJ_NUM
call obj_set_tag
mov rcx, rax ; acc
mov rdi, qword [rsp] ; $rdi = evaled list
mov rsi, qword [rsp + 8] ; $rsi = env
lea rdx, [rel p_sub_inner]
call fold_list
add rsp, 24
ret
p_mul:
sub rsp, 24
mov qword [rsp + 8], rsi ; env
call eval_list
mov qword [rsp], rax ; evaled list
mov rdi, 16
mov rsi, 8
call alloc
mov rdi, rax
mov dword [rdi], 1 ; refcount = 1
mov qword [rdi + 8], 1 ; value = 1
mov rsi, OBJ_NUM
call obj_set_tag
mov rcx, rax ; acc
mov rdi, qword [rsp] ; $rdi = evaled list
mov rsi, qword [rsp + 8] ; $rsi = env
lea rdx, [rel p_imul_inner]
call fold_list
add rsp, 24
ret
p_div:
sub rsp, 24
mov qword [rsp + 8], rsi ; env
call eval_list
mov qword [rsp], rax ; evaled list
mov rdi, qword [rsp] ; $rdi = evaled list
mov rsi, qword [rsp + 8] ; $rsi = env
lea rdx, [rel p_idiv_inner]
call reduce_list
add rsp, 24
ret
p_rem:
sub rsp, 24
mov qword [rsp + 8], rsi ; env
call eval_list
mov qword [rsp], rax ; evaled list
mov rdi, qword [rsp] ; $rdi = evaled list
mov rsi, qword [rsp + 8] ; $rsi = env
lea rdx, [rel p_irem_inner]
call reduce_list
add rsp, 24
ret
p_car:
call eval_list
mov rdi, rax
call get_car
mov rdi, rax
call get_car
ret
p_cdr:
call eval_list
mov rdi, rax
call get_car
mov rdi, rax
call get_cdr
ret
p_not:
call eval_list
mov rdi, rax
call get_car
lea rsi, [rel nil]
cmp rsi, rax
lea rax, qword [rel atoms]
mov rax, [rax + ATOM_TRUE * 8] ; get the "t" atom
cmovne rax, rsi
ret
p_quote:
call get_car
ret
p_lambda:
push rsi
call get_car
push rax ; params
call get_cdr
mov rdi, rax
call get_car
mov rsi, rax ; body
pop rdi ; params
pop rdx
call clos
ret
p_define:
call get_car
push rax ; params
call get_cdr
mov rdi, rax
call eval
mov rsi, rax ; evaled value
mov rdi, qword [rsp] ; name
call cons
mov rdi, rax
mov rsi, [rel env]
call cons
mov [rel env], rax
pop rax
ret
;; (if cond then else)
;; cond = car($rdi)
;; then = car(cdr($rdi))
;; else = car(cdr(cdr($rdi)))
;; result = eval(cond) ? eval(then) : eval(else)
p_if:
; eval(car(cdr(not(eval(car($rdi), $rsi)) ? cdr(rdi) : $rdi)) $rsi)
sub rsp, 16
mov qword [rsp], rdi ; save $rdi = arg
mov qword [rsp + 8], rsi ; save $rsi = env
call get_car
mov rdi, rax
call eval
mov rdi, rax
call is_nil
mov cl, al
mov rdi, qword [rsp] ; restore $rdi = arg
call get_cdr
mov rdi, rax
call get_car
mov rsi, rax
call get_cdr
test cl, cl
cmovnz rsi, rax
mov rdi, rsi
call get_car
mov rdi, rax
mov rsi, qword [rsp + 8] ; restore $rsi = env
call eval
add rsp, 16
ret
;; (Env, (var val), Env) -> Env
;; $rdi = acc-env, $rsi = (var val), $rdx = genv
p_let_inner:
sub rsp, 24
mov qword [rsp], rdi ; acc-env
mov qword [rsp + 8], rsi ; (var val)
mov qword [rsp + 16], rdx ; genv
call get_cdr
mov rdi, rax
call get_car
mov rdi, rax
mov rsi, qword [rsp + 16] ; genv
call eval
mov rsi, rax ; val
mov rdi, qword [rsp + 8] ; (var val)
call get_car
mov rdi, rax ; var
call cons
mov rdi, rax
mov rsi, qword [rsp] ; acc-env
call cons
add rsp, 24
ret
;; (Env, (var val), Env) -> Env
;; $rdi = acc-env, $rsi = (var val), $rdx = genv
p_let_star_inner:
sub rsp, 24
mov qword [rsp], rdi ; acc-env
mov qword [rsp + 8], rsi ; (var val)
call get_cdr
mov rdi, rax
call get_car
mov rdi, rax
mov rsi, qword [rsp] ; genv
call eval
mov rsi, rax ; val
mov rdi, qword [rsp + 8] ; (var val)
call get_car
mov rdi, rax ; var
call cons
mov rdi, rax
mov rsi, qword [rsp] ; acc-env
call cons
add rsp, 24
ret
;; (let ((var1 val1) (var2 val2) ...) body)
;; $rdi = (bindings expr) where bindings = ((var1 val1) (var2 val2) ...)
p_let:
sub rsp, 24
call get_car
mov qword [rsp], rax ; bindings
call get_cdr
mov rdi, rax
call get_car
mov qword [rsp + 8], rax ; body
mov qword [rsp + 16], rsi ; in_env
mov rdi, qword [rsp] ; $rdi = bindings
mov rsi, qword [rsp + 16] ; $rsi = in_env
mov rcx, rsi
lea rdx, [rel p_let_inner]
call fold_list
mov rsi, rax ; body_env = fold_list(...)
mov rdi, qword [rsp + 8] ; $rdi =
call eval
add rsp, 24
ret
p_let_star:
sub rsp, 24
call get_car
mov qword [rsp], rax ; bindings
call get_cdr
mov rdi, rax
call get_car
mov qword [rsp + 8], rax ; body
mov qword [rsp + 16], rsi ; in_env
mov rdi, qword [rsp] ; $rdi = bindings
mov rsi, qword [rsp + 16] ; $rsi = in_env
mov rcx, rsi
lea rdx, [rel p_let_star_inner]
call fold_list
mov rsi, rax ; body_env = fold_list(...)
mov rdi, qword [rsp + 8] ; $rdi =
call eval
add rsp, 24
ret
; p_let_rec:
global init_env
init_env:
call init_atoms
lea rdi, [rel atoms]
mov rsi, [rdi + ATOM_TRUE * 8] ; get the "t" atom
mov rdi, rsi
call cons ; (t . t)
mov rdi, rax ; $rdi = (t . t)
lea rsi, [rel nil]
call cons ; ((t . t) . nil)
push rax
lea rdi, [rel atoms]
mov rdi, [rdi + ATOM_QUOTE * 8] ; get the "quote" atom
lea rsi, [rel p_quote]
call make_prim_pair ; ("quote" . p_add)
mov rdi, rax
mov rsi, qword [rsp]
call cons
mov qword [rsp], rax
lea rdi, [rel atoms]
mov rdi, [rdi + ATOM_PLUS * 8] ; get the "+" atom
lea rsi, [rel p_add]
call make_prim_pair ; ("+" . p_add)
mov rdi, rax
mov rsi, qword [rsp]
call cons
mov qword [rsp], rax
lea rdi, [rel atoms]
mov rdi, [rdi + ATOM_MINUS * 8] ; get the "+" atom
lea rsi, [rel p_sub]
call make_prim_pair ; ("+" . p_add)
mov rdi, rax
mov rsi, qword [rsp]
call cons
mov qword [rsp], rax
lea rdi, [rel atoms]
mov rdi, [rdi + ATOM_MUL * 8] ; get the "*" atom
lea rsi, [rel p_mul]
call make_prim_pair ; ("*" . p_mul)
mov rdi, rax
mov rsi, qword [rsp]
call cons
mov qword [rsp], rax
lea rdi, [rel atoms]
mov rdi, [rdi + ATOM_DIV * 8] ; get the "/" atom
lea rsi, [rel p_div]
call make_prim_pair ; ("/" . p_div)
mov rdi, rax
mov rsi, qword [rsp]
call cons
mov qword [rsp], rax
lea rdi, [rel atoms]
mov rdi, [rdi + ATOM_REM * 8] ; get the "%" atom
lea rsi, [rel p_rem]
call make_prim_pair ; ("%" . p_rem)
mov rdi, rax
mov rsi, qword [rsp]
call cons
mov qword [rsp], rax
lea rdi, [rel atoms]
mov rdi, [rdi + ATOM_LT * 8] ; get the "<" atom
lea rsi, [rel p_lt]
call make_prim_pair ; ("<" . p_lt)
mov rdi, rax
mov rsi, qword [rsp]
call cons
mov qword [rsp], rax
lea rdi, [rel atoms]
mov rdi, [rdi + ATOM_EQ * 8] ; get the "=" atom
lea rsi, [rel p_eq]
call make_prim_pair ; ("=" . p_eq)
mov rdi, rax
mov rsi, qword [rsp]
call cons
mov qword [rsp], rax
lea rdi, [rel atoms]
mov rdi, [rdi + ATOM_CAR * 8] ; get the "car" atom
lea rsi, [rel p_car]
call make_prim_pair ; ("car" . p_car)
mov rdi, rax
mov rsi, qword [rsp]
call cons
mov qword [rsp], rax
lea rdi, [rel atoms]
mov rdi, [rdi + ATOM_CDR * 8] ; get the "cdr" atom
lea rsi, [rel p_cdr]
call make_prim_pair ; ("cdr" . p_cdr)
mov rdi, rax
mov rsi, qword [rsp]
call cons
mov qword [rsp], rax
lea rdi, [rel atoms]
mov rdi, [rdi + ATOM_NOT * 8] ; get the "not" atom
lea rsi, [rel p_not]
call make_prim_pair ; ("not" . p_cdr)
mov rdi, rax
mov rsi, qword [rsp]
call cons
mov qword [rsp], rax
pop rax
mov qword [rel env], rax ; env = ((t . t) . nil)
ret
global init_atoms
;; makes a (atom . prim) cons cell out of an atom symbol and a primitive function pointer
make_prim_pair:
push rdi
push rsi
mov rdi, 16
mov rsi, 8
call alloc
mov dword [rax], 1 ; refcount = 1
pop rsi
mov qword [rax + 8], rsi ; pointer to primitive function
mov rdi, rax
mov rsi, OBJ_PRIM
call obj_set_tag
mov rsi, rax
pop rdi
call obj_inc_ref
call cons
ret
make_atom:
push rdi
push rsi
mov rdi, 16
mov rsi, 8
call alloc
pop rsi
pop rdi
mov dword [rax], 1 ; refcount = 1
mov dword [rax + 4], esi ; length of string
mov qword [rax + 8], rdi ; pointer to string
mov rdi, rax
mov rsi, OBJ_ATOM
call obj_set_tag
ret
init_atoms:
mov rdi, QUOTE_STR
mov rsi, QUOTE_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx], rax ; atoms[0] = "quote"
mov rdi, TRUE_STR
mov rsi, TRUE_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + 8], rax ; atoms[1] = "t"
mov rdi, PLUS_STR
mov rsi, PLUS_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_PLUS * 8], rax ; atoms[2] = "+"
mov rdi, MINUS_STR
mov rsi, MINUS_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_MINUS * 8], rax ; atoms[3] = "-"
mov rdi, MUL_STR
mov rsi, MUL_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_MUL * 8], rax ; atoms[4] = "*"
mov rdi, DIV_STR
mov rsi, DIV_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_DIV * 8], rax ; atoms[5] = "/"
mov rdi, REM_STR
mov rsi, REM_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_REM * 8], rax ; atoms[6] = "%"
mov rdi, LT_STR
mov rsi, LT_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_LT * 8], rax ; atoms[7] = "<"
mov rdi, EQ_STR
mov rsi, EQ_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_EQ * 8], rax ; atoms[8] = "="
mov rdi, CAR_STR
mov rsi, CAR_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_CAR * 8], rax ; atoms[9] = "car"
mov rdi, CDR_STR
mov rsi, CDR_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_CDR * 8], rax ; atoms[10] = "cdr"
mov rdi, NOT_STR
mov rsi, NOT_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_NOT * 8], rax ; atoms[11] = "not"
ret
;; returns 0 if the objects pointed at by $rdi and $rsi are equal, and 1 otherwise.
obj_eq:
sub rsp, 24
mov qword [rsp], rdi ; save obj1
mov qword [rsp + 8], rsi ; save obj2
call obj_tag_part
mov byte [rsp + 16], al ; save tag1
mov rdi, qword [rsp + 8] ; restore obj2
call obj_tag_part
cmp al, byte [rsp + 16] ; compare tag2 with tag1
jne .not_equal
cmp al, OBJ_ATOM
je .compare_atoms
cmp al, OBJ_STR
je .compare_strs
cmp al, OBJ_NUM
je .compare_nums
cmp al, OBJ_CONS
je .compare_cons
cmp al, OBJ_CLOS
je .compare_clos
cmp al, OBJ_PRIM
je .compare_prims
jmp .equal
.compare_atoms:
.compare_strs:
call obj_addr_part
mov qword [rsp + 8], rax
mov rdi, qword [rsp] ; restore obj1
call obj_addr_part
movzx rdx, dword [rax + 4] ; len1
mov rcx, qword [rsp + 8] ; ptr2
movzx rbx, dword [rcx + 4] ; len2
cmp rdx, rbx
jne .not_equal
mov rdx, qword [rax + 8] ; ptr1
mov rsi, qword [rcx + 8] ; ptr2
cmp rdx, rsi
je .equal
mov rdi, rdx
mov rdx, rsi
mov rsi, rbx
mov rcx, rbx
call strcmp
test al, al
je .equal
jmp .not_equal
.compare_nums:
call obj_addr_part
mov qword [rsp + 8], rax
mov rdi, qword [rsp] ; restore obj1
call obj_addr_part
mov rdx, qword [rax + 8] ; num1
mov rcx, qword [rsp + 8]
mov rcx, qword [rcx + 8] ; num2
cmp rdx, rcx
je .equal
jmp .not_equal
.compare_cons:
.compare_clos:
mov rdi, qword [rsp] ; restore obj1
call get_cdr
mov qword [rsp + 8], rax ; save cdr1
call get_car
mov rcx, rax ; $rcx = car1
mov rdi, qword [rsp + 16] ; $rdi = obj2
call get_cdr
mov qword [rsp + 16], rax ; save cdr2
call get_car
mov rdi, rcx ; $rdi = car1
mov rsi, rax ; $rsi = car2
call obj_eq
test al, al
jne .not_equal
mov rdi, qword [rsp + 8] ; $rdi = cdr1
mov rsi, qword [rsp + 16] ; $rsi = cdr2
call obj_eq
test al, al
jne .not_equal
jmp .equal
.compare_prims:
call obj_addr_part
mov rax, qword [rax + 8] ; prim1
mov qword [rsp + 16], rax
mov rdi, qword [rsp] ; restore obj1
call obj_addr_part
mov rdx, qword [rax + 8] ; prim1
mov rcx, qword [rsp + 16] ; prim2
cmp rdx, rcx
je .equal
jmp .not_equal
.equal:
xor eax, eax
add rsp, 24
ret
.not_equal:
mov eax, 1
add rsp, 24
ret
dtor_table:
dd 0
dd dtor_table - dtor_cons
dd dtor_table - dtor_clos
dd dtor_table - dtor_atom
dd dtor_table - dtor_num
dd 0
dd dtor_table - dtor_str
dd 0
dtor_nil:
dtor_prim:
ret
dtor_cons:
dtor_clos:
push rdi
mov rdi, qword [rdi + 8] ; car
call obj_dec_ref
mov rdi, qword [rsp]
mov rdi, qword [rdi + 16] ; cdr
call obj_dec_ref
pop rdi
mov rsi, 24
mov rdx, 8
call dealloc
ret
dtor_atom:
dtor_str:
push rdi
mov rax, rdi
mov rdi, qword [rax + 8] ; pointer to string
movzx rsi, dword [rax + 4] ; length of string
mov rdx, 1
call dealloc
pop rdi
dtor_num:
mov rsi, 16
mov rdx, 8
call dealloc
ret
obj_inc_ref:
call is_nil
test al, al
jnz .done ; nil is immortal, so we're done
call obj_addr_part
inc dword [rax] ; increment the refcount
.done:
ret
obj_dec_ref:
call is_nil
test al, al
jnz .done ; nil is immortal, so we're done
call obj_tag_part
mov rsi, rax
call obj_addr_part
dec dword [rax] ; decrement the refcount
jnz .done ; if refcount != 0, we're done
lea rcx, qword [rel dtor_table]
movsxd rsi, [rcx + rsi * 4] ; get the offset of the destructor
test rsi, rsi
jz .done ; if there's no destructor, we're done
add rcx, rsi ; p_dtor = &dtor_table + dtor_table[tag]
mov rdi, rax ; set the argument for the destructor (the object pointer)
call rcx ; call the destructor
.done:
ret
;; returns the tag part of the object handle in $rdi
obj_tag_part:
mov rax, rdi
and al, 0x7
movzx rax, al
ret
;; returns the address part of the object handle in $rdi
obj_addr_part:
mov rax, rdi
and rax, -8
ret
obj_set_tag:
mov rax, rdi
and rax, -8 ; clear the tag bits
or rax, rsi ; set the new tag bits
ret
;; construcst a closure object with params $rdi, body $rsi, and env $rdx
clos:
push rdx
call cons
mov rdi, rax
pop rsi
call cons
mov rdi, rax
mov rsi, OBJ_CLOS
call obj_set_tag
ret
;; constructs a cons cell ($rdi . $rsi) and returns an object pointer to it
cons:
push rdi
push rsi
mov rdi, 24 ; struct { refcount: usize, car: *const Object, cdr: *const Object }
mov rsi, 8
call alloc
pop rsi
pop rdi
mov dword [rax], 1 ; refcount = 1
mov qword [rax + 8], rdi ; car = a
mov qword [rax + 16], rsi ; cdr = b
mov rdi, rax
mov rsi, OBJ_CONS
call obj_set_tag
ret
get_car:
call obj_tag_part
cmp al, OBJ_CONS
jne .invalid
call obj_addr_part
mov rax, qword [rax + 8] ; return the car
ret
.invalid:
xor rdi, rdi
call panic_abort
get_cdr:
call obj_tag_part
cmp al, OBJ_CONS
jne .invalid
call obj_addr_part
mov rax, qword [rax + 16] ; return the cdr
ret
.invalid:
xor rdi, rdi
call panic_abort
set_cdr:
call obj_tag_part
cmp al, OBJ_CONS
jne .invalid
call obj_addr_part
mov qword [rax + 16], rsi ; set the cdr to
ret
.invalid:
xor rdi, rdi
call panic_abort
;; returns 1 if the result of `peekc($rdi)` is $sil
;; treats all characters less than ' ' as spaces.
is_ch:
push rsi
call peekc
pop rsi
cmp al, ' '
setbe cl ; cl = al <= ' '
movzx ecx, cl
mov edx, ' '
mul ecx, edx ; cl = (al < ' ') ? ' ' : 0
cmp sil, ' '
cmove ax, cx ; al = (sil < ' ') ? cl : al
cmp al, sil
setz al ; al = al == sil
ret
next_token:
push r14
push rdi
xor r14, r14
.skip_spaces:
mov rsi, ' '
call is_ch
test al, al
mov rdi, qword [rsp]
jz .skip_spaces_done
call getc
mov rdi, qword [rsp]
jmp .skip_spaces
.skip_spaces_done:
call peekc
cmp al, '('
je .leading_kw
cmp al, ')'
je .leading_kw
cmp al, "'"
jne .eat
.leading_kw:
mov rdi, qword [rsp]
call getc
lea rcx, [rel buf]
lea rcx, [rcx + r14]
mov byte [rcx], al
inc r14
jmp .done
.eat:
mov rdi, qword [rsp]
call getc
lea rcx, [rel buf]
lea rcx, [rcx + r14]
mov byte [rcx], al
inc r14
mov rdi, qword [rsp]
mov rsi, ' '
call is_ch
test al, al
jnz .done
mov rdi, qword [rsp]
mov rsi, '('
call is_ch
test al, al
jnz .done
mov rdi, qword [rsp]
mov rsi, ')'
call is_ch
test al, al
jnz .done
jmp .eat
.done:
lea rcx, [rel buf]
lea rax, [rcx + r14]
mov byte [rax], 0
pop rdi
pop r14
mov al, byte [rel buf]
ret
parse_cur_token:
push rdi
cmp byte [rel buf], '('
je .list
cmp byte [rel buf], "'"
je .quote
cmp byte [rel buf], `"`
je .string
pop rdi
call parse_atom
ret
.list:
pop rdi
call parse_list
ret
.quote:
pop rdi
call parse_quote
ret
.string:
ret
;; fn parse_string(&mut iter) -> Box<[u8]>
parse_string:
push r12
xor r12, r12
.loop:
; scan for closing quote
mov al, byte [rel buf + r12]
; cmp al, `\\`
; je .escape
cmp al, `"`
je .done
test al, al
jz .invalid
inc r12
jmp .loop
.done:
mov rdi, r12
mov rsi, 1
call alloc
lea rdi, [rel buf]
mov rsi, rax
mov rdx, r12
call memcpy
push rax
mov rdi, 16
mov rsi, 8
call alloc
mov dword [rax], 1 ; refcount = 1
mov dword [rax + 4], r12w ; length of string
pop rdi
mov qword [rax + 8], rdi ; pointer to string
mov rdi, rax
mov rsi, OBJ_STR
call obj_set_tag
pop r12
ret
.invalid:
xor rdi, rdi
call panic_abort
parse_num:
push r12
sub rsp, 16
mov qword [rsp], 0 ; acc = 0
mov dword [rsp + 8], 10 ; radix = 10
lea r12, [rel buf]
cmp byte [r12], '-' ; check for optional leading '-'
jne .skip_sign
mov qword [rsp], -1
inc r12 ; index++
.skip_sign:
cmp byte [r12], '0'
jne .skip_radix
inc r12 ; index++
mov dil, byte [r12]
cmp dil, 'x'
jne .skip_radix
inc r12 ; index++
mov qword [rsp + 4], 16 ; radix = 16
.skip_radix:
mov dil, byte [r12]
test dil, dil
jz .done
movzx rsi, dword [rsp + 8] ; radix
call to_digit
test al, al
jz .done
mov rax, qword [rsp] ; acc
mov rcx, rdx ; digit
imul rax, rsi ; acc *= radix
add rax, rcx ; acc += digit
mov qword [rsp], rax
inc r12 ; index++
jmp .skip_radix
.done:
cmp byte [r12], 0
setz al
lea rcx, [rel buf]
sub r12, rcx ; r12 = count
mul rax, r12
mov rdx, qword [rsp] ; acc
add rsp, 16
pop r12
ret
parse_atom:
call parse_num
test al, al
jz .not_num
push rdx
mov rdi, 16 ; struct { refcount: usize, value: i64 }
mov rsi, 8
call alloc
pop rdx
mov dword [rax], 1 ; refcount = 1
mov qword [rax + 8], rdx ; value = acc
mov rdi, rax
mov rsi, OBJ_NUM
call obj_set_tag
ret
.not_num:
lea rdi, [rel buf]
call strlen
push rax ; len
mov rdi, rax
mov rsi, 1
call alloc ; allocate memory for the string
pop rdx ; len
push rax ; ptr
push rdx ; len
lea rdi, [rel buf]
mov rsi, rax
call memcpy ; memcpy(ptr, buf, len)
mov rdi, 16 ; struct { refcount: u32, length: u32, ptr: *const u8 }
mov rsi, 8
call alloc ; allocate the atom object
pop rdx ; length of string
pop rcx ; pointer to string
mov dword [rax], 1 ; refcount = 1
mov dword [rax + 4], edx ; length of string
mov qword [rax + 8], rcx ; pointer to string
mov rdi, rax
mov rsi, OBJ_ATOM
call obj_set_tag
ret
parse_quote:
call parse_next_token
mov rdi, rax
lea rsi, [rel nil]
call cons ; (t . nil)
push rax
lea rdi, [rel atoms]
mov rdi, [rdi + ATOM_QUOTE * 8] ; get the "quote" atom
push rdi
call obj_inc_ref
pop rdi ; $rdi = "quote" atom
pop rsi ; $rsi = (t . nil)
call cons ; ("quote" . (t . nil))
ret
parse_list:
sub rsp, 24
mov qword [rsp], rdi
lea rax, [rel nil]
mov qword [rsp + 8], rax ; head = nil
mov qword [rsp + 16], rax ; tail = nil
.tailcall:
mov rdi, qword [rsp]
call next_token
cmp al, ')'
je .finish
cmp al, '.'
mov rdi, qword [rsp]
jnz .list
; dotted_pair:
call parse_next_token ; if (a . b), return b since we're in the recursive call for (cons a b)
mov rsi, rax ; $rsi = b
mov rdi, qword [rsp + 8] ; $rdi = (a . nil)
call set_cdr ; set_cdr((a . nil), b) => (a . b)
mov rdi, qword [rsp]
call next_token
cmp al, ')'
jnz .invalid
jmp .finish
.list:
call parse_cur_token
mov rdi, rax
lea rsi, [rel nil]
call cons ; (t . nil)
xchg rax, qword [rsp + 16] ; replace(&mut tail, new_tail)
lea rsi, [rel nil]
cmp rax, rsi
je .init_tail ; if tail was nil, set head = tail = (t . nil)
mov rdi, rax ; $rdi = old tail
mov rsi, qword [rsp + 16] ; $rsi = new tail
call set_cdr ; set_cdr(old_tail, new_tail) => (old_tail . (t . nil))
jmp .tailcall
.init_tail:
mov rax, qword [rsp + 16] ; $rax = (t . nil)
mov qword [rsp + 8], rax ; head = (t . nil)
jmp .tailcall
.invalid:
xor rdi, rdi
call panic_abort
.finish:
mov rax, [rsp + 8] ; the list we've been building up
add rsp, 24
ret
global parse_next_token
parse_next_token:
push rdi
call next_token
pop rdi
call parse_cur_token
ret
global eval
;; evaluates the expression in $rdi in the environment $rsi and returns the result in $rax
eval:
call obj_tag_part
cmp al, OBJ_ATOM
je .eval_atom
cmp al, OBJ_CONS
jne .uneval
; eval_list($rdi, $rsi)
sub rsp, 16
mov qword [rsp], rsi ; env
call get_cdr
mov qword [rsp + 8], rax ; cdr($rdi)
call get_car
mov rdi, rax ; $rdi = car($rdi)
mov rsi, qword [rsp] ; $rsi = env
call eval ; $rax = eval(car($rdi), $rsi)
mov rdi, rax ; $rdi = eval(car($rdi),
mov rsi, qword [rsp + 8] ; $rsi = cdr($rdi)
mov rdx, qword [rsp] ; $rdx = env
call apply
add rsp, 16
ret
.eval_atom:
call assoc
ret
.uneval:
mov rax, rdi
ret
;; returns a list of the evaluated elements in the list in $rdi in the environment $rsi
eval_list:
sub rsp, 32
mov qword [rsp], rsi ; save the environment on the stack
lea rax, [rel nil]
mov qword [rsp + 8], rax ; tail = nil
mov qword [rsp + 16], rax ; head = nil
.tailcall:
call obj_tag_part
cmp al, OBJ_CONS
jne .not_cons
; cons(eval(car($rdi), $rsi), eval_list(cdr($rdi), $rsi))
call get_car
mov rcx, rax
call get_cdr
mov qword [rsp + 24], rax ; cdr($rdi)
mov rdi, rcx ; $rdi = car($rdi)
mov rsi, qword [rsp] ; $rsi = environment
call eval
mov rdi, rax ; $rdi = eval(car($rdi), $rsi)
lea rsi, [rel nil] ; nil
call cons ; $rax = cons(eval(car($rdi), $rsi), 'nil)
xchg rax, qword [rsp + 8] ; replace(&mut tail, new_tail)
lea rsi, [rel nil]
cmp rax, rsi
jne .has_init_head
mov rax, qword [rsp + 8] ; $rax = new tail
mov qword [rsp + 16], rax ; head = new tail
mov rdi, qword [rsp + 24] ; $rdi = cdr($rdi)
jmp .tailcall
.has_init_head:
mov rdi, rax
mov rsi, qword [rsp + 8]
call set_cdr
mov rdi, qword [rsp + 24] ; $rdi = cdr($rdi)
jmp .tailcall
.not_cons:
call is_nil
test al, al
jnz .nil
mov rsi, qword [rsp] ; $rsi = environment
call eval
mov rsi, rax
mov rdi, qword [rsp + 8] ; $rdi = tail
; cdr of tail starts as nil, in the case of a cons cell as a list, we want to update it to be the eval of the cdr of the list
call set_cdr ; set_cdr(tail, eval($rdi, $rsi))
.nil:
; the tail starts as nil
mov rax, qword [rsp + 16] ; $rax = head
add rsp, 32
ret
;; returns the value associated with the symbol in $rdi in the environment $rsi, or nil if not found
assoc:
sub rsp, 16
mov qword [rsp], rsi ; save the environment on the
mov qword [rsp + 8], rdi ; save the symbol on the stack
.assoc_loop:
mov rdi, qword [rsp] ; $rdi = environment
call is_nil
test al, al
jnz .not_found ; if we've reached the end of the environment, return nil
call obj_tag_part
cmp al, OBJ_CONS
jne .invalid_env
call get_car
mov rdi, rax
call get_car
mov rdi, rax ; $rdi = key
mov rsi, qword [rsp + 8] ; $rsi = sym
call obj_eq
test al, al
je .found
mov rdi, qword [rsp] ; $rdi = environment
call get_cdr
mov qword [rsp], rax ; environment = cdr(environment)
jmp .assoc_loop
.found:
mov rdi, qword [rsp] ; $rdi = environment
call get_car
mov rdi, rax
call get_cdr
add rsp, 16
ret
.invalid_env:
.not_found:
lea rax, [rel nil]
add rsp, 16
ret
;; applies the function in $rdi to the argument list in $rsi in the environment $rdx and returns the result in $rax
apply:
call obj_tag_part
cmp al, OBJ_PRIM
je .apply_prim
cmp al, OBJ_CLOS
jne .invalid_func
; closure application:
call reduce
ret
.apply_prim:
call obj_addr_part
mov rax, qword [rax + 8] ; get the function pointer
mov rdi, rsi ; $rdi = argument list
mov rsi, rdx ; $rsi = environment
jmp rax
.invalid_func:
xor rdi, rdi
call panic_abort
;; recursively binds the symbols in $rdi to the values in $rsi in the environment $rdx and returns the new environment in $rax
bind:
mov rax, rdx
call is_nil
test al, al
jnz .done ; if there are no more symbols to bind, return the old environment
sub rsp, 32
mov qword [rsp], rax ; save the old environment on the stack
mov qword [rsp + 16], rsi ; tmp_values
call get_car
mov qword [rsp + 8],rax ; key
call get_cdr
mov qword [rsp + 16], rax ; keys
mov rdi, qword [rsp + 16] ; values
call get_car
mov qword [rsp + 16], rax ; val
call get_cdr
mov qword [rsp + 24], rax ; vals
mov rdi, qword [rsp + 8] ; $rdi = key
mov rsi, qword [rsp + 16] ; $rsi = val
mov rdx, qword [rsp] ; $rdx = old environment
call prepend ; new_env = ((key . val) . old_env)
mov rdx, rax
mov rdi, qword [rsp + 16] ; $rdi = keys
mov rsi, qword [rsp + 24] ; $rsi = vals
add rsp, 32
jmp bind ; recursively bind the rest of the keys and vals in the new environment
.done:
ret
;; applies closure $rdi to arguments $rsi and environment $rdx, returning the result in $rax
reduce:
; a closure has the following form: ((params . body) . env)
sub rsp, 32
mov qword [rsp], rdx ; eval_env
mov qword [rsp + 8], rdi ; closure
call get_cdr
mov rdi, rax
call is_nil
test al, al
mov rax, qword [rsp] ; eval_env
cmovz rax, rdi ; env = closure_env == nil ? eval_env : closure_env
xchg qword [rsp + 8], rax ;
mov rdi, rax ; restore closure
call get_car
mov rdi, rax ; (params . body)
call get_car
mov qword [rsp + 16], rax ; params
call get_cdr
mov qword [rsp + 24], rax ; body
;; k: $rdi, v: $rsi, e: $rdx -> ((k . v) . e)
prepend:
push rdx
call cons
mov rsi, rax
pop rdi
call cons
ret
;; PRIMITIVES
;; returns 1 if the object in $rdi is nil, 0 otherwise
is_nil:
lea rax, [rel nil]
cmp rdi, rax
sete al
ret