parsing operands

This commit is contained in:
janis 2026-06-09 16:43:04 +02:00
parent 9bd36905af
commit 07435f5839
Signed by: janis
SSH key fingerprint: SHA256:bB1qbbqmDXZNT0KKD5c2Dfjg53JGhj7B3CFcLIzSqq8
2 changed files with 471 additions and 1 deletions

View file

@ -2,6 +2,9 @@
;; It's also the first stage in this bootstrapping experiment that will ;; It's also the first stage in this bootstrapping experiment that will
;; consume proper mnemonic assembly, rather than hex input. ;; consume proper mnemonic assembly, rather than hex input.
section .bss
buf resb 0x100
labels resb 0x1000
section .data section .data
_start_lbl db "_start", 0 _start_lbl db "_start", 0
@ -42,6 +45,214 @@ _mov:
db 2 db 2
dq instr_table - _mov_handler dq instr_table - _mov_handler
;; calculates a quick hash of a byte sequence given by rdi (pointer) and rsi (length)
fasthash:
mov eax, 0x811c9dc5 ; FNV offset basis
.fashhash_loop:
test rsi, rsi
jz .fashhash_exit
movzx ecx, byte [rdi] ; get next byte
xor eax, ecx
imul eax, 0x1000193 ; FNV prime
inc rdi
dec rsi
jmp .fashhash_loop
.fashhash_exit:
ret
;; 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
lea rax, [rax + rax*2]
shl rax, 2 ; $rax = num_labels + size_of::<label_entry>()
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, dies.
find_lbl_ptr:
call fasthash
lea rdx, [r15 + 0x5010] ; rdx = pointer to start of labels array
mov ebx, dword [r15 + 0x6000]
.find_lbl_loop:
test rbx, rbx
jz .find_lbl_not_found
dec rbx
cmp qword [rdx], rax
je .find_lbl_found
add rdx, 12 ; move to next label entry
jmp .find_lbl_loop
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_not_found:
xor rax, rax
ret
.find_lbl_found:
mov rax, rdx
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, 2097119
add edi, -65
cmp edi, 26
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
lea eax, [rdi - 48]
cmp eax, 10
setb al
mov ecx, edi
and ecx, 2097119
add ecx, -65
cmp ecx, 26
setb cl
or al, cl
cmp edi, 45 ; '-'
sete cl
or al, cl
cmp edi, 95 ; '_'
sete cl
or al, cl
ret
is_digit:
movzx edi, dil
add edi, -48
cmp edi, 10
setb al
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
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
;; @param lhs: (rdi, rsi) ;; @param lhs: (rdi, rsi)
;; @param rhs: (rdx, rcx) ;; @param rhs: (rdx, rcx)
;; @return al ;; @return al
@ -83,8 +294,27 @@ read_file:
; ;; returns the next byte in the input stream and advances the read position. ; ;; returns the next byte in the input stream and advances the read position.
; getc: ; getc:
extern panic_abort
extern peekc extern peekc
extern getc extern getc
extern consuming_peekc
skip_whitespaces:
push rdi
call peekc
.loop:
cmp al, ' '
je .read
cmp al, `\t`
je .read
cmp al, `\n`
je .read
pop rdi
ret
.read:
call consuming_peekc
jmp .loop
;; 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. ;; 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: to_digit:
@ -155,6 +385,8 @@ parse_digits:
; R15, ; R15,
;; } ;; }
;; parses a non-extended GPR operand (e.g. a register name that isn't r8-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 ;; returns the register number in rax and the width in rdx
;; if $rdx == 0, then parsing failed (invalid register name) ;; if $rdx == 0, then parsing failed (invalid register name)
@ -342,16 +574,187 @@ parse_reg:
ret ret
global parse_packed_reg
parse_packed_reg:
call parse_reg
test rdx, rdx
jz .invalid
shl rdx, 4
or rax, rdx
ret
.invalid:
xor rax, rax
ret
;; enum Operand { ;; enum Operand {
;; Reg(Reg, width: u8), ;; Reg(Reg, width: u8),
;; Imm(u32, width: u8), ;; Imm(u32, width: u8),
;; Mem(width: u8, base: Option<Reg>, index: Option<(Reg, scale: u8)>, disp: Option<u32>), ;; Mem(width: u8, base: Option<Reg>, index: Option<(Reg, scale: u8)>, disp: Option<u32>),
;; } ;; }
;; operands can be registers, memory operands, immediates or labels
;; labels start with a '
;; immediates start with a digit or a '-'
;; memory operands may have the forms: [<label>] | [reg ( +reg*scale)? (+ disp)?] | [+reg*scale (+ disp)?]
parse_operand:
call peekc
cmp al, `'`
je .label
cmp al, `[`
je .mem
cmp al, `-`
je .imm
cmp al, `9`
jb .imm
jmp .reg
.reg:
call parse_packed_reg
test rax, rax
jz .invalid
mov rdx, rax
mov rax, 0 ; operand type = reg
ret
.imm:
call parse_num
mov rdx, rax
mov rax, 1 ; operand type = imm
ret
.label:
call parse_label
mov edx, dword [rax + 8] ; get the offset of the label
mov rax, 3 ; operand type = label
ret
.mem:
sub rsp, 40
mov byte [rsp], 0xff ; base
mov byte [rsp + 1], 0xff ; index
mov byte [rsp + 2], 0 ; scale
mov qword [rsp + 4], 0 ; disp
mov qword [rsp + 32], rdi ; source iterator
call getc
mov rdi, qword [rsp + 32] ; restore source iterator
call peekc
cmp al, `'`
je .mem_label
mov rdi, qword [rsp + 32] ; restore source iterator
call parse_packed_reg
test rax, rax
jz .invalid
mov rdi, qword [rsp + 32] ; restore source iterator
call skip_whitespaces
call peekc
cmp al, `+`
je .index_part
cmp al, `]`
je .mem_done
cmp al, `*`
jne .invalid
mov al, byte [rsp]
mov byte [rsp + 1], al ; index = base if there's no base
.scale_part:
mov rdi, qword [rsp + 32] ; restore source iterator
call getc ; consume the '*'
mov rdi, qword [rsp + 32] ; restore source iterator
call skip_whitespaces
mov rdi, qword [rsp + 32] ; restore source iterator
call parse_num
bsr eax, eax ; TODO: maybe?
mov byte [rsp + 2], al ; scale
mov rdi, qword [rsp + 32] ; restore source iterator
call skip_whitespaces
cmp al, `+`
je .disp_part
cmp al, `]`
je .mem_done
jmp .invalid
.index_part:
mov rdi, qword [rsp + 32] ; restore source iterator
call skip_whitespaces
; check if displacement
cmp al, `-`
je .disp_part
cmp al, `9`
jb .disp_part
mov rdi, qword [rsp + 32] ; restore source iterator
call parse_packed_reg
test rax, rax
jz .invalid
mov byte [rsp + 1], al ; index
mov rdi, qword [rsp + 32] ; restore source iterator
call skip_whitespaces
cmp al, `+`
je .disp_part
cmp al, `]`
je .mem_done
cmp al, `*`
jne .invalid
jmp .scale_part
.disp_part:
mov rdi, qword [rsp + 32] ; restore source iterator
call parse_num
mov qword [rsp + 4], rax ; disp
mov rdi, qword [rsp + 32] ; restore source iterator
call skip_whitespaces
cmp al, `]`
je .mem_done
jmp .invalid
.mem_done:
mov rax, 2 ; operand type = mem
mov rdx, qword [rsp] ; 8b8i2s32d
add rsp, 40
ret
.mem_label:
mov rdi, qword [rsp + 32] ; restore source iterator
call parse_label
mov rdx, rax
mov rax, 4 ; operand type = mem_label
add rsp, 40
ret
.invalid:
xor rdi, rdi
call panic_abort
;; mov r/m, r ;; mov r/m, r
;; mov r, r/m ;; mov r, r/m
;; mov r/m, imm ;; mov r/m, imm
_mov_handler: _mov_handler:
sub rsp, 40
mov qword [rsp], rdi ; source iterator
call skip_whitespaces
mov rdi, qword [rsp] ; restore source iterator
call parse_operand
mov qword [rsp + 8], rax ; operand 1 type
mov qword [rsp + 16], rdx ; operand 1 value
mov rdi, qword [rsp] ; restore source iterator
call skip_whitespaces
mov rdi, qword [rsp] ; restore source iterator
call peekc
cmp al, `,`
jne .invalid
mov rdi, qword [rsp] ; restore source iterator
call getc
mov rdi, qword [rsp] ; restore source iterator
call skip_whitespaces
mov rdi, qword [rsp] ; restore source iterator
call parse_operand
mov qword [rsp + 24], rax ; operand 2 type
mov qword [rsp + 32], rdx ; operand 2 value
ret ret

View file

@ -146,3 +146,70 @@ fn test_parse_gpr() {
} }
} }
} }
enum Register {
A = 0,
B,
C,
D,
Src,
Dst,
Sp,
Bp,
R8,
R9,
R10,
R11,
R12,
R13,
R14,
R15,
}
enum RegisterSize {
Byte = 1,
Word = 2,
Dword = 4,
Qword = 8,
HighByte = 3,
None = 0,
}
#[repr(C)]
struct PackedRegister(u8);
impl From<PackedRegister> for Option<(Register, RegisterSize)> {
fn from(value: PackedRegister) -> Self {
let reg_bits = value.0 & 0x00F;
let size_bits = (value.0 >> 4) & 0x0F;
let size = match size_bits {
1 => RegisterSize::Byte,
2 => RegisterSize::Word,
4 => RegisterSize::Dword,
8 => RegisterSize::Qword,
3 => RegisterSize::HighByte,
0 => return None,
_ => panic!("Invalid register size"),
};
let reg = match reg_bits {
0 => Register::A,
1 => Register::B,
2 => Register::C,
3 => Register::D,
4 => Register::Sp,
5 => Register::Bp,
6 => Register::Src,
7 => Register::Dst,
8 => Register::R8,
9 => Register::R9,
10 => Register::R10,
11 => Register::R11,
12 => Register::R12,
13 => Register::R13,
14 => Register::R14,
15 => Register::R15,
_ => panic!("Invalid register number"),
};
Some((reg, size))
}
}