From f4e833ac46d8169d452b9bcf0a7a5c02b7824a92 Mon Sep 17 00:00:00 2001 From: janis Date: Tue, 4 Aug 2026 18:20:58 +0200 Subject: [PATCH] idk>>?? --- .gitignore | 1 + lang/src/typeck.asm | 65 ++++++++ stages/as0/as1.asm | 385 +++++++++++++++++++++++++++++++++++++++++++ stages/as0/types.zig | 45 +++++ stages/lisp0/lisp.in | 56 +++++++ test.asm | 51 ++++++ 6 files changed, 603 insertions(+) create mode 100644 lang/src/typeck.asm create mode 100644 stages/as0/as1.asm create mode 100644 stages/as0/types.zig create mode 100644 stages/lisp0/lisp.in create mode 100644 test.asm diff --git a/.gitignore b/.gitignore index 9453346..67d6fd6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ lang/target lang/libcompiler/target /target/ +/stages/build/ diff --git a/lang/src/typeck.asm b/lang/src/typeck.asm new file mode 100644 index 0000000..3f3cd99 --- /dev/null +++ b/lang/src/typeck.asm @@ -0,0 +1,65 @@ +default rel +%include "src/ast.inc" + +section .text +extern vec_init_with +extern vec_push +extern vec_get +extern vec_insert_sorted +extern vec_get_or +extern panic +extern memcpy +extern strcmp +extern vec_binary_search_by +extern vec_insert + +extern bump_alloc + +;; rdi: *Ctx +;; rsi: *Ast +;; rdx: node +;; rcx: scope +;; define-fn fn typeck_for_each(ctx: *mut TypeCheckCtx, ast: *mut Ast, node_index: u64, scope: u64) +typeck_for_each: + push rbp + mov rbp, rsp + + ; start-structs + ; struct TypeCheckCtx { + ; type_graph: TCGraph, + ; ast_to_type_map: Vec<(u64, u64)>, + ; } + ; struct TCEdge { + ; nodes: [u64; 2], + ; next_edge: [u64; 2], + ; weight: E, + ; } + ; struct TCNode { + ; next_edge: [u64; 2], + ; weight: N, + ; } + ; struct TCGraph { + ; types: Vec>, + ; type_vars: Vec>, + ; edges: Vec>, + ; } + ; end-structs + + ; In the pre-pass, the typechecker graph is built according to the typing rules of each ast node. + ; E.g., for a binary expression, both operands must be of the same type, and the result type must be a supertype of the operand type: + ; E1(Add, E2(..), E3(..)) + ; E1: T1, E2: T2, E3: T3 + ; T2 == T3, T1 >= T2, T1 >= T3 + + ; In a subsequent pass, the type-variable graph is simplified and equal type nodes are merged to make traversal easier. + ; The resulting type graph should be a DAG. + + sub rsp, 32 + mov [rsp], rsi ; Ast + mov [rsp + 8], rdi ; TypeCheckCtx + mov [rsp + 16], rdx ; node_index + mov [rsp + 24], rcx ; scope + + pop rbp + ret +;; } typeck_for_each diff --git a/stages/as0/as1.asm b/stages/as0/as1.asm new file mode 100644 index 0000000..82fbd2e --- /dev/null +++ b/stages/as0/as1.asm @@ -0,0 +1,385 @@ +section .bss + token_buf resb 0x100 + buf resb 0x100 + labels resb 0x1000 + +section .text +extern panic_abort +;; returns the next character from the source without advancing. +extern peekc +;; returns the next character from the source and advances. +extern getc +;; advances past the next character and peeks the next. +extern consuming_peekc + +;; calculates a quick hash of a byte sequence given by rdi (pointer) and rsi (length) +fasthash: + mov eax, 0x811c9dc5 ; FNV offset basis +.loop: + test rsi, rsi + jz .done + movzx ecx, byte [rdi] ; get next byte + xor eax, ecx + imul eax, 0x1000193 ; FNV prime + inc rdi + dec rsi + jmp .loop +.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 +.loop: + cmp rsi, rax + je .equal + movzx ecx, byte [rdx + rax] + cmp byte [rdi + rax], cl + lea rax, [rax + 1] + je .loop + seta al ; al = lhs > rhs + sbb al, 0 ; al = al - CF + ret +.equal: + xor eax, eax + ret + +memcpy: + xor rax, rax +.loop: + cmp rax, rdx + je .done + mov al, byte [rdi + rax] + mov byte [rsi + rax], al + lea rax, [rax + 1] + jmp .loop +.done: + ret + +;; returns 1 if the result of `peekc()` 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 + +;; struct packed LabelEntry { +;; hash: u64, +;; file_offset: Option, +;; } +;; push the label pointed at by rdi with length rsi, and the current file offset onto the labels array. +push_lbl: + push r12 + lea r12, [rel labels] + mov rax, dword [r12 + 0x1000 - 4] ; get current label count + cmp rax, 341 + jge panic_abort + lea rax, [rax + rax*2] + shl rax, 2 ; $rax = num_labels + size_of::() + add r12, rax + call fasthash + mov qword [r12], rax ; store hash in labels array + mov dword [r12 + 8], -1 ; initialize file offset to -1 (invalid) + inc dword [r12 + 0x1000 - 4] ; increment label count + pop r12 + ret + +;; searches for the offset of the label with the name pointed at by rdi with length rsi, and returns it in rax. If the label is not found, returns 0 in rax. +find_lbl_ptr: + call fasthash + lea rdx, [r15 + 0x5010] ; rdx = pointer to start of labels array + mov ebx, dword [r15 + 0x6000] +.loop: + test rbx, rbx + jz .not_found + dec rbx + cmp qword [rdx], rax + je .found + add rdx, 12 ; move to next label entry + jmp .loop +.found: + mov rax, rdx + ret +.not_found: + xor rax, rax + ret + +push_or_find_lbl: + call fasthash + push rax + mov rdi, rax + call find_lbl_ptr + test rax, rax + jz .push + add rsp, 8 + ret +.push: + pop rdi + call push_lbl + ret + +find_lbl_offset: + call find_lbl_ptr + test rax, rax + jz .not_found + mov eax, dword [rax + 8] ; get file offset of label + ret +.not_found: + xor rdi, rdi + call panic_abort + +set_lbl_offset: + push rsi + call find_lbl_ptr + pop rsi + mov dword [rax + 8], esi ; set file offset of label + ret + +is_alpha: + movzx edi, dil + and edi, 0x1fffdf ; ignore bit 5 (case) + add edi, -65 ; subtract 'A' + cmp edi, 26 ; check if in range 0-25 + setb al + ret + +is_digit: + movzx edi, dil + lea eax, [rdi - 48] ; subtract '0' + cmp eax, 10 ; check if in range 0-9 + setb al + ret + +;; returns 1 if $dil is a valid identifier character (alphanumeric, '-' or '_'), and 0 otherwise. +;; clobbers rdi, rax, rcx +is_id_cont: + movzx rdi, dil + ; is_digit { + lea eax, [rdi - 48] + cmp eax, 10 + setb al + ; } || is_alpha { + mov ecx, edi + and ecx, 0x1fffdf + add ecx, -65 + cmp ecx, 26 + setb cl + ; } is_alpha + or al, cl + cmp edi, 45 ; || '-' + sete cl + or al, cl + cmp edi, 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. +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 + +;; reads from $rdi and parses digits with radix $rsi until a non-digit is encountered. +parse_digits: + sub rsp, 24 + mov qword [rsp + 16], rsi ; save the radix + mov qword [rsp + 8], rdi ; save the source iterator + mov qword [rsp], 0 + .loop: + mov rdi, qword [rsp + 8] ; restore the source iterator + call peekc + mov dil, al + mov rsi, qword [rsp + 16] ; restore the radix + call to_digit + test al, al + jz .done + mov rax, qword [rsp] + mov rsi, qword [rsp + 16] ; restore the radix + imul rax, rsi + add rax, rdx + mov qword [rsp], rax + mov rdi, qword [rsp + 8] ; restore the source iterator + call getc + jmp .loop + + .done: + mov rax, [rsp] + add rsp, 24 + ret + +parse_num: + sub rsp, 24 + mov qword [rsp], 0 ; acc + mov qword [rsp + 8], rdi ; source iterator + mov dword [rsp + 16], 10 ; radix + call peekc + cmp al, `-` + jne .skip_sign + mov qword [rsp], -1 ; acc = -1 + call consuming_peekc +.skip_sign: + cmp al, `0` + jne .skip_radix + call consuming_peekc + cmp al, `x` + jne .skip_radix + mov dword [rsp + 16], 16 ; radix = 16 + call consuming_peekc +.skip_radix: + mov dil, al + call to_digit + test al, al + jz .done + mov rax, qword [rsp] ; acc + mov rsi, qword [rsp + 16] ; radix + mov rcx, rdx + imul rax, rsi + add rax, rcx + mov qword [rsp], rax ; acc = acc * radix + digit + mov rdi, qword [rsp + 8] ; source iterator + call consuming_peekc + jmp .skip_radix +.done: + mov rax, qword [rsp] ; move the result into rax + add rsp, 24 + ret + +parse_label: + push r12 + lea r12, [rel buf] + sub rsp, 8 + mov qword [rsp], rdi ; source iterator + call consuming_peekc + mov dil, al + call is_alpha + test al, al + jz .done + mov byte [r12], al + inc r12 + mov rdi, qword [rsp] ; restore source iterator + call consuming_peekc + mov dil, al + call is_id_cont + test al, al + jz .done + jmp .loop +.done: + lea rdi, [rel buf] + mov rsi, r12 + sub rsi, rdi + call push_or_find_lbl + add rsp, 8 + pop r12 + ret + + ;; reads the next token into `token_buf` +next_token: + sub rsp, 8 + mov qword [rsp], 0 +.skip_spaces: + mov rsi, ' ' + call is_ch + test al, al + jz .skipped_spaces: + call getc + jmp .skip_spaces +.skipped_spaces: + ; leading [ is a memory access + mov rsi, `[` + call is_ch + test al, al + jnz .mem_access + ; leading @ is label-def + mov rsi, '@' + call is_ch + test al, al + jnz .label_def + ; leading ' is a label-ref + mov rsi, `'` + call is_ch + test al, al + jnz .label_ref + ; leading digit is a number + call peekc + mov rdi, rax + call is_digit + test al, al + jnz .number + ; leading ; or # is a comment + mov rsi, `;` + call is_ch + test al, al + jnz .comment + mov rsi, `#` + call is_ch + test al, al + jnz .comment + ; leading , is a separator + mov rsi, `,` + call is_ch + test al, al + jz .ident + ; just skip the comma since its not important for parsing + call getc + jmp .skip_spaces +.ident: + ; now we have some identifier + + ; (mov rax (byte rdi)) + + + +;; registers: +;; r0/rax +;; r1/rcx +;; r2/rdx +;; r3/rbx +;; r4/rsp +;; r5/rbp +;; r6/rsi +;; r7/rdi +;; r8 +;; r9 +;; r10 +;; r11 +;; r12 +;; r13 +;; r14 +;; r15 +;; +;; registers of the form `rNUM` may have an optional suffix b, w, d, or q +;; legacy registers may also be referenced by their 8-bit, 16-bit, or 32-bit names (e.g. al, ax, eax, rax or spl, sp, esp, rsp) +;; high-byte registers (ah, bh, ch, dh) are not supported + diff --git a/stages/as0/types.zig b/stages/as0/types.zig new file mode 100644 index 0000000..727ca82 --- /dev/null +++ b/stages/as0/types.zig @@ -0,0 +1,45 @@ +const PackedOperandKind = enum(u3) { + reg = 0, + imm = 1, + mem = 2, + label = 3, + mem_label = 4, + +}; + +const Register = enum(u4) { + rax = 0, + rbx = 1, + rcx = 2, + rdx = 3, + rsi = 4, + rdi = 5, + rsp = 6, + rbp = 7, + r8 = 8, + r9 = 9, + r10 = 10, + r11 = 11, + r12 = 12, + r13 = 13, + r14 = 14, + r15 = 15, +}; + +const PackedRegister = packed struct { + reg: Register, + size: u4, +}; + +const PackedOperand = union(PackedOperandKind) { + reg: PackedRegister, + imm: i64, + label: u32, + mem_label: u32, + mem: struct { + base: PackedRegister, + index: PackedRegister, + scale: u2, + disp: i32, + }, +}; diff --git a/stages/lisp0/lisp.in b/stages/lisp0/lisp.in new file mode 100644 index 0000000..d446aa9 --- /dev/null +++ b/stages/lisp0/lisp.in @@ -0,0 +1,56 @@ +;; Atom String Table +; t +116 +; quote +113 117 111 116 101 +; + +43 +; - +45 +; * +42 +; / +47 +; % +37 +; < +60 +; = +61 +; car +99 97 114 +; cdr +99 100 114 +; not +100 111 116 +; lambda +108 97 109 98 100 97 +;define +100 101 102 105 100 101 +; eval +101 118 97 108 +; if +105 102 +; let +108 101 116 +; let* +108 101 116 42 +; str-len +115 116 114 45 108 101 100 +; list-len +108 105 115 116 45 108 101 100 +; str-parts +115 116 114 45 112 97 114 116 115 +; syscall +115 121 115 99 97 108 108 +; print-env +112 114 105 100 116 45 101 100 118 +; \Space +92 83 112 97 99 101 +; \NL +92 78 76 +; \Tab +92 84 97 98 +;; Atom Table +; ref-count| length | pointer to string | +01 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 \ No newline at end of file diff --git a/test.asm b/test.asm new file mode 100644 index 0000000..46db33a --- /dev/null +++ b/test.asm @@ -0,0 +1,51 @@ +section .text + +global _start +_start: + mov rdi, 42 + call main + mov rdi, rax + mov rax, 60 ; syscall: exit + syscall + +global main +main: +push rbp +mov rbp, rsp +jmp .prologue +.args: +mov qword [rbp -8], rdi +.body: +mov rbx, 42 +mov rcx, qword [rbp -8] +mov rax, rbx +cmp rcx, rax +sete bl +cmp bl, 0 +je .Be +mov rbx, 7 +mov qword [rbp -16], rbx +mov rdi, qword [rbp -16] +mov rax, rsp +sub rsp, 8 +and rsp, -16 +mov [rsp], rax +call main +pop rsp +mov rbx, rax +mov rax, rbx +jmp .epilogue +jmp .PHI4 +.Be: +lea rax, main +jmp .epilogue +.PHI4: +.epilogue: +pop rbx +add rsp, 16 +pop rbp +ret +.prologue: +sub rsp, 16 +push rbx +jmp .args