; sanctuary ; macros {{{ ; the error handling feels slow but i dont know a better way ; todo at some point make these use actual error codes %macro pspush 1 lea r15, [r15-8] mov qword [r15], %1 cmp r15, wstk_b jge %%ok call q_overflow %%ok: %endmacro %macro pspop 1 mov %1, qword [r15] lea r15, [r15+8] cmp r15, wstk jle %%ok call q_underflow %%ok: %endmacro %define s_latest 0 %macro defdict 3 ; name label flags %strlen slen %1 global lfa_%2 lfa_%2: dq s_latest %define s_latest lfa_%2 ffa_%2: db %3 nfa_%2: db slen db %1 %endmacro %macro defcode 3 defdict %1, %2, %3 %2: %endmacro %macro defdefer 3 defcode %1, %2, %3 mov r11, dodefer call r11 dq do_%2 do_%2: %endmacro ; this is just taken from jewelforth, and does not correspond ; to how user variables are planned to work in sanctuary ; so todo make better later? i don't know if it really matters ; because it will only apply to builtin variables. %macro defvar 4 %2: dq %4 defdict %1, %2, %3 do_%2: pspush qword %2 ret %endmacro %macro defdefervar 4 %2: dq %4 defdict %1, %2, %3 do_%2: mov r11, dodefer call r11 dq in_%2 in_%2: pspush qword %2 ret %endmacro ; }}} %assign smudge_mask 0x1 %assign immediate_mask 0x2 %assign comp_only_mask 0x4 %assign false 0x0 %assign true (~0x0) %assign INTERPRET 0x0 %assign COMPILING (~0x0) %assign __NR_write 1 %assign __NR_mprotect 10 %assign __NR_brk 12 %assign __NR_exit 60 %assign init_brk 0x9c400 section .bss wstk_b: resq 4091 wstk: resq 1 section .text global _start _start: lea r15, [wstk] mov [rp0], rsp call brk@ pspop r11 mov qword [dp], r11 mov qword [dp0], r11 mov r11, init_brk pspush r11 call grow call interpret call bye defcode "brk@", brk@, 0 xor rdi, rdi mov rax, __NR_brk syscall pspush rax ret defcode "grow", grow, 0 call brk@ pspop rdi pspop r13 add rdi, r13 mov rax, __NR_brk syscall mov qword [dp$], rax ; this marks the *whole user memory* as executable. ; technically redundant but i think it's fine mov r14, qword [dp0] pspush r14 sub rax, r14 pspush rax call executable ret defcode "executable", executable, 0 mov rdx, 0x7 ; PROT_{READ,WRITE,EXEC} pspop rsi pspop rdi ; addr mov rax, __NR_mprotect syscall ret defcode "here", here, 0 mov r11, qword [dp] pspush r11 ret defcode "sp", psp, 0 mov r11, r15 pspush r11 ret ; todo doc defcode "sp-reset", sp_reset, 0 lea r15, [wstk] ret defcode "rp", rp, 0 mov r11, rsp add r11, 8 pspush r11 ret ; todo doc defcode "r@", rfetch, 0 mov r11, rsp add r11, 8 mov r12, qword [r11] pspush r12 ; ; mov r11, rsp ; pop r11 ; pspush rsp ; push r11 ret defcode "bye", bye, 0 mov rdi, 0 mov rax, __NR_exit syscall ret ; input parsing {{{ ; r11: string character count ; rsi: input buffer address ; al: char being parsed ; r10: end of input buffer defcode "parse-name", parse_name, 0 mov rsi, qword [to_in] mov r10, qword [tib] add rsi, r10 add r10, qword [n_tib] xor rax, rax .wsloop: cmp rsi, r10 jge .empty lodsb cmp al, 0x20 je .wsloop cmp al, 0x09 je .wsloop cmp al, 0x0a je .wsloop cmp rsi, r10 jg .empty mov r11, 1 dec rsi ; bring down by one to point to the start push rsi ; will become `a` inc rsi .wordloop: cmp al, 0x20 je .wordloop_e cmp al, 0x09 je .wordloop_e cmp al, 0x0a je .wordloop_e cmp rsi, r10 jg .wordloop_e inc r11 lodsb jmp .wordloop .wordloop_e: dec r11 sub rsi, qword [tib] mov qword [to_in], rsi pop rsi pspush rsi pspush r11 ret .empty: pspush 0 pspush 0 ret ; r11: string character count ; rsi: input buffer address ; al: char being parsed ; r10: end of input buffer defcode "parse", parse, 0 pspop rbx mov rsi, qword [to_in] mov r10, qword [tib] add rsi, r10 add r10, qword [n_tib] xor rax, rax .wsloop: cmp rsi, r10 jg .empty lodsb cmp al, bl je .wsloop cmp al, 0x0a je .wsloop cmp rsi, r10 jg .empty mov r11, 1 dec rsi ; bring down by one to point to the start push rsi ; will become `a` inc rsi .wordloop: cmp al, bl je .wordloop_e cmp al, 0x0a je .wordloop_e cmp rsi, r10 jg .wordloop_e inc r11 lodsb jmp .wordloop .wordloop_e: dec r11 sub rsi, qword [tib] mov qword [to_in], rsi pop rsi pspush rsi pspush r11 ret .empty: pspush 0 pspush 0 ret ; }}} ; r9: processing temporary value ; r10: input size ; r11: input addr ; r12: pointer into currently processing word ; r13: same as r12 but kept at xt defdefer "find", find, 0 pspop r10 ; u pspop r11 ; a mov r12, qword [latest] mov r13, r12 .check_smudge: add r12, 8 mov r9b, byte [r12] test r9b, smudge_mask jnz .no inc r12 mov r9b, byte [r12] cmp r9b, r10b jne .no mov rsi, r12 inc rsi mov rdi, r11 mov rcx, r10 repe cmpsb jnz .no pspush r13 mov r13, true pspush r13 ret .no: mov r12, qword [r13] mov r13, r12 cmp r12, 0 jne .check_smudge pspush r11 pspush r10 mov r13, false pspush r13 ret ; interpret {{{ ; r11: word found flag ; r12: state defcode "interpret", interpret, 0 .loop: call parse_name cmp qword [r15], 0 je .eof call find mov r12, qword [state] cmp r12, COMPILING je .compl pspop r11 cmp r11, false je .intrpnum call dup call compile_only_q pspop r13 cmp r13, true je .componly .callw: ; label here for immed jump call to_body pspop r13 call r13 jmp .loop .componly: call q_componly jmp .loop .intrpnum: call number pspop r11 cmp r11, false je .notfound jmp .loop .compl: pspop r11 cmp r11, false je .complnum ; dup immediate? if [imm] else normal then call dup call immediate_q pspop r13 cmp r13, true je .callw call to_body call compile_comma jmp .loop .complnum: call number pspop r11 cmp r11, false je .notfound call literal jmp .loop .notfound: ; error handling should go here call q_notfound jmp .loop .eof: lea r15, [r15+16] ; drop a u ret ; }}} defcode "immediate?", immediate_q, 0 pspop r11 add r11, 8 mov r12b, byte [r11] xor r13, r13 test r12b, immediate_mask setnz r13b neg r13 pspush r13 ret defcode "compile-only?", compile_only_q, 0 pspop r11 add r11, 8 mov r12b, byte [r11] xor r13, r13 test r12b, comp_only_mask setnz r13b neg r13 pspush r13 ret defcode ">body", to_body, 0 pspop r11 add r11, 9 xor r12, r12 mov r12b, byte [r11] inc r11 add r11, r12 pspush r11 ret defcode "literal", literal, immediate_mask|comp_only_mask ; 4d 8d 7f f8 ; 49 bb VAL ; 4d 89 1f pspop r11 mov r12, qword [dp] mov dword [r12], 0xf87f8d4d add r12, 4 mov word [r12], 0xbb49 add r12, 2 mov qword [r12], r11 add r12, 8 mov word [r12], 0x894d add r12, 2 mov byte [r12], 0x1f inc r12 mov qword [dp], r12 ret defcode "compile,", compile_comma, 0 pspop r11 mov r12, [dp] ; compile mov r11, [cfa] mov word [r12], 0xbb49 add r12, 2 mov qword [r12], r11 add r12, 8 ; compile call r11 mov word [r12], 0xff41 add r12, 2 mov byte [r12], 0xd3 inc r12 mov qword [dp], r12 ret defcode "[", lbrac, immediate_mask mov qword [state], INTERPRET ret defcode "]", rbrac, immediate_mask mov qword [state], COMPILING ret defdefer "(header)", brac_header, 0 pspop r9 pspop r10 mov r11, qword [latest] mov r12, qword [dp] push r12 mov qword [r12], r11 add r12, 8 mov byte [r12], 0 inc r12 mov byte [r12], r9b inc r12 mov rcx, r9 mov rsi, r10 mov rdi, r12 rep movsb add r12, r9 mov qword [dp], r12 pop r12 pspush r12 ret ; fix to follow ans: yielding colon-sys defdefer ":", colon, 0 call parse_name ; todo check zero call brac_header pspop r11 mov qword [latest], r11 call smudge mov qword [state], COMPILING ret ; fix to follow ans: reading from colon-sys ; this will not work with :noname or i think does>. defcode ";", semicolon, immediate_mask mov r12, [latest] add r12, 8 mov r13b, [r12] and r13b, ~smudge_mask ; does this syntax work? equiv SMUDGEMASK NOT mov byte [r12], r13b mov r11, qword [dp] mov byte [r11], 0xc3 ; ret inc r11 mov qword [dp], r11 mov qword [state], INTERPRET ret defdefer "smudge", smudge, 0 mov r12, [latest] add r12, 8 mov r13b, [r12] xor r13b, smudge_mask mov byte [r12], r13b ret defdefer "immediate", immediate, 0 mov r12, [latest] add r12, 8 mov r13b, [r12] mov r14b, immediate_mask or r13b, r14b mov byte [r12], r13b ret defdefer "compile-only", compile_only, 0 mov r12, [latest] add r12, 8 mov r13b, [r12] mov r14b, comp_only_mask or r13b, r14b mov byte [r12], r13b ret defcode "char", char, 0 call parse_name call drop pspop r12 xor r11, r11 mov r11b, [r12] pspush r11 ret defcode "execute", execute, 0 pspop r11 call r11 ret ; number {{{ defcode "number", number, 0 ; ( c-addr u -- ?n flag ) pspop r11 ; u pspop r12 ; c-addr xor r13, r13 ; r13: result xor r14, r14 ; r14b: current char xor r10, r10 ; r10: negative flag mov r9, qword [base] cmp r11, 0 je .no mov r14b, byte [r12] cmp r14b, '-' jnz .enterloop mov r10, true inc r12 dec r11 .loop: mov r14b, byte [r12] .enterloop: ; non numeral = goodbye cmp r14b, 48 jl .no sub r14b, 48 cmp r14b, 10 ; 48+10: < ':', <= '9' jl .basecmp cmp r14b, 17 ; ':' - '@' jl .no sub r14b, 7 ; keep 10 so 'A' = 10 cmp r14b, 36 ; < '[' <= 'Z' jl .basecmp cmp r14b, 42 ; < 'a' jl .no sub r14b, 32 cmp r14b, 36 ; < '{' <= 'z' jl .basecmp jmp .no .basecmp: cmp r14, r9 jge .no imul r13, r9 add r13, r14 inc r12 dec r11 cmp r11, 0 jne .loop test r10, r10 jz .bye neg r13 .bye: pspush r13 mov r13, true pspush r13 ret .no: mov r13, false pspush r13 ret ; }}} ; syscall {{{ defcode "syscall0", syscall0, 0 pspop rax syscall pspush rax ret defcode "syscall1", syscall1, 0 pspop rax pspop rdi syscall pspush rax ret defcode "syscall2", syscall2, 0 pspop rax pspop rdi pspop rsi syscall pspush rax ret defcode "syscall3", syscall3, 0 pspop rax pspop rdi pspop rsi pspop rdx syscall pspush rax ret defcode "syscall4", syscall4, 0 pspop rax pspop rdi pspop rsi pspop rdx pspop r10 syscall pspush rax ret defcode "syscall5", syscall5, 0 pspop rax pspop rdi pspop rsi pspop rdx pspop r10 pspop r8 syscall pspush rax ret defcode "syscall6", syscall6, 0 pspop rax pspop rdi pspop rsi pspop rdx pspop r10 pspop r8 pspop r9 syscall pspush rax ret ; }}} ; stack {{{ defcode "dup", dup, 0 mov r11, [r15] pspush r11 ret defcode "2dup", twodup, 0 mov r11, [r15] mov r12, [r15+8] pspush r12 pspush r11 ret defcode "swap", swap, 0 pspop r11 pspop r12 pspush r11 pspush r12 ret defcode "over", over, 0 mov r11, [r15+8] pspush r11 ret defcode "rot", rot, 0 pspop r11 pspop r12 pspop r13 pspush r12 pspush r11 pspush r13 ret defcode "-rot", dash_rot, 0 pspop r11 pspop r12 pspop r13 pspush r11 pspush r13 pspush r12 ret defcode "r>", from_r, 0 pop r12 ; keep return address pop r11 pspush r11 push r12 ret defcode ">r", to_r, 0 pop r12 ; ret addr pspop r11 push r11 push r12 ret defcode "drop", drop, 0 pspop r11 ret defcode "2drop", twodrop, 0 pspop r11 pspop r11 ret defcode "rdrop", rdrop, 0 pop r12 ; ret addr pop r11 push r12 ret ; }}} ; memory access {{{ defcode "@", fetch, 0 pspop r11 mov r12, qword [r11] pspush r12 ret defcode "c@", cfetch, 0 pspop r11 xor r12, r12 mov r12b, byte [r11] pspush r12 ret defcode "!", store, 0 pspop r11 pspop r12 mov qword [r11], r12 ret defcode "d!", dstore, 0 pspop r11 pspop r12 mov dword [r11], r12d ret defcode "c!", cstore, 0 pspop r11 pspop r12 mov byte [r11], r12b ret defcode "+!", plusstore, 0 pspop r11 pspop r12 add qword [r11], r12 ret defcode "-!", minusstore, 0 pspop r11 pspop r12 sub qword [r11], r12 ret defcode ",", comma, 0 pspop r11 mov r12, [dp] mov qword [r12], r11 add r12, 8 mov qword [dp], r12 ret defcode "d,", d_comma, 0 pspop r11 mov r12, [dp] mov dword [r12], r11d add r12, 4 mov qword [dp], r12 ret defcode "c,", c_comma, 0 pspop r11 mov r12, [dp] mov byte [r12], r11b inc r12 mov qword [dp], r12 ret defcode "cmove", _cmove, 0 pspop rcx pspop rdi pspop rsi rep movsb ret defcode "cmove>", cmove_to, 0 std pspop rcx pspop rdi add rdi, rcx pspop rsi add rsi, rcx rep movsb cld ret ; }}} ; math + comparison {{{ ; i believe some of these could be improved by direct accesses to [r15] defcode "+", plus, 0 pspop r11 pspop r12 add r11, r12 pspush r11 ret defcode "-", minus, 0 pspop r11 pspop r12 sub r12, r11 pspush r12 ret defcode "1+", oneplus, 0 inc qword [r15] ret defcode "1-", oneminus, 0 dec qword [r15] ret defcode "*", _times, 0 pspop r11 pspop r12 imul r11, r12 pspush r11 ret defcode "/mod", divmod, 0 xor rdx, rdx pspop r11 pspop rax idiv r11 pspush rdx pspush rax ret defcode "and", _and, 0 pspop r11 and [r15], r11 ret defcode "or", _or, 0 pspop r11 or [r15], r11 ret defcode "xor", _xor, 0 pspop r11 xor [r15], r11 ret defcode "invert", invert, 0 not qword [r15] ret defcode "*/mod", starslashmod, 0 pspop r15 ; n3 pspop r13 ; n2 pspop rax ; n1 imul r13 idiv r15 pspush rdx pspush rax ret defcode "=", equals, 0 pspop r11 pspop r12 cmp r11, r12 sete r13b movzx r11, r13b neg r11 pspush r11 ret defcode "<>", less_greater, 0 pspop r11 pspop r12 cmp r11, r12 setne r13b movzx r11, r13b neg r11 pspush r11 ret defcode "<", less, 0 pspop r11 pspop r12 cmp r12, r11 setl r13b movzx r11, r13b neg r11 pspush r11 ret defcode ">", greater, 0 pspop r11 pspop r12 cmp r12, r11 setg r13b movzx r11, r13b neg r11 pspush r11 ret defcode "<=", lesseq, 0 pspop r11 pspop r12 cmp r12, r11 setle r13b movzx r11, r13b neg r11 pspush r11 ret defcode ">=", greatereq, 0 pspop r11 pspop r12 cmp r12, r11 setge r13b movzx r11, r13b neg r11 pspush r11 ret defcode "0=", zero_equals, 0 pspop r11 test r11, r11 sete r13b movzx r11, r13b neg r11 pspush r11 ret defcode "0<>", zero_less_greater, 0 pspop r11 test r11, r11 setne r13b movzx r11, r13b neg r11 pspush r11 ret defcode "0<", zero_less, 0 pspop r11 test r11, r11 setl r13b movzx r11, r13b neg r11 pspush r11 ret defcode "0>", zero_greater, 0 pspop r11 test r11, r11 setg r13b movzx r11, r13b neg r11 pspush r11 ret defcode "0<=", zero_lesseq, 0 pspop r11 test r11, r11 setle r13b movzx r11, r13b neg r11 pspush r11 ret defcode "0>=", zero_greatereq, 0 pspop r11 test r11, r11 setge r13b movzx r11, r13b neg r11 pspush r11 ret defcode "u<", uless, 0 pspop r11 pspop r12 cmp r12, r11 setb r13b movzx r11, r13b neg r11 pspush r11 ret defcode "u>", ugreater, 0 pspop r11 pspop r12 cmp r12, r11 seta r13b movzx r11, r13b neg r11 pspush r11 ret defcode "u<=", ulesseq, 0 pspop r11 pspop r12 cmp r12, r11 setbe r13b movzx r11, r13b neg r11 pspush r11 ret defcode "u>=", ugreatereq, 0 pspop r11 pspop r12 cmp r12, r11 setae r13b movzx r11, r13b neg r11 pspush r11 ret ; }}} ; todo doc ; ( a¹ u¹ a² u² -- n ) defcode "compare", compare, 0 pspop r11 ; u2 pspop rdi ; a2 pspop r13 ; u1 pspop rsi ; a1 ; check a* = 0? .loop: cmpsb jl .below jg .above dec r11 dec r13 ; check both = 0 ; either of these = 0 then jmp accordingly also cmp r11, 0 jne .11n0 cmp r13, 0 ; r11 = r13 & strings identical je .same ; u2 > u1 jmp .above .11n0: cmp r13, 0 ; u1 < u2 je .below .cont: jmp .loop .below: mov r11, true jmp .e .above: mov r11, 1 jmp .e .same: mov r11, false .e: pspush r11 ret defcode "branch", branch, 0 mov r12, [dp] mov byte [r12], 0xe9 inc r12 mov qword [dp], r12 ret ; 4d 8b 1f mov r11, qword [r15] ; 4d 8d 7f 08 lea r15, [r15+8] ; 4d 85 db test r11, r11 ; 0f 84 [REL] je [REL] defcode "?branch", q_branch, 0 mov r12, [dp] mov r11, 0x4d087f8d4d1f8b4d mov qword [r12], r11 add r12, 8 mov dword [r12], 0x840fdb85 add r12, 4 mov qword [dp], r12 ret ; error handling {{{ ; default error handler, because we don't have QUIT yet ; it just Exits with exit code 1 defcode "(0handler)", do_0handler, 0 mov rdi, 1 mov rax, __NR_exit syscall ret defcode "abort", abort, 0 mov r11, qword [handler] call r11 ret ; i don't like these names defdefer "?underflow?", q_underflow, 0 call abort ret defdefer "?overflow?", q_overflow, 0 call abort ret defdefer "?notfound?", q_notfound, 0 call abort ret defdefer "?componly?", q_componly, 0 call abort ret ; }}} ; these words are called from `create`d words, ; (create) expects a dummy value (used by (does>)) to skip past. defcode "(create)", docreate, 0 pop r11 add r11, 8 ; skip dummy value unused by (create) pspush r11 ret defcode "(does>)", dodoes, 0 pop r11 mov r12, [r11] add r11, 8 pspush r11 jmp r12 ; no RET defcode "(defer)", dodefer, 0 pop r11 mov r12, [r11] jmp r12 ; also no RET ; TEMPORARY WONKY DEBUGGING FUNCTIONS {{{ ; .s {{{ defcode ".s", dots, 0 push r11 push r12 mov r12, r15 .loop: cmp r12, wstk jge .done mov [.space], r12 mov rdx, 8 ; qword mov rsi, .space mov rdi, 1 mov rax, __NR_write syscall mov rdx, 8 ; qword mov rsi, r12 mov rdi, 1 mov rax, __NR_write syscall lea r12, [r12+8] jmp .loop .done: mov rdx, 16 ; 2 qword mov rsi, .dmsg mov rdi, 1 mov rax, __NR_write syscall pop r12 pop r11 ret .space: resq 1 .dmsg: db "DONEDONEYIPPEEEE" ; }}} defcode "HEREDUMP", heredump, 0 mov rdx, [dp] mov r11, [dp0] sub rdx, r11 mov rsi, [dp0] mov rdi, 1 mov rax, __NR_write syscall ret ; }}} defvar "state", state, 0, INTERPRET defvar "base", base, 0, 10 defvar "sp0", sp0, 0, wstk ; todo doc defvar "rp0", rp0, 0, 0 defvar "dp", dp, 0, 0 defvar "dp0", dp0, 0, 0 defvar "dp$", dp$, 0, 0 defvar "tib", tib, 0, initfile defvar "#tib", n_tib, 0, initlen defvar ">in", to_in, 0, 0 defvar "handler", handler, 0, do_0handler defdefervar "latest", latest, 0, lfa_latest initfile: incbin "sanctuary.fs" initlen equ $ - initfile