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::operands::*;
5use crate::{
6    X86Error,
7    core::{
8        arch_traits::Arch,
9        buffer::{CodeBuffer, CodeOffset, ConstantData, LabelUse},
10        globals::InstOptions,
11        operand::*,
12        patch::{PatchableBlock, PatchableSite},
13        target::Environment,
14    },
15};
16use super::instdb::InstId;
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            return unsafe { PatchableBlock::new(u32::MAX, size, arch) };
467        }
468
469        let offset = self.buffer.cur_offset();
470        let previous_error = self.buffer.error().cloned();
471        self.long();
472        MovEmitter::mov(self, dst, src);
473        if self.buffer.error().cloned() != previous_error
474            || self.buffer.cur_offset() < offset + size
475        {
476            return unsafe { PatchableBlock::new(u32::MAX, size, arch) };
477        }
478
479        let offset = self.buffer.cur_offset() - size;
480        let _ = self.buffer.record_patch_block(offset, size, 1);
481        // SAFETY: long mov-imm places a `size`-byte immediate at `offset`.
482        unsafe { PatchableBlock::new(offset, size, arch) }
483    }
484}
485
486#[derive(Debug, Clone, Copy, PartialEq, Eq)]
487pub enum CondCode {
488    O = 0x0,
489    NO = 0x1,
490    C = 0x2,
491    NC = 0x3,
492    Z = 0x4,
493    NZ = 0x5,
494    BE = 0x6,
495    A = 0x7,
496    S = 0x8,
497    NS = 0x9,
498    P = 0xa,
499
500    NP = 0xb,
501    L = 0xc,
502    GE = 0xd,
503    LE = 0xe,
504    G = 0xf,
505}
506
507impl CondCode {
508    pub const B: Self = Self::C;
509    pub const NAE: Self = Self::C;
510    pub const AE: Self = Self::NC;
511    pub const NB: Self = Self::NC;
512    pub const E: Self = Self::Z;
513    pub const NE: Self = Self::NZ;
514    pub const NA: Self = Self::BE;
515    pub const NBE: Self = Self::A;
516    pub const PO: Self = Self::NP;
517    pub const NGE: Self = Self::L;
518    pub const NL: Self = Self::GE;
519    pub const NG: Self = Self::LE;
520    pub const NLE: Self = Self::G;
521    pub const PE: Self = Self::P;
522
523    pub const fn code(self) -> u8 {
524        self as u8
525    }
526
527    pub fn invert(self) -> Self {
528        match self {
529            Self::O => Self::NO,
530            Self::NO => Self::O,
531            Self::C => Self::NC,
532            Self::NC => Self::C,
533            Self::Z => Self::NZ,
534            Self::NZ => Self::Z,
535            Self::BE => Self::A,
536            Self::A => Self::BE,
537            Self::S => Self::NS,
538            Self::NS => Self::S,
539            Self::P => Self::NP,
540            Self::NP => Self::P,
541            Self::L => Self::GE,
542            Self::GE => Self::L,
543            Self::LE => Self::G,
544            Self::G => Self::LE,
545        }
546    }
547}
548
549#[cfg(test)]
550mod tests {
551    use super::*;
552    use crate::core::builder::Builder;
553    use crate::core::inst::Inst;
554    use crate::x86::instdb::InstId;
555    use crate::x86::operands::regs::*;
556
557    /// Assembles via `emit_n`, asserting no error, and finalizes label fixups.
558    fn asm(f: impl FnOnce(&mut Assembler)) -> std::vec::Vec<u8> {
559        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
560        {
561            let mut a = Assembler::new(&mut buf);
562            f(&mut a);
563            assert!(a.last_error().is_none(), "{:?}", a.last_error());
564        }
565        buf.finish().unwrap().data().to_vec()
566    }
567
568    #[test]
569    fn emit_n_integer_forms() {
570        // mov rax, 1 — sign-extended C7 /0 form (AsmJit default, no long_form).
571        assert_eq!(
572            asm(|a| a.emit_n(InstId::Mov as u32, &[RAX.as_operand(), imm(1).as_operand()])),
573            [0x48, 0xC7, 0xC0, 0x01, 0x00, 0x00, 0x00]
574        );
575        // mov rax, rbx.
576        assert_eq!(
577            asm(|a| a.emit_n(InstId::Mov as u32, &[RAX.as_operand(), RBX.as_operand()])),
578            [0x48, 0x89, 0xD8]
579        );
580        // add rax, rbx.
581        assert_eq!(
582            asm(|a| a.emit_n(InstId::Add as u32, &[RAX.as_operand(), RBX.as_operand()])),
583            [0x48, 0x01, 0xD8]
584        );
585        // push rax / pop rax.
586        assert_eq!(
587            asm(|a| a.emit_n(InstId::Push as u32, &[RAX.as_operand()])),
588            [0x50]
589        );
590        assert_eq!(
591            asm(|a| a.emit_n(InstId::Pop as u32, &[RAX.as_operand()])),
592            [0x58]
593        );
594        // cmovz rax, rbx.
595        assert_eq!(
596            asm(|a| a.emit_n(InstId::Cmovz as u32, &[RAX.as_operand(), RBX.as_operand()])),
597            [0x48, 0x0F, 0x44, 0xC3]
598        );
599        // ret / syscall.
600        assert_eq!(asm(|a| a.emit_n(InstId::Ret as u32, &[])), [0xC3]);
601        assert_eq!(asm(|a| a.emit_n(InstId::Syscall as u32, &[])), [0x0F, 0x05]);
602        // mov rax, 0x123456789 — 64-bit immediate uses the B8 (movabs) form.
603        assert_eq!(
604            asm(|a| a.emit_n(
605                InstId::Mov as u32,
606                &[RAX.as_operand(), imm(0x1_2345_6789i64).as_operand()]
607            )),
608            [0x48, 0xB8, 0x89, 0x67, 0x45, 0x23, 0x01, 0x00, 0x00, 0x00]
609        );
610    }
611
612    #[test]
613    fn emit_n_invalid_sets_last_error() {
614        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
615        let mut a = Assembler::new(&mut buf);
616        // add rax, xmm0 — no signature matches; nothing is emitted.
617        a.emit_n(InstId::Add as u32, &[RAX.as_operand(), XMM0.as_operand()]);
618        assert!(matches!(
619            a.last_error(),
620            Some(X86Error::InvalidInstruction { .. })
621        ));
622        assert!(a.buffer.data().is_empty());
623        // Unknown instruction id.
624        a.buffer.clear();
625        a.emit_n(u32::MAX, &[]);
626        assert!(a.last_error().is_some());
627    }
628
629    #[test]
630    fn raw_invalid_symbol_id_is_rejected() {
631        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
632        let mut a = Assembler::new(&mut buf);
633        let mut operand = Operand::new();
634        operand.set_signature(OperandSignature::from(0x001A_0012));
635
636        assert_eq!(
637            a.try_emit_n(727u32, &[&operand]),
638            Err(crate::AsmError::X86(X86Error::InvalidOperand {
639                operand_index: 0,
640                reason: "symbol is not declared in this buffer",
641            }))
642        );
643        assert!(a.buffer.data().is_empty());
644
645        a.emit_n(727u32, &[&operand]);
646        assert_eq!(
647            a.buffer.error(),
648            Some(&crate::AsmError::X86(X86Error::InvalidOperand {
649                operand_index: 0,
650                reason: "symbol is not declared in this buffer",
651            }))
652        );
653        assert!(a.buffer.data().is_empty());
654    }
655
656    #[test]
657    fn emit_n_rejects_more_than_six_operands() {
658        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
659        let mut a = Assembler::new(&mut buf);
660        let ops = [RAX.as_operand(); 7];
661
662        a.emit_n(InstId::Ret as u32, &ops);
663
664        assert!(a.last_error().is_some());
665        assert!(a.buffer.data().is_empty());
666    }
667
668    #[test]
669    fn patchable_mov_emits_once_and_rejects_unsupported_operands() {
670        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
671        {
672            let mut a = Assembler::new(&mut buf);
673            a.patchable_mov(EAX, imm(42));
674            assert_eq!(a.buffer.data(), &[0xB8, 42, 0, 0, 0]);
675            assert!(a.last_error().is_none());
676
677            a.patchable_mov(RAX, imm(42));
678            assert_eq!(
679                a.buffer.data(),
680                &[0xB8, 42, 0, 0, 0, 0x48, 0xB8, 42, 0, 0, 0, 0, 0, 0, 0]
681            );
682
683            a.patchable_mov(RAX, RBX);
684            assert!(matches!(
685                a.last_error(),
686                Some(X86Error::InvalidOperand {
687                    operand_index: 1,
688                    ..
689                })
690            ));
691            assert_eq!(
692                a.buffer.data(),
693                &[0xB8, 42, 0, 0, 0, 0x48, 0xB8, 42, 0, 0, 0, 0, 0, 0, 0]
694            );
695        }
696
697        assert!(matches!(
698            buf.finish_patched(),
699            Err(crate::AsmError::X86(X86Error::InvalidOperand {
700                operand_index: 1,
701                ..
702            }))
703        ));
704    }
705
706    #[test]
707    fn emit_n_memory_forms() {
708        // mov rax, [rip + 0x1234].
709        assert_eq!(
710            asm(|a| a.emit_n(
711                InstId::Mov as u32,
712                &[RAX.as_operand(), qword_ptr_rip(0x1234).as_operand()]
713            )),
714            [0x48, 0x8B, 0x05, 0x34, 0x12, 0x00, 0x00]
715        );
716        // mov [rip + label], eax — label bound right after the instruction.
717        assert_eq!(
718            asm(|a| {
719                let label = a.get_label();
720                a.emit_n(
721                    InstId::Mov as u32,
722                    &[dword_ptr_label(label, 0).as_operand(), EAX.as_operand()],
723                );
724                a.bind_label(label);
725                a.emit_n(InstId::Ret as u32, &[]);
726            }),
727            [0x89, 0x05, 0x00, 0x00, 0x00, 0x00, 0xC3]
728        );
729        // fs prefix from the prefix setter applies to the mem operand.
730        assert_eq!(
731            asm(|a| {
732                a.fs();
733                a.emit_n(
734                    InstId::Mov as u32,
735                    &[RAX.as_operand(), qword_ptr_u64(0x40).as_operand()],
736                );
737            }),
738            [0x64, 0x48, 0x8B, 0x04, 0x25, 0x40, 0x00, 0x00, 0x00]
739        );
740    }
741
742    #[test]
743    fn emit_n_branch_forms() {
744        // Backward jmp to a bound label uses rel8 by default.
745        assert_eq!(
746            asm(|a| {
747                let label = a.get_label();
748                a.bind_label(label);
749                a.emit_n(
750                    InstId::Jmp as u32,
751                    &[Label::from_id(label.id()).as_operand()],
752                );
753            }),
754            [0xEB, 0xFE]
755        );
756        // Forward jmp to an unbound label is relaxed at finalization.
757        assert_eq!(
758            asm(|a| {
759                let label = a.get_label();
760                a.emit_n(
761                    InstId::Jmp as u32,
762                    &[Label::from_id(label.id()).as_operand()],
763                );
764                a.bind_label(label);
765            }),
766            [0xEB, 0x00]
767        );
768        // call rel32 to an unbound label, bound right after.
769        assert_eq!(
770            asm(|a| {
771                let label = a.get_label();
772                a.emit_n(
773                    InstId::Call as u32,
774                    &[Label::from_id(label.id()).as_operand()],
775                );
776                a.bind_label(label);
777            }),
778            [0xE8, 0x00, 0x00, 0x00, 0x00]
779        );
780    }
781
782    #[test]
783    fn patchable_branches_keep_the_near_form() {
784        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
785        let target = buf.get_label();
786        let site = {
787            let mut asm = Assembler::new(&mut buf);
788            asm.patchable_jmp(target)
789        };
790        buf.bind_label(target);
791
792        let code = buf.finish_patched().unwrap();
793        assert_eq!(code.data(), &[0xE9, 0, 0, 0, 0]);
794        assert_eq!(site.offset(), 1);
795        let catalog_site = code
796            .patch_catalog()
797            .sites()
798            .iter()
799            .find(|s| s.offset == site.offset())
800            .unwrap();
801        assert_eq!(catalog_site.current_target, 5);
802    }
803
804    #[test]
805    fn patchable_jcc_and_mov_can_be_rewritten_offline() {
806        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
807        let (jcc, imm_block, alt) = {
808            let mut asm = Assembler::new(&mut buf);
809            let target = asm.get_label();
810            let alt = asm.get_label();
811            let imm_block = asm.patchable_mov(EAX, imm(1i32));
812            let jcc = asm.patchable_jcc(CondCode::Z, target);
813            asm.bind_label(target);
814            asm.emit_n(InstId::Ret as u32, &[]);
815            let alt_off = {
816                asm.bind_label(alt);
817                asm.emit_n(InstId::Ret as u32, &[]);
818                asm.label_offset(alt)
819            };
820            (jcc, imm_block, alt_off)
821        };
822
823        let code = buf.finish_patched().unwrap();
824        let mut bytes = code.data().to_vec();
825        unsafe {
826            imm_block.repatch_u32(&mut bytes, 0x99).unwrap();
827            jcc.retarget(&mut bytes, alt).unwrap();
828        }
829        assert_eq!(&bytes[imm_block.offset() as usize..][..4], &0x99u32.to_le_bytes());
830    }
831
832    #[test]
833    fn emit_n_vex_evex_forms() {
834        // vaddps ymm1, ymm2, ymm3 — AsmJit golden "C5EC58CB".
835        assert_eq!(
836            asm(|a| a.emit_n(
837                InstId::Vaddps as u32,
838                &[YMM1.as_operand(), YMM2.as_operand(), YMM3.as_operand()]
839            )),
840            [0xC5, 0xEC, 0x58, 0xCB]
841        );
842        // vmovdqu ymm0, ymm1.
843        assert_eq!(
844            asm(|a| a.emit_n(
845                InstId::Vmovdqu as u32,
846                &[YMM0.as_operand(), YMM1.as_operand()]
847            )),
848            [0xC5, 0xFE, 0x6F, 0xC1]
849        );
850        // vaddpd zmm1, zmm2, zmm3 (unmasked EVEX).
851        assert_eq!(
852            asm(|a| a.emit_n(
853                InstId::Vaddpd as u32,
854                &[ZMM1.as_operand(), ZMM2.as_operand(), ZMM3.as_operand()]
855            )),
856            [0x62, 0xF1, 0xED, 0x48, 0x58, 0xCB]
857        );
858        // {k1} mask from the prefix setter: EVEX P2 aaa=001.
859        assert_eq!(
860            asm(|a| {
861                a.k(K1);
862                a.emit_n(
863                    InstId::Vaddpd as u32,
864                    &[ZMM1.as_operand(), ZMM2.as_operand(), ZMM3.as_operand()],
865                );
866            }),
867            [0x62, 0xF1, 0xED, 0x49, 0x58, 0xCB]
868        );
869    }
870
871    #[test]
872    fn emitter_traits_match_emit_n() {
873        // The generated emitter traits (src/x86/emitter.rs) must produce the
874        // same bytes as the direct `emit_n` calls above.
875        use crate::x86::emitter::{AddEmitter, JmpEmitter, MovEmitter, VaddpsEmitter};
876
877        // mov rax, 1.
878        assert_eq!(
879            asm(|a| MovEmitter::mov(a, RAX, imm(1))),
880            [0x48, 0xC7, 0xC0, 0x01, 0x00, 0x00, 0x00]
881        );
882        // mov rax, rbx.
883        assert_eq!(asm(|a| MovEmitter::mov(a, RAX, RBX)), [0x48, 0x89, 0xD8]);
884        // add rax, rbx.
885        assert_eq!(asm(|a| AddEmitter::add(a, RAX, RBX)), [0x48, 0x01, 0xD8]);
886        // vaddps ymm1, ymm2, ymm3.
887        assert_eq!(
888            asm(|a| VaddpsEmitter::vaddps(a, YMM1, YMM2, YMM3)),
889            [0xC5, 0xEC, 0x58, 0xCB]
890        );
891        // Backward jmp to a bound label through the trait.
892        assert_eq!(
893            asm(|a| {
894                let label = a.get_label();
895                a.bind_label(label);
896                JmpEmitter::jmp(a, label);
897            }),
898            [0xEB, 0xFE]
899        );
900    }
901
902    #[test]
903    fn emitter_typed_call_sites() {
904        // Sized register constants and integer literals must work directly,
905        // producing the same bytes as the abstract forms above.
906        use crate::x86::emitter::{AddEmitter, MovEmitter, PaddwEmitter, VaddpsEmitter};
907
908        // mov rax, 42 (integer literal via Into<Imm>).
909        assert_eq!(
910            asm(|a| MovEmitter::mov(a, RAX, 42)),
911            [0x48, 0xC7, 0xC0, 0x2A, 0x00, 0x00, 0x00]
912        );
913        // mov eax, 42 (no REX prefix).
914        assert_eq!(
915            asm(|a| MovEmitter::mov(a, EAX, 42)),
916            [0xB8, 0x2A, 0x00, 0x00, 0x00]
917        );
918        // add rax, rbx.
919        assert_eq!(asm(|a| AddEmitter::add(a, RAX, RBX)), [0x48, 0x01, 0xD8]);
920        // paddw xmm0, xmm1 (legacy SSE form).
921        assert_eq!(
922            asm(|a| PaddwEmitter::paddw(a, XMM0, XMM1)),
923            [0x66, 0x0F, 0xFD, 0xC1]
924        );
925        // vaddps ymm1, ymm2, ymm3 (VEX form).
926        assert_eq!(
927            asm(|a| VaddpsEmitter::vaddps(a, YMM1, YMM2, YMM3)),
928            [0xC5, 0xEC, 0x58, 0xCB]
929        );
930    }
931
932    #[test]
933    fn emit_n_prefix_forms() {
934        // rep movsq (explicit implicit-mem form): F3 48 A5.
935        assert_eq!(
936            asm(|a| {
937                a.rep();
938                a.emit_n(
939                    InstId::Movs as u32,
940                    &[
941                        qword_ptr(RDI, 0).as_operand(),
942                        qword_ptr(RSI, 0).as_operand(),
943                    ],
944                );
945            }),
946            [0xF3, 0x48, 0xA5]
947        );
948        // lock add dword ptr [rbx], eax: F0 01 03.
949        assert_eq!(
950            asm(|a| {
951                a.lock();
952                a.emit_n(
953                    InstId::Add as u32,
954                    &[dword_ptr(RBX, 0).as_operand(), EAX.as_operand()],
955                );
956            }),
957            [0xF0, 0x01, 0x03]
958        );
959    }
960
961    #[test]
962    fn conflicting_prefix_setters_poison_without_emitting() {
963        let cases: &[fn(&mut Assembler<'_>)] = &[
964            |a| {
965                a.fs().gs();
966            },
967            |a| {
968                a.k(K1).k(K2);
969            },
970            |a| {
971                a.rd_sae().ru_sae();
972            },
973            |a| {
974                a.seg(sreg(7));
975            },
976            |a| {
977                a.k(k(8));
978            },
979            |a| {
980                a.k(K0);
981            },
982        ];
983
984        for set_prefixes in cases {
985            let mut buffer = CodeBuffer::new(Environment::new(Arch::X64));
986            let mut assembler = Assembler::new(&mut buffer);
987            set_prefixes(&mut assembler);
988            assembler.emit_n(InstId::Ret as u32, &[]);
989            assert!(assembler.buffer.error().is_some());
990            assert!(assembler.buffer.data().is_empty());
991        }
992    }
993
994    #[test]
995    fn emit_n_builder_replay_matches_direct() {
996        fn direct() -> std::vec::Vec<u8> {
997            asm(|a| {
998                let done = a.get_label();
999                a.emit_n(InstId::Mov as u32, &[RAX.as_operand(), imm(1).as_operand()]);
1000                a.emit_n(InstId::Add as u32, &[RAX.as_operand(), RBX.as_operand()]);
1001                a.emit_n(
1002                    InstId::Jmp as u32,
1003                    &[Label::from_id(done.id()).as_operand()],
1004                );
1005                a.emit_n(InstId::Ret as u32, &[]);
1006                a.bind_label(done);
1007                a.emit_n(InstId::Ret as u32, &[]);
1008            })
1009        }
1010
1011        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
1012        let mut builder = Builder::new();
1013        let done = buf.get_label();
1014        builder
1015            .push_inst(Inst::with_operands(
1016                InstId::Mov as u32,
1017                &[*RAX.as_operand(), *imm(1).as_operand()],
1018            ))
1019            .unwrap();
1020        builder
1021            .push_inst(Inst::with_operands(
1022                InstId::Add as u32,
1023                &[*RAX.as_operand(), *RBX.as_operand()],
1024            ))
1025            .unwrap();
1026        builder
1027            .push_inst(Inst::with_operands(
1028                InstId::Jmp as u32,
1029                &[*Label::from_id(done.id()).as_operand()],
1030            ))
1031            .unwrap();
1032        builder
1033            .push_inst(Inst::with_operands(InstId::Ret as u32, &[]))
1034            .unwrap();
1035        builder.push_label(done);
1036        builder
1037            .push_inst(Inst::with_operands(InstId::Ret as u32, &[]))
1038            .unwrap();
1039        {
1040            let mut a = Assembler::new(&mut buf);
1041            builder.emit_into(&mut a).unwrap();
1042            assert!(a.last_error().is_none(), "{:?}", a.last_error());
1043        }
1044        assert_eq!(buf.finish().unwrap().data().to_vec(), direct());
1045    }
1046
1047    #[test]
1048    fn builder_replays_options_and_mask_register() {
1049        let direct = asm(|a| {
1050            a.k(K1).z();
1051            a.emit_n(
1052                InstId::Vaddpd as u32,
1053                &[ZMM1.as_operand(), ZMM2.as_operand(), ZMM3.as_operand()],
1054            );
1055        });
1056
1057        let mut inst = Inst::with_arch_operands(
1058            Arch::X64,
1059            InstId::Vaddpd as u32,
1060            &[*ZMM1.as_operand(), *ZMM2.as_operand(), *ZMM3.as_operand()],
1061        )
1062        .unwrap();
1063        inst.set_options(InstOptions::X86_ZMASK);
1064        inst.set_extra_reg(*K1.as_operand());
1065        let mut builder = Builder::for_arch(Arch::X64);
1066        builder.push_inst(inst).unwrap();
1067
1068        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
1069        builder.emit_into(&mut Assembler::new(&mut buf)).unwrap();
1070        assert_eq!(buf.finish().unwrap().data(), direct);
1071    }
1072
1073    /// Assembles in 32-bit mode via `emit_n`, asserting no error.
1074    fn asm32(f: impl FnOnce(&mut Assembler)) -> std::vec::Vec<u8> {
1075        let mut buf = CodeBuffer::new(Environment::new(Arch::X86));
1076        {
1077            let mut a = Assembler::new(&mut buf);
1078            f(&mut a);
1079            assert!(a.last_error().is_none(), "{:?}", a.last_error());
1080        }
1081        buf.finish().unwrap().data().to_vec()
1082    }
1083
1084    /// Assembles in 32-bit mode, expecting an error.
1085    fn asm32_err(f: impl FnOnce(&mut Assembler)) -> X86Error {
1086        let mut buf = CodeBuffer::new(Environment::new(Arch::X86));
1087        let mut a = Assembler::new(&mut buf);
1088        f(&mut a);
1089        a.last_error().expect("expected an emit error")
1090    }
1091
1092    /// Assembles in 64-bit mode, expecting an error.
1093    fn asm64_err(f: impl FnOnce(&mut Assembler)) -> X86Error {
1094        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
1095        let mut a = Assembler::new(&mut buf);
1096        f(&mut a);
1097        a.last_error().expect("expected an emit error")
1098    }
1099
1100    #[test]
1101    fn emit_n_32bit_gp_forms() {
1102        use crate::x86::emitter::{AddEmitter, MovEmitter};
1103
1104        // mov eax, ebx — no REX in 32-bit mode.
1105        assert_eq!(asm32(|a| MovEmitter::mov(a, EAX, EBX)), [0x89, 0xD8]);
1106        // mov ax, bx / mov al, bl.
1107        assert_eq!(asm32(|a| MovEmitter::mov(a, AX, BX)), [0x66, 0x89, 0xD8]);
1108        assert_eq!(asm32(|a| MovEmitter::mov(a, AL, BL)), [0x88, 0xD8]);
1109        // add ecx, 1 (imm8 form); add eax, 0x12345678 (accumulator short form).
1110        assert_eq!(
1111            asm32(|a| a.emit_n(InstId::Add as u32, &[ECX.as_operand(), imm(1).as_operand()])),
1112            [0x83, 0xC1, 0x01]
1113        );
1114        assert_eq!(
1115            asm32(|a| AddEmitter::add(a, EAX, 0x1234_5678i32)),
1116            [0x05, 0x78, 0x56, 0x34, 0x12]
1117        );
1118        // mov eax, 0x12345678 / mov ax, 0x1234 / mov cl, 0x12.
1119        assert_eq!(
1120            asm32(|a| MovEmitter::mov(a, EAX, 0x1234_5678i32)),
1121            [0xB8, 0x78, 0x56, 0x34, 0x12]
1122        );
1123        assert_eq!(
1124            asm32(|a| MovEmitter::mov(a, AX, 0x1234)),
1125            [0x66, 0xB8, 0x34, 0x12]
1126        );
1127        assert_eq!(asm32(|a| MovEmitter::mov(a, CL, 0x12)), [0xB1, 0x12]);
1128    }
1129
1130    #[test]
1131    fn emit_n_32bit_inc_dec_short_forms() {
1132        use crate::x86::emitter::{DecEmitter, IncEmitter};
1133
1134        // INC/DEC r16|r32 short forms exist only in 32-bit mode.
1135        assert_eq!(asm32(|a| IncEmitter::inc(a, EAX)), [0x40]);
1136        assert_eq!(asm32(|a| IncEmitter::inc(a, ECX)), [0x41]);
1137        assert_eq!(asm32(|a| IncEmitter::inc(a, AX)), [0x66, 0x40]);
1138        assert_eq!(asm32(|a| DecEmitter::dec(a, EDX)), [0x4A]);
1139        assert_eq!(asm32(|a| DecEmitter::dec(a, DX)), [0x66, 0x4A]);
1140        // 8-bit and memory forms are shared with 64-bit.
1141        assert_eq!(asm32(|a| IncEmitter::inc(a, AL)), [0xFE, 0xC0]);
1142        assert_eq!(
1143            asm32(|a| IncEmitter::inc(a, dword_ptr(ECX, 0))),
1144            [0xFF, 0x01]
1145        );
1146        // In 64-bit mode the same instructions use the FF /0|/1 forms.
1147        assert_eq!(
1148            asm(|a| a.emit_n(InstId::Inc as u32, &[EAX.as_operand()])),
1149            [0xFF, 0xC0]
1150        );
1151    }
1152
1153    #[test]
1154    fn emit_n_32bit_push_pop() {
1155        use crate::x86::emitter::{PopEmitter, PushEmitter};
1156
1157        assert_eq!(asm32(|a| PushEmitter::push(a, EAX)), [0x50]);
1158        assert_eq!(asm32(|a| PushEmitter::push(a, AX)), [0x66, 0x50]);
1159        assert_eq!(asm32(|a| PopEmitter::pop(a, ECX)), [0x59]);
1160        assert_eq!(
1161            asm32(|a| PushEmitter::push(a, 0x1234_5678i32)),
1162            [0x68, 0x78, 0x56, 0x34, 0x12]
1163        );
1164        assert_eq!(
1165            asm32(|a| PushEmitter::push(a, dword_ptr(ECX, 0))),
1166            [0xFF, 0x31]
1167        );
1168        assert_eq!(
1169            asm32(|a| PushEmitter::push(a, word_ptr(ECX, 0))),
1170            [0x66, 0xFF, 0x31]
1171        );
1172        // push/pop m64 is not encodable in 32-bit mode.
1173        asm32_err(|a| PushEmitter::push(a, qword_ptr(ECX, 0)));
1174        asm32_err(|a| PopEmitter::pop(a, qword_ptr(ECX, 0)));
1175    }
1176
1177    #[test]
1178    fn emit_n_32bit_far_pointer_forms() {
1179        // lcall/ljmp imm16, imm32 — 32-bit only.
1180        assert_eq!(
1181            asm32(|a| a.emit_n(
1182                InstId::Lcall as u32,
1183                &[imm(0x1234).as_operand(), imm(0x1234_5678).as_operand()]
1184            )),
1185            [0x9A, 0x78, 0x56, 0x34, 0x12, 0x34, 0x12]
1186        );
1187        assert_eq!(
1188            asm32(|a| a.emit_n(
1189                InstId::Ljmp as u32,
1190                &[imm(0x10).as_operand(), imm(0x20).as_operand()]
1191            )),
1192            [0xEA, 0x20, 0x00, 0x00, 0x00, 0x10, 0x00]
1193        );
1194        // Selector above 0xFFFF is rejected.
1195        asm32_err(|a| {
1196            a.emit_n(
1197                InstId::Lcall as u32,
1198                &[imm(0x1_0000).as_operand(), imm(0).as_operand()],
1199            )
1200        });
1201        // The imm,imm form is rejected in 64-bit mode.
1202        asm64_err(|a| {
1203            a.emit_n(
1204                InstId::Lcall as u32,
1205                &[imm(0x1234).as_operand(), imm(0x1234_5678).as_operand()],
1206            )
1207        });
1208        // lcall fword [ecx] — m16:32.
1209        assert_eq!(
1210            asm32(|a| a.emit_n(InstId::Lcall as u32, &[fword_ptr(ECX, 0).as_operand()])),
1211            [0xFF, 0x19]
1212        );
1213    }
1214
1215    #[test]
1216    fn emit_n_32bit_movabs_and_xchg() {
1217        use crate::x86::emitter::{MovEmitter, XchgEmitter};
1218
1219        // mov eax, [abs] uses the moffs A1 form in 32-bit mode.
1220        assert_eq!(
1221            asm32(|a| MovEmitter::mov(a, EAX, dword_ptr_u64(0x1234_5678))),
1222            [0xA1, 0x78, 0x56, 0x34, 0x12]
1223        );
1224        // mov [abs], eax — moffs A3 form.
1225        assert_eq!(
1226            asm32(|a| MovEmitter::mov(a, dword_ptr_u64(0x1234_5678), EAX)),
1227            [0xA3, 0x78, 0x56, 0x34, 0x12]
1228        );
1229        // xchg eax, eax is encoded as 90 in 32-bit mode (generic path in 64-bit).
1230        assert_eq!(asm32(|a| XchgEmitter::xchg(a, EAX, EAX)), [0x90]);
1231        assert_eq!(
1232            asm(|a| a.emit_n(InstId::Xchg as u32, &[EAX.as_operand(), EAX.as_operand()])),
1233            [0x87, 0xC0]
1234        );
1235    }
1236
1237    #[test]
1238    fn emit_n_32bit_creg_lock_extension() {
1239        // mov eax, cr8 / mov cr8, eax use the LOCK prefix in 32-bit mode (AMD ext).
1240        assert_eq!(
1241            asm32(|a| a.emit_n(InstId::Mov as u32, &[EAX.as_operand(), CR8.as_operand()])),
1242            [0xF0, 0x0F, 0x20, 0xC0]
1243        );
1244        assert_eq!(
1245            asm32(|a| a.emit_n(InstId::Mov as u32, &[CR8.as_operand(), EAX.as_operand()])),
1246            [0xF0, 0x0F, 0x22, 0xC0]
1247        );
1248        // cr0 needs no LOCK.
1249        assert_eq!(
1250            asm32(|a| a.emit_n(InstId::Mov as u32, &[EAX.as_operand(), CR0.as_operand()])),
1251            [0x0F, 0x20, 0xC0]
1252        );
1253    }
1254
1255    #[test]
1256    fn emit_n_32bit_mode_gating() {
1257        // 64-bit registers are not available in 32-bit mode.
1258        asm32_err(|a| a.emit_n(InstId::Mov as u32, &[RAX.as_operand(), RBX.as_operand()]));
1259        asm32_err(|a| a.emit_n(InstId::Push as u32, &[RAX.as_operand()]));
1260        // Register ids above 7 are not available in 32-bit mode.
1261        asm32_err(|a| a.emit_n(InstId::Mov as u32, &[EAX.as_operand(), R8D.as_operand()]));
1262        asm32_err(|a| {
1263            a.emit_n(
1264                InstId::Paddw as u32,
1265                &[XMM0.as_operand(), XMM8.as_operand()],
1266            )
1267        });
1268        // X64-only instructions are rejected in 32-bit mode.
1269        asm32_err(|a| a.emit_n(InstId::Syscall as u32, &[]));
1270        asm32_err(|a| a.emit_n(InstId::Movsxd as u32, &[EAX.as_operand(), EBX.as_operand()]));
1271        // 64-bit addressing registers are rejected in 32-bit mode.
1272        asm32_err(|a| {
1273            a.emit_n(
1274                InstId::Mov as u32,
1275                &[EAX.as_operand(), dword_ptr(RBX, 0).as_operand()],
1276            )
1277        });
1278        // A 64-bit absolute address is not encodable in 32-bit mode.
1279        asm32_err(|a| {
1280            a.emit_n(
1281                InstId::Mov as u32,
1282                &[EAX.as_operand(), dword_ptr_u64(0x1_0000_0000).as_operand()],
1283            )
1284        });
1285    }
1286
1287    #[test]
1288    fn emit_n_32bit_string_ops() {
1289        // movsd with implicit [edi]/[esi] operands: no 67h in 32-bit mode.
1290        assert_eq!(
1291            asm32(|a| a.emit_n(
1292                InstId::Movs as u32,
1293                &[
1294                    dword_ptr(EDI, 0).as_operand(),
1295                    dword_ptr(ESI, 0).as_operand()
1296                ]
1297            )),
1298            [0xA5]
1299        );
1300        // jecxz with an explicit cx: 67h in 32-bit mode (immediate raw-disp8 form).
1301        assert_eq!(
1302            asm32(|a| a.emit_n(
1303                InstId::Jecxz as u32,
1304                &[CX.as_operand(), imm(-2).as_operand()],
1305            )),
1306            [0x67, 0xE3, 0xFE]
1307        );
1308    }
1309}