From 6f76735710d6789ea7f4c31b3a001a7849ee1210 Mon Sep 17 00:00:00 2001 From: janis Date: Thu, 4 Jun 2026 23:22:49 +0200 Subject: [PATCH] stage 1 hex --- stages/hex0/hex1.asm | 287 +++++++++++++++++++++++++++++++++++++++++++ stages/hex1/hex1.in | 4 + 2 files changed, 291 insertions(+) create mode 100644 stages/hex0/hex1.asm create mode 100644 stages/hex1/hex1.in diff --git a/stages/hex0/hex1.asm b/stages/hex0/hex1.asm new file mode 100644 index 0000000..8264fc7 --- /dev/null +++ b/stages/hex0/hex1.asm @@ -0,0 +1,287 @@ + ;; This is the 2nd stage of the hex compiler. + ;; It still requires hex input, but it can automatically calculate rip-relative offsets to labels + + ;; we need the following variables: + ;; +0 = buf: [u8; 0x1000] + ;; +0x1000 = write_buf: [u8; 0x4000] + ;; +0x5000 = read_cursor: u32 + ;; +0x5004 write_cursor: u32 + ;; +0x5008 read_end: u32 + ;; +0x500c file_offset: u32 (the offset of the current file in the overall input, used for calculating label offsets) + ;; +0x5010 labels: [(u64, u32); 340] + ;; +0x6000 num_labels: u32 + ;; +0x6004 scratch space + + ;; exits the program +panic: + mov rax, 60 ; syscall: exit + mov rdi, 1 ; status: 1 + syscall + + ;; exits the program +die: + mov rax, 60 ; syscall: exit + mov rdi, 0 ; status: 0 + syscall + + ;; calculates a quick hash of a byte sequence given by rdi (pointer) and rsi (length) + .fashhash_exit: + ret +fasthash: + mov eax, 0x811c9dc5 ; FNV offset basis + .fashhash_loop: + test rsi, rsi + jz .fashhash_done + movzx ecx, byte ptr [rdi] ; get next byte + xor eax, ecx + imul eax, 0x1000193 ; FNV prime + inc rdi + dec rsi + jmp .fashhash_loop + + ;; push the label pointed at by rdi with length rsi, and the current file offset onto the labels array. +push_lbl: + mov rax, num_labels + cmp rax, 340 + jge panic + add rax, rax ; rax = num_labels * 2 + add rax, num_labels ; rax = num_labels * 3 + sal rax, 2 ; rax = num_labels * 12 (size of each label entry) + push rax + call fasthash + pop rdx ; rdx = offset of label entry in labels array + mov [labels + rdx], rax ; store hash in labels array + mov eax, file_offset + mov [labels + rdx + 8], eax ; store file offset in labels array + inc num_labels + 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, dies. + .find_lbl_not_found: + call panic + .find_lbl_found: + mov rax, [rdx + 8] ; get file offset of label + ret +find_lbl: + call fasthash + mov rbx, num_labels + lea rdx, [labels] ; rdx = pointer to start of labels array + .find_lbl_loop: + test rbx, rbx + jz .find_lbl_not_found + dec rbx + mov rcx, [rdx] ; get hash of label at rdx + cmp rcx, rax + je .find_lbl_found + add rdx, 12 ; move to next label entry + jmp .find_lbl_loop + +die_jmp_start: + sub rsp, 8 + mov qword ptr [rsp], '\0\0trats_' + mov rdi, rsp + mov rsi, 6 + call find_lbl + neg eax + mov byte ptr [write_buf + write_cursor], 0xe9 ; write the opcode for jmp rel32 + add write_cursor, 1 + mov dword ptr [write_buf + write_cursor], eax ; write the offset of the label reference + add write_cursor, 4 + add offset 5 + call die + + ;; reads up to 0x1000 bytes from stdin into buf, and sets read_end to the number of bytes read, and resets read_cursor to 0 + ;; dies if no bytes were read (if it returns, there were bytes read) +rd_stdin: + mov rax, 0 ; syscall: read + mov rdi, 0 ; fd: stdin + mov rsi, buf ; buf: pointer to buffer + mov rdx, 0x1000 ; count: size of buffer + syscall + mov read_end, rax + mov read_cursor, 0 + cmp read_end, 0 + je die_jmp_start + ret + + ;; write up to write_cursor to stdout, and then reset the write cursor to 0 +wrt_stdout: + mov rax, 1 ; syscall: write + mov rdi, 1 ; fd: stdout + mov rsi, write_buf ; buf: pointer to buffer + mov rdx, write_cursor ; count: number of bytes to write + syscall + mov write_cursor, 0 + ret + + ;; return the next byte from the input, or 0 if we are at the end of the input + ;; automatically writes to stdout and reads from stdin if we are at the end of the buffer + .rd_buf_end: + mov al, byte ptr [buf + read_cursor] ; get byte at read_cursor + inc read_cursor + ret +rd_buf: + cmp read_cursor, read_end + jl .rd_buf_end + call wrt_stdout + call rd_stdin + jmp .rd_buf_end + + ;; The grammar is slightly updated from hex0, as follows: + ;; program = (label-def / instruction-seq / comment)* + ;; comment = ";" [^\n]* "\n" + ;; label-def = "@" label-name ":" comment? + ;; instruction-seq = instruction+ comment? + ;; instruction = label-ref / opcode + ;; label-ref = "'" label-name + ;; opcode = hex hex + ;; label-name = [a-zA-Z0-9_-]+ + ;; hex = [0-9a-f][0-9a-f] + ;; + ;; whitespaces may occur between any tokens and are ignored. + ;; whitespace = [ \t\n]+ + + + .skip_line_read: + call rd_buf +skip_line: + cmp al, '\n' + jne .skip_line_read + call rd_buf + ret + + .skip_whitespace_read: + call rd_buf +skip_whitespaces: + cmp al, ' ' + je .skip_whitespace_read + cmp al, '\t' + je .skip_whitespace_read + cmp al, '\n' + je .skip_whitespace_read + ret + + .skip_comment_ret: + ret + .skip_comment_read: + call rd_buf +skip_comment: + cmp al, '\;' + jne .skip_comment_ret + call skip_line + ret + +write_u32le: + mov dword ptr [write_buf + write_cursor], edi + add write_cursor, 4 + ret + + .get_next_token_skip: + call skip_whitespaces + call skip_comment +get_next_token: + cmp al, '\;' + je .get_next_token_skip + cmp al, ' ' + je .get_next_token_skip + cmp al, '\t' + je .get_next_token_skip + cmp al, '\n' + je .get_next_token_skip + ret + + ;; read the label into scratch, call push_lbl + .label_ret + lea rdi, [r15 + 0x6004] + call push_lbl + ret +label: + lea rdi, [r15 + 0x6004] ; rdi = pointer to scratch + xor rsi, rsi + cmp al, ':' + je .label_ret + mov byte ptr [rdi + rsi], al + inc rsi + call rd_buf + jmp label + + .label_ref_ret: + lea rdi, [r15 + 0x6004] + call find_lbl + add file_offset, 4 + sub eax, file_offset + mov dword ptr [write_buf + write_cursor], eax ; write the offset of the label reference + add write_cursor, 4 + ret +label_ref: + lea rdi, [r15 + 0x6004] ; rdi = pointer to scratch + xor rsi, rsi + call rd_buf + cmp al, ' ' + je .label_ref_ret + cmp al, '\t' + je .label_ref_ret + cmp al, '\n' + je .label_ref_ret + mov byte ptr [rdi + rsi], al + inc rsi + jmp label_ref + + .invalid_hex: + call panic + .is_digit: + sub al, '0' + ret + .is_lower_hex: + sub al, 'a' - 10 + ret + .is_upper_hex: + sub al, 'A' - 10 + ret +char_to_u4: + cmp al, '0' + jb .invalid_hex + cmp al, '9' + jbe .is_digit + cmp al, 'a' + jb .invalid_hex + cmp al, 'f' + jbe .is_lower_hex + cmp al, 'A' + jb .invalid_hex + cmp al, 'F' + jbe .is_upper_hex + jmp .invalid_hex + +octet: + sub rsp, 8 + call char_to_u4 + shl rax, 4 + mov byte ptr [rsp], al + call rd_buf + call char_to_u4 + or byte ptr [rsp], al + mov al, byte ptr [rsp] + add rsp, 8 + inc file_offset + ret + +_start: + push rbp + mov rbp, rsp + sub rsp, 0x8000 ; allocate space for our variables + some scratch space + mov r15, rsp ; r15 will point to the start of our variables + + .loop: + call get_next_token + cmp al, '@' + ;; label + call label + cmp al, '\'' + ;; label-ref + call label_ref + ;; else opcode + call octet + jmp .loop + + diff --git a/stages/hex1/hex1.in b/stages/hex1/hex1.in new file mode 100644 index 0000000..2129573 --- /dev/null +++ b/stages/hex1/hex1.in @@ -0,0 +1,4 @@ +# panic: +0 +# mov rax, 60 ; syscall: exit +# mov rdi, 1 ; status: 1 +# syscall