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