diff --git a/stages/lisp0/lisp.asm b/stages/lisp0/lisp.asm index 0f7d867..8caf651 100644 --- a/stages/lisp0/lisp.asm +++ b/stages/lisp0/lisp.asm @@ -41,6 +41,27 @@ strlen: .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 @@ -131,7 +152,107 @@ init_atoms: mov qword [rcx + 8], rax ; atoms[1] = "t" 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 + mov rdx, dword [rax + 4] ; len1 + mov rcx, qword [rsp + 8] ; ptr2 + mov 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 ; $rdi = ptr1 + mov rsi, rsi ; $rsi = ptr2 + mov rdx, rbx ; $rdx = len1 = len2 + 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 dtor_table - dtor_nil @@ -150,13 +271,9 @@ dtor_cons: dtor_clos: push rdi mov rdi, qword [rdi + 8] ; car - call obj_addr_part - mov rdi, rax call obj_dec_ref mov rdi, qword [rsp] mov rdi, qword [rdi + 16] ; cdr - call obj_addr_part - mov rdi, rax call obj_dec_ref pop rdi mov rsi, 24 @@ -470,7 +587,6 @@ parse_quote: lea rdi, [rel atoms] mov rdi, [rdi + ATOM_QUOTE * 8] ; get the "quote" atom push rdi - call obj_addr_part call obj_inc_ref pop rdi ; $rdi = "quote" atom pop rsi ; $rsi = (t . nil) @@ -534,3 +650,215 @@ parse_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: + cmp al, OBJ_NIL + je .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 + 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 + xchg qword [rsp + 8], rax ; + cmovz qword [rsp + 8], rdi ; env = closure_env == nil ? eval_env : closure_env + 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: + call obj_tag_part + cmp al, OBJ_NIL + setz al + ret diff --git a/stages/lisp0/lisp.rs b/stages/lisp0/lisp.rs index dcdb5a4..a962df0 100644 --- a/stages/lisp0/lisp.rs +++ b/stages/lisp0/lisp.rs @@ -1,7 +1,7 @@ unsafe extern "C" { fn init_atoms(); fn parse_next_token(src: *mut Source<'_>) -> Object; - fn init_env(); + fn init_env() -> Object; } struct Source<'a> { @@ -53,11 +53,11 @@ extern "C" fn panic_abort(cstr: *const u8) -> ! { } } -static mut ENV_INIT: std::cell::LazyCell<()> = std::cell::LazyCell::new(|| unsafe { - init_env(); -}); +static mut ENV_INIT: std::cell::LazyCell = + std::cell::LazyCell::new(|| unsafe { init_env() }); #[repr(C)] +#[derive(Copy, Clone)] struct Object(*mut ()); // impl Clone for Object { @@ -127,9 +127,8 @@ impl Object { #[test] fn test_parse_list() { - unsafe { - *ENV_INIT; - } + let env = unsafe { *ENV_INIT }; + println!("env: {:?}", env); let input = b"('a 3 4 5)"; let mut src = Source::from(&input[..]);