Skip to main content

asmkit/x86/
assembler.rs

1#![allow(dead_code)]
2use super::emit::{self, PendingPrefixes};
3use super::emitter::{CallEmitter, JmpEmitter, MovEmitter};
4use super::instdb::InstId;
5use super::operands::*;
6use crate::{
7    X86Error,
8    core::{
9        arch_traits::Arch,
10        buffer::{CodeBuffer, CodeOffset, ConstantData, LabelUse},
11        globals::InstOptions,
12        operand::*,
13        patch::{PatchableBlock, PatchableSite},
14        target::Environment,
15    },
16};
17
18/// X86/X64 Assembler implementation.
19pub struct Assembler<'a> {
20    pub(crate) buffer: &'a mut CodeBuffer,
21    flags: u64,
22    extra_reg: Reg,
23}
24
25const RC_RN: u64 = 0x0000000;
26const RC_RD: u64 = 0x0800000;
27const RC_RU: u64 = 0x1000000;
28const RC_RZ: u64 = 0x1800000;
29const RC_MASK: u64 = RC_RD | RC_RU;
30const RC_ENABLED: u64 = 0x4000000;
31const SEG_MASK: u64 = 0xe0000000;
32const LONG: u64 = 0x100000000;
33
34/// Bit 37: LOCK prefix (bits 23/24 are the rounding-mode RC bits, bits 20/21
35/// REP/REPNE, bit 26 SAE/ER enable, bits 29..=31 segment, bit 32 long-form,
36/// bits 33..=35 mask id, bit 36 zeroing mask).
37const OPC_LOCK: u64 = 0x2000000000;
38/// Bit 36: AVX-512 zeroing mask `{z}` (bits 33..=35 carry the mask register id).
39const OPC_Z: u64 = 0x1000000000;
40
41impl crate::core::builder::InstSink for Assembler<'_> {
42    fn arch(&self) -> Arch {
43        self.environment().arch()
44    }
45
46    fn emit_inst(&mut self, inst: &crate::core::inst::Inst) -> Result<(), crate::AsmError> {
47        let ops = inst.operands();
48        let mut refs: smallvec::SmallVec<[&Operand; 6]> = smallvec::SmallVec::new();
49        refs.extend(ops.iter());
50
51        let extra_reg = inst.extra_reg();
52        let mask_id = match extra_reg.signature.try_op_type() {
53            Some(OperandType::None) if extra_reg.signature.bits() == 0 => 0,
54            Some(OperandType::Reg) if extra_reg.signature.try_reg_type() == Some(RegType::Mask) => {
55                extra_reg.id()
56            }
57            _ => {
58                return Err(X86Error::InvalidMasking {
59                    mask_reg: extra_reg.id(),
60                    reason: "x86 extra register must be a mask register",
61                }
62                .into());
63            }
64        };
65        self.try_emit_n_with_prefixes(
66            inst.id(),
67            &refs,
68            PendingPrefixes {
69                options: inst.options(),
70                segment_id: 0,
71                mask_id,
72            },
73        )
74    }
75
76    fn bind_label(&mut self, label: Label) -> Result<(), crate::AsmError> {
77        self.try_bind_label(label)
78    }
79}
80
81impl<'a> Assembler<'a> {
82    /// Collects the pending prefix flags set by the prefix setters (`rep`, `lock`,
83    /// `seg`, `k`, sae/rounding, `long`) and resets them, mapping them onto the
84    /// asmjit-style [`InstOptions`]/extra-reg model consumed by [`Assembler::emit_n`].
85    fn take_pending_prefixes(&mut self) -> PendingPrefixes {
86        let flags = self.flags;
87        self.flags = 0;
88
89        let mut prefixes = PendingPrefixes::default();
90        if flags & OPC_LOCK != 0 {
91            prefixes.options |= InstOptions::X86_LOCK;
92        }
93        if flags & 0x200000 != 0 {
94            prefixes.options |= InstOptions::X86_REP;
95        }
96        if flags & 0x100000 != 0 {
97            prefixes.options |= InstOptions::X86_REPNE;
98        }
99        if flags & LONG != 0 {
100            prefixes.options |= InstOptions::LONG_FORM;
101        }
102        if flags & OPC_Z != 0 {
103            prefixes.options |= InstOptions::X86_ZMASK;
104        }
105        if flags & RC_ENABLED != 0 {
106            let rc = flags & (RC_RD | RC_RU);
107            prefixes.options |= if rc == RC_RD {
108                InstOptions::X86_ER | InstOptions::X86_RD_SAE
109            } else if rc == RC_RU {
110                InstOptions::X86_ER | InstOptions::X86_RU_SAE
111            } else if rc == RC_RZ {
112                InstOptions::X86_ER | InstOptions::X86_RZ_SAE
113            } else {
114                // `sae()` and `rn_sae()` share the same flag bits (RC_RN is zero);
115                // both encode identically (EVEX.b with LL=00).
116                InstOptions::X86_SAE
117            };
118        }
119        prefixes.segment_id = ((flags & SEG_MASK) >> 29) as u32;
120        prefixes.mask_id = ((flags >> 33) & 0x7) as u32;
121        prefixes
122    }
123
124    /// Emits one instruction by id with explicit operands, using the asmjit-style
125    /// InstInfo → signature match → emit-handler pipeline (the new primary emit
126    /// path). Pending prefixes set by the prefix setters apply.
127    pub fn emit_n(&mut self, id: impl Into<u32>, ops: &[&Operand]) {
128        if let Err(error) = self.try_emit_n(id, ops) {
129            self.buffer.record_error(error);
130        }
131    }
132
133    pub fn try_emit_n(
134        &mut self,
135        id: impl Into<u32>,
136        ops: &[&Operand],
137    ) -> Result<(), crate::AsmError> {
138        if let Some(error) = self.buffer.error().cloned() {
139            return Err(error);
140        }
141        let prefixes = self.take_pending_prefixes();
142        self.try_emit_n_with_prefixes(id, ops, prefixes)
143    }
144
145    fn try_emit_n_with_prefixes(
146        &mut self,
147        id: impl Into<u32>,
148        ops: &[&Operand],
149        prefixes: PendingPrefixes,
150    ) -> Result<(), crate::AsmError> {
151        if let Some(error) = self.buffer.error().cloned() {
152            return Err(error);
153        }
154        let checkpoint = self.buffer.checkpoint();
155        if let Err(error) = emit::emit_n(self.buffer, id.into(), ops, prefixes, self.is_32bit()) {
156            self.buffer.rollback(checkpoint);
157            return Err(error);
158        }
159        Ok(())
160    }
161
162    pub fn new(buf: &'a mut CodeBuffer) -> Self {
163        if !matches!(buf.env().arch(), Arch::X86 | Arch::X64) {
164            return Self::poisoned(buf, crate::AsmError::InvalidArch);
165        }
166        Self::unchecked(buf)
167    }
168
169    pub fn try_new(buf: &'a mut CodeBuffer) -> Result<Self, crate::AsmError> {
170        if !matches!(buf.env().arch(), Arch::X86 | Arch::X64) {
171            return Err(crate::AsmError::InvalidArch);
172        }
173        Ok(Self::unchecked(buf))
174    }
175
176    fn unchecked(buf: &'a mut CodeBuffer) -> Self {
177        Self {
178            buffer: buf,
179            extra_reg: Reg::new(),
180            flags: 0,
181        }
182    }
183
184    fn poisoned(buf: &'a mut CodeBuffer, error: crate::AsmError) -> Self {
185        buf.record_error(error);
186        Self {
187            buffer: buf,
188            extra_reg: Reg::new(),
189            flags: 0,
190        }
191    }
192
193    /// Returns the environment (arch/mode) this assembler targets.
194    pub fn environment(&self) -> &Environment {
195        self.buffer.env()
196    }
197
198    /// Tests whether the assembler targets 32-bit X86 mode.
199    pub fn is_32bit(&self) -> bool {
200        self.buffer.env().is_32bit()
201    }
202
203    /// Tests whether the assembler targets 64-bit X64 mode.
204    pub fn is_64bit(&self) -> bool {
205        self.buffer.env().is_64bit()
206    }
207
208    #[cfg(test)]
209    fn last_error(&self) -> Option<X86Error> {
210        match self.buffer.error() {
211            Some(crate::AsmError::X86(error)) => Some(error.clone()),
212            _ => None,
213        }
214    }
215
216    pub fn sae(&mut self) -> &mut Self {
217        self.set_rounding(RC_RN)
218    }
219
220    pub fn rn_sae(&mut self) -> &mut Self {
221        self.set_rounding(RC_RN)
222    }
223
224    pub fn rd_sae(&mut self) -> &mut Self {
225        self.set_rounding(RC_RD)
226    }
227    pub fn ru_sae(&mut self) -> &mut Self {
228        self.set_rounding(RC_RU)
229    }
230
231    pub fn rz_sae(&mut self) -> &mut Self {
232        self.set_rounding(RC_RZ)
233    }
234
235    fn set_rounding(&mut self, rounding: u64) -> &mut Self {
236        let mask = RC_ENABLED | RC_MASK;
237        let pending = self.flags & mask;
238        let requested = RC_ENABLED | rounding;
239        if pending != 0 && pending != requested {
240            self.buffer
241                .record_error(crate::AsmError::X86(X86Error::InvalidRoundingControl {
242                    rc: requested,
243                    reason: "conflicting pending rounding modes",
244                }));
245            return self;
246        }
247        self.flags = (self.flags & !mask) | requested;
248        self
249    }
250
251    pub fn seg(&mut self, sreg: SReg) -> &mut Self {
252        let segment_id = sreg.id();
253        if !(SReg::ES..=SReg::GS).contains(&segment_id) {
254            self.buffer
255                .record_error(crate::AsmError::X86(X86Error::InvalidPrefix {
256                    prefix: segment_id as u64,
257                    reason: "invalid segment override",
258                }));
259            return self;
260        }
261        let pending = (self.flags & SEG_MASK) >> 29;
262        if pending != 0 && pending != segment_id as u64 {
263            self.buffer
264                .record_error(crate::AsmError::X86(X86Error::InvalidPrefix {
265                    prefix: segment_id as u64,
266                    reason: "conflicting pending segment overrides",
267                }));
268            return self;
269        }
270        self.flags = (self.flags & !SEG_MASK) | (segment_id as u64) << 29;
271        self
272    }
273
274    pub fn fs(&mut self) -> &mut Self {
275        self.seg(FS)
276    }
277
278    pub fn gs(&mut self) -> &mut Self {
279        self.seg(GS)
280    }
281
282    pub fn k(&mut self, k: KReg) -> &mut Self {
283        let mask_id = k.id();
284        if !(1..=7).contains(&mask_id) {
285            self.buffer
286                .record_error(crate::AsmError::X86(X86Error::InvalidMasking {
287                    mask_reg: mask_id,
288                    reason: "mask register must be k1..k7",
289                }));
290            return self;
291        }
292        let pending = (self.flags >> 33) & 0x7;
293        if pending != 0 && pending != mask_id as u64 {
294            self.buffer
295                .record_error(crate::AsmError::X86(X86Error::InvalidMasking {
296                    mask_reg: mask_id,
297                    reason: "conflicting pending mask registers",
298                }));
299            return self;
300        }
301        self.flags = (self.flags & !(0x7 << 33)) | (mask_id as u64) << 33;
302
303        self
304    }
305
306    /// AVX-512 zeroing mask `{z}` for the next instruction (requires `k()`).
307    pub fn z(&mut self) -> &mut Self {
308        self.flags |= OPC_Z;
309        self
310    }
311
312    pub fn rep(&mut self) -> &mut Self {
313        self.flags |= 0x200000;
314        self
315    }
316
317    pub fn repnz(&mut self) -> &mut Self {
318        self.flags |= 0x100000;
319        self
320    }
321
322    pub fn repz(&mut self) -> &mut Self {
323        self.rep()
324    }
325
326    pub fn lock(&mut self) -> &mut Self {
327        self.flags |= OPC_LOCK;
328        self
329    }
330
331    pub fn long(&mut self) -> &mut Self {
332        self.flags |= LONG;
333        self
334    }
335
336    pub fn get_label(&mut self) -> Label {
337        self.buffer.get_label()
338    }
339
340    pub fn bind_label(&mut self, label: Label) {
341        if let Err(error) = self.try_bind_label(label) {
342            self.buffer.record_error(error);
343        }
344    }
345
346    pub fn try_bind_label(&mut self, label: Label) -> Result<(), crate::AsmError> {
347        self.buffer.try_bind_label(label)
348    }
349
350    pub fn add_constant(&mut self, c: impl Into<ConstantData>) -> Label {
351        let c = self.buffer.add_constant(c);
352        self.buffer.get_label_for_constant(c)
353    }
354
355    pub fn label_offset(&self, label: Label) -> CodeOffset {
356        self.buffer.label_offset(label)
357    }
358
359    pub fn data(&self) -> &[u8] {
360        self.buffer.data()
361    }
362
363    pub fn error(&self) -> Option<&crate::AsmError> {
364        self.buffer.error()
365    }
366
367    /// Reserve a nop-filled island for later custom rewriting.
368    pub fn reserve_patch_block(
369        &mut self,
370        size: CodeOffset,
371        align: CodeOffset,
372    ) -> Result<PatchableBlock, crate::AsmError> {
373        self.buffer.reserve_patch_block(size, align)
374    }
375
376    pub fn patchable_jmp(&mut self, label: Label) -> PatchableSite {
377        self.long();
378        self.jmp(label);
379        let offset = self
380            .buffer
381            .cur_offset()
382            .saturating_sub(LabelUse::X86JmpRel32.patch_size() as u32);
383        let _ = self
384            .buffer
385            .record_label_patch_site(offset, label, LabelUse::X86JmpRel32);
386        // SAFETY: long jmp/call emits a rel32 displacement at `offset`.
387        unsafe { PatchableSite::new(offset, LabelUse::X86JmpRel32, 0) }
388    }
389
390    pub fn patchable_call(&mut self, label: Label) -> PatchableSite {
391        self.long();
392        self.call(label);
393        let offset = self
394            .buffer
395            .cur_offset()
396            .saturating_sub(LabelUse::X86JmpRel32.patch_size() as u32);
397        let _ = self
398            .buffer
399            .record_label_patch_site(offset, label, LabelUse::X86JmpRel32);
400        // SAFETY: long jmp/call emits a rel32 displacement at `offset`.
401        unsafe { PatchableSite::new(offset, LabelUse::X86JmpRel32, 0) }
402    }
403
404    /// Patchable conditional jump (forced rel32).
405    pub fn patchable_jcc(&mut self, cc: CondCode, label: Label) -> PatchableSite {
406        const JCC: [InstId; 16] = [
407            InstId::Jo,
408            InstId::Jno,
409            InstId::Jb,
410            InstId::Jnb,
411            InstId::Jz,
412            InstId::Jnz,
413            InstId::Jbe,
414            InstId::Jnbe,
415            InstId::Js,
416            InstId::Jns,
417            InstId::Jp,
418            InstId::Jnp,
419            InstId::Jl,
420            InstId::Jnl,
421            InstId::Jle,
422            InstId::Jnle,
423        ];
424        self.long();
425        self.emit_n(JCC[cc.code() as usize] as u32, &[label.as_operand()]);
426        let offset = self
427            .buffer
428            .cur_offset()
429            .saturating_sub(LabelUse::X86JmpRel32.patch_size() as u32);
430        let _ = self
431            .buffer
432            .record_label_patch_site(offset, label, LabelUse::X86JmpRel32);
433        // SAFETY: long jcc emits a rel32 displacement at `offset`.
434        unsafe { PatchableSite::new(offset, LabelUse::X86JmpRel32, 0) }
435    }
436
437    pub fn patchable_mov<A, B>(&mut self, dst: A, src: B) -> PatchableBlock
438    where
439        A: OperandCast + Copy,
440        B: OperandCast + Copy,
441        Self: MovEmitter<A, B>,
442    {
443        let arch = self.buffer.env().arch();
444        let dst_op = *dst.as_operand();
445        let src_op = *src.as_operand();
446        let size = if dst_op.is_reg_type_of(RegType::Gp64) {
447            8
448        } else if dst_op.is_reg_type_of(RegType::Gp32) {
449            4
450        } else {
451            self.buffer
452                .record_error(crate::AsmError::X86(X86Error::InvalidOperand {
453                    operand_index: 0,
454                    reason: "patchable_mov requires a Gp32 or Gp64 destination",
455                }));
456            // SAFETY: poisoned handle; apply will fail bounds checks.
457            return unsafe { PatchableBlock::new(u32::MAX, 1, arch) };
458        };
459
460        if !src_op.is_imm() {
461            self.buffer
462                .record_error(crate::AsmError::X86(X86Error::InvalidOperand {
463                    operand_index: 1,
464                    reason: "patchable_mov requires an immediate source",
465                }));
466            // SAFETY: poisoned handle for an error path; the sentinel offset is
467            // rejected by the bounds checks when applied.
468            return unsafe { PatchableBlock::new(u32::MAX, size, arch) };
469        }
470
471        let offset = self.buffer.cur_offset();
472        let previous_error = self.buffer.error().cloned();
473        self.long();
474        MovEmitter::mov(self, dst, src);
475        if self.buffer.error().cloned() != previous_error
476            || self.buffer.cur_offset() < offset + size
477        {
478            // SAFETY: poisoned handle for an error path; the sentinel offset is
479            // rejected by the bounds checks when applied.
480            return unsafe { PatchableBlock::new(u32::MAX, size, arch) };
481        }
482
483        let offset = self.buffer.cur_offset() - size;
484        let _ = self.buffer.record_patch_block(offset, size, 1);
485        // SAFETY: long mov-imm places a `size`-byte immediate at `offset`.
486        unsafe { PatchableBlock::new(offset, size, arch) }
487    }
488}
489
490#[derive(Debug, Clone, Copy, PartialEq, Eq)]
491pub enum CondCode {
492    O = 0x0,
493    NO = 0x1,
494    C = 0x2,
495    NC = 0x3,
496    Z = 0x4,
497    NZ = 0x5,
498    BE = 0x6,
499    A = 0x7,
500    S = 0x8,
501    NS = 0x9,
502    P = 0xa,
503
504    NP = 0xb,
505    L = 0xc,
506    GE = 0xd,
507    LE = 0xe,
508    G = 0xf,
509}
510
511impl CondCode {
512    pub const B: Self = Self::C;
513    pub const NAE: Self = Self::C;
514    pub const AE: Self = Self::NC;
515    pub const NB: Self = Self::NC;
516    pub const E: Self = Self::Z;
517    pub const NE: Self = Self::NZ;
518    pub const NA: Self = Self::BE;
519    pub const NBE: Self = Self::A;
520    pub const PO: Self = Self::NP;
521    pub const NGE: Self = Self::L;
522    pub const NL: Self = Self::GE;
523    pub const NG: Self = Self::LE;
524    pub const NLE: Self = Self::G;
525    pub const PE: Self = Self::P;
526
527    pub const fn code(self) -> u8 {
528        self as u8
529    }
530
531    pub fn invert(self) -> Self {
532        match self {
533            Self::O => Self::NO,
534            Self::NO => Self::O,
535            Self::C => Self::NC,
536            Self::NC => Self::C,
537            Self::Z => Self::NZ,
538            Self::NZ => Self::Z,
539            Self::BE => Self::A,
540            Self::A => Self::BE,
541            Self::S => Self::NS,
542            Self::NS => Self::S,
543            Self::P => Self::NP,
544            Self::NP => Self::P,
545            Self::L => Self::GE,
546            Self::GE => Self::L,
547            Self::LE => Self::G,
548            Self::G => Self::LE,
549        }
550    }
551}
552
553#[cfg(test)]
554mod tests {
555    use super::*;
556    use crate::core::builder::Builder;
557    use crate::core::inst::Inst;
558    use crate::x86::instdb::InstId;
559    use crate::x86::operands::regs::*;
560
561    /// Assembles via `emit_n`, asserting no error, and finalizes label fixups.
562    fn asm(f: impl FnOnce(&mut Assembler)) -> std::vec::Vec<u8> {
563        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
564        {
565            let mut a = Assembler::new(&mut buf);
566            f(&mut a);
567            assert!(a.last_error().is_none(), "{:?}", a.last_error());
568        }
569        buf.finish().unwrap().data().to_vec()
570    }
571
572    #[test]
573    fn emit_n_integer_forms() {
574        // mov rax, 1: sign-extended C7 /0 form (AsmJit default, no long_form).
575        assert_eq!(
576            asm(|a| a.emit_n(InstId::Mov as u32, &[RAX.as_operand(), imm(1).as_operand()])),
577            [0x48, 0xC7, 0xC0, 0x01, 0x00, 0x00, 0x00]
578        );
579        // mov rax, rbx.
580        assert_eq!(
581            asm(|a| a.emit_n(InstId::Mov as u32, &[RAX.as_operand(), RBX.as_operand()])),
582            [0x48, 0x89, 0xD8]
583        );
584        // add rax, rbx.
585        assert_eq!(
586            asm(|a| a.emit_n(InstId::Add as u32, &[RAX.as_operand(), RBX.as_operand()])),
587            [0x48, 0x01, 0xD8]
588        );
589        // push rax / pop rax.
590        assert_eq!(
591            asm(|a| a.emit_n(InstId::Push as u32, &[RAX.as_operand()])),
592            [0x50]
593        );
594        assert_eq!(
595            asm(|a| a.emit_n(InstId::Pop as u32, &[RAX.as_operand()])),
596            [0x58]
597        );
598        // cmovz rax, rbx.
599        assert_eq!(
600            asm(|a| a.emit_n(InstId::Cmovz as u32, &[RAX.as_operand(), RBX.as_operand()])),
601            [0x48, 0x0F, 0x44, 0xC3]
602        );
603        // ret / syscall.
604        assert_eq!(asm(|a| a.emit_n(InstId::Ret as u32, &[])), [0xC3]);
605        assert_eq!(asm(|a| a.emit_n(InstId::Syscall as u32, &[])), [0x0F, 0x05]);
606        // mov rax, 0x123456789: 64-bit immediate uses the B8 (movabs) form.
607        assert_eq!(
608            asm(|a| a.emit_n(
609                InstId::Mov as u32,
610                &[RAX.as_operand(), imm(0x1_2345_6789i64).as_operand()]
611            )),
612            [0x48, 0xB8, 0x89, 0x67, 0x45, 0x23, 0x01, 0x00, 0x00, 0x00]
613        );
614    }
615
616    #[test]
617    fn emit_n_invalid_sets_last_error() {
618        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
619        let mut a = Assembler::new(&mut buf);
620        // add rax, xmm0: no signature matches; nothing is emitted.
621        a.emit_n(InstId::Add as u32, &[RAX.as_operand(), XMM0.as_operand()]);
622        assert!(matches!(
623            a.last_error(),
624            Some(X86Error::InvalidInstruction { .. })
625        ));
626        assert!(a.buffer.data().is_empty());
627        // Unknown instruction id.
628        a.buffer.clear();
629        a.emit_n(u32::MAX, &[]);
630        assert!(a.last_error().is_some());
631    }
632
633    #[test]
634    fn raw_invalid_symbol_id_is_rejected() {
635        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
636        let mut a = Assembler::new(&mut buf);
637        let mut operand = Operand::new();
638        operand.set_signature(OperandSignature::from(0x001A_0012));
639
640        assert_eq!(
641            a.try_emit_n(727u32, &[&operand]),
642            Err(crate::AsmError::X86(X86Error::InvalidOperand {
643                operand_index: 0,
644                reason: "symbol is not declared in this buffer",
645            }))
646        );
647        assert!(a.buffer.data().is_empty());
648
649        a.emit_n(727u32, &[&operand]);
650        assert_eq!(
651            a.buffer.error(),
652            Some(&crate::AsmError::X86(X86Error::InvalidOperand {
653                operand_index: 0,
654                reason: "symbol is not declared in this buffer",
655            }))
656        );
657        assert!(a.buffer.data().is_empty());
658    }
659
660    #[test]
661    fn emit_n_rejects_more_than_six_operands() {
662        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
663        let mut a = Assembler::new(&mut buf);
664        let ops = [RAX.as_operand(); 7];
665
666        a.emit_n(InstId::Ret as u32, &ops);
667
668        assert!(a.last_error().is_some());
669        assert!(a.buffer.data().is_empty());
670    }
671
672    #[test]
673    fn patchable_mov_emits_once_and_rejects_unsupported_operands() {
674        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
675        {
676            let mut a = Assembler::new(&mut buf);
677            a.patchable_mov(EAX, imm(42));
678            assert_eq!(a.buffer.data(), &[0xB8, 42, 0, 0, 0]);
679            assert!(a.last_error().is_none());
680
681            a.patchable_mov(RAX, imm(42));
682            assert_eq!(
683                a.buffer.data(),
684                &[0xB8, 42, 0, 0, 0, 0x48, 0xB8, 42, 0, 0, 0, 0, 0, 0, 0]
685            );
686
687            a.patchable_mov(RAX, RBX);
688            assert!(matches!(
689                a.last_error(),
690                Some(X86Error::InvalidOperand {
691                    operand_index: 1,
692                    ..
693                })
694            ));
695            assert_eq!(
696                a.buffer.data(),
697                &[0xB8, 42, 0, 0, 0, 0x48, 0xB8, 42, 0, 0, 0, 0, 0, 0, 0]
698            );
699        }
700
701        assert!(matches!(
702            buf.finish_patched(),
703            Err(crate::AsmError::X86(X86Error::InvalidOperand {
704                operand_index: 1,
705                ..
706            }))
707        ));
708    }
709
710    #[test]
711    fn emit_n_memory_forms() {
712        // mov rax, [rip + 0x1234].
713        assert_eq!(
714            asm(|a| a.emit_n(
715                InstId::Mov as u32,
716                &[RAX.as_operand(), qword_ptr_rip(0x1234).as_operand()]
717            )),
718            [0x48, 0x8B, 0x05, 0x34, 0x12, 0x00, 0x00]
719        );
720        // mov [rip + label], eax: label bound right after the instruction.
721        assert_eq!(
722            asm(|a| {
723                let label = a.get_label();
724                a.emit_n(
725                    InstId::Mov as u32,
726                    &[dword_ptr_label(label, 0).as_operand(), EAX.as_operand()],
727                );
728                a.bind_label(label);
729                a.emit_n(InstId::Ret as u32, &[]);
730            }),
731            [0x89, 0x05, 0x00, 0x00, 0x00, 0x00, 0xC3]
732        );
733        // fs prefix from the prefix setter applies to the mem operand.
734        assert_eq!(
735            asm(|a| {
736                a.fs();
737                a.emit_n(
738                    InstId::Mov as u32,
739                    &[RAX.as_operand(), qword_ptr_u64(0x40).as_operand()],
740                );
741            }),
742            [0x64, 0x48, 0x8B, 0x04, 0x25, 0x40, 0x00, 0x00, 0x00]
743        );
744    }
745
746    #[test]
747    fn emit_n_branch_forms() {
748        // Backward jmp to a bound label uses rel8 by default.
749        assert_eq!(
750            asm(|a| {
751                let label = a.get_label();
752                a.bind_label(label);
753                a.emit_n(
754                    InstId::Jmp as u32,
755                    &[Label::from_id(label.id()).as_operand()],
756                );
757            }),
758            [0xEB, 0xFE]
759        );
760        // Forward jmp to an unbound label is relaxed at finalization.
761        assert_eq!(
762            asm(|a| {
763                let label = a.get_label();
764                a.emit_n(
765                    InstId::Jmp as u32,
766                    &[Label::from_id(label.id()).as_operand()],
767                );
768                a.bind_label(label);
769            }),
770            [0xEB, 0x00]
771        );
772        // call rel32 to an unbound label, bound right after.
773        assert_eq!(
774            asm(|a| {
775                let label = a.get_label();
776                a.emit_n(
777                    InstId::Call as u32,
778                    &[Label::from_id(label.id()).as_operand()],
779                );
780                a.bind_label(label);
781            }),
782            [0xE8, 0x00, 0x00, 0x00, 0x00]
783        );
784    }
785
786    #[test]
787    fn patchable_branches_keep_the_near_form() {
788        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
789        let target = buf.get_label();
790        let site = {
791            let mut asm = Assembler::new(&mut buf);
792            asm.patchable_jmp(target)
793        };
794        buf.bind_label(target);
795
796        let code = buf.finish_patched().unwrap();
797        assert_eq!(code.data(), &[0xE9, 0, 0, 0, 0]);
798        assert_eq!(site.offset(), 1);
799        let catalog_site = code
800            .patch_catalog()
801            .sites()
802            .iter()
803            .find(|s| s.offset == site.offset())
804            .unwrap();
805        assert_eq!(catalog_site.current_target, 5);
806    }
807
808    #[test]
809    fn patchable_jcc_and_mov_can_be_rewritten_offline() {
810        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
811        let (jcc, imm_block, alt) = {
812            let mut asm = Assembler::new(&mut buf);
813            let target = asm.get_label();
814            let alt = asm.get_label();
815            let imm_block = asm.patchable_mov(EAX, imm(1i32));
816            let jcc = asm.patchable_jcc(CondCode::Z, target);
817            asm.bind_label(target);
818            asm.emit_n(InstId::Ret as u32, &[]);
819            let alt_off = {
820                asm.bind_label(alt);
821                asm.emit_n(InstId::Ret as u32, &[]);
822                asm.label_offset(alt)
823            };
824            (jcc, imm_block, alt_off)
825        };
826
827        let code = buf.finish_patched().unwrap();
828        let mut bytes = code.data().to_vec();
829        unsafe {
830            imm_block.repatch_u32(&mut bytes, 0x99).unwrap();
831            jcc.retarget(&mut bytes, alt).unwrap();
832        }
833        assert_eq!(
834            &bytes[imm_block.offset() as usize..][..4],
835            &0x99u32.to_le_bytes()
836        );
837    }
838
839    #[test]
840    fn emit_n_vex_evex_forms() {
841        // vaddps ymm1, ymm2, ymm3: AsmJit golden "C5EC58CB".
842        assert_eq!(
843            asm(|a| a.emit_n(
844                InstId::Vaddps as u32,
845                &[YMM1.as_operand(), YMM2.as_operand(), YMM3.as_operand()]
846            )),
847            [0xC5, 0xEC, 0x58, 0xCB]
848        );
849        // vmovdqu ymm0, ymm1.
850        assert_eq!(
851            asm(|a| a.emit_n(
852                InstId::Vmovdqu as u32,
853                &[YMM0.as_operand(), YMM1.as_operand()]
854            )),
855            [0xC5, 0xFE, 0x6F, 0xC1]
856        );
857        // vaddpd zmm1, zmm2, zmm3 (unmasked EVEX).
858        assert_eq!(
859            asm(|a| a.emit_n(
860                InstId::Vaddpd as u32,
861                &[ZMM1.as_operand(), ZMM2.as_operand(), ZMM3.as_operand()]
862            )),
863            [0x62, 0xF1, 0xED, 0x48, 0x58, 0xCB]
864        );
865        // {k1} mask from the prefix setter: EVEX P2 aaa=001.
866        assert_eq!(
867            asm(|a| {
868                a.k(K1);
869                a.emit_n(
870                    InstId::Vaddpd as u32,
871                    &[ZMM1.as_operand(), ZMM2.as_operand(), ZMM3.as_operand()],
872                );
873            }),
874            [0x62, 0xF1, 0xED, 0x49, 0x58, 0xCB]
875        );
876    }
877
878    #[test]
879    fn emitter_traits_match_emit_n() {
880        // The generated emitter traits (src/x86/emitter.rs) must produce the
881        // same bytes as the direct `emit_n` calls above.
882        use crate::x86::emitter::{AddEmitter, JmpEmitter, MovEmitter, VaddpsEmitter};
883
884        // mov rax, 1.
885        assert_eq!(
886            asm(|a| MovEmitter::mov(a, RAX, imm(1))),
887            [0x48, 0xC7, 0xC0, 0x01, 0x00, 0x00, 0x00]
888        );
889        // mov rax, rbx.
890        assert_eq!(asm(|a| MovEmitter::mov(a, RAX, RBX)), [0x48, 0x89, 0xD8]);
891        // add rax, rbx.
892        assert_eq!(asm(|a| AddEmitter::add(a, RAX, RBX)), [0x48, 0x01, 0xD8]);
893        // vaddps ymm1, ymm2, ymm3.
894        assert_eq!(
895            asm(|a| VaddpsEmitter::vaddps(a, YMM1, YMM2, YMM3)),
896            [0xC5, 0xEC, 0x58, 0xCB]
897        );
898        // Backward jmp to a bound label through the trait.
899        assert_eq!(
900            asm(|a| {
901                let label = a.get_label();
902                a.bind_label(label);
903                JmpEmitter::jmp(a, label);
904            }),
905            [0xEB, 0xFE]
906        );
907    }
908
909    #[test]
910    fn emitter_typed_call_sites() {
911        // Sized register constants and integer literals must work directly,
912        // producing the same bytes as the abstract forms above.
913        use crate::x86::emitter::{AddEmitter, MovEmitter, PaddwEmitter, VaddpsEmitter};
914
915        // mov rax, 42 (integer literal via Into<Imm>).
916        assert_eq!(
917            asm(|a| MovEmitter::mov(a, RAX, 42)),
918            [0x48, 0xC7, 0xC0, 0x2A, 0x00, 0x00, 0x00]
919        );
920        // mov eax, 42 (no REX prefix).
921        assert_eq!(
922            asm(|a| MovEmitter::mov(a, EAX, 42)),
923            [0xB8, 0x2A, 0x00, 0x00, 0x00]
924        );
925        // add rax, rbx.
926        assert_eq!(asm(|a| AddEmitter::add(a, RAX, RBX)), [0x48, 0x01, 0xD8]);
927        // paddw xmm0, xmm1 (legacy SSE form).
928        assert_eq!(
929            asm(|a| PaddwEmitter::paddw(a, XMM0, XMM1)),
930            [0x66, 0x0F, 0xFD, 0xC1]
931        );
932        // vaddps ymm1, ymm2, ymm3 (VEX form).
933        assert_eq!(
934            asm(|a| VaddpsEmitter::vaddps(a, YMM1, YMM2, YMM3)),
935            [0xC5, 0xEC, 0x58, 0xCB]
936        );
937    }
938
939    #[test]
940    fn emit_n_prefix_forms() {
941        // rep movsq (explicit implicit-mem form): F3 48 A5.
942        assert_eq!(
943            asm(|a| {
944                a.rep();
945                a.emit_n(
946                    InstId::Movs as u32,
947                    &[
948                        qword_ptr(RDI, 0).as_operand(),
949                        qword_ptr(RSI, 0).as_operand(),
950                    ],
951                );
952            }),
953            [0xF3, 0x48, 0xA5]
954        );
955        // lock add dword ptr [rbx], eax: F0 01 03.
956        assert_eq!(
957            asm(|a| {
958                a.lock();
959                a.emit_n(
960                    InstId::Add as u32,
961                    &[dword_ptr(RBX, 0).as_operand(), EAX.as_operand()],
962                );
963            }),
964            [0xF0, 0x01, 0x03]
965        );
966    }
967
968    #[test]
969    fn conflicting_prefix_setters_poison_without_emitting() {
970        let cases: &[fn(&mut Assembler<'_>)] = &[
971            |a| {
972                a.fs().gs();
973            },
974            |a| {
975                a.k(K1).k(K2);
976            },
977            |a| {
978                a.rd_sae().ru_sae();
979            },
980            |a| {
981                a.seg(sreg(7));
982            },
983            |a| {
984                a.k(k(8));
985            },
986            |a| {
987                a.k(K0);
988            },
989        ];
990
991        for set_prefixes in cases {
992            let mut buffer = CodeBuffer::new(Environment::new(Arch::X64));
993            let mut assembler = Assembler::new(&mut buffer);
994            set_prefixes(&mut assembler);
995            assembler.emit_n(InstId::Ret as u32, &[]);
996            assert!(assembler.buffer.error().is_some());
997            assert!(assembler.buffer.data().is_empty());
998        }
999    }
1000
1001    #[test]
1002    fn emit_n_builder_replay_matches_direct() {
1003        fn direct() -> std::vec::Vec<u8> {
1004            asm(|a| {
1005                let done = a.get_label();
1006                a.emit_n(InstId::Mov as u32, &[RAX.as_operand(), imm(1).as_operand()]);
1007                a.emit_n(InstId::Add as u32, &[RAX.as_operand(), RBX.as_operand()]);
1008                a.emit_n(
1009                    InstId::Jmp as u32,
1010                    &[Label::from_id(done.id()).as_operand()],
1011                );
1012                a.emit_n(InstId::Ret as u32, &[]);
1013                a.bind_label(done);
1014                a.emit_n(InstId::Ret as u32, &[]);
1015            })
1016        }
1017
1018        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
1019        let mut builder = Builder::new();
1020        let done = buf.get_label();
1021        builder
1022            .push_inst(Inst::with_operands(
1023                InstId::Mov as u32,
1024                &[*RAX.as_operand(), *imm(1).as_operand()],
1025            ))
1026            .unwrap();
1027        builder
1028            .push_inst(Inst::with_operands(
1029                InstId::Add as u32,
1030                &[*RAX.as_operand(), *RBX.as_operand()],
1031            ))
1032            .unwrap();
1033        builder
1034            .push_inst(Inst::with_operands(
1035                InstId::Jmp as u32,
1036                &[*Label::from_id(done.id()).as_operand()],
1037            ))
1038            .unwrap();
1039        builder
1040            .push_inst(Inst::with_operands(InstId::Ret as u32, &[]))
1041            .unwrap();
1042        builder.push_label(done);
1043        builder
1044            .push_inst(Inst::with_operands(InstId::Ret as u32, &[]))
1045            .unwrap();
1046        {
1047            let mut a = Assembler::new(&mut buf);
1048            builder.emit_into(&mut a).unwrap();
1049            assert!(a.last_error().is_none(), "{:?}", a.last_error());
1050        }
1051        assert_eq!(buf.finish().unwrap().data().to_vec(), direct());
1052    }
1053
1054    #[test]
1055    fn builder_replays_options_and_mask_register() {
1056        let direct = asm(|a| {
1057            a.k(K1).z();
1058            a.emit_n(
1059                InstId::Vaddpd as u32,
1060                &[ZMM1.as_operand(), ZMM2.as_operand(), ZMM3.as_operand()],
1061            );
1062        });
1063
1064        let mut inst = Inst::with_arch_operands(
1065            Arch::X64,
1066            InstId::Vaddpd as u32,
1067            &[*ZMM1.as_operand(), *ZMM2.as_operand(), *ZMM3.as_operand()],
1068        )
1069        .unwrap();
1070        inst.set_options(InstOptions::X86_ZMASK);
1071        inst.set_extra_reg(*K1.as_operand());
1072        let mut builder = Builder::for_arch(Arch::X64);
1073        builder.push_inst(inst).unwrap();
1074
1075        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
1076        builder.emit_into(&mut Assembler::new(&mut buf)).unwrap();
1077        assert_eq!(buf.finish().unwrap().data(), direct);
1078    }
1079
1080    /// Assembles in 32-bit mode via `emit_n`, asserting no error.
1081    fn asm32(f: impl FnOnce(&mut Assembler)) -> std::vec::Vec<u8> {
1082        let mut buf = CodeBuffer::new(Environment::new(Arch::X86));
1083        {
1084            let mut a = Assembler::new(&mut buf);
1085            f(&mut a);
1086            assert!(a.last_error().is_none(), "{:?}", a.last_error());
1087        }
1088        buf.finish().unwrap().data().to_vec()
1089    }
1090
1091    /// Assembles in 32-bit mode, expecting an error.
1092    fn asm32_err(f: impl FnOnce(&mut Assembler)) -> X86Error {
1093        let mut buf = CodeBuffer::new(Environment::new(Arch::X86));
1094        let mut a = Assembler::new(&mut buf);
1095        f(&mut a);
1096        a.last_error().expect("expected an emit error")
1097    }
1098
1099    /// Assembles in 64-bit mode, expecting an error.
1100    fn asm64_err(f: impl FnOnce(&mut Assembler)) -> X86Error {
1101        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
1102        let mut a = Assembler::new(&mut buf);
1103        f(&mut a);
1104        a.last_error().expect("expected an emit error")
1105    }
1106
1107    #[test]
1108    fn emit_n_32bit_gp_forms() {
1109        use crate::x86::emitter::{AddEmitter, MovEmitter};
1110
1111        // mov eax, ebx: no REX in 32-bit mode.
1112        assert_eq!(asm32(|a| MovEmitter::mov(a, EAX, EBX)), [0x89, 0xD8]);
1113        // mov ax, bx / mov al, bl.
1114        assert_eq!(asm32(|a| MovEmitter::mov(a, AX, BX)), [0x66, 0x89, 0xD8]);
1115        assert_eq!(asm32(|a| MovEmitter::mov(a, AL, BL)), [0x88, 0xD8]);
1116        // add ecx, 1 (imm8 form); add eax, 0x12345678 (accumulator short form).
1117        assert_eq!(
1118            asm32(|a| a.emit_n(InstId::Add as u32, &[ECX.as_operand(), imm(1).as_operand()])),
1119            [0x83, 0xC1, 0x01]
1120        );
1121        assert_eq!(
1122            asm32(|a| AddEmitter::add(a, EAX, 0x1234_5678i32)),
1123            [0x05, 0x78, 0x56, 0x34, 0x12]
1124        );
1125        // mov eax, 0x12345678 / mov ax, 0x1234 / mov cl, 0x12.
1126        assert_eq!(
1127            asm32(|a| MovEmitter::mov(a, EAX, 0x1234_5678i32)),
1128            [0xB8, 0x78, 0x56, 0x34, 0x12]
1129        );
1130        assert_eq!(
1131            asm32(|a| MovEmitter::mov(a, AX, 0x1234)),
1132            [0x66, 0xB8, 0x34, 0x12]
1133        );
1134        assert_eq!(asm32(|a| MovEmitter::mov(a, CL, 0x12)), [0xB1, 0x12]);
1135    }
1136
1137    #[test]
1138    fn emit_n_32bit_inc_dec_short_forms() {
1139        use crate::x86::emitter::{DecEmitter, IncEmitter};
1140
1141        // INC/DEC r16|r32 short forms exist only in 32-bit mode.
1142        assert_eq!(asm32(|a| IncEmitter::inc(a, EAX)), [0x40]);
1143        assert_eq!(asm32(|a| IncEmitter::inc(a, ECX)), [0x41]);
1144        assert_eq!(asm32(|a| IncEmitter::inc(a, AX)), [0x66, 0x40]);
1145        assert_eq!(asm32(|a| DecEmitter::dec(a, EDX)), [0x4A]);
1146        assert_eq!(asm32(|a| DecEmitter::dec(a, DX)), [0x66, 0x4A]);
1147        // 8-bit and memory forms are shared with 64-bit.
1148        assert_eq!(asm32(|a| IncEmitter::inc(a, AL)), [0xFE, 0xC0]);
1149        assert_eq!(
1150            asm32(|a| IncEmitter::inc(a, dword_ptr(ECX, 0))),
1151            [0xFF, 0x01]
1152        );
1153        // In 64-bit mode the same instructions use the FF /0|/1 forms.
1154        assert_eq!(
1155            asm(|a| a.emit_n(InstId::Inc as u32, &[EAX.as_operand()])),
1156            [0xFF, 0xC0]
1157        );
1158    }
1159
1160    #[test]
1161    fn emit_n_32bit_push_pop() {
1162        use crate::x86::emitter::{PopEmitter, PushEmitter};
1163
1164        assert_eq!(asm32(|a| PushEmitter::push(a, EAX)), [0x50]);
1165        assert_eq!(asm32(|a| PushEmitter::push(a, AX)), [0x66, 0x50]);
1166        assert_eq!(asm32(|a| PopEmitter::pop(a, ECX)), [0x59]);
1167        assert_eq!(
1168            asm32(|a| PushEmitter::push(a, 0x1234_5678i32)),
1169            [0x68, 0x78, 0x56, 0x34, 0x12]
1170        );
1171        assert_eq!(
1172            asm32(|a| PushEmitter::push(a, dword_ptr(ECX, 0))),
1173            [0xFF, 0x31]
1174        );
1175        assert_eq!(
1176            asm32(|a| PushEmitter::push(a, word_ptr(ECX, 0))),
1177            [0x66, 0xFF, 0x31]
1178        );
1179        // push/pop m64 is not encodable in 32-bit mode.
1180        asm32_err(|a| PushEmitter::push(a, qword_ptr(ECX, 0)));
1181        asm32_err(|a| PopEmitter::pop(a, qword_ptr(ECX, 0)));
1182    }
1183
1184    #[test]
1185    fn emit_n_32bit_far_pointer_forms() {
1186        // lcall/ljmp imm16, imm32: 32-bit only.
1187        assert_eq!(
1188            asm32(|a| a.emit_n(
1189                InstId::Lcall as u32,
1190                &[imm(0x1234).as_operand(), imm(0x1234_5678).as_operand()]
1191            )),
1192            [0x9A, 0x78, 0x56, 0x34, 0x12, 0x34, 0x12]
1193        );
1194        assert_eq!(
1195            asm32(|a| a.emit_n(
1196                InstId::Ljmp as u32,
1197                &[imm(0x10).as_operand(), imm(0x20).as_operand()]
1198            )),
1199            [0xEA, 0x20, 0x00, 0x00, 0x00, 0x10, 0x00]
1200        );
1201        // Selector above 0xFFFF is rejected.
1202        asm32_err(|a| {
1203            a.emit_n(
1204                InstId::Lcall as u32,
1205                &[imm(0x1_0000).as_operand(), imm(0).as_operand()],
1206            )
1207        });
1208        // The imm,imm form is rejected in 64-bit mode.
1209        asm64_err(|a| {
1210            a.emit_n(
1211                InstId::Lcall as u32,
1212                &[imm(0x1234).as_operand(), imm(0x1234_5678).as_operand()],
1213            )
1214        });
1215        // lcall fword [ecx]: m16:32.
1216        assert_eq!(
1217            asm32(|a| a.emit_n(InstId::Lcall as u32, &[fword_ptr(ECX, 0).as_operand()])),
1218            [0xFF, 0x19]
1219        );
1220    }
1221
1222    #[test]
1223    fn emit_n_32bit_movabs_and_xchg() {
1224        use crate::x86::emitter::{MovEmitter, XchgEmitter};
1225
1226        // mov eax, [abs] uses the moffs A1 form in 32-bit mode.
1227        assert_eq!(
1228            asm32(|a| MovEmitter::mov(a, EAX, dword_ptr_u64(0x1234_5678))),
1229            [0xA1, 0x78, 0x56, 0x34, 0x12]
1230        );
1231        // mov [abs], eax: moffs A3 form.
1232        assert_eq!(
1233            asm32(|a| MovEmitter::mov(a, dword_ptr_u64(0x1234_5678), EAX)),
1234            [0xA3, 0x78, 0x56, 0x34, 0x12]
1235        );
1236        // xchg eax, eax is encoded as 90 in 32-bit mode (generic path in 64-bit).
1237        assert_eq!(asm32(|a| XchgEmitter::xchg(a, EAX, EAX)), [0x90]);
1238        assert_eq!(
1239            asm(|a| a.emit_n(InstId::Xchg as u32, &[EAX.as_operand(), EAX.as_operand()])),
1240            [0x87, 0xC0]
1241        );
1242    }
1243
1244    #[test]
1245    fn emit_n_32bit_creg_lock_extension() {
1246        // mov eax, cr8 / mov cr8, eax use the LOCK prefix in 32-bit mode (AMD ext).
1247        assert_eq!(
1248            asm32(|a| a.emit_n(InstId::Mov as u32, &[EAX.as_operand(), CR8.as_operand()])),
1249            [0xF0, 0x0F, 0x20, 0xC0]
1250        );
1251        assert_eq!(
1252            asm32(|a| a.emit_n(InstId::Mov as u32, &[CR8.as_operand(), EAX.as_operand()])),
1253            [0xF0, 0x0F, 0x22, 0xC0]
1254        );
1255        // cr0 needs no LOCK.
1256        assert_eq!(
1257            asm32(|a| a.emit_n(InstId::Mov as u32, &[EAX.as_operand(), CR0.as_operand()])),
1258            [0x0F, 0x20, 0xC0]
1259        );
1260    }
1261
1262    #[test]
1263    fn emit_n_32bit_mode_gating() {
1264        // 64-bit registers are not available in 32-bit mode.
1265        asm32_err(|a| a.emit_n(InstId::Mov as u32, &[RAX.as_operand(), RBX.as_operand()]));
1266        asm32_err(|a| a.emit_n(InstId::Push as u32, &[RAX.as_operand()]));
1267        // Register ids above 7 are not available in 32-bit mode.
1268        asm32_err(|a| a.emit_n(InstId::Mov as u32, &[EAX.as_operand(), R8D.as_operand()]));
1269        asm32_err(|a| {
1270            a.emit_n(
1271                InstId::Paddw as u32,
1272                &[XMM0.as_operand(), XMM8.as_operand()],
1273            )
1274        });
1275        // X64-only instructions are rejected in 32-bit mode.
1276        asm32_err(|a| a.emit_n(InstId::Syscall as u32, &[]));
1277        asm32_err(|a| a.emit_n(InstId::Movsxd as u32, &[EAX.as_operand(), EBX.as_operand()]));
1278        // 64-bit addressing registers are rejected in 32-bit mode.
1279        asm32_err(|a| {
1280            a.emit_n(
1281                InstId::Mov as u32,
1282                &[EAX.as_operand(), dword_ptr(RBX, 0).as_operand()],
1283            )
1284        });
1285        // A 64-bit absolute address is not encodable in 32-bit mode.
1286        asm32_err(|a| {
1287            a.emit_n(
1288                InstId::Mov as u32,
1289                &[EAX.as_operand(), dword_ptr_u64(0x1_0000_0000).as_operand()],
1290            )
1291        });
1292    }
1293
1294    #[test]
1295    fn emit_n_32bit_string_ops() {
1296        // movsd with implicit [edi]/[esi] operands: no 67h in 32-bit mode.
1297        assert_eq!(
1298            asm32(|a| a.emit_n(
1299                InstId::Movs as u32,
1300                &[
1301                    dword_ptr(EDI, 0).as_operand(),
1302                    dword_ptr(ESI, 0).as_operand()
1303                ]
1304            )),
1305            [0xA5]
1306        );
1307        // jecxz with an explicit cx: 67h in 32-bit mode (immediate raw-disp8 form).
1308        assert_eq!(
1309            asm32(|a| a.emit_n(
1310                InstId::Jecxz as u32,
1311                &[CX.as_operand(), imm(-2).as_operand()],
1312            )),
1313            [0x67, 0xE3, 0xFE]
1314        );
1315    }
1316}