parse registers
This commit is contained in:
parent
d54b7ba1a9
commit
f49939b72b
12
stages/as0/Makefile
Normal file
12
stages/as0/Makefile
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
test.bin: test.rs as0.o
|
||||
rustc -Clink-arg=-fuse-ld=mold -Clink-arg=as0.o --edition=2024 --test -g $< -o $@
|
||||
|
||||
as0.o: as0.asm
|
||||
nasm -g -f elf64 -o as0.o as0.asm
|
||||
|
||||
test: test.bin
|
||||
./test.bin
|
||||
|
||||
clean:
|
||||
rm -f as0.o test.bin
|
||||
357
stages/as0/as0.asm
Normal file
357
stages/as0/as0.asm
Normal file
|
|
@ -0,0 +1,357 @@
|
|||
;; 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
|
||||
|
||||
|
||||
148
stages/as0/test.rs
Normal file
148
stages/as0/test.rs
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
unsafe extern "C" {
|
||||
unsafe fn parse_gpr(src: *mut Source, prefix: u8) -> (u64, u64);
|
||||
unsafe fn parse_reg(src: *mut Source) -> (u64, u64);
|
||||
}
|
||||
|
||||
struct Source<'a> {
|
||||
iter: core::iter::Peekable<core::slice::Iter<'a, u8>>,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a [u8]> for Source<'a> {
|
||||
fn from(value: &'a [u8]) -> Self {
|
||||
Self {
|
||||
iter: value.iter().peekable(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
extern "C" fn peekc(src: *mut Source) -> u8 {
|
||||
unsafe { (*src).iter.peek().cloned().unwrap_or(&0).clone() }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
extern "C" fn getc(src: *mut Source) -> u8 {
|
||||
unsafe { (*src).iter.next().unwrap_or(&0).clone() }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_reg() {
|
||||
let cases = [
|
||||
(&b"rax"[..], (0, 8)),
|
||||
(&b"rbx"[..], (1, 8)),
|
||||
(&b"rcx"[..], (2, 8)),
|
||||
(&b"rdx"[..], (3, 8)),
|
||||
(&b"rsp"[..], (4, 8)),
|
||||
(&b"rbp"[..], (5, 8)),
|
||||
(&b"rsi"[..], (6, 8)),
|
||||
(&b"rdi"[..], (7, 8)),
|
||||
(&b"eax"[..], (0, 4)),
|
||||
(&b"ebx"[..], (1, 4)),
|
||||
(&b"ecx"[..], (2, 4)),
|
||||
(&b"edx"[..], (3, 4)),
|
||||
(&b"esp"[..], (4, 4)),
|
||||
(&b"ebp"[..], (5, 4)),
|
||||
(&b"esi"[..], (6, 4)),
|
||||
(&b"edi"[..], (7, 4)),
|
||||
(&b"r8"[..], (8, 8)),
|
||||
(&b"r9"[..], (9, 8)),
|
||||
(&b"r10"[..], (10, 8)),
|
||||
(&b"r11"[..], (11, 8)),
|
||||
(&b"r12"[..], (12, 8)),
|
||||
(&b"r13"[..], (13, 8)),
|
||||
(&b"r14"[..], (14, 8)),
|
||||
(&b"r15"[..], (15, 8)),
|
||||
(&b"r8d"[..], (8, 4)),
|
||||
(&b"r9d"[..], (9, 4)),
|
||||
(&b"r10d"[..], (10, 4)),
|
||||
(&b"r11d"[..], (11, 4)),
|
||||
(&b"r12d"[..], (12, 4)),
|
||||
(&b"r13d"[..], (13, 4)),
|
||||
(&b"r14d"[..], (14, 4)),
|
||||
(&b"r15d"[..], (15, 4)),
|
||||
(&b"r8w"[..], (8, 2)),
|
||||
(&b"r9w"[..], (9, 2)),
|
||||
(&b"r10w"[..], (10, 2)),
|
||||
(&b"r11w"[..], (11, 2)),
|
||||
(&b"r12w"[..], (12, 2)),
|
||||
(&b"r13w"[..], (13, 2)),
|
||||
(&b"r14w"[..], (14, 2)),
|
||||
(&b"r15w"[..], (15, 2)),
|
||||
(&b"r8b"[..], (8, 1)),
|
||||
(&b"r9b"[..], (9, 1)),
|
||||
(&b"r10b"[..], (10, 1)),
|
||||
(&b"r11b"[..], (11, 1)),
|
||||
(&b"r12b"[..], (12, 1)),
|
||||
(&b"r13b"[..], (13, 1)),
|
||||
(&b"r14b"[..], (14, 1)),
|
||||
(&b"r15b"[..], (15, 1)),
|
||||
(&b"ax"[..], (0, 2)),
|
||||
(&b"bx"[..], (1, 2)),
|
||||
(&b"cx"[..], (2, 2)),
|
||||
(&b"dx"[..], (3, 2)),
|
||||
(&b"sp"[..], (4, 2)),
|
||||
(&b"bp"[..], (5, 2)),
|
||||
(&b"si"[..], (6, 2)),
|
||||
(&b"di"[..], (7, 2)),
|
||||
(&b"al"[..], (0, 1)),
|
||||
(&b"bl"[..], (1, 1)),
|
||||
(&b"cl"[..], (2, 1)),
|
||||
(&b"dl"[..], (3, 1)),
|
||||
(&b"ah"[..], (0, 3)),
|
||||
(&b"bh"[..], (1, 3)),
|
||||
(&b"ch"[..], (2, 3)),
|
||||
(&b"dh"[..], (3, 3)),
|
||||
(&b"sil"[..], (6, 1)),
|
||||
(&b"dil"[..], (7, 1)),
|
||||
(&b"asd"[..], (0, 0)),
|
||||
];
|
||||
|
||||
for (text, result) in cases {
|
||||
let mut src = Source::from(text);
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
parse_reg(&mut src),
|
||||
result,
|
||||
"Failed to parse '{}'",
|
||||
std::str::from_utf8(text).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_gpr() {
|
||||
let cases = [
|
||||
(&b"ax"[..], (0, 2)),
|
||||
(&b"bx"[..], (1, 2)),
|
||||
(&b"cx"[..], (2, 2)),
|
||||
(&b"dx"[..], (3, 2)),
|
||||
(&b"sp"[..], (4, 2)),
|
||||
(&b"bp"[..], (5, 2)),
|
||||
(&b"si"[..], (6, 2)),
|
||||
(&b"di"[..], (7, 2)),
|
||||
(&b"al"[..], (0, 1)),
|
||||
(&b"bl"[..], (1, 1)),
|
||||
(&b"cl"[..], (2, 1)),
|
||||
(&b"dl"[..], (3, 1)),
|
||||
(&b"ah"[..], (0, 3)),
|
||||
(&b"bh"[..], (1, 3)),
|
||||
(&b"ch"[..], (2, 3)),
|
||||
(&b"dh"[..], (3, 3)),
|
||||
(&b"sil"[..], (6, 1)),
|
||||
(&b"dil"[..], (7, 1)),
|
||||
(&b"asd"[..], (0, 0)),
|
||||
];
|
||||
|
||||
for (text, result) in cases {
|
||||
let mut src = Source::from(text);
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
parse_gpr(&mut src, b'r'),
|
||||
result,
|
||||
"Failed to parse '{}'",
|
||||
std::str::from_utf8(text).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue