Skip to main content

asm_rs/
encoder.rs

1//! x86-64 instruction encoder.
2//!
3//! Encodes parsed `Instruction`s into machine-code bytes.
4//! Uses a table-driven approach for opcode lookup with manual
5//! ModR/M, SIB, REX prefix construction.
6
7// VEX/SSE encoding helpers inherently require many parameters (opcode bytes,
8// prefix, W-bit, register operands, etc.); suppressing this lint is the
9// pragmatic choice over wrapping parameters in a struct that adds no clarity.
10#![allow(clippy::too_many_arguments)]
11
12#[allow(unused_imports)]
13use alloc::string::String;
14#[allow(unused_imports)]
15use alloc::string::ToString;
16#[allow(unused_imports)]
17use alloc::vec;
18use alloc::vec::Vec;
19
20use crate::error::AsmError;
21#[allow(unused_imports)]
22use crate::error::Span;
23use crate::ir::*;
24
25// ─── InstrBytes: stack-allocated instruction buffer ────────────────────
26
27/// Stack-allocated instruction byte buffer — eliminates per-instruction heap
28/// allocation on the encoding hot path.
29///
30/// x86/x86-64 instructions are at most 15 bytes; AArch64 and ARM32 are
31/// fixed at 4; RISC-V pseudo-instructions expand to at most 8 words (32
32/// bytes for RV64 `li` with a full 64-bit immediate).  This inline buffer
33/// covers **all** architectures without touching the heap.
34///
35/// Capacity: 32 bytes on the stack.
36#[derive(Clone)]
37pub struct InstrBytes {
38    data: [u8; 32],
39    len: u8,
40}
41
42impl InstrBytes {
43    /// Create an empty buffer.
44    #[inline]
45    pub const fn new() -> Self {
46        Self {
47            data: [0; 32],
48            len: 0,
49        }
50    }
51
52    /// Create a buffer pre-filled from a byte slice (max 32 bytes).
53    #[inline]
54    pub fn from_slice(src: &[u8]) -> Self {
55        let mut buf = Self::new();
56        buf.extend_from_slice(src);
57        buf
58    }
59
60    /// Append a single byte.
61    ///
62    /// # Panics
63    ///
64    /// Panics if the buffer is already full (32 bytes). The capacity is an
65    /// internal invariant — the longest encodable instruction on any supported
66    /// target is 15 bytes (x86-64's architectural maximum) — so overflow means
67    /// a bug in an encoder, not bad input.
68    #[inline]
69    pub fn push(&mut self, byte: u8) {
70        assert!(
71            (self.len as usize) < 32,
72            "InstrBytes overflow: cannot push beyond 32 bytes"
73        );
74        self.data[self.len as usize] = byte;
75        self.len += 1;
76    }
77
78    /// Append a slice of bytes.
79    ///
80    /// # Panics
81    ///
82    /// Panics if appending would exceed the 32-byte capacity; see
83    /// [`push`](Self::push) for why that is an internal invariant.
84    #[inline]
85    pub fn extend_from_slice(&mut self, bytes: &[u8]) {
86        let start = self.len as usize;
87        let end = start + bytes.len();
88        assert!(
89            end <= 32,
90            "InstrBytes overflow: {} + {} exceeds 32-byte capacity",
91            start,
92            bytes.len()
93        );
94        self.data[start..end].copy_from_slice(bytes);
95        self.len = end as u8;
96    }
97
98    /// Insert a byte at the given position, shifting subsequent bytes right.
99    ///
100    /// # Panics
101    ///
102    /// Panics if the buffer is full or `pos` is out of bounds.
103    #[inline]
104    pub fn insert(&mut self, pos: usize, byte: u8) {
105        let len = self.len as usize;
106        assert!(
107            pos <= len && len < 32,
108            "InstrBytes insert: pos={} len={} out of bounds",
109            pos,
110            len
111        );
112        // Shift bytes from pos..len right by 1
113        let mut i = len;
114        while i > pos {
115            self.data[i] = self.data[i - 1];
116            i -= 1;
117        }
118        self.data[pos] = byte;
119        self.len += 1;
120    }
121
122    /// Remove a byte at the given position, shifting subsequent bytes left.
123    ///
124    /// # Panics
125    ///
126    /// Panics if `pos` is out of bounds.
127    #[inline]
128    pub fn remove(&mut self, pos: usize) {
129        let len = self.len as usize;
130        assert!(
131            pos < len,
132            "InstrBytes remove: pos={} out of bounds (len={})",
133            pos,
134            len
135        );
136        let mut i = pos;
137        while i + 1 < len {
138            self.data[i] = self.data[i + 1];
139            i += 1;
140        }
141        self.data[len - 1] = 0;
142        self.len -= 1;
143    }
144
145    /// Number of bytes in the buffer.
146    #[inline]
147    pub fn len(&self) -> usize {
148        self.len as usize
149    }
150
151    /// Whether the buffer is empty.
152    #[inline]
153    pub fn is_empty(&self) -> bool {
154        self.len == 0
155    }
156
157    /// Convert to a heap-allocated `Vec<u8>`.
158    #[inline]
159    pub fn to_vec(&self) -> Vec<u8> {
160        self.as_ref().to_vec()
161    }
162}
163
164impl Default for InstrBytes {
165    #[inline]
166    fn default() -> Self {
167        Self::new()
168    }
169}
170
171impl core::ops::Deref for InstrBytes {
172    type Target = [u8];
173    #[inline]
174    fn deref(&self) -> &[u8] {
175        &self.data[..self.len as usize]
176    }
177}
178
179impl core::ops::DerefMut for InstrBytes {
180    #[inline]
181    fn deref_mut(&mut self) -> &mut [u8] {
182        &mut self.data[..self.len as usize]
183    }
184}
185
186impl AsRef<[u8]> for InstrBytes {
187    #[inline]
188    fn as_ref(&self) -> &[u8] {
189        self
190    }
191}
192
193impl AsMut<[u8]> for InstrBytes {
194    #[inline]
195    fn as_mut(&mut self) -> &mut [u8] {
196        self
197    }
198}
199
200impl core::fmt::Debug for InstrBytes {
201    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
202        f.debug_list().entries(self.iter()).finish()
203    }
204}
205
206impl PartialEq for InstrBytes {
207    fn eq(&self, other: &Self) -> bool {
208        **self == **other
209    }
210}
211
212impl Eq for InstrBytes {}
213
214impl PartialEq<[u8]> for InstrBytes {
215    fn eq(&self, other: &[u8]) -> bool {
216        **self == *other
217    }
218}
219
220impl PartialEq<Vec<u8>> for InstrBytes {
221    fn eq(&self, other: &Vec<u8>) -> bool {
222        **self == **other
223    }
224}
225
226// ─── EncodedInstr ──────────────────────────────────────────
227
228/// Result of encoding a single instruction.
229#[derive(Debug, Clone)]
230pub struct EncodedInstr {
231    /// The machine code bytes (long form for relaxable instructions).
232    pub bytes: InstrBytes,
233    /// If the instruction references a label, this records it for the linker.
234    pub relocation: Option<Relocation>,
235    /// If present, the instruction can be shortened via branch relaxation.
236    pub relax: Option<RelaxInfo>,
237}
238
239/// How the linker should patch the relocation target into the instruction.
240#[derive(Debug, Clone, Copy, PartialEq, Eq)]
241#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
242pub enum RelocKind {
243    /// x86 RIP-relative: raw `i8`/`i32` displacement written at `offset`.
244    /// `trailing_bytes` accounts for any immediate after the disp field.
245    X86Relative,
246    /// Absolute address written as raw LE bytes (1/2/4/8).
247    Absolute,
248    /// ARM32 B/BL: PC-relative offset >> 2 in bits 23:0 of the 32-bit word.
249    #[cfg(feature = "arm")]
250    ArmBranch24,
251    /// ARM32 LDR literal (PC-relative): 12-bit offset in bits 11:0, U-bit in bit 23.
252    #[cfg(feature = "arm")]
253    ArmLdrLit,
254    /// ARM32 ADR (data-processing immediate): 8-bit value with 4-bit rotation in bits 11:0,
255    /// ADD/SUB opcode in bits 24:21, U-sense via opcode (ADD=0x4, SUB=0x2).
256    #[cfg(feature = "arm")]
257    ArmAdr,
258    /// Thumb-2 conditional branch (16-bit): 8-bit signed offset >> 1 in bits 7:0.
259    /// Range: ±256 bytes from PC+4.
260    #[cfg(feature = "arm")]
261    ThumbBranch8,
262    /// Thumb unconditional branch (16-bit): 11-bit signed offset >> 1 in bits 10:0.
263    /// Range: ±2 KB from PC+4.
264    #[cfg(feature = "arm")]
265    ThumbBranch11,
266    /// Thumb-2 BL (32-bit): 25-bit offset across two halfwords. Range: ±16 MB.
267    #[cfg(feature = "arm")]
268    ThumbBl,
269    /// Thumb-2 B.W (32-bit wide unconditional branch): 24-bit offset. Range: ±16 MB.
270    #[cfg(feature = "arm")]
271    ThumbBranchW,
272    /// Thumb-2 B.cond.W (32-bit wide conditional branch): 20-bit offset. Range: ±1 MB.
273    #[cfg(feature = "arm")]
274    ThumbCondBranchW,
275    /// Thumb LDR Rt, \[PC, #imm8×4\]: 8-bit word-aligned PC-relative literal load.
276    /// Range: 0–1020 bytes forward only. PC = (instr_addr + 4) & ~3.
277    #[cfg(feature = "arm")]
278    ThumbLdrLit8,
279    /// AArch64 B/BL: PC-relative offset >> 2 in bits 25:0 of the 32-bit word.
280    #[cfg(feature = "aarch64")]
281    Aarch64Jump26,
282    /// AArch64 B.cond / CBZ / CBNZ: PC-relative offset >> 2 in bits 23:5.
283    #[cfg(feature = "aarch64")]
284    Aarch64Branch19,
285    /// AArch64 TBZ / TBNZ: PC-relative offset >> 2 in bits 18:5 (14-bit imm).
286    #[cfg(feature = "aarch64")]
287    Aarch64Branch14,
288    /// AArch64 LDR (literal): PC-relative offset >> 2 in bits 23:5.
289    #[cfg(feature = "aarch64")]
290    Aarch64LdrLit19,
291    /// AArch64 ADR: PC-relative offset with immhi (bits 23:5) and immlo (bits 30:29).
292    #[cfg(feature = "aarch64")]
293    Aarch64Adr21,
294    /// AArch64 ADRP: page-relative offset with immhi/immlo, target &= ~0xFFF.
295    #[cfg(feature = "aarch64")]
296    Aarch64Adrp,
297    /// AArch64 ADRP+ADD pair (8 bytes): ADR relaxation long form.
298    /// The first word is ADRP (patched with page offset), the second word
299    /// is ADD (patched with lo12 = target & 0xFFF).
300    #[cfg(feature = "aarch64")]
301    Aarch64AdrpAddPair,
302    /// RISC-V JAL: PC-relative offset in J-type immediate (bits 31:12), ±1MB range.
303    #[cfg(feature = "riscv")]
304    RvJal20,
305    /// RISC-V B-type branch: PC-relative offset in B-type immediate (bits 31:7), ±4KB range.
306    #[cfg(feature = "riscv")]
307    RvBranch12,
308    /// RISC-V AUIPC: upper 20 bits of PC-relative offset (bits 31:12).
309    #[cfg(feature = "riscv")]
310    RvAuipc20,
311    /// RISC-V C-extension CB-type branch: 9-bit signed PC-relative offset (±256 B).
312    #[cfg(feature = "riscv")]
313    RvCBranch8,
314    /// RISC-V C-extension CJ-type jump: 12-bit signed PC-relative offset (±2 KB).
315    #[cfg(feature = "riscv")]
316    RvCJump11,
317}
318
319/// A relocation record for unresolved labels.
320#[derive(Debug, Clone)]
321#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
322pub struct Relocation {
323    /// Offset within the instruction bytes where the relocation target is.
324    pub offset: usize,
325    /// Size of the relocation field in bytes (1, 2, or 4).
326    pub size: u8,
327    /// The label name to resolve.  Stored as `Rc<str>` so that cloning during
328    /// relocation propagation and linker resolution is a cheap refcount bump
329    /// instead of a heap allocation.
330    pub label: alloc::rc::Rc<str>,
331    /// How the linker patches the target address into the instruction bytes.
332    pub kind: RelocKind,
333    /// The addend for the relocation (constant offset).
334    pub addend: i64,
335    /// Number of instruction bytes that follow the relocation field.
336    /// For x86 RIP-relative relocations, the CPU computes EA = RIP + disp where
337    /// RIP = address of the byte AFTER the entire instruction.  When a trailing
338    /// immediate follows the displacement, we need this to calculate the correct
339    /// RIP at link time: `rip = reloc_addr + size + trailing_bytes`.
340    pub trailing_bytes: u8,
341}
342
343/// Information for branch relaxation — allows the linker to try a shorter encoding.
344///
345/// When present on an [`EncodedInstr`], the linker starts with this short form
346/// and only promotes to the long form (in `bytes`) when the target is out of
347/// ±127 byte range.  This implements Szymanski-style monotonic growth.
348#[derive(Debug, Clone)]
349pub struct RelaxInfo {
350    /// Complete short-form instruction bytes (opcode + placeholder rel8).
351    pub short_bytes: InstrBytes,
352    /// Offset of the rel8 displacement byte within `short_bytes`.
353    pub short_reloc_offset: usize,
354    /// Optional relocation for the short form.  When `Some`, the linker
355    /// applies this relocation to `short_bytes` instead of raw byte-patching.
356    /// Used for architectures like RISC-V where even the short form needs
357    /// complex bitfield manipulation.
358    pub short_relocation: Option<Relocation>,
359}
360
361/// Encode one instruction into machine code bytes.
362/// Extract label name and addend from a label or expression operand.
363///
364/// Returns `Some((label, addend))` for:
365/// - `Operand::Label("foo")` → `("foo", 0)`
366/// - `Operand::Expression(label + N)` → `("label", N)`
367///
368/// Returns `None` for non-label operands or expressions with multiple labels.
369#[inline]
370#[cfg(any(feature = "x86", feature = "x86_64", feature = "riscv"))]
371pub(crate) fn extract_label(op: &Operand) -> Option<(&str, i64)> {
372    match op {
373        Operand::Label(name) => Some((name.as_str(), 0)),
374        Operand::Expression(expr) => expr.label_addend(),
375        _ => None,
376    }
377}
378
379/// # Errors
380///
381/// Returns `Err(AsmError)` if the instruction mnemonic is unknown, the
382/// operand combination is invalid, or the target architecture is not
383/// supported.
384#[inline]
385pub fn encode_instruction(instr: &Instruction, arch: Arch) -> Result<EncodedInstr, AsmError> {
386    #[allow(unreachable_patterns)]
387    match arch {
388        #[cfg(feature = "x86_64")]
389        Arch::X86_64 => encode_x86_64(instr),
390        #[cfg(feature = "x86")]
391        Arch::X86 => encode_x86_32(instr),
392        #[cfg(feature = "arm")]
393        Arch::Arm | Arch::Thumb => crate::arm::encode_arm(instr, arch),
394        #[cfg(feature = "aarch64")]
395        Arch::Aarch64 => crate::aarch64::encode_aarch64(instr),
396        #[cfg(feature = "riscv")]
397        Arch::Rv32 | Arch::Rv64 => crate::riscv::encode_riscv(instr, arch),
398        _ => Err(AsmError::Syntax {
399            msg: alloc::format!(
400                "encoder not implemented for {} (enable the feature flag)",
401                arch
402            ),
403            span: instr.span,
404        }),
405    }
406}
407
408// ─── Shared x86/x86-64 helpers ───────────────────────────────
409
410/// Emit legacy prefixes (LOCK/REP/REPNE/segment) and memory-operand
411/// segment overrides into `buf`.  Returns the byte length of all emitted
412/// prefix bytes (needed later for displacement scanning).
413#[cfg(any(feature = "x86", feature = "x86_64"))]
414fn emit_x86_prefixes(buf: &mut InstrBytes, instr: &Instruction, ops: &OperandList) -> usize {
415    for pfx in &instr.prefixes {
416        match pfx {
417            Prefix::Lock => buf.push(0xF0),
418            Prefix::Rep => buf.push(0xF3),
419            Prefix::Repne => buf.push(0xF2),
420            Prefix::SegFs => buf.push(0x64),
421            Prefix::SegGs => buf.push(0x65),
422        }
423    }
424
425    // Emit segment override from memory operand (must come before REX/opcode).
426    // Skip if already emitted via instr.prefixes (avoid double emission).
427    let has_seg_prefix = instr
428        .prefixes
429        .iter()
430        .any(|p| matches!(p, Prefix::SegFs | Prefix::SegGs));
431    if !has_seg_prefix {
432        for op in ops {
433            if let Operand::Memory(mem) = op {
434                if let Some(seg) = mem.segment {
435                    let seg_byte = match seg {
436                        Register::Cs => Some(0x2E_u8),
437                        Register::Ds => Some(0x3E),
438                        Register::Es => Some(0x26),
439                        Register::Fs => Some(0x64),
440                        Register::Gs => Some(0x65),
441                        Register::Ss => Some(0x36),
442                        _ => None,
443                    };
444                    if let Some(b) = seg_byte {
445                        buf.push(b);
446                    }
447                }
448            }
449        }
450    }
451
452    buf.len()
453}
454
455/// Check whether any memory operand uses 32-bit base/index registers,
456/// requiring the address-size override prefix (0x67) in 64-bit mode.
457///
458/// In 64-bit mode the default address size is 64 bits. Using `[eax]` or
459/// `[ecx+edx*4]` requires the `0x67` prefix to select 32-bit addressing.
460/// This is emitted only in `encode_x86_64`, not in `encode_x86_32` where
461/// 32-bit addressing is already the default.
462#[cfg(feature = "x86_64")]
463fn needs_addr_size_override(ops: &OperandList) -> bool {
464    for op in ops {
465        if let Operand::Memory(mem) = op {
466            if let Some(base) = mem.base {
467                // RIP-relative is always 64-bit addressing — no override
468                if base == Register::Rip {
469                    continue;
470                }
471                if base.size_bits() == 32 {
472                    return true;
473                }
474            }
475            if let Some(idx) = mem.index {
476                if idx.size_bits() == 32 {
477                    return true;
478                }
479            }
480        }
481    }
482    false
483}
484
485/// Validate that a LOCK prefix is only used with a memory destination operand.
486#[cfg(any(feature = "x86", feature = "x86_64"))]
487fn validate_lock_prefix(instr: &Instruction, ops: &OperandList) -> Result<(), AsmError> {
488    if instr.prefixes.contains(&Prefix::Lock) {
489        let has_memory_dst = matches!(ops.first(), Some(Operand::Memory(_)));
490        if !has_memory_dst {
491            return Err(AsmError::InvalidOperands {
492                detail: String::from("LOCK prefix requires a memory destination operand"),
493                span: instr.span,
494            });
495        }
496    }
497    Ok(())
498}
499
500/// If a memory operand carries a `disp_label` and the encoder didn't set a
501/// relocation explicitly, scan the encoded bytes for the displacement field
502/// and create the relocation.  Also computes `trailing_bytes` for
503/// RIP-relative relocations.
504#[cfg(any(feature = "x86", feature = "x86_64"))]
505fn propagate_disp_label_reloc(
506    buf: &[u8],
507    ops: &OperandList,
508    prefix_len: usize,
509    reloc: &mut Option<Relocation>,
510) {
511    if reloc.is_some() {
512        return;
513    }
514
515    for op in ops {
516        if let Operand::Memory(mem) = op {
517            if let Some(ref label) = mem.disp_label {
518                // Structurally locate the displacement offset by parsing
519                // the instruction's ModR/M + SIB layout, rather than
520                // byte-pattern scanning which is fragile when displacement
521                // bytes accidentally match opcode/prefix bytes.
522                if let Some(off) = find_disp_offset_structural(buf, prefix_len) {
523                    let kind = if mem.base == Some(Register::Rip) {
524                        RelocKind::X86Relative
525                    } else {
526                        RelocKind::Absolute
527                    };
528                    *reloc = Some(Relocation {
529                        offset: off,
530                        size: 4,
531                        label: alloc::rc::Rc::from(&**label),
532                        kind,
533                        addend: mem.disp,
534                        trailing_bytes: 0,
535                    });
536                }
537                break; // At most one relocation per instruction
538            }
539        }
540    }
541
542    // Compute trailing_bytes for RIP-relative relocations
543    if let Some(ref mut r) = reloc {
544        if r.kind == RelocKind::X86Relative {
545            let end_of_reloc = r.offset + r.size as usize;
546            r.trailing_bytes = (buf.len() - end_of_reloc) as u8;
547        }
548    }
549}
550
551/// Returns `true` for x86 legacy prefix bytes that an individual encoder may
552/// emit after the top-level `emit_x86_prefixes()` call: operand-size override
553/// (0x66), address-size override (0x67), and SSE mandatory prefixes (0xF2/0xF3).
554#[inline]
555#[cfg(any(feature = "x86", feature = "x86_64"))]
556fn is_post_prefix_legacy_byte(b: u8) -> bool {
557    matches!(b, 0x66 | 0x67 | 0xF2 | 0xF3)
558}
559
560/// Structurally locate the displacement field offset within an encoded x86
561/// instruction by parsing the prefix → opcode → ModR/M → SIB chain.
562///
563/// This replaces the previous byte-pattern scanning approach (which searched
564/// for `(disp as i32).to_le_bytes()` in the buffer) and is immune to false
565/// matches when displacement bytes happen to equal opcode or prefix bytes.
566///
567/// Returns `Some(offset)` — the byte index in `buf` where the displacement
568/// field starts — or `None` if the instruction has no memory displacement.
569#[cfg(any(feature = "x86", feature = "x86_64"))]
570fn find_disp_offset_structural(buf: &[u8], prefix_len: usize) -> Option<usize> {
571    if buf.len() <= prefix_len {
572        return None;
573    }
574
575    let mut pos = prefix_len;
576
577    // Skip legacy prefixes emitted by individual encoders (0x66 operand-size,
578    // 0x67 address-size, 0xF2/0xF3 mandatory SSE prefixes).  These always
579    // precede the REX/VEX/EVEX prefix and opcode.
580    while pos < buf.len() && is_post_prefix_legacy_byte(buf[pos]) {
581        pos += 1;
582    }
583
584    if pos >= buf.len() {
585        return None;
586    }
587
588    // ── VEX / EVEX ──────────────────────────────────────────────────
589    // VEX 2-byte: [C5] [RvvvvLpp]            → 1 opcode byte follows
590    // VEX 3-byte: [C4] [RXBmmmmm] [WvvvvLpp] → 1 opcode byte follows
591    // EVEX:       [62] [P0] [P1] [P2]         → 1 opcode byte follows
592    //
593    // The opcode map (0F / 0F38 / 0F3A) is encoded *inside* the VEX/EVEX
594    // prefix, so exactly one opcode byte appears before ModR/M.
595    let modrm_pos = if buf[pos] == 0xC5 {
596        pos + 3 // C5 + 1 prefix byte + 1 opcode
597    } else if buf[pos] == 0xC4 {
598        pos + 4 // C4 + 2 prefix bytes + 1 opcode
599    } else if buf[pos] == 0x62 {
600        pos + 5 // 62 + 3 prefix bytes + 1 opcode
601    } else {
602        // ── Legacy encoding ─────────────────────────────────────────
603        // Skip REX prefix (0x40..0x4F).  In 32-bit mode these bytes are
604        // the INC/DEC short forms which only encode register operands, so
605        // they never appear when a memory displacement needs locating.
606        if (buf[pos] & 0xF0) == 0x40 {
607            pos += 1;
608        }
609        if pos >= buf.len() {
610            return None;
611        }
612
613        // Parse opcode escape:
614        //   0x0F 0x38 xx → 3-byte opcode (ModR/M follows xx)
615        //   0x0F 0x3A xx → 3-byte opcode (ModR/M follows xx)
616        //   0x0F xx      → 2-byte opcode (ModR/M follows xx)
617        //   xx           → 1-byte opcode (ModR/M follows xx)
618        if buf[pos] == 0x0F {
619            pos += 1;
620            if pos >= buf.len() {
621                return None;
622            }
623            if buf[pos] == 0x38 || buf[pos] == 0x3A {
624                pos += 2; // escape extension byte + opcode byte
625            } else {
626                pos += 1; // opcode byte
627            }
628        } else {
629            pos += 1; // single-byte opcode
630        }
631
632        pos
633    };
634
635    // ── Parse ModR/M ────────────────────────────────────────────────
636    if modrm_pos >= buf.len() {
637        return None;
638    }
639
640    let modrm = buf[modrm_pos];
641    let mod_bits = (modrm >> 6) & 0x03;
642    let rm = modrm & 0x07;
643
644    // mod=11 → register-direct, no memory displacement
645    if mod_bits == 0x03 {
646        return None;
647    }
648
649    let mut disp_pos = modrm_pos + 1;
650
651    // r/m=100 → SIB byte follows ModR/M
652    if rm == 0x04 {
653        if disp_pos >= buf.len() {
654            return None;
655        }
656        let sib_base = buf[disp_pos] & 0x07;
657        disp_pos += 1; // skip SIB
658
659        match mod_bits {
660            0b00 if sib_base == 0x05 => Some(disp_pos), // [index*scale + disp32]
661            0b00 => None,                               // [base + index*scale]
662            0b01 | 0b10 => Some(disp_pos),              // [base + index*scale + disp]
663            _ => None,
664        }
665    } else {
666        match mod_bits {
667            0b00 if rm == 0x05 => Some(disp_pos), // [RIP + disp32] or [disp32]
668            0b00 => None,                         // [base], no displacement
669            0b01 | 0b10 => Some(disp_pos),        // [base + disp8/32]
670            _ => None,
671        }
672    }
673}
674
675/// Fix up `trailing_bytes` for any RIP-relative relocation.  Must be called
676/// AFTER the instruction buffer is final — works for both dispatch-created
677/// and disp_label-propagated relocations.
678#[cfg(feature = "x86_64")]
679fn fixup_rip_trailing_bytes(buf: &[u8], reloc: &mut Option<Relocation>) {
680    if let Some(ref mut r) = reloc {
681        if r.kind == RelocKind::X86Relative {
682            let end_of_reloc = r.offset + r.size as usize;
683            r.trailing_bytes = (buf.len() - end_of_reloc) as u8;
684        }
685    }
686}
687
688#[cfg(feature = "x86_64")]
689fn encode_x86_64(instr: &Instruction) -> Result<EncodedInstr, AsmError> {
690    let mut buf = InstrBytes::new();
691    let mut reloc: Option<Relocation> = None;
692    let mut relax_info: Option<RelaxInfo> = None;
693
694    let ops = &instr.operands;
695    let prefix_len = emit_x86_prefixes(&mut buf, instr, ops);
696
697    // Address-size override: emit 0x67 when memory operands use 32-bit
698    // base/index registers in 64-bit mode (e.g. `mov eax, [ecx]`).
699    if needs_addr_size_override(ops) {
700        buf.push(0x67);
701    }
702
703    let mnemonic = instr.mnemonic.as_str();
704
705    match crate::x86::dispatch_x86_64(mnemonic, &mut buf, ops, instr, &mut reloc, &mut relax_info) {
706        Some(Ok(())) => {}
707        Some(Err(e)) => return Err(e),
708        None => {
709            return Err(AsmError::UnknownMnemonic {
710                mnemonic: String::from(mnemonic),
711                arch: crate::error::ArchName::X86_64,
712                span: instr.span,
713            });
714        }
715    }
716
717    validate_lock_prefix(instr, ops)?;
718    propagate_disp_label_reloc(&buf, ops, prefix_len, &mut reloc);
719
720    // Compute trailing_bytes for RIP-relative relocations: the number of
721    // instruction bytes that follow the relocation field.  The CPU computes
722    // EA = RIP + disp32, where RIP = address past the ENTIRE instruction.
723    fixup_rip_trailing_bytes(&buf, &mut reloc);
724
725    Ok(EncodedInstr {
726        bytes: buf,
727        relocation: reloc,
728        relax: relax_info,
729    })
730}
731
732// ─── x86-32 encoder ──────────────────────────────────────────
733
734/// Validate that an instruction's operands are legal for 32-bit protected mode.
735#[cfg(feature = "x86")]
736fn validate_x86_32(instr: &Instruction) -> Result<(), AsmError> {
737    for op in &instr.operands {
738        match op {
739            Operand::Register(reg) => {
740                if reg.size_bits() == 64 {
741                    return Err(AsmError::InvalidOperands {
742                        detail: String::from("64-bit registers are not available in 32-bit mode"),
743                        span: instr.span,
744                    });
745                }
746                if reg.is_extended() {
747                    return Err(AsmError::InvalidOperands {
748                        detail: String::from(
749                            "extended registers (R8-R15) are not available in 32-bit mode",
750                        ),
751                        span: instr.span,
752                    });
753                }
754                if reg.requires_rex_for_byte() {
755                    return Err(AsmError::InvalidOperands {
756                        detail: String::from(
757                            "SPL/BPL/SIL/DIL are not available in 32-bit mode (require REX)",
758                        ),
759                        span: instr.span,
760                    });
761                }
762                if matches!(reg, Register::Rip) {
763                    return Err(AsmError::InvalidOperands {
764                        detail: String::from(
765                            "RIP-relative addressing is not available in 32-bit mode",
766                        ),
767                        span: instr.span,
768                    });
769                }
770            }
771            Operand::Memory(mem) => {
772                if let Some(base) = mem.base {
773                    if base == Register::Rip {
774                        return Err(AsmError::InvalidOperands {
775                            detail: String::from(
776                                "RIP-relative addressing is not available in 32-bit mode",
777                            ),
778                            span: instr.span,
779                        });
780                    }
781                    if base.size_bits() == 64 || base.is_extended() {
782                        return Err(AsmError::InvalidOperands {
783                            detail: String::from(
784                                "64-bit/extended registers cannot be used as memory base in 32-bit mode",
785                            ),
786                            span: instr.span,
787                        });
788                    }
789                }
790                if let Some(idx) = mem.index {
791                    if idx.size_bits() == 64 || idx.is_extended() {
792                        return Err(AsmError::InvalidOperands {
793                            detail: String::from(
794                                "64-bit/extended registers cannot be used as memory index in 32-bit mode",
795                            ),
796                            span: instr.span,
797                        });
798                    }
799                }
800            }
801            _ => {}
802        }
803    }
804
805    if instr.mnemonic == "movsxd" {
806        return Err(AsmError::UnknownMnemonic {
807            mnemonic: String::from("movsxd"),
808            arch: crate::error::ArchName::X86,
809            span: instr.span,
810        });
811    }
812
813    Ok(())
814}
815
816/// x86-32 (protected mode) encoder.
817///
818/// Shares the instruction encoding logic with x86-64 but:
819/// - Rejects 64-bit registers, extended registers (R8-R15), RIP-relative
820/// - push/pop accept 32-bit registers (default operand size)
821/// - All other instructions produce identical byte sequences
822#[cfg(feature = "x86")]
823fn encode_x86_32(instr: &Instruction) -> Result<EncodedInstr, AsmError> {
824    validate_x86_32(instr)?;
825
826    let mut buf = InstrBytes::new();
827    let mut reloc: Option<Relocation> = None;
828    let mut relax_info: Option<RelaxInfo> = None;
829
830    let ops = &instr.operands;
831    let prefix_len = emit_x86_prefixes(&mut buf, instr, ops);
832    let mnemonic = instr.mnemonic.as_str();
833
834    // Handle push/pop/inc/dec specially — 32-bit mode has dedicated short forms.
835    match mnemonic {
836        "push" => {
837            encode_push_32(&mut buf, ops, instr, &mut reloc)?;
838            return Ok(EncodedInstr {
839                bytes: buf,
840                relocation: reloc,
841                relax: relax_info,
842            });
843        }
844        "pop" => {
845            encode_pop_32(&mut buf, ops, instr)?;
846            return Ok(EncodedInstr {
847                bytes: buf,
848                relocation: reloc,
849                relax: relax_info,
850            });
851        }
852        "inc" | "dec" => {
853            // In 32-bit mode, INC r16/r32 (0x40+rd) and DEC r16/r32 (0x48+rd)
854            // are single-byte short forms. These opcodes are repurposed as
855            // REX prefixes in 64-bit mode, so this path is x86-32 only.
856            // For 8-bit regs and memory operands, fall through to the
857            // generic encoder which uses the 0xFE/0xFF ModR/M form.
858            if let [Operand::Register(reg)] = ops.as_slice() {
859                let size = reg_size(*reg);
860                if size == 16 || size == 32 {
861                    if size == 16 {
862                        buf.push(0x66);
863                    }
864                    let base = if mnemonic == "inc" { 0x40 } else { 0x48 };
865                    buf.push(base + reg.base_code());
866                    return Ok(EncodedInstr {
867                        bytes: buf,
868                        relocation: reloc,
869                        relax: relax_info,
870                    });
871                }
872            }
873            // 8-bit / memory forms — fall through to generic dispatch
874        }
875        _ => {}
876    }
877
878    // Everything else: reuse the x86-64 dispatch.
879    // Since we've validated no 64-bit/extended registers are present,
880    // the encoder functions will not emit REX prefixes — producing
881    // valid 32-bit code.
882    match crate::x86::dispatch_x86_64(mnemonic, &mut buf, ops, instr, &mut reloc, &mut relax_info) {
883        Some(Ok(())) => {}
884        Some(Err(e)) => return Err(e),
885        None => {
886            return Err(AsmError::UnknownMnemonic {
887                mnemonic: String::from(mnemonic),
888                arch: crate::error::ArchName::X86,
889                span: instr.span,
890            });
891        }
892    }
893
894    validate_lock_prefix(instr, ops)?;
895    propagate_disp_label_reloc(&buf, ops, prefix_len, &mut reloc);
896
897    Ok(EncodedInstr {
898        bytes: buf,
899        relocation: reloc,
900        relax: relax_info,
901    })
902}
903
904// ─── x86-16 encoder (real mode) ─────────────────────────────
905
906/// x86-16 (real mode) encoder.
907///
908/// In 16-bit mode the default operand size is 16 bits and the default
909/// address size is 16 bits.  The `0x66` prefix switches operand size to
910/// 32 bits, and `0x67` switches address size to 32 bits — the reverse
911/// of 32-bit protected mode.
912///
913/// Implementation: reuse the 32-bit encoder (which handles push/pop/inc/dec
914/// short forms, segment registers, etc.) and then toggle the `0x66` prefix:
915///
916///   - If `0x66` is present → remove it (16-bit is now the default)
917///   - If `0x66` is absent AND the instruction uses 32-bit GPRs → add it
918#[cfg(feature = "x86")]
919pub fn encode_instruction_16(instr: &Instruction) -> Result<EncodedInstr, AsmError> {
920    // Step 1: Encode using the 32-bit encoder (handles push/pop/inc/dec
921    // short forms, segment registers, LOCK prefix validation, etc.)
922    let mut result = encode_x86_32(instr)?;
923
924    // Step 2: Toggle the 0x66 operand-size prefix.
925    // The 32-bit encoder adds 0x66 for 16-bit operands (non-default in
926    // 32-bit mode).  In 16-bit mode the semantics reverse: 16-bit is
927    // default (remove prefix) and 32-bit needs the prefix (add it).
928    toggle_operand_size_prefix_16(&mut result.bytes, &instr.operands, &mut result.relocation);
929
930    Ok(result)
931}
932
933/// Toggle the 0x66 operand-size prefix for 16-bit mode encoding.
934///
935/// In 16-bit mode: remove 0x66 if present (16-bit is now default),
936/// or add 0x66 if absent and instruction uses 32-bit registers.
937#[cfg(feature = "x86")]
938fn toggle_operand_size_prefix_16(
939    buf: &mut InstrBytes,
940    ops: &OperandList,
941    reloc: &mut Option<Relocation>,
942) {
943    // Scan for a 0x66 byte in the prefix region (before the first non-prefix byte).
944    let mut found_66_at = None;
945    for i in 0..buf.len() {
946        let b = buf[i];
947        if b == 0x66 {
948            found_66_at = Some(i);
949            break;
950        }
951        // Stop at the first non-prefix byte (0x67 is addr-size, still a prefix)
952        if !is_legacy_prefix(b) && b != 0x67 {
953            break;
954        }
955    }
956
957    if let Some(pos) = found_66_at {
958        // Remove 0x66 — in 16-bit mode, 16-bit operands don't need it
959        buf.remove(pos);
960        if let Some(ref mut r) = reloc {
961            if r.offset > pos {
962                r.offset -= 1;
963            }
964        }
965    } else {
966        // Check if the instruction uses 32-bit GP registers (not EIP).
967        // If so, add 0x66 — in 16-bit mode, 32-bit operands need the override.
968        let has_32bit_gpr = ops.iter().any(|op| {
969            if let Operand::Register(r) = op {
970                r.size_bits() == 32 && !matches!(r, Register::Eip)
971            } else {
972                false
973            }
974        });
975
976        // Also check for DWORD memory size annotation (e.g., mov dword [bx], 1).
977        let has_dword_mem = ops.iter().any(
978            |op| matches!(op, Operand::Memory(m) if m.size == Some(crate::ir::OperandSize::Dword)),
979        );
980
981        if has_32bit_gpr || has_dword_mem {
982            // Insert 0x66 after any existing prefixes
983            let mut insert_pos = 0;
984            for i in 0..buf.len() {
985                let b = buf[i];
986                if is_legacy_prefix(b) || b == 0x67 {
987                    insert_pos = i + 1;
988                } else {
989                    break;
990                }
991            }
992            buf.insert(insert_pos, 0x66);
993            if let Some(ref mut r) = reloc {
994                if r.offset >= insert_pos {
995                    r.offset += 1;
996                }
997            }
998        }
999    }
1000}
1001
1002/// Check if a byte is a legacy x86 prefix.
1003#[cfg(feature = "x86")]
1004#[inline]
1005fn is_legacy_prefix(b: u8) -> bool {
1006    matches!(
1007        b,
1008        0xF0 | 0xF2 | 0xF3 | 0x26 | 0x2E | 0x36 | 0x3E | 0x64 | 0x65 | 0x66 | 0x67
1009    )
1010}
1011#[cfg(feature = "x86")]
1012fn encode_push_32(
1013    buf: &mut InstrBytes,
1014    ops: &OperandList,
1015    instr: &Instruction,
1016    reloc: &mut Option<Relocation>,
1017) -> Result<(), AsmError> {
1018    if ops.len() != 1 {
1019        return Err(invalid_operands("push", "expected 1 operand", instr.span));
1020    }
1021    match &ops[0] {
1022        Operand::Register(reg) => {
1023            let size = reg.size_bits();
1024            // Segment register push — all 6 valid in 32-bit mode
1025            match reg {
1026                Register::Es => {
1027                    buf.push(0x06);
1028                    return Ok(());
1029                }
1030                Register::Cs => {
1031                    buf.push(0x0E);
1032                    return Ok(());
1033                }
1034                Register::Ss => {
1035                    buf.push(0x16);
1036                    return Ok(());
1037                }
1038                Register::Ds => {
1039                    buf.push(0x1E);
1040                    return Ok(());
1041                }
1042                Register::Fs => {
1043                    buf.push(0x0F);
1044                    buf.push(0xA0);
1045                    return Ok(());
1046                }
1047                Register::Gs => {
1048                    buf.push(0x0F);
1049                    buf.push(0xA8);
1050                    return Ok(());
1051                }
1052                _ => {}
1053            }
1054            if size == 8 {
1055                return Err(invalid_operands(
1056                    "push",
1057                    "push does not accept 8-bit registers",
1058                    instr.span,
1059                ));
1060            }
1061            if size == 16 {
1062                buf.push(0x66); // operand size override for 16-bit
1063            }
1064            buf.push(0x50 + reg.base_code());
1065        }
1066        Operand::Immediate(imm) => {
1067            if *imm >= i8::MIN as i128 && *imm <= i8::MAX as i128 {
1068                buf.push(0x6A);
1069                buf.push(*imm as i8 as u8);
1070            } else if *imm >= i32::MIN as i128 && *imm <= u32::MAX as i128 {
1071                buf.push(0x68);
1072                buf.extend_from_slice(&(*imm as i32).to_le_bytes());
1073            } else {
1074                return Err(invalid_operands(
1075                    "push",
1076                    "immediate value out of range for push (must fit in 32 bits)",
1077                    instr.span,
1078                ));
1079            }
1080        }
1081        Operand::Memory(mem) => {
1082            let msize = mem.size.map(|s| s.bits()).unwrap_or(32);
1083            if msize == 16 {
1084                buf.push(0x66);
1085            }
1086            buf.push(0xFF);
1087            emit_mem_modrm(buf, 6, mem);
1088        }
1089        op @ (Operand::Label(_) | Operand::Expression(_)) => {
1090            let Some((label, addend)) = extract_label(op) else {
1091                return Err(invalid_operands("push", "unsupported operand", instr.span));
1092            };
1093            buf.push(0x68);
1094            let reloc_off = buf.len();
1095            buf.extend_from_slice(&0i32.to_le_bytes());
1096            *reloc = Some(Relocation {
1097                offset: reloc_off,
1098                size: 4,
1099                label: alloc::rc::Rc::from(label),
1100                kind: RelocKind::Absolute,
1101                addend,
1102                trailing_bytes: 0,
1103            });
1104        }
1105        _ => return Err(invalid_operands("push", "unsupported operand", instr.span)),
1106    }
1107    Ok(())
1108}
1109
1110/// x86-32 pop: accepts 32-bit and 16-bit GP registers + FS/GS/DS/ES/SS.
1111#[cfg(feature = "x86")]
1112fn encode_pop_32(
1113    buf: &mut InstrBytes,
1114    ops: &OperandList,
1115    instr: &Instruction,
1116) -> Result<(), AsmError> {
1117    if ops.len() != 1 {
1118        return Err(invalid_operands("pop", "expected 1 operand", instr.span));
1119    }
1120    match &ops[0] {
1121        Operand::Register(reg) => {
1122            let size = reg.size_bits();
1123            match reg {
1124                Register::Es => {
1125                    buf.push(0x07);
1126                    return Ok(());
1127                }
1128                Register::Ss => {
1129                    buf.push(0x17);
1130                    return Ok(());
1131                }
1132                Register::Ds => {
1133                    buf.push(0x1F);
1134                    return Ok(());
1135                }
1136                Register::Fs => {
1137                    buf.push(0x0F);
1138                    buf.push(0xA1);
1139                    return Ok(());
1140                }
1141                Register::Gs => {
1142                    buf.push(0x0F);
1143                    buf.push(0xA9);
1144                    return Ok(());
1145                }
1146                Register::Cs => {
1147                    return Err(invalid_operands("pop", "cannot pop into CS", instr.span));
1148                }
1149                _ => {}
1150            }
1151            if size == 8 {
1152                return Err(invalid_operands(
1153                    "pop",
1154                    "pop does not accept 8-bit registers",
1155                    instr.span,
1156                ));
1157            }
1158            if size == 16 {
1159                buf.push(0x66);
1160            }
1161            buf.push(0x58 + reg.base_code());
1162        }
1163        Operand::Memory(mem) => {
1164            let msize = mem.size.map(|s| s.bits()).unwrap_or(32);
1165            if msize == 16 {
1166                buf.push(0x66);
1167            }
1168            buf.push(0x8F);
1169            emit_mem_modrm(buf, 0, mem);
1170        }
1171        _ => return Err(invalid_operands("pop", "unsupported operand", instr.span)),
1172    }
1173    Ok(())
1174}
1175
1176// ─── REX / ModR/M / SIB helpers ──────────────────────────────
1177
1178/// Build a REX prefix byte.
1179#[inline]
1180#[cfg(any(feature = "x86", feature = "x86_64"))]
1181pub(crate) fn rex(w: bool, r: bool, x: bool, b: bool) -> u8 {
1182    let mut val: u8 = 0x40;
1183    if w {
1184        val |= 0x08;
1185    }
1186    if r {
1187        val |= 0x04;
1188    }
1189    if x {
1190        val |= 0x02;
1191    }
1192    if b {
1193        val |= 0x01;
1194    }
1195    val
1196}
1197
1198/// Whether a REX prefix with at least one flag is needed.
1199#[inline]
1200#[cfg(any(feature = "x86", feature = "x86_64"))]
1201pub(crate) fn needs_rex(w: bool, r: bool, x: bool, b: bool) -> bool {
1202    w || r || x || b
1203}
1204
1205/// Build ModR/M byte.
1206#[inline]
1207#[cfg(any(feature = "x86", feature = "x86_64"))]
1208pub(crate) fn modrm(mod_: u8, reg: u8, rm: u8) -> u8 {
1209    (mod_ << 6) | ((reg & 7) << 3) | (rm & 7)
1210}
1211
1212/// Build SIB byte.
1213#[inline]
1214#[cfg(any(feature = "x86", feature = "x86_64"))]
1215fn sib(scale: u8, index: u8, base: u8) -> u8 {
1216    let ss = match scale {
1217        1 => 0,
1218        2 => 1,
1219        4 => 2,
1220        8 => 3,
1221        _ => 0,
1222    };
1223    (ss << 6) | ((index & 7) << 3) | (base & 7)
1224}
1225
1226/// Get the operand size from a register as u8 (for GP registers only, panics for vector).
1227#[inline]
1228#[cfg(any(feature = "x86", feature = "x86_64"))]
1229pub(crate) fn reg_size(reg: Register) -> u8 {
1230    let s = reg.size_bits();
1231    debug_assert!(s <= 128, "reg_size() used on vector register wider than u8");
1232    s as u8
1233}
1234
1235/// Check if using a high-byte register (AH, BH, CH, DH) together with any operand
1236/// that requires a REX prefix.  On x86-64, a REX byte changes the meaning of
1237/// register codes 4-7 from AH/CH/DH/BH to SPL/BPL/SIL/DIL, so the two are
1238/// incompatible.  Returns an error if the conflict is detected.
1239#[cfg(any(feature = "x86", feature = "x86_64"))]
1240pub(crate) fn check_high_byte_rex_conflict(
1241    regs: &[Register],
1242    span: crate::error::Span,
1243) -> Result<(), AsmError> {
1244    let has_high = regs.iter().any(|r| r.is_high_byte());
1245    let needs_rex = regs
1246        .iter()
1247        .any(|r| r.is_extended() || r.requires_rex_for_byte() || r.size_bits() == 64);
1248    if has_high && needs_rex {
1249        return Err(AsmError::InvalidOperands {
1250            detail: String::from(
1251                "high-byte registers (AH, BH, CH, DH) cannot be used with REX-requiring operands (64-bit regs, extended regs R8-R15, SPL/BPL/SIL/DIL)"
1252            ),
1253            span,
1254        });
1255    }
1256    Ok(())
1257}
1258
1259/// Emit REX prefix if needed, then opcode + ModR/M for reg,reg.
1260#[inline]
1261#[cfg(any(feature = "x86", feature = "x86_64"))]
1262fn emit_rr(
1263    buf: &mut InstrBytes,
1264    opcode: &[u8],
1265    dst: Register,
1266    src: Register,
1267    span: crate::error::Span,
1268) -> Result<(), AsmError> {
1269    let size = reg_size(dst);
1270    let w = size == 64;
1271    let r = src.is_extended();
1272    let b = dst.is_extended();
1273
1274    // Validate high-byte / REX conflict
1275    if size == 8 {
1276        check_high_byte_rex_conflict(&[dst, src], span)?;
1277    }
1278
1279    // 16-bit operand size prefix
1280    if size == 16 {
1281        buf.push(0x66);
1282    }
1283
1284    // REX
1285    let need_rex =
1286        needs_rex(w, r, false, b) || dst.requires_rex_for_byte() || src.requires_rex_for_byte();
1287    if need_rex {
1288        buf.push(rex(w, r, false, b));
1289    }
1290
1291    buf.extend_from_slice(opcode);
1292    buf.push(modrm(0b11, src.base_code(), dst.base_code()));
1293    Ok(())
1294}
1295
1296/// If the memory operand has a `disp_label`, create a relocation entry.
1297#[inline]
1298#[cfg(any(feature = "x86", feature = "x86_64"))]
1299pub(crate) fn set_mem_reloc(
1300    reloc: &mut Option<Relocation>,
1301    mem: &MemoryOperand,
1302    disp_offset: Option<usize>,
1303    buf_len: usize,
1304) {
1305    if let Some(ref label) = mem.disp_label {
1306        *reloc = Some(Relocation {
1307            offset: disp_offset.unwrap_or(buf_len),
1308            size: 4,
1309            label: alloc::rc::Rc::from(&**label),
1310            kind: if mem.base == Some(Register::Rip) {
1311                RelocKind::X86Relative
1312            } else {
1313                RelocKind::Absolute
1314            },
1315            addend: mem.disp,
1316            trailing_bytes: 0, // updated by encode_x86_64 after instruction is complete
1317        });
1318    }
1319}
1320
1321/// Emit ModR/M + SIB + displacement for a memory operand.
1322/// Returns the offset where a relocation displacement starts (if any).
1323#[cfg(any(feature = "x86", feature = "x86_64"))]
1324pub(crate) fn emit_mem_modrm(
1325    buf: &mut InstrBytes,
1326    reg_field: u8,
1327    mem: &MemoryOperand,
1328) -> Option<usize> {
1329    // NOTE: Segment override prefix is emitted in encode_x86_64 BEFORE the
1330    // REX/opcode bytes. Do NOT emit it here (after the opcode).
1331
1332    let base = mem.base;
1333    let index = mem.index;
1334    let disp = mem.disp;
1335
1336    // RIP-relative addressing: [rip + disp32]
1337    if base == Some(Register::Rip) && index.is_none() {
1338        buf.push(modrm(0b00, reg_field, 0b101));
1339        let reloc_offset = buf.len();
1340        buf.extend_from_slice(&(disp as i32).to_le_bytes());
1341        return Some(reloc_offset);
1342    }
1343
1344    // Absolute address / displacement only: [disp32]
1345    if base.is_none() && index.is_none() {
1346        // In 64-bit mode, we need SIB to encode absolute address
1347        buf.push(modrm(0b00, reg_field, 0b100));
1348        buf.push(sib(1, 0b100, 0b101));
1349        let reloc_offset = buf.len();
1350        buf.extend_from_slice(&(disp as i32).to_le_bytes());
1351        return Some(reloc_offset);
1352    }
1353
1354    // SIB index-only: [index*scale + disp32] — no base register.
1355    // Must use mod=00, base=101 (means "no base, disp32 follows").
1356    if let (None, Some(idx_reg)) = (base, index) {
1357        buf.push(modrm(0b00, reg_field, 0b100));
1358        buf.push(sib(mem.scale, idx_reg.base_code(), 0b101));
1359        let reloc_offset = buf.len();
1360        buf.extend_from_slice(&(disp as i32).to_le_bytes());
1361        return Some(reloc_offset);
1362    }
1363
1364    // SAFETY: At this point base is guaranteed Some — displacement-only
1365    // (mod=00, r/m=5) and index-only (SIB with base=5) both returned early
1366    // above.  Every remaining path uses a base register.
1367    let base = base?;
1368
1369    // Determine if we need SIB
1370    let need_sib = index.is_some() || base.base_code() == 4; // RSP/R12 need SIB
1371
1372    let (mod_bits, disp_size) = if disp == 0 && base.base_code() != 5 {
1373        // mod=00, no displacement (unless base is RBP/R13)
1374        (0b00, 0)
1375    } else if (-128..=127).contains(&disp) {
1376        (0b01, 1)
1377    } else {
1378        (0b10, 4)
1379    };
1380
1381    if need_sib {
1382        let idx_reg = index.unwrap_or(Register::Rsp); // 0b100 = no index
1383
1384        buf.push(modrm(mod_bits, reg_field, 0b100));
1385        buf.push(sib(mem.scale, idx_reg.base_code(), base.base_code()));
1386    } else {
1387        buf.push(modrm(mod_bits, reg_field, base.base_code()));
1388    }
1389
1390    let reloc_offset = if disp_size > 0 { Some(buf.len()) } else { None };
1391
1392    match disp_size {
1393        1 => buf.push(disp as i8 as u8),
1394        4 => buf.extend_from_slice(&(disp as i32).to_le_bytes()),
1395        _ => {}
1396    }
1397
1398    reloc_offset
1399}
1400
1401/// Emit REX prefix for a reg+mem operation.
1402#[inline]
1403#[cfg(any(feature = "x86", feature = "x86_64"))]
1404pub(crate) fn emit_rex_for_reg_mem(
1405    buf: &mut InstrBytes,
1406    reg: Register,
1407    mem: &MemoryOperand,
1408) -> Result<(), AsmError> {
1409    let w = reg.size_bits() == 64;
1410    let r = reg.is_extended();
1411    let x = mem.index.is_some_and(|r| r.is_extended());
1412    let b = mem.base.is_some_and(|r| r.is_extended());
1413
1414    if reg.size_bits() == 16 {
1415        buf.push(0x66);
1416    }
1417
1418    // Validate high-byte / REX conflict: AH/BH/CH/DH + extended base/index
1419    if reg.size_bits() == 8 && reg.is_high_byte() {
1420        let mem_needs_rex = x || b;
1421        if mem_needs_rex {
1422            return Err(AsmError::InvalidOperands {
1423                detail: String::from(
1424                    "high-byte registers (AH, BH, CH, DH) cannot be used with memory operands requiring REX prefix (R8-R15 base/index)"
1425                ),
1426                span: crate::error::Span { line: 0, col: 0, offset: 0, len: 0 },
1427            });
1428        }
1429    }
1430
1431    let need = needs_rex(w, r, x, b) || reg.requires_rex_for_byte();
1432    if need {
1433        buf.push(rex(w, r, x, b));
1434    }
1435    Ok(())
1436}
1437
1438/// Emit REX prefix for a /digit+mem operation (no separate reg operand).
1439#[inline]
1440#[cfg(any(feature = "x86", feature = "x86_64"))]
1441pub(crate) fn emit_rex_for_digit_mem(buf: &mut InstrBytes, size: u8, mem: &MemoryOperand) {
1442    let w = size == 64;
1443    let x = mem.index.is_some_and(|r| r.is_extended());
1444    let b = mem.base.is_some_and(|r| r.is_extended());
1445
1446    if size == 16 {
1447        buf.push(0x66);
1448    }
1449    if needs_rex(w, false, x, b) {
1450        buf.push(rex(w, false, x, b));
1451    }
1452}
1453
1454// ─── Instruction encoders ─────────────────────────────────────
1455
1456#[cfg(any(feature = "x86", feature = "x86_64"))]
1457pub(crate) fn encode_nop(
1458    buf: &mut InstrBytes,
1459    ops: &OperandList,
1460    instr: &Instruction,
1461) -> Result<(), AsmError> {
1462    if ops.is_empty() {
1463        buf.push(0x90);
1464        Ok(())
1465    } else {
1466        Err(AsmError::InvalidOperands {
1467            detail: String::from("nop takes no operands"),
1468            span: instr.span,
1469        })
1470    }
1471}
1472
1473#[cfg(any(feature = "x86", feature = "x86_64"))]
1474pub(crate) fn encode_multibyte_nop(buf: &mut InstrBytes, mnemonic: &str) -> Result<(), AsmError> {
1475    let n: usize = mnemonic[3..].parse().unwrap_or(1);
1476    // Intel recommended multi-byte NOP sequences
1477    match n {
1478        2 => buf.extend_from_slice(&[0x66, 0x90]),
1479        3 => buf.extend_from_slice(&[0x0F, 0x1F, 0x00]),
1480        4 => buf.extend_from_slice(&[0x0F, 0x1F, 0x40, 0x00]),
1481        5 => buf.extend_from_slice(&[0x0F, 0x1F, 0x44, 0x00, 0x00]),
1482        6 => buf.extend_from_slice(&[0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00]),
1483        7 => buf.extend_from_slice(&[0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00]),
1484        8 => buf.extend_from_slice(&[0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00]),
1485        9 => buf.extend_from_slice(&[0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00]),
1486        _ => buf.push(0x90),
1487    }
1488    Ok(())
1489}
1490
1491#[cfg(any(feature = "x86", feature = "x86_64"))]
1492pub(crate) fn encode_int(
1493    buf: &mut InstrBytes,
1494    ops: &OperandList,
1495    instr: &Instruction,
1496) -> Result<(), AsmError> {
1497    if ops.len() != 1 {
1498        return Err(invalid_operands(
1499            "int",
1500            "expected one immediate operand",
1501            instr.span,
1502        ));
1503    }
1504    match &ops[0] {
1505        Operand::Immediate(3) => buf.push(0xCC), // INT 3 → single byte
1506        Operand::Immediate(n) if *n >= 0 && *n <= 255 => {
1507            buf.push(0xCD);
1508            buf.push(*n as u8);
1509        }
1510        _ => {
1511            return Err(invalid_operands(
1512                "int",
1513                "expected immediate 0-255",
1514                instr.span,
1515            ))
1516        }
1517    }
1518    Ok(())
1519}
1520
1521#[cfg(any(feature = "x86", feature = "x86_64"))]
1522pub(crate) fn encode_ret(
1523    buf: &mut InstrBytes,
1524    ops: &OperandList,
1525    instr: &Instruction,
1526) -> Result<(), AsmError> {
1527    if ops.is_empty() {
1528        buf.push(0xC3); // Near return
1529    } else if ops.len() == 1 {
1530        match &ops[0] {
1531            Operand::Immediate(n) if *n >= 0 && *n <= 65535 => {
1532                buf.push(0xC2); // Near return with stack pop
1533                buf.extend_from_slice(&(*n as u16).to_le_bytes());
1534            }
1535            _ => {
1536                return Err(invalid_operands(
1537                    "ret",
1538                    "expected immediate 0-65535",
1539                    instr.span,
1540                ))
1541            }
1542        }
1543    } else {
1544        return Err(invalid_operands(
1545            "ret",
1546            "expected 0 or 1 operands",
1547            instr.span,
1548        ));
1549    }
1550    Ok(())
1551}
1552
1553#[cfg(any(feature = "x86", feature = "x86_64"))]
1554pub(crate) fn encode_retf(
1555    buf: &mut InstrBytes,
1556    ops: &OperandList,
1557    instr: &Instruction,
1558) -> Result<(), AsmError> {
1559    if ops.is_empty() {
1560        buf.push(0xCB); // Far return
1561    } else if ops.len() == 1 {
1562        match &ops[0] {
1563            Operand::Immediate(n) if *n >= 0 && *n <= 65535 => {
1564                buf.push(0xCA); // Far return with stack pop
1565                buf.extend_from_slice(&(*n as u16).to_le_bytes());
1566            }
1567            _ => {
1568                return Err(invalid_operands(
1569                    "retf",
1570                    "expected immediate 0-65535",
1571                    instr.span,
1572                ))
1573            }
1574        }
1575    } else {
1576        return Err(invalid_operands(
1577            "retf",
1578            "expected 0 or 1 operands",
1579            instr.span,
1580        ));
1581    }
1582    Ok(())
1583}
1584
1585#[cfg(any(feature = "x86", feature = "x86_64"))]
1586pub(crate) fn encode_mov(
1587    buf: &mut InstrBytes,
1588    ops: &OperandList,
1589    instr: &Instruction,
1590    reloc: &mut Option<Relocation>,
1591) -> Result<(), AsmError> {
1592    if ops.len() != 2 {
1593        return Err(invalid_operands("mov", "expected 2 operands", instr.span));
1594    }
1595
1596    match (&ops[0], &ops[1]) {
1597        // mov r, r
1598        (Operand::Register(dst), Operand::Register(src)) => {
1599            let size = reg_size(*dst);
1600            if size != reg_size(*src) {
1601                return Err(invalid_operands("mov", "operand size mismatch", instr.span));
1602            }
1603            let opcode = if size == 8 {
1604                &[0x88u8] as &[u8]
1605            } else {
1606                &[0x89u8]
1607            };
1608            emit_rr(buf, opcode, *dst, *src, instr.span)?;
1609        }
1610
1611        // mov r, imm
1612        (Operand::Register(dst), Operand::Immediate(imm)) => {
1613            encode_mov_reg_imm(buf, *dst, *imm, instr.span)?;
1614        }
1615
1616        // mov r, label  /  mov r, label+offset
1617        (Operand::Register(dst), op @ (Operand::Label(_) | Operand::Expression(_))) => {
1618            let Some((label, addend)) = extract_label(op) else {
1619                return Err(invalid_operands(
1620                    "mov",
1621                    "expected label expression",
1622                    instr.span,
1623                ));
1624            };
1625            // movabs r64, imm64 with relocation
1626            let size = reg_size(*dst);
1627            if size == 64 {
1628                let w = true;
1629                let b = dst.is_extended();
1630                buf.push(rex(w, false, false, b));
1631                buf.push(0xB8 + dst.base_code());
1632                let reloc_off = buf.len();
1633                buf.extend_from_slice(&0u64.to_le_bytes());
1634                *reloc = Some(Relocation {
1635                    offset: reloc_off,
1636                    size: 8,
1637                    label: alloc::rc::Rc::from(label),
1638                    kind: RelocKind::Absolute,
1639                    addend,
1640                    trailing_bytes: 0,
1641                });
1642            } else {
1643                return Err(invalid_operands(
1644                    "mov",
1645                    "label operand requires 64-bit register",
1646                    instr.span,
1647                ));
1648            }
1649        }
1650
1651        // mov r, [mem]
1652        (Operand::Register(dst), Operand::Memory(mem)) => {
1653            let size = reg_size(*dst);
1654            let opcode: u8 = if size == 8 { 0x8A } else { 0x8B };
1655            emit_rex_for_reg_mem(buf, *dst, mem)?;
1656            buf.push(opcode);
1657            let reloc_off = emit_mem_modrm(buf, dst.base_code(), mem);
1658            if let Some(ref label) = mem.disp_label {
1659                *reloc = Some(Relocation {
1660                    offset: reloc_off.unwrap_or(buf.len()),
1661                    size: 4,
1662                    label: alloc::rc::Rc::from(&**label),
1663                    kind: if mem.base == Some(Register::Rip) {
1664                        RelocKind::X86Relative
1665                    } else {
1666                        RelocKind::Absolute
1667                    },
1668                    addend: mem.disp,
1669                    trailing_bytes: 0,
1670                });
1671            }
1672        }
1673
1674        // mov [mem], r
1675        (Operand::Memory(mem), Operand::Register(src)) => {
1676            let size = reg_size(*src);
1677            let opcode: u8 = if size == 8 { 0x88 } else { 0x89 };
1678            emit_rex_for_reg_mem(buf, *src, mem)?;
1679            buf.push(opcode);
1680            let disp_off = emit_mem_modrm(buf, src.base_code(), mem);
1681            set_mem_reloc(reloc, mem, disp_off, buf.len());
1682        }
1683
1684        // mov [mem], imm
1685        (Operand::Memory(mem), Operand::Immediate(imm)) => {
1686            let size = instr
1687                .size_hint
1688                .map_or(mem.size.map_or(32u8, |s| s.bits() as u8), |s| {
1689                    s.bits() as u8
1690                });
1691            // mov r/m64, imm32 sign-extends — reject values that don't fit
1692            if size == 64 {
1693                let v = *imm;
1694                if v > i64::from(i32::MAX) as i128 || v < i64::from(i32::MIN) as i128 {
1695                    return Err(invalid_operands(
1696                        "mov",
1697                        "immediate too large for mov [mem], imm (max sign-extended imm32); use mov reg, imm64 + mov [mem], reg",
1698                        instr.span,
1699                    ));
1700                }
1701            }
1702            let opcode: u8 = if size == 8 { 0xC6 } else { 0xC7 };
1703            emit_rex_for_digit_mem(buf, size, mem);
1704            buf.push(opcode);
1705            let disp_off = emit_mem_modrm(buf, 0, mem); // /0
1706            set_mem_reloc(reloc, mem, disp_off, buf.len());
1707            emit_imm(buf, *imm, if size > 32 { 32 } else { size }); // max imm32 for mov r/m, imm
1708        }
1709
1710        _ => {
1711            return Err(invalid_operands(
1712                "mov",
1713                "unsupported operand combination",
1714                instr.span,
1715            ))
1716        }
1717    }
1718    Ok(())
1719}
1720
1721#[cfg(any(feature = "x86", feature = "x86_64"))]
1722pub(crate) fn encode_mov_reg_imm(
1723    buf: &mut InstrBytes,
1724    dst: Register,
1725    imm: i128,
1726    span: Span,
1727) -> Result<(), AsmError> {
1728    let size = reg_size(dst);
1729
1730    match size {
1731        8 => {
1732            let b = dst.is_extended();
1733            let need = b || dst.requires_rex_for_byte();
1734            if need {
1735                buf.push(rex(false, false, false, b));
1736            }
1737            buf.push(0xB0 + dst.base_code());
1738            buf.push(imm as u8);
1739        }
1740        16 => {
1741            buf.push(0x66);
1742            let b = dst.is_extended();
1743            if b {
1744                buf.push(rex(false, false, false, b));
1745            }
1746            buf.push(0xB8 + dst.base_code());
1747            buf.extend_from_slice(&(imm as u16).to_le_bytes());
1748        }
1749        32 => {
1750            let b = dst.is_extended();
1751            if b {
1752                buf.push(rex(false, false, false, b));
1753            }
1754            buf.push(0xB8 + dst.base_code());
1755            buf.extend_from_slice(&(imm as u32).to_le_bytes());
1756        }
1757        64 => {
1758            // Check if we can use shorter encoding
1759            let b = dst.is_extended();
1760            if imm >= 0 && imm <= u32::MAX as i128 {
1761                // mov r32, imm32 (zero-extends to r64)
1762                if b {
1763                    buf.push(rex(false, false, false, true));
1764                }
1765                buf.push(0xB8 + dst.base_code());
1766                buf.extend_from_slice(&(imm as u32).to_le_bytes());
1767            } else if imm >= i32::MIN as i128 && imm <= i32::MAX as i128 {
1768                // mov r64, sign-extended imm32
1769                buf.push(rex(true, false, false, b));
1770                buf.push(0xC7);
1771                buf.push(modrm(0b11, 0, dst.base_code()));
1772                buf.extend_from_slice(&(imm as i32).to_le_bytes());
1773            } else {
1774                // movabs r64, imm64
1775                buf.push(rex(true, false, false, b));
1776                buf.push(0xB8 + dst.base_code());
1777                buf.extend_from_slice(&(imm as u64).to_le_bytes());
1778            }
1779        }
1780        _ => {
1781            return Err(AsmError::InvalidOperands {
1782                detail: String::from("unsupported register size for mov immediate"),
1783                span,
1784            });
1785        }
1786    }
1787    Ok(())
1788}
1789
1790#[cfg(any(feature = "x86", feature = "x86_64"))]
1791pub(crate) fn encode_lea(
1792    buf: &mut InstrBytes,
1793    ops: &OperandList,
1794    instr: &Instruction,
1795    reloc: &mut Option<Relocation>,
1796) -> Result<(), AsmError> {
1797    if ops.len() != 2 {
1798        return Err(invalid_operands("lea", "expected 2 operands", instr.span));
1799    }
1800    match (&ops[0], &ops[1]) {
1801        (Operand::Register(dst), Operand::Memory(mem)) => {
1802            emit_rex_for_reg_mem(buf, *dst, mem)?;
1803            buf.push(0x8D);
1804            let reloc_off = emit_mem_modrm(buf, dst.base_code(), mem);
1805            if let Some(ref label) = mem.disp_label {
1806                *reloc = Some(Relocation {
1807                    offset: reloc_off.unwrap_or(buf.len()),
1808                    size: 4,
1809                    label: alloc::rc::Rc::from(&**label),
1810                    kind: if mem.base == Some(Register::Rip) {
1811                        RelocKind::X86Relative
1812                    } else {
1813                        RelocKind::Absolute
1814                    },
1815                    addend: mem.disp,
1816                    trailing_bytes: 0,
1817                });
1818            }
1819        }
1820        _ => return Err(invalid_operands("lea", "expected reg, [mem]", instr.span)),
1821    }
1822    Ok(())
1823}
1824
1825#[cfg(any(feature = "x86", feature = "x86_64"))]
1826pub(crate) fn encode_push(
1827    buf: &mut InstrBytes,
1828    ops: &OperandList,
1829    instr: &Instruction,
1830    reloc: &mut Option<Relocation>,
1831) -> Result<(), AsmError> {
1832    if ops.len() != 1 {
1833        return Err(invalid_operands("push", "expected 1 operand", instr.span));
1834    }
1835    match &ops[0] {
1836        Operand::Register(reg) => {
1837            let size = reg.size_bits();
1838            // Segment register push (only FS and GS in 64-bit mode)
1839            match reg {
1840                Register::Fs => {
1841                    buf.push(0x0F);
1842                    buf.push(0xA0);
1843                    return Ok(());
1844                }
1845                Register::Gs => {
1846                    buf.push(0x0F);
1847                    buf.push(0xA8);
1848                    return Ok(());
1849                }
1850                Register::Cs | Register::Ds | Register::Es | Register::Ss => {
1851                    return Err(invalid_operands(
1852                        "push",
1853                        "CS/DS/ES/SS push not valid in 64-bit mode",
1854                        instr.span,
1855                    ));
1856                }
1857                _ => {}
1858            }
1859            if size == 8 || size == 32 {
1860                return Err(invalid_operands(
1861                    "push",
1862                    "push requires 64-bit or 16-bit register in 64-bit mode",
1863                    instr.span,
1864                ));
1865            }
1866            let b = reg.is_extended();
1867            if size == 16 {
1868                buf.push(0x66);
1869            }
1870            if b {
1871                buf.push(rex(false, false, false, true));
1872            }
1873            buf.push(0x50 + reg.base_code());
1874        }
1875        Operand::Immediate(imm) => {
1876            if *imm >= i8::MIN as i128 && *imm <= i8::MAX as i128 {
1877                buf.push(0x6A);
1878                buf.push(*imm as i8 as u8);
1879            } else if *imm >= i32::MIN as i128 && *imm <= u32::MAX as i128 {
1880                buf.push(0x68);
1881                buf.extend_from_slice(&(*imm as i32).to_le_bytes());
1882            } else {
1883                return Err(invalid_operands(
1884                    "push",
1885                    "immediate value out of range for push (must fit in 32 bits)",
1886                    instr.span,
1887                ));
1888            }
1889        }
1890        Operand::Memory(mem) => {
1891            // push defaults to 64-bit operand size — REX.W is redundant.
1892            // Still need REX.B/X for extended base/index registers.
1893            emit_rex_for_digit_mem(buf, 0, mem);
1894            buf.push(0xFF);
1895            emit_mem_modrm(buf, 6, mem); // /6
1896        }
1897        op @ (Operand::Label(_) | Operand::Expression(_)) => {
1898            let Some((label, addend)) = extract_label(op) else {
1899                return Err(invalid_operands("push", "unsupported operand", instr.span));
1900            };
1901            // push imm32 with relocation
1902            buf.push(0x68);
1903            let reloc_off = buf.len();
1904            buf.extend_from_slice(&0i32.to_le_bytes());
1905            *reloc = Some(Relocation {
1906                offset: reloc_off,
1907                size: 4,
1908                label: alloc::rc::Rc::from(label),
1909                kind: RelocKind::Absolute,
1910                addend,
1911                trailing_bytes: 0,
1912            });
1913        }
1914        _ => return Err(invalid_operands("push", "unsupported operand", instr.span)),
1915    }
1916    Ok(())
1917}
1918
1919#[cfg(any(feature = "x86", feature = "x86_64"))]
1920pub(crate) fn encode_pop(
1921    buf: &mut InstrBytes,
1922    ops: &OperandList,
1923    instr: &Instruction,
1924) -> Result<(), AsmError> {
1925    if ops.len() != 1 {
1926        return Err(invalid_operands("pop", "expected 1 operand", instr.span));
1927    }
1928    match &ops[0] {
1929        Operand::Register(reg) => {
1930            let size = reg.size_bits();
1931            // Segment register pop (only FS and GS in 64-bit mode)
1932            match reg {
1933                Register::Fs => {
1934                    buf.push(0x0F);
1935                    buf.push(0xA1);
1936                    return Ok(());
1937                }
1938                Register::Gs => {
1939                    buf.push(0x0F);
1940                    buf.push(0xA9);
1941                    return Ok(());
1942                }
1943                Register::Cs | Register::Ds | Register::Es | Register::Ss => {
1944                    return Err(invalid_operands(
1945                        "pop",
1946                        "CS/DS/ES/SS pop not valid in 64-bit mode",
1947                        instr.span,
1948                    ));
1949                }
1950                _ => {}
1951            }
1952            if size == 8 || size == 32 {
1953                return Err(invalid_operands(
1954                    "pop",
1955                    "pop requires 64-bit or 16-bit register in 64-bit mode",
1956                    instr.span,
1957                ));
1958            }
1959            let b = reg.is_extended();
1960            if size == 16 {
1961                buf.push(0x66);
1962            }
1963            if b {
1964                buf.push(rex(false, false, false, true));
1965            }
1966            buf.push(0x58 + reg.base_code());
1967        }
1968        Operand::Memory(mem) => {
1969            // pop defaults to 64-bit operand size — REX.W is redundant.
1970            emit_rex_for_digit_mem(buf, 0, mem);
1971            buf.push(0x8F);
1972            emit_mem_modrm(buf, 0, mem);
1973        }
1974        _ => return Err(invalid_operands("pop", "unsupported operand", instr.span)),
1975    }
1976    Ok(())
1977}
1978
1979/// Encode ALU instructions: add/or/adc/sbb/and/sub/xor/cmp.
1980/// `alu_num` is 0=add, 1=or, 2=adc, 3=sbb, 4=and, 5=sub, 6=xor, 7=cmp.
1981#[cfg(any(feature = "x86", feature = "x86_64"))]
1982pub(crate) fn encode_alu(
1983    buf: &mut InstrBytes,
1984    ops: &OperandList,
1985    instr: &Instruction,
1986    alu_num: u8,
1987    reloc: &mut Option<Relocation>,
1988) -> Result<(), AsmError> {
1989    if ops.len() != 2 {
1990        return Err(invalid_operands(
1991            &instr.mnemonic,
1992            "expected 2 operands",
1993            instr.span,
1994        ));
1995    }
1996
1997    match (&ops[0], &ops[1]) {
1998        // r/m, r
1999        (Operand::Register(dst), Operand::Register(src)) => {
2000            let size = reg_size(*dst);
2001            let base_opcode = if size == 8 {
2002                alu_num * 8
2003            } else {
2004                alu_num * 8 + 1
2005            };
2006            emit_rr(buf, &[base_opcode], *dst, *src, instr.span)?;
2007        }
2008
2009        // r, imm
2010        (Operand::Register(dst), Operand::Immediate(imm)) => {
2011            encode_alu_reg_imm(buf, *dst, *imm, alu_num)?;
2012        }
2013
2014        // r, [mem]
2015        (Operand::Register(dst), Operand::Memory(mem)) => {
2016            let size = reg_size(*dst);
2017            let opcode: u8 = if size == 8 {
2018                alu_num * 8 + 2
2019            } else {
2020                alu_num * 8 + 3
2021            };
2022            emit_rex_for_reg_mem(buf, *dst, mem)?;
2023            buf.push(opcode);
2024            let disp_off = emit_mem_modrm(buf, dst.base_code(), mem);
2025            set_mem_reloc(reloc, mem, disp_off, buf.len());
2026        }
2027
2028        // [mem], r
2029        (Operand::Memory(mem), Operand::Register(src)) => {
2030            let size = reg_size(*src);
2031            let opcode: u8 = if size == 8 {
2032                alu_num * 8
2033            } else {
2034                alu_num * 8 + 1
2035            };
2036            emit_rex_for_reg_mem(buf, *src, mem)?;
2037            buf.push(opcode);
2038            let disp_off = emit_mem_modrm(buf, src.base_code(), mem);
2039            set_mem_reloc(reloc, mem, disp_off, buf.len());
2040        }
2041
2042        // [mem], imm
2043        (Operand::Memory(mem), Operand::Immediate(imm)) => {
2044            let size = instr
2045                .size_hint
2046                .map_or(mem.size.map_or(32u8, |s| s.bits() as u8), |s| {
2047                    s.bits() as u8
2048                });
2049            let disp_off = encode_alu_mem_imm(buf, mem, *imm, alu_num, size)?;
2050            set_mem_reloc(reloc, mem, disp_off, buf.len());
2051        }
2052
2053        // al/ax/eax/rax, imm (special short forms)
2054        _ => {
2055            return Err(invalid_operands(
2056                &instr.mnemonic,
2057                "unsupported operand combination",
2058                instr.span,
2059            ));
2060        }
2061    }
2062    Ok(())
2063}
2064
2065#[cfg(any(feature = "x86", feature = "x86_64"))]
2066pub(crate) fn encode_alu_reg_imm(
2067    buf: &mut InstrBytes,
2068    dst: Register,
2069    imm: i128,
2070    alu_num: u8,
2071) -> Result<(), AsmError> {
2072    let size = reg_size(dst);
2073
2074    // Special case: al/ax/eax/rax, imm (short form)
2075    if dst.base_code() == 0 && !dst.is_extended() && size == 8 {
2076        buf.push(alu_num * 8 + 4);
2077        buf.push(imm as u8);
2078        return Ok(());
2079    }
2080
2081    if size == 8 {
2082        let b = dst.is_extended();
2083        let need = b || dst.requires_rex_for_byte();
2084        if need {
2085            buf.push(rex(false, false, false, b));
2086        }
2087        buf.push(0x80);
2088        buf.push(modrm(0b11, alu_num, dst.base_code()));
2089        buf.push(imm as u8);
2090    } else if imm >= i8::MIN as i128 && imm <= i8::MAX as i128 {
2091        // Sign-extended imm8
2092        let w = size == 64;
2093        let b = dst.is_extended();
2094        if size == 16 {
2095            buf.push(0x66);
2096        }
2097        if needs_rex(w, false, false, b) {
2098            buf.push(rex(w, false, false, b));
2099        }
2100        buf.push(0x83);
2101        buf.push(modrm(0b11, alu_num, dst.base_code()));
2102        buf.push(imm as i8 as u8);
2103    } else {
2104        // Full immediate
2105        let w = size == 64;
2106        let b = dst.is_extended();
2107        if size == 16 {
2108            buf.push(0x66);
2109        }
2110        if needs_rex(w, false, false, b) {
2111            buf.push(rex(w, false, false, b));
2112        }
2113
2114        // Special case: eax/rax can use shorter opcode
2115        if dst.base_code() == 0 && !dst.is_extended() {
2116            buf.push(alu_num * 8 + 5);
2117        } else {
2118            buf.push(0x81);
2119            buf.push(modrm(0b11, alu_num, dst.base_code()));
2120        }
2121        let imm_size = if size > 32 { 32 } else { size };
2122        emit_imm(buf, imm, imm_size);
2123    }
2124    Ok(())
2125}
2126
2127#[cfg(any(feature = "x86", feature = "x86_64"))]
2128pub(crate) fn encode_alu_mem_imm(
2129    buf: &mut InstrBytes,
2130    mem: &MemoryOperand,
2131    imm: i128,
2132    alu_num: u8,
2133    size: u8,
2134) -> Result<Option<usize>, AsmError> {
2135    if size == 8 {
2136        emit_rex_for_digit_mem(buf, size, mem);
2137        buf.push(0x80);
2138        let disp_off = emit_mem_modrm(buf, alu_num, mem);
2139        buf.push(imm as u8);
2140        Ok(disp_off)
2141    } else if imm >= i8::MIN as i128 && imm <= i8::MAX as i128 {
2142        emit_rex_for_digit_mem(buf, size, mem);
2143        buf.push(0x83);
2144        let disp_off = emit_mem_modrm(buf, alu_num, mem);
2145        buf.push(imm as i8 as u8);
2146        Ok(disp_off)
2147    } else {
2148        emit_rex_for_digit_mem(buf, size, mem);
2149        buf.push(0x81);
2150        let disp_off = emit_mem_modrm(buf, alu_num, mem);
2151        let imm_size = if size > 32 { 32 } else { size };
2152        emit_imm(buf, imm, imm_size);
2153        Ok(disp_off)
2154    }
2155}
2156
2157#[cfg(any(feature = "x86", feature = "x86_64"))]
2158pub(crate) fn encode_test(
2159    buf: &mut InstrBytes,
2160    ops: &OperandList,
2161    instr: &Instruction,
2162    reloc: &mut Option<Relocation>,
2163) -> Result<(), AsmError> {
2164    if ops.len() != 2 {
2165        return Err(invalid_operands("test", "expected 2 operands", instr.span));
2166    }
2167    match (&ops[0], &ops[1]) {
2168        (Operand::Register(dst), Operand::Register(src)) => {
2169            let size = reg_size(*dst);
2170            let opcode = if size == 8 { 0x84u8 } else { 0x85u8 };
2171            emit_rr(buf, &[opcode], *dst, *src, instr.span)?;
2172        }
2173        (Operand::Register(dst), Operand::Immediate(imm)) => {
2174            let size = reg_size(*dst);
2175            // Short form for AL/AX/EAX/RAX
2176            if dst.base_code() == 0 && !dst.is_extended() && size == 8 {
2177                buf.push(0xA8);
2178                buf.push(*imm as u8);
2179            } else if dst.base_code() == 0 && !dst.is_extended() && size > 8 {
2180                let w = size == 64;
2181                if size == 16 {
2182                    buf.push(0x66);
2183                }
2184                if w {
2185                    buf.push(rex(true, false, false, false));
2186                }
2187                buf.push(0xA9);
2188                let imm_size = if size > 32 { 32 } else { size };
2189                emit_imm(buf, *imm, imm_size);
2190            } else {
2191                let w = size == 64;
2192                let b = dst.is_extended();
2193                if size == 16 {
2194                    buf.push(0x66);
2195                }
2196                let need = needs_rex(w, false, false, b) || dst.requires_rex_for_byte();
2197                if need {
2198                    buf.push(rex(w, false, false, b));
2199                }
2200                buf.push(if size == 8 { 0xF6 } else { 0xF7 });
2201                buf.push(modrm(0b11, 0, dst.base_code()));
2202                let imm_size = if size == 8 {
2203                    8
2204                } else if size > 32 {
2205                    32
2206                } else {
2207                    size
2208                };
2209                emit_imm(buf, *imm, imm_size);
2210            }
2211        }
2212        (Operand::Memory(mem), Operand::Register(src)) => {
2213            let size = reg_size(*src);
2214            let opcode = if size == 8 { 0x84u8 } else { 0x85u8 };
2215            emit_rex_for_reg_mem(buf, *src, mem)?;
2216            buf.push(opcode);
2217            let disp_off = emit_mem_modrm(buf, src.base_code(), mem);
2218            set_mem_reloc(reloc, mem, disp_off, buf.len());
2219        }
2220        (Operand::Memory(mem), Operand::Immediate(imm)) => {
2221            let size = instr
2222                .size_hint
2223                .map_or(mem.size.map_or(32u8, |s| s.bits() as u8), |s| {
2224                    s.bits() as u8
2225                });
2226            let opcode = if size == 8 { 0xF6u8 } else { 0xF7u8 };
2227            emit_rex_for_digit_mem(buf, size, mem);
2228            buf.push(opcode);
2229            let disp_off = emit_mem_modrm(buf, 0, mem); // /0
2230            let imm_size = if size == 8 {
2231                8
2232            } else if size > 32 {
2233                32
2234            } else {
2235                size
2236            };
2237            emit_imm(buf, *imm, imm_size);
2238            set_mem_reloc(reloc, mem, disp_off, buf.len());
2239        }
2240        _ => {
2241            return Err(invalid_operands(
2242                "test",
2243                "unsupported operand combination",
2244                instr.span,
2245            ))
2246        }
2247    }
2248    Ok(())
2249}
2250
2251/// Encode unary instructions: NOT, NEG, MUL, DIV, IDIV.
2252#[cfg(any(feature = "x86", feature = "x86_64"))]
2253pub(crate) fn encode_unary(
2254    buf: &mut InstrBytes,
2255    ops: &OperandList,
2256    instr: &Instruction,
2257    digit: u8,
2258) -> Result<(), AsmError> {
2259    if ops.len() != 1 {
2260        return Err(invalid_operands(
2261            &instr.mnemonic,
2262            "expected 1 operand",
2263            instr.span,
2264        ));
2265    }
2266    match &ops[0] {
2267        Operand::Register(reg) => {
2268            let size = reg_size(*reg);
2269            let w = size == 64;
2270            let b = reg.is_extended();
2271            if size == 16 {
2272                buf.push(0x66);
2273            }
2274            let need = needs_rex(w, false, false, b) || reg.requires_rex_for_byte();
2275            if need {
2276                buf.push(rex(w, false, false, b));
2277            }
2278            buf.push(if size == 8 { 0xF6 } else { 0xF7 });
2279            buf.push(modrm(0b11, digit, reg.base_code()));
2280        }
2281        Operand::Memory(mem) => {
2282            let size = instr
2283                .size_hint
2284                .map_or(mem.size.map_or(32u8, |s| s.bits() as u8), |s| {
2285                    s.bits() as u8
2286                });
2287            emit_rex_for_digit_mem(buf, size, mem);
2288            buf.push(if size == 8 { 0xF6 } else { 0xF7 });
2289            emit_mem_modrm(buf, digit, mem);
2290        }
2291        _ => {
2292            return Err(invalid_operands(
2293                &instr.mnemonic,
2294                "expected register or memory operand",
2295                instr.span,
2296            ))
2297        }
2298    }
2299    Ok(())
2300}
2301
2302#[cfg(any(feature = "x86", feature = "x86_64"))]
2303pub(crate) fn encode_imul(
2304    buf: &mut InstrBytes,
2305    ops: &OperandList,
2306    instr: &Instruction,
2307) -> Result<(), AsmError> {
2308    match ops.len() {
2309        1 => {
2310            // One-operand IMUL: EDX:EAX = EAX * r/m
2311            encode_unary(buf, ops, instr, 5)
2312        }
2313        2 => {
2314            // Two-operand IMUL: r = r * r/m
2315            match (&ops[0], &ops[1]) {
2316                (Operand::Register(dst), Operand::Register(src)) => {
2317                    let size = reg_size(*dst);
2318                    if size == 8 {
2319                        return Err(invalid_operands(
2320                            "imul",
2321                            "8-bit operands not supported for 2/3-operand IMUL",
2322                            instr.span,
2323                        ));
2324                    }
2325                    let w = size == 64;
2326                    let r = dst.is_extended();
2327                    let b = src.is_extended();
2328                    if size == 16 {
2329                        buf.push(0x66);
2330                    }
2331                    if needs_rex(w, r, false, b) {
2332                        buf.push(rex(w, r, false, b));
2333                    }
2334                    buf.push(0x0F);
2335                    buf.push(0xAF);
2336                    buf.push(modrm(0b11, dst.base_code(), src.base_code()));
2337                }
2338                (Operand::Register(dst), Operand::Memory(mem)) => {
2339                    if reg_size(*dst) == 8 {
2340                        return Err(invalid_operands(
2341                            "imul",
2342                            "8-bit operands not supported for 2/3-operand IMUL",
2343                            instr.span,
2344                        ));
2345                    }
2346                    emit_rex_for_reg_mem(buf, *dst, mem)?;
2347                    buf.push(0x0F);
2348                    buf.push(0xAF);
2349                    emit_mem_modrm(buf, dst.base_code(), mem);
2350                }
2351                _ => {
2352                    return Err(invalid_operands(
2353                        "imul",
2354                        "unsupported operand combination",
2355                        instr.span,
2356                    ))
2357                }
2358            }
2359            Ok(())
2360        }
2361        3 => {
2362            // Three-operand IMUL: r = r/m * imm
2363            match (&ops[0], &ops[1], &ops[2]) {
2364                (Operand::Register(dst), Operand::Register(src), Operand::Immediate(imm)) => {
2365                    let size = reg_size(*dst);
2366                    if size == 8 {
2367                        return Err(invalid_operands(
2368                            "imul",
2369                            "8-bit operands not supported for 2/3-operand IMUL",
2370                            instr.span,
2371                        ));
2372                    }
2373                    let w = size == 64;
2374                    let r = dst.is_extended();
2375                    let b = src.is_extended();
2376                    if size == 16 {
2377                        buf.push(0x66);
2378                    }
2379                    if needs_rex(w, r, false, b) {
2380                        buf.push(rex(w, r, false, b));
2381                    }
2382
2383                    if *imm >= i8::MIN as i128 && *imm <= i8::MAX as i128 {
2384                        buf.push(0x6B);
2385                        buf.push(modrm(0b11, dst.base_code(), src.base_code()));
2386                        buf.push(*imm as i8 as u8);
2387                    } else {
2388                        buf.push(0x69);
2389                        buf.push(modrm(0b11, dst.base_code(), src.base_code()));
2390                        let imm_size = if size > 32 { 32 } else { size };
2391                        emit_imm(buf, *imm, imm_size);
2392                    }
2393                }
2394                (Operand::Register(dst), Operand::Memory(mem), Operand::Immediate(imm)) => {
2395                    let size = reg_size(*dst);
2396                    if size == 8 {
2397                        return Err(invalid_operands(
2398                            "imul",
2399                            "8-bit operands not supported for 2/3-operand IMUL",
2400                            instr.span,
2401                        ));
2402                    }
2403                    emit_rex_for_reg_mem(buf, *dst, mem)?;
2404
2405                    if *imm >= i8::MIN as i128 && *imm <= i8::MAX as i128 {
2406                        buf.push(0x6B);
2407                        emit_mem_modrm(buf, dst.base_code(), mem);
2408                        buf.push(*imm as i8 as u8);
2409                    } else {
2410                        buf.push(0x69);
2411                        emit_mem_modrm(buf, dst.base_code(), mem);
2412                        let imm_size = if size > 32 { 32 } else { size };
2413                        emit_imm(buf, *imm, imm_size);
2414                    }
2415                }
2416                _ => {
2417                    return Err(invalid_operands(
2418                        "imul",
2419                        "expected reg, r/m, imm",
2420                        instr.span,
2421                    ))
2422                }
2423            }
2424            Ok(())
2425        }
2426        _ => Err(invalid_operands(
2427            "imul",
2428            "expected 1-3 operands",
2429            instr.span,
2430        )),
2431    }
2432}
2433
2434#[cfg(any(feature = "x86", feature = "x86_64"))]
2435pub(crate) fn encode_inc_dec(
2436    buf: &mut InstrBytes,
2437    ops: &OperandList,
2438    instr: &Instruction,
2439    digit: u8,
2440) -> Result<(), AsmError> {
2441    if ops.len() != 1 {
2442        return Err(invalid_operands(
2443            &instr.mnemonic,
2444            "expected 1 operand",
2445            instr.span,
2446        ));
2447    }
2448    match &ops[0] {
2449        Operand::Register(reg) => {
2450            let size = reg_size(*reg);
2451            let w = size == 64;
2452            let b = reg.is_extended();
2453            if size == 16 {
2454                buf.push(0x66);
2455            }
2456            let need = needs_rex(w, false, false, b) || reg.requires_rex_for_byte();
2457            if need {
2458                buf.push(rex(w, false, false, b));
2459            }
2460            buf.push(if size == 8 { 0xFE } else { 0xFF });
2461            buf.push(modrm(0b11, digit, reg.base_code()));
2462        }
2463        Operand::Memory(mem) => {
2464            let size = instr
2465                .size_hint
2466                .map_or(mem.size.map_or(32u8, |s| s.bits() as u8), |s| {
2467                    s.bits() as u8
2468                });
2469            emit_rex_for_digit_mem(buf, size, mem);
2470            buf.push(if size == 8 { 0xFE } else { 0xFF });
2471            emit_mem_modrm(buf, digit, mem);
2472        }
2473        _ => {
2474            return Err(invalid_operands(
2475                &instr.mnemonic,
2476                "expected register or memory",
2477                instr.span,
2478            ))
2479        }
2480    }
2481    Ok(())
2482}
2483
2484#[cfg(any(feature = "x86", feature = "x86_64"))]
2485pub(crate) fn encode_shift(
2486    buf: &mut InstrBytes,
2487    ops: &OperandList,
2488    instr: &Instruction,
2489    digit: u8,
2490) -> Result<(), AsmError> {
2491    if ops.len() != 2 {
2492        return Err(invalid_operands(
2493            &instr.mnemonic,
2494            "expected 2 operands",
2495            instr.span,
2496        ));
2497    }
2498    match (&ops[0], &ops[1]) {
2499        (Operand::Register(dst), Operand::Immediate(1)) => {
2500            let size = reg_size(*dst);
2501            let w = size == 64;
2502            let b = dst.is_extended();
2503            if size == 16 {
2504                buf.push(0x66);
2505            }
2506            let need = needs_rex(w, false, false, b) || dst.requires_rex_for_byte();
2507            if need {
2508                buf.push(rex(w, false, false, b));
2509            }
2510            buf.push(if size == 8 { 0xD0 } else { 0xD1 });
2511            buf.push(modrm(0b11, digit, dst.base_code()));
2512        }
2513        (Operand::Register(dst), Operand::Immediate(imm)) => {
2514            let size = reg_size(*dst);
2515            let w = size == 64;
2516            let b = dst.is_extended();
2517            if size == 16 {
2518                buf.push(0x66);
2519            }
2520            let need = needs_rex(w, false, false, b) || dst.requires_rex_for_byte();
2521            if need {
2522                buf.push(rex(w, false, false, b));
2523            }
2524            buf.push(if size == 8 { 0xC0 } else { 0xC1 });
2525            buf.push(modrm(0b11, digit, dst.base_code()));
2526            buf.push(*imm as u8);
2527        }
2528        (Operand::Register(dst), Operand::Register(Register::Cl)) => {
2529            let size = reg_size(*dst);
2530            let w = size == 64;
2531            let b = dst.is_extended();
2532            if size == 16 {
2533                buf.push(0x66);
2534            }
2535            let need = needs_rex(w, false, false, b) || dst.requires_rex_for_byte();
2536            if need {
2537                buf.push(rex(w, false, false, b));
2538            }
2539            buf.push(if size == 8 { 0xD2 } else { 0xD3 });
2540            buf.push(modrm(0b11, digit, dst.base_code()));
2541        }
2542        (Operand::Memory(mem), Operand::Immediate(1)) => {
2543            let size = instr
2544                .size_hint
2545                .map_or(mem.size.map_or(32u8, |s| s.bits() as u8), |s| {
2546                    s.bits() as u8
2547                });
2548            let w = size == 64;
2549            let x = mem.index.is_some_and(|r| r.is_extended());
2550            let b = mem.base.is_some_and(|r| r.is_extended());
2551            if size == 16 {
2552                buf.push(0x66);
2553            }
2554            if needs_rex(w, false, x, b) {
2555                buf.push(rex(w, false, x, b));
2556            }
2557            buf.push(if size == 8 { 0xD0 } else { 0xD1 });
2558            emit_mem_modrm(buf, digit, mem);
2559        }
2560        (Operand::Memory(mem), Operand::Immediate(imm)) => {
2561            let size = instr
2562                .size_hint
2563                .map_or(mem.size.map_or(32u8, |s| s.bits() as u8), |s| {
2564                    s.bits() as u8
2565                });
2566            let w = size == 64;
2567            let x = mem.index.is_some_and(|r| r.is_extended());
2568            let b = mem.base.is_some_and(|r| r.is_extended());
2569            if size == 16 {
2570                buf.push(0x66);
2571            }
2572            if needs_rex(w, false, x, b) {
2573                buf.push(rex(w, false, x, b));
2574            }
2575            buf.push(if size == 8 { 0xC0 } else { 0xC1 });
2576            emit_mem_modrm(buf, digit, mem);
2577            buf.push(*imm as u8);
2578        }
2579        (Operand::Memory(mem), Operand::Register(Register::Cl)) => {
2580            let size = instr
2581                .size_hint
2582                .map_or(mem.size.map_or(32u8, |s| s.bits() as u8), |s| {
2583                    s.bits() as u8
2584                });
2585            let w = size == 64;
2586            let x = mem.index.is_some_and(|r| r.is_extended());
2587            let b = mem.base.is_some_and(|r| r.is_extended());
2588            if size == 16 {
2589                buf.push(0x66);
2590            }
2591            if needs_rex(w, false, x, b) {
2592                buf.push(rex(w, false, x, b));
2593            }
2594            buf.push(if size == 8 { 0xD2 } else { 0xD3 });
2595            emit_mem_modrm(buf, digit, mem);
2596        }
2597        _ => {
2598            return Err(invalid_operands(
2599                &instr.mnemonic,
2600                "expected r/m, imm or r/m, cl",
2601                instr.span,
2602            ))
2603        }
2604    }
2605    Ok(())
2606}
2607
2608#[cfg(any(feature = "x86", feature = "x86_64"))]
2609pub(crate) fn encode_jmp(
2610    buf: &mut InstrBytes,
2611    ops: &OperandList,
2612    instr: &Instruction,
2613    reloc: &mut Option<Relocation>,
2614    relax: &mut Option<RelaxInfo>,
2615) -> Result<(), AsmError> {
2616    if ops.len() != 1 {
2617        return Err(invalid_operands("jmp", "expected 1 operand", instr.span));
2618    }
2619    match &ops[0] {
2620        op @ (Operand::Label(_) | Operand::Expression(_)) => {
2621            let Some((label, addend)) = extract_label(op) else {
2622                return Err(invalid_operands("jmp", "expected label", instr.span));
2623            };
2624            // Long form: E9 rel32 (5 bytes) — linker may relax to EB rel8 (2 bytes)
2625            buf.push(0xE9);
2626            let reloc_off = buf.len();
2627            buf.extend_from_slice(&0i32.to_le_bytes());
2628            *reloc = Some(Relocation {
2629                offset: reloc_off,
2630                size: 4,
2631                label: alloc::rc::Rc::from(label),
2632                kind: RelocKind::X86Relative,
2633                addend,
2634                trailing_bytes: 0,
2635            });
2636            // Short form for relaxation (only when no addend — with addend
2637            // the displacement arithmetic is the same but we still offer it)
2638            *relax = Some(RelaxInfo {
2639                short_bytes: InstrBytes::from_slice(&[0xEB, 0x00]),
2640                short_reloc_offset: 1,
2641                short_relocation: None,
2642            });
2643        }
2644        Operand::Immediate(target) => {
2645            // Short jump with known offset
2646            buf.push(0xE9);
2647            buf.extend_from_slice(&(*target as i32).to_le_bytes());
2648        }
2649        Operand::Register(reg) => {
2650            let b = reg.is_extended();
2651            if b {
2652                buf.push(rex(false, false, false, true));
2653            }
2654            buf.push(0xFF);
2655            buf.push(modrm(0b11, 4, reg.base_code()));
2656        }
2657        Operand::Memory(mem) => {
2658            // jmp defaults to 64-bit operand size — REX.W is redundant.
2659            emit_rex_for_digit_mem(buf, 0, mem);
2660            buf.push(0xFF);
2661            emit_mem_modrm(buf, 4, mem);
2662        }
2663        _ => return Err(invalid_operands("jmp", "unsupported operand", instr.span)),
2664    }
2665    Ok(())
2666}
2667
2668#[cfg(any(feature = "x86", feature = "x86_64"))]
2669pub(crate) fn encode_call(
2670    buf: &mut InstrBytes,
2671    ops: &OperandList,
2672    instr: &Instruction,
2673    reloc: &mut Option<Relocation>,
2674) -> Result<(), AsmError> {
2675    if ops.len() != 1 {
2676        return Err(invalid_operands("call", "expected 1 operand", instr.span));
2677    }
2678    match &ops[0] {
2679        op @ (Operand::Label(_) | Operand::Expression(_)) => {
2680            let Some((label, addend)) = extract_label(op) else {
2681                return Err(invalid_operands("call", "expected label", instr.span));
2682            };
2683            buf.push(0xE8);
2684            let reloc_off = buf.len();
2685            buf.extend_from_slice(&0i32.to_le_bytes());
2686            *reloc = Some(Relocation {
2687                offset: reloc_off,
2688                size: 4,
2689                label: alloc::rc::Rc::from(label),
2690                kind: RelocKind::X86Relative,
2691                addend,
2692                trailing_bytes: 0,
2693            });
2694        }
2695        Operand::Register(reg) => {
2696            let b = reg.is_extended();
2697            if b {
2698                buf.push(rex(false, false, false, true));
2699            }
2700            buf.push(0xFF);
2701            buf.push(modrm(0b11, 2, reg.base_code()));
2702        }
2703        Operand::Memory(mem) => {
2704            // call defaults to 64-bit operand size — REX.W is redundant.
2705            emit_rex_for_digit_mem(buf, 0, mem);
2706            buf.push(0xFF);
2707            emit_mem_modrm(buf, 2, mem);
2708        }
2709        _ => return Err(invalid_operands("call", "unsupported operand", instr.span)),
2710    }
2711    Ok(())
2712}
2713
2714#[cfg(any(feature = "x86", feature = "x86_64"))]
2715pub(crate) fn encode_jcc(
2716    buf: &mut InstrBytes,
2717    ops: &OperandList,
2718    instr: &Instruction,
2719    cc: u8,
2720    reloc: &mut Option<Relocation>,
2721    relax: &mut Option<RelaxInfo>,
2722) -> Result<(), AsmError> {
2723    if ops.len() != 1 {
2724        return Err(invalid_operands(
2725            &instr.mnemonic,
2726            "expected 1 operand",
2727            instr.span,
2728        ));
2729    }
2730    match &ops[0] {
2731        op @ (Operand::Label(_) | Operand::Expression(_)) => {
2732            let Some((label, addend)) = extract_label(op) else {
2733                return Err(invalid_operands(
2734                    &instr.mnemonic,
2735                    "expected label or offset",
2736                    instr.span,
2737                ));
2738            };
2739            // Long form: 0F 8x rel32 (6 bytes) — linker may relax to 7x rel8 (2 bytes)
2740            buf.push(0x0F);
2741            buf.push(0x80 + cc);
2742            let reloc_off = buf.len();
2743            buf.extend_from_slice(&0i32.to_le_bytes());
2744            *reloc = Some(Relocation {
2745                offset: reloc_off,
2746                size: 4,
2747                label: alloc::rc::Rc::from(label),
2748                kind: RelocKind::X86Relative,
2749                addend,
2750                trailing_bytes: 0,
2751            });
2752            // Short form for relaxation
2753            *relax = Some(RelaxInfo {
2754                short_bytes: InstrBytes::from_slice(&[0x70 + cc, 0x00]),
2755                short_reloc_offset: 1,
2756                short_relocation: None,
2757            });
2758        }
2759        Operand::Immediate(off) => {
2760            // With known numeric offset — emit near form
2761            buf.push(0x0F);
2762            buf.push(0x80 + cc);
2763            buf.extend_from_slice(&(*off as i32).to_le_bytes());
2764        }
2765        _ => {
2766            return Err(invalid_operands(
2767                &instr.mnemonic,
2768                "expected label or offset",
2769                instr.span,
2770            ))
2771        }
2772    }
2773    Ok(())
2774}
2775
2776#[cfg(any(feature = "x86", feature = "x86_64"))]
2777pub(crate) fn encode_loop(
2778    buf: &mut InstrBytes,
2779    ops: &OperandList,
2780    instr: &Instruction,
2781    opcode: u8,
2782    reloc: &mut Option<Relocation>,
2783    relax: &mut Option<RelaxInfo>,
2784) -> Result<(), AsmError> {
2785    if ops.len() != 1 {
2786        return Err(invalid_operands(
2787            &instr.mnemonic,
2788            "expected 1 operand",
2789            instr.span,
2790        ));
2791    }
2792    match &ops[0] {
2793        op @ (Operand::Label(_) | Operand::Expression(_)) => {
2794            let Some((label, addend)) = extract_label(op) else {
2795                return Err(invalid_operands(
2796                    &instr.mnemonic,
2797                    "expected label",
2798                    instr.span,
2799                ));
2800            };
2801            // Long form (9 bytes): LOOPx +2 / JMP_short +5 / JMP_near rel32
2802            //
2803            // If the target is within ±127 bytes the linker relaxes to the
2804            // short form (2 bytes): LOOPx rel8.
2805            //
2806            // Long-form layout:
2807            //   [0] opcode  LOOPx
2808            //   [1] 0x02    rel8 = +2  → skips the JMP_short, lands on JMP_near
2809            //   [2] 0xEB    JMP short
2810            //   [3] 0x05    rel8 = +5  → skips the JMP_near (CX was zero / cond false)
2811            //   [4] 0xE9    JMP near
2812            //   [5..9] rel32 placeholder → target label
2813            buf.push(opcode);
2814            buf.push(0x02);
2815            buf.push(0xEB);
2816            buf.push(0x05);
2817            buf.push(0xE9);
2818            let reloc_off = buf.len();
2819            buf.extend_from_slice(&0i32.to_le_bytes());
2820            *reloc = Some(Relocation {
2821                offset: reloc_off,
2822                size: 4,
2823                label: alloc::rc::Rc::from(label),
2824                kind: RelocKind::X86Relative,
2825                addend,
2826                trailing_bytes: 0,
2827            });
2828            // Short form for relaxation: LOOPx rel8 (2 bytes)
2829            *relax = Some(RelaxInfo {
2830                short_bytes: InstrBytes::from_slice(&[opcode, 0x00]),
2831                short_reloc_offset: 1,
2832                short_relocation: None,
2833            });
2834        }
2835        _ => {
2836            return Err(invalid_operands(
2837                &instr.mnemonic,
2838                "expected label",
2839                instr.span,
2840            ))
2841        }
2842    }
2843    Ok(())
2844}
2845
2846#[cfg(any(feature = "x86", feature = "x86_64"))]
2847pub(crate) fn encode_setcc(
2848    buf: &mut InstrBytes,
2849    ops: &OperandList,
2850    instr: &Instruction,
2851    cc: u8,
2852) -> Result<(), AsmError> {
2853    if ops.len() != 1 {
2854        return Err(invalid_operands(
2855            &instr.mnemonic,
2856            "expected 1 operand",
2857            instr.span,
2858        ));
2859    }
2860    match &ops[0] {
2861        Operand::Register(reg) => {
2862            if reg.size_bits() != 8 {
2863                return Err(invalid_operands(
2864                    &instr.mnemonic,
2865                    "SETcc requires an 8-bit register operand",
2866                    instr.span,
2867                ));
2868            }
2869            let b = reg.is_extended();
2870            let need = b || reg.requires_rex_for_byte();
2871            if need {
2872                buf.push(rex(false, false, false, b));
2873            }
2874            buf.push(0x0F);
2875            buf.push(0x90 + cc);
2876            buf.push(modrm(0b11, 0, reg.base_code()));
2877        }
2878        Operand::Memory(mem) => {
2879            emit_rex_for_digit_mem(buf, 8, mem);
2880            buf.push(0x0F);
2881            buf.push(0x90 + cc);
2882            emit_mem_modrm(buf, 0, mem);
2883        }
2884        _ => {
2885            return Err(invalid_operands(
2886                &instr.mnemonic,
2887                "expected register or memory",
2888                instr.span,
2889            ))
2890        }
2891    }
2892    Ok(())
2893}
2894
2895#[cfg(any(feature = "x86", feature = "x86_64"))]
2896pub(crate) fn encode_cmovcc(
2897    buf: &mut InstrBytes,
2898    ops: &OperandList,
2899    instr: &Instruction,
2900    cc: u8,
2901) -> Result<(), AsmError> {
2902    if ops.len() != 2 {
2903        return Err(invalid_operands(
2904            &instr.mnemonic,
2905            "expected 2 operands",
2906            instr.span,
2907        ));
2908    }
2909    match (&ops[0], &ops[1]) {
2910        (Operand::Register(dst), Operand::Register(src)) => {
2911            let size = reg_size(*dst);
2912            if size == 8 {
2913                return Err(invalid_operands(
2914                    &instr.mnemonic,
2915                    "CMOVcc requires 16/32/64-bit operands",
2916                    instr.span,
2917                ));
2918            }
2919            let w = size == 64;
2920            let r = dst.is_extended();
2921            let b = src.is_extended();
2922            if size == 16 {
2923                buf.push(0x66);
2924            }
2925            if needs_rex(w, r, false, b) {
2926                buf.push(rex(w, r, false, b));
2927            }
2928            buf.push(0x0F);
2929            buf.push(0x40 + cc);
2930            buf.push(modrm(0b11, dst.base_code(), src.base_code()));
2931        }
2932        (Operand::Register(dst), Operand::Memory(mem)) => {
2933            if reg_size(*dst) == 8 {
2934                return Err(invalid_operands(
2935                    &instr.mnemonic,
2936                    "CMOVcc requires 16/32/64-bit operands",
2937                    instr.span,
2938                ));
2939            }
2940            emit_rex_for_reg_mem(buf, *dst, mem)?;
2941            buf.push(0x0F);
2942            buf.push(0x40 + cc);
2943            emit_mem_modrm(buf, dst.base_code(), mem);
2944        }
2945        _ => {
2946            return Err(invalid_operands(
2947                &instr.mnemonic,
2948                "expected reg, r/m",
2949                instr.span,
2950            ))
2951        }
2952    }
2953    Ok(())
2954}
2955
2956#[cfg(any(feature = "x86", feature = "x86_64"))]
2957pub(crate) fn encode_movzx(
2958    buf: &mut InstrBytes,
2959    ops: &OperandList,
2960    instr: &Instruction,
2961) -> Result<(), AsmError> {
2962    if ops.len() != 2 {
2963        return Err(invalid_operands("movzx", "expected 2 operands", instr.span));
2964    }
2965    match (&ops[0], &ops[1]) {
2966        (Operand::Register(dst), Operand::Register(src)) => {
2967            let dst_size = reg_size(*dst);
2968            let src_size = reg_size(*src);
2969            let w = dst_size == 64;
2970            let r = dst.is_extended();
2971            let b = src.is_extended();
2972            if dst_size == 16 {
2973                buf.push(0x66);
2974            }
2975            let need = needs_rex(w, r, false, b) || src.requires_rex_for_byte();
2976            if need {
2977                buf.push(rex(w, r, false, b));
2978            }
2979            buf.push(0x0F);
2980            buf.push(if src_size == 8 { 0xB6 } else { 0xB7 }); // B6=byte, B7=word
2981            buf.push(modrm(0b11, dst.base_code(), src.base_code()));
2982        }
2983        (Operand::Register(dst), Operand::Memory(mem)) => {
2984            let src_size = instr
2985                .size_hint
2986                .map_or(mem.size.map_or(8u8, |s| s.bits() as u8), |s| s.bits() as u8);
2987            emit_rex_for_reg_mem(buf, *dst, mem)?;
2988            buf.push(0x0F);
2989            buf.push(if src_size == 8 { 0xB6 } else { 0xB7 });
2990            emit_mem_modrm(buf, dst.base_code(), mem);
2991        }
2992        _ => return Err(invalid_operands("movzx", "expected reg, r/m", instr.span)),
2993    }
2994    Ok(())
2995}
2996
2997#[cfg(any(feature = "x86", feature = "x86_64"))]
2998pub(crate) fn encode_movsx(
2999    buf: &mut InstrBytes,
3000    ops: &OperandList,
3001    instr: &Instruction,
3002) -> Result<(), AsmError> {
3003    if ops.len() != 2 {
3004        return Err(invalid_operands("movsx", "expected 2 operands", instr.span));
3005    }
3006    match (&ops[0], &ops[1]) {
3007        (Operand::Register(dst), Operand::Register(src)) => {
3008            let dst_size = reg_size(*dst);
3009            let src_size = reg_size(*src);
3010            let w = dst_size == 64;
3011            let r = dst.is_extended();
3012            let b = src.is_extended();
3013            if dst_size == 16 {
3014                buf.push(0x66);
3015            }
3016            let need = needs_rex(w, r, false, b) || src.requires_rex_for_byte();
3017            if need {
3018                buf.push(rex(w, r, false, b));
3019            }
3020            if src_size == 32 {
3021                // movsxd: 63 /r
3022                buf.push(0x63);
3023            } else {
3024                buf.push(0x0F);
3025                buf.push(if src_size == 8 { 0xBE } else { 0xBF });
3026            }
3027            buf.push(modrm(0b11, dst.base_code(), src.base_code()));
3028        }
3029        (Operand::Register(dst), Operand::Memory(mem)) => {
3030            let src_size = instr
3031                .size_hint
3032                .map_or(mem.size.map_or(8u8, |s| s.bits() as u8), |s| s.bits() as u8);
3033            let dst_size = reg_size(*dst);
3034            if src_size == 32 {
3035                // movsxd: REX.W 63 /r
3036                let w = dst_size == 64;
3037                let r = dst.is_extended();
3038                let x = mem.index.is_some_and(|r| r.is_extended());
3039                let b = mem.base.is_some_and(|r| r.is_extended());
3040                if dst_size == 16 {
3041                    buf.push(0x66);
3042                }
3043                if needs_rex(w, r, x, b) {
3044                    buf.push(rex(w, r, x, b));
3045                }
3046                buf.push(0x63);
3047            } else {
3048                emit_rex_for_reg_mem(buf, *dst, mem)?;
3049                buf.push(0x0F);
3050                buf.push(if src_size == 8 { 0xBE } else { 0xBF });
3051            }
3052            emit_mem_modrm(buf, dst.base_code(), mem);
3053        }
3054        _ => return Err(invalid_operands("movsx", "expected reg, r/m", instr.span)),
3055    }
3056    Ok(())
3057}
3058
3059#[cfg(any(feature = "x86", feature = "x86_64"))]
3060pub(crate) fn encode_xchg(
3061    buf: &mut InstrBytes,
3062    ops: &OperandList,
3063    instr: &Instruction,
3064) -> Result<(), AsmError> {
3065    if ops.len() != 2 {
3066        return Err(invalid_operands("xchg", "expected 2 operands", instr.span));
3067    }
3068    match (&ops[0], &ops[1]) {
3069        (Operand::Register(dst), Operand::Register(src)) => {
3070            let size = reg_size(*dst);
3071            // Special case: xchg ax, r16 or xchg r16, ax → 66 90+rd
3072            if size == 16
3073                && ((dst.base_code() == 0 && !dst.is_extended())
3074                    || (src.base_code() == 0 && !src.is_extended()))
3075            {
3076                let other = if dst.base_code() == 0 && !dst.is_extended() {
3077                    *src
3078                } else {
3079                    *dst
3080                };
3081                let b = other.is_extended();
3082                buf.push(0x66);
3083                if b {
3084                    buf.push(rex(false, false, false, b));
3085                }
3086                buf.push(0x90 + other.base_code());
3087                return Ok(());
3088            }
3089            // Special case: xchg eax/rax, reg or xchg reg, eax/rax → 90+rd
3090            if size >= 32
3091                && ((dst.base_code() == 0 && !dst.is_extended())
3092                    || (src.base_code() == 0 && !src.is_extended()))
3093            {
3094                let other = if dst.base_code() == 0 && !dst.is_extended() {
3095                    *src
3096                } else {
3097                    *dst
3098                };
3099                let w = size == 64;
3100                let b = other.is_extended();
3101                if needs_rex(w, false, false, b) {
3102                    buf.push(rex(w, false, false, b));
3103                }
3104                buf.push(0x90 + other.base_code());
3105                return Ok(());
3106            }
3107            let opcode = if size == 8 { 0x86u8 } else { 0x87u8 };
3108            emit_rr(buf, &[opcode], *dst, *src, instr.span)?;
3109        }
3110        // xchg reg, [mem]
3111        (Operand::Register(reg), Operand::Memory(mem)) => {
3112            let size = reg_size(*reg);
3113            let opcode = if size == 8 { 0x86u8 } else { 0x87u8 };
3114            emit_rex_for_reg_mem(buf, *reg, mem)?;
3115            buf.push(opcode);
3116            emit_mem_modrm(buf, reg.base_code(), mem);
3117        }
3118        // xchg [mem], reg
3119        (Operand::Memory(mem), Operand::Register(reg)) => {
3120            let size = reg_size(*reg);
3121            let opcode = if size == 8 { 0x86u8 } else { 0x87u8 };
3122            emit_rex_for_reg_mem(buf, *reg, mem)?;
3123            buf.push(opcode);
3124            emit_mem_modrm(buf, reg.base_code(), mem);
3125        }
3126        _ => {
3127            return Err(invalid_operands(
3128                "xchg",
3129                "unsupported operand combination",
3130                instr.span,
3131            ))
3132        }
3133    }
3134    Ok(())
3135}
3136
3137#[cfg(any(feature = "x86", feature = "x86_64"))]
3138pub(crate) fn encode_bt(
3139    buf: &mut InstrBytes,
3140    ops: &OperandList,
3141    instr: &Instruction,
3142    digit: u8,
3143) -> Result<(), AsmError> {
3144    if ops.len() != 2 {
3145        return Err(invalid_operands(
3146            &instr.mnemonic,
3147            "expected 2 operands",
3148            instr.span,
3149        ));
3150    }
3151    // BT family only supports 16/32/64-bit operands
3152    if let Operand::Register(r) = &ops[0] {
3153        if reg_size(*r) == 8 {
3154            return Err(invalid_operands(
3155                &instr.mnemonic,
3156                "8-bit operands not supported",
3157                instr.span,
3158            ));
3159        }
3160    }
3161    match (&ops[0], &ops[1]) {
3162        (Operand::Register(dst), Operand::Immediate(imm)) => {
3163            let size = reg_size(*dst);
3164            let w = size == 64;
3165            let b = dst.is_extended();
3166            if size == 16 {
3167                buf.push(0x66);
3168            }
3169            if needs_rex(w, false, false, b) {
3170                buf.push(rex(w, false, false, b));
3171            }
3172            buf.push(0x0F);
3173            buf.push(0xBA);
3174            buf.push(modrm(0b11, digit, dst.base_code()));
3175            buf.push(*imm as u8);
3176        }
3177        (Operand::Register(dst), Operand::Register(src)) => {
3178            let size = reg_size(*dst);
3179            let w = size == 64;
3180            let r = src.is_extended();
3181            let b = dst.is_extended();
3182            if size == 16 {
3183                buf.push(0x66);
3184            }
3185            if needs_rex(w, r, false, b) {
3186                buf.push(rex(w, r, false, b));
3187            }
3188            buf.push(0x0F);
3189            let base = match digit {
3190                4 => 0xA3, // BT
3191                5 => 0xAB, // BTS
3192                6 => 0xB3, // BTR
3193                7 => 0xBB, // BTC
3194                _ => 0xA3,
3195            };
3196            buf.push(base);
3197            buf.push(modrm(0b11, src.base_code(), dst.base_code()));
3198        }
3199        (Operand::Memory(mem), Operand::Register(src)) => {
3200            let size = mem.size.map_or(reg_size(*src), |s| s.bits() as u8);
3201            let w = size == 64;
3202            let r = src.is_extended();
3203            let x = mem.index.is_some_and(|r| r.is_extended());
3204            let b = mem.base.is_some_and(|r| r.is_extended());
3205            if size == 16 {
3206                buf.push(0x66);
3207            }
3208            if needs_rex(w, r, x, b) {
3209                buf.push(rex(w, r, x, b));
3210            }
3211            buf.push(0x0F);
3212            let base = match digit {
3213                4 => 0xA3,
3214                5 => 0xAB,
3215                6 => 0xB3,
3216                7 => 0xBB,
3217                _ => 0xA3,
3218            };
3219            buf.push(base);
3220            emit_mem_modrm(buf, src.base_code(), mem);
3221        }
3222        (Operand::Memory(mem), Operand::Immediate(imm)) => {
3223            let size = instr
3224                .size_hint
3225                .map_or(mem.size.map_or(32u8, |s| s.bits() as u8), |s| {
3226                    s.bits() as u8
3227                });
3228            let w = size == 64;
3229            let x = mem.index.is_some_and(|r| r.is_extended());
3230            let b = mem.base.is_some_and(|r| r.is_extended());
3231            if size == 16 {
3232                buf.push(0x66);
3233            }
3234            if needs_rex(w, false, x, b) {
3235                buf.push(rex(w, false, x, b));
3236            }
3237            buf.push(0x0F);
3238            buf.push(0xBA);
3239            emit_mem_modrm(buf, digit, mem);
3240            buf.push(*imm as u8);
3241        }
3242        _ => {
3243            return Err(invalid_operands(
3244                &instr.mnemonic,
3245                "unsupported operand combination",
3246                instr.span,
3247            ))
3248        }
3249    }
3250    Ok(())
3251}
3252
3253#[cfg(any(feature = "x86", feature = "x86_64"))]
3254pub(crate) fn encode_bsf_bsr(
3255    buf: &mut InstrBytes,
3256    ops: &OperandList,
3257    instr: &Instruction,
3258    opcode2: u8,
3259) -> Result<(), AsmError> {
3260    if ops.len() != 2 {
3261        return Err(invalid_operands(
3262            &instr.mnemonic,
3263            "expected 2 operands",
3264            instr.span,
3265        ));
3266    }
3267    // BSF/BSR only support 16/32/64-bit operands
3268    if let Operand::Register(r) = &ops[0] {
3269        if reg_size(*r) == 8 {
3270            return Err(invalid_operands(
3271                &instr.mnemonic,
3272                "8-bit operands not supported",
3273                instr.span,
3274            ));
3275        }
3276    }
3277    match (&ops[0], &ops[1]) {
3278        (Operand::Register(dst), Operand::Register(src)) => {
3279            let size = reg_size(*dst);
3280            let w = size == 64;
3281            let r = dst.is_extended();
3282            let b = src.is_extended();
3283            if size == 16 {
3284                buf.push(0x66);
3285            }
3286            if needs_rex(w, r, false, b) {
3287                buf.push(rex(w, r, false, b));
3288            }
3289            buf.push(0x0F);
3290            buf.push(opcode2);
3291            buf.push(modrm(0b11, dst.base_code(), src.base_code()));
3292        }
3293        (Operand::Register(dst), Operand::Memory(mem)) => {
3294            emit_rex_for_reg_mem(buf, *dst, mem)?;
3295            buf.push(0x0F);
3296            buf.push(opcode2);
3297            emit_mem_modrm(buf, dst.base_code(), mem);
3298        }
3299        _ => {
3300            return Err(invalid_operands(
3301                &instr.mnemonic,
3302                "expected reg, r/m",
3303                instr.span,
3304            ))
3305        }
3306    }
3307    Ok(())
3308}
3309
3310/// Unified encoder for F3 0F xx reg,r/m class instructions (popcnt, lzcnt, tzcnt).
3311#[inline]
3312#[cfg(any(feature = "x86", feature = "x86_64"))]
3313fn encode_f3_0f_rm(
3314    buf: &mut InstrBytes,
3315    ops: &OperandList,
3316    mnemonic: &str,
3317    opcode: u8,
3318    span: Span,
3319) -> Result<(), AsmError> {
3320    if ops.len() != 2 {
3321        return Err(invalid_operands(mnemonic, "expected 2 operands", span));
3322    }
3323    if let Operand::Register(r) = &ops[0] {
3324        if reg_size(*r) == 8 {
3325            return Err(invalid_operands(
3326                mnemonic,
3327                "8-bit operands not supported",
3328                span,
3329            ));
3330        }
3331    }
3332    match (&ops[0], &ops[1]) {
3333        (Operand::Register(dst), Operand::Register(src)) => {
3334            let size = reg_size(*dst);
3335            let w = size == 64;
3336            let r = dst.is_extended();
3337            let b = src.is_extended();
3338            buf.push(0xF3);
3339            if size == 16 {
3340                buf.push(0x66);
3341            }
3342            if needs_rex(w, r, false, b) {
3343                buf.push(rex(w, r, false, b));
3344            }
3345            buf.push(0x0F);
3346            buf.push(opcode);
3347            buf.push(modrm(0b11, dst.base_code(), src.base_code()));
3348        }
3349        (Operand::Register(dst), Operand::Memory(mem)) => {
3350            buf.push(0xF3);
3351            emit_rex_for_reg_mem(buf, *dst, mem)?;
3352            buf.push(0x0F);
3353            buf.push(opcode);
3354            emit_mem_modrm(buf, dst.base_code(), mem);
3355        }
3356        _ => return Err(invalid_operands(mnemonic, "expected reg, r/m", span)),
3357    }
3358    Ok(())
3359}
3360
3361#[cfg(any(feature = "x86", feature = "x86_64"))]
3362pub(crate) fn encode_popcnt(
3363    buf: &mut InstrBytes,
3364    ops: &OperandList,
3365    instr: &Instruction,
3366) -> Result<(), AsmError> {
3367    encode_f3_0f_rm(buf, ops, "popcnt", 0xB8, instr.span)
3368}
3369
3370#[cfg(any(feature = "x86", feature = "x86_64"))]
3371pub(crate) fn encode_lzcnt(
3372    buf: &mut InstrBytes,
3373    ops: &OperandList,
3374    instr: &Instruction,
3375) -> Result<(), AsmError> {
3376    encode_f3_0f_rm(buf, ops, "lzcnt", 0xBD, instr.span)
3377}
3378
3379#[cfg(any(feature = "x86", feature = "x86_64"))]
3380pub(crate) fn encode_tzcnt(
3381    buf: &mut InstrBytes,
3382    ops: &OperandList,
3383    instr: &Instruction,
3384) -> Result<(), AsmError> {
3385    encode_f3_0f_rm(buf, ops, "tzcnt", 0xBC, instr.span)
3386}
3387
3388#[cfg(any(feature = "x86", feature = "x86_64"))]
3389pub(crate) fn encode_bswap(
3390    buf: &mut InstrBytes,
3391    ops: &OperandList,
3392    instr: &Instruction,
3393) -> Result<(), AsmError> {
3394    if ops.len() != 1 {
3395        return Err(invalid_operands("bswap", "expected 1 operand", instr.span));
3396    }
3397    match &ops[0] {
3398        Operand::Register(reg) => {
3399            let size = reg_size(*reg);
3400            if size == 8 {
3401                return Err(invalid_operands(
3402                    "bswap",
3403                    "8-bit operands not supported",
3404                    instr.span,
3405                ));
3406            }
3407            if size == 16 {
3408                return Err(invalid_operands(
3409                    "bswap",
3410                    "16-bit bswap has undefined behavior; use xchg or rol instead",
3411                    instr.span,
3412                ));
3413            }
3414            let w = size == 64;
3415            let b = reg.is_extended();
3416            if needs_rex(w, false, false, b) {
3417                buf.push(rex(w, false, false, b));
3418            }
3419            buf.push(0x0F);
3420            buf.push(0xC8 + reg.base_code());
3421        }
3422        _ => return Err(invalid_operands("bswap", "expected register", instr.span)),
3423    }
3424    Ok(())
3425}
3426
3427/// Emit an immediate value of the given size, in bits.
3428///
3429/// `size` comes from this crate's own encoding tables, so anything other than
3430/// 8/16/32/64 is an internal bug. It is handled by rounding up to the next
3431/// supported width rather than panicking: the assembler processes untrusted
3432/// input, and a panic in a `no_std` embedding aborts rather than unwinding.
3433/// Debug builds still trip an assertion so the bug surfaces in testing.
3434#[cfg(any(feature = "x86", feature = "x86_64"))]
3435pub(crate) fn emit_imm(buf: &mut InstrBytes, imm: i128, size: u8) {
3436    debug_assert!(
3437        matches!(size, 8 | 16 | 32 | 64),
3438        "emit_imm: unsupported immediate size {size} (expected 8, 16, 32, or 64)"
3439    );
3440    match size {
3441        0..=8 => buf.push(imm as u8),
3442        9..=16 => buf.extend_from_slice(&(imm as u16).to_le_bytes()),
3443        17..=32 => buf.extend_from_slice(&(imm as u32).to_le_bytes()),
3444        _ => buf.extend_from_slice(&(imm as u64).to_le_bytes()),
3445    }
3446}
3447
3448#[inline]
3449#[cfg(any(feature = "x86", feature = "x86_64"))]
3450pub(crate) fn invalid_operands(_mnemonic: &str, detail: &str, span: Span) -> AsmError {
3451    AsmError::InvalidOperands {
3452        detail: String::from(detail),
3453        span,
3454    }
3455}
3456
3457// ─── SSE / XMM encoder helpers ────────────────────────────────
3458
3459/// Emit REX prefix for an XMM-register,XMM-register or XMM-register,GPR form.
3460/// `w` controls REX.W (needed for movd/movq 64-bit forms).
3461#[cfg(any(feature = "x86", feature = "x86_64"))]
3462fn emit_rex_sse_rr(buf: &mut InstrBytes, w: bool, reg: Register, rm: Register) {
3463    let r = reg.is_extended();
3464    let b = rm.is_extended();
3465    if needs_rex(w, r, false, b) {
3466        buf.push(rex(w, r, false, b));
3467    }
3468}
3469
3470/// Emit REX prefix for XMM register + memory operand.
3471/// `w` controls REX.W.
3472#[cfg(any(feature = "x86", feature = "x86_64"))]
3473fn emit_rex_sse_rm(buf: &mut InstrBytes, w: bool, reg: Register, mem: &MemoryOperand) {
3474    let r = reg.is_extended();
3475    let x = mem.index.is_some_and(|r| r.is_extended());
3476    let b = mem.base.is_some_and(|r| r.is_extended());
3477    if needs_rex(w, r, x, b) {
3478        buf.push(rex(w, r, x, b));
3479    }
3480}
3481
3482/// Encode an SSE instruction: xmm, xmm/m  (or xmm/m, xmm for stores).
3483///
3484/// Pattern: `[mandatory_prefix] [REX] opcode_bytes ModR/M`
3485///
3486/// `opcode` is the full opcode slice (e.g. `&[0x0F, 0x58]` for ADDPS).
3487/// `mandatory_prefix` is 0 (none), 0x66, 0xF3, or 0xF2.
3488/// `rex_w` forces REX.W (needed for 64-bit movd/movq).
3489#[cfg(any(feature = "x86", feature = "x86_64"))]
3490pub(crate) fn encode_sse_rr(
3491    buf: &mut InstrBytes,
3492    mandatory_prefix: u8,
3493    opcode: &[u8],
3494    dst: Register,
3495    src: Register,
3496    rex_w: bool,
3497) {
3498    if mandatory_prefix != 0 {
3499        buf.push(mandatory_prefix);
3500    }
3501    emit_rex_sse_rr(buf, rex_w, dst, src);
3502    buf.extend_from_slice(opcode);
3503    buf.push(modrm(0b11, dst.base_code(), src.base_code()));
3504}
3505
3506/// Encode SSE xmm, mem (load direction): [prefix] REX opcode ModR/M [SIB] [disp]
3507#[cfg(any(feature = "x86", feature = "x86_64"))]
3508pub(crate) fn encode_sse_rm(
3509    buf: &mut InstrBytes,
3510    mandatory_prefix: u8,
3511    opcode: &[u8],
3512    reg: Register,
3513    mem: &MemoryOperand,
3514    reloc: &mut Option<Relocation>,
3515    rex_w: bool,
3516) {
3517    if mandatory_prefix != 0 {
3518        buf.push(mandatory_prefix);
3519    }
3520    emit_rex_sse_rm(buf, rex_w, reg, mem);
3521    buf.extend_from_slice(opcode);
3522    let disp_off = emit_mem_modrm(buf, reg.base_code(), mem);
3523    set_mem_reloc(reloc, mem, disp_off, buf.len());
3524}
3525
3526/// Encode SSE mem, xmm (store direction): [prefix] REX opcode ModR/M [SIB] [disp]
3527/// Same as encode_sse_rm but with swapped semantics (reg is source, mem is dest).
3528#[cfg(any(feature = "x86", feature = "x86_64"))]
3529pub(crate) fn encode_sse_mr(
3530    buf: &mut InstrBytes,
3531    mandatory_prefix: u8,
3532    opcode: &[u8],
3533    mem: &MemoryOperand,
3534    reg: Register,
3535    reloc: &mut Option<Relocation>,
3536    rex_w: bool,
3537) {
3538    // Encoding is identical — reg field goes in ModR/M.reg, mem in ModR/M.rm
3539    encode_sse_rm(buf, mandatory_prefix, opcode, reg, mem, reloc, rex_w);
3540}
3541
3542/// Generic SSE two-operand encoder: xmm, xmm/m or xmm/m, xmm.
3543///
3544/// `reverse` = true means the first operand is memory (store direction, uses `store_opcode`).
3545/// `load_opcode` / `store_opcode` are the full opcode byte sequences.
3546#[cfg(any(feature = "x86", feature = "x86_64"))]
3547pub(crate) fn encode_sse_op(
3548    buf: &mut InstrBytes,
3549    ops: &OperandList,
3550    instr: &Instruction,
3551    mandatory_prefix: u8,
3552    load_opcode: &[u8],
3553    store_opcode: Option<&[u8]>,
3554    reloc: &mut Option<Relocation>,
3555) -> Result<(), AsmError> {
3556    match (ops.first(), ops.get(1)) {
3557        // xmm, xmm
3558        (Some(Operand::Register(dst)), Some(Operand::Register(src)))
3559            if dst.is_xmm() && src.is_xmm() =>
3560        {
3561            encode_sse_rr(buf, mandatory_prefix, load_opcode, *dst, *src, false);
3562            Ok(())
3563        }
3564        // xmm, mem
3565        (Some(Operand::Register(dst)), Some(Operand::Memory(mem))) if dst.is_xmm() => {
3566            encode_sse_rm(buf, mandatory_prefix, load_opcode, *dst, mem, reloc, false);
3567            Ok(())
3568        }
3569        // mem, xmm (store)
3570        (Some(Operand::Memory(mem)), Some(Operand::Register(src))) if src.is_xmm() => {
3571            let opcode = store_opcode.unwrap_or(load_opcode);
3572            encode_sse_mr(buf, mandatory_prefix, opcode, mem, *src, reloc, false);
3573            Ok(())
3574        }
3575        _ => Err(invalid_operands(
3576            instr.mnemonic.as_str(),
3577            "expected xmm,xmm/m or m,xmm operands",
3578            instr.span,
3579        )),
3580    }
3581}
3582
3583/// SSE instruction with an immediate byte: xmm, xmm/m, imm8.
3584#[cfg(any(feature = "x86", feature = "x86_64"))]
3585pub(crate) fn encode_sse_imm(
3586    buf: &mut InstrBytes,
3587    ops: &OperandList,
3588    instr: &Instruction,
3589    mandatory_prefix: u8,
3590    opcode: &[u8],
3591    reloc: &mut Option<Relocation>,
3592) -> Result<(), AsmError> {
3593    let imm = match ops.get(2) {
3594        Some(Operand::Immediate(v)) => *v,
3595        _ => {
3596            return Err(invalid_operands(
3597                instr.mnemonic.as_str(),
3598                "expected xmm, xmm/m, imm8",
3599                instr.span,
3600            ));
3601        }
3602    };
3603    match (ops.first(), ops.get(1)) {
3604        (Some(Operand::Register(dst)), Some(Operand::Register(src)))
3605            if dst.is_xmm() && src.is_xmm() =>
3606        {
3607            encode_sse_rr(buf, mandatory_prefix, opcode, *dst, *src, false);
3608        }
3609        (Some(Operand::Register(dst)), Some(Operand::Memory(mem))) if dst.is_xmm() => {
3610            encode_sse_rm(buf, mandatory_prefix, opcode, *dst, mem, reloc, false);
3611        }
3612        _ => {
3613            return Err(invalid_operands(
3614                instr.mnemonic.as_str(),
3615                "expected xmm, xmm/m, imm8",
3616                instr.span,
3617            ));
3618        }
3619    }
3620    buf.push(imm as u8);
3621    Ok(())
3622}
3623
3624/// Encode MOVD/MOVQ — GP ↔ XMM transfers.
3625#[cfg(any(feature = "x86", feature = "x86_64"))]
3626pub(crate) fn encode_movd_movq(
3627    buf: &mut InstrBytes,
3628    ops: &OperandList,
3629    instr: &Instruction,
3630    reloc: &mut Option<Relocation>,
3631    is_movq: bool,
3632) -> Result<(), AsmError> {
3633    match (ops.first(), ops.get(1)) {
3634        // movd/movq xmm, r/m32/64 — load: 66 [REX.W] 0F 6E /r
3635        (Some(Operand::Register(dst)), Some(Operand::Register(src)))
3636            if dst.is_xmm() && !src.is_xmm() =>
3637        {
3638            let w = is_movq || src.size_bits() == 64;
3639            encode_sse_rr(buf, 0x66, &[0x0F, 0x6E], *dst, *src, w);
3640            Ok(())
3641        }
3642        (Some(Operand::Register(dst)), Some(Operand::Memory(mem))) if dst.is_xmm() => {
3643            let w = is_movq;
3644            encode_sse_rm(buf, 0x66, &[0x0F, 0x6E], *dst, mem, reloc, w);
3645            Ok(())
3646        }
3647        // movd/movq r/m32/64, xmm — store: 66 [REX.W] 0F 7E /r
3648        (Some(Operand::Register(dst)), Some(Operand::Register(src)))
3649            if !dst.is_xmm() && src.is_xmm() =>
3650        {
3651            let w = is_movq || dst.size_bits() == 64;
3652            // Note: in the store form, the XMM reg is the source but goes in ModR/M.reg
3653            encode_sse_rr(buf, 0x66, &[0x0F, 0x7E], *src, *dst, w);
3654            Ok(())
3655        }
3656        (Some(Operand::Memory(mem)), Some(Operand::Register(src))) if src.is_xmm() => {
3657            let w = is_movq;
3658            encode_sse_mr(buf, 0x66, &[0x0F, 0x7E], mem, *src, reloc, w);
3659            Ok(())
3660        }
3661        // movq xmm, xmm — use F3 0F 7E /r
3662        (Some(Operand::Register(dst)), Some(Operand::Register(src)))
3663            if dst.is_xmm() && src.is_xmm() =>
3664        {
3665            encode_sse_rr(buf, 0xF3, &[0x0F, 0x7E], *dst, *src, false);
3666            Ok(())
3667        }
3668        _ => Err(invalid_operands(
3669            instr.mnemonic.as_str(),
3670            "expected xmm,r/m or r/m,xmm operands",
3671            instr.span,
3672        )),
3673    }
3674}
3675
3676/// Encode CVTSI2SS / CVTSI2SD: xmm, r/m32/64.
3677#[cfg(any(feature = "x86", feature = "x86_64"))]
3678pub(crate) fn encode_cvtsi2(
3679    buf: &mut InstrBytes,
3680    ops: &OperandList,
3681    instr: &Instruction,
3682    mandatory_prefix: u8,
3683    reloc: &mut Option<Relocation>,
3684) -> Result<(), AsmError> {
3685    match (ops.first(), ops.get(1)) {
3686        (Some(Operand::Register(dst)), Some(Operand::Register(src)))
3687            if dst.is_xmm() && !src.is_xmm() =>
3688        {
3689            let w = src.size_bits() == 64;
3690            encode_sse_rr(buf, mandatory_prefix, &[0x0F, 0x2A], *dst, *src, w);
3691            Ok(())
3692        }
3693        (Some(Operand::Register(dst)), Some(Operand::Memory(mem))) if dst.is_xmm() => {
3694            let w = mem.size == Some(OperandSize::Qword);
3695            encode_sse_rm(buf, mandatory_prefix, &[0x0F, 0x2A], *dst, mem, reloc, w);
3696            Ok(())
3697        }
3698        _ => Err(invalid_operands(
3699            instr.mnemonic.as_str(),
3700            "expected xmm, r/m32 or xmm, r/m64",
3701            instr.span,
3702        )),
3703    }
3704}
3705
3706/// Encode CVTSS2SI / CVTSD2SI / CVTTSS2SI / CVTTSD2SI: r32/64, xmm/m.
3707#[cfg(any(feature = "x86", feature = "x86_64"))]
3708pub(crate) fn encode_cvt2si(
3709    buf: &mut InstrBytes,
3710    ops: &OperandList,
3711    instr: &Instruction,
3712    mandatory_prefix: u8,
3713    opcode2: u8,
3714    reloc: &mut Option<Relocation>,
3715) -> Result<(), AsmError> {
3716    match (ops.first(), ops.get(1)) {
3717        (Some(Operand::Register(dst)), Some(Operand::Register(src)))
3718            if !dst.is_xmm() && src.is_xmm() =>
3719        {
3720            let w = dst.size_bits() == 64;
3721            encode_sse_rr(buf, mandatory_prefix, &[0x0F, opcode2], *dst, *src, w);
3722            Ok(())
3723        }
3724        (Some(Operand::Register(dst)), Some(Operand::Memory(mem))) if !dst.is_xmm() => {
3725            let w = dst.size_bits() == 64;
3726            encode_sse_rm(buf, mandatory_prefix, &[0x0F, opcode2], *dst, mem, reloc, w);
3727            Ok(())
3728        }
3729        _ => Err(invalid_operands(
3730            instr.mnemonic.as_str(),
3731            "expected r32/r64, xmm/m operands",
3732            instr.span,
3733        )),
3734    }
3735}
3736
3737/// Encode prefetch family: 0F 18 /digit (memory-only).
3738#[cfg(any(feature = "x86", feature = "x86_64"))]
3739pub(crate) fn encode_prefetch(
3740    buf: &mut InstrBytes,
3741    ops: &OperandList,
3742    instr: &Instruction,
3743    digit: u8,
3744) -> Result<(), AsmError> {
3745    match ops.first() {
3746        Some(Operand::Memory(mem)) => {
3747            // No REX.W, no prefix for prefetch
3748            let x = mem.index.is_some_and(|r| r.is_extended());
3749            let b = mem.base.is_some_and(|r| r.is_extended());
3750            if needs_rex(false, false, x, b) {
3751                buf.push(rex(false, false, x, b));
3752            }
3753            buf.extend_from_slice(&[0x0F, 0x18]);
3754            emit_mem_modrm(buf, digit, mem);
3755            Ok(())
3756        }
3757        _ => Err(invalid_operands(
3758            instr.mnemonic.as_str(),
3759            "expected memory operand",
3760            instr.span,
3761        )),
3762    }
3763}
3764
3765/// Encode CLFLUSH: 0F AE /7 (memory-only).
3766#[cfg(any(feature = "x86", feature = "x86_64"))]
3767pub(crate) fn encode_clflush(
3768    buf: &mut InstrBytes,
3769    ops: &OperandList,
3770    instr: &Instruction,
3771) -> Result<(), AsmError> {
3772    match ops.first() {
3773        Some(Operand::Memory(mem)) => {
3774            let x = mem.index.is_some_and(|r| r.is_extended());
3775            let b = mem.base.is_some_and(|r| r.is_extended());
3776            if needs_rex(false, false, x, b) {
3777                buf.push(rex(false, false, x, b));
3778            }
3779            buf.extend_from_slice(&[0x0F, 0xAE]);
3780            emit_mem_modrm(buf, 7, mem);
3781            Ok(())
3782        }
3783        _ => Err(invalid_operands(
3784            "clflush",
3785            "expected memory operand",
3786            instr.span,
3787        )),
3788    }
3789}
3790
3791/// Encode CLFLUSHOPT: 66 0F AE /7 (memory-only).
3792#[cfg(any(feature = "x86", feature = "x86_64"))]
3793pub(crate) fn encode_clflushopt(
3794    buf: &mut InstrBytes,
3795    ops: &OperandList,
3796    instr: &Instruction,
3797) -> Result<(), AsmError> {
3798    match ops.first() {
3799        Some(Operand::Memory(mem)) => {
3800            buf.push(0x66);
3801            let x = mem.index.is_some_and(|r| r.is_extended());
3802            let b = mem.base.is_some_and(|r| r.is_extended());
3803            if needs_rex(false, false, x, b) {
3804                buf.push(rex(false, false, x, b));
3805            }
3806            buf.extend_from_slice(&[0x0F, 0xAE]);
3807            emit_mem_modrm(buf, 7, mem);
3808            Ok(())
3809        }
3810        _ => Err(invalid_operands(
3811            "clflushopt",
3812            "expected memory operand",
3813            instr.span,
3814        )),
3815    }
3816}
3817
3818/// Encode CLWB: 66 0F AE /6 (memory-only).
3819#[cfg(any(feature = "x86", feature = "x86_64"))]
3820pub(crate) fn encode_clwb(
3821    buf: &mut InstrBytes,
3822    ops: &OperandList,
3823    instr: &Instruction,
3824) -> Result<(), AsmError> {
3825    match ops.first() {
3826        Some(Operand::Memory(mem)) => {
3827            buf.push(0x66);
3828            let x = mem.index.is_some_and(|r| r.is_extended());
3829            let b = mem.base.is_some_and(|r| r.is_extended());
3830            if needs_rex(false, false, x, b) {
3831                buf.push(rex(false, false, x, b));
3832            }
3833            buf.extend_from_slice(&[0x0F, 0xAE]);
3834            emit_mem_modrm(buf, 6, mem);
3835            Ok(())
3836        }
3837        _ => Err(invalid_operands(
3838            "clwb",
3839            "expected memory operand",
3840            instr.span,
3841        )),
3842    }
3843}
3844
3845/// Encode PREFETCHW: 0F 0D /1 (memory-only).
3846#[cfg(any(feature = "x86", feature = "x86_64"))]
3847pub(crate) fn encode_prefetchw(
3848    buf: &mut InstrBytes,
3849    ops: &OperandList,
3850    instr: &Instruction,
3851) -> Result<(), AsmError> {
3852    match ops.first() {
3853        Some(Operand::Memory(mem)) => {
3854            let x = mem.index.is_some_and(|r| r.is_extended());
3855            let b = mem.base.is_some_and(|r| r.is_extended());
3856            if needs_rex(false, false, x, b) {
3857                buf.push(rex(false, false, x, b));
3858            }
3859            buf.extend_from_slice(&[0x0F, 0x0D]);
3860            emit_mem_modrm(buf, 1, mem);
3861            Ok(())
3862        }
3863        _ => Err(invalid_operands(
3864            "prefetchw",
3865            "expected memory operand",
3866            instr.span,
3867        )),
3868    }
3869}
3870
3871/// Encode CRC32: F2 [REX.W] 0F 38 F0/F1 /r.
3872#[cfg(any(feature = "x86", feature = "x86_64"))]
3873pub(crate) fn encode_crc32(
3874    buf: &mut InstrBytes,
3875    ops: &OperandList,
3876    instr: &Instruction,
3877) -> Result<(), AsmError> {
3878    match (ops.first(), ops.get(1)) {
3879        (Some(Operand::Register(dst)), Some(Operand::Register(src))) => {
3880            let dst_s = dst.size_bits();
3881            let src_s = src.size_bits();
3882            if dst_s != 32 && dst_s != 64 {
3883                return Err(invalid_operands(
3884                    "crc32",
3885                    "destination must be r32 or r64",
3886                    instr.span,
3887                ));
3888            }
3889            buf.push(0xF2);
3890            let w = dst_s == 64;
3891            let opcode2 = if src_s == 8 { 0xF0u8 } else { 0xF1 };
3892            if src_s == 16 {
3893                buf.push(0x66);
3894            }
3895            emit_rex_sse_rr(buf, w, *dst, *src);
3896            buf.extend_from_slice(&[0x0F, 0x38, opcode2]);
3897            buf.push(modrm(0b11, dst.base_code(), src.base_code()));
3898            Ok(())
3899        }
3900        (Some(Operand::Register(dst)), Some(Operand::Memory(mem))) => {
3901            let dst_s = dst.size_bits();
3902            if dst_s != 32 && dst_s != 64 {
3903                return Err(invalid_operands(
3904                    "crc32",
3905                    "destination must be r32 or r64",
3906                    instr.span,
3907                ));
3908            }
3909            let src_s = instr.size_hint.map_or_else(
3910                || mem.size.map_or(32u8, |s| s.bits() as u8),
3911                |s| s.bits() as u8,
3912            );
3913            buf.push(0xF2);
3914            let w = dst_s == 64;
3915            let opcode2 = if src_s == 8 { 0xF0u8 } else { 0xF1 };
3916            if src_s == 16 {
3917                buf.push(0x66);
3918            }
3919            emit_rex_sse_rm(buf, w, *dst, mem);
3920            buf.extend_from_slice(&[0x0F, 0x38, opcode2]);
3921            emit_mem_modrm(buf, dst.base_code(), mem);
3922            Ok(())
3923        }
3924        _ => Err(invalid_operands(
3925            "crc32",
3926            "expected r32/r64, r/m operands",
3927            instr.span,
3928        )),
3929    }
3930}
3931
3932// ─── VEX / EVEX prefix encoding infrastructure ──────────────────────────────
3933//
3934// These functions implement VEX and EVEX prefix encoding for SSE/AVX instructions.
3935// They are not yet wired into the text-assembly path but are fully tested and
3936// ready for use once the mnemonic dispatch tables include VEX-encoded mnemonics.
3937
3938/// VEX prefix "pp" field (implied mandatory prefix).
3939///   0 = none, 1 = 0x66, 2 = 0xF3, 3 = 0xF2
3940#[cfg(any(feature = "x86", feature = "x86_64"))]
3941fn vex_pp(mandatory_prefix: u8) -> u8 {
3942    match mandatory_prefix {
3943        0x00 => 0b00,
3944        0x66 => 0b01,
3945        0xF3 => 0b10,
3946        0xF2 => 0b11,
3947        _ => 0b00,
3948    }
3949}
3950
3951/// VEX "m-mmmm" field (implied escape bytes).
3952///   1 = 0F, 2 = 0F 38, 3 = 0F 3A
3953#[cfg(any(feature = "x86", feature = "x86_64"))]
3954fn vex_mmmmm(escape: &[u8]) -> u8 {
3955    match escape {
3956        [0x0F] => 0b00001,
3957        [0x0F, 0x38] => 0b00010,
3958        [0x0F, 0x3A] => 0b00011,
3959        _ => 0b00001,
3960    }
3961}
3962
3963/// Emit a 2-byte VEX prefix: C5 [R vvvv L pp]
3964/// - R: inverted REX.R (1 = no extension)
3965/// - vvvv: inverted source register (NDS), 0b1111 = unused
3966/// - L: vector length (0 = 128-bit, 1 = 256-bit)
3967/// - pp: implied prefix
3968///
3969/// 2-byte VEX can only be used when:
3970///   - m-mmmm == 0b00001 (0F escape)
3971///   - W == 0
3972///   - X == 1 (no REX.X) and B == 1 (no REX.B)
3973#[cfg(any(feature = "x86", feature = "x86_64"))]
3974fn emit_vex2(buf: &mut InstrBytes, r: bool, vvvv: u8, l: bool, pp: u8) {
3975    let byte1 = (if r { 0 } else { 0x80 })
3976        | (((!vvvv) & 0x0F) << 3)
3977        | (if l { 0x04 } else { 0 })
3978        | (pp & 0x03);
3979    buf.push(0xC5);
3980    buf.push(byte1);
3981}
3982
3983/// Emit a 3-byte VEX prefix: C4 [R X B mmmmm] [W vvvv L pp]
3984#[cfg(any(feature = "x86", feature = "x86_64"))]
3985fn emit_vex3(
3986    buf: &mut InstrBytes,
3987    r: bool,
3988    x: bool,
3989    b: bool,
3990    mmmmm: u8,
3991    w: bool,
3992    vvvv: u8,
3993    l: bool,
3994    pp: u8,
3995) {
3996    let byte1 = (if r { 0 } else { 0x80 })
3997        | (if x { 0 } else { 0x40 })
3998        | (if b { 0 } else { 0x20 })
3999        | (mmmmm & 0x1F);
4000    let byte2 = (if w { 0x80 } else { 0 })
4001        | (((!vvvv) & 0x0F) << 3)
4002        | (if l { 0x04 } else { 0 })
4003        | (pp & 0x03);
4004    buf.push(0xC4);
4005    buf.push(byte1);
4006    buf.push(byte2);
4007}
4008
4009/// Choose and emit the most compact VEX prefix (2-byte if possible, else 3-byte).
4010/// For VEX-encoded instructions.
4011///
4012/// Parameters:
4013/// - `reg`: the ModR/M reg field register (or the first source for some forms)
4014/// - `vvvv_reg`: the VEX.vvvv register (NDS/NDD source), or 0 if unused
4015/// - `rm_extended`: whether the R/M or base register is extended (R8-R15, etc.)
4016/// - `x_extended`: whether the SIB index register is extended
4017/// - `w`: REX.W equivalent
4018/// - `l`: vector length (false = 128, true = 256)
4019/// - `pp`: implied mandatory prefix
4020/// - `escape`: the escape byte sequence (e.g., &[0x0F] or &[0x0F, 0x38])
4021#[cfg(any(feature = "x86", feature = "x86_64"))]
4022fn emit_vex_prefix(
4023    buf: &mut InstrBytes,
4024    reg_extended: bool,
4025    x_extended: bool,
4026    rm_extended: bool,
4027    w: bool,
4028    vvvv: u8,
4029    l: bool,
4030    pp: u8,
4031    escape: &[u8],
4032) {
4033    let mmmmm = vex_mmmmm(escape);
4034    // 2-byte VEX can be used when: mmmmm == 0b00001, W == 0, X == 1 (not extended), B == 1 (not extended)
4035    if mmmmm == 0b00001 && !w && !x_extended && !rm_extended {
4036        emit_vex2(buf, reg_extended, vvvv, l, pp);
4037    } else {
4038        emit_vex3(
4039            buf,
4040            reg_extended,
4041            x_extended,
4042            rm_extended,
4043            mmmmm,
4044            w,
4045            vvvv,
4046            l,
4047            pp,
4048        );
4049    }
4050}
4051
4052/// Encode a VEX-prefix instruction: reg, reg (3-operand non-destructive, e.g., vaddps xmm0, xmm1, xmm2)
4053/// `dst` is the ModR/M reg field, `src1` is VEX.vvvv (NDS), `src2` is ModR/M r/m field
4054#[cfg(any(feature = "x86", feature = "x86_64"))]
4055pub(crate) fn encode_vex_rrr(
4056    buf: &mut InstrBytes,
4057    pp: u8,
4058    escape: &[u8],
4059    opcode: u8,
4060    dst: Register,
4061    src1: Register,
4062    src2: Register,
4063    w: bool,
4064    l: bool,
4065) {
4066    let mandatory_pp = vex_pp(pp);
4067    emit_vex_prefix(
4068        buf,
4069        dst.is_extended(),
4070        false,
4071        src2.is_extended(),
4072        w,
4073        src1.base_code() | if src1.is_extended() { 8 } else { 0 },
4074        l,
4075        mandatory_pp,
4076        escape,
4077    );
4078    buf.push(opcode);
4079    buf.push(0xC0 | (dst.base_code() << 3) | src2.base_code());
4080}
4081
4082/// Encode VEX reg, [mem] (3-operand: dst = reg, src1 = vvvv, src2 = mem)
4083#[cfg(any(feature = "x86", feature = "x86_64"))]
4084pub(crate) fn encode_vex_rrm(
4085    buf: &mut InstrBytes,
4086    pp: u8,
4087    escape: &[u8],
4088    opcode: u8,
4089    dst: Register,
4090    src1: Register,
4091    mem: &MemoryOperand,
4092    reloc: &mut Option<Relocation>,
4093    w: bool,
4094    l: bool,
4095) {
4096    let mandatory_pp = vex_pp(pp);
4097    let x_ext = mem.index.is_some_and(|r| r.is_extended());
4098    let b_ext = mem.base.is_some_and(|r| r.is_extended());
4099    emit_vex_prefix(
4100        buf,
4101        dst.is_extended(),
4102        x_ext,
4103        b_ext,
4104        w,
4105        src1.base_code() | if src1.is_extended() { 8 } else { 0 },
4106        l,
4107        mandatory_pp,
4108        escape,
4109    );
4110    buf.push(opcode);
4111    emit_mem_modrm(buf, dst.base_code(), mem);
4112    if let Some(ref mut rel) = reloc {
4113        rel.offset = buf.len() - 4;
4114    }
4115}
4116
4117/// Generic VEX-encoded SSE/AVX instruction dispatcher.
4118/// Handles the common patterns:
4119///   - vop xmm/ymm, xmm/ymm, xmm/ymm  (3 register operands, NDS form)
4120///   - vop xmm/ymm, xmm/ymm, [mem]     (reg, reg, mem)
4121///   - vop xmm/ymm, xmm/ymm            (2 operands => dst=src1=first, src2=second for moves)
4122///   - vop [mem], xmm/ymm               (store, if store_opcode given)
4123#[cfg(any(feature = "x86", feature = "x86_64"))]
4124pub(crate) fn encode_vex_op(
4125    buf: &mut InstrBytes,
4126    ops: &OperandList,
4127    instr: &Instruction,
4128    pp: u8,
4129    escape: &[u8],
4130    load_opcode: u8,
4131    store_opcode: Option<u8>,
4132    w: bool,
4133    reloc: &mut Option<Relocation>,
4134) -> Result<(), AsmError> {
4135    use Operand::*;
4136    let o = (ops.first(), ops.get(1), ops.get(2));
4137    match o {
4138        // 3-operand: reg, reg, reg
4139        (Some(Register(dst)), Some(Register(src1)), Some(Register(src2)))
4140            if dst.is_vector() && src1.is_vector() && src2.is_vector() =>
4141        {
4142            let l = dst.is_ymm() || src1.is_ymm();
4143            encode_vex_rrr(buf, pp, escape, load_opcode, *dst, *src1, *src2, w, l);
4144            Ok(())
4145        }
4146        // 3-operand: reg, reg, mem
4147        (Some(Register(dst)), Some(Register(src1)), Some(Memory(mem)))
4148            if dst.is_vector() && src1.is_vector() =>
4149        {
4150            let l = dst.is_ymm() || src1.is_ymm();
4151            encode_vex_rrm(buf, pp, escape, load_opcode, *dst, *src1, mem, reloc, w, l);
4152            Ok(())
4153        }
4154        // 2-operand reg, reg (move-like: vvvv unused)
4155        (Some(Register(dst)), Some(Register(src)), None) if dst.is_vector() && src.is_vector() => {
4156            let l = dst.is_ymm() || src.is_ymm();
4157            let mandatory_pp = vex_pp(pp);
4158            emit_vex_prefix(
4159                buf,
4160                dst.is_extended(),
4161                false,
4162                src.is_extended(),
4163                w,
4164                0,
4165                l,
4166                mandatory_pp,
4167                escape,
4168            );
4169            buf.push(load_opcode);
4170            buf.push(0xC0 | (dst.base_code() << 3) | src.base_code());
4171            Ok(())
4172        }
4173        // 2-operand reg, mem (load)
4174        (Some(Register(dst)), Some(Memory(mem)), None) if dst.is_vector() => {
4175            let l = dst.is_ymm();
4176            let mandatory_pp = vex_pp(pp);
4177            let x_ext = mem.index.is_some_and(|r| r.is_extended());
4178            let b_ext = mem.base.is_some_and(|r| r.is_extended());
4179            emit_vex_prefix(
4180                buf,
4181                dst.is_extended(),
4182                x_ext,
4183                b_ext,
4184                w,
4185                0,
4186                l,
4187                mandatory_pp,
4188                escape,
4189            );
4190            buf.push(load_opcode);
4191            emit_mem_modrm(buf, dst.base_code(), mem);
4192            Ok(())
4193        }
4194        // 2-operand mem, reg (store)
4195        (Some(Memory(mem)), Some(Register(src)), None)
4196            if src.is_vector() && store_opcode.is_some() =>
4197        {
4198            let l = src.is_ymm();
4199            let mandatory_pp = vex_pp(pp);
4200            let x_ext = mem.index.is_some_and(|r| r.is_extended());
4201            let b_ext = mem.base.is_some_and(|r| r.is_extended());
4202            emit_vex_prefix(
4203                buf,
4204                src.is_extended(),
4205                x_ext,
4206                b_ext,
4207                w,
4208                0,
4209                l,
4210                mandatory_pp,
4211                escape,
4212            );
4213            // SAFETY: match guard `store_opcode.is_some()` guarantees Some
4214            buf.push(store_opcode.unwrap_or(0));
4215            emit_mem_modrm(buf, src.base_code(), mem);
4216            Ok(())
4217        }
4218        _ => Err(invalid_operands(
4219            &instr.mnemonic,
4220            "expected VEX xmm/ymm operands",
4221            instr.span,
4222        )),
4223    }
4224}
4225
4226/// VEX instruction with an immediate byte (4 operands: dst, src1, src2/mem, imm8)
4227#[cfg(any(feature = "x86", feature = "x86_64"))]
4228pub(crate) fn encode_vex_imm(
4229    buf: &mut InstrBytes,
4230    ops: &OperandList,
4231    instr: &Instruction,
4232    pp: u8,
4233    escape: &[u8],
4234    opcode: u8,
4235    w: bool,
4236    reloc: &mut Option<Relocation>,
4237) -> Result<(), AsmError> {
4238    use Operand::*;
4239    let o = (ops.first(), ops.get(1), ops.get(2), ops.get(3));
4240    match o {
4241        // 4-operand: reg, reg, reg, imm8
4242        (Some(Register(dst)), Some(Register(src1)), Some(Register(src2)), Some(Immediate(imm)))
4243            if dst.is_vector() && src1.is_vector() && src2.is_vector() =>
4244        {
4245            let l = dst.is_ymm() || src1.is_ymm();
4246            encode_vex_rrr(buf, pp, escape, opcode, *dst, *src1, *src2, w, l);
4247            buf.push(*imm as u8);
4248            Ok(())
4249        }
4250        // 4-operand: reg, reg, mem, imm8
4251        (Some(Register(dst)), Some(Register(src1)), Some(Memory(mem)), Some(Immediate(imm)))
4252            if dst.is_vector() && src1.is_vector() =>
4253        {
4254            let l = dst.is_ymm() || src1.is_ymm();
4255            encode_vex_rrm(buf, pp, escape, opcode, *dst, *src1, mem, reloc, w, l);
4256            buf.push(*imm as u8);
4257            Ok(())
4258        }
4259        // 3-operand: reg, reg/mem, imm8 (vvvv unused, like vpshufd)
4260        (Some(Register(dst)), Some(Register(src)), Some(Immediate(imm)), None)
4261            if dst.is_vector() && src.is_vector() =>
4262        {
4263            let l = dst.is_ymm() || src.is_ymm();
4264            let mandatory_pp = vex_pp(pp);
4265            emit_vex_prefix(
4266                buf,
4267                dst.is_extended(),
4268                false,
4269                src.is_extended(),
4270                w,
4271                0,
4272                l,
4273                mandatory_pp,
4274                escape,
4275            );
4276            buf.push(opcode);
4277            buf.push(0xC0 | (dst.base_code() << 3) | src.base_code());
4278            buf.push(*imm as u8);
4279            Ok(())
4280        }
4281        (Some(Register(dst)), Some(Memory(mem)), Some(Immediate(imm)), None) if dst.is_vector() => {
4282            let l = dst.is_ymm();
4283            let mandatory_pp = vex_pp(pp);
4284            let x_ext = mem.index.is_some_and(|r| r.is_extended());
4285            let b_ext = mem.base.is_some_and(|r| r.is_extended());
4286            emit_vex_prefix(
4287                buf,
4288                dst.is_extended(),
4289                x_ext,
4290                b_ext,
4291                w,
4292                0,
4293                l,
4294                mandatory_pp,
4295                escape,
4296            );
4297            buf.push(opcode);
4298            emit_mem_modrm(buf, dst.base_code(), mem);
4299            buf.push(*imm as u8);
4300            Ok(())
4301        }
4302        _ => Err(invalid_operands(
4303            &instr.mnemonic,
4304            "expected VEX xmm/ymm operands with imm8",
4305            instr.span,
4306        )),
4307    }
4308}
4309
4310/// VEX-encoded BMI instruction: reg, reg/mem (2-operand, VEX.vvvv = dst)
4311/// e.g., ANDN r32, r32, r/m32; BLSI r32, r/m32; TZCNT-like
4312#[cfg(any(feature = "x86", feature = "x86_64"))]
4313pub(crate) fn encode_vex_bmi_vex_ndd(
4314    buf: &mut InstrBytes,
4315    ops: &OperandList,
4316    instr: &Instruction,
4317    pp: u8,
4318    escape: &[u8],
4319    opcode: u8,
4320    w_from_size: bool,
4321) -> Result<(), AsmError> {
4322    use Operand::*;
4323    match (ops.first(), ops.get(1), ops.get(2)) {
4324        // 3-operand: r, r, r/m (e.g., andn eax, ebx, ecx)
4325        (Some(Register(dst)), Some(Register(src1)), Some(Register(src2))) => {
4326            let w = if w_from_size {
4327                dst.size_bits() == 64
4328            } else {
4329                false
4330            };
4331            let mandatory_pp = vex_pp(pp);
4332            emit_vex_prefix(
4333                buf,
4334                dst.is_extended(),
4335                false,
4336                src2.is_extended(),
4337                w,
4338                src1.base_code() | if src1.is_extended() { 8 } else { 0 },
4339                false,
4340                mandatory_pp,
4341                escape,
4342            );
4343            buf.push(opcode);
4344            buf.push(0xC0 | (dst.base_code() << 3) | src2.base_code());
4345            Ok(())
4346        }
4347        (Some(Register(dst)), Some(Register(src1)), Some(Memory(mem))) => {
4348            let w = if w_from_size {
4349                dst.size_bits() == 64
4350            } else {
4351                false
4352            };
4353            let mandatory_pp = vex_pp(pp);
4354            let x_ext = mem.index.is_some_and(|r| r.is_extended());
4355            let b_ext = mem.base.is_some_and(|r| r.is_extended());
4356            emit_vex_prefix(
4357                buf,
4358                dst.is_extended(),
4359                x_ext,
4360                b_ext,
4361                w,
4362                src1.base_code() | if src1.is_extended() { 8 } else { 0 },
4363                false,
4364                mandatory_pp,
4365                escape,
4366            );
4367            buf.push(opcode);
4368            emit_mem_modrm(buf, dst.base_code(), mem);
4369            Ok(())
4370        }
4371        // 2-operand forms (e.g., blsi r32, r/m32 — dst in VEX.vvvv, src in ModR/M r/m)
4372        (Some(Register(dst)), Some(Register(src)), None) => {
4373            let w = if w_from_size {
4374                dst.size_bits() == 64
4375            } else {
4376                false
4377            };
4378            let mandatory_pp = vex_pp(pp);
4379            emit_vex_prefix(
4380                buf,
4381                false, // reg field is the /digit, not a real register
4382                false,
4383                src.is_extended(),
4384                w,
4385                dst.base_code() | if dst.is_extended() { 8 } else { 0 },
4386                false,
4387                mandatory_pp,
4388                escape,
4389            );
4390            buf.push(opcode);
4391            // The reg field contains the /digit (set by caller via opcode encoding)
4392            // Actually for BLSI/BLSR/BLSMSK the reg field is fixed (/3, /1, /2)
4393            // This function sets reg=0; caller must set via a wrapper
4394            buf.push(0xC0 | src.base_code());
4395            Ok(())
4396        }
4397        (Some(Register(dst)), Some(Memory(mem)), None) => {
4398            let w = if w_from_size {
4399                dst.size_bits() == 64
4400            } else {
4401                false
4402            };
4403            let mandatory_pp = vex_pp(pp);
4404            let x_ext = mem.index.is_some_and(|r| r.is_extended());
4405            let b_ext = mem.base.is_some_and(|r| r.is_extended());
4406            emit_vex_prefix(
4407                buf,
4408                false,
4409                x_ext,
4410                b_ext,
4411                w,
4412                dst.base_code() | if dst.is_extended() { 8 } else { 0 },
4413                false,
4414                mandatory_pp,
4415                escape,
4416            );
4417            buf.push(opcode);
4418            emit_mem_modrm(buf, 0, mem);
4419            Ok(())
4420        }
4421        _ => Err(invalid_operands(
4422            &instr.mnemonic,
4423            "expected GP register operands",
4424            instr.span,
4425        )),
4426    }
4427}
4428
4429/// VEX BMI with /digit in the reg field + VEX.vvvv = dst  (BLSI, BLSR, BLSMSK)
4430#[cfg(any(feature = "x86", feature = "x86_64"))]
4431pub(crate) fn encode_vex_bmi_digit(
4432    buf: &mut InstrBytes,
4433    ops: &OperandList,
4434    instr: &Instruction,
4435    pp: u8,
4436    escape: &[u8],
4437    opcode: u8,
4438    digit: u8,
4439    w_from_size: bool,
4440) -> Result<(), AsmError> {
4441    use Operand::*;
4442    match (ops.first(), ops.get(1)) {
4443        (Some(Register(dst)), Some(Register(src))) => {
4444            let w = if w_from_size {
4445                dst.size_bits() == 64
4446            } else {
4447                false
4448            };
4449            let mandatory_pp = vex_pp(pp);
4450            emit_vex_prefix(
4451                buf,
4452                false, // reg field is /digit, never extended
4453                false,
4454                src.is_extended(),
4455                w,
4456                dst.base_code() | if dst.is_extended() { 8 } else { 0 },
4457                false,
4458                mandatory_pp,
4459                escape,
4460            );
4461            buf.push(opcode);
4462            buf.push(0xC0 | (digit << 3) | src.base_code());
4463            Ok(())
4464        }
4465        (Some(Register(dst)), Some(Memory(mem))) => {
4466            let w = if w_from_size {
4467                dst.size_bits() == 64
4468            } else {
4469                false
4470            };
4471            let mandatory_pp = vex_pp(pp);
4472            let x_ext = mem.index.is_some_and(|r| r.is_extended());
4473            let b_ext = mem.base.is_some_and(|r| r.is_extended());
4474            emit_vex_prefix(
4475                buf,
4476                false,
4477                x_ext,
4478                b_ext,
4479                w,
4480                dst.base_code() | if dst.is_extended() { 8 } else { 0 },
4481                false,
4482                mandatory_pp,
4483                escape,
4484            );
4485            buf.push(opcode);
4486            emit_mem_modrm(buf, digit, mem);
4487            Ok(())
4488        }
4489        _ => Err(invalid_operands(
4490            &instr.mnemonic,
4491            "expected r32/r64, r/m32/r/m64",
4492            instr.span,
4493        )),
4494    }
4495}
4496
4497/// VEX BMI2 with immediate (RORX: VEX.LZ.F2.0F3A.W0/W1 F0 /r ib)
4498#[cfg(any(feature = "x86", feature = "x86_64"))]
4499pub(crate) fn encode_vex_bmi_imm(
4500    buf: &mut InstrBytes,
4501    ops: &OperandList,
4502    instr: &Instruction,
4503    pp: u8,
4504    escape: &[u8],
4505    opcode: u8,
4506    w_from_size: bool,
4507) -> Result<(), AsmError> {
4508    use Operand::*;
4509    match (ops.first(), ops.get(1), ops.get(2)) {
4510        (Some(Register(dst)), Some(Register(src)), Some(Immediate(imm))) => {
4511            let w = if w_from_size {
4512                dst.size_bits() == 64
4513            } else {
4514                false
4515            };
4516            let mandatory_pp = vex_pp(pp);
4517            emit_vex_prefix(
4518                buf,
4519                dst.is_extended(),
4520                false,
4521                src.is_extended(),
4522                w,
4523                0,
4524                false,
4525                mandatory_pp,
4526                escape,
4527            );
4528            buf.push(opcode);
4529            buf.push(0xC0 | (dst.base_code() << 3) | src.base_code());
4530            buf.push(*imm as u8);
4531            Ok(())
4532        }
4533        (Some(Register(dst)), Some(Memory(mem)), Some(Immediate(imm))) => {
4534            let w = if w_from_size {
4535                dst.size_bits() == 64
4536            } else {
4537                false
4538            };
4539            let mandatory_pp = vex_pp(pp);
4540            let x_ext = mem.index.is_some_and(|r| r.is_extended());
4541            let b_ext = mem.base.is_some_and(|r| r.is_extended());
4542            emit_vex_prefix(
4543                buf,
4544                dst.is_extended(),
4545                x_ext,
4546                b_ext,
4547                w,
4548                0,
4549                false,
4550                mandatory_pp,
4551                escape,
4552            );
4553            buf.push(opcode);
4554            emit_mem_modrm(buf, dst.base_code(), mem);
4555            buf.push(*imm as u8);
4556            Ok(())
4557        }
4558        _ => Err(invalid_operands(
4559            &instr.mnemonic,
4560            "expected r, r/m, imm8",
4561            instr.span,
4562        )),
4563    }
4564}
4565
4566/// VEX BMI instruction with reversed operand mapping: dst(reg), src(r/m), control(vvvv)
4567/// Used for BEXTR, BZHI, SARX, SHLX, SHRX where the second operand is
4568/// the ModR/M r/m field and the third operand is VEX.vvvv.
4569#[cfg(any(feature = "x86", feature = "x86_64"))]
4570pub(crate) fn encode_vex_bmi_rmv(
4571    buf: &mut InstrBytes,
4572    ops: &OperandList,
4573    instr: &Instruction,
4574    pp: u8,
4575    escape: &[u8],
4576    opcode: u8,
4577    w_from_size: bool,
4578) -> Result<(), AsmError> {
4579    use Operand::*;
4580    match (ops.first(), ops.get(1), ops.get(2)) {
4581        // 3-operand: r, r, r (dst=reg, src=r/m, control=vvvv)
4582        (Some(Register(dst)), Some(Register(src)), Some(Register(ctrl))) => {
4583            let w = if w_from_size {
4584                dst.size_bits() == 64
4585            } else {
4586                false
4587            };
4588            let mandatory_pp = vex_pp(pp);
4589            emit_vex_prefix(
4590                buf,
4591                dst.is_extended(),
4592                false,
4593                src.is_extended(),
4594                w,
4595                ctrl.base_code() | if ctrl.is_extended() { 8 } else { 0 },
4596                false,
4597                mandatory_pp,
4598                escape,
4599            );
4600            buf.push(opcode);
4601            buf.push(0xC0 | (dst.base_code() << 3) | src.base_code());
4602            Ok(())
4603        }
4604        // 3-operand: r, mem, r (dst=reg, src=r/m, control=vvvv)
4605        (Some(Register(dst)), Some(Memory(mem)), Some(Register(ctrl))) => {
4606            let w = if w_from_size {
4607                dst.size_bits() == 64
4608            } else {
4609                false
4610            };
4611            let mandatory_pp = vex_pp(pp);
4612            let x_ext = mem.index.is_some_and(|r| r.is_extended());
4613            let b_ext = mem.base.is_some_and(|r| r.is_extended());
4614            emit_vex_prefix(
4615                buf,
4616                dst.is_extended(),
4617                x_ext,
4618                b_ext,
4619                w,
4620                ctrl.base_code() | if ctrl.is_extended() { 8 } else { 0 },
4621                false,
4622                mandatory_pp,
4623                escape,
4624            );
4625            buf.push(opcode);
4626            emit_mem_modrm(buf, dst.base_code(), mem);
4627            Ok(())
4628        }
4629        _ => Err(invalid_operands(
4630            &instr.mnemonic,
4631            "expected r, r/m, r",
4632            instr.span,
4633        )),
4634    }
4635}
4636
4637/// VEX-encoded packed shift instruction dispatcher.
4638///
4639/// Handles two forms:
4640///   1. `vpsllw xmm1, xmm2, xmm3/m128` — 3-operand NDS (reg, reg, reg/mem)
4641///   2. `vpsllw xmm1, xmm2, imm8`       — 3-operand NDD with /digit (reg, reg, imm)
4642///
4643/// Parameters:
4644///   - `reg_opcode`: opcode for the reg,reg,reg/mem form (ModR/M reg = dst)
4645///   - `imm_opcode`: opcode for the reg,reg,imm8 form (/digit in reg field)
4646///   - `digit`: the /digit value for the immediate form
4647#[cfg(any(feature = "x86", feature = "x86_64"))]
4648pub(crate) fn encode_vex_shift(
4649    buf: &mut InstrBytes,
4650    ops: &OperandList,
4651    instr: &Instruction,
4652    pp: u8,
4653    escape: &[u8],
4654    reg_opcode: u8,
4655    imm_opcode: u8,
4656    digit: u8,
4657    reloc: &mut Option<Relocation>,
4658) -> Result<(), AsmError> {
4659    use Operand::*;
4660    match (ops.first(), ops.get(1), ops.get(2)) {
4661        // 3-operand: reg, reg, reg (shift by register/xmm)
4662        (Some(Register(dst)), Some(Register(src1)), Some(Register(src2)))
4663            if dst.is_vector() && src1.is_vector() && src2.is_vector() =>
4664        {
4665            let l = dst.is_ymm() || src1.is_ymm();
4666            encode_vex_rrr(buf, pp, escape, reg_opcode, *dst, *src1, *src2, false, l);
4667            Ok(())
4668        }
4669        // 3-operand: reg, reg, mem (shift by memory)
4670        (Some(Register(dst)), Some(Register(src1)), Some(Memory(mem)))
4671            if dst.is_vector() && src1.is_vector() =>
4672        {
4673            let l = dst.is_ymm() || src1.is_ymm();
4674            encode_vex_rrm(
4675                buf, pp, escape, reg_opcode, *dst, *src1, mem, reloc, false, l,
4676            );
4677            Ok(())
4678        }
4679        // 3-operand: reg, reg, imm8 (shift by immediate — NDD form)
4680        (Some(Register(dst)), Some(Register(src)), Some(Immediate(imm)))
4681            if dst.is_vector() && src.is_vector() =>
4682        {
4683            let l = dst.is_ymm() || src.is_ymm();
4684            let mandatory_pp = vex_pp(pp);
4685            // NDD: VEX.vvvv = dst, ModR/M r/m = src, ModR/M reg = /digit
4686            emit_vex_prefix(
4687                buf,
4688                false, // reg field is /digit, never extended
4689                false,
4690                src.is_extended(),
4691                false, // W = 0 (WIG)
4692                dst.base_code() | if dst.is_extended() { 8 } else { 0 },
4693                l,
4694                mandatory_pp,
4695                escape,
4696            );
4697            buf.push(imm_opcode);
4698            buf.push(0xC0 | (digit << 3) | src.base_code());
4699            buf.push(*imm as u8);
4700            Ok(())
4701        }
4702        _ => Err(invalid_operands(
4703            &instr.mnemonic,
4704            "expected VEX xmm/ymm shift operands",
4705            instr.span,
4706        )),
4707    }
4708}
4709
4710/// VEX-encoded instruction with mixed GP/XMM operands.
4711///
4712/// Handles conversions like VCVTSI2SS (xmm, xmm, r/m32/64):
4713///   - dst(xmm) = ModR/M reg, src1(xmm) = VEX.vvvv (NDS), src2(GP/mem) = ModR/M r/m
4714///
4715/// Also handles the reverse (r32, xmm/m): VCVTSS2SI, VCVTTSS2SI
4716#[cfg(any(feature = "x86", feature = "x86_64"))]
4717pub(crate) fn encode_vex_cvt(
4718    buf: &mut InstrBytes,
4719    ops: &OperandList,
4720    instr: &Instruction,
4721    pp: u8,
4722    escape: &[u8],
4723    opcode: u8,
4724    _reloc: &mut Option<Relocation>,
4725) -> Result<(), AsmError> {
4726    use Operand::*;
4727    match (ops.first(), ops.get(1), ops.get(2)) {
4728        // 3-operand: xmm, xmm, r/m (VCVTSI2SS xmm, xmm, r32/r64/m32/m64)
4729        (Some(Register(dst)), Some(Register(src1)), Some(Register(src2)))
4730            if dst.is_xmm() && src1.is_xmm() =>
4731        {
4732            let w = src2.size_bits() == 64;
4733            let mandatory_pp = vex_pp(pp);
4734            emit_vex_prefix(
4735                buf,
4736                dst.is_extended(),
4737                false,
4738                src2.is_extended(),
4739                w,
4740                src1.base_code() | if src1.is_extended() { 8 } else { 0 },
4741                false,
4742                mandatory_pp,
4743                escape,
4744            );
4745            buf.push(opcode);
4746            buf.push(0xC0 | (dst.base_code() << 3) | src2.base_code());
4747            Ok(())
4748        }
4749        (Some(Register(dst)), Some(Register(src1)), Some(Memory(mem)))
4750            if dst.is_xmm() && src1.is_xmm() =>
4751        {
4752            let w = mem.size.is_some_and(|s| s.bits() == 64);
4753            let mandatory_pp = vex_pp(pp);
4754            let x_ext = mem.index.is_some_and(|r| r.is_extended());
4755            let b_ext = mem.base.is_some_and(|r| r.is_extended());
4756            emit_vex_prefix(
4757                buf,
4758                dst.is_extended(),
4759                x_ext,
4760                b_ext,
4761                w,
4762                src1.base_code() | if src1.is_extended() { 8 } else { 0 },
4763                false,
4764                mandatory_pp,
4765                escape,
4766            );
4767            buf.push(opcode);
4768            emit_mem_modrm(buf, dst.base_code(), mem);
4769            Ok(())
4770        }
4771        // 2-operand: r32/r64, xmm (VCVTSS2SI r32, xmm)
4772        (Some(Register(dst)), Some(Register(src)), None) if !dst.is_vector() && src.is_xmm() => {
4773            let w = dst.size_bits() == 64;
4774            let mandatory_pp = vex_pp(pp);
4775            emit_vex_prefix(
4776                buf,
4777                dst.is_extended(),
4778                false,
4779                src.is_extended(),
4780                w,
4781                0,
4782                false,
4783                mandatory_pp,
4784                escape,
4785            );
4786            buf.push(opcode);
4787            buf.push(0xC0 | (dst.base_code() << 3) | src.base_code());
4788            Ok(())
4789        }
4790        // 2-operand: r32/r64, mem (VCVTSS2SI r32, m32)
4791        (Some(Register(dst)), Some(Memory(mem)), None) if !dst.is_vector() => {
4792            let w = dst.size_bits() == 64;
4793            let mandatory_pp = vex_pp(pp);
4794            let x_ext = mem.index.is_some_and(|r| r.is_extended());
4795            let b_ext = mem.base.is_some_and(|r| r.is_extended());
4796            emit_vex_prefix(
4797                buf,
4798                dst.is_extended(),
4799                x_ext,
4800                b_ext,
4801                w,
4802                0,
4803                false,
4804                mandatory_pp,
4805                escape,
4806            );
4807            buf.push(opcode);
4808            emit_mem_modrm(buf, dst.base_code(), mem);
4809            Ok(())
4810        }
4811        _ => Err(invalid_operands(
4812            &instr.mnemonic,
4813            "expected conversion operands",
4814            instr.span,
4815        )),
4816    }
4817}
4818
4819// ─── EVEX prefix encoding infrastructure (AVX-512) ──────────────────────────
4820//
4821// EVEX is a 4-byte prefix (62h + P0 P1 P2) used by AVX-512 instructions.
4822//
4823// P0: [R  X  B  R' 0 0 m m]      — R/X/B from legacy REX, R' extends ModR/M.reg to 5 bits, mm = map
4824// P1: [W  v3 v2 v1 v0 1  p p]    — W, vvvv (inverted NDS src), fixed 1, pp
4825// P2: [z  L' L  b  V' a a a]     — z=zeroing, L'L=vector length, b=broadcast, V' extends vvvv, aaa=opmask
4826//
4827// Vector length: L'L = 00 → 128, 01 → 256, 10 → 512
4828
4829/// Emit a 4-byte EVEX prefix: 62 [P0] [P1] [P2]
4830///
4831/// Parameters:
4832/// - `r_ext`: ModR/M.reg is extended (bit 3 via ~R, like REX.R)
4833/// - `x_ext`: SIB.index is extended (bit 3 via ~X, like REX.X)
4834/// - `b_ext`: ModR/M.rm / SIB.base is extended (bit 3 via ~B, like REX.B)
4835/// - `r_prime`: ModR/M.reg bit 4 (EVEX.R', inverted — for regs 16-31)
4836/// - `mm`: map select (01 = 0F, 02 = 0F38, 03 = 0F3A)
4837/// - `w`: operand size promotion (like REX.W)
4838/// - `vvvv`: NDS source register (4-bit, inverted in prefix)
4839/// - `v_prime`: vvvv bit 4 (EVEX.V', inverted — for NDS regs 16-31)
4840/// - `pp`: implied mandatory prefix (00=none, 01=66, 10=F3, 11=F2)
4841/// - `z`: zeroing-masking (1 = zero, 0 = merge)
4842/// - `ll`: vector length (0=128, 1=256, 2=512)
4843/// - `b_bit`: broadcast / rounding control / SAE
4844/// - `aaa`: opmask register number (0 = no mask)
4845#[cfg(any(feature = "x86", feature = "x86_64"))]
4846fn emit_evex(
4847    buf: &mut InstrBytes,
4848    r_ext: bool,
4849    x_ext: bool,
4850    b_ext: bool,
4851    r_prime: bool,
4852    mm: u8,
4853    w: bool,
4854    vvvv: u8,
4855    v_prime: bool,
4856    pp: u8,
4857    z: bool,
4858    ll: u8,
4859    b_bit: bool,
4860    aaa: u8,
4861) {
4862    // P0: ~R ~X ~B ~R' 0 0 mm
4863    let p0 = (if r_ext { 0 } else { 0x80 })
4864        | (if x_ext { 0 } else { 0x40 })
4865        | (if b_ext { 0 } else { 0x20 })
4866        | (if r_prime { 0 } else { 0x10 })
4867        | (mm & 0x03);
4868
4869    // P1: W ~v3 ~v2 ~v1 ~v0 1 pp
4870    let p1 = (if w { 0x80 } else { 0 })
4871        | (((!vvvv) & 0x0F) << 3)
4872        | 0x04 // fixed bit
4873        | (pp & 0x03);
4874
4875    // P2: z L'L b ~V' aaa
4876    let p2 = (if z { 0x80 } else { 0 })
4877        | ((ll & 0x03) << 5)
4878        | (if b_bit { 0x10 } else { 0 })
4879        | (if v_prime { 0 } else { 0x08 })
4880        | (aaa & 0x07);
4881
4882    buf.push(0x62);
4883    buf.push(p0);
4884    buf.push(p1);
4885    buf.push(p2);
4886}
4887
4888/// Helper: compute EVEX register encoding bits from a Register.
4889/// Returns (base_code_3bit, is_extended_bit3, is_evex_bit4)
4890#[cfg(any(feature = "x86", feature = "x86_64"))]
4891fn evex_reg_bits(reg: Register) -> (u8, bool, bool) {
4892    (reg.base_code(), reg.is_extended(), reg.is_evex_extended())
4893}
4894
4895/// Helper: Compute the EVEX vector length field from operand registers.
4896/// Returns the L'L value: 0 = 128 (XMM), 1 = 256 (YMM), 2 = 512 (ZMM).
4897#[cfg(any(feature = "x86", feature = "x86_64"))]
4898fn evex_ll(reg: Register) -> u8 {
4899    if reg.is_zmm() {
4900        2
4901    } else if reg.is_ymm() {
4902        1
4903    } else {
4904        0
4905    }
4906}
4907
4908/// Emit EVEX prefix for a reg,reg,reg instruction.
4909/// `dst` = ModR/M.reg (destination), `src1` = vvvv (NDS), `src2` = ModR/M.rm
4910///
4911/// `aaa` and `z` support opmask + zeroing. Pass aaa=0, z=false for unmasked.
4912#[cfg(any(feature = "x86", feature = "x86_64"))]
4913fn emit_evex_prefix_rrr(
4914    buf: &mut InstrBytes,
4915    dst: Register,
4916    src1: Register,
4917    src2: Register,
4918    mm: u8,
4919    w: bool,
4920    pp: u8,
4921    ll: u8,
4922    aaa: u8,
4923    z: bool,
4924) {
4925    let (_, dst_ext, dst_evex) = evex_reg_bits(dst);
4926    let (src1_code, src1_ext, src1_evex) = evex_reg_bits(src1);
4927    let (_, src2_ext, src2_evex) = evex_reg_bits(src2);
4928
4929    let vvvv = src1_code | if src1_ext { 8 } else { 0 };
4930
4931    emit_evex(
4932        buf, dst_ext,   // R: reg bit 3
4933        src2_evex, // X: r/m bit 4 (repurposed in reg-reg form)
4934        src2_ext,  // B: r/m bit 3
4935        dst_evex,  // R': reg bit 4
4936        mm, w, vvvv, src1_evex, // V': vvvv bit 4
4937        pp, z, ll, false, // b: no broadcast for reg-reg
4938        aaa,
4939    );
4940}
4941
4942/// Emit EVEX prefix for a reg,reg,mem instruction.
4943/// `dst` = ModR/M.reg (destination), `src1` = vvvv (NDS), `mem` = memory operand
4944#[cfg(any(feature = "x86", feature = "x86_64"))]
4945fn emit_evex_prefix_rrm(
4946    buf: &mut InstrBytes,
4947    dst: Register,
4948    src1: Register,
4949    mem: &MemoryOperand,
4950    mm: u8,
4951    w: bool,
4952    pp: u8,
4953    ll: u8,
4954    b_bit: bool,
4955    aaa: u8,
4956    z: bool,
4957) {
4958    let (_, dst_ext, dst_evex) = evex_reg_bits(dst);
4959    let (src1_code, src1_ext, src1_evex) = evex_reg_bits(src1);
4960
4961    let vvvv = src1_code | if src1_ext { 8 } else { 0 };
4962    let x_ext = mem.index.is_some_and(|r| r.is_extended());
4963    let b_ext = mem.base.is_some_and(|r| r.is_extended());
4964
4965    emit_evex(
4966        buf, dst_ext, x_ext, b_ext, dst_evex, mm, w, vvvv, src1_evex, pp, z, ll, b_bit, aaa,
4967    );
4968}
4969
4970/// Generic EVEX-encoded AVX-512 instruction dispatcher.
4971/// Handles the common patterns:
4972///   - evex_op zmm, zmm, zmm    (3 register operands, NDS form)
4973///   - evex_op zmm, zmm, [mem]  (reg, reg, mem — optional broadcast)
4974///   - evex_op zmm, zmm         (2-operand reg-reg, vvvv=0)
4975///   - evex_op zmm, [mem]       (2-operand load)
4976///   - evex_op [mem], zmm       (2-operand store)
4977///
4978/// Opmask/zeroing/broadcast are read from `instr.opmask`, `instr.zeroing`,
4979/// and `instr.broadcast` fields set by the parser.
4980#[cfg(any(feature = "x86", feature = "x86_64"))]
4981fn evex_aaa(instr: &Instruction) -> u8 {
4982    instr.opmask.map_or(0, |reg| reg.base_code())
4983}
4984
4985#[cfg(any(feature = "x86", feature = "x86_64"))]
4986fn evex_broadcast_bit(instr: &Instruction) -> bool {
4987    instr.broadcast.is_some()
4988}
4989
4990#[cfg(any(feature = "x86", feature = "x86_64"))]
4991pub(crate) fn encode_evex_op(
4992    buf: &mut InstrBytes,
4993    ops: &OperandList,
4994    instr: &Instruction,
4995    pp: u8,
4996    mm: u8,
4997    load_opcode: u8,
4998    store_opcode: Option<u8>,
4999    w: bool,
5000    reloc: &mut Option<Relocation>,
5001) -> Result<(), AsmError> {
5002    use Operand::*;
5003    let mandatory_pp = vex_pp(pp);
5004    let o = (ops.first(), ops.get(1), ops.get(2));
5005    match o {
5006        // 3-operand: reg, reg, reg
5007        (Some(Register(dst)), Some(Register(src1)), Some(Register(src2)))
5008            if dst.is_vector() && src1.is_vector() && src2.is_vector() =>
5009        {
5010            let ll = evex_ll(*dst);
5011            let aaa = evex_aaa(instr);
5012            emit_evex_prefix_rrr(
5013                buf,
5014                *dst,
5015                *src1,
5016                *src2,
5017                mm,
5018                w,
5019                mandatory_pp,
5020                ll,
5021                aaa,
5022                instr.zeroing,
5023            );
5024            buf.push(load_opcode);
5025            buf.push(0xC0 | (dst.base_code() << 3) | src2.base_code());
5026            Ok(())
5027        }
5028        // 3-operand: reg, reg, mem
5029        (Some(Register(dst)), Some(Register(src1)), Some(Memory(mem)))
5030            if dst.is_vector() && src1.is_vector() =>
5031        {
5032            let ll = evex_ll(*dst);
5033            let aaa = evex_aaa(instr);
5034            let b_bit = evex_broadcast_bit(instr);
5035            emit_evex_prefix_rrm(
5036                buf,
5037                *dst,
5038                *src1,
5039                mem,
5040                mm,
5041                w,
5042                mandatory_pp,
5043                ll,
5044                b_bit,
5045                aaa,
5046                instr.zeroing,
5047            );
5048            buf.push(load_opcode);
5049            emit_mem_modrm(buf, dst.base_code(), mem);
5050            if let Some(ref mut rel) = reloc {
5051                rel.offset = buf.len() - 4;
5052            }
5053            Ok(())
5054        }
5055        // 2-operand reg, reg (move-like: vvvv unused, use K0 as dummy NDS)
5056        (Some(Register(dst)), Some(Register(src)), None) if dst.is_vector() && src.is_vector() => {
5057            let ll = evex_ll(*dst);
5058            let aaa = evex_aaa(instr);
5059            // vvvv = 0 (K0 stand-in for unused NDS)
5060            let (_, src_ext, src_evex) = evex_reg_bits(*src);
5061            let (_, dst_ext, dst_evex) = evex_reg_bits(*dst);
5062            emit_evex(
5063                buf,
5064                dst_ext,
5065                src_evex,
5066                src_ext,
5067                dst_evex,
5068                mm,
5069                w,
5070                0,
5071                false,
5072                mandatory_pp,
5073                instr.zeroing,
5074                ll,
5075                false,
5076                aaa,
5077            );
5078            buf.push(load_opcode);
5079            buf.push(0xC0 | (dst.base_code() << 3) | src.base_code());
5080            Ok(())
5081        }
5082        // 2-operand reg, mem (load)
5083        (Some(Register(dst)), Some(Memory(mem)), None) if dst.is_vector() => {
5084            let ll = evex_ll(*dst);
5085            let aaa = evex_aaa(instr);
5086            let b_bit = evex_broadcast_bit(instr);
5087            let (_, dst_ext, dst_evex) = evex_reg_bits(*dst);
5088            let x_ext = mem.index.is_some_and(|r| r.is_extended());
5089            let b_ext = mem.base.is_some_and(|r| r.is_extended());
5090            emit_evex(
5091                buf,
5092                dst_ext,
5093                x_ext,
5094                b_ext,
5095                dst_evex,
5096                mm,
5097                w,
5098                0,
5099                false,
5100                mandatory_pp,
5101                instr.zeroing,
5102                ll,
5103                b_bit,
5104                aaa,
5105            );
5106            buf.push(load_opcode);
5107            emit_mem_modrm(buf, dst.base_code(), mem);
5108            if let Some(ref mut rel) = reloc {
5109                rel.offset = buf.len() - 4;
5110            }
5111            Ok(())
5112        }
5113        // 2-operand mem, reg (store)
5114        (Some(Memory(mem)), Some(Register(src)), None)
5115            if src.is_vector() && store_opcode.is_some() =>
5116        {
5117            let ll = evex_ll(*src);
5118            let aaa = evex_aaa(instr);
5119            let (_, src_ext, src_evex) = evex_reg_bits(*src);
5120            let x_ext = mem.index.is_some_and(|r| r.is_extended());
5121            let b_ext = mem.base.is_some_and(|r| r.is_extended());
5122            emit_evex(
5123                buf,
5124                src_ext,
5125                x_ext,
5126                b_ext,
5127                src_evex,
5128                mm,
5129                w,
5130                0,
5131                false,
5132                mandatory_pp,
5133                instr.zeroing,
5134                ll,
5135                false,
5136                aaa,
5137            );
5138            // SAFETY: match guard `store_opcode.is_some()` guarantees Some
5139            buf.push(store_opcode.unwrap_or(0));
5140            emit_mem_modrm(buf, src.base_code(), mem);
5141            if let Some(ref mut rel) = reloc {
5142                rel.offset = buf.len() - 4;
5143            }
5144            Ok(())
5145        }
5146        _ => Err(invalid_operands(
5147            &instr.mnemonic,
5148            "expected EVEX zmm/ymm/xmm operands",
5149            instr.span,
5150        )),
5151    }
5152}
5153
5154/// EVEX instruction with immediate byte (4 operands: dst, src1, src2/mem, imm8)
5155#[cfg(any(feature = "x86", feature = "x86_64"))]
5156pub(crate) fn encode_evex_imm(
5157    buf: &mut InstrBytes,
5158    ops: &OperandList,
5159    instr: &Instruction,
5160    pp: u8,
5161    mm: u8,
5162    opcode: u8,
5163    w: bool,
5164    reloc: &mut Option<Relocation>,
5165) -> Result<(), AsmError> {
5166    use Operand::*;
5167    let mandatory_pp = vex_pp(pp);
5168    match (ops.first(), ops.get(1), ops.get(2), ops.get(3)) {
5169        // reg, reg, reg, imm8
5170        (Some(Register(dst)), Some(Register(src1)), Some(Register(src2)), Some(Immediate(imm)))
5171            if dst.is_vector() && src1.is_vector() && src2.is_vector() =>
5172        {
5173            let ll = evex_ll(*dst);
5174            let aaa = evex_aaa(instr);
5175            emit_evex_prefix_rrr(
5176                buf,
5177                *dst,
5178                *src1,
5179                *src2,
5180                mm,
5181                w,
5182                mandatory_pp,
5183                ll,
5184                aaa,
5185                instr.zeroing,
5186            );
5187            buf.push(opcode);
5188            buf.push(0xC0 | (dst.base_code() << 3) | src2.base_code());
5189            buf.push(*imm as u8);
5190            Ok(())
5191        }
5192        // reg, reg, mem, imm8
5193        (Some(Register(dst)), Some(Register(src1)), Some(Memory(mem)), Some(Immediate(imm)))
5194            if dst.is_vector() && src1.is_vector() =>
5195        {
5196            let ll = evex_ll(*dst);
5197            let aaa = evex_aaa(instr);
5198            let b_bit = evex_broadcast_bit(instr);
5199            emit_evex_prefix_rrm(
5200                buf,
5201                *dst,
5202                *src1,
5203                mem,
5204                mm,
5205                w,
5206                mandatory_pp,
5207                ll,
5208                b_bit,
5209                aaa,
5210                instr.zeroing,
5211            );
5212            buf.push(opcode);
5213            emit_mem_modrm(buf, dst.base_code(), mem);
5214            if let Some(ref mut rel) = reloc {
5215                rel.offset = buf.len() - 4;
5216            }
5217            buf.push(*imm as u8);
5218            Ok(())
5219        }
5220        // 3-operand with imm: reg, reg/mem, imm8 (some instructions are NDS-free)
5221        (Some(Register(dst)), Some(Register(src)), Some(Immediate(imm)), None)
5222            if dst.is_vector() && src.is_vector() =>
5223        {
5224            let ll = evex_ll(*dst);
5225            let aaa = evex_aaa(instr);
5226            let (_, src_ext, src_evex) = evex_reg_bits(*src);
5227            let (_, dst_ext, dst_evex) = evex_reg_bits(*dst);
5228            emit_evex(
5229                buf,
5230                dst_ext,
5231                src_evex,
5232                src_ext,
5233                dst_evex,
5234                mm,
5235                w,
5236                0,
5237                false,
5238                mandatory_pp,
5239                instr.zeroing,
5240                ll,
5241                false,
5242                aaa,
5243            );
5244            buf.push(opcode);
5245            buf.push(0xC0 | (dst.base_code() << 3) | src.base_code());
5246            buf.push(*imm as u8);
5247            Ok(())
5248        }
5249        _ => Err(invalid_operands(
5250            &instr.mnemonic,
5251            "expected EVEX operands with immediate",
5252            instr.span,
5253        )),
5254    }
5255}
5256
5257/// EVEX opmask instruction: `KADDB k1, k2, k3` etc.
5258/// Opmask-to-opmask operations use EVEX with vvvv=k, reg=k, r/m=k.
5259/// Will be wired up when opmask instruction dispatch is added.
5260#[cfg(any(feature = "x86", feature = "x86_64"))]
5261#[allow(dead_code)]
5262pub(crate) fn encode_evex_opmask(
5263    buf: &mut InstrBytes,
5264    ops: &OperandList,
5265    instr: &Instruction,
5266    pp: u8,
5267    _mm: u8,
5268    opcode: u8,
5269    w: bool,
5270) -> Result<(), AsmError> {
5271    use Operand::*;
5272    let mandatory_pp = vex_pp(pp);
5273    match (ops.first(), ops.get(1), ops.get(2)) {
5274        // 3-operand: k, k, k (e.g., KADDB k1, k2, k3)
5275        (Some(Register(dst)), Some(Register(src1)), Some(Register(src2)))
5276            if dst.is_opmask() && src1.is_opmask() && src2.is_opmask() =>
5277        {
5278            // Opmask instructions use VEX encoding (not EVEX) per Intel SDM
5279            // VEX.L=1 for most opmask ops, vvvv=src1
5280            let src1_vvvv = src1.base_code();
5281            emit_vex_prefix(
5282                buf,
5283                false,
5284                false,
5285                false,
5286                w,
5287                src1_vvvv,
5288                true,
5289                mandatory_pp,
5290                &[0x0F],
5291            );
5292            buf.push(opcode);
5293            buf.push(0xC0 | (dst.base_code() << 3) | src2.base_code());
5294            Ok(())
5295        }
5296        // 2-operand: k, k (e.g., KNOTB k1, k2)
5297        (Some(Register(dst)), Some(Register(src)), None) if dst.is_opmask() && src.is_opmask() => {
5298            emit_vex_prefix(buf, false, false, false, w, 0, true, mandatory_pp, &[0x0F]);
5299            buf.push(opcode);
5300            buf.push(0xC0 | (dst.base_code() << 3) | src.base_code());
5301            Ok(())
5302        }
5303        _ => Err(invalid_operands(
5304            &instr.mnemonic,
5305            "expected opmask register operands (k0-k7)",
5306            instr.span,
5307        )),
5308    }
5309}
5310
5311#[cfg(test)]
5312mod tests {
5313    use super::*;
5314    use crate::error::Span;
5315
5316    fn span() -> Span {
5317        Span::new(1, 1, 0, 0)
5318    }
5319
5320    fn make_instr(mnemonic: &str, operands: Vec<Operand>) -> Instruction {
5321        Instruction {
5322            mnemonic: Mnemonic::from(mnemonic),
5323            operands: OperandList::from(operands),
5324            size_hint: None,
5325            prefixes: PrefixList::new(),
5326            opmask: None,
5327            zeroing: false,
5328            broadcast: None,
5329            span: span(),
5330        }
5331    }
5332
5333    fn make_instr_with_hint(
5334        mnemonic: &str,
5335        operands: Vec<Operand>,
5336        hint: Option<OperandSize>,
5337    ) -> Instruction {
5338        Instruction {
5339            mnemonic: Mnemonic::from(mnemonic),
5340            operands: OperandList::from(operands),
5341            size_hint: hint,
5342            prefixes: PrefixList::new(),
5343            opmask: None,
5344            zeroing: false,
5345            broadcast: None,
5346            span: span(),
5347        }
5348    }
5349
5350    fn encode(mnemonic: &str, operands: Vec<Operand>) -> Vec<u8> {
5351        let instr = make_instr(mnemonic, operands);
5352        encode_instruction(&instr, Arch::X86_64)
5353            .unwrap()
5354            .bytes
5355            .to_vec()
5356    }
5357
5358    fn encode_with_hint(mnemonic: &str, operands: Vec<Operand>, hint: OperandSize) -> Vec<u8> {
5359        let mut instr = make_instr(mnemonic, operands);
5360        instr.size_hint = Some(hint);
5361        encode_instruction(&instr, Arch::X86_64)
5362            .unwrap()
5363            .bytes
5364            .to_vec()
5365    }
5366
5367    fn encode_with_prefix(mnemonic: &str, operands: Vec<Operand>, prefix: Prefix) -> Vec<u8> {
5368        let mut instr = make_instr(mnemonic, operands);
5369        instr.prefixes = PrefixList::from(alloc::vec![prefix]);
5370        encode_instruction(&instr, Arch::X86_64)
5371            .unwrap()
5372            .bytes
5373            .to_vec()
5374    }
5375
5376    use crate::ir::Register::*;
5377    use Operand::*;
5378
5379    // === Zero-operand instructions ===
5380
5381    #[test]
5382    fn test_nop() {
5383        assert_eq!(encode("nop", vec![]), vec![0x90]);
5384    }
5385
5386    #[test]
5387    fn test_ret() {
5388        assert_eq!(encode("ret", vec![]), vec![0xC3]);
5389    }
5390
5391    #[test]
5392    fn test_syscall() {
5393        assert_eq!(encode("syscall", vec![]), vec![0x0F, 0x05]);
5394    }
5395
5396    #[test]
5397    fn test_int3() {
5398        assert_eq!(encode("int3", vec![]), vec![0xCC]);
5399    }
5400
5401    #[test]
5402    fn test_int_0x80() {
5403        assert_eq!(encode("int", vec![Immediate(0x80)]), vec![0xCD, 0x80]);
5404    }
5405
5406    #[test]
5407    fn test_hlt() {
5408        assert_eq!(encode("hlt", vec![]), vec![0xF4]);
5409    }
5410
5411    // === MOV reg, reg ===
5412
5413    #[test]
5414    fn test_mov_rax_rbx() {
5415        // REX.W + 89 /r (mov r/m64, r64)
5416        let bytes = encode("mov", vec![Register(Rax), Register(Rbx)]);
5417        assert_eq!(bytes, vec![0x48, 0x89, 0xD8]);
5418    }
5419
5420    #[test]
5421    fn test_mov_eax_ecx() {
5422        // 89 /r (mov r/m32, r32)
5423        let bytes = encode("mov", vec![Register(Eax), Register(Ecx)]);
5424        assert_eq!(bytes, vec![0x89, 0xC8]);
5425    }
5426
5427    #[test]
5428    fn test_mov_r8_r9() {
5429        // REX.W+REX.R+REX.B + 89 /r
5430        let bytes = encode("mov", vec![Register(R8), Register(R9)]);
5431        assert_eq!(bytes, vec![0x4D, 0x89, 0xC8]);
5432    }
5433
5434    #[test]
5435    fn test_mov_al_bl() {
5436        let bytes = encode("mov", vec![Register(Al), Register(Bl)]);
5437        assert_eq!(bytes, vec![0x88, 0xD8]);
5438    }
5439
5440    // === MOV reg, imm ===
5441
5442    #[test]
5443    fn test_mov_eax_1() {
5444        let bytes = encode("mov", vec![Register(Eax), Immediate(1)]);
5445        assert_eq!(bytes, vec![0xB8, 0x01, 0x00, 0x00, 0x00]);
5446    }
5447
5448    #[test]
5449    fn test_mov_al_0xff() {
5450        let bytes = encode("mov", vec![Register(Al), Immediate(0xFF)]);
5451        assert_eq!(bytes, vec![0xB0, 0xFF]);
5452    }
5453
5454    #[test]
5455    fn test_mov_rax_small_imm() {
5456        // Should use mov eax, imm32 (zero extends to rax)
5457        let bytes = encode("mov", vec![Register(Rax), Immediate(1)]);
5458        assert_eq!(bytes, vec![0xB8, 0x01, 0x00, 0x00, 0x00]);
5459    }
5460
5461    #[test]
5462    fn test_mov_rax_neg1() {
5463        // mov rax, -1 → REX.W C7 /0 imm32 (sign-extended)
5464        let bytes = encode("mov", vec![Register(Rax), Immediate(-1)]);
5465        assert_eq!(bytes, vec![0x48, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF]);
5466    }
5467
5468    #[test]
5469    fn test_mov_rax_large_imm() {
5470        // movabs rax, imm64
5471        let bytes = encode("mov", vec![Register(Rax), Immediate(0x0102030405060708)]);
5472        assert_eq!(
5473            bytes,
5474            vec![0x48, 0xB8, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]
5475        );
5476    }
5477
5478    #[test]
5479    fn test_mov_r8d_imm() {
5480        let bytes = encode("mov", vec![Register(R8d), Immediate(42)]);
5481        assert_eq!(bytes, vec![0x41, 0xB8, 0x2A, 0x00, 0x00, 0x00]);
5482    }
5483
5484    // === MOV reg, [mem] and [mem], reg ===
5485
5486    #[test]
5487    fn test_mov_rax_mem_rbx() {
5488        // mov rax, [rbx] → REX.W 8B 03
5489        let mem = MemoryOperand {
5490            base: Some(Rbx),
5491            ..Default::default()
5492        };
5493        let bytes = encode("mov", vec![Register(Rax), Memory(Box::new(mem))]);
5494        assert_eq!(bytes, vec![0x48, 0x8B, 0x03]);
5495    }
5496
5497    #[test]
5498    fn test_mov_mem_rbx_rax() {
5499        // mov [rbx], rax → REX.W 89 03
5500        let mem = MemoryOperand {
5501            base: Some(Rbx),
5502            ..Default::default()
5503        };
5504        let bytes = encode("mov", vec![Memory(Box::new(mem)), Register(Rax)]);
5505        assert_eq!(bytes, vec![0x48, 0x89, 0x03]);
5506    }
5507
5508    #[test]
5509    fn test_mov_rax_mem_rbp_disp8() {
5510        // mov rax, [rbp + 8] → REX.W 8B 45 08
5511        let mem = MemoryOperand {
5512            base: Some(Rbp),
5513            disp: 8,
5514            ..Default::default()
5515        };
5516        let bytes = encode("mov", vec![Register(Rax), Memory(Box::new(mem))]);
5517        assert_eq!(bytes, vec![0x48, 0x8B, 0x45, 0x08]);
5518    }
5519
5520    #[test]
5521    fn test_mov_rax_mem_rbp_disp32() {
5522        // mov rax, [rbp + 0x200] → REX.W 8B 85 00 02 00 00
5523        let mem = MemoryOperand {
5524            base: Some(Rbp),
5525            disp: 0x200,
5526            ..Default::default()
5527        };
5528        let bytes = encode("mov", vec![Register(Rax), Memory(Box::new(mem))]);
5529        assert_eq!(bytes, vec![0x48, 0x8B, 0x85, 0x00, 0x02, 0x00, 0x00]);
5530    }
5531
5532    // === PUSH / POP ===
5533
5534    #[test]
5535    fn test_push_rbp() {
5536        assert_eq!(encode("push", vec![Register(Rbp)]), vec![0x55]);
5537    }
5538
5539    #[test]
5540    fn test_push_r12() {
5541        assert_eq!(encode("push", vec![Register(R12)]), vec![0x41, 0x54]);
5542    }
5543
5544    #[test]
5545    fn test_pop_rbp() {
5546        assert_eq!(encode("pop", vec![Register(Rbp)]), vec![0x5D]);
5547    }
5548
5549    #[test]
5550    fn test_push_imm8() {
5551        assert_eq!(encode("push", vec![Immediate(1)]), vec![0x6A, 0x01]);
5552    }
5553
5554    #[test]
5555    fn test_push_imm32() {
5556        assert_eq!(
5557            encode("push", vec![Immediate(0x1000)]),
5558            vec![0x68, 0x00, 0x10, 0x00, 0x00]
5559        );
5560    }
5561
5562    // === ALU ===
5563
5564    #[test]
5565    fn test_add_rax_rbx() {
5566        let bytes = encode("add", vec![Register(Rax), Register(Rbx)]);
5567        assert_eq!(bytes, vec![0x48, 0x01, 0xD8]); // REX.W 01 /r
5568    }
5569
5570    #[test]
5571    fn test_add_eax_1() {
5572        // add eax, 1 → 83 C0 01 (sign-extended imm8)
5573        let bytes = encode("add", vec![Register(Eax), Immediate(1)]);
5574        assert_eq!(bytes, vec![0x83, 0xC0, 0x01]);
5575    }
5576
5577    #[test]
5578    fn test_sub_rsp_8() {
5579        // sub rsp, 8 → REX.W 83 EC 08
5580        let bytes = encode("sub", vec![Register(Rsp), Immediate(8)]);
5581        assert_eq!(bytes, vec![0x48, 0x83, 0xEC, 0x08]);
5582    }
5583
5584    #[test]
5585    fn test_xor_eax_eax() {
5586        let bytes = encode("xor", vec![Register(Eax), Register(Eax)]);
5587        assert_eq!(bytes, vec![0x31, 0xC0]);
5588    }
5589
5590    #[test]
5591    fn test_cmp_rax_0() {
5592        let bytes = encode("cmp", vec![Register(Rax), Immediate(0)]);
5593        assert_eq!(bytes, vec![0x48, 0x83, 0xF8, 0x00]);
5594    }
5595
5596    #[test]
5597    fn test_and_al_imm() {
5598        // and al, 0x0F → 24 0F (short form)
5599        let bytes = encode("and", vec![Register(Al), Immediate(0x0F)]);
5600        assert_eq!(bytes, vec![0x24, 0x0F]);
5601    }
5602
5603    #[test]
5604    fn test_or_eax_large_imm() {
5605        // or eax, 0x1000 → 0D 00 10 00 00 (short form for eax)
5606        let bytes = encode("or", vec![Register(Eax), Immediate(0x1000)]);
5607        assert_eq!(bytes, vec![0x0D, 0x00, 0x10, 0x00, 0x00]);
5608    }
5609
5610    // === TEST ===
5611
5612    #[test]
5613    fn test_test_al_imm() {
5614        let bytes = encode("test", vec![Register(Al), Immediate(1)]);
5615        assert_eq!(bytes, vec![0xA8, 0x01]);
5616    }
5617
5618    #[test]
5619    fn test_test_eax_eax() {
5620        let bytes = encode("test", vec![Register(Eax), Register(Eax)]);
5621        assert_eq!(bytes, vec![0x85, 0xC0]);
5622    }
5623
5624    // === Shifts ===
5625
5626    #[test]
5627    fn test_shl_eax_1() {
5628        let bytes = encode("shl", vec![Register(Eax), Immediate(1)]);
5629        assert_eq!(bytes, vec![0xD1, 0xE0]);
5630    }
5631
5632    #[test]
5633    fn test_shr_rcx_4() {
5634        let bytes = encode("shr", vec![Register(Rcx), Immediate(4)]);
5635        assert_eq!(bytes, vec![0x48, 0xC1, 0xE9, 0x04]);
5636    }
5637
5638    #[test]
5639    fn test_sar_rax_cl() {
5640        let bytes = encode("sar", vec![Register(Rax), Register(Cl)]);
5641        assert_eq!(bytes, vec![0x48, 0xD3, 0xF8]);
5642    }
5643
5644    // === INC / DEC ===
5645
5646    #[test]
5647    fn test_inc_rax() {
5648        let bytes = encode("inc", vec![Register(Rax)]);
5649        assert_eq!(bytes, vec![0x48, 0xFF, 0xC0]);
5650    }
5651
5652    #[test]
5653    fn test_dec_ecx() {
5654        let bytes = encode("dec", vec![Register(Ecx)]);
5655        assert_eq!(bytes, vec![0xFF, 0xC9]);
5656    }
5657
5658    // === NEG / NOT ===
5659
5660    #[test]
5661    fn test_neg_rax() {
5662        let bytes = encode("neg", vec![Register(Rax)]);
5663        assert_eq!(bytes, vec![0x48, 0xF7, 0xD8]);
5664    }
5665
5666    #[test]
5667    fn test_not_eax() {
5668        let bytes = encode("not", vec![Register(Eax)]);
5669        assert_eq!(bytes, vec![0xF7, 0xD0]);
5670    }
5671
5672    // === JMP / CALL ===
5673
5674    #[test]
5675    fn test_jmp_label_relocation() {
5676        let instr = make_instr("jmp", vec![Label(String::from("target"))]);
5677        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
5678        assert_eq!(result.bytes, vec![0xE9, 0x00, 0x00, 0x00, 0x00]);
5679        assert!(result.relocation.is_some());
5680        let r = result.relocation.unwrap();
5681        assert_eq!(&*r.label, "target");
5682        assert_eq!(r.kind, RelocKind::X86Relative);
5683        assert_eq!(r.size, 4);
5684    }
5685
5686    #[test]
5687    fn test_jmp_reg() {
5688        let bytes = encode("jmp", vec![Register(Rax)]);
5689        assert_eq!(bytes, vec![0xFF, 0xE0]);
5690    }
5691
5692    #[test]
5693    fn test_call_reg() {
5694        let bytes = encode("call", vec![Register(Rax)]);
5695        assert_eq!(bytes, vec![0xFF, 0xD0]);
5696    }
5697
5698    #[test]
5699    fn test_call_r12() {
5700        let bytes = encode("call", vec![Register(R12)]);
5701        assert_eq!(bytes, vec![0x41, 0xFF, 0xD4]);
5702    }
5703
5704    // === Jcc ===
5705
5706    #[test]
5707    fn test_je_label() {
5708        let instr = make_instr("je", vec![Label(String::from("target"))]);
5709        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
5710        assert_eq!(result.bytes[0..2], [0x0F, 0x84]);
5711        assert!(result.relocation.is_some());
5712    }
5713
5714    #[test]
5715    fn test_jne_label() {
5716        let instr = make_instr("jne", vec![Label(String::from("target"))]);
5717        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
5718        assert_eq!(result.bytes[0..2], [0x0F, 0x85]);
5719    }
5720
5721    // === SETcc ===
5722
5723    #[test]
5724    fn test_sete_al() {
5725        let bytes = encode("sete", vec![Register(Al)]);
5726        assert_eq!(bytes, vec![0x0F, 0x94, 0xC0]);
5727    }
5728
5729    // === CMOVcc ===
5730
5731    #[test]
5732    fn test_cmove_rax_rbx() {
5733        let bytes = encode("cmove", vec![Register(Rax), Register(Rbx)]);
5734        assert_eq!(bytes, vec![0x48, 0x0F, 0x44, 0xC3]);
5735    }
5736
5737    // === MOVZX / MOVSX ===
5738
5739    #[test]
5740    fn test_movzx_eax_al() {
5741        let bytes = encode("movzx", vec![Register(Eax), Register(Al)]);
5742        assert_eq!(bytes, vec![0x0F, 0xB6, 0xC0]);
5743    }
5744
5745    #[test]
5746    fn test_movsx_rax_eax() {
5747        let bytes = encode("movsx", vec![Register(Rax), Register(Eax)]);
5748        assert_eq!(bytes, vec![0x48, 0x63, 0xC0]);
5749    }
5750
5751    // === LEA ===
5752
5753    #[test]
5754    fn test_lea_rax_rbx_rcx_8() {
5755        let mem = MemoryOperand {
5756            base: Some(Rbx),
5757            index: Some(Rcx),
5758            scale: 8,
5759            disp: 0,
5760            ..Default::default()
5761        };
5762        let bytes = encode("lea", vec![Register(Rax), Memory(Box::new(mem))]);
5763        assert_eq!(bytes, vec![0x48, 0x8D, 0x04, 0xCB]);
5764    }
5765
5766    // === Prefix ===
5767
5768    #[test]
5769    fn test_lock_prefix() {
5770        let mem = MemoryOperand {
5771            base: Some(Rax),
5772            ..Default::default()
5773        };
5774        let bytes = encode_with_prefix(
5775            "add",
5776            vec![Memory(Box::new(mem)), Immediate(1)],
5777            Prefix::Lock,
5778        );
5779        assert_eq!(bytes[0], 0xF0); // LOCK prefix
5780    }
5781
5782    // === IMUL ===
5783
5784    #[test]
5785    fn test_imul_r_r() {
5786        let bytes = encode("imul", vec![Register(Rax), Register(Rbx)]);
5787        assert_eq!(bytes, vec![0x48, 0x0F, 0xAF, 0xC3]);
5788    }
5789
5790    #[test]
5791    fn test_imul_r_r_imm8() {
5792        let bytes = encode("imul", vec![Register(Rax), Register(Rbx), Immediate(10)]);
5793        assert_eq!(bytes, vec![0x48, 0x6B, 0xC3, 0x0A]);
5794    }
5795
5796    // === BSWAP ===
5797
5798    #[test]
5799    fn test_bswap_eax() {
5800        let bytes = encode("bswap", vec![Register(Eax)]);
5801        assert_eq!(bytes, vec![0x0F, 0xC8]);
5802    }
5803
5804    #[test]
5805    fn test_bswap_rax() {
5806        let bytes = encode("bswap", vec![Register(Rax)]);
5807        assert_eq!(bytes, vec![0x48, 0x0F, 0xC8]);
5808    }
5809
5810    // === String Instructions ===
5811
5812    #[test]
5813    fn test_rep_movsb() {
5814        let mut instr = make_instr("movsb", vec![]);
5815        instr.prefixes = PrefixList::from(alloc::vec![Prefix::Rep]);
5816        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
5817        assert_eq!(result.bytes, vec![0xF3, 0xA4]);
5818    }
5819
5820    // === Multi-byte NOP ===
5821
5822    #[test]
5823    fn test_nop3_encoding() {
5824        let instr = make_instr("nop3", vec![]);
5825        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
5826        assert_eq!(result.bytes, vec![0x0F, 0x1F, 0x00]);
5827    }
5828
5829    // === RSP/RBP edge cases (SIB byte required) ===
5830
5831    #[test]
5832    fn test_mov_rax_mem_rsp() {
5833        // [rsp] requires SIB byte
5834        let mem = MemoryOperand {
5835            base: Some(Rsp),
5836            ..Default::default()
5837        };
5838        let bytes = encode("mov", vec![Register(Rax), Memory(Box::new(mem))]);
5839        // REX.W 8B 04 24 (ModRM=04h → SIB follows, SIB=24h → base=RSP, no index)
5840        assert_eq!(bytes, vec![0x48, 0x8B, 0x04, 0x24]);
5841    }
5842
5843    // === CDQ / CQO ===
5844
5845    #[test]
5846    fn test_cdq() {
5847        assert_eq!(encode("cdq", vec![]), vec![0x99]);
5848    }
5849
5850    #[test]
5851    fn test_cqo() {
5852        assert_eq!(encode("cqo", vec![]), vec![0x48, 0x99]);
5853    }
5854
5855    // === 16-bit operations ===
5856
5857    #[test]
5858    fn test_mov_ax_bx() {
5859        let bytes = encode("mov", vec![Register(Ax), Register(Bx)]);
5860        assert_eq!(bytes, vec![0x66, 0x89, 0xD8]);
5861    }
5862
5863    #[test]
5864    fn test_add_ax_imm() {
5865        let bytes = encode("add", vec![Register(Ax), Immediate(1)]);
5866        assert_eq!(bytes, vec![0x66, 0x83, 0xC0, 0x01]);
5867    }
5868
5869    // === Arch::X86 basic smoke test ===
5870
5871    #[test]
5872    fn test_arch_x86_nop() {
5873        let instr = make_instr("nop", vec![]);
5874        let result = encode_instruction(&instr, Arch::X86);
5875        assert!(result.is_ok());
5876        assert_eq!(result.unwrap().bytes, vec![0x90]);
5877    }
5878
5879    // === x86-32 INC/DEC short forms (0x40+rd / 0x48+rd) ===
5880
5881    #[test]
5882    fn test_x86_32_inc_eax() {
5883        // inc eax → 0x40 (short form)
5884        let instr = make_instr("inc", vec![Register(Eax)]);
5885        let result = encode_instruction(&instr, Arch::X86).unwrap();
5886        assert_eq!(result.bytes, vec![0x40]);
5887    }
5888
5889    #[test]
5890    fn test_x86_32_inc_ebx() {
5891        // inc ebx → 0x43 (0x40 + 3)
5892        let instr = make_instr("inc", vec![Register(Ebx)]);
5893        let result = encode_instruction(&instr, Arch::X86).unwrap();
5894        assert_eq!(result.bytes, vec![0x43]);
5895    }
5896
5897    #[test]
5898    fn test_x86_32_inc_edi() {
5899        // inc edi → 0x47 (0x40 + 7)
5900        let instr = make_instr("inc", vec![Register(Edi)]);
5901        let result = encode_instruction(&instr, Arch::X86).unwrap();
5902        assert_eq!(result.bytes, vec![0x47]);
5903    }
5904
5905    #[test]
5906    fn test_x86_32_dec_eax() {
5907        // dec eax → 0x48 (short form)
5908        let instr = make_instr("dec", vec![Register(Eax)]);
5909        let result = encode_instruction(&instr, Arch::X86).unwrap();
5910        assert_eq!(result.bytes, vec![0x48]);
5911    }
5912
5913    #[test]
5914    fn test_x86_32_dec_esp() {
5915        // dec esp → 0x4C (0x48 + 4)
5916        let instr = make_instr("dec", vec![Register(Esp)]);
5917        let result = encode_instruction(&instr, Arch::X86).unwrap();
5918        assert_eq!(result.bytes, vec![0x4C]);
5919    }
5920
5921    #[test]
5922    fn test_x86_32_inc_ax() {
5923        // inc ax → 66 40 (16-bit override + short form)
5924        let instr = make_instr("inc", vec![Register(Ax)]);
5925        let result = encode_instruction(&instr, Arch::X86).unwrap();
5926        assert_eq!(result.bytes, vec![0x66, 0x40]);
5927    }
5928
5929    #[test]
5930    fn test_x86_32_dec_cx() {
5931        // dec cx → 66 49 (16-bit override + short form)
5932        let instr = make_instr("dec", vec![Register(Cx)]);
5933        let result = encode_instruction(&instr, Arch::X86).unwrap();
5934        assert_eq!(result.bytes, vec![0x66, 0x49]);
5935    }
5936
5937    #[test]
5938    fn test_x86_32_inc_al_uses_modrm() {
5939        // inc al → FE C0 (8-bit uses ModR/M form, not short form)
5940        let instr = make_instr("inc", vec![Register(Al)]);
5941        let result = encode_instruction(&instr, Arch::X86).unwrap();
5942        assert_eq!(result.bytes, vec![0xFE, 0xC0]);
5943    }
5944
5945    // === push/pop 16-bit registers ===
5946
5947    #[test]
5948    fn test_push_ax() {
5949        // push ax → 66 50
5950        let bytes = encode("push", vec![Register(Ax)]);
5951        assert_eq!(bytes, vec![0x66, 0x50]);
5952    }
5953
5954    #[test]
5955    fn test_pop_ax() {
5956        // pop ax → 66 58
5957        let bytes = encode("pop", vec![Register(Ax)]);
5958        assert_eq!(bytes, vec![0x66, 0x58]);
5959    }
5960
5961    #[test]
5962    fn test_push_bx() {
5963        let bytes = encode("push", vec![Register(Bx)]);
5964        assert_eq!(bytes, vec![0x66, 0x53]);
5965    }
5966
5967    #[test]
5968    fn test_pop_bx() {
5969        let bytes = encode("pop", vec![Register(Bx)]);
5970        assert_eq!(bytes, vec![0x66, 0x5B]);
5971    }
5972
5973    // === xchg 16-bit ===
5974
5975    #[test]
5976    fn test_xchg_ax_bx_shortcut() {
5977        // xchg ax, bx → 66 93
5978        let bytes = encode("xchg", vec![Register(Ax), Register(Bx)]);
5979        assert_eq!(bytes, vec![0x66, 0x93]);
5980    }
5981
5982    // === movsx with memory operand ===
5983
5984    #[test]
5985    fn test_movsx_eax_byte_mem() {
5986        // movsx eax, byte [rbx] → 0F BE 03
5987        let mem = MemoryOperand {
5988            base: Some(Rbx),
5989            ..Default::default()
5990        };
5991        let bytes = encode_with_hint(
5992            "movsx",
5993            vec![Register(Eax), Memory(Box::new(mem))],
5994            OperandSize::Byte,
5995        );
5996        assert_eq!(bytes, vec![0x0F, 0xBE, 0x03]);
5997    }
5998
5999    #[test]
6000    fn test_movsx_rax_word_mem() {
6001        // movsx rax, word [rbx] → 48 0F BF 03
6002        let mem = MemoryOperand {
6003            base: Some(Rbx),
6004            ..Default::default()
6005        };
6006        let bytes = encode_with_hint(
6007            "movsx",
6008            vec![Register(Rax), Memory(Box::new(mem))],
6009            OperandSize::Word,
6010        );
6011        assert_eq!(bytes, vec![0x48, 0x0F, 0xBF, 0x03]);
6012    }
6013
6014    #[test]
6015    fn test_movsxd_rax_dword_mem() {
6016        // movsxd rax, dword [rbx] → 48 63 03
6017        let mem = MemoryOperand {
6018            base: Some(Rbx),
6019            ..Default::default()
6020        };
6021        let bytes = encode_with_hint(
6022            "movsx",
6023            vec![Register(Rax), Memory(Box::new(mem))],
6024            OperandSize::Dword,
6025        );
6026        assert_eq!(bytes, vec![0x48, 0x63, 0x03]);
6027    }
6028
6029    // === bsf/bsr with memory operand ===
6030
6031    #[test]
6032    fn test_bsf_eax_mem() {
6033        // bsf eax, [rbx] → 0F BC 03
6034        let mem = MemoryOperand {
6035            base: Some(Rbx),
6036            ..Default::default()
6037        };
6038        let bytes = encode("bsf", vec![Register(Eax), Memory(Box::new(mem))]);
6039        assert_eq!(bytes, vec![0x0F, 0xBC, 0x03]);
6040    }
6041
6042    #[test]
6043    fn test_bsr_rax_mem() {
6044        // bsr rax, [rbx] → 48 0F BD 03
6045        let mem = MemoryOperand {
6046            base: Some(Rbx),
6047            ..Default::default()
6048        };
6049        let bytes = encode("bsr", vec![Register(Rax), Memory(Box::new(mem))]);
6050        assert_eq!(bytes, vec![0x48, 0x0F, 0xBD, 0x03]);
6051    }
6052
6053    // === popcnt/lzcnt/tzcnt with memory operand ===
6054
6055    #[test]
6056    fn test_popcnt_eax_mem() {
6057        // popcnt eax, [rbx] → F3 0F B8 03
6058        let mem = MemoryOperand {
6059            base: Some(Rbx),
6060            ..Default::default()
6061        };
6062        let bytes = encode("popcnt", vec![Register(Eax), Memory(Box::new(mem))]);
6063        assert_eq!(bytes, vec![0xF3, 0x0F, 0xB8, 0x03]);
6064    }
6065
6066    #[test]
6067    fn test_lzcnt_rax_mem() {
6068        // lzcnt rax, [rbx] → F3 48 0F BD 03
6069        let mem = MemoryOperand {
6070            base: Some(Rbx),
6071            ..Default::default()
6072        };
6073        let bytes = encode("lzcnt", vec![Register(Rax), Memory(Box::new(mem))]);
6074        assert_eq!(bytes, vec![0xF3, 0x48, 0x0F, 0xBD, 0x03]);
6075    }
6076
6077    #[test]
6078    fn test_tzcnt_eax_mem() {
6079        // tzcnt eax, [rbx] → F3 0F BC 03
6080        let mem = MemoryOperand {
6081            base: Some(Rbx),
6082            ..Default::default()
6083        };
6084        let bytes = encode("tzcnt", vec![Register(Eax), Memory(Box::new(mem))]);
6085        assert_eq!(bytes, vec![0xF3, 0x0F, 0xBC, 0x03]);
6086    }
6087
6088    // === bt/bts/btr/btc with memory operands ===
6089
6090    #[test]
6091    fn test_bt_mem_reg() {
6092        // bt [rbx], eax → 0F A3 03
6093        let mem = MemoryOperand {
6094            base: Some(Rbx),
6095            ..Default::default()
6096        };
6097        let bytes = encode("bt", vec![Memory(Box::new(mem)), Register(Eax)]);
6098        assert_eq!(bytes, vec![0x0F, 0xA3, 0x03]);
6099    }
6100
6101    #[test]
6102    fn test_bts_mem_imm() {
6103        // bts dword [rbx], 5 → 0F BA 2B 05
6104        let mem = MemoryOperand {
6105            base: Some(Rbx),
6106            size: Some(OperandSize::Dword),
6107            ..Default::default()
6108        };
6109        let bytes = encode("bts", vec![Memory(Box::new(mem)), Immediate(5)]);
6110        assert_eq!(bytes, vec![0x0F, 0xBA, 0x2B, 0x05]);
6111    }
6112
6113    // === shift instructions with memory operands ===
6114
6115    #[test]
6116    fn test_shl_mem_1() {
6117        // shl dword [rbx], 1 → D1 23
6118        let mem = MemoryOperand {
6119            base: Some(Rbx),
6120            size: Some(OperandSize::Dword),
6121            ..Default::default()
6122        };
6123        let bytes = encode("shl", vec![Memory(Box::new(mem)), Immediate(1)]);
6124        assert_eq!(bytes, vec![0xD1, 0x23]);
6125    }
6126
6127    #[test]
6128    fn test_shr_mem_imm() {
6129        // shr dword [rbx], 4 → C1 2B 04
6130        let mem = MemoryOperand {
6131            base: Some(Rbx),
6132            size: Some(OperandSize::Dword),
6133            ..Default::default()
6134        };
6135        let bytes = encode("shr", vec![Memory(Box::new(mem)), Immediate(4)]);
6136        assert_eq!(bytes, vec![0xC1, 0x2B, 0x04]);
6137    }
6138
6139    #[test]
6140    fn test_sar_mem_cl() {
6141        // sar dword [rbx], cl → D3 3B
6142        let mem = MemoryOperand {
6143            base: Some(Rbx),
6144            size: Some(OperandSize::Dword),
6145            ..Default::default()
6146        };
6147        let bytes = encode("sar", vec![Memory(Box::new(mem)), Register(Cl)]);
6148        assert_eq!(bytes, vec![0xD3, 0x3B]);
6149    }
6150
6151    // === mov_reg_imm error on unsupported size ===
6152
6153    #[test]
6154    fn test_mov_xmm_imm_error() {
6155        let instr = make_instr("mov", vec![Register(Xmm0), Immediate(1)]);
6156        let result = encode_instruction(&instr, Arch::X86_64);
6157        assert!(result.is_err());
6158    }
6159
6160    // === Expression operands ===
6161
6162    #[test]
6163    fn test_mov_rax_label_expression() {
6164        // mov rax, label+8 → REX.W B8 <imm64> with relocation addend=8
6165        let expr = Expr::Add(
6166            Box::new(Expr::Label(String::from("data"))),
6167            Box::new(Expr::Num(8)),
6168        );
6169        let instr = make_instr("mov", vec![Register(Rax), Expression(expr)]);
6170        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
6171        assert_eq!(result.bytes[0], 0x48); // REX.W
6172        assert_eq!(result.bytes[1], 0xB8); // mov rax, imm64
6173        let reloc = result.relocation.unwrap();
6174        assert_eq!(&*reloc.label, "data");
6175        assert_eq!(reloc.addend, 8);
6176        assert_eq!(reloc.size, 8);
6177        assert_eq!(reloc.kind, RelocKind::Absolute);
6178    }
6179
6180    #[test]
6181    fn test_jmp_label_expression() {
6182        let expr = Expr::Sub(
6183            Box::new(Expr::Label(String::from("target"))),
6184            Box::new(Expr::Num(2)),
6185        );
6186        let instr = make_instr("jmp", vec![Expression(expr)]);
6187        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
6188        assert_eq!(result.bytes[0], 0xE9); // jmp rel32
6189        let reloc = result.relocation.unwrap();
6190        assert_eq!(&*reloc.label, "target");
6191        assert_eq!(reloc.addend, -2);
6192        assert_eq!(reloc.kind, RelocKind::X86Relative);
6193        assert!(result.relax.is_some()); // Should have relaxation info
6194    }
6195
6196    #[test]
6197    fn test_call_label_expression() {
6198        let expr = Expr::Add(
6199            Box::new(Expr::Label(String::from("func"))),
6200            Box::new(Expr::Num(4)),
6201        );
6202        let instr = make_instr("call", vec![Expression(expr)]);
6203        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
6204        assert_eq!(result.bytes[0], 0xE8); // call rel32
6205        let reloc = result.relocation.unwrap();
6206        assert_eq!(&*reloc.label, "func");
6207        assert_eq!(reloc.addend, 4);
6208    }
6209
6210    #[test]
6211    fn test_jcc_label_expression() {
6212        let expr = Expr::Add(
6213            Box::new(Expr::Label(String::from("dest"))),
6214            Box::new(Expr::Num(0)),
6215        );
6216        let instr = make_instr("je", vec![Expression(expr)]);
6217        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
6218        assert_eq!(result.bytes[0], 0x0F); // je near
6219        let reloc = result.relocation.unwrap();
6220        assert_eq!(&*reloc.label, "dest");
6221        assert_eq!(reloc.addend, 0);
6222    }
6223
6224    #[test]
6225    fn test_push_label_expression() {
6226        let expr = Expr::Add(
6227            Box::new(Expr::Label(String::from("data"))),
6228            Box::new(Expr::Num(16)),
6229        );
6230        let instr = make_instr("push", vec![Expression(expr)]);
6231        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
6232        assert_eq!(result.bytes[0], 0x68); // push imm32
6233        let reloc = result.relocation.unwrap();
6234        assert_eq!(&*reloc.label, "data");
6235        assert_eq!(reloc.addend, 16);
6236    }
6237
6238    #[test]
6239    fn test_loop_label_expression() {
6240        let expr = Expr::Sub(
6241            Box::new(Expr::Label(String::from("top"))),
6242            Box::new(Expr::Num(1)),
6243        );
6244        let instr = make_instr("loop", vec![Expression(expr)]);
6245        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
6246        // Long form: E2 02 EB 05 E9 [rel32]
6247        assert_eq!(result.bytes[0], 0xE2); // loop
6248        let reloc = result.relocation.unwrap();
6249        assert_eq!(&*reloc.label, "top");
6250        assert_eq!(reloc.addend, -1);
6251        assert_eq!(reloc.size, 4); // rel32 in long form
6252                                   // Relaxation short form provided
6253        assert!(result.relax.is_some());
6254        let ri = result.relax.unwrap();
6255        assert_eq!(ri.short_bytes[0], 0xE2);
6256        assert_eq!(ri.short_bytes.len(), 2);
6257    }
6258
6259    // === extract_label helper ===
6260
6261    #[test]
6262    fn test_extract_label_plain() {
6263        let op = Label(String::from("foo"));
6264        assert_eq!(extract_label(&op), Some(("foo", 0)));
6265    }
6266
6267    #[test]
6268    fn test_extract_label_expression() {
6269        let expr = Expr::Add(
6270            Box::new(Expr::Label(String::from("bar"))),
6271            Box::new(Expr::Num(10)),
6272        );
6273        assert_eq!(extract_label(&Expression(expr)), Some(("bar", 10)));
6274    }
6275
6276    #[test]
6277    fn test_extract_label_non_label() {
6278        assert_eq!(extract_label(&Immediate(42)), None);
6279        assert_eq!(extract_label(&Register(Rax)), None);
6280    }
6281
6282    // === IMUL reg, mem, imm ===
6283
6284    #[test]
6285    fn test_imul_reg_mem_imm8() {
6286        // imul eax, [rcx], 5 → 6B 01 05
6287        let mem = MemoryOperand {
6288            base: Some(Rcx),
6289            ..Default::default()
6290        };
6291        let bytes = encode(
6292            "imul",
6293            vec![Register(Eax), Memory(Box::new(mem)), Immediate(5)],
6294        );
6295        assert_eq!(bytes, vec![0x6B, 0x01, 0x05]);
6296    }
6297
6298    #[test]
6299    fn test_imul_reg_mem_imm32() {
6300        // imul rax, [rdx], 1000 → 48 69 02 E8 03 00 00
6301        let mem = MemoryOperand {
6302            base: Some(Rdx),
6303            ..Default::default()
6304        };
6305        let bytes = encode(
6306            "imul",
6307            vec![Register(Rax), Memory(Box::new(mem)), Immediate(1000)],
6308        );
6309        assert_eq!(bytes, vec![0x48, 0x69, 0x02, 0xE8, 0x03, 0x00, 0x00]);
6310    }
6311
6312    // === ret imm16 ===
6313
6314    #[test]
6315    fn test_ret_imm16() {
6316        // ret 8 → C2 08 00
6317        let bytes = encode("ret", vec![Immediate(8)]);
6318        assert_eq!(bytes, vec![0xC2, 0x08, 0x00]);
6319    }
6320
6321    #[test]
6322    fn test_ret_imm16_large() {
6323        // ret 0x1234 → C2 34 12
6324        let bytes = encode("ret", vec![Immediate(0x1234)]);
6325        assert_eq!(bytes, vec![0xC2, 0x34, 0x12]);
6326    }
6327
6328    #[test]
6329    fn test_retn_alias() {
6330        // retn = ret (near return)
6331        assert_eq!(encode("retn", vec![]), vec![0xC3]);
6332        assert_eq!(encode("retn", vec![Immediate(4)]), vec![0xC2, 0x04, 0x00]);
6333    }
6334
6335    // === retf / lret (far return) ===
6336
6337    #[test]
6338    fn test_retf() {
6339        assert_eq!(encode("retf", vec![]), vec![0xCB]);
6340    }
6341
6342    #[test]
6343    fn test_retf_imm16() {
6344        // retf 4 → CA 04 00
6345        let bytes = encode("retf", vec![Immediate(4)]);
6346        assert_eq!(bytes, vec![0xCA, 0x04, 0x00]);
6347    }
6348
6349    #[test]
6350    fn test_lret_alias() {
6351        assert_eq!(encode("lret", vec![]), vec![0xCB]);
6352        assert_eq!(encode("lret", vec![Immediate(8)]), vec![0xCA, 0x08, 0x00]);
6353    }
6354
6355    // === movabs alias ===
6356
6357    #[test]
6358    fn test_movabs_alias() {
6359        // movabs rax, 0x12345678 should work like mov rax, 0x12345678
6360        let bytes_mov = encode("mov", vec![Register(Rax), Immediate(0x12345678)]);
6361        let bytes_movabs = encode("movabs", vec![Register(Rax), Immediate(0x12345678)]);
6362        assert_eq!(bytes_mov, bytes_movabs);
6363    }
6364
6365    #[test]
6366    fn test_movabs_imm64() {
6367        // movabs rax, 0x0102030405060708
6368        let bytes = encode("movabs", vec![Register(Rax), Immediate(0x0102030405060708)]);
6369        assert_eq!(
6370            bytes,
6371            vec![0x48, 0xB8, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]
6372        );
6373    }
6374
6375    // === REX + high-byte conflict detection ===
6376
6377    #[test]
6378    fn test_high_byte_rex_conflict_rejected() {
6379        // mov ah, sil should fail: AH is incompatible with REX (needed for SIL)
6380        let instr = make_instr("mov", vec![Register(Ah), Register(Sil)]);
6381        let result = encode_instruction(&instr, Arch::X86_64);
6382        assert!(result.is_err());
6383        let err = result.unwrap_err();
6384        match err {
6385            AsmError::InvalidOperands { detail, .. } => {
6386                assert!(detail.contains("high-byte"));
6387            }
6388            other => panic!("expected InvalidOperands, got {:?}", other),
6389        }
6390    }
6391
6392    #[test]
6393    fn test_high_byte_extended_reg_conflict_rejected() {
6394        // add ah, r8b should fail: AH + extended register
6395        let instr = make_instr("add", vec![Register(Ah), Register(R8b)]);
6396        let result = encode_instruction(&instr, Arch::X86_64);
6397        assert!(result.is_err());
6398    }
6399
6400    #[test]
6401    fn test_high_byte_without_rex_ok() {
6402        // mov ah, al — both legacy 8-bit, no REX needed → should work
6403        // AH=code 4, AL=code 0, opcode 0x88 r/m,r: modrm(11, 0, 4) = 0xC4
6404        let bytes = encode("mov", vec![Register(Ah), Register(Al)]);
6405        assert_eq!(bytes, vec![0x88, 0xC4]);
6406    }
6407
6408    #[test]
6409    fn test_high_byte_pair_ok() {
6410        // xor ah, ch — two high-byte regs, no REX needed → should work
6411        // AH=code 4, CH=code 5, opcode 0x30 r/m,r: modrm(11, 5, 4) = 0xEC
6412        let bytes = encode("xor", vec![Register(Ah), Register(Ch)]);
6413        assert_eq!(bytes, vec![0x30, 0xEC]);
6414    }
6415
6416    // === LOCK prefix validation ===
6417
6418    #[test]
6419    fn test_lock_valid_memory_dest() {
6420        // lock add dword ptr [rax], 1 — valid (memory destination)
6421        let mem = MemoryOperand {
6422            base: Some(Rax),
6423            ..Default::default()
6424        };
6425        let bytes = encode_with_prefix(
6426            "add",
6427            vec![Memory(Box::new(mem)), Immediate(1)],
6428            Prefix::Lock,
6429        );
6430        assert_eq!(bytes[0], 0xF0);
6431    }
6432
6433    #[test]
6434    fn test_lock_invalid_reg_dest() {
6435        // lock add eax, ebx — invalid (register destination)
6436        let mut instr = make_instr("add", vec![Register(Eax), Register(Ebx)]);
6437        instr.prefixes = PrefixList::from(alloc::vec![Prefix::Lock]);
6438        let result = encode_instruction(&instr, Arch::X86_64);
6439        assert!(result.is_err());
6440        match result.unwrap_err() {
6441            AsmError::InvalidOperands { detail, .. } => {
6442                assert!(detail.contains("LOCK"));
6443            }
6444            other => panic!("expected InvalidOperands for LOCK, got {:?}", other),
6445        }
6446    }
6447
6448    #[test]
6449    fn test_lock_invalid_imm_dest() {
6450        // lock xchg eax, ecx — invalid (register destination, even though xchg is lockable)
6451        let mut instr = make_instr("xchg", vec![Register(Eax), Register(Ecx)]);
6452        instr.prefixes = PrefixList::from(alloc::vec![Prefix::Lock]);
6453        let result = encode_instruction(&instr, Arch::X86_64);
6454        assert!(result.is_err());
6455    }
6456
6457    // === Push/Pop segment registers ===
6458
6459    #[test]
6460    fn test_push_fs() {
6461        // push fs → 0F A0
6462        assert_eq!(encode("push", vec![Register(Fs)]), vec![0x0F, 0xA0]);
6463    }
6464
6465    #[test]
6466    fn test_push_gs() {
6467        // push gs → 0F A8
6468        assert_eq!(encode("push", vec![Register(Gs)]), vec![0x0F, 0xA8]);
6469    }
6470
6471    #[test]
6472    fn test_pop_fs() {
6473        // pop fs → 0F A1
6474        assert_eq!(encode("pop", vec![Register(Fs)]), vec![0x0F, 0xA1]);
6475    }
6476
6477    #[test]
6478    fn test_pop_gs() {
6479        // pop gs → 0F A9
6480        assert_eq!(encode("pop", vec![Register(Gs)]), vec![0x0F, 0xA9]);
6481    }
6482
6483    // === xchg eax,eax → NOP (0x90 single-byte) ===
6484
6485    #[test]
6486    fn test_xchg_eax_eax_is_nop() {
6487        // xchg eax, eax → 90 (the canonical NOP encoding)
6488        let bytes = encode("xchg", vec![Register(Eax), Register(Eax)]);
6489        assert_eq!(bytes, vec![0x90]);
6490    }
6491
6492    #[test]
6493    fn test_xchg_rax_rax() {
6494        // xchg rax, rax → 48 90 (REX.W + NOP form)
6495        let bytes = encode("xchg", vec![Register(Rax), Register(Rax)]);
6496        assert_eq!(bytes, vec![0x48, 0x90]);
6497    }
6498
6499    #[test]
6500    fn test_xchg_ax_ax() {
6501        // xchg ax, ax → 66 90 (16-bit xchg, operand-size prefix + NOP form)
6502        let bytes = encode("xchg", vec![Register(Ax), Register(Ax)]);
6503        assert_eq!(bytes, vec![0x66, 0x90]);
6504    }
6505
6506    // === High-byte register encoding correctness ===
6507
6508    #[test]
6509    fn test_mov_ah_imm8() {
6510        // mov ah, 0x42 → B4 42 (B0+4=B4 for AH)
6511        let bytes = encode("mov", vec![Register(Ah), Immediate(0x42)]);
6512        assert_eq!(bytes, vec![0xB4, 0x42]);
6513    }
6514
6515    #[test]
6516    fn test_mov_ch_imm8() {
6517        // mov ch, 0x11 → B5 11 (B0+5=B5 for CH)
6518        let bytes = encode("mov", vec![Register(Ch), Immediate(0x11)]);
6519        assert_eq!(bytes, vec![0xB5, 0x11]);
6520    }
6521
6522    #[test]
6523    fn test_mov_dh_imm8() {
6524        // mov dh, 0x22 → B6 22 (B0+6=B6 for DH)
6525        let bytes = encode("mov", vec![Register(Dh), Immediate(0x22)]);
6526        assert_eq!(bytes, vec![0xB6, 0x22]);
6527    }
6528
6529    #[test]
6530    fn test_mov_bh_imm8() {
6531        // mov bh, 0x33 → B7 33 (B0+7=B7 for BH)
6532        let bytes = encode("mov", vec![Register(Bh), Immediate(0x33)]);
6533        assert_eq!(bytes, vec![0xB7, 0x33]);
6534    }
6535
6536    // === Shift memory operand with size_hint ===
6537
6538    #[test]
6539    fn test_shl_byte_ptr_mem_1() {
6540        // shl byte ptr [rbx], 1 → D0 /4 with byte operand
6541        let mem = MemoryOperand {
6542            base: Some(Rbx),
6543            ..Default::default()
6544        };
6545        let bytes = encode_with_hint(
6546            "shl",
6547            vec![Memory(Box::new(mem)), Immediate(1)],
6548            OperandSize::Byte,
6549        );
6550        // Should encode as D0 23 (D0=shift byte by 1, ModRM /4 with [rbx])
6551        assert_eq!(bytes[0], 0xD0); // byte opcode, not D1 (dword)
6552    }
6553
6554    #[test]
6555    fn test_shr_qword_ptr_mem_cl() {
6556        // shr qword ptr [rax], cl → REX.W D3 /5
6557        let mem = MemoryOperand {
6558            base: Some(Rax),
6559            ..Default::default()
6560        };
6561        let bytes = encode_with_hint(
6562            "shr",
6563            vec![Memory(Box::new(mem)), Register(Cl)],
6564            OperandSize::Qword,
6565        );
6566        assert_eq!(bytes[0], 0x48); // REX.W
6567        assert_eq!(bytes[1], 0xD3); // qword shift by cl
6568    }
6569
6570    #[test]
6571    fn test_shl_word_ptr_mem_imm() {
6572        // shl word ptr [rcx], 4 → 66 C1 /4 imm8
6573        let mem = MemoryOperand {
6574            base: Some(Rcx),
6575            ..Default::default()
6576        };
6577        let bytes = encode_with_hint(
6578            "shl",
6579            vec![Memory(Box::new(mem)), Immediate(4)],
6580            OperandSize::Word,
6581        );
6582        assert_eq!(bytes[0], 0x66); // 16-bit prefix
6583        assert_eq!(bytes[1], 0xC1); // word shift by imm8
6584    }
6585
6586    // === Push/Pop validation ===
6587
6588    #[test]
6589    fn test_push_eax_rejected() {
6590        // push eax is invalid in 64-bit mode
6591        let instr = make_instr("push", vec![Register(Eax)]);
6592        let result = encode_instruction(&instr, Arch::X86_64);
6593        assert!(result.is_err());
6594    }
6595
6596    #[test]
6597    fn test_pop_eax_rejected() {
6598        // pop eax is invalid in 64-bit mode
6599        let instr = make_instr("pop", vec![Register(Eax)]);
6600        let result = encode_instruction(&instr, Arch::X86_64);
6601        assert!(result.is_err());
6602    }
6603
6604    #[test]
6605    fn test_push_al_rejected() {
6606        // push al is invalid
6607        let instr = make_instr("push", vec![Register(Al)]);
6608        let result = encode_instruction(&instr, Arch::X86_64);
6609        assert!(result.is_err());
6610    }
6611
6612    #[test]
6613    fn test_push_cs_rejected() {
6614        // push cs invalid in 64-bit mode
6615        let instr = make_instr("push", vec![Register(Cs)]);
6616        let result = encode_instruction(&instr, Arch::X86_64);
6617        assert!(result.is_err());
6618    }
6619
6620    #[test]
6621    fn test_pop_ds_rejected() {
6622        // pop ds invalid in 64-bit mode
6623        let instr = make_instr("pop", vec![Register(Ds)]);
6624        let result = encode_instruction(&instr, Arch::X86_64);
6625        assert!(result.is_err());
6626    }
6627
6628    // === CMOVcc 8-bit rejection ===
6629
6630    #[test]
6631    fn test_cmove_8bit_rejected() {
6632        let instr = make_instr("cmove", vec![Register(Al), Register(Bl)]);
6633        let result = encode_instruction(&instr, Arch::X86_64);
6634        assert!(result.is_err());
6635    }
6636
6637    // === disp_label relocation propagation ===
6638
6639    #[test]
6640    fn test_add_rax_mem_label_reloc() {
6641        // add rax, [my_data] — should produce relocation
6642        let mem = MemoryOperand {
6643            base: Some(Rip),
6644            disp_label: Some(alloc::string::String::from("my_data")),
6645            addr_mode: AddrMode::Offset,
6646            ..Default::default()
6647        };
6648        let instr = make_instr("add", vec![Register(Rax), Memory(Box::new(mem))]);
6649        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
6650        assert!(
6651            result.relocation.is_some(),
6652            "expected relocation for add rax, [my_data]"
6653        );
6654        let reloc = result.relocation.unwrap();
6655        assert_eq!(&*reloc.label, "my_data");
6656        assert_eq!(reloc.kind, RelocKind::X86Relative);
6657    }
6658
6659    #[test]
6660    fn test_cmp_mem_label_imm_reloc() {
6661        // cmp dword ptr [my_data], 0 — should produce relocation via centralized scan
6662        let mem = MemoryOperand {
6663            base: Some(Rip),
6664            disp_label: Some(alloc::string::String::from("counter")),
6665            addr_mode: AddrMode::Offset,
6666            ..Default::default()
6667        };
6668        let mut instr = make_instr("cmp", vec![Memory(Box::new(mem)), Immediate(0)]);
6669        instr.size_hint = Some(OperandSize::Dword);
6670        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
6671        assert!(
6672            result.relocation.is_some(),
6673            "expected relocation for cmp [counter], 0"
6674        );
6675        let reloc = result.relocation.unwrap();
6676        assert_eq!(&*reloc.label, "counter");
6677    }
6678
6679    #[test]
6680    fn test_mov_mem_label_reg_reloc() {
6681        // mov [my_var], rax — should produce relocation
6682        let mem = MemoryOperand {
6683            base: Some(Rip),
6684            disp_label: Some(alloc::string::String::from("my_var")),
6685            addr_mode: AddrMode::Offset,
6686            ..Default::default()
6687        };
6688        let instr = make_instr("mov", vec![Memory(Box::new(mem)), Register(Rax)]);
6689        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
6690        assert!(
6691            result.relocation.is_some(),
6692            "expected relocation for mov [my_var], rax"
6693        );
6694        let reloc = result.relocation.unwrap();
6695        assert_eq!(&*reloc.label, "my_var");
6696        assert_eq!(reloc.kind, RelocKind::X86Relative);
6697    }
6698
6699    #[test]
6700    fn test_mov_mem_label_imm_reloc() {
6701        // mov dword ptr [flag], 1 — should produce relocation
6702        let mem = MemoryOperand {
6703            base: Some(Rip),
6704            disp_label: Some(alloc::string::String::from("flag")),
6705            addr_mode: AddrMode::Offset,
6706            ..Default::default()
6707        };
6708        let mut instr = make_instr("mov", vec![Memory(Box::new(mem)), Immediate(1)]);
6709        instr.size_hint = Some(OperandSize::Dword);
6710        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
6711        assert!(
6712            result.relocation.is_some(),
6713            "expected relocation for mov [flag], 1"
6714        );
6715        let reloc = result.relocation.unwrap();
6716        assert_eq!(&*reloc.label, "flag");
6717        assert_eq!(reloc.kind, RelocKind::X86Relative);
6718    }
6719
6720    #[test]
6721    fn test_test_mem_label_reloc() {
6722        // test dword ptr [status], 1 — should produce relocation via centralized scan
6723        let mem = MemoryOperand {
6724            base: Some(Rip),
6725            disp_label: Some(alloc::string::String::from("status")),
6726            addr_mode: AddrMode::Offset,
6727            ..Default::default()
6728        };
6729        let mut instr = make_instr("test", vec![Memory(Box::new(mem)), Immediate(1)]);
6730        instr.size_hint = Some(OperandSize::Dword);
6731        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
6732        assert!(
6733            result.relocation.is_some(),
6734            "expected relocation for test [status], 1"
6735        );
6736    }
6737
6738    // ── P0-1: Segment override prefix must come BEFORE REX/opcode ──
6739
6740    #[test]
6741    fn test_segment_override_fs_prefix_position() {
6742        // mov rax, fs:[rbx] → 64 48 8B 03
6743        let mem = MemoryOperand {
6744            base: Some(Rbx),
6745            segment: Some(Fs),
6746            ..Default::default()
6747        };
6748        let bytes = encode("mov", vec![Register(Rax), Memory(Box::new(mem))]);
6749        // First byte must be 0x64 (FS override), not the REX prefix
6750        assert_eq!(bytes[0], 0x64, "FS segment override must be first byte");
6751        assert_eq!(bytes[1], 0x48, "REX.W must follow segment override");
6752        assert_eq!(bytes[2], 0x8B, "opcode must follow REX");
6753    }
6754
6755    #[test]
6756    fn test_segment_override_gs_prefix_position() {
6757        // mov eax, gs:[rdx] → 65 8B 02
6758        let mem = MemoryOperand {
6759            base: Some(Rdx),
6760            segment: Some(Gs),
6761            ..Default::default()
6762        };
6763        let bytes = encode("mov", vec![Register(Eax), Memory(Box::new(mem))]);
6764        assert_eq!(bytes[0], 0x65, "GS segment override must be first byte");
6765        assert_eq!(bytes[1], 0x8B, "opcode must follow segment override");
6766    }
6767
6768    #[test]
6769    fn test_segment_override_with_extended_reg() {
6770        // add rax, fs:[r12] → 64 49 03 04 24 (FS + REX.WB + opcode + SIB)
6771        let mem = MemoryOperand {
6772            base: Some(R12),
6773            segment: Some(Fs),
6774            ..Default::default()
6775        };
6776        let bytes = encode("add", vec![Register(Rax), Memory(Box::new(mem))]);
6777        assert_eq!(bytes[0], 0x64, "FS must precede REX");
6778        assert_eq!(bytes[1] & 0xF0, 0x40, "REX must follow segment override");
6779    }
6780
6781    // ── P0-2: SIB index-only addressing ──
6782
6783    #[test]
6784    fn test_sib_index_only_disp0() {
6785        // mov rax, [rsi*4] → 48 8B 04 B5 00 00 00 00
6786        // Must have mod=00, base=101, and 4 bytes of disp32=0
6787        let mem = MemoryOperand {
6788            index: Some(Rsi),
6789            scale: 4,
6790            ..Default::default()
6791        };
6792        let bytes = encode("mov", vec![Register(Rax), Memory(Box::new(mem))]);
6793        assert_eq!(bytes.len(), 8, "SIB index-only must include 4-byte disp32");
6794        assert_eq!(bytes[0], 0x48); // REX.W
6795        assert_eq!(bytes[1], 0x8B); // MOV
6796        assert_eq!(bytes[2] & 0xC7, 0x04); // mod=00, rm=100 (SIB)
6797                                           // SIB: scale=4 (log2=10), index=RSI (110), base=101 (no base)
6798        assert_eq!(bytes[3], 0xB5); // 10_110_101
6799        assert_eq!(&bytes[4..8], &[0, 0, 0, 0]); // disp32=0
6800    }
6801
6802    #[test]
6803    fn test_sib_index_only_with_disp() {
6804        // lea rax, [rdi*8+16] → 48 8D 04 FD 10 00 00 00
6805        // NOT [rbp + rdi*8 + 16]
6806        let mem = MemoryOperand {
6807            index: Some(Rdi),
6808            scale: 8,
6809            disp: 16,
6810            ..Default::default()
6811        };
6812        let bytes = encode("lea", vec![Register(Rax), Memory(Box::new(mem))]);
6813        assert_eq!(bytes.len(), 8, "SIB index-only with disp must use disp32");
6814        assert_eq!(bytes[2] & 0xC0, 0x00, "mod must be 00 (disp32, no base)");
6815        // disp32 should be 16
6816        assert_eq!(&bytes[4..8], &(16i32).to_le_bytes());
6817    }
6818
6819    #[test]
6820    fn test_sib_index_only_extended_index() {
6821        // mov eax, [r9*2] → 42 8B 04 4D 00 00 00 00
6822        let mem = MemoryOperand {
6823            index: Some(R9),
6824            scale: 2,
6825            ..Default::default()
6826        };
6827        let bytes = encode("mov", vec![Register(Eax), Memory(Box::new(mem))]);
6828        assert_eq!(bytes.len(), 8, "SIB index-only with extended index");
6829        assert_eq!(bytes[0] & 0x42, 0x42, "REX.X must be set for R9 index");
6830    }
6831
6832    // ── P2: 8-bit operand validation ──
6833
6834    #[test]
6835    fn test_bt_rejects_8bit() {
6836        let instr = make_instr("bt", vec![Register(Al), Immediate(1)]);
6837        assert!(encode_instruction(&instr, Arch::X86_64).is_err());
6838    }
6839
6840    #[test]
6841    fn test_bsf_rejects_8bit() {
6842        let instr = make_instr("bsf", vec![Register(Al), Register(Cl)]);
6843        assert!(encode_instruction(&instr, Arch::X86_64).is_err());
6844    }
6845
6846    #[test]
6847    fn test_bsr_rejects_8bit() {
6848        let instr = make_instr("bsr", vec![Register(Al), Register(Cl)]);
6849        assert!(encode_instruction(&instr, Arch::X86_64).is_err());
6850    }
6851
6852    #[test]
6853    fn test_popcnt_rejects_8bit() {
6854        let instr = make_instr("popcnt", vec![Register(Al), Register(Cl)]);
6855        assert!(encode_instruction(&instr, Arch::X86_64).is_err());
6856    }
6857
6858    #[test]
6859    fn test_lzcnt_rejects_8bit() {
6860        let instr = make_instr("lzcnt", vec![Register(Al), Register(Cl)]);
6861        assert!(encode_instruction(&instr, Arch::X86_64).is_err());
6862    }
6863
6864    #[test]
6865    fn test_tzcnt_rejects_8bit() {
6866        let instr = make_instr("tzcnt", vec![Register(Al), Register(Cl)]);
6867        assert!(encode_instruction(&instr, Arch::X86_64).is_err());
6868    }
6869
6870    #[test]
6871    fn test_bswap_rejects_8bit() {
6872        let instr = make_instr("bswap", vec![Register(Al)]);
6873        assert!(encode_instruction(&instr, Arch::X86_64).is_err());
6874    }
6875
6876    #[test]
6877    fn test_bswap_rejects_16bit() {
6878        let instr = make_instr("bswap", vec![Register(Ax)]);
6879        assert!(encode_instruction(&instr, Arch::X86_64).is_err());
6880    }
6881
6882    // ── P3-1: No redundant REX.W on push/pop/jmp/call [mem] ──
6883
6884    #[test]
6885    fn test_push_mem_no_redundant_rex() {
6886        // push [rdi] → FF 37 (not 48 FF 37)
6887        let mem = MemoryOperand {
6888            base: Some(Rdi),
6889            ..Default::default()
6890        };
6891        let bytes = encode("push", vec![Memory(Box::new(mem))]);
6892        assert_eq!(bytes, &[0xFF, 0x37], "push [rdi] should not have REX.W");
6893    }
6894
6895    #[test]
6896    fn test_pop_mem_no_redundant_rex() {
6897        // pop [rdi] → 8F 07 (not 48 8F 07)
6898        let mem = MemoryOperand {
6899            base: Some(Rdi),
6900            ..Default::default()
6901        };
6902        let bytes = encode("pop", vec![Memory(Box::new(mem))]);
6903        assert_eq!(bytes, &[0x8F, 0x07], "pop [rdi] should not have REX.W");
6904    }
6905
6906    #[test]
6907    fn test_jmp_mem_no_redundant_rex() {
6908        // jmp [rdi] → FF 27 (not 48 FF 27)
6909        let mem = MemoryOperand {
6910            base: Some(Rdi),
6911            ..Default::default()
6912        };
6913        let bytes = encode("jmp", vec![Memory(Box::new(mem))]);
6914        assert_eq!(bytes, &[0xFF, 0x27], "jmp [rdi] should not have REX.W");
6915    }
6916
6917    #[test]
6918    fn test_call_mem_no_redundant_rex() {
6919        // call [rdi] → FF 17 (not 48 FF 17)
6920        let mem = MemoryOperand {
6921            base: Some(Rdi),
6922            ..Default::default()
6923        };
6924        let bytes = encode("call", vec![Memory(Box::new(mem))]);
6925        assert_eq!(bytes, &[0xFF, 0x17], "call [rdi] should not have REX.W");
6926    }
6927
6928    #[test]
6929    fn test_push_mem_extended_needs_rex() {
6930        // push [r15] → 41 FF 37 (REX.B for r15, but not REX.W)
6931        let mem = MemoryOperand {
6932            base: Some(R15),
6933            ..Default::default()
6934        };
6935        let bytes = encode("push", vec![Memory(Box::new(mem))]);
6936        assert_eq!(bytes[0], 0x41, "push [r15] needs REX.B");
6937        assert_eq!(bytes[1], 0xFF);
6938    }
6939
6940    // ── P3-2: xchg short form both directions ──
6941
6942    #[test]
6943    fn test_xchg_rbx_rax_short_form() {
6944        // xchg rbx, rax → 48 93 (same as xchg rax, rbx)
6945        let bytes = encode("xchg", vec![Register(Rbx), Register(Rax)]);
6946        assert_eq!(bytes, &[0x48, 0x93], "xchg rbx, rax should use short form");
6947    }
6948
6949    #[test]
6950    fn test_xchg_ecx_eax_short_form() {
6951        // xchg ecx, eax → 91 (same as xchg eax, ecx)
6952        let bytes = encode("xchg", vec![Register(Ecx), Register(Eax)]);
6953        assert_eq!(bytes, &[0x91], "xchg ecx, eax should use short form");
6954    }
6955
6956    // ── P1-2: ALU mem,imm reloc uses explicit disp offset (not heuristic) ──
6957
6958    #[test]
6959    fn test_alu_mem_imm_reloc_explicit() {
6960        // add dword ptr [label], 5 — relocation must point to displacement, not imm
6961        let mem = MemoryOperand {
6962            base: Some(Rip),
6963            disp_label: Some(alloc::string::String::from("data")),
6964            addr_mode: AddrMode::Offset,
6965            ..Default::default()
6966        };
6967        let mut instr = make_instr("add", vec![Memory(Box::new(mem)), Immediate(5)]);
6968        instr.size_hint = Some(OperandSize::Dword);
6969        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
6970        assert!(
6971            result.relocation.is_some(),
6972            "expected relocation for add [label], imm"
6973        );
6974        // The relocation offset should point inside the instruction, not at the trailing imm8
6975        let roff = result.relocation.as_ref().unwrap().offset;
6976        // For add [rip+disp32], imm8: opcode(1) + modrm(1) → disp32 at offset 2
6977        assert_eq!(roff, 2, "reloc should point at displacement, not immediate");
6978    }
6979
6980    // ── P1-3: No double segment override from builder API ──
6981
6982    #[test]
6983    fn test_no_double_segment_override() {
6984        // Builder constructs: prefixes=[SegFs] AND mem.segment=Some(Fs)
6985        let mem = MemoryOperand {
6986            base: Some(Rbx),
6987            segment: Some(Fs),
6988            ..Default::default()
6989        };
6990        let mut instr = make_instr("mov", vec![Register(Rax), Memory(Box::new(mem))]);
6991        instr.prefixes.push(Prefix::SegFs);
6992        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
6993        // Should only have ONE 0x64 byte, not two
6994        let count_64 = result.bytes.iter().filter(|&&b| b == 0x64).count();
6995        assert_eq!(count_64, 1, "should not emit double FS segment override");
6996    }
6997
6998    #[test]
6999    fn test_rip_relative_trailing_bytes_mov_mem_imm() {
7000        // mov dword ptr [rip+label], 42  →  C7 05 [disp32] [imm32]
7001        // The reloc should have trailing_bytes = 4 (the imm32 after disp32)
7002        let mem = MemoryOperand {
7003            base: Some(crate::ir::Register::Rip),
7004            index: None,
7005            scale: 1,
7006            disp: 0,
7007            size: Some(OperandSize::Dword),
7008            segment: None,
7009            disp_label: Some(String::from("data")),
7010            addr_mode: AddrMode::Offset,
7011            index_subtract: false,
7012        };
7013        let instr = make_instr_with_hint(
7014            "mov",
7015            vec![Memory(Box::new(mem)), Immediate(42)],
7016            Some(OperandSize::Dword),
7017        );
7018        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
7019        // Expected: C7 05 00 00 00 00 2A 00 00 00
7020        assert_eq!(result.bytes[0], 0xC7); // opcode
7021        assert_eq!(result.bytes[1], 0x05); // modrm: mod=00, /0, rm=101 (RIP)
7022        let reloc = result.relocation.as_ref().unwrap();
7023        assert_eq!(reloc.offset, 2); // disp32 starts at byte 2
7024        assert_eq!(reloc.size, 4); // 4-byte disp32
7025        assert_eq!(reloc.kind, RelocKind::X86Relative);
7026        assert_eq!(reloc.trailing_bytes, 4); // 4 bytes of imm32 follow
7027        assert_eq!(result.bytes.len(), 10); // total: opcode(1) + modrm(1) + disp32(4) + imm32(4) = 10
7028    }
7029
7030    #[test]
7031    fn test_rip_relative_trailing_bytes_mov_mem_reg() {
7032        // mov [rip+label], rax  →  48 89 05 [disp32]
7033        // The reloc should have trailing_bytes = 0 (nothing after disp32)
7034        let mem = MemoryOperand {
7035            base: Some(crate::ir::Register::Rip),
7036            index: None,
7037            scale: 1,
7038            disp: 0,
7039            size: None,
7040            segment: None,
7041            disp_label: Some(String::from("data")),
7042            addr_mode: AddrMode::Offset,
7043            index_subtract: false,
7044        };
7045        let instr = make_instr(
7046            "mov",
7047            vec![
7048                Memory(Box::new(mem)),
7049                Operand::Register(crate::ir::Register::Rax),
7050            ],
7051        );
7052        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
7053        let reloc = result.relocation.as_ref().unwrap();
7054        assert_eq!(reloc.kind, RelocKind::X86Relative);
7055        assert_eq!(reloc.trailing_bytes, 0); // disp32 is at the end of instruction
7056    }
7057
7058    #[test]
7059    fn test_rip_relative_trailing_bytes_alu_mem_imm8() {
7060        // add dword ptr [rip+label], 5  →  83 05 [disp32] 05
7061        // trailing_bytes = 1 (the imm8 after disp32)
7062        let mem = MemoryOperand {
7063            base: Some(crate::ir::Register::Rip),
7064            index: None,
7065            scale: 1,
7066            disp: 0,
7067            size: Some(OperandSize::Dword),
7068            segment: None,
7069            disp_label: Some(String::from("target")),
7070            addr_mode: AddrMode::Offset,
7071            index_subtract: false,
7072        };
7073        let instr = make_instr_with_hint(
7074            "add",
7075            vec![Memory(Box::new(mem)), Immediate(5)],
7076            Some(OperandSize::Dword),
7077        );
7078        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
7079        let reloc = result.relocation.as_ref().unwrap();
7080        assert_eq!(reloc.kind, RelocKind::X86Relative);
7081        assert_eq!(reloc.trailing_bytes, 1); // 1 byte of imm8 follows
7082    }
7083
7084    #[test]
7085    fn test_rip_relative_trailing_bytes_jmp() {
7086        // jmp label  →  E9 [disp32]
7087        // trailing_bytes = 0
7088        let instr = make_instr("jmp", vec![Label(String::from("target"))]);
7089        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
7090        let reloc = result.relocation.as_ref().unwrap();
7091        assert_eq!(reloc.kind, RelocKind::X86Relative);
7092        assert_eq!(reloc.trailing_bytes, 0);
7093    }
7094
7095    // === 8th Audit: Push imm range validation ===
7096
7097    #[test]
7098    fn test_push_imm8_short_form() {
7099        // push 0x42 → 6A 42
7100        assert_eq!(encode("push", vec![Immediate(0x42)]), vec![0x6A, 0x42]);
7101    }
7102
7103    #[test]
7104    fn test_push_imm32_full_form() {
7105        assert_eq!(
7106            encode("push", vec![Immediate(0x12345678)]),
7107            vec![0x68, 0x78, 0x56, 0x34, 0x12]
7108        );
7109    }
7110
7111    #[test]
7112    fn test_push_imm_out_of_range_rejects() {
7113        // push 0x1_0000_0000 → should error (doesn't fit imm32)
7114        let instr = make_instr("push", vec![Immediate(0x1_0000_0000)]);
7115        assert!(encode_instruction(&instr, Arch::X86_64).is_err());
7116    }
7117
7118    // === 8th Audit: IMUL 8-bit rejection ===
7119
7120    #[test]
7121    fn test_imul_2op_rejects_8bit() {
7122        let instr = make_instr("imul", vec![Register(Al), Register(Bl)]);
7123        assert!(encode_instruction(&instr, Arch::X86_64).is_err());
7124    }
7125
7126    #[test]
7127    fn test_imul_2op_mem_rejects_8bit() {
7128        let mem = MemoryOperand {
7129            base: Some(Rbx),
7130            ..Default::default()
7131        };
7132        let instr = make_instr("imul", vec![Register(Al), Memory(Box::new(mem))]);
7133        assert!(encode_instruction(&instr, Arch::X86_64).is_err());
7134    }
7135
7136    #[test]
7137    fn test_imul_3op_rejects_8bit() {
7138        let instr = make_instr("imul", vec![Register(Al), Register(Bl), Immediate(5)]);
7139        assert!(encode_instruction(&instr, Arch::X86_64).is_err());
7140    }
7141
7142    #[test]
7143    fn test_imul_3op_mem_rejects_8bit() {
7144        let mem = MemoryOperand {
7145            base: Some(Rcx),
7146            ..Default::default()
7147        };
7148        let instr = make_instr(
7149            "imul",
7150            vec![Register(Al), Memory(Box::new(mem)), Immediate(5)],
7151        );
7152        assert!(encode_instruction(&instr, Arch::X86_64).is_err());
7153    }
7154
7155    // === 8th Audit: CMOVcc 8-bit rejection (reg, mem path) ===
7156
7157    #[test]
7158    fn test_cmovcc_reg_mem_rejects_8bit() {
7159        let mem = MemoryOperand {
7160            base: Some(Rbx),
7161            ..Default::default()
7162        };
7163        let instr = make_instr("cmove", vec![Register(Al), Memory(Box::new(mem))]);
7164        assert!(encode_instruction(&instr, Arch::X86_64).is_err());
7165    }
7166
7167    // === 8th Audit: SETcc rejects non-8-bit registers ===
7168
7169    #[test]
7170    fn test_setcc_rejects_32bit_register() {
7171        let instr = make_instr("sete", vec![Register(Eax)]);
7172        assert!(encode_instruction(&instr, Arch::X86_64).is_err());
7173    }
7174
7175    #[test]
7176    fn test_setcc_accepts_8bit_register() {
7177        // sete al → 0F 94 C0
7178        assert_eq!(encode("sete", vec![Register(Al)]), vec![0x0F, 0x94, 0xC0]);
7179    }
7180
7181    // === 8th Audit: movzx/movsx mem.size fallback ===
7182
7183    #[test]
7184    fn test_movzx_mem_word_source_via_mem_size() {
7185        // movzx eax, word ptr [rbx] via mem.size (builder API path)
7186        let mem = MemoryOperand {
7187            base: Some(Rbx),
7188            size: Some(OperandSize::Word),
7189            ..Default::default()
7190        };
7191        // No size_hint on instruction — should use mem.size
7192        let instr = make_instr("movzx", vec![Register(Eax), Memory(Box::new(mem))]);
7193        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
7194        // movzx eax, word [rbx] → 0F B7 03  (B7 = word source, not B6 = byte)
7195        assert_eq!(result.bytes, vec![0x0F, 0xB7, 0x03]);
7196    }
7197
7198    #[test]
7199    fn test_movsx_mem_word_source_via_mem_size() {
7200        // movsx eax, word ptr [rbx] via mem.size (builder API path)
7201        let mem = MemoryOperand {
7202            base: Some(Rbx),
7203            size: Some(OperandSize::Word),
7204            ..Default::default()
7205        };
7206        let instr = make_instr("movsx", vec![Register(Eax), Memory(Box::new(mem))]);
7207        let result = encode_instruction(&instr, Arch::X86_64).unwrap();
7208        // movsx eax, word [rbx] → 0F BF 03  (BF = word source, not BE = byte)
7209        assert_eq!(result.bytes, vec![0x0F, 0xBF, 0x03]);
7210    }
7211
7212    // ─── 16-bit mode (encode_instruction_16) tests ───────────────
7213
7214    #[test]
7215    fn test_16bit_mov_ax_imm16() {
7216        // In 16-bit mode, mov ax, 0x1234 → B8 34 12 (no 0x66 prefix)
7217        // The 32-bit encoder produces 66 B8 34 12, toggle removes 0x66.
7218        let instr = make_instr("mov", vec![Register(Ax), Immediate(0x1234)]);
7219        let result = encode_instruction_16(&instr).unwrap();
7220        assert_eq!(result.bytes, vec![0xB8, 0x34, 0x12]);
7221    }
7222
7223    #[test]
7224    fn test_16bit_mov_eax_imm32() {
7225        // In 16-bit mode, mov eax, 0x12345678 → 66 B8 78 56 34 12
7226        // The 32-bit encoder produces B8 78 56 34 12, toggle adds 0x66.
7227        let instr = make_instr("mov", vec![Register(Eax), Immediate(0x1234_5678)]);
7228        let result = encode_instruction_16(&instr).unwrap();
7229        assert_eq!(result.bytes, vec![0x66, 0xB8, 0x78, 0x56, 0x34, 0x12]);
7230    }
7231
7232    #[test]
7233    fn test_16bit_xor_ax_ax() {
7234        // xor ax, ax → 31 C0 (no prefix in 16-bit mode)
7235        let instr = make_instr("xor", vec![Register(Ax), Register(Ax)]);
7236        let result = encode_instruction_16(&instr).unwrap();
7237        assert_eq!(result.bytes, vec![0x31, 0xC0]);
7238    }
7239
7240    #[test]
7241    fn test_16bit_xor_eax_eax() {
7242        // xor eax, eax → 66 31 C0 (prefix needed for 32-bit in 16-bit mode)
7243        let instr = make_instr("xor", vec![Register(Eax), Register(Eax)]);
7244        let result = encode_instruction_16(&instr).unwrap();
7245        assert_eq!(result.bytes, vec![0x66, 0x31, 0xC0]);
7246    }
7247
7248    #[test]
7249    fn test_16bit_push_ax() {
7250        // push ax → 50 (no prefix in 16-bit mode)
7251        let instr = make_instr("push", vec![Register(Ax)]);
7252        let result = encode_instruction_16(&instr).unwrap();
7253        assert_eq!(result.bytes, vec![0x50]);
7254    }
7255
7256    #[test]
7257    fn test_16bit_push_eax() {
7258        // push eax → 66 50 (prefix needed for 32-bit in 16-bit mode)
7259        let instr = make_instr("push", vec![Register(Eax)]);
7260        let result = encode_instruction_16(&instr).unwrap();
7261        assert_eq!(result.bytes, vec![0x66, 0x50]);
7262    }
7263
7264    #[test]
7265    fn test_16bit_pop_bx() {
7266        // pop bx → 5B (no prefix in 16-bit mode)
7267        let instr = make_instr("pop", vec![Register(Bx)]);
7268        let result = encode_instruction_16(&instr).unwrap();
7269        assert_eq!(result.bytes, vec![0x5B]);
7270    }
7271
7272    #[test]
7273    fn test_16bit_pop_ebx() {
7274        // pop ebx → 66 5B (prefix needed for 32-bit)
7275        let instr = make_instr("pop", vec![Register(Ebx)]);
7276        let result = encode_instruction_16(&instr).unwrap();
7277        assert_eq!(result.bytes, vec![0x66, 0x5B]);
7278    }
7279
7280    #[test]
7281    fn test_16bit_inc_cx() {
7282        // inc cx → 41 (short form, no prefix)
7283        let instr = make_instr("inc", vec![Register(Cx)]);
7284        let result = encode_instruction_16(&instr).unwrap();
7285        assert_eq!(result.bytes, vec![0x41]);
7286    }
7287
7288    #[test]
7289    fn test_16bit_inc_ecx() {
7290        // inc ecx → 66 41 (prefix needed for 32-bit)
7291        let instr = make_instr("inc", vec![Register(Ecx)]);
7292        let result = encode_instruction_16(&instr).unwrap();
7293        assert_eq!(result.bytes, vec![0x66, 0x41]);
7294    }
7295
7296    #[test]
7297    fn test_16bit_dec_dx() {
7298        // dec dx → 4A (short form, no prefix)
7299        let instr = make_instr("dec", vec![Register(Dx)]);
7300        let result = encode_instruction_16(&instr).unwrap();
7301        assert_eq!(result.bytes, vec![0x4A]);
7302    }
7303
7304    #[test]
7305    fn test_16bit_nop() {
7306        // nop → 90 (identical in all modes)
7307        let instr = make_instr("nop", vec![]);
7308        let result = encode_instruction_16(&instr).unwrap();
7309        assert_eq!(result.bytes, vec![0x90]);
7310    }
7311
7312    #[test]
7313    fn test_16bit_cli() {
7314        // cli → FA (identical in all modes)
7315        let instr = make_instr("cli", vec![]);
7316        let result = encode_instruction_16(&instr).unwrap();
7317        assert_eq!(result.bytes, vec![0xFA]);
7318    }
7319
7320    #[test]
7321    fn test_16bit_int_10h() {
7322        // int 0x10 → CD 10 (identical in all modes)
7323        let instr = make_instr("int", vec![Immediate(0x10)]);
7324        let result = encode_instruction_16(&instr).unwrap();
7325        assert_eq!(result.bytes, vec![0xCD, 0x10]);
7326    }
7327
7328    #[test]
7329    fn test_16bit_push_es() {
7330        // push es → 06 (segment push valid in 16-bit mode, no prefix toggle)
7331        let instr = make_instr("push", vec![Register(Es)]);
7332        let result = encode_instruction_16(&instr).unwrap();
7333        assert_eq!(result.bytes, vec![0x06]);
7334    }
7335
7336    #[test]
7337    fn test_16bit_push_cs() {
7338        // push cs → 0E
7339        let instr = make_instr("push", vec![Register(Cs)]);
7340        let result = encode_instruction_16(&instr).unwrap();
7341        assert_eq!(result.bytes, vec![0x0E]);
7342    }
7343
7344    #[test]
7345    fn test_16bit_pop_ds() {
7346        // pop ds → 1F
7347        let instr = make_instr("pop", vec![Register(Ds)]);
7348        let result = encode_instruction_16(&instr).unwrap();
7349        assert_eq!(result.bytes, vec![0x1F]);
7350    }
7351
7352    #[test]
7353    fn test_16bit_push_imm8() {
7354        // push 0x42 → 6A 42 (imm8 encoding, same in all modes)
7355        let instr = make_instr("push", vec![Immediate(0x42)]);
7356        let result = encode_instruction_16(&instr).unwrap();
7357        assert_eq!(result.bytes, vec![0x6A, 0x42]);
7358    }
7359
7360    #[test]
7361    fn test_16bit_add_ax_bx() {
7362        // add ax, bx → 01 D8 (no prefix in 16-bit mode)
7363        let instr = make_instr("add", vec![Register(Ax), Register(Bx)]);
7364        let result = encode_instruction_16(&instr).unwrap();
7365        assert_eq!(result.bytes, vec![0x01, 0xD8]);
7366    }
7367
7368    #[test]
7369    fn test_16bit_mov_al_imm8() {
7370        // mov al, 0x42 → B0 42 (8-bit, no operand-size prefix in any mode)
7371        let instr = make_instr("mov", vec![Register(Al), Immediate(0x42)]);
7372        let result = encode_instruction_16(&instr).unwrap();
7373        assert_eq!(result.bytes, vec![0xB0, 0x42]);
7374    }
7375
7376    #[test]
7377    fn test_16bit_rejects_64bit_register() {
7378        // 64-bit registers are invalid in 16-bit mode
7379        let instr = make_instr("mov", vec![Register(Rax), Immediate(1)]);
7380        assert!(encode_instruction_16(&instr).is_err());
7381    }
7382
7383    // ===================================================================
7384    // AVX-512 (EVEX-encoded) instruction tests
7385    // ===================================================================
7386
7387    // ── EVEX prefix helper unit tests ────────────────────────────────
7388
7389    #[test]
7390    fn test_emit_evex_basic_prefix() {
7391        // Test the raw EVEX prefix bytes for known inputs
7392        let mut buf = InstrBytes::new();
7393        // Simulate VADDPS zmm0, zmm1, zmm2:
7394        // R=false(zmm0 not ext), X=false, B=false(zmm2 not ext),
7395        // R'=false(zmm0 bit4=0), mm=1, W=false, vvvv=1(zmm1), V'=false, pp=0,
7396        // z=false, ll=2(512), b=false, aaa=0
7397        emit_evex(
7398            &mut buf, false, false, false, false, 1, false, 1, false, 0, false, 2, false, 0,
7399        );
7400        assert_eq!(buf[0], 0x62, "EVEX escape byte");
7401        // P0: ~R=1 ~X=1 ~B=1 ~R'=1 0 0 01 = 0xF1
7402        assert_eq!(buf[1], 0xF1, "P0");
7403        // P1: W=0 ~vvvv=~0001=1110 1 pp=00 = 0x74
7404        assert_eq!(buf[2], 0x74, "P1");
7405        // P2: z=0 L'L=10 b=0 ~V'=1 aaa=000 = 0x48
7406        assert_eq!(buf[3], 0x48, "P2");
7407    }
7408
7409    #[test]
7410    fn test_emit_evex_w_bit() {
7411        let mut buf = InstrBytes::new();
7412        // W=1, everything else zero-ish, mm=1
7413        emit_evex(
7414            &mut buf, false, false, false, false, 1, true, 0, false, 0, false, 2, false, 0,
7415        );
7416        // P1 should have W=1: 0x80 | ~0000<<3=0x78 | 0x04 | pp=0 = 0xFC
7417        assert_eq!(buf[2], 0xFC, "P1 with W=1");
7418    }
7419
7420    #[test]
7421    fn test_emit_evex_extended_reg() {
7422        let mut buf = InstrBytes::new();
7423        // R=true (extended), R'=true (evex extended) for zmm28 etc.
7424        emit_evex(
7425            &mut buf, true, false, false, true, 1, false, 0, false, 0, false, 2, false, 0,
7426        );
7427        // P0: ~R=0 ~X=1 ~B=1 ~R'=0 00 01 = 0x61
7428        assert_eq!(buf[1], 0x61, "P0 with R,R' extended");
7429    }
7430
7431    // ── EVEX evex_ll tests ──────────────────────────────────────────
7432
7433    #[test]
7434    fn test_evex_ll_zmm() {
7435        assert_eq!(evex_ll(Zmm0), 2);
7436        assert_eq!(evex_ll(Zmm31), 2);
7437    }
7438
7439    #[test]
7440    fn test_evex_ll_ymm() {
7441        assert_eq!(evex_ll(Ymm0), 1);
7442    }
7443
7444    #[test]
7445    fn test_evex_ll_xmm() {
7446        assert_eq!(evex_ll(Xmm0), 0);
7447    }
7448
7449    // ── EVEX full instruction encoding ──────────────────────────────
7450
7451    #[test]
7452    fn test_evex_vaddps_zmm0_zmm1_zmm2() {
7453        // VADDPS zmm0, zmm1, zmm2 → 62 F1 74 48 58 C2
7454        let bytes = encode(
7455            "vaddps",
7456            vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
7457        );
7458        assert_eq!(bytes, vec![0x62, 0xF1, 0x74, 0x48, 0x58, 0xC2]);
7459    }
7460
7461    #[test]
7462    fn test_evex_vaddpd_zmm0_zmm1_zmm2() {
7463        // VADDPD zmm0, zmm1, zmm2 → 62 F1 F5 48 58 C2
7464        // W=1 → P1 = 0x80 | 0x74 = 0xF4, but vvvv=1 so ~vvvv=1110,
7465        // P1 = 0x80 | (0x0E << 3) | 0x04 | 0x01 = 0x80|0x70|0x04|0x01 = 0xF5
7466        let bytes = encode(
7467            "vaddpd",
7468            vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
7469        );
7470        assert_eq!(bytes, vec![0x62, 0xF1, 0xF5, 0x48, 0x58, 0xC2]);
7471    }
7472
7473    #[test]
7474    fn test_evex_vsubps_zmm3_zmm4_zmm5() {
7475        // VSUBPS zmm3, zmm4, zmm5
7476        // dst=zmm3(code=3), src1=zmm4(code=4), src2=zmm5(code=5)
7477        // P0: all not ext → 0xF1
7478        // P1: W=0, vvvv=4 → ~4=~0100=1011 → 01011, 1, 00 → 0x5C...
7479        // ~vvvv = 0x0B, P1 = (0x0B << 3) | 0x04 | 0x00 = 0x58|0x04 = 0x5C
7480        // P2: z=0, L'L=10, b=0, ~V'=1, aaa=0 → 0x48
7481        // opcode=0x5C, ModRM: 0xC0|(3<<3)|5 = 0xDD
7482        let bytes = encode(
7483            "vsubps",
7484            vec![Register(Zmm3), Register(Zmm4), Register(Zmm5)],
7485        );
7486        assert_eq!(bytes, vec![0x62, 0xF1, 0x5C, 0x48, 0x5C, 0xDD]);
7487    }
7488
7489    #[test]
7490    fn test_evex_vmulps_zmm0_zmm1_zmm2() {
7491        let bytes = encode(
7492            "vmulps",
7493            vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
7494        );
7495        assert_eq!(bytes, vec![0x62, 0xF1, 0x74, 0x48, 0x59, 0xC2]);
7496    }
7497
7498    #[test]
7499    fn test_evex_vdivps_zmm0_zmm1_zmm2() {
7500        let bytes = encode(
7501            "vdivps",
7502            vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
7503        );
7504        assert_eq!(bytes, vec![0x62, 0xF1, 0x74, 0x48, 0x5E, 0xC2]);
7505    }
7506
7507    #[test]
7508    fn test_evex_vmovaps_zmm0_zmm1() {
7509        // VMOVAPS zmm0, zmm1 (2-op, vvvv=0)
7510        // P1: W=0, vvvv=0 → ~0=1111, P1 = (0x0F<<3)|0x04|0x00 = 0x78|0x04 = 0x7C
7511        // P2: z=0, L'L=10, b=0, ~V'=1, aaa=0 → 0x48
7512        // opcode=0x28, ModRM: 0xC0|(0<<3)|1 = 0xC1
7513        let bytes = encode("vmovaps", vec![Register(Zmm0), Register(Zmm1)]);
7514        assert_eq!(bytes, vec![0x62, 0xF1, 0x7C, 0x48, 0x28, 0xC1]);
7515    }
7516
7517    #[test]
7518    fn test_evex_vmovdqa32_zmm0_zmm1() {
7519        // VMOVDQA32 zmm0, zmm1 → pp=0x66(→1), W=0
7520        // P1: W=0, vvvv=0, pp=0x01 → 0x7C|0x01 = 0x7D
7521        let bytes = encode("vmovdqa32", vec![Register(Zmm0), Register(Zmm1)]);
7522        assert_eq!(bytes, vec![0x62, 0xF1, 0x7D, 0x48, 0x6F, 0xC1]);
7523    }
7524
7525    #[test]
7526    fn test_evex_vmovdqa64_zmm0_zmm1() {
7527        // VMOVDQA64 zmm0, zmm1 → pp=0x66(→1), W=1
7528        // P1: W=1, vvvv=0, pp=0x01 → 0xFC|0x01 = 0xFD
7529        let bytes = encode("vmovdqa64", vec![Register(Zmm0), Register(Zmm1)]);
7530        assert_eq!(bytes, vec![0x62, 0xF1, 0xFD, 0x48, 0x6F, 0xC1]);
7531    }
7532
7533    #[test]
7534    fn test_evex_vpternlogd_zmm0_zmm1_zmm2_imm() {
7535        // VPTERNLOGD zmm0, zmm1, zmm2, 0xFF → EVEX map3, opcode=0x25, W=0
7536        // P0: mm=3 → 0xF3
7537        // P1: W=0, vvvv=1 → 0x74|0x01 = 0x71... wait pp=0x66→1
7538        // P1: (0x0E<<3)|0x04|0x01 = 0x70|0x04|0x01 = 0x75
7539        let bytes = encode(
7540            "vpternlogd",
7541            vec![
7542                Register(Zmm0),
7543                Register(Zmm1),
7544                Register(Zmm2),
7545                Immediate(0xFF),
7546            ],
7547        );
7548        assert_eq!(bytes, vec![0x62, 0xF3, 0x75, 0x48, 0x25, 0xC2, 0xFF]);
7549    }
7550
7551    #[test]
7552    fn test_evex_vpternlogq_zmm0_zmm1_zmm2_imm() {
7553        // VPTERNLOGQ zmm0, zmm1, zmm2, 0xDB → W=1
7554        let bytes = encode(
7555            "vpternlogq",
7556            vec![
7557                Register(Zmm0),
7558                Register(Zmm1),
7559                Register(Zmm2),
7560                Immediate(0xDB),
7561            ],
7562        );
7563        assert_eq!(bytes, vec![0x62, 0xF3, 0xF5, 0x48, 0x25, 0xC2, 0xDB]);
7564    }
7565
7566    #[test]
7567    fn test_evex_vpaddd_zmm0_zmm1_zmm2() {
7568        // VPADDD zmm0, zmm1, zmm2 → map1, pp=66, W=0, opcode=0xFE
7569        let bytes = encode(
7570            "vpaddd",
7571            vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
7572        );
7573        assert_eq!(bytes, vec![0x62, 0xF1, 0x75, 0x48, 0xFE, 0xC2]);
7574    }
7575
7576    #[test]
7577    fn test_evex_vpaddq_zmm0_zmm1_zmm2() {
7578        // VPADDQ zmm0, zmm1, zmm2 → map1, pp=66, W=1, opcode=0xD4
7579        let bytes = encode(
7580            "vpaddq",
7581            vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
7582        );
7583        assert_eq!(bytes, vec![0x62, 0xF1, 0xF5, 0x48, 0xD4, 0xC2]);
7584    }
7585
7586    #[test]
7587    fn test_evex_vpxord_zmm0_zmm1_zmm2() {
7588        let bytes = encode(
7589            "vpxord",
7590            vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
7591        );
7592        assert_eq!(bytes, vec![0x62, 0xF1, 0x75, 0x48, 0xEF, 0xC2]);
7593    }
7594
7595    #[test]
7596    fn test_evex_vpxorq_zmm0_zmm1_zmm2() {
7597        let bytes = encode(
7598            "vpxorq",
7599            vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
7600        );
7601        assert_eq!(bytes, vec![0x62, 0xF1, 0xF5, 0x48, 0xEF, 0xC2]);
7602    }
7603
7604    #[test]
7605    fn test_evex_vblendmps_zmm0_zmm1_zmm2() {
7606        // VBLENDMPS zmm0, zmm1, zmm2 → map2(0F38), pp=66, W=0, opcode=0x65
7607        let bytes = encode(
7608            "vblendmps",
7609            vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
7610        );
7611        assert_eq!(bytes, vec![0x62, 0xF2, 0x75, 0x48, 0x65, 0xC2]);
7612    }
7613
7614    #[test]
7615    fn test_evex_vpmullq_zmm0_zmm1_zmm2() {
7616        // VPMULLQ zmm0, zmm1, zmm2 → map2(0F38), pp=66, W=1, opcode=0x40
7617        let bytes = encode(
7618            "vpmullq",
7619            vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
7620        );
7621        assert_eq!(bytes, vec![0x62, 0xF2, 0xF5, 0x48, 0x40, 0xC2]);
7622    }
7623
7624    // ── Extended registers (ZMM16+) ─────────────────────────────────
7625
7626    #[test]
7627    fn test_evex_vaddps_zmm16_zmm17_zmm18() {
7628        // ZMM16: index=16, base_code=0, is_ext=false(bit3=0), is_evex_ext=true(bit4=1)
7629        // ZMM17: index=17, base_code=1, is_ext=false(bit3=0), is_evex_ext=true(bit4=1)
7630        // ZMM18: index=18, base_code=2, is_ext=false(bit3=0), is_evex_ext=true(bit4=1)
7631        // dst=zmm16: R=ext=false, R'=evex=true
7632        // src1=zmm17: vvvv=1, V'=evex=true
7633        // src2=zmm18: B=ext=false, X=evex=true
7634        // P0: ~R=1(0x80), ~X=0, ~B=1(0x20), ~R'=0, mm=01 → 0xA1
7635        // P1: W=0, vvvv=1 → ~1=1110 → (0xE<<3)|0x04|0x00 = 0x74
7636        // P2: z=0, L'L=10, b=0, ~V'=0, aaa=0 → 0x40
7637        let bytes = encode(
7638            "vaddps",
7639            vec![Register(Zmm16), Register(Zmm17), Register(Zmm18)],
7640        );
7641        assert_eq!(bytes, vec![0x62, 0xA1, 0x74, 0x40, 0x58, 0xC2]);
7642    }
7643
7644    #[test]
7645    fn test_evex_vmovaps_zmm31_zmm16() {
7646        // ZMM31: base_code=7, is_ext=true, is_evex_ext=true
7647        // ZMM16: base_code=0, is_ext=false, is_evex_ext=true
7648        // 2-op form: vvvv=0, V'=false
7649        // dst=zmm31: R=ext=true, R'=evex=true
7650        // src=zmm16: B=ext=false, X=evex=true
7651        // P0: ~R=0, ~X=0, ~B=1(0x20), ~R'=0, mm=01 → 0x21
7652        // P1: W=0, vvvv=0, pp=0 → (0x0F<<3)|0x04 = 0x7C
7653        // P2: z=0, L'L=10, b=0, ~V'=1, aaa=0 → 0x48
7654        // ModRM: 0xC0|(7<<3)|0 = 0xF8
7655        let bytes = encode("vmovaps", vec![Register(Zmm31), Register(Zmm16)]);
7656        assert_eq!(bytes, vec![0x62, 0x21, 0x7C, 0x48, 0x28, 0xF8]);
7657    }
7658
7659    // ── EVEX-only instructions (no VEX equivalent) ──────────────────
7660
7661    #[test]
7662    fn test_evex_vmovdqu8_zmm0_zmm1() {
7663        // VMOVDQU8 zmm0, zmm1 → pp=F2(→3), W=0, map1, opcode=0x6F
7664        // P1: (0x0F<<3)|0x04|0x03 = 0x7C|0x03 = 0x7F
7665        let bytes = encode("vmovdqu8", vec![Register(Zmm0), Register(Zmm1)]);
7666        assert_eq!(bytes, vec![0x62, 0xF1, 0x7F, 0x48, 0x6F, 0xC1]);
7667    }
7668
7669    #[test]
7670    fn test_evex_vmovdqu16_zmm0_zmm1() {
7671        // VMOVDQU16 zmm0, zmm1 → pp=F2(→3), W=1
7672        // P1: 0x80|(0x0F<<3)|0x04|0x03 = 0xFC|0x03 = 0xFF
7673        let bytes = encode("vmovdqu16", vec![Register(Zmm0), Register(Zmm1)]);
7674        assert_eq!(bytes, vec![0x62, 0xF1, 0xFF, 0x48, 0x6F, 0xC1]);
7675    }
7676
7677    #[test]
7678    fn test_evex_vpsravq_zmm0_zmm1_zmm2() {
7679        // VPSRAVQ zmm0, zmm1, zmm2 → map2, pp=66, W=1, opcode=0x46
7680        let bytes = encode(
7681            "vpsravq",
7682            vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
7683        );
7684        assert_eq!(bytes, vec![0x62, 0xF2, 0xF5, 0x48, 0x46, 0xC2]);
7685    }
7686
7687    #[test]
7688    fn test_evex_vpandd_zmm0_zmm1_zmm2() {
7689        let bytes = encode(
7690            "vpandd",
7691            vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
7692        );
7693        assert_eq!(bytes, vec![0x62, 0xF1, 0x75, 0x48, 0xDB, 0xC2]);
7694    }
7695
7696    #[test]
7697    fn test_evex_vpord_zmm0_zmm1_zmm2() {
7698        let bytes = encode(
7699            "vpord",
7700            vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
7701        );
7702        assert_eq!(bytes, vec![0x62, 0xF1, 0x75, 0x48, 0xEB, 0xC2]);
7703    }
7704
7705    // ── VEX instructions still work (regression) ────────────────────
7706
7707    #[test]
7708    fn test_vex_vaddps_xmm_still_works() {
7709        // VADDPS xmm0, xmm1, xmm2 should use VEX, not EVEX
7710        let bytes = encode(
7711            "vaddps",
7712            vec![Register(Xmm0), Register(Xmm1), Register(Xmm2)],
7713        );
7714        // VEX.128.0F 58 → C5 F0 58 C2 (2-byte VEX)
7715        assert_eq!(bytes[0], 0xC5, "should be 2-byte VEX prefix");
7716    }
7717
7718    #[test]
7719    fn test_vex_vaddps_ymm_still_works() {
7720        // VADDPS ymm0, ymm1, ymm2 should use VEX, not EVEX
7721        let bytes = encode(
7722            "vaddps",
7723            vec![Register(Ymm0), Register(Ymm1), Register(Ymm2)],
7724        );
7725        // VEX.256.0F 58 → C5 F4 58 C2
7726        assert_eq!(bytes[0], 0xC5, "should be 2-byte VEX prefix for ymm");
7727    }
7728
7729    // ── Compress/expand (EVEX-only) ─────────────────────────────────
7730
7731    #[test]
7732    fn test_evex_vcompressps_zmm0_zmm1() {
7733        // VCOMPRESSPS zmm0, zmm1 → map2, pp=66, W=0, opcode=0x8A
7734        let bytes = encode("vcompressps", vec![Register(Zmm0), Register(Zmm1)]);
7735        assert_eq!(bytes, vec![0x62, 0xF2, 0x7D, 0x48, 0x8A, 0xC1]);
7736    }
7737
7738    #[test]
7739    fn test_evex_vexpandps_zmm0_zmm1() {
7740        // VEXPANDPS zmm0, zmm1 → map2, pp=66, W=0, opcode=0x88
7741        let bytes = encode("vexpandps", vec![Register(Zmm0), Register(Zmm1)]);
7742        assert_eq!(bytes, vec![0x62, 0xF2, 0x7D, 0x48, 0x88, 0xC1]);
7743    }
7744
7745    // ── EVEX shuffle / pshufd with imm ──────────────────────────────
7746
7747    #[test]
7748    fn test_evex_vpshufd_zmm0_zmm1_imm() {
7749        // VPSHUFD zmm0, zmm1, 0xE4 → map1, pp=66, W=0, opcode=0x70
7750        // 3-op imm form: dst=zmm0, src=zmm1, imm=0xE4
7751        let bytes = encode(
7752            "vpshufd",
7753            vec![Register(Zmm0), Register(Zmm1), Immediate(0xE4)],
7754        );
7755        assert_eq!(bytes, vec![0x62, 0xF1, 0x7D, 0x48, 0x70, 0xC1, 0xE4]);
7756    }
7757}