1use crate::AsmError;
2use crate::aarch64::operands::*;
3use crate::aarch64::{Gp, Reg, instdb::*};
4use crate::core::arch_traits::Arch;
5use crate::core::buffer::CodeBuffer;
6use crate::core::buffer::{CodeOffset, Constant, LabelUse, Reloc, RelocDistance, RelocTarget};
7use crate::core::globals::CondCode;
8use crate::core::operand::*;
9use crate::core::patch::{PatchableBlock, PatchableSite};
10use crate::core::target::Environment;
11
12pub struct Assembler<'a> {
13 pub(crate) buffer: &'a mut CodeBuffer,
14 pub(crate) last_error: Option<AsmError>,
16}
17
18fn validate_raw_operand(buffer: &CodeBuffer, op: &Operand) -> bool {
19 let Some(op_type) = op.signature.try_op_type() else {
20 return false;
21 };
22
23 match op_type {
24 OperandType::None => op.signature.bits() == 0,
25 OperandType::Reg => {
26 let Some(reg_type) = op.signature.try_reg_type() else {
27 return false;
28 };
29 let Some(_) = op.signature.try_reg_group() else {
30 return false;
31 };
32 let expected = Reg::signature_of(reg_type);
33 let mask = OperandSignature::OP_TYPE_MASK
34 | OperandSignature::REG_TYPE_MASK
35 | OperandSignature::REG_GROUP_MASK
36 | OperandSignature::SIZE_MASK;
37 expected.bits() != 0
38 && op.signature.subset(mask) == expected.subset(mask)
39 && match reg_type {
40 RegType::Gp32 | RegType::Gp64 => op.id() <= 31 || op.id() == Gp::ID_ZR,
41 RegType::Vec8
42 | RegType::Vec16
43 | RegType::Vec32
44 | RegType::Vec64
45 | RegType::Vec128 => op.id() <= 31,
46 _ => false,
47 }
48 }
49 OperandType::Mem => {
50 let Some(base_type) = op.signature.try_mem_base_type() else {
51 return false;
52 };
53 let Some(index_type) = op.signature.try_mem_index_type() else {
54 return false;
55 };
56 let mem = op.as_::<BaseMem>();
57 let offset_mode = op
58 .signature
59 .get_field::<{ Mem::SIGNATURE_MEM_OFFSET_MODE_MASK }>();
60 let shift_op = op
61 .signature
62 .get_field::<{ Mem::SIGNATURE_MEM_SHIFT_OP_MASK }>();
63 let valid_base = match base_type {
64 RegType::None | RegType::LabelTag | RegType::SymTag => true,
65 RegType::Gp32 | RegType::Gp64 => mem.base_id() <= 31,
66 _ => false,
67 };
68 let valid_index = match index_type {
69 RegType::None => mem.index_id() == 0,
70 RegType::Gp32 | RegType::Gp64 => mem.index_id() <= 31,
71 _ => false,
72 };
73 valid_base
74 && valid_index
75 && offset_mode <= OffsetMode::PostIndex as u32
76 && shift_op <= 13
77 && (!mem.has_base_sym()
78 || buffer.symbol_name(Sym::from_id(mem.base_id())).is_some())
79 }
80 OperandType::Sym => buffer.symbol_name(Sym::from_id(op.id())).is_some(),
81 OperandType::Imm | OperandType::Label => true,
82 OperandType::RegList => false,
83 }
84}
85
86impl crate::core::builder::InstSink for Assembler<'_> {
87 fn arch(&self) -> Arch {
88 self.environment().arch()
89 }
90
91 fn emit_inst(&mut self, inst: &crate::core::inst::Inst) -> Result<(), AsmError> {
92 let ops = inst.operands();
93 let mut refs: smallvec::SmallVec<[&Operand; 6]> = smallvec::SmallVec::new();
94 refs.extend(ops.iter());
95 self.try_emit_n(inst.id(), &refs)
96 }
97
98 fn bind_label(&mut self, label: Label) -> Result<(), AsmError> {
99 self.try_bind_label(label)
100 }
101}
102
103pub trait LoadConstantEmitter<DST, SRC> {
104 fn load_constant(&mut self, dst: DST, src: SRC);
105}
106
107impl LoadConstantEmitter<Gp, Constant> for Assembler<'_> {
108 fn load_constant(&mut self, dst: Gp, src: Constant) {
109 let label = self.buffer.get_label_for_constant(src);
110 self.load_constant(dst, label);
111 }
112}
113
114impl LoadConstantEmitter<Gp, Label> for Assembler<'_> {
115 fn load_constant(&mut self, dst: Gp, src: Label) {
116 self.adrp(dst, src);
117 self.buffer
118 .use_label_at_offset(self.buffer.cur_offset(), src, LabelUse::A64AddAbsLo12);
119 self.add(dst, dst, imm(0));
120 }
121}
122
123impl LoadConstantEmitter<Gp, Sym> for Assembler<'_> {
124 fn load_constant(&mut self, dst: Gp, src: Sym) {
125 let Some(distance) = self.buffer.symbol_distance(src) else {
126 self.buffer.record_error(AsmError::InvalidArgument);
127 return;
128 };
129
130 if self.buffer.env().pic() {
131 self.buffer
133 .add_reloc(Reloc::Aarch64AdrGotPage21, RelocTarget::Sym(src), 0);
134 self.adrp(dst, imm(0));
135 self.buffer
136 .add_reloc(Reloc::Aarch64Ld64GotLo12Nc, RelocTarget::Sym(src), 0);
137 self.ldr(dst, ptr(dst, 0));
138 return;
139 }
140
141 match distance {
142 RelocDistance::Near => {
143 self.buffer
144 .add_reloc(Reloc::Aarch64AdrPrelPgHi21, RelocTarget::Sym(src), 0);
145 self.adrp(dst, imm(0));
146 self.buffer
147 .add_reloc(Reloc::Aarch64AddAbsLo12Nc, RelocTarget::Sym(src), 0);
148 self.add(dst, dst, imm(0));
149 }
150
151 RelocDistance::Far => {
152 let constant_start = self.buffer.get_label();
160 let constant_end = self.buffer.get_label();
161 self.ldr(dst, label_ptr(constant_start, 0));
162 self.b(constant_end);
163 self.buffer.bind_label(constant_start);
164 self.buffer.add_reloc(Reloc::Abs8, RelocTarget::Sym(src), 0);
165 self.buffer.write_u64(0);
166 self.buffer.bind_label(constant_end);
167 }
168 }
169 }
170}
171
172impl<'a> Assembler<'a> {
173 pub fn new(buffer: &'a mut CodeBuffer) -> Self {
174 if buffer.env().arch() != Arch::AArch64 {
175 return Self::poisoned(buffer, AsmError::InvalidArch);
176 }
177 Self::unchecked(buffer)
178 }
179
180 pub fn try_new(buffer: &'a mut CodeBuffer) -> Result<Self, AsmError> {
181 if buffer.env().arch() != Arch::AArch64 {
182 return Err(AsmError::InvalidArch);
183 }
184 Ok(Self::unchecked(buffer))
185 }
186
187 fn unchecked(buffer: &'a mut CodeBuffer) -> Self {
188 Self {
189 buffer,
190 last_error: None,
191 }
192 }
193
194 fn poisoned(buffer: &'a mut CodeBuffer, error: AsmError) -> Self {
195 buffer.record_error(error);
196 Self::unchecked(buffer)
197 }
198
199 pub fn environment(&self) -> &Environment {
201 self.buffer.env()
202 }
203
204 pub fn is_32bit(&self) -> bool {
206 self.buffer.env().is_32bit()
207 }
208
209 pub fn is_64bit(&self) -> bool {
211 self.buffer.env().is_64bit()
212 }
213
214 pub fn get_label(&mut self) -> Label {
215 self.buffer.get_label()
216 }
217
218 pub fn bind_label(&mut self, label: Label) {
219 if let Err(error) = self.try_bind_label(label) {
220 self.buffer.record_error(error);
221 }
222 }
223
224 pub fn try_bind_label(&mut self, label: Label) -> Result<(), AsmError> {
225 self.buffer.try_bind_label(label)
226 }
227
228 pub fn load_constant<DST, SRC>(&mut self, dst: DST, src: SRC)
245 where
246 Self: LoadConstantEmitter<DST, SRC>,
247 {
248 if self.buffer.error().is_some() {
249 return;
250 }
251 let checkpoint = self.buffer.checkpoint();
252 <Self as LoadConstantEmitter<DST, SRC>>::load_constant(self, dst, src);
253 if self.buffer.error().is_some() {
254 self.buffer.rollback(checkpoint);
255 }
256 }
257
258 #[cfg(test)]
259 fn last_error(&self) -> Option<AsmError> {
260 self.buffer.error().cloned()
261 }
262
263 pub fn emit_n(&mut self, id: impl Into<u32>, ops: &[&Operand]) {
264 if let Err(error) = self.try_emit_n(id, ops) {
265 self.buffer.record_error(error);
266 }
267 }
268
269 pub fn try_emit_n(&mut self, id: impl Into<u32>, ops: &[&Operand]) -> Result<(), AsmError> {
270 if let Some(error) = self.buffer.error().cloned() {
271 return Err(error);
272 }
273 if ops.len() > 6 || ops.iter().any(|op| !validate_raw_operand(self.buffer, op)) {
274 return Err(AsmError::InvalidOperand);
275 }
276 let id = id.into();
277 let checkpoint = self.buffer.checkpoint();
278 self.last_error = None;
279 self._emit(id, ops);
280 if let Some(error) = self.last_error.take() {
281 self.buffer.rollback(checkpoint);
282 return Err(error);
283 }
284 if let Some(error) = self.buffer.error().cloned() {
285 self.buffer.rollback(checkpoint);
286 return Err(error);
287 }
288 Ok(())
289 }
290
291 pub fn data(&self) -> &[u8] {
292 self.buffer.data()
293 }
294
295 pub fn relocs(&self) -> &[crate::core::buffer::AsmReloc] {
296 self.buffer.relocs()
297 }
298
299 pub fn error(&self) -> Option<&AsmError> {
300 self.buffer.error()
301 }
302
303 pub fn reserve_patch_block(
305 &mut self,
306 size: CodeOffset,
307 align: CodeOffset,
308 ) -> Result<PatchableBlock, AsmError> {
309 self.buffer.reserve_patch_block(size, align)
310 }
311
312 pub fn patchable_b(&mut self, label: Label) -> PatchableSite {
313 if self.buffer.error().is_some() {
314 return unsafe { PatchableSite::new(u32::MAX, LabelUse::A64Branch26, 0) };
315 }
316 let checkpoint = self.buffer.checkpoint();
317 let offset = self.buffer.cur_offset();
318 self.b(label);
319 let _ = self
320 .buffer
321 .record_label_patch_site(offset, label, LabelUse::A64Branch26);
322 if self.buffer.error().is_some() {
323 self.buffer.rollback(checkpoint);
324 return unsafe { PatchableSite::new(u32::MAX, LabelUse::A64Branch26, 0) };
325 }
326 unsafe { PatchableSite::new(offset, LabelUse::A64Branch26, 0) }
328 }
329
330 pub fn patchable_bl(&mut self, label: Label) -> PatchableSite {
331 if self.buffer.error().is_some() {
332 return unsafe { PatchableSite::new(u32::MAX, LabelUse::A64Branch26, 0) };
333 }
334 let checkpoint = self.buffer.checkpoint();
335 let offset = self.buffer.cur_offset();
336 self.bl(label);
337 let _ = self
338 .buffer
339 .record_label_patch_site(offset, label, LabelUse::A64Branch26);
340 if self.buffer.error().is_some() {
341 self.buffer.rollback(checkpoint);
342 return unsafe { PatchableSite::new(u32::MAX, LabelUse::A64Branch26, 0) };
343 }
344 unsafe { PatchableSite::new(offset, LabelUse::A64Branch26, 0) }
346 }
347
348 pub fn patchable_mov(&mut self, rd: Gp, imm: impl Into<u64>) -> PatchableBlock {
354 let arch = Arch::AArch64;
355 let value = imm.into();
356 if self.buffer.error().is_some() {
357 return unsafe { PatchableBlock::new(u32::MAX, 4, arch) };
358 }
359 let checkpoint = self.buffer.checkpoint();
360 let is_64 = rd.is_gp64();
361 let encoded = encode_patchable_mov_imm(rd.id(), is_64, value);
362 let offset = self.buffer.cur_offset();
363 for chunk in encoded.chunks_exact(4) {
364 let word = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
365 self.buffer.write_u32(word);
366 }
367 let size = encoded.len() as CodeOffset;
368 let _ = self.buffer.record_patch_block(offset, size, 4);
369 if self.buffer.error().is_some() {
370 self.buffer.rollback(checkpoint);
371 return unsafe { PatchableBlock::new(u32::MAX, size.max(4), arch) };
372 }
373 unsafe { PatchableBlock::new(offset, size, arch) }
375 }
376}
377
378pub fn encode_patchable_mov_imm(rd: u32, is_64bit: bool, value: u64) -> smallvec::SmallVec<[u8; 16]> {
383 let rd = rd & 0x1f;
384 let mut out = smallvec::SmallVec::new();
385 if is_64bit {
386 const MOVZ: u32 = 0b11010010100000000000000000000000;
387 const MOVK: u32 = 0b11110010100000000000000000000000;
388 let words = [
389 MOVZ | (0 << 21) | (((value as u32) & 0xFFFF) << 5) | rd,
390 MOVK | (1 << 21) | ((((value >> 16) as u32) & 0xFFFF) << 5) | rd,
391 MOVK | (2 << 21) | ((((value >> 32) as u32) & 0xFFFF) << 5) | rd,
392 MOVK | (3 << 21) | ((((value >> 48) as u32) & 0xFFFF) << 5) | rd,
393 ];
394 for w in words {
395 out.extend_from_slice(&w.to_le_bytes());
396 }
397 } else {
398 const MOVZ: u32 = 0b01010010100000000000000000000000;
399 const MOVK: u32 = 0b01110010100000000000000000000000;
400 let value = value as u32;
401 let words = [
402 MOVZ | (0 << 21) | ((value & 0xFFFF) << 5) | rd,
403 MOVK | (1 << 21) | (((value >> 16) & 0xFFFF) << 5) | rd,
404 ];
405 for w in words {
406 out.extend_from_slice(&w.to_le_bytes());
407 }
408 }
409 out
410}
411
412impl InstId {
413 pub const ARM_COND: u32 = 0x78000000;
414 pub const REAL_ID: u32 = 65535;
415 pub const fn with_cc(self, cond: CondCode) -> u32 {
416 let x = self as u32;
417 x | (cond as u32) << Self::ARM_COND.trailing_zeros()
418 }
419
420 pub const fn extract_cc(inst: u32) -> CondCode {
421 unsafe {
422 core::mem::transmute(((inst & Self::ARM_COND) >> Self::ARM_COND.trailing_zeros()) as u8)
423 }
424 }
425
426 pub const fn extract_real_id(inst: u32) -> u32 {
427 inst & Self::REAL_ID
428 }
429}
430
431pub const BLE: u32 = InstId::B.with_cc(CondCode::GE);
432
433impl From<InstId> for u32 {
434 fn from(inst: InstId) -> Self {
435 inst as u32
436 }
437}
438
439pub(super) use crate::aarch64::encoder::OffsetType;
441pub use crate::aarch64::encoder::{
442 LogicalImm, count_zero_half_words_64, encode_fp64_to_imm8, encode_logical_imm, is_fp16_imm8,
443 is_fp32_imm8, is_fp64_imm8,
444};
445
446#[cfg(test)]
447mod tests {
448 use super::*;
449 use crate::aarch64::operands::regs;
450 use crate::core::buffer::RelocDistance;
451
452 #[test]
453 fn pic_symbol_load_uses_only_the_got_sequence() {
454 for distance in [RelocDistance::Near, RelocDistance::Far] {
455 let mut environment = Environment::new(Arch::AArch64);
456 environment.set_pic(true);
457 let mut buffer = CodeBuffer::new(environment);
458 let symbol = buffer.extern_sym("external", distance);
459
460 {
461 let mut asm = Assembler::new(&mut buffer);
462 asm.load_constant(regs::x(0), symbol);
463 assert_eq!(
464 asm.buffer.data(),
465 &[0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x40, 0xF9]
466 );
467 assert_eq!(asm.buffer.relocs().len(), 2);
468 assert_eq!(asm.buffer.relocs()[0].offset, 0);
469 assert_eq!(asm.buffer.relocs()[0].kind, Reloc::Aarch64AdrGotPage21);
470 assert_eq!(asm.buffer.relocs()[0].target, RelocTarget::Sym(symbol));
471 assert_eq!(asm.buffer.relocs()[0].addend, 0);
472 assert_eq!(asm.buffer.relocs()[1].offset, 4);
473 assert_eq!(asm.buffer.relocs()[1].kind, Reloc::Aarch64Ld64GotLo12Nc);
474 assert_eq!(asm.buffer.relocs()[1].target, RelocTarget::Sym(symbol));
475 assert_eq!(asm.buffer.relocs()[1].addend, 0);
476 }
477 }
478 }
479
480 #[test]
481 fn invalid_raw_instruction_ids_are_rejected() {
482 let mut buffer = CodeBuffer::new(Environment::new(Arch::AArch64));
483 let mut asm = Assembler::new(&mut buffer);
484
485 asm.emit_n(0u32, &[]);
486 assert_eq!(asm.last_error(), Some(AsmError::InvalidInstruction));
487 assert!(asm.buffer.data().is_empty());
488
489 asm.buffer.clear();
490 asm.emit_n(u32::MAX, &[]);
491 assert_eq!(asm.last_error(), Some(AsmError::InvalidInstruction));
492 assert!(asm.buffer.data().is_empty());
493
494 asm.buffer.clear();
495 asm.emit_n(InstId::Add as u32 | (1 << 16), &[]);
496 assert_eq!(asm.last_error(), Some(AsmError::InvalidInstruction));
497 assert!(asm.buffer.data().is_empty());
498 }
499
500 #[test]
501 fn baseline_rejects_optional_features_before_writing() {
502 let mut buffer = CodeBuffer::new(Environment::baseline(Arch::AArch64));
503 let mut asm = Assembler::new(&mut buffer);
504
505 let error = asm
506 .try_emit_n(
507 InstId::Crc32b,
508 &[
509 regs::w(0).as_operand(),
510 regs::w(1).as_operand(),
511 regs::w(2).as_operand(),
512 ],
513 )
514 .unwrap_err();
515
516 assert!(
517 matches!(error, AsmError::MissingCpuFeature { feature } if feature.contains("crc32b") && feature.contains("CRC32"))
518 );
519 assert!(asm.buffer.data().is_empty());
520 }
521
522 #[test]
523 fn optional_features_are_enabled_by_default() {
524 let mut buffer = CodeBuffer::new(Environment::new(Arch::AArch64));
525 let mut asm = Assembler::new(&mut buffer);
526
527 asm.try_emit_n(
528 InstId::Crc32b,
529 &[
530 regs::w(0).as_operand(),
531 regs::w(1).as_operand(),
532 regs::w(2).as_operand(),
533 ],
534 )
535 .unwrap();
536
537 assert_eq!(asm.buffer.data().len(), 4);
538 }
539
540 #[test]
541 fn mixed_instruction_checks_the_selected_form() {
542 let mut environment = Environment::baseline(Arch::AArch64);
543 environment.set_aarch64_feature(crate::aarch64::CpuFeature::Asimd, true);
544 let mut buffer = CodeBuffer::new(environment);
545 let mut asm = Assembler::new(&mut buffer);
546
547 asm.try_emit_n(
548 InstId::Fadd_v,
549 &[
550 regs::s(0).as_operand(),
551 regs::s(1).as_operand(),
552 regs::s(2).as_operand(),
553 ],
554 )
555 .unwrap();
556 let accepted_len = asm.buffer.data().len();
557
558 let error = asm
559 .try_emit_n(
560 InstId::Fadd_v,
561 &[
562 regs::h(0).as_operand(),
563 regs::h(1).as_operand(),
564 regs::h(2).as_operand(),
565 ],
566 )
567 .unwrap_err();
568
569 assert!(
570 matches!(error, AsmError::MissingCpuFeature { feature } if feature.contains("fadd Hd") && feature.contains("FP16"))
571 );
572 assert_eq!(asm.buffer.data().len(), accepted_len);
573 }
574
575 #[test]
576 fn raw_emission_rejects_malformed_or_extra_operands_without_mutation() {
577 let mut buffer = CodeBuffer::new(Environment::new(Arch::AArch64));
578 let mut asm = Assembler::new(&mut buffer);
579 let malformed = Operand {
580 signature: OperandSignature::from(7),
581 base_id: 0,
582 data: [0; 2],
583 };
584 let invalid_register = Operand {
585 signature: OperandSignature::from(
586 OperandType::Reg as u32 | (31 << OperandSignature::REG_TYPE_SHIFT),
587 ),
588 base_id: 0,
589 data: [0; 2],
590 };
591 let none = Operand::new();
592 let mut invalid_memory = ptr(regs::x(0), 0);
593 invalid_memory.set_base_id(64);
594 let mut invalid_mode = ptr(regs::x(0), 0);
595 invalid_mode
596 .signature
597 .set_field::<{ Mem::SIGNATURE_MEM_OFFSET_MODE_MASK }>(3);
598
599 assert_eq!(
600 asm.try_emit_n(InstId::Add as u32, &[&malformed]),
601 Err(AsmError::InvalidOperand)
602 );
603 assert_eq!(
604 asm.try_emit_n(InstId::Add as u32, &[&invalid_register]),
605 Err(AsmError::InvalidOperand)
606 );
607 assert_eq!(
608 asm.try_emit_n(
609 InstId::Ldr as u32,
610 &[regs::x(1).as_operand(), invalid_memory.as_operand()],
611 ),
612 Err(AsmError::InvalidOperand)
613 );
614 assert_eq!(
615 asm.try_emit_n(
616 InstId::Ldr as u32,
617 &[regs::x(1).as_operand(), invalid_mode.as_operand()],
618 ),
619 Err(AsmError::InvalidOperand)
620 );
621 assert_eq!(
622 asm.try_emit_n(
623 InstId::Add as u32,
624 &[&none, &none, &none, &none, &none, &none, &none],
625 ),
626 Err(AsmError::InvalidOperand)
627 );
628 assert!(asm.buffer.error().is_none());
629 assert!(asm.buffer.data().is_empty());
630 }
631
632 #[test]
633 fn raw_label_registration_error_rolls_back_emission() {
634 let mut buffer = CodeBuffer::new(Environment::new(Arch::AArch64));
635 let mut asm = Assembler::new(&mut buffer);
636 let invalid_label = Label::from_id(0);
637
638 assert_eq!(
639 asm.try_emit_n(InstId::B as u32, &[invalid_label.as_operand()]),
640 Err(AsmError::InvalidArgument)
641 );
642 assert_eq!(asm.buffer.error(), Some(&AsmError::InvalidArgument));
643 assert!(asm.buffer.data().is_empty());
644 }
645
646 #[test]
647 fn failed_constant_load_rolls_back_the_whole_sequence() {
648 let mut buffer = CodeBuffer::new(Environment::new(Arch::AArch64));
649 let mut asm = Assembler::new(&mut buffer);
650
651 asm.load_constant(regs::x(0), Label::from_id(0));
652
653 assert_eq!(asm.buffer.error(), Some(&AsmError::InvalidArgument));
654 assert!(asm.buffer.data().is_empty());
655 }
656
657 #[test]
658 fn invalid_symbol_load_sets_error_without_mutation() {
659 let mut buffer = CodeBuffer::new(Environment::new(Arch::AArch64));
660 let mut asm = Assembler::new(&mut buffer);
661
662 asm.load_constant(regs::x(0), Sym::from_id(u32::MAX));
663
664 assert_eq!(asm.buffer.error(), Some(&AsmError::InvalidArgument));
665 assert!(asm.buffer.data().is_empty());
666 }
667
668 #[test]
669 fn patchable_b_and_mov_handles_work_offline() {
670 let mut buffer = CodeBuffer::new(Environment::new(Arch::AArch64));
671 let (site, mov, alt) = {
672 let mut asm = Assembler::new(&mut buffer);
673 let target = asm.get_label();
674 let alt = asm.get_label();
675 let mov = asm.patchable_mov(regs::x(0), 0x1111_2222_3333_4444u64);
676 let site = asm.patchable_b(target);
677 asm.bind_label(target);
678 asm.ret(regs::x(30));
679 asm.bind_label(alt);
680 asm.ret(regs::x(30));
681 let alt_off = asm.buffer.label_offset(alt);
682 (site, mov, alt_off)
683 };
684
685 let code = buffer.finish_patched().unwrap();
686 assert_eq!(mov.size(), 16);
687 let mut bytes = code.data().to_vec();
688 let rewritten = encode_patchable_mov_imm(0, true, 0xAAAA_BBBB_CCCC_DDDD);
689 unsafe {
690 mov.rewrite(&mut bytes, &rewritten).unwrap();
691 site.retarget(&mut bytes, alt).unwrap();
692 }
693 assert_eq!(&bytes[mov.offset() as usize..][..16], rewritten.as_slice());
694 }
695}