parse operands

This commit is contained in:
janis 2026-07-07 18:14:14 +02:00
parent f87ab3afa4
commit c4f9eb0e55
Signed by: janis
SSH key fingerprint: SHA256:bB1qbbqmDXZNT0KKD5c2Dfjg53JGhj7B3CFcLIzSqq8
2 changed files with 788 additions and 96 deletions

View file

@ -5,7 +5,9 @@
section .bss section .bss
global buf global buf
buf resb 0x100 buf resb 0x100
labels resb 0x1000 labels resq 1
label_refs resq 1
heap resq 1
section .data section .data
_start_lbl db "_start", 0 _start_lbl db "_start", 0
@ -374,6 +376,230 @@ op_table_end:
section .text section .text
panic_abort:
mov rdi, 1
mov rax, 60
syscall
;; Allocator
;; allocates $rdi bytes worth of pages via mmap
alloc_pages:
mov rax, 9 ; syscall: mmap
mov rsi, rdi ; length: rdi
xor rdi, rdi ; addr: NULL
mov rdx, 3 ; prot: PROT_READ | PROT_WRITE
mov r10, 34 ; flags: MAP_PRIVATE | MAP_ANONYMOUS
mov r8, -1 ; fd: -1
xor r9, r9 ; offset: 0
syscall
cmp rax, -1
jae panic_abort
ret
dealloc_pages:
mov rax, 11 ; syscall: munmap
mov rdi, rsi ; addr: rsi
mov rsi, rdx ; length: rdx
syscall
cmp rax, -1
jae panic_abort
ret
;; reallocates memory at $rdi[..$rsi] to a new location of size $rdx.
realloc_pages:
sub rsp, 24
mov qword [rsp], rsi
mov qword [rsp + 8], rdi
mov rdi, rdx
call alloc_pages
mov rsi, rdi
mov rdi, qword [rsp + 8]
mov rdx, qword [rsp]
mov qword [rsp + 16], rax
call memcpy
mov rdi, qword [rsp + 8]
mov rsi, qword [rsp]
call dealloc_pages
mov rax, qword [rsp + 16]
add rsp, 24
ret
make_slab:
push rdi
mov rdi, 0x4000
call alloc_pages
mov qword [rax + 0x4000 - 8], 0 ; initialize the tail pointer to NULL
pop rdi
mov qword [rdi], 0 ; tail_end = 0
mov qword [rdi + 8], 0 ; free = NULL
mov qword [rdi + 16], rax ; first_block = allocated
mov qword [rdi + 24], rax ; last_block = allocated
mov rax, rdi
ret
init_heap:
push r14
xor r14, r14
mov rax, qword [rel heap]
test rax, rax
jnz .done
mov rdi, 0x1000
call alloc_pages
mov qword [rel heap], rax
.loop:
cmp r14, 9
jge .done
mov rdi, r14
shl rdi, 5 ; idx * 32
mov rax, qword [rel heap]
lea rdi, [rax + rdi] ; &heap.slabs[idx]
call make_slab
inc r14
jmp .loop
.done:
pop r14
ret
;; finds the correct slab for an allocation with size $rdi and align $rsi
slab_bucket:
xor rax, rax
dec rdi ; if size is a power of two, dec so we can later inc
dec rsi ; ^^
or rdi, rsi ; we only care about the log2, so just gather all the bits
bsr rsi, rdi ; log2((size-1) | (align-1))
sub rsi, 2 ; +1 for the dec, -3 to collapse the first 3 slabs into one
cmovae rax, rsi ; saturating sub
ret
slab_alloc:
push rbx
mov rax, qword [rdi + 8] ; free
test rax, rax
jz .no_free
mov rdx, qword [rax] ; next free chunk
mov qword [rdi + 8], rdx ; free = next
pop rbx
ret
.no_free:
add esi, 3 ; undo the -3 from slab_bucket to get the actual log2(size)
and esi, 63 ; clamp for safety
mov edx, 16376 ; 0x4000 - 8
mov ecx, esi
shr rdx, cl ; 0x4000 - 8 >> log2(size)
mov rax, qword [rdi] ; tail_end
mov rbx, qword [rdi + 24] ; last_block
cmp rax, rdx
jb .alloc_from_block
push rsi
push rax
push rdi
mov rdi, 0x4000 ; allocate a new block
call alloc_pages
mov qword [rax + 0x4000 - 8], 0 ; initialize the tail pointer to NULL
pop rdi
mov rsi, qword [rdi + 24] ; last_block
mov qword [rsi + 0x4000 - 8], rax
mov qword [rdi + 24], rax ; last_block = new block
mov rbx, rax
pop rax
pop rsi
.alloc_from_block:
inc qword [rdi] ; tail_end++
mov ecx, esi
shl rax, cl
add rax, rbx
pop rbx
ret
;; allocate a chunk of memory of size $rdi and alignment $rsi
heap_alloc:
mov rax, qword [rel heap]
cmp rax, 0
je .init
.is_init:
push rdi
call slab_bucket
cmp rax, 9
jge .mmap
mov rsi, rax
shl rax, 5 ; idx * 32
mov rdi, qword [rel heap]
lea rdi, [rdi + rax] ; &heap.slabs[idx]
call slab_alloc
pop rdi
ret
.init:
push rdi
push rsi
call init_heap
pop rsi
pop rdi
jmp .is_init
.mmap:
pop rdi
call alloc_pages
ret
;; deallocates a chunk of memory at $rdi of size $rsi and alignment $rdx
heap_dealloc:
push rsi
push rdi
mov rdi, rsi
mov rsi, rdx
call slab_bucket
cmp rax, 9
jge .mmap
mov rsi, rax
shl rax, 5 ; idx * 32
mov rdi, qword [rel heap]
lea rdi, [rdi + rax] ; &heap.slabs[idx]
mov rax, qword [rdi + 8] ; free
pop rsi
mov qword [rsi], rax
mov qword [rdi + 8], rsi
pop rsi
ret
.mmap:
pop rdi
pop rsi
call dealloc_pages
ret
;; reallocates a chunk of memory at $rdi of size $rsi and alignment $rdx to a new location of size $rcx
heap_realloc:
push rbx
push r12
push r13
push r14
mov rbx, rdi
mov r12, rsi
mov r13, rdx
mov r14, rcx
mov rdi, r14
mov rsi, r13
call heap_alloc
mov r14, rax
mov rdi, rbx
mov rsi, rax
mov rdx, r12
call memcpy
mov rdi, rbx
mov rsi, r12
mov rdx, r13
call heap_dealloc
mov rax, r14
pop r14
pop r13
pop r12
pop rbx
ret
;; format dil as hex u8 into $ax ;; format dil as hex u8 into $ax
format_u8: format_u8:
xor ecx, ecx xor ecx, ecx
@ -613,6 +839,34 @@ is_digit:
setb al setb al
ret ret
is_alphanumeric:
lea eax, [rdi - 48]
cmp eax, 10
setb cl
and dil, 0xdf
add dil, -65
cmp dil, 26
setb al
or al, cl
ret
is_id_start:
call is_alpha
cmp dil, 95
sete cl
or al, cl
ret
is_id_cont:
call is_alphanumeric
cmp dil, 45
sete cl
or al, cl
cmp dil, 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. ;; 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:
lea eax, [rsi - 2] lea eax, [rsi - 2]
@ -646,6 +900,8 @@ exit:
%define KIND_REG 1 %define KIND_REG 1
%define KIND_MEM 2 %define KIND_MEM 2
%define KIND_IMM 3 %define KIND_IMM 3
%define KIND_MEM_LABEL 4 ; a memory operand whose displacement is a label
%define KIND_IMM_LABEL 5 ; an immediate value that is a label
struc operand struc operand
.type resd 1 .type resd 1
@ -679,7 +935,7 @@ try_parse_reg:
mov rbx, rdi mov rbx, rdi
mov dil, byte [rbx] mov dil, byte [rbx]
call is_alpha call is_alpha
jnb .done jnb .not_reg
cmp dil, 'r' cmp dil, 'r'
jne .not_64_or_rX jne .not_64_or_rX
@ -707,10 +963,11 @@ try_parse_reg:
mov rsi, 3 mov rsi, 3
mov dl, byte [rbx] mov dl, byte [rbx]
call strfind call strfind
mov r13d, 3
cmp rax, 0 cmp rax, 0
jl .done
mov r13d, eax mov r13d, eax
mov edi, 3 inc rbx
cmovl r13d, edi
jmp .done jmp .done
.not_rX: .not_rX:
@ -720,6 +977,11 @@ try_parse_reg:
mov dx, word [rbx] mov dx, word [rbx]
add rbx, 2 add rbx, 2
xor eax, eax
cmp r13d, 0
sete al
add rbx, rax ; spl, bpl, sil, dil have a 3rd character we need to account for
mov r12d, 4 ; rsp mov r12d, 4 ; rsp
cmp dx, 'sp' cmp dx, 'sp'
je .done je .done
@ -740,12 +1002,20 @@ try_parse_reg:
cmp dx, 'ip' cmp dx, 'ip'
je .done je .done
sub rbx, rax ; its not spl, bpl, sil, dil, so undo the inc we did earlier
lea rax, [rel abcd_reg_indices] lea rax, [rel abcd_reg_indices]
sub dl, 'a' sub dl, 'a'
cmp dl, 3
ja .not_reg
movzx edx, dl movzx edx, dl
movzx r12d, byte [rax + rdx] ; rax, rcx, rdx, rbx movzx r12d, byte [rax + rdx] ; rax, rcx, rdx, rbx
.done: .done:
mov dil, byte [rbx]
call is_alphanumeric
jnz .not_reg ; if the next char is alphanumeric, then this is not a valid reg
mov rax, rbx mov rax, rbx
pop rdi pop rdi
sub rax, rdi ; number of bytes consumed sub rax, rdi ; number of bytes consumed
@ -760,6 +1030,15 @@ try_parse_reg:
pop rbx pop rbx
ret ret
.not_reg:
xor rax, rax
xor rdx, rdx
pop rdi
pop r13
pop r12
pop rbx
ret
.not_64_or_rX: .not_64_or_rX:
cmp dil, 'e' cmp dil, 'e'
jne .not_32 jne .not_32
@ -791,10 +1070,9 @@ try_parse_num:
xor eax, eax xor eax, eax
mov rbx, rdi mov rbx, rdi
; eat leading - and flip sign ; eat leading -
cmp byte [rbx], '-' cmp byte [rbx], '-'
sete al sete al
sub r13, rax
lea rbx, [rbx + rax] lea rbx, [rbx + rax]
; eat any leading + ; eat any leading +
@ -852,6 +1130,11 @@ try_parse_num:
sub rax, rdi ; number of bytes consumed sub rax, rdi ; number of bytes consumed
mov rdx, r13 ; acc mov rdx, r13 ; acc
cmp byte [rdi], '-'
jne .positive
neg rdx
.positive:
pop r13 pop r13
pop r12 pop r12
pop rbx pop rbx
@ -931,9 +1214,14 @@ try_parse_mem:
jne .not_mem jne .not_mem
inc rbx inc rbx
cmp byte [rbx], `'`
je .disp_lbl
call skip_whitespace_rbx call skip_whitespace_rbx
mov rdi, rbx mov rdi, rbx
call try_parse_reg call try_parse_reg
test eax, eax
jz .disp
add rbx, rax add rbx, rax
call skip_whitespace_rbx call skip_whitespace_rbx
@ -958,6 +1246,7 @@ try_parse_mem:
; TODO: allow for label as displacement ; TODO: allow for label as displacement
.disp: .disp:
mov dil, byte [rbx]
sub dil, `,` sub dil, `,`
neg dil ; sign neg dil ; sign
mov al, dil mov al, dil
@ -969,6 +1258,12 @@ try_parse_mem:
inc rbx inc rbx
call skip_whitespace_rbx call skip_whitespace_rbx
.disp_skip_sign:
cmp byte [rbx], `'`
je .disp_lbl
mov rdi, rbx mov rdi, rbx
call try_parse_num call try_parse_num
test eax, eax test eax, eax
@ -983,19 +1278,39 @@ try_parse_mem:
je .done je .done
jmp .not_mem jmp .not_mem
.disp_lbl:
inc rbx ; skip the leading `'`
mov dword [r12 + operand.type], KIND_MEM_LABEL
mov rdi, rbx
call try_parse_label
test eax, eax
jz .not_mem
mov rdi, rbx
mov rsi, rax
add rbx, rax
call fasthash
mov dword [r12 + operand.disp], eax
call skip_whitespace_rbx
cmp byte [rbx], ']'
je .done
jmp .not_mem
.base: .base:
mov qword [r12 + operand.reg], rdx mov qword [r12 + operand.reg], rdx
cmp byte [rbx], ']' cmp byte [rbx], ']'
je .done je .done
cmp byte [rbx], '+' cmp byte [rbx], '+'
jne .not_mem jne .disp
inc rbx inc rbx
call skip_whitespace_rbx call skip_whitespace_rbx
mov rdi, rbx mov rdi, rbx
call try_parse_reg call try_parse_reg
test eax, eax test eax, eax
jz .disp jz .disp_skip_sign
add rbx, rax add rbx, rax
mov qword [r12 + operand.index], rdx mov qword [r12 + operand.index], rdx
@ -1026,6 +1341,232 @@ try_parse_mem:
ret ret
global try_parse_operand
try_parse_operand:
push rbx ; buf
push r12 ; operand ptr
mov rbx, rdi
mov r12, rsi ; output operand ptr
mov dword [r12 + operand.type], KIND_NONE
mov dword [r12 + operand.size], 0
mov qword [r12 + operand.reg], -1
mov qword [r12 + operand.index], -1
mov dword [r12 + operand.scale], 0
mov dword [r12 + operand.disp], 0
mov al, byte [rbx]
cmp al, '['
je .mem
cmp al, `'`
je .label
mov dword [r12 + operand.type], KIND_IMM
call try_parse_num
test eax, eax
jz .not_imm
mov qword [r12 + operand_imm.imm], rdx
test rdx, rdx
jz .done
mov rcx, rdx
neg rdx
cmovs rdx, rcx ; abs(imm)
bsr rcx, rdx ; # of bits required to represent the immediate
mov edx, ecx
cmp edx, 1
adc edx, -1 ; saturating sub
bsr edx, edx ; log2 of the (# of bits - 1) + 1
inc edx
cmp ecx, 2
mov ecx, 1
cmovae ecx, edx ; checked ilog2
mov dword [r12 + operand.size], ecx
jmp .done
.not_imm:
mov dword [r12 + operand.type], KIND_REG
mov rdi, rbx
call try_parse_reg
test eax, eax
jz .not_reg
mov qword [r12 + operand.reg], rdx
mov dword [r12 + operand.size], edx ; low dword of rdx is the size
jmp .done
.not_reg:
.mem:
mov rdi, rbx
mov rsi, r12
call try_parse_mem
test eax, eax
jz .dont_know
jmp .done
.label:
inc rbx ; skip the leading `'`
mov dword [r12 + operand.type], KIND_IMM_LABEL
mov rdi, rbx
call try_parse_label
test eax, eax
jz .dont_know
mov rdi, rbx
mov rsi, rax
mov rbx, rax
call fasthash
mov dword [r12 + operand_imm.imm], eax
mov rax, rbx ; number of bytes consumed
jmp .done
.dont_know:
xor rax, rax
.done:
mov rdx, r12 ; output operand ptr
pop r12
pop rbx
ret
try_parse_label:
push rbx ; buf
push r12 ; label length
mov rbx, rdi
xor r12d, r12d
mov dil, byte [rbx]
call is_id_start
jz .done
inc r12d
.loop:
mov dil, byte [rbx + r12]
call is_id_cont
jz .done
inc r12d
jmp .loop
.done:
mov eax, r12d
pop r12
pop rbx
ret
;; Labels
struc Label
.name_hash resd 1
.offset resd 1
endstruc
struc LabelTable
.size resd 1
.cap resd 1
.labels resq 1
endstruc
;; calculates the hash of a byte sequence in $rdi of length $rsi, returning it in $eax
global fasthash
fasthash:
mov eax, 0x811C9DC5 ; FNV-1a 32-bit offset basis
.loop:
test rsi, rsi
jz .done
movzx ecx, byte [rdi]
xor eax, ecx
imul eax, 0x01000193 ; FNV prime
inc rdi
dec rsi
jmp .loop
.done:
ret
;; push label $rdi with length $rsi and offset $rdx into the label ref table
push_lbl_ref:
mov rcx, [rel label_refs]
jmp push_lbl_common
;; push label $rdi with length $rsi and offset $edx into the label table
push_lbl:
mov rcx, [rel labels]
push_lbl_common:
push rbx
push r12
push r13
mov rbx, rcx
mov r13d, edx ; label offset
call fasthash
mov r12d, eax ; store the hash in r12d
mov eax, dword [rbx + LabelTable.size]
cmp eax, dword [rbx + LabelTable.cap]
jl .has_space
mov rdi, qword [rbx + LabelTable.labels]
mov esi, dword [rbx + LabelTable.cap]
mov edx, 8
mov ecx, esi
shl ecx, 1
call heap_realloc
mov qword [rbx + LabelTable.labels], rax
shl dword [rbx + LabelTable.cap], 1
.has_space:
mov rax, qword [rbx + LabelTable.labels]
mov edi, dword [rbx + LabelTable.size]
lea rax, [rax + rdi * 8]
mov dword [rax + Label.name_hash], r12d
mov dword [rax + Label.offset], r13d
inc dword [rbx + LabelTable.size]
pop r13
pop r12
pop rbx
ret
find_lbl:
push rbx
push r12
push r13
mov rbx, [rel labels]
call fasthash
mov r12d, eax ; store the hash in r12d
xor r13d, r13d
.loop:
cmp r13d, dword [rbx + LabelTable.size]
jge .not_found
mov rax, qword [rbx + LabelTable.labels]
lea rax, [rax + r13 * 8]
cmp dword [rax + Label.name_hash], r12d
je .found
inc r13d
jmp .loop
.found:
mov eax, dword [rax + Label.offset]
jmp .done
.not_found:
xor rax, rax
.done:
pop r13
pop r12
pop rbx
ret
global _entry_print_table global _entry_print_table
_entry_print_table: _entry_print_table:
call print_op_table call print_op_table

View file

@ -4,6 +4,10 @@ unsafe extern "C" {
unsafe fn try_parse_reg(_: *const u8) -> (usize, Register); unsafe fn try_parse_reg(_: *const u8) -> (usize, Register);
unsafe fn try_parse_mem(_: *const u8, _: *mut Operand) -> (usize, *const Operand); unsafe fn try_parse_mem(_: *const u8, _: *mut Operand) -> (usize, *const Operand);
#[link_name = "try_parse_operand"]
unsafe fn try_parse_operand_impl(_: *const u8, _: *mut Operand) -> (usize, *const Operand);
unsafe fn fasthash(_: *const u8, _: usize) -> u32;
#[link_name = "buf"] #[link_name = "buf"]
static mut BUF: [u8; 0x100]; static mut BUF: [u8; 0x100];
@ -16,10 +20,21 @@ struct Register {
num: u32, num: u32,
} }
#[repr(C)] #[repr(u32)]
#[derive(Debug, PartialEq, Eq)]
enum OperandKind {
None = 0,
Reg = 1,
Mem = 2,
Imm = 3,
MemLabel = 4,
ImmLabel = 5,
}
#[repr(C, align(8))]
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
struct Operand { struct Operand {
kind: u32, kind: OperandKind,
size: u32, size: u32,
reg: Register, reg: Register,
index: Register, index: Register,
@ -27,6 +42,64 @@ struct Operand {
disp: i32, disp: i32,
} }
impl Operand {
fn parse(bytes: &[u8]) -> Option<(usize, Self)> {
let mut op = core::mem::MaybeUninit::<Operand>::uninit();
let (n, ptr) = unsafe { try_parse_operand_impl(bytes.as_ptr(), op.as_mut_ptr()) };
if n == 0 {
None
} else {
Some((n, unsafe { op.assume_init() }))
}
}
fn label(name: &[u8]) -> Self {
let mut this = Self {
kind: OperandKind::ImmLabel,
size: 0,
reg: Register::invalid(),
index: Register::invalid(),
scale: 0,
disp: 0,
};
unsafe {
let hash = fasthash(name.as_ptr(), name.len());
(&raw mut this).byte_add(8).cast::<u32>().write(hash as u32);
}
this
}
fn imm(value: i64) -> Self {
let size = if value.abs() == 0 {
value as u32
} else {
unsafe {
(value.abs() as u64)
.checked_ilog2()
.unwrap_unchecked()
.saturating_sub(1)
.checked_ilog2()
.unwrap_or(0)
+ 1
}
};
let mut this = Self {
kind: OperandKind::Imm,
size,
reg: Register::invalid(),
index: Register::invalid(),
scale: 0,
disp: 0,
};
unsafe {
(&raw mut this).byte_add(8).cast::<i64>().write(value);
}
this
}
}
impl Register { impl Register {
fn new(num: u32, size: u32) -> Self { fn new(num: u32, size: u32) -> Self {
Self { Self {
@ -54,22 +127,22 @@ mod tests {
#[test] #[test]
fn parse_mem() { fn parse_mem() {
let mut op = MaybeUninit::<Operand>::uninit(); let mut op = MaybeUninit::<Operand>::uninit();
// let (n, _) = let (n, _) =
// unsafe { try_parse_mem(b"qword [rax + rbx * 2 + 0x10]\0".as_ptr(), op.as_mut_ptr()) }; unsafe { try_parse_mem(b"qword [rax + rbx * 2 + 0x10]\0".as_ptr(), op.as_mut_ptr()) };
// assert_eq!( assert_eq!(
// (n, unsafe { op.assume_init_ref() }), (n, unsafe { op.assume_init_ref() }),
// ( (
// 28, 28,
// &Operand { &Operand {
// kind: 2, kind: OperandKind::Mem,
// size: 3, size: 3,
// reg: Register::new(0, 8), reg: Register::new(0, 8),
// index: Register::new(3, 8), index: Register::new(3, 8),
// scale: 2, scale: 2,
// disp: 0x10, disp: 0x10,
// } }
// ) )
// ); );
let (n, _) = let (n, _) =
unsafe { try_parse_mem(b"byte [r12 + r13 * 4 - 0x20]\0".as_ptr(), op.as_mut_ptr()) }; unsafe { try_parse_mem(b"byte [r12 + r13 * 4 - 0x20]\0".as_ptr(), op.as_mut_ptr()) };
@ -78,7 +151,7 @@ mod tests {
( (
27, 27,
&Operand { &Operand {
kind: 2, kind: OperandKind::Mem,
size: 0, size: 0,
reg: Register::new(12, 8), reg: Register::new(12, 8),
index: Register::new(13, 8), index: Register::new(13, 8),
@ -94,7 +167,7 @@ mod tests {
( (
17, 17,
&Operand { &Operand {
kind: 2, kind: OperandKind::Mem,
size: 1, size: 1,
reg: Register::invalid(), reg: Register::invalid(),
index: Register::new(8, 8), index: Register::new(8, 8),
@ -106,81 +179,159 @@ mod tests {
} }
#[test] #[test]
#[ignore] fn parse_operand() {
let cases = [
(
&b"rax\0"[..],
Operand {
kind: OperandKind::Reg,
size: 3,
reg: Register::new(0, 8),
index: Register::invalid(),
scale: 0,
disp: 0,
},
),
(
&b"qword [rax + rbx * 2 + 0x10]\0"[..],
Operand {
kind: OperandKind::Mem,
size: 3,
reg: Register::new(0, 8),
index: Register::new(3, 8),
scale: 2,
disp: 0x10,
},
),
(
&b"byte [r12 + r13 * 4 - 0x20]\0"[..],
Operand {
kind: OperandKind::Mem,
size: 0,
reg: Register::new(12, 8),
index: Register::new(13, 8),
scale: 4,
disp: -0x20,
},
),
(
&b"word [r8 * 8 + 5]\0"[..],
Operand {
kind: OperandKind::Mem,
size: 1,
reg: Register::invalid(),
index: Register::new(8, 8),
scale: 8,
disp: 5,
},
),
(&b"0x12345678\0"[..], Operand::imm(0x12345678)),
(&b"-0x12345678\0"[..], Operand::imm(-0x12345678)),
(&b"0\0"[..], Operand::imm(0)),
(&b"-1\0"[..], Operand::imm(-1)),
(&b"1\0"[..], Operand::imm(1)),
(&b"255\0"[..], Operand::imm(255)),
(&b"-255\0"[..], Operand::imm(-255)),
(&b"'wazzaah\0"[..], Operand::label(b"wazzaah")),
(
&b"qword [rsp + 'hii]"[..],
Operand {
kind: OperandKind::MemLabel,
size: 3,
reg: Register::new(4, 8),
index: Register::invalid(),
scale: 0,
disp: unsafe { fasthash(b"hii".as_ptr(), 3) as i32 },
},
),
];
for (text, expected) in cases {
let (n, result) = Operand::parse(text).expect("Failed to parse operand");
assert_eq!(
result,
expected,
"Failed to parse '{}'",
std::str::from_utf8(text).unwrap()
);
}
}
#[test]
fn parse_reg() { fn parse_reg() {
let cases = [ let cases = [
(&b"rax"[..], (0, 8)), (&b"rax\0"[..], (0, 8)),
(&b"rcx"[..], (1, 8)), (&b"rcx\0"[..], (1, 8)),
(&b"rdx"[..], (2, 8)), (&b"rdx\0"[..], (2, 8)),
(&b"rbx"[..], (3, 8)), (&b"rbx\0"[..], (3, 8)),
(&b"rsp"[..], (4, 8)), (&b"rsp\0"[..], (4, 8)),
(&b"rbp"[..], (5, 8)), (&b"rbp\0"[..], (5, 8)),
(&b"rsi"[..], (6, 8)), (&b"rsi\0"[..], (6, 8)),
(&b"rdi"[..], (7, 8)), (&b"rdi\0"[..], (7, 8)),
(&b"r8"[..], (8, 8)), (&b"r8\0"[..], (8, 8)),
(&b"r9"[..], (9, 8)), (&b"r9\0"[..], (9, 8)),
(&b"r10"[..], (10, 8)), (&b"r10\0"[..], (10, 8)),
(&b"r11"[..], (11, 8)), (&b"r11\0"[..], (11, 8)),
(&b"r12"[..], (12, 8)), (&b"r12\0"[..], (12, 8)),
(&b"r13"[..], (13, 8)), (&b"r13\0"[..], (13, 8)),
(&b"r14"[..], (14, 8)), (&b"r14\0"[..], (14, 8)),
(&b"r15"[..], (15, 8)), (&b"r15\0"[..], (15, 8)),
(&b"rip"[..], (!0, 8)), (&b"rip\0"[..], (!0, 8)),
(&b"eax"[..], (0, 4)), (&b"eax\0"[..], (0, 4)),
(&b"ecx"[..], (1, 4)), (&b"ecx\0"[..], (1, 4)),
(&b"edx"[..], (2, 4)), (&b"edx\0"[..], (2, 4)),
(&b"ebx"[..], (3, 4)), (&b"ebx\0"[..], (3, 4)),
(&b"esp"[..], (4, 4)), (&b"esp\0"[..], (4, 4)),
(&b"ebp"[..], (5, 4)), (&b"ebp\0"[..], (5, 4)),
(&b"esi"[..], (6, 4)), (&b"esi\0"[..], (6, 4)),
(&b"edi"[..], (7, 4)), (&b"edi\0"[..], (7, 4)),
(&b"r8d"[..], (8, 4)), (&b"r8d\0"[..], (8, 4)),
(&b"r9d"[..], (9, 4)), (&b"r9d\0"[..], (9, 4)),
(&b"r10d"[..], (10, 4)), (&b"r10d\0"[..], (10, 4)),
(&b"r11d"[..], (11, 4)), (&b"r11d\0"[..], (11, 4)),
(&b"r12d"[..], (12, 4)), (&b"r12d\0"[..], (12, 4)),
(&b"r13d"[..], (13, 4)), (&b"r13d\0"[..], (13, 4)),
(&b"r14d"[..], (14, 4)), (&b"r14d\0"[..], (14, 4)),
(&b"r15d"[..], (15, 4)), (&b"r15d\0"[..], (15, 4)),
(&b"r8w"[..], (8, 2)), (&b"r8w\0"[..], (8, 2)),
(&b"r9w"[..], (9, 2)), (&b"r9w\0"[..], (9, 2)),
(&b"r10w"[..], (10, 2)), (&b"r10w\0"[..], (10, 2)),
(&b"r11w"[..], (11, 2)), (&b"r11w\0"[..], (11, 2)),
(&b"r12w"[..], (12, 2)), (&b"r12w\0"[..], (12, 2)),
(&b"r13w"[..], (13, 2)), (&b"r13w\0"[..], (13, 2)),
(&b"r14w"[..], (14, 2)), (&b"r14w\0"[..], (14, 2)),
(&b"r15w"[..], (15, 2)), (&b"r15w\0"[..], (15, 2)),
(&b"r8b"[..], (8, 1)), (&b"r8b\0"[..], (8, 1)),
(&b"r9b"[..], (9, 1)), (&b"r9b\0"[..], (9, 1)),
(&b"r10b"[..], (10, 1)), (&b"r10b\0"[..], (10, 1)),
(&b"r11b"[..], (11, 1)), (&b"r11b\0"[..], (11, 1)),
(&b"r12b"[..], (12, 1)), (&b"r12b\0"[..], (12, 1)),
(&b"r13b"[..], (13, 1)), (&b"r13b\0"[..], (13, 1)),
(&b"r14b"[..], (14, 1)), (&b"r14b\0"[..], (14, 1)),
(&b"r15b"[..], (15, 1)), (&b"r15b\0"[..], (15, 1)),
(&b"ax"[..], (0, 2)), (&b"ax\0"[..], (0, 2)),
(&b"cx"[..], (1, 2)), (&b"cx\0"[..], (1, 2)),
(&b"dx"[..], (2, 2)), (&b"dx\0"[..], (2, 2)),
(&b"bx"[..], (3, 2)), (&b"bx\0"[..], (3, 2)),
(&b"sp"[..], (4, 2)), (&b"sp\0"[..], (4, 2)),
(&b"bp"[..], (5, 2)), (&b"bp\0"[..], (5, 2)),
(&b"si"[..], (6, 2)), (&b"si\0"[..], (6, 2)),
(&b"di"[..], (7, 2)), (&b"di\0"[..], (7, 2)),
(&b"al"[..], (0, 1)), (&b"al\0"[..], (0, 1)),
(&b"cl"[..], (1, 1)), (&b"cl\0"[..], (1, 1)),
(&b"dl"[..], (2, 1)), (&b"dl\0"[..], (2, 1)),
(&b"bl"[..], (3, 1)), (&b"bl\0"[..], (3, 1)),
(&b"spl"[..], (4, 1)), (&b"spl\0"[..], (4, 1)),
(&b"bpl"[..], (5, 1)), (&b"bpl\0"[..], (5, 1)),
(&b"sil"[..], (6, 1)), (&b"sil\0"[..], (6, 1)),
(&b"dil"[..], (7, 1)), (&b"dil\0"[..], (7, 1)),
]; ];
for (text, (reg, size)) in cases { for (text, (reg, size)) in cases {
let (n, result) = unsafe { try_parse_reg(text.as_ptr()) }; let (n, result) = unsafe { try_parse_reg(text.as_ptr()) };
assert_eq!( assert_eq!(
result, (n, result),
Register::new(reg, size), (text.len() - 1, Register::new(reg, size)),
"Failed to parse '{}' n={n}", "Failed to parse '{}' n={n}",
std::str::from_utf8(text).unwrap() std::str::from_utf8(text).unwrap()
); );