from-scratch/stages/as0/as0.asm
2026-06-07 00:11:58 +02:00

358 lines
8.4 KiB
NASM

;; This assembler is a 2-pass compiler, permitting forward and backward label references.
;; It's also the first stage in this bootstrapping experiment that will
;; consume proper mnemonic assembly, rather than hex input.
section .data
_start_lbl db "_start", 0
;; supported instructions:
;; mov
;; test
;; xor
;; and
;; shl
;; shr
;; inc
;; dec
;; neg
;; ret
;; not
;; add
;; sub
;; imul
;; cmp
;; lea
;; push, pop
;; cmovcc
;; xchg
;; bts
;; bsf
;; bt
;; syscall
;; jcc rel32
;; jmp rel32
;; call rel32
section .text
instr_table:
; mnemonic, num_operands, handler
_mov:
dq "mov"
db 2
dq instr_table - _mov_handler
;; @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
memcpy:
xor rax, rax
.memcpy_loop:
cmp rax, rdx
je .memcpy_done
mov al, byte [rdi + rax]
mov byte [rsi + rax], al
lea rax, [rax + 1]
jmp .memcpy_loop
.memcpy_done:
ret
;; read from input file
read_file:
; ;; returns the next byte in the input stream without advancing the read position.
; peekc:
; ;; returns the next byte in the input stream and advances the read position.
; getc:
extern peekc
extern getc
;; 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
;; enum Register {
; A = 0,
; B,
; C,
; D,
; Src,
; Dst,
; Sp,
; Bp,
; R8,
; R9,
; R10,
; R11,
; R12,
; R13,
; R14,
; R15,
;; }
;; parses a non-extended GPR operand (e.g. a register name that isn't r8-r15)
;; returns the register number in rax and the width in rdx
;; if $rdx == 0, then parsing failed (invalid register name)
;; $rdi = source
;; $rsi = prefix char (e.g. 'r' or 'e' or 0)
global parse_gpr
parse_gpr:
sub rsp, 24
mov qword [rsp + 16], -1 ; register number, initialized to -1 (invalid)
mov qword [rsp + 8], rdi ; source iterator
mov qword [rsp], rsi ; prefix char
call getc
; check for a,b,c,d,s
movzx rax, al
sub rax, 'a' ; convert 'a' to 0, 'b' to 1, ..., 's' to 4
cmp rax, 's' - 'a'
ja .invalid ; if it's greater than 's', it's invalid
mov rcx, 4
cmovz rax, rcx ; if it's 's', set rax to 4
cmp rax, 4
ja .invalid ; if it's greater than 4, it's invalid
lea rcx, [rel .jt] ; jump table for a,b,c,d
movsxd rdx, dword [rcx + rax * 4] ; get the offset of the handler if a-d
add rcx, rdx ; calculate the address of the handler
jmp rcx
.suffix:
mov byte [rsp + 16], al ; save the register number for a,b,c,d,s
mov rdi, qword [rsp + 8] ; restore the source iterator
call peekc
cmp al, 'l'
jne .not_l
mov rdi, qword [rsp + 8] ; restore the source iterator
call getc ; consume the suffix
mov rdx, 1 ; width = 1 for al, bl, cl, dl, sil, dil
jmp .done
.not_l: ; x is only valid for a,b,c,d, not sp, bp, si or di
mov rdx, 2
cmp byte [rsp + 16], 4
jae .done ; for sp, bp, si, di, the width is 2 if not 'l'
cmp al, 'x'
jne .not_x
mov rdi, qword [rsp + 8] ; restore the source iterator
call getc ; consume the suffix
mov rdx, 2 ; width = 2 for ax, bx, cx, dx
jmp .done
.not_x: ; x is only valid for a,b,c,d
cmp al, 'h'
jne .invalid
mov rdx, 3 ; width = 3 for ah, bh, ch, dh
.done:
movzx rax, byte [rsp + 16] ; move the register number into rax
add rsp, 24
ret
.invalid:
xor rax, rax ; rax = 0 indicates invalid register
xor rdx, rdx ; rdx = 0 indicates invalid register
jmp .done
.a:
mov rax, 0
jmp .suffix
.b:
mov rdi, qword [rsp + 8] ; restore the source iterator
call peekc
cmp al, 'p'
mov rax, 1
jne .suffix
mov rdi, qword [rsp + 8] ; restore the source iterator
call getc
mov rax, 5
jmp .suffix
.c:
mov rax, 2
jmp .suffix
.d:
mov rdi, qword [rsp + 8] ; restore the source iterator
call peekc
cmp al, 'i'
mov rax, 3
jne .suffix
mov rdi, qword [rsp + 8] ; restore the source iterator
call getc
mov rax, 7
jmp .suffix
.s:
mov rdi, qword [rsp + 8] ; restore the source iterator
call getc
mov cl, al
cmp cl, 'i'
mov rax, 6
je .suffix
cmp cl, 'p'
mov rax, 4
je .suffix
jmp .invalid
.jt:
dd .a-.jt
dd .b-.jt
dd .c-.jt
dd .d-.jt
dd .s-.jt
;; parses an extended GPR operand (e.g. r8-r15)
;; $rdi = source, leading r is already consumed
parse_egpr:
sub rsp, 16
mov qword [rsp + 8], rdi ; source iterator
mov rsi, 10
call parse_digits
cmp rax, 8
jb .invalid
cmp rax, 15
ja .invalid
mov qword [rsp], rax
.suffix:
mov rdi, qword [rsp + 8] ; restore the source iterator
call peekc
mov rcx, 1
cmp al, 'b'
je .done_getc
mov rcx, 2
cmp al, 'w'
je .done_getc
mov rcx, 4
cmp al, 'd'
je .done_getc
mov rcx, 8 ; default width is 8 for r8-r15
jmp .done
.done_getc:
mov rdi, qword [rsp + 8] ; restore the source iterator
push rcx
call getc
pop rcx
.done:
mov rdx, rcx
mov rax, qword [rsp] ; move the register number into rax
add rsp, 16
ret
.invalid:
mov qword [rsp], 0
xor rcx, rcx ; rdx = 0 indicates invalid register
jmp .done
;; (rdi, rsi): source string
global parse_reg
parse_reg:
sub rsp, 16
mov qword [rsp + 8], rdi ; source iterator
mov byte [rsp], 0 ; x1
call peekc
cmp al, 'r'
mov rdi, qword [rsp + 8] ; restore the source iterator
je .r
cmp al, 'e'
jne .gpr
mov byte [rsp], 1 ; x2
call getc ; consume the 'e'
mov rdi, qword [rsp + 8] ; restore the source iterator
jmp .gpr
.r:
mov byte [rsp], 2 ; x4
call getc ; consume the 'r'
mov rdi, qword [rsp + 8] ; restore the source iterator
call peekc
mov dil, al
mov rsi, 10
call to_digit
test al, al
mov rdi, qword [rsp + 8] ; restore the source iterator
jz .gpr
call parse_egpr
jmp .done
.gpr:
call parse_gpr
mov cl, byte [rsp]
shl rdx, cl
.done:
add rsp, 16
ret
;; enum Operand {
;; Reg(Reg, width: u8),
;; Imm(u32, width: u8),
;; Mem(width: u8, base: Option<Reg>, index: Option<(Reg, scale: u8)>, disp: Option<u32>),
;; }
;; mov r/m, r
;; mov r, r/m
;; mov r/m, imm
_mov_handler:
ret