diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/display.rs | 1076 | ||||
| -rw-r--r-- | src/lib.rs | 2 | ||||
| -rw-r--r-- | src/long_mode/display.rs | 285 | ||||
| -rw-r--r-- | src/long_mode/mod.rs | 46 | 
4 files changed, 98 insertions, 1311 deletions
| diff --git a/src/display.rs b/src/display.rs index 9b72cb3..e69de29 100644 --- a/src/display.rs +++ b/src/display.rs @@ -1,1076 +0,0 @@ -use core::fmt; - -use crate::safer_unchecked::unreachable_kinda_unchecked; - -extern crate alloc; - -// TODO: find a better place to put this.... -fn c_to_hex(c: u8) -> u8 { -    /* -    static CHARSET: &'static [u8; 16] = b"0123456789abcdef"; -    CHARSET[c as usize] -    */ -    // the conditional branch below is faster than a lookup, yes -    if c < 10 { -        b'0' + c -    } else { -        b'a' + c - 10 -    } -} - -pub enum TokenType { -    Mnemonic, -    Operand, -    Immediate, -    Register, -    Offset, -} - -/// `DisplaySink` allows client code to collect output and minimal markup. this is currently used -/// in formatting instructions for two reasons: -/// * `DisplaySink` implementations have the opportunity to collect starts and ends of tokens at -///   the same time as collecting output itself. -/// * `DisplaySink` implementations provides specialized functions for writing strings in -///   circumstances where a simple "use `core::fmt`" might incur unwanted overhead. -/// -/// spans are reported through `span_start` and `span_exit` to avoid constraining implementations -/// into tracking current output offset (which may not be knowable) or span size (which may be -/// knowable, but incur additional overhead to compute or track). -/// -/// spans are entered and exited in a FILO manner: a function writing to some `DisplaySink` must -/// exit spans in reverse order to when they are entered. a function sequence like -/// `sink.span_start(Operand); sink.span_start(Immediate); sink.span_exit(Operand)` is in error. -/// -/// the `write_*` helpers on `DisplaySink` may be able to take advantage of contraints described in -/// documentation here to better support writing some kinds of inputs than a fully-general solution -/// (such as `core::fmt`) might be able to yield. -/// -/// currently there are two motivating factors for `write_*` helpers: -/// -/// instruction formatting often involves writing small but variable-size strings, such as register -/// names, which is something of a pathological case for string appending as Rust currently exists: -/// this often becomes `memcpy` and specifically a call to the platform's `memcpy` (rather than an -/// inlined `rep movsb`) just to move 3-5 bytes. one relevant Rust issue for reference: -/// https://github.com/rust-lang/rust/issues/92993#issuecomment-2028915232 -/// -/// there are similar papercuts around formatting integers as base-16 numbers, such as -/// https://github.com/rust-lang/rust/pull/122770 . in isolation and in most applications these are -/// not a significant source of overhead. but for programs bounded on decoding and printing -/// instructions, these can add up to significant overhead - on the order of 10-20% of total -/// runtime. -/// -/// `DisplaySink` -pub trait DisplaySink: fmt::Write { -    #[inline(always)] -    fn write_fixed_size(&mut self, s: &str) -> Result<(), core::fmt::Error> { -        self.write_str(s) -    } - -    /// write a string to this sink that is less than 32 bytes. this is provided for optimization -    /// opportunities when writing a variable-length string with known max size. -    /// -    /// SAFETY: the provided `s` must be less than 32 bytes. if the provided string is longer than -    /// 31 bytes, implementations may only copy part of a multi-byte codepoint while writing to a -    /// utf-8 string. this may corrupt Rust strings. -    unsafe fn write_lt_32(&mut self, s: &str) -> Result<(), core::fmt::Error> { -        self.write_str(s) -    } -    /// write a string to this sink that is less than 16 bytes. this is provided for optimization -    /// opportunities when writing a variable-length string with known max size. -    /// -    /// SAFETY: the provided `s` must be less than 16 bytes. if the provided string is longer than -    /// 15 bytes, implementations may only copy part of a multi-byte codepoint while writing to a -    /// utf-8 string. this may corrupt Rust strings. -    unsafe fn write_lt_16(&mut self, s: &str) -> Result<(), core::fmt::Error> { -        self.write_str(s) -    } -    /// write a string to this sink that is less than 8 bytes. this is provided for optimization -    /// opportunities when writing a variable-length string with known max size. -    /// -    /// SAFETY: the provided `s` must be less than 8 bytes. if the provided string is longer than -    /// 7 bytes, implementations may only copy part of a multi-byte codepoint while writing to a -    /// utf-8 string. this may corrupt Rust strings. -    unsafe fn write_lt_8(&mut self, s: &str) -> Result<(), core::fmt::Error> { -        self.write_str(s) -    } - -    /// write a u8 to the output as a base-16 integer. -    /// -    /// this is provided for optimization opportunities when the formatted integer can be written -    /// directly to the sink (rather than formatted to an intermediate buffer and output as a -    /// followup step) -    fn write_u8(&mut self, v: u8) -> Result<(), core::fmt::Error> { -        write!(self, "{:x}", v) -    } -    /// write a u16 to the output as a base-16 integer. -    /// -    /// this is provided for optimization opportunities when the formatted integer can be written -    /// directly to the sink (rather than formatted to an intermediate buffer and output as a -    /// followup step) -    fn write_u16(&mut self, v: u16) -> Result<(), core::fmt::Error> { -        write!(self, "{:x}", v) -    } -    /// write a u32 to the output as a base-16 integer. -    /// -    /// this is provided for optimization opportunities when the formatted integer can be written -    /// directly to the sink (rather than formatted to an intermediate buffer and output as a -    /// followup step) -    fn write_u32(&mut self, v: u32) -> Result<(), core::fmt::Error> { -        write!(self, "{:x}", v) -    } -    /// write a u64 to the output as a base-16 integer. -    /// -    /// this is provided for optimization opportunities when the formatted integer can be written -    /// directly to the sink (rather than formatted to an intermediate buffer and output as a -    /// followup step) -    fn write_u64(&mut self, v: u64) -> Result<(), core::fmt::Error> { -        write!(self, "{:x}", v) -    } -    /// enter a region inside which output corresponds to a `ty`. -    /// -    /// the default implementation of these functions is as a no-op. this way, providing span -    /// information to a `DisplaySink` that does not want it is eliminated at compile time. -    /// -    /// spans are entered and ended in a FILO manner: a function writing to some `DisplaySink` must -    /// end spans in reverse order to when they are entered. a function sequence like -    /// `sink.span_start(Operand); sink.span_start(Immediate); sink.span_end(Operand)` is in error. -    /// -    /// a simple use of `span_start`/`span_end` might look something like: -    /// ```compile_fail -    /// sink.span_start(Operand) -    /// sink.write_char('[') -    /// sink.span_start(Register) -    /// sink.write_fixed_size("rbp") -    /// sink.span_end(Register) -    /// sink.write_char(']') -    /// sink.span_end(Operand) -    /// ``` -    /// which writes the text `[rbp]`, with span indicators where the operand (`[ ... ]`) begins, -    /// as well as the start and end of a register name. -    fn span_start(&mut self, _ty: TokenType) { } -    /// end a region where a `ty` was written. see docs on [`DisplaySink::span_start`] for more. -    fn span_end(&mut self, _ty: TokenType) { } -} - -pub struct NoColorsSink<'a, T: fmt::Write> { -    pub out: &'a mut T, -} - -impl<'a, T: fmt::Write> DisplaySink for NoColorsSink<'a, T> { -    fn span_start(&mut self, _ty: TokenType) { } -    fn span_end(&mut self, _ty: TokenType) { } -} - -impl<'a, T: fmt::Write> fmt::Write for NoColorsSink<'a, T> { -    fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { -        self.out.write_str(s) -    } -    fn write_char(&mut self, c: char) -> Result<(), core::fmt::Error> { -        self.out.write_char(c) -    } -    fn write_fmt(&mut self, f: fmt::Arguments) -> Result<(), core::fmt::Error> { -        self.out.write_fmt(f) -    } -} - -/// this is an implementation detail of yaxpeax-arch and related crates. if you are a user of the -/// disassemblers, do not use this struct. do not depend on this struct existing. this struct is -/// not stable. this struct is not safe for general use. if you use this struct you and your -/// program will be eaten by gremlins. -/// -/// if you are implementing an instruction formatter for the yaxpeax family of crates: this struct -/// is guaranteed to contain a string that is long enough to hold a fully-formatted instruction. -/// because the buffer is guaranteed to be long enough, writes through `InstructionTextSink` are -/// not bounds-checked, and the buffer is never grown. -/// -/// this is wildly dangerous in general use. the public constructor of `InstructionTextSink` is -/// unsafe as a result. as used in `InstructionFormatter`, the buffer is guaranteed to be -/// `clear()`ed before use, `InstructionFormatter` ensures the buffer is large enough, *and* -/// `InstructionFormatter` never allows `InstructionTextSink` to exist in a context where it would -/// be written to without being rewound first. -/// -/// because this opens a very large hole through which `fmt::Write` can become unsafe, incorrect -/// uses of this struct will be hard to debug in general. `InstructionFormatter` is probably at the -/// limit of easily-reasoned-about lifecycle of the buffer, which "only" leaves the problem of -/// ensuring that instruction formatting impls this buffer is passed to are appropriately sized. -/// -/// this is intended to be hidden in docs. if you see this in docs, it's a bug. -#[doc(hidden)] -pub(crate) struct InstructionTextSink<'buf> { -    buf: &'buf mut alloc::string::String -} - -impl<'buf> InstructionTextSink<'buf> { -    pub unsafe fn new(buf: &'buf mut alloc::string::String) -> Self { -        Self { buf } -    } -} - -impl<'buf> fmt::Write for InstructionTextSink<'buf> { -    fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { -        self.buf.write_str(s) -    } -    fn write_char(&mut self, c: char) -> Result<(), core::fmt::Error> { -        if cfg!(debug_assertions) { -            if self.buf.capacity() < self.buf.len() + 1 { -                panic!("InstructionTextSink::write_char would overflow output"); -            } -        } -        // SAFETY: `buf` is assumed to be long enough to hold all input, `buf` at `underlying.len()` -        // is valid for writing, but may be uninitialized. -        // -        // this function is essentially equivalent to `Vec::push` specialized for the case that -        // `len < buf.capacity()`: -        // https://github.com/rust-lang/rust/blob/be9e27e/library/alloc/src/vec/mod.rs#L1993-L2006 -        unsafe { -            let underlying = self.buf.as_mut_vec(); -            // `InstructionTextSink::write_char` is only used by yaxpeax-x86, and is only used to -            // write single ASCII characters. this is wrong in the general case, but `write_char` -            // here is not going to be used in the general case. -            if cfg!(debug_asertions) { -                panic!("InstructionTextSink::write_char would truncate output"); -            } -            let to_push = c as u8; -            // `ptr::write` here because `underlying.add(underlying.len())` may not point to an -            // initialized value, which would mean that turning that pointer into a `&mut u8` to -            // store through would be UB. `ptr::write` avoids taking the mut ref. -            underlying.as_mut_ptr().offset(underlying.len() as isize).write(to_push); -            // we have initialized all (one) bytes that `set_len` is increasing the length to -            // include. -            underlying.set_len(underlying.len() + 1); -        } -        Ok(()) -    } -} - -/// this DisplaySink impl exists to support somewhat more performant buffering of the kinds of -/// strings `yaxpeax-x86` uses in formatting instructions. -impl DisplaySink for alloc::string::String { -    #[inline(always)] -    fn write_fixed_size(&mut self, s: &str) -> Result<(), core::fmt::Error> { -        self.reserve(s.len()); -        let buf = unsafe { self.as_mut_vec() }; -        let new_bytes = s.as_bytes(); - -        if new_bytes.len() == 0 { -            unsafe { unreachable_kinda_unchecked() } -        } - -        if new_bytes.len() >= 16 { -            unsafe { unreachable_kinda_unchecked() } -        } - -        unsafe { -            let dest = buf.as_mut_ptr().offset(buf.len() as isize); - -            // this used to be enough to bamboozle llvm away from -            // https://github.com/rust-lang/rust/issues/92993#issuecomment-2028915232 -            // if `s` is not fixed size. somewhere between Rust 1.68 and Rust 1.74 this stopped -            // being sufficient, so `write_fixed_size` truly should only be used for fixed size `s` -            // (otherwise this is a libc memcpy call in disguise). for fixed-size strings this -            // unrolls into some kind of appropriate series of `mov`. -            dest.offset(0 as isize).write(new_bytes[0]); -            for i in 1..new_bytes.len() { -                dest.offset(i as isize).write(new_bytes[i]); -            } - -            buf.set_len(buf.len() + new_bytes.len()); -        } - -        Ok(()) -    } -    unsafe fn write_lt_32(&mut self, s: &str) -> Result<(), fmt::Error> { -        self.reserve(s.len()); - -        // SAFETY: todo -        let buf = unsafe { self.as_mut_vec() }; -        let new_bytes = s.as_bytes(); - -        // should get DCE -        if new_bytes.len() >= 32 { -            unsafe { core::hint::unreachable_unchecked() } -        } - -        unsafe { -            let dest = buf.as_mut_ptr().offset(buf.len() as isize); -            let src = new_bytes.as_ptr(); - -            let rem = new_bytes.len() as isize; - -            // set_len early because there is no way to avoid the following asm!() writing that -            // same number of bytes into buf -            buf.set_len(buf.len() + new_bytes.len()); - -            core::arch::asm!( -                "6:", -                "cmp {rem:e}, 16", -                "jb 7f", -                "mov {buf:r}, qword ptr [{src} + {rem} - 16]", -                "mov qword ptr [{dest} + {rem} - 16], {buf:r}", -                "mov {buf:r}, qword ptr [{src} + {rem} - 8]", -                "mov qword ptr [{dest} + {rem} - 8], {buf:r}", -                "sub {rem:e}, 16", -                "jz 11f", -                "7:", -                "cmp {rem:e}, 8", -                "jb 8f", -                "mov {buf:r}, qword ptr [{src} + {rem} - 8]", -                "mov qword ptr [{dest} + {rem} - 8], {buf:r}", -                "sub {rem:e}, 8", -                "jz 11f", -                "8:", -                "cmp {rem:e}, 4", -                "jb 9f", -                "mov {buf:e}, dword ptr [{src} + {rem} - 4]", -                "mov dword ptr [{dest} + {rem} - 4], {buf:e}", -                "sub {rem:e}, 4", -                "jz 11f", -                "9:", -                "cmp {rem:e}, 2", -                "jb 10f", -                "mov {buf:x}, word ptr [{src} + {rem} - 2]", -                "mov word ptr [{dest} + {rem} - 2], {buf:x}", -                "sub {rem:e}, 2", -                "jz 11f", -                "10:", -                "cmp {rem:e}, 1", -                "jb 11f", -                "mov {buf:l}, byte ptr [{src} + {rem} - 1]", -                "mov byte ptr [{dest} + {rem} - 1], {buf:l}", -                "11:", -                src = in(reg) src, -                dest = in(reg) dest, -                rem = inout(reg) rem => _, -                buf = out(reg) _, -                options(nostack), -            ); -        } -        /* -        for i in 0..new_bytes.len() { -            unsafe { -                buf.as_mut_ptr().offset(buf.len() as isize).offset(i as isize).write_volatile(new_bytes[i]); -            } -        } -        */ - -        Ok(()) -    } -    unsafe fn write_lt_16(&mut self, s: &str) -> Result<(), fmt::Error> { -        self.reserve(s.len()); - -        // SAFETY: todo -        let buf = unsafe { self.as_mut_vec() }; -        let new_bytes = s.as_bytes(); - -        // should get DCE -        if new_bytes.len() >= 16 { -            unsafe { core::hint::unreachable_unchecked() } -        } - -        unsafe { -            let dest = buf.as_mut_ptr().offset(buf.len() as isize); -            let src = new_bytes.as_ptr(); - -            let rem = new_bytes.len() as isize; - -            // set_len early because there is no way to avoid the following asm!() writing that -            // same number of bytes into buf -            buf.set_len(buf.len() + new_bytes.len()); - -            core::arch::asm!( -                "7:", -                "cmp {rem:e}, 8", -                "jb 8f", -                "mov {buf:r}, qword ptr [{src} + {rem} - 8]", -                "mov qword ptr [{dest} + {rem} - 8], {buf:r}", -                "sub {rem:e}, 8", -                "jz 11f", -                "8:", -                "cmp {rem:e}, 4", -                "jb 9f", -                "mov {buf:e}, dword ptr [{src} + {rem} - 4]", -                "mov dword ptr [{dest} + {rem} - 4], {buf:e}", -                "sub {rem:e}, 4", -                "jz 11f", -                "9:", -                "cmp {rem:e}, 2", -                "jb 10f", -                "mov {buf:x}, word ptr [{src} + {rem} - 2]", -                "mov word ptr [{dest} + {rem} - 2], {buf:x}", -                "sub {rem:e}, 2", -                "jz 11f", -                "10:", -                "cmp {rem:e}, 1", -                "jb 11f", -                "mov {buf:l}, byte ptr [{src} + {rem} - 1]", -                "mov byte ptr [{dest} + {rem} - 1], {buf:l}", -                "11:", -                src = in(reg) src, -                dest = in(reg) dest, -                rem = inout(reg) rem => _, -                buf = out(reg) _, -                options(nostack), -            ); -        } -        /* -        for i in 0..new_bytes.len() { -            unsafe { -                buf.as_mut_ptr().offset(buf.len() as isize).offset(i as isize).write_volatile(new_bytes[i]); -            } -        } -        */ - -        Ok(()) -    } -    unsafe fn write_lt_8(&mut self, s: &str) -> Result<(), fmt::Error> { -        self.reserve(s.len()); - -        // SAFETY: todo -        let buf = unsafe { self.as_mut_vec() }; -        let new_bytes = s.as_bytes(); - -        // should get DCE -        if new_bytes.len() >= 8 { -            unsafe { core::hint::unreachable_unchecked() } -        } - -        unsafe { -            let dest = buf.as_mut_ptr().offset(buf.len() as isize); -            let src = new_bytes.as_ptr(); - -            let rem = new_bytes.len() as isize; - -            // set_len early because there is no way to avoid the following asm!() writing that -            // same number of bytes into buf -            buf.set_len(buf.len() + new_bytes.len()); - -            core::arch::asm!( -                "8:", -                "cmp {rem:e}, 4", -                "jb 9f", -                "mov {buf:e}, dword ptr [{src} + {rem} - 4]", -                "mov dword ptr [{dest} + {rem} - 4], {buf:e}", -                "sub {rem:e}, 4", -                "jz 11f", -                "9:", -                "cmp {rem:e}, 2", -                "jb 10f", -                "mov {buf:x}, word ptr [{src} + {rem} - 2]", -                "mov word ptr [{dest} + {rem} - 2], {buf:x}", -                "sub {rem:e}, 2", -                "jz 11f", -                "10:", -                "cmp {rem:e}, 1", -                "jb 11f", -                "mov {buf:l}, byte ptr [{src} + {rem} - 1]", -                "mov byte ptr [{dest} + {rem} - 1], {buf:l}", -                "11:", -                src = in(reg) src, -                dest = in(reg) dest, -                rem = inout(reg) rem => _, -                buf = out(reg) _, -                options(nostack), -            ); -        } -        /* -        for i in 0..new_bytes.len() { -            unsafe { -                buf.as_mut_ptr().offset(buf.len() as isize).offset(i as isize).write_volatile(new_bytes[i]); -            } -        } -        */ - -        Ok(()) -    } -    /// write a u8 to the output as a base-16 integer. -    /// -    /// this is provided for optimization opportunities when the formatted integer can be written -    /// directly to the sink (rather than formatted to an intermediate buffer and output as a -    /// followup step) -    #[inline(always)] -    fn write_u8(&mut self, mut v: u8) -> Result<(), core::fmt::Error> { -        if v == 0 { -            return self.write_fixed_size("0"); -        } -        // we can fairly easily predict the size of a formatted string here with lzcnt, which also -        // means we can write directly into the correct offsets of the output string. -        let printed_size = ((8 - v.leading_zeros() + 3) >> 2) as usize; - -        self.reserve(printed_size); - -        let buf = unsafe { self.as_mut_vec() }; -        let new_len = buf.len() + printed_size; - -        unsafe { -            buf.set_len(new_len); -        } -        let mut p = unsafe { buf.as_mut_ptr().offset(new_len as isize) }; - -        loop { -            let digit = v % 16; -            let c = c_to_hex(digit as u8); -            unsafe { -                p = p.offset(-1); -                p.write(c); -            } -            v = v / 16; -            if v == 0 { -                break; -            } -        } - -        Ok(()) -    } -    /// write a u16 to the output as a base-16 integer. -    /// -    /// this is provided for optimization opportunities when the formatted integer can be written -    /// directly to the sink (rather than formatted to an intermediate buffer and output as a -    /// followup step) -    #[inline(always)] -    fn write_u16(&mut self, mut v: u16) -> Result<(), core::fmt::Error> { -        if v == 0 { -            return self.write_fixed_size("0"); -        } -        // we can fairly easily predict the size of a formatted string here with lzcnt, which also -        // means we can write directly into the correct offsets of the output string. -        let printed_size = ((16 - v.leading_zeros() + 3) >> 2) as usize; - -        self.reserve(printed_size); - -        let buf = unsafe { self.as_mut_vec() }; -        let new_len = buf.len() + printed_size; - -        unsafe { -            buf.set_len(new_len); -        } -        let mut p = unsafe { buf.as_mut_ptr().offset(new_len as isize) }; - -        loop { -            let digit = v % 16; -            let c = c_to_hex(digit as u8); -            unsafe { -                p = p.offset(-1); -                p.write(c); -            } -            v = v / 16; -            if v == 0 { -                break; -            } -        } - -        Ok(()) -    } -    /// write a u32 to the output as a base-16 integer. -    /// -    /// this is provided for optimization opportunities when the formatted integer can be written -    /// directly to the sink (rather than formatted to an intermediate buffer and output as a -    /// followup step) -    #[inline(always)] -    fn write_u32(&mut self, mut v: u32) -> Result<(), core::fmt::Error> { -        if v == 0 { -            return self.write_fixed_size("0"); -        } -        // we can fairly easily predict the size of a formatted string here with lzcnt, which also -        // means we can write directly into the correct offsets of the output string. -        let printed_size = ((32 - v.leading_zeros() + 3) >> 2) as usize; - -        self.reserve(printed_size); - -        let buf = unsafe { self.as_mut_vec() }; -        let new_len = buf.len() + printed_size; - -        unsafe { -            buf.set_len(new_len); -        } -        let mut p = unsafe { buf.as_mut_ptr().offset(new_len as isize) }; - -        loop { -            let digit = v % 16; -            let c = c_to_hex(digit as u8); -            unsafe { -                p = p.offset(-1); -                p.write(c); -            } -            v = v / 16; -            if v == 0 { -                break; -            } -        } - -        Ok(()) -    } -    /// write a u64 to the output as a base-16 integer. -    /// -    /// this is provided for optimization opportunities when the formatted integer can be written -    /// directly to the sink (rather than formatted to an intermediate buffer and output as a -    /// followup step) -    #[inline(always)] -    fn write_u64(&mut self, mut v: u64) -> Result<(), core::fmt::Error> { -        if v == 0 { -            return self.write_fixed_size("0"); -        } -        // we can fairly easily predict the size of a formatted string here with lzcnt, which also -        // means we can write directly into the correct offsets of the output string. -        let printed_size = ((64 - v.leading_zeros() + 3) >> 2) as usize; - -        self.reserve(printed_size); - -        let buf = unsafe { self.as_mut_vec() }; -        let new_len = buf.len() + printed_size; - -        unsafe { -            buf.set_len(new_len); -        } -        let mut p = unsafe { buf.as_mut_ptr().offset(new_len as isize) }; - -        loop { -            let digit = v % 16; -            let c = c_to_hex(digit as u8); -            unsafe { -                p = p.offset(-1); -                p.write(c); -            } -            v = v / 16; -            if v == 0 { -                break; -            } -        } - -        Ok(()) -    } -    fn span_start(&mut self, _ty: TokenType) {} -    fn span_end(&mut self, _ty: TokenType) {} -} - -impl<'buf> DisplaySink for InstructionTextSink<'buf> { -    #[inline(always)] -    fn write_fixed_size(&mut self, s: &str) -> Result<(), core::fmt::Error> { -        if cfg!(debug_assertions) { -            if self.buf.capacity() < self.buf.len() + s.len() { -                panic!("InstructionTextSink::write_fixed_size would overflow output"); -            } -        } - -        let buf = unsafe { self.buf.as_mut_vec() }; -        let new_bytes = s.as_bytes(); - -        if new_bytes.len() == 0 { -            return Ok(()); -        } - -        if new_bytes.len() >= 16 { -            unsafe { unreachable_kinda_unchecked() } -        } - -        unsafe { -            let dest = buf.as_mut_ptr().offset(buf.len() as isize); - -            // this used to be enough to bamboozle llvm away from -            // https://github.com/rust-lang/rust/issues/92993#issuecomment-2028915232https://github.com/rust-lang/rust/issues/92993#issuecomment-2028915232 -            // if `s` is not fixed size. somewhere between Rust 1.68 and Rust 1.74 this stopped -            // being sufficient, so `write_fixed_size` truly should only be used for fixed size `s` -            // (otherwise this is a libc memcpy call in disguise). for fixed-size strings this -            // unrolls into some kind of appropriate series of `mov`. -            dest.offset(0 as isize).write(new_bytes[0]); -            for i in 1..new_bytes.len() { -                dest.offset(i as isize).write(new_bytes[i]); -            } - -            buf.set_len(buf.len() + new_bytes.len()); -        } - -        Ok(()) -    } -    unsafe fn write_lt_32(&mut self, s: &str) -> Result<(), fmt::Error> { -        if cfg!(debug_assertions) { -            if self.buf.capacity() < self.buf.len() + s.len() { -                panic!("InstructionTextSink::write_lt_32 would overflow output"); -            } -        } - -        // SAFETY: todo -        let buf = unsafe { self.buf.as_mut_vec() }; -        let new_bytes = s.as_bytes(); - -        // should get DCE -        if new_bytes.len() >= 32 { -            unsafe { core::hint::unreachable_unchecked() } -        } - -        unsafe { -            let dest = buf.as_mut_ptr().offset(buf.len() as isize); -            let src = new_bytes.as_ptr(); - -            let rem = new_bytes.len() as isize; - -            // set_len early because there is no way to avoid the following asm!() writing that -            // same number of bytes into buf -            buf.set_len(buf.len() + new_bytes.len()); - -            core::arch::asm!( -                "6:", -                "cmp {rem:e}, 16", -                "jb 7f", -                "mov {buf:r}, qword ptr [{src} + {rem} - 16]", -                "mov qword ptr [{dest} + {rem} - 16], {buf:r}", -                "mov {buf:r}, qword ptr [{src} + {rem} - 8]", -                "mov qword ptr [{dest} + {rem} - 8], {buf:r}", -                "sub {rem:e}, 16", -                "jz 11f", -                "7:", -                "cmp {rem:e}, 8", -                "jb 8f", -                "mov {buf:r}, qword ptr [{src} + {rem} - 8]", -                "mov qword ptr [{dest} + {rem} - 8], {buf:r}", -                "sub {rem:e}, 8", -                "jz 11f", -                "8:", -                "cmp {rem:e}, 4", -                "jb 9f", -                "mov {buf:e}, dword ptr [{src} + {rem} - 4]", -                "mov dword ptr [{dest} + {rem} - 4], {buf:e}", -                "sub {rem:e}, 4", -                "jz 11f", -                "9:", -                "cmp {rem:e}, 2", -                "jb 10f", -                "mov {buf:x}, word ptr [{src} + {rem} - 2]", -                "mov word ptr [{dest} + {rem} - 2], {buf:x}", -                "sub {rem:e}, 2", -                "jz 11f", -                "10:", -                "cmp {rem:e}, 1", -                "jb 11f", -                "mov {buf:l}, byte ptr [{src} + {rem} - 1]", -                "mov byte ptr [{dest} + {rem} - 1], {buf:l}", -                "11:", -                src = in(reg) src, -                dest = in(reg) dest, -                rem = inout(reg) rem => _, -                buf = out(reg) _, -                options(nostack), -            ); -        } -        /* -        for i in 0..new_bytes.len() { -            unsafe { -                buf.as_mut_ptr().offset(buf.len() as isize).offset(i as isize).write_volatile(new_bytes[i]); -            } -        } -        */ - -        Ok(()) -    } -    unsafe fn write_lt_16(&mut self, s: &str) -> Result<(), fmt::Error> { -        if cfg!(debug_assertions) { -            if self.buf.capacity() < self.buf.len() + s.len() { -                panic!("InstructionTextSink::write_lt_16 would overflow output"); -            } -        } - -        // SAFETY: todo -        let buf = unsafe { self.buf.as_mut_vec() }; -        let new_bytes = s.as_bytes(); - -        // should get DCE -        if new_bytes.len() >= 16 { -            unsafe { core::hint::unreachable_unchecked() } -        } - -        unsafe { -            let dest = buf.as_mut_ptr().offset(buf.len() as isize); -            let src = new_bytes.as_ptr(); - -            let rem = new_bytes.len() as isize; - -            // set_len early because there is no way to avoid the following asm!() writing that -            // same number of bytes into buf -            buf.set_len(buf.len() + new_bytes.len()); - -            core::arch::asm!( -                "7:", -                "cmp {rem:e}, 8", -                "jb 8f", -                "mov {buf:r}, qword ptr [{src} + {rem} - 8]", -                "mov qword ptr [{dest} + {rem} - 8], {buf:r}", -                "sub {rem:e}, 8", -                "jz 11f", -                "8:", -                "cmp {rem:e}, 4", -                "jb 9f", -                "mov {buf:e}, dword ptr [{src} + {rem} - 4]", -                "mov dword ptr [{dest} + {rem} - 4], {buf:e}", -                "sub {rem:e}, 4", -                "jz 11f", -                "9:", -                "cmp {rem:e}, 2", -                "jb 10f", -                "mov {buf:x}, word ptr [{src} + {rem} - 2]", -                "mov word ptr [{dest} + {rem} - 2], {buf:x}", -                "sub {rem:e}, 2", -                "jz 11f", -                "10:", -                "cmp {rem:e}, 1", -                "jb 11f", -                "mov {buf:l}, byte ptr [{src} + {rem} - 1]", -                "mov byte ptr [{dest} + {rem} - 1], {buf:l}", -                "11:", -                src = in(reg) src, -                dest = in(reg) dest, -                rem = inout(reg) rem => _, -                buf = out(reg) _, -                options(nostack), -            ); -        } -        /* -        for i in 0..new_bytes.len() { -            unsafe { -                buf.as_mut_ptr().offset(buf.len() as isize).offset(i as isize).write_volatile(new_bytes[i]); -            } -        } -        */ - -        Ok(()) -    } -    unsafe fn write_lt_8(&mut self, s: &str) -> Result<(), fmt::Error> { -        if cfg!(debug_assertions) { -            if self.buf.capacity() < self.buf.len() + s.len() { -                panic!("InstructionTextSink::write_lt_8 would overflow output"); -            } -        } - -        // SAFETY: todo -        let buf = unsafe { self.buf.as_mut_vec() }; -        let new_bytes = s.as_bytes(); - -        // should get DCE -        if new_bytes.len() >= 8 { -            unsafe { core::hint::unreachable_unchecked() } -        } - -        unsafe { -            let dest = buf.as_mut_ptr().offset(buf.len() as isize); -            let src = new_bytes.as_ptr(); - -            let rem = new_bytes.len() as isize; - -            // set_len early because there is no way to avoid the following asm!() writing that -            // same number of bytes into buf -            buf.set_len(buf.len() + new_bytes.len()); - -            core::arch::asm!( -                "8:", -                "cmp {rem:e}, 4", -                "jb 9f", -                "mov {buf:e}, dword ptr [{src} + {rem} - 4]", -                "mov dword ptr [{dest} + {rem} - 4], {buf:e}", -                "sub {rem:e}, 4", -                "jz 11f", -                "9:", -                "cmp {rem:e}, 2", -                "jb 10f", -                "mov {buf:x}, word ptr [{src} + {rem} - 2]", -                "mov word ptr [{dest} + {rem} - 2], {buf:x}", -                "sub {rem:e}, 2", -                "jz 11f", -                "10:", -                "cmp {rem:e}, 1", -                "jb 11f", -                "mov {buf:l}, byte ptr [{src} + {rem} - 1]", -                "mov byte ptr [{dest} + {rem} - 1], {buf:l}", -                "11:", -                src = in(reg) src, -                dest = in(reg) dest, -                rem = inout(reg) rem => _, -                buf = out(reg) _, -                options(nostack), -            ); -        } -        /* -        for i in 0..new_bytes.len() { -            unsafe { -                buf.as_mut_ptr().offset(buf.len() as isize).offset(i as isize).write_volatile(new_bytes[i]); -            } -        } -        */ - -        Ok(()) -    } -    /// write a u8 to the output as a base-16 integer. -    /// -    /// this is provided for optimization opportunities when the formatted integer can be written -    /// directly to the sink (rather than formatted to an intermediate buffer and output as a -    /// followup step) -    #[inline(always)] -    fn write_u8(&mut self, mut v: u8) -> Result<(), core::fmt::Error> { -        if v == 0 { -            return self.write_fixed_size("0"); -        } -        // we can fairly easily predict the size of a formatted string here with lzcnt, which also -        // means we can write directly into the correct offsets of the output string. -        let printed_size = ((8 - v.leading_zeros() + 3) >> 2) as usize; - -        if cfg!(debug_assertions) { -            if self.buf.capacity() < self.buf.len() + printed_size { -                panic!("InstructionTextSink::write_u8 would overflow output"); -            } -        } - -        let buf = unsafe { self.buf.as_mut_vec() }; -        let new_len = buf.len() + printed_size; - -        unsafe { -            buf.set_len(new_len); -        } -        let mut p = unsafe { buf.as_mut_ptr().offset(new_len as isize) }; - -        loop { -            let digit = v % 16; -            let c = c_to_hex(digit as u8); -            unsafe { -                p = p.offset(-1); -                p.write(c); -            } -            v = v / 16; -            if v == 0 { -                break; -            } -        } - -        Ok(()) -    } -    /// write a u16 to the output as a base-16 integer. -    /// -    /// this is provided for optimization opportunities when the formatted integer can be written -    /// directly to the sink (rather than formatted to an intermediate buffer and output as a -    /// followup step) -    #[inline(always)] -    fn write_u16(&mut self, mut v: u16) -> Result<(), core::fmt::Error> { -        if v == 0 { -            return self.write_fixed_size("0"); -        } - -        // we can fairly easily predict the size of a formatted string here with lzcnt, which also -        // means we can write directly into the correct offsets of the output string. -        let printed_size = ((16 - v.leading_zeros() + 3) >> 2) as usize; - -        if cfg!(debug_assertions) { -            if self.buf.capacity() < self.buf.len() + printed_size { -                panic!("InstructionTextSink::write_u16 would overflow output"); -            } -        } - -        let buf = unsafe { self.buf.as_mut_vec() }; -        let new_len = buf.len() + printed_size; - -        unsafe { -            buf.set_len(new_len); -        } -        let mut p = unsafe { buf.as_mut_ptr().offset(new_len as isize) }; - -        loop { -            let digit = v % 16; -            let c = c_to_hex(digit as u8); -            unsafe { -                p = p.offset(-1); -                p.write(c); -            } -            v = v / 16; -            if v == 0 { -                break; -            } -        } - -        Ok(()) -    } -    /// write a u32 to the output as a base-16 integer. -    /// -    /// this is provided for optimization opportunities when the formatted integer can be written -    /// directly to the sink (rather than formatted to an intermediate buffer and output as a -    /// followup step) -    #[inline(always)] -    fn write_u32(&mut self, mut v: u32) -> Result<(), core::fmt::Error> { -        if v == 0 { -            return self.write_fixed_size("0"); -        } - -        // we can fairly easily predict the size of a formatted string here with lzcnt, which also -        // means we can write directly into the correct offsets of the output string. -        let printed_size = ((32 - v.leading_zeros() + 3) >> 2) as usize; - -        if cfg!(debug_assertions) { -            if self.buf.capacity() < self.buf.len() + printed_size { -                panic!("InstructionTextSink::write_u32 would overflow output"); -            } -        } - -        let buf = unsafe { self.buf.as_mut_vec() }; -        let new_len = buf.len() + printed_size; - -        unsafe { -            buf.set_len(new_len); -        } -        let mut p = unsafe { buf.as_mut_ptr().offset(new_len as isize) }; - -        loop { -            let digit = v % 16; -            let c = c_to_hex(digit as u8); -            unsafe { -                p = p.offset(-1); -                p.write(c); -            } -            v = v / 16; -            if v == 0 { -                break; -            } -        } - -        Ok(()) -    } -    /// write a u64 to the output as a base-16 integer. -    /// -    /// this is provided for optimization opportunities when the formatted integer can be written -    /// directly to the sink (rather than formatted to an intermediate buffer and output as a -    /// followup step) -    #[inline(always)] -    fn write_u64(&mut self, mut v: u64) -> Result<(), core::fmt::Error> { -        if v == 0 { -            return self.write_fixed_size("0"); -        } - -        // we can fairly easily predict the size of a formatted string here with lzcnt, which also -        // means we can write directly into the correct offsets of the output string. -        let printed_size = ((64 - v.leading_zeros() + 3) >> 2) as usize; - -        if cfg!(debug_assertions) { -            if self.buf.capacity() < self.buf.len() + printed_size { -                panic!("InstructionTextSink::write_u64 would overflow output"); -            } -        } - -        let buf = unsafe { self.buf.as_mut_vec() }; -        let new_len = buf.len() + printed_size; - -        unsafe { -            buf.set_len(new_len); -        } -        let mut p = unsafe { buf.as_mut_ptr().offset(new_len as isize) }; - -        loop { -            let digit = v % 16; -            let c = c_to_hex(digit as u8); -            unsafe { -                p = p.offset(-1); -                p.write(c); -            } -            v = v / 16; -            if v == 0 { -                break; -            } -        } - -        Ok(()) -    } -    fn span_start(&mut self, _ty: TokenType) {} -    fn span_end(&mut self, _ty: TokenType) {} -} @@ -139,8 +139,6 @@ pub mod real_mode;  pub use real_mode::Arch as x86_16;  mod safer_unchecked; -#[cfg(feature = "fmt")] -pub mod display;  const MEM_SIZE_STRINGS: [&'static str; 65] = [      "BUG", diff --git a/src/long_mode/display.rs b/src/long_mode/display.rs index 3615538..18314ab 100644 --- a/src/long_mode/display.rs +++ b/src/long_mode/display.rs @@ -1,14 +1,12 @@  use core::fmt;  use yaxpeax_arch::{Colorize, ShowContextual, NoColors, YaxColors}; -use yaxpeax_arch::display::*;  use crate::safer_unchecked::GetSaferUnchecked as _;  use crate::MEM_SIZE_STRINGS;  use crate::long_mode::{RegSpec, Opcode, Operand, MergeMode, InstDecoder, Instruction, Segment, PrefixRex, OperandSpec}; -use crate::display::DisplaySink; -use crate::display::TokenType; +use yaxpeax_arch::display::DisplaySink;  impl fmt::Display for InstDecoder {      fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -166,202 +164,13 @@ impl fmt::Display for Operand {  impl <T: fmt::Write, Y: YaxColors> Colorize<T, Y> for Operand {      fn colorize(&self, colors: &Y, f: &mut T) -> fmt::Result { -        match self { -            &Operand::ImmediateU8(imm) => { -                write!(f, "{}", colors.number(u8_hex(imm))) -            } -            &Operand::ImmediateI8(imm) => { -                write!(f, "{}", -                    colors.number(signed_i8_hex(imm))) -            }, -            &Operand::ImmediateU16(imm) => { -                write!(f, "{}", colors.number(u16_hex(imm))) -            } -            &Operand::ImmediateI16(imm) => { -                write!(f, "{}", -                    colors.number(signed_i16_hex(imm))) -            }, -            &Operand::ImmediateU32(imm) => { -                write!(f, "{}", colors.number(u32_hex(imm))) -            } -            &Operand::ImmediateI32(imm) => { -                write!(f, "{}", -                    colors.number(signed_i32_hex(imm))) -            }, -            &Operand::ImmediateU64(imm) => { -                write!(f, "{}", colors.number(u64_hex(imm))) -            } -            &Operand::ImmediateI64(imm) => { -                write!(f, "{}", -                    colors.number(signed_i64_hex(imm))) -            }, -            &Operand::Register(ref spec) => { -                f.write_str(regspec_label(spec)) -            } -            &Operand::RegisterMaskMerge(ref spec, ref mask, merge_mode) => { -                f.write_str(regspec_label(spec))?; -                if mask.num != 0 { -                    f.write_str("{")?; -                    f.write_str(regspec_label(mask))?; -                    f.write_str("}")?; -                } -                if let MergeMode::Zero = merge_mode { -                    f.write_str("{z}")?; -                } -                Ok(()) -            } -            &Operand::RegisterMaskMergeSae(ref spec, ref mask, merge_mode, sae_mode) => { -                f.write_str(regspec_label(spec))?; -                if mask.num != 0 { -                    f.write_str("{")?; -                    f.write_str(regspec_label(mask))?; -                    f.write_str("}")?; -                } -                if let MergeMode::Zero = merge_mode { -                    f.write_str("{z}")?; -                } -                f.write_str(sae_mode.label())?; -                Ok(()) -            } -            &Operand::RegisterMaskMergeSaeNoround(ref spec, ref mask, merge_mode) => { -                f.write_str(regspec_label(spec))?; -                if mask.num != 0 { -                    f.write_str("{")?; -                    f.write_str(regspec_label(mask))?; -                    f.write_str("}")?; -                } -                if let MergeMode::Zero = merge_mode { -                    f.write_str("{z}")?; -                } -                f.write_str("{sae}")?; -                Ok(()) -            } -            &Operand::DisplacementU32(imm) => { -                write!(f, "[{}]", colors.address(u32_hex(imm))) -            } -            &Operand::DisplacementU64(imm) => { -                write!(f, "[{}]", colors.address(u64_hex(imm))) -            } -            &Operand::RegDisp(ref spec, disp) => { -                write!(f, "[{} ", regspec_label(spec))?; -                format_number_i32(colors, f, disp, NumberStyleHint::HexSignedWithSignSplit)?; -                write!(f, "]") -            }, -            &Operand::RegDeref(ref spec) => { -                f.write_str("[")?; -                f.write_str(regspec_label(spec))?; -                f.write_str("]") -            }, -            &Operand::RegScale(ref spec, scale) => { -                write!(f, "[{} * {}]", -                    regspec_label(spec), -                    colors.number(scale) -                ) -            }, -            &Operand::RegScaleDisp(ref spec, scale, disp) => { -                write!(f, "[{} * {} ", -                    regspec_label(spec), -                    colors.number(scale), -                )?; -                format_number_i32(colors, f, disp, NumberStyleHint::HexSignedWithSignSplit)?; -                write!(f, "]") -            }, -            &Operand::RegIndexBase(ref base, ref index) => { -                f.write_str("[")?; -                f.write_str(regspec_label(base))?; -                f.write_str(" + ")?; -                f.write_str(regspec_label(index))?; -                f.write_str("]") -            } -            &Operand::RegIndexBaseDisp(ref base, ref index, disp) => { -                write!(f, "[{} + {} ", -                    regspec_label(base), -                    regspec_label(index), -                )?; -                format_number_i32(colors, f, disp, NumberStyleHint::HexSignedWithSignSplit)?; -                write!(f, "]") -            }, -            &Operand::RegIndexBaseScale(ref base, ref index, scale) => { -                write!(f, "[{} + {} * {}]", -                    regspec_label(base), -                    regspec_label(index), -                    colors.number(scale) -                ) -            } -            &Operand::RegIndexBaseScaleDisp(ref base, ref index, scale, disp) => { -                write!(f, "[{} + {} * {} ", -                    regspec_label(base), -                    regspec_label(index), -                    colors.number(scale), -                )?; -                format_number_i32(colors, f, disp, NumberStyleHint::HexSignedWithSignSplit)?; -                write!(f, "]") -            }, -            &Operand::RegDispMasked(ref spec, disp, ref mask_reg) => { -                write!(f, "[{} ", regspec_label(spec))?; -                format_number_i32(colors, f, disp, NumberStyleHint::HexSignedWithSignSplit)?; -                write!(f, "]")?; -                write!(f, "{{{}}}", regspec_label(mask_reg)) -            }, -            &Operand::RegDerefMasked(ref spec, ref mask_reg) => { -                f.write_str("[")?; -                f.write_str(regspec_label(spec))?; -                f.write_str("]")?; -                write!(f, "{{{}}}", regspec_label(mask_reg)) -            }, -            &Operand::RegScaleMasked(ref spec, scale, ref mask_reg) => { -                write!(f, "[{} * {}]", -                    regspec_label(spec), -                    colors.number(scale) -                )?; -                write!(f, "{{{}}}", regspec_label(mask_reg)) -            }, -            &Operand::RegScaleDispMasked(ref spec, scale, disp, ref mask_reg) => { -                write!(f, "[{} * {} ", -                    regspec_label(spec), -                    colors.number(scale), -                )?; -                format_number_i32(colors, f, disp, NumberStyleHint::HexSignedWithSignSplit)?; -                write!(f, "]")?; -                write!(f, "{{{}}}", regspec_label(mask_reg)) -            }, -            &Operand::RegIndexBaseMasked(ref base, ref index, ref mask_reg) => { -                f.write_str("[")?; -                f.write_str(regspec_label(base))?; -                f.write_str(" + ")?; -                f.write_str(regspec_label(index))?; -                f.write_str("]")?; -                write!(f, "{{{}}}", regspec_label(mask_reg)) -            } -            &Operand::RegIndexBaseDispMasked(ref base, ref index, disp, ref mask_reg) => { -                write!(f, "[{} + {} ", -                    regspec_label(base), -                    regspec_label(index), -                )?; -                format_number_i32(colors, f, disp, NumberStyleHint::HexSignedWithSignSplit)?; -                write!(f, "]")?; -                write!(f, "{{{}}}", regspec_label(mask_reg)) -            }, -            &Operand::RegIndexBaseScaleMasked(ref base, ref index, scale, ref mask_reg) => { -                write!(f, "[{} + {} * {}]", -                    regspec_label(base), -                    regspec_label(index), -                    colors.number(scale) -                )?; -                write!(f, "{{{}}}", regspec_label(mask_reg)) -            } -            &Operand::RegIndexBaseScaleDispMasked(ref base, ref index, scale, disp, ref mask_reg) => { -                write!(f, "[{} + {} * {} ", -                    regspec_label(base), -                    regspec_label(index), -                    colors.number(scale), -                )?; -                format_number_i32(colors, f, disp, NumberStyleHint::HexSignedWithSignSplit)?; -                write!(f, "]")?; -                write!(f, "{{{}}}", regspec_label(mask_reg)) -            }, -            &Operand::Nothing => { Ok(()) }, -        } +        let mut f = yaxpeax_arch::display::NoColorsSink { +            out: f +        }; +        let mut visitor = ColorizingOperandVisitor { +            f: &mut f +        }; +        self.visit(&mut visitor)      }  } @@ -375,15 +184,15 @@ impl <T: DisplaySink> crate::long_mode::OperandVisitor for ColorizingOperandVisi      #[cfg_attr(feature="profiling", inline(never))]      fn visit_u8(&mut self, imm: u8) -> Result<Self::Ok, Self::Error> { -        self.f.span_start(TokenType::Immediate); +        self.f.span_start_immediate();          self.f.write_fixed_size("0x")?;          self.f.write_u8(imm)?; -        self.f.span_end(TokenType::Immediate); +        self.f.span_end_immediate();          Ok(())      }      #[cfg_attr(feature="profiling", inline(never))]      fn visit_i8(&mut self, imm: i8) -> Result<Self::Ok, Self::Error> { -        self.f.span_start(TokenType::Immediate); +        self.f.span_start_immediate();          let mut v = imm as u8;          if imm < 0 {              self.f.write_char('-')?; @@ -391,20 +200,20 @@ impl <T: DisplaySink> crate::long_mode::OperandVisitor for ColorizingOperandVisi          }          self.f.write_fixed_size("0x")?;          self.f.write_u8(v)?; -        self.f.span_end(TokenType::Immediate); +        self.f.span_end_immediate();          Ok(())      }      #[cfg_attr(feature="profiling", inline(never))]      fn visit_u16(&mut self, imm: u16) -> Result<Self::Ok, Self::Error> { -        self.f.span_start(TokenType::Immediate); +        self.f.span_start_immediate();          self.f.write_fixed_size("0x")?;          self.f.write_u16(imm)?; -        self.f.span_end(TokenType::Immediate); +        self.f.span_end_immediate();          Ok(())      }      #[cfg_attr(feature="profiling", inline(never))]      fn visit_i16(&mut self, imm: i16) -> Result<Self::Ok, Self::Error> { -        self.f.span_start(TokenType::Immediate); +        self.f.span_start_immediate();          let mut v = imm as u16;          if imm < 0 {              self.f.write_char('-')?; @@ -412,19 +221,19 @@ impl <T: DisplaySink> crate::long_mode::OperandVisitor for ColorizingOperandVisi          }          self.f.write_fixed_size("0x")?;          self.f.write_u16(v)?; -        self.f.span_end(TokenType::Immediate); +        self.f.span_end_immediate();          Ok(())      }      #[cfg_attr(feature="profiling", inline(never))]      fn visit_u32(&mut self, imm: u32) -> Result<Self::Ok, Self::Error> { -        self.f.span_start(TokenType::Immediate); +        self.f.span_start_immediate();          self.f.write_fixed_size("0x")?;          self.f.write_u32(imm)?; -        self.f.span_end(TokenType::Immediate); +        self.f.span_end_immediate();          Ok(())      }      fn visit_i32(&mut self, imm: i32) -> Result<Self::Ok, Self::Error> { -        self.f.span_start(TokenType::Immediate); +        self.f.span_start_immediate();          let mut v = imm as u32;          if imm < 0 {              self.f.write_char('-')?; @@ -432,20 +241,20 @@ impl <T: DisplaySink> crate::long_mode::OperandVisitor for ColorizingOperandVisi          }          self.f.write_fixed_size("0x")?;          self.f.write_u32(v)?; -        self.f.span_end(TokenType::Immediate); +        self.f.span_end_immediate();          Ok(())      }      #[cfg_attr(feature="profiling", inline(never))]      fn visit_u64(&mut self, imm: u64) -> Result<Self::Ok, Self::Error> { -        self.f.span_start(TokenType::Immediate); +        self.f.span_start_immediate();          self.f.write_fixed_size("0x")?;          self.f.write_u64(imm)?; -        self.f.span_end(TokenType::Immediate); +        self.f.span_end_immediate();          Ok(())      }      #[cfg_attr(feature="profiling", inline(never))]      fn visit_i64(&mut self, imm: i64) -> Result<Self::Ok, Self::Error> { -        self.f.span_start(TokenType::Immediate); +        self.f.span_start_immediate();          let mut v = imm as u64;          if imm < 0 {              self.f.write_char('-')?; @@ -453,25 +262,25 @@ impl <T: DisplaySink> crate::long_mode::OperandVisitor for ColorizingOperandVisi          }          self.f.write_fixed_size("0x")?;          self.f.write_u64(v)?; -        self.f.span_end(TokenType::Immediate); +        self.f.span_end_immediate();          Ok(())      }      #[cfg_attr(feature="profiling", inline(never))]      fn visit_reg(&mut self, reg: RegSpec) -> Result<Self::Ok, Self::Error> { -        self.f.span_start(TokenType::Register); +        self.f.span_start_register();          unsafe { self.f.write_lt_8(regspec_label(®))?; } -        self.f.span_end(TokenType::Register); +        self.f.span_end_register();          Ok(())      }      fn visit_reg_mask_merge(&mut self, spec: RegSpec, mask: RegSpec, merge_mode: MergeMode) -> Result<Self::Ok, Self::Error> { -        self.f.span_start(TokenType::Register); +        self.f.span_start_register();          unsafe { self.f.write_lt_8(regspec_label(&spec))?; } -        self.f.span_end(TokenType::Register); +        self.f.span_end_register();          if mask.num != 0 {              self.f.write_fixed_size("{")?; -            self.f.span_start(TokenType::Register); +            self.f.span_start_register();              unsafe { self.f.write_lt_8(regspec_label(&mask))?; } -            self.f.span_end(TokenType::Register); +            self.f.span_end_register();              self.f.write_fixed_size("}")?;          }          if let MergeMode::Zero = merge_mode { @@ -3923,20 +3732,30 @@ pub(crate) fn contextualize_c<T: DisplaySink>(instr: &Instruction, out: &mut T)      }      fn write_jmp_operand<T: fmt::Write>(op: Operand, out: &mut T) -> fmt::Result { +        let mut out = yaxpeax_arch::display::NoColorsSink { +            out, +        }; +        use core::fmt::Write;          match op {              Operand::ImmediateI8(rel) => { -                if rel >= 0 { -                    write!(out, "$+{}", (signed_i32_hex(rel as i32))) +                let rel = if rel >= 0 { +                    out.write_str("$+")?; +                    rel as u8                  } else { -                    write!(out, "${}", (signed_i32_hex(rel as i32))) -                } +                    out.write_str("$-")?; +                    rel.unsigned_abs() +                }; +                out.write_prefixed_u8(rel)              }              Operand::ImmediateI32(rel) => { -                if rel >= 0 { -                    write!(out, "$+{}", (signed_i32_hex(rel))) +                let rel = if rel >= 0 { +                    out.write_str("$+")?; +                    rel as u32                  } else { -                    write!(out, "${}", (signed_i32_hex(rel))) -                } +                    out.write_str("$-")?; +                    rel.unsigned_abs() +                }; +                out.write_prefixed_u32(rel)              }              other => {                  write!(out, "{}", other) @@ -4217,7 +4036,7 @@ impl <'instr, T: fmt::Write, Y: YaxColors> ShowContextual<u64, NoContext, T, Y>              style,          } = self; -        let mut out = crate::display::NoColorsSink { +        let mut out = yaxpeax_arch::display::NoColorsSink {              out: out,          }; @@ -4235,7 +4054,7 @@ impl <'instr, T: fmt::Write, Y: YaxColors> ShowContextual<u64, NoContext, T, Y>  #[cfg(feature="std")]  impl <T: fmt::Write, Y: YaxColors> ShowContextual<u64, [Option<alloc::string::String>], T, Y> for Instruction {      fn contextualize(&self, colors: &Y, _address: u64, context: Option<&[Option<alloc::string::String>]>, out: &mut T) -> fmt::Result { -        let mut out = crate::display::NoColorsSink { +        let mut out = yaxpeax_arch::display::NoColorsSink {              out,          };          let out = &mut out; @@ -4539,11 +4358,11 @@ impl InstructionTextBuffer {      /// into.      ///      /// SAFETY: callers must print at most one instruction into this handle. -    unsafe fn write_handle(&mut self) -> crate::display::InstructionTextSink { +    unsafe fn write_handle(&mut self) -> yaxpeax_arch::display::InstructionTextSink {          self.content.clear();          // Safety: `content` was just cleared, so writing begins at the start of the buffer.          // `content`is large enough to hold a fully-formatted instruction (see          // `InstructionTextBuffer::new`). -        crate::display::InstructionTextSink::new(&mut self.content) +        yaxpeax_arch::display::InstructionTextSink::new(&mut self.content)      }  } diff --git a/src/long_mode/mod.rs b/src/long_mode/mod.rs index 9aeacdc..d66f59a 100644 --- a/src/long_mode/mod.rs +++ b/src/long_mode/mod.rs @@ -802,6 +802,52 @@ impl Operand {              }          }      } + +    /// provided for parity with [`Instruction::visit_operand`]. this has little utility other than +    /// to reuse an `OperandVisitor` on an `Operand` directly. +    pub fn visit<T: OperandVisitor>(&self, visitor: &mut T) -> Result<T::Ok, T::Error> { +        match self { +            Operand::Nothing => { +                visitor.visit_other() +            } +            Operand::Register(reg) => { +                visitor.visit_reg(*reg) +            } +            Operand::RegDeref(reg) => { +                visitor.visit_deref(*reg) +            } +            Operand::RegDisp(reg, disp) => { +                visitor.visit_disp(*reg, *disp) +            } +            Operand::ImmediateI8(imm) => visitor.visit_i8(*imm), +            Operand::ImmediateU8(imm) => visitor.visit_u8(*imm), +            Operand::ImmediateI16(imm) => visitor.visit_i16(*imm), +            Operand::ImmediateU16(imm) => visitor.visit_u16(*imm), +            Operand::ImmediateI32(imm) => visitor.visit_i32(*imm), +            Operand::ImmediateU32(imm) => visitor.visit_u32(*imm), +            Operand::ImmediateI64(imm) => visitor.visit_i64(*imm), +            Operand::ImmediateU64(imm) => visitor.visit_u64(*imm), +            Operand::DisplacementU32(disp) => visitor.visit_abs_u32(*disp), +            Operand::DisplacementU64(disp) => visitor.visit_abs_u64(*disp), +            Operand::RegScale(reg, scale) => visitor.visit_reg_scale(*reg, *scale), +            Operand::RegScaleDisp(reg, scale, disp) => visitor.visit_reg_scale_disp(*reg, *scale, *disp), +            Operand::RegIndexBase(_, _) => { /* not actually reachable anymore */ visitor.visit_other() }, +            Operand::RegIndexBaseDisp(_, _, _) => { /* not actually reachable anymore */ visitor.visit_other() }, +            Operand::RegIndexBaseScale(base, index, scale) => visitor.visit_index_base_scale(*base, *index, *scale), +            Operand::RegIndexBaseScaleDisp(base, index, scale, disp) => visitor.visit_index_base_scale_disp(*base, *index, *scale, *disp), +            Operand::RegisterMaskMerge(reg, mask, merge) => visitor.visit_reg_mask_merge(*reg, *mask, *merge), +            Operand::RegisterMaskMergeSae(reg, mask, merge, sae) => visitor.visit_reg_mask_merge_sae(*reg, *mask, *merge, *sae), +            Operand::RegisterMaskMergeSaeNoround(reg, mask, merge) => visitor.visit_reg_mask_merge_sae_noround(*reg, *mask, *merge), +            Operand::RegDerefMasked(reg, mask) => visitor.visit_reg_deref_masked(*reg, *mask), +            Operand::RegDispMasked(reg, disp, mask) => visitor.visit_reg_disp_masked(*reg, *disp, *mask), +            Operand::RegScaleMasked(reg, scale, mask) => visitor.visit_reg_scale_masked(*reg, *scale, *mask), +            Operand::RegIndexBaseMasked(_, _, _) => { /* not actually reachable anymore */ visitor.visit_other() }, +            Operand::RegIndexBaseDispMasked(_, _, _, _) => { /* not actually reachable anymore */ visitor.visit_other() }, +            Operand::RegScaleDispMasked(base, scale, disp, mask) => visitor.visit_reg_scale_disp_masked(*base, *scale, *disp, *mask), +            Operand::RegIndexBaseScaleMasked(base, index, scale, mask) => visitor.visit_index_base_scale_masked(*base, *index, *scale, *mask), +            Operand::RegIndexBaseScaleDispMasked(base, index, scale, disp, mask) => visitor.visit_index_base_scale_disp_masked(*base, *index, *scale, *disp, *mask), +        } +    }  }  #[test] | 
